From: Radek Czajka Date: Thu, 29 Mar 2012 09:59:12 +0000 (+0200) Subject: Merge branch 'custompdf' X-Git-Url: https://git.mdrn.pl/wolnelektury.git/commitdiff_plain/5eeb9dace6068f83e2b70b5222cfab0c0a5e71eb?hp=a6554d68f532f4d15b75f75acd0d85d2b3b7ace2 Merge branch 'custompdf' Conflicts: wolnelektury/settings/__init__.py --- diff --git a/apps/ajaxable/templates/ajaxable/form.html b/apps/ajaxable/templates/ajaxable/form.html index 84e86e15f..38113dbb6 100755 --- a/apps/ajaxable/templates/ajaxable/form.html +++ b/apps/ajaxable/templates/ajaxable/form.html @@ -1,8 +1,14 @@ {% load i18n %} +

{{ title }}

+{% csrf_token %} +{% if honeypot %} + {% load honeypot %} + {% render_honeypot_field %} +{% endif %}
    {{ form.as_ul }} diff --git a/apps/ajaxable/utils.py b/apps/ajaxable/utils.py index 02e87671e..4ae6e869e 100755 --- a/apps/ajaxable/utils.py +++ b/apps/ajaxable/utils.py @@ -10,6 +10,7 @@ from django.utils.http import urlquote_plus from django.utils import simplejson from django.utils.translation import ugettext_lazy as _ from django.views.decorators.vary import vary_on_headers +from honeypot.decorators import verify_honeypot_value class LazyEncoder(simplejson.JSONEncoder): @@ -76,6 +77,7 @@ class AjaxableFormView(object): formname = "form" form_prefix = None full_template = "ajaxable/form_on_page.html" + honeypot = False @method_decorator(vary_on_headers('X-Requested-With')) def __call__(self, request, *args, **kwargs): @@ -86,6 +88,11 @@ class AjaxableFormView(object): form_kwargs['prefix'] = self.form_prefix if request.method == "POST": + if self.honeypot: + response = verify_honeypot_value(request, None) + if response: + return response + # do I need to be logged in? if self.POST_login and not request.user.is_authenticated(): return require_login(request) @@ -94,14 +101,16 @@ class AjaxableFormView(object): form = self.form_class(*form_args, **form_kwargs) if form.is_valid(): add_args = self.success(form, request) - redirect = request.GET.get('next') - if not request.is_ajax() and redirect: - return HttpResponseRedirect(urlquote_plus( - redirect, safe='/?=&')) - response_data = {'success': True, - 'message': self.success_message, 'redirect': redirect} + response_data = { + 'success': True, + 'message': self.success_message, + 'redirect': request.GET.get('next') + } if add_args: response_data.update(add_args) + if not request.is_ajax() and response_data['redirect']: + return HttpResponseRedirect(urlquote_plus( + response_data['redirect'], safe='/?=&')) elif request.is_ajax(): # Form was sent with errors. Send them back. if self.form_prefix: @@ -136,6 +145,7 @@ class AjaxableFormView(object): context = { self.formname: form, "title": title, + "honeypot": self.honeypot, "placeholdize": self.placeholdize, "submit": self.submit, "response_data": response_data, diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 9115b2ddb..c4ddbcbf5 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -6,6 +6,10 @@ from django import forms from django.utils.translation import ugettext_lazy as _ from catalogue.models import Book +from waiter.models import WaitedFile +from django.core.exceptions import ValidationError +from catalogue.utils import get_customized_pdf_path +from catalogue.tasks import build_custom_pdf class BookImportForm(forms.Form): @@ -38,51 +42,61 @@ class DownloadFormatsForm(forms.Form): super(DownloadFormatsForm, self).__init__(*args, **kwargs) -PDF_PAGE_SIZES = ( - ('a4paper', _('A4')), - ('a5paper', _('A5')), -) - - -PDF_LEADINGS = ( - ('', _('Normal leading')), - ('onehalfleading', _('One and a half leading')), - ('doubleleading', _('Double leading')), +CUSTOMIZATION_FLAGS = ( + ('nofootnotes', _("Don't show footnotes")), + ('nothemes', _("Don't disply themes")), + ('nowlfont', _("Don't use our custom font")), ) - -PDF_FONT_SIZES = ( - ('11pt', _('Default')), - ('13pt', _('Big')) +CUSTOMIZATION_OPTIONS = ( + ('leading', _("Leading"), ( + ('defaultleading', _('Normal leading')), + ('onehalfleading', _('One and a half leading')), + ('doubleleading', _('Double leading')), + )), + ('fontsize', _("Font size"), ( + ('11pt', _('Default')), + ('13pt', _('Big')) + )), +# ('pagesize', _("Paper size"), ( +# ('a4paper', _('A4')), +# ('a5paper', _('A5')), +# )), ) class CustomPDFForm(forms.Form): - nofootnotes = forms.BooleanField(required=False, label=_("Don't show footnotes")) - nothemes = forms.BooleanField(required=False, label=_("Don't disply themes")) - nowlfont = forms.BooleanField(required=False, label=_("Don't use our custom font")) - ## pagesize = forms.ChoiceField(PDF_PAGE_SIZES, required=True, label=_("Paper size")) - leading = forms.ChoiceField(PDF_LEADINGS, required=False, label=_("Leading")) - fontsize = forms.ChoiceField(PDF_FONT_SIZES, required=True, label=_("Font size")) + def __init__(self, book, *args, **kwargs): + super(CustomPDFForm, self).__init__(*args, **kwargs) + self.book = book + for name, label in CUSTOMIZATION_FLAGS: + self.fields[name] = forms.BooleanField(required=False, label=label) + for name, label, choices in CUSTOMIZATION_OPTIONS: + self.fields[name] = forms.ChoiceField(choices, label=label) + + def clean(self): + self.cleaned_data['cust'] = self.customizations + self.cleaned_data['path'] = get_customized_pdf_path(self.book, + self.cleaned_data['cust']) + if not WaitedFile.can_order(self.cleaned_data['path']): + raise ValidationError(_('Queue is full. Please try again later.')) + return self.cleaned_data @property def customizations(self): c = [] - if self.cleaned_data['nofootnotes']: - c.append('nofootnotes') - - if self.cleaned_data['nothemes']: - c.append('nothemes') - - if self.cleaned_data['nowlfont']: - c.append('nowlfont') - - ## c.append(self.cleaned_data['pagesize']) - c.append(self.cleaned_data['fontsize']) - - if self.cleaned_data['leading']: - c.append(self.cleaned_data['leading']) - + for name, label in CUSTOMIZATION_FLAGS: + if self.cleaned_data.get(name): + c.append(name) + for name, label, choices in CUSTOMIZATION_OPTIONS: + c.append(self.cleaned_data[name]) c.sort() - return c + def save(self, *args, **kwargs): + url = WaitedFile.order(self.cleaned_data['path'], + lambda p: build_custom_pdf.delay(self.book.id, + self.cleaned_data['cust'], p), + self.book.pretty_title() + ) + #return redirect(url) + return {"redirect": url} diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index ea09c048f..ba1a5d203 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -5,10 +5,9 @@ from collections import namedtuple from django.db import models -from django.db.models import permalink, Q +from django.db.models import permalink import django.dispatch from django.core.cache import get_cache -from django.core.files.storage import DefaultStorage from django.utils.translation import ugettext_lazy as _ from django.contrib.auth.models import User from django.template.loader import render_to_string @@ -16,7 +15,7 @@ from django.utils.datastructures import SortedDict from django.utils.safestring import mark_safe from django.utils.translation import get_language from django.core.urlresolvers import reverse -from django.db.models.signals import post_save, m2m_changed, pre_delete, post_delete +from django.db.models.signals import post_save, pre_delete, post_delete import jsonfield from django.conf import settings @@ -25,11 +24,8 @@ from newtagging.models import TagBase, tags_updated from newtagging import managers from catalogue.fields import JSONField, OverwritingFileField from catalogue.utils import create_zip, split_tags, truncate_html_words -from catalogue.tasks import touch_tag, index_book -from shutil import copy -from glob import glob +from catalogue import tasks import re -from os import path import search @@ -216,32 +212,6 @@ def book_upload_path(ext=None, maxlen=100): return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen) -def customizations_hash(customizations): - customizations.sort() - return hash(tuple(customizations)) - - -def get_customized_pdf_path(book, customizations): - """ - Returns a MEDIA_ROOT relative path for a customized pdf. The name will contain a hash of customization options. - """ - h = customizations_hash(customizations) - pdf_name = '%s-custom-%s' % (book.slug, h) - pdf_file = get_dynamic_path(None, pdf_name, ext='pdf') - - return pdf_file - - -def get_existing_customized_pdf(book): - """ - Returns a list of paths to generated customized pdf of a book - """ - pdf_glob = '%s-custom-' % (book.slug,) - pdf_glob = get_dynamic_path(None, pdf_glob, ext='pdf') - pdf_glob = re.sub(r"[.]([a-z0-9]+)$", "*.\\1", pdf_glob) - return glob(path.join(settings.MEDIA_ROOT, pdf_glob)) - - class BookMedia(models.Model): FileFormat = namedtuple("FileFormat", "name ext") formats = SortedDict([ @@ -274,7 +244,7 @@ class BookMedia(models.Model): try: old = BookMedia.objects.get(pk=self.pk) - except BookMedia.DoesNotExist, e: + except BookMedia.DoesNotExist: old = None else: # if name changed, change the file name, too @@ -428,18 +398,18 @@ class Book(models.Model): book_tag.save() return book_tag - def has_media(self, type): - if type in Book.formats: - return bool(getattr(self, "%s_file" % type)) + def has_media(self, type_): + if type_ in Book.formats: + return bool(getattr(self, "%s_file" % type_)) else: - return self.media.filter(type=type).exists() + return self.media.filter(type=type_).exists() - def get_media(self, type): - if self.has_media(type): - if type in Book.formats: - return getattr(self, "%s_file" % type) + def get_media(self, type_): + if self.has_media(type_): + if type_ in Book.formats: + return getattr(self, "%s_file" % type_) else: - return self.media.filter(type=type) + return self.media.filter(type=type_) else: return None @@ -504,69 +474,6 @@ class Book(models.Model): cover.save(imgstr, 'png') self.cover.save(None, ContentFile(imgstr.getvalue())) - def build_pdf(self, customizations=None, file_name=None): - """ (Re)builds the pdf file. - customizations - customizations which are passed to LaTeX class file. - file_name - save the pdf file under a different name and DO NOT save it in db. - """ - from os import unlink - from django.core.files import File - from catalogue.utils import remove_zip - - pdf = self.wldocument().as_pdf(customizations=customizations) - - if file_name is None: - # we'd like to be sure not to overwrite changes happening while - # (timely) pdf generation is taking place (async celery scenario) - current_self = Book.objects.get(id=self.id) - current_self.pdf_file.save('%s.pdf' % self.slug, - File(open(pdf.get_filename()))) - self.pdf_file = current_self.pdf_file - - # remove cached downloadables - remove_zip(settings.ALL_PDF_ZIP) - - for customized_pdf in get_existing_customized_pdf(self): - unlink(customized_pdf) - else: - print "saving %s" % file_name - print "to: %s" % DefaultStorage().path(file_name) - DefaultStorage().save(file_name, File(open(pdf.get_filename()))) - - def build_mobi(self): - """ (Re)builds the MOBI file. - - """ - from django.core.files import File - from catalogue.utils import remove_zip - - mobi = self.wldocument().as_mobi() - - self.mobi_file.save('%s.mobi' % self.slug, File(open(mobi.get_filename()))) - - # remove zip with all mobi files - remove_zip(settings.ALL_MOBI_ZIP) - - def build_epub(self): - """(Re)builds the epub file.""" - from django.core.files import File - from catalogue.utils import remove_zip - - epub = self.wldocument().as_epub() - - self.epub_file.save('%s.epub' % self.slug, - File(open(epub.get_filename()))) - - # remove zip package with all epub files - remove_zip(settings.ALL_EPUB_ZIP) - - def build_txt(self): - from django.core.files.base import ContentFile - - text = self.wldocument().as_text() - self.txt_file.save('%s.txt' % self.slug, ContentFile(text.get_string())) - - def build_html(self): from django.core.files.base import ContentFile from slughifi import slughifi @@ -624,6 +531,16 @@ class Book(models.Model): return True return False + # Thin wrappers for builder tasks + def build_pdf(self, *args, **kwargs): + return tasks.build_pdf.delay(self.pk, *args, **kwargs) + def build_epub(self, *args, **kwargs): + return tasks.build_epub.delay(self.pk, *args, **kwargs) + def build_mobi(self, *args, **kwargs): + return tasks.build_mobi.delay(self.pk, *args, **kwargs) + def build_txt(self, *args, **kwargs): + return tasks.build_txt.delay(self.pk, *args, **kwargs) + @staticmethod def zip_format(format_): def pretty_file_name(book): @@ -636,15 +553,13 @@ class Book(models.Model): books = Book.objects.filter(parent=None).exclude(**{field_name: ""}) paths = [(pretty_file_name(b), getattr(b, field_name).path) for b in books] - result = create_zip.delay(paths, + return create_zip(paths, getattr(settings, "ALL_%s_ZIP" % format_.upper())) - return result.wait() def zip_audiobooks(self, format_): bm = BookMedia.objects.filter(book=self, type=format_) paths = map(lambda bm: (None, bm.file.path), bm) - result = create_zip.delay(paths, "%s_%s" % (self.slug, format_)) - return result.wait() + return create_zip(paths, "%s_%s" % (self.slug, format_)) def search_index(self, book_info=None, reuse_index=False, index_tags=True): if reuse_index: @@ -680,8 +595,6 @@ class Book(models.Model): def from_text_and_meta(cls, raw_file, book_info, overwrite=False, build_epub=True, build_txt=True, build_pdf=True, build_mobi=True, search_index=True, search_index_tags=True, search_index_reuse=False): - import re - from sortify import sortify # check for parts before we do anything children = [] @@ -689,7 +602,7 @@ class Book(models.Model): for part_url in book_info.parts: try: children.append(Book.objects.get(slug=part_url.slug)) - except Book.DoesNotExist, e: + except Book.DoesNotExist: raise Book.DoesNotExist(_('Book "%s" does not exist.') % part_url.slug) @@ -767,7 +680,7 @@ class Book(models.Model): book_descendants += list(child_book.children.all()) for tag in descendants_tags: - touch_tag(tag) + tasks.touch_tag(tag) book.save() @@ -808,6 +721,13 @@ class Book(models.Model): type(self).objects.filter(pk=self.pk).update(_related_info=rel) return rel + def related_themes(self): + theme_counter = self.theme_counter + book_themes = Tag.objects.filter(pk__in=theme_counter.keys()) + for tag in book_themes: + tag.count = theme_counter[tag.pk] + return book_themes + def reset_tag_counter(self): if self.id is None: return @@ -1055,7 +975,7 @@ def _tags_updated_handler(sender, affected_tags, **kwargs): # reset tag global counter # we want Tag.changed_at updated for API to know the tag was touched for tag in affected_tags: - touch_tag(tag) + tasks.touch_tag(tag) # if book tags changed, reset book tag counter if isinstance(sender, Book) and \ diff --git a/apps/catalogue/tasks.py b/apps/catalogue/tasks.py index e1ff9151a..6d19ee18a 100755 --- a/apps/catalogue/tasks.py +++ b/apps/catalogue/tasks.py @@ -3,11 +3,12 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from datetime import datetime -from celery.task import task -import catalogue.models from traceback import print_exc +from celery.task import task +from django.conf import settings -@task + +# TODO: move to model? def touch_tag(tag): update_dict = { 'book_count': tag.get_count(), @@ -19,9 +20,97 @@ def touch_tag(tag): @task def index_book(book_id, book_info=None): + from catalogue.models import Book try: - return catalogue.models.Book.objects.get(id=book_id).search_index(book_info) + return Book.objects.get(id=book_id).search_index(book_info) except Exception, e: print "Exception during index: %s" % e print_exc() raise e + + +@task(ignore_result=True) +def build_txt(book_id): + """(Re)builds the TXT file for a book.""" + from django.core.files.base import ContentFile + from catalogue.models import Book + + text = Book.objects.get(pk=book_id).wldocument().as_text() + + # Save the file in new instance. Building TXT takes time and we don't want + # to overwrite any interim changes. + book = Book.objects.get(id=book_id) + book.txt_file.save('%s.txt' % book.slug, ContentFile(text.get_string())) + + +@task(ignore_result=True, rate_limit=settings.CATALOGUE_PDF_RATE_LIMIT) +def build_pdf(book_id): + """(Re)builds the pdf file for a book.""" + from django.core.files import File + from catalogue.models import Book + from catalogue.utils import remove_zip + from waiter.utils import clear_cache + + pdf = Book.objects.get(pk=book_id).wldocument().as_pdf( + morefloats=settings.LIBRARIAN_PDF_MOREFLOATS) + + # Save the file in new instance. Building PDF takes time and we don't want + # to overwrite any interim changes. + book = Book.objects.get(id=book_id) + book.pdf_file.save('%s.pdf' % book.slug, + File(open(pdf.get_filename()))) + + # Remove cached downloadables + remove_zip(settings.ALL_PDF_ZIP) + clear_cache(book.slug) + + +@task(ignore_result=True, rate_limit=settings.CATALOGUE_EPUB_RATE_LIMIT) +def build_epub(book_id): + """(Re)builds the EPUB file for a book.""" + from django.core.files import File + from catalogue.models import Book + from catalogue.utils import remove_zip + + epub = Book.objects.get(pk=book_id).wldocument().as_epub() + # Save the file in new instance. Building MOBI takes time and we don't want + # to overwrite any interim changes. + book = Book.objects.get(id=book_id) + book.epub_file.save('%s.epub' % book.slug, + File(open(epub.get_filename()))) + + # remove zip with all epub files + remove_zip(settings.ALL_EPUB_ZIP) + + +@task(ignore_result=True, rate_limit=settings.CATALOGUE_MOBI_RATE_LIMIT) +def build_mobi(book_id): + """(Re)builds the MOBI file for a book.""" + from django.core.files import File + from catalogue.models import Book + from catalogue.utils import remove_zip + + mobi = Book.objects.get(pk=book_id).wldocument().as_mobi() + # Save the file in new instance. Building MOBI takes time and we don't want + # to overwrite any interim changes. + book = Book.objects.get(id=book_id) + book.mobi_file.save('%s.mobi' % book.slug, + File(open(mobi.get_filename()))) + + # remove zip with all mobi files + remove_zip(settings.ALL_MOBI_ZIP) + + +@task(rate_limit=settings.CATALOGUE_CUSTOMPDF_RATE_LIMIT) +def build_custom_pdf(book_id, customizations, file_name): + """Builds a custom PDF file.""" + from django.core.files import File + from django.core.files.storage import DefaultStorage + from catalogue.models import Book + + print "will gen %s" % DefaultStorage().path(file_name) + if not DefaultStorage().exists(file_name): + pdf = Book.objects.get(pk=book_id).wldocument().as_pdf( + customizations=customizations, + morefloats=settings.LIBRARIAN_PDF_MOREFLOATS) + DefaultStorage().save(file_name, File(open(pdf.get_filename()))) diff --git a/apps/catalogue/templates/catalogue/book_short.html b/apps/catalogue/templates/catalogue/book_short.html index 1b1eb4b43..d9b5b7689 100644 --- a/apps/catalogue/templates/catalogue/book_short.html +++ b/apps/catalogue/templates/catalogue/book_short.html @@ -27,6 +27,7 @@
    + {% csrf_token %}
    diff --git a/apps/catalogue/templates/catalogue/book_wide.html b/apps/catalogue/templates/catalogue/book_wide.html index c683fce61..ad588bd7e 100644 --- a/apps/catalogue/templates/catalogue/book_wide.html +++ b/apps/catalogue/templates/catalogue/book_wide.html @@ -52,7 +52,6 @@
    - {% if related.media.mp3 or related.media.ogg %}

    {% trans "Download" %}

    - {% endif %}
    {% endblock %} diff --git a/apps/catalogue/templatetags/catalogue_tags.py b/apps/catalogue/templatetags/catalogue_tags.py index 7d401289b..e5e4d4fa0 100644 --- a/apps/catalogue/templatetags/catalogue_tags.py +++ b/apps/catalogue/templatetags/catalogue_tags.py @@ -295,10 +295,7 @@ def book_info(book): @register.inclusion_tag('catalogue/book_wide.html', takes_context=True) def book_wide(context, book): - theme_counter = book.theme_counter - book_themes = Tag.objects.filter(pk__in=theme_counter.keys()) - for tag in book_themes: - tag.count = theme_counter[tag.pk] + book_themes = book.related_themes() extra_info = book.get_extra_info_value() hide_about = extra_info.get('about', '').startswith('http://wiki.wolnepodreczniki.pl') @@ -309,7 +306,6 @@ def book_wide(context, book): 'extra_info': book.get_extra_info_value(), 'hide_about': hide_about, 'themes': book_themes, - 'custom_pdf_form': forms.CustomPDFForm(), 'request': context.get('request'), } diff --git a/apps/catalogue/tests/book_import.py b/apps/catalogue/tests/book_import.py index 3af1bb486..6ece3287e 100644 --- a/apps/catalogue/tests/book_import.py +++ b/apps/catalogue/tests/book_import.py @@ -7,8 +7,7 @@ from catalogue import models from librarian import WLURI from nose.tools import raises -import tempfile -from os import unlink, path, makedirs +from os import path, makedirs class BookImportLogicTests(WLTestCase): @@ -237,9 +236,7 @@ class ChildImportTests(WLTestCase): """ child = models.Book.from_text_and_meta(ContentFile(CHILD_TEXT), self.child_info, overwrite=True) - - themes = self.client.get(parent.get_absolute_url()).context['book_themes'] - + themes = parent.related_themes() self.assertEqual(['Kot'], [tag.name for tag in themes], 'wrong related theme list') @@ -283,26 +280,30 @@ class MultilingualBookImportTest(WLTestCase): class BookImportGenerateTest(WLTestCase): def setUp(self): WLTestCase.setUp(self) - input = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml') - self.book = models.Book.from_xml_file(input) + xml = path.join(path.dirname(__file__), 'files/fraszka-do-anusie.xml') + self.book = models.Book.from_xml_file(xml) def test_gen_pdf(self): self.book.build_pdf() - self.assertTrue(path.exists(self.book.pdf_file.path)) + book = models.Book.objects.get(pk=self.book.pk) + self.assertTrue(path.exists(book.pdf_file.path)) def test_gen_pdf_parent(self): """This book contains a child.""" - input = path.join(path.dirname(__file__), "files/fraszki.xml") - parent = models.Book.from_xml_file(input) + xml = path.join(path.dirname(__file__), "files/fraszki.xml") + parent = models.Book.from_xml_file(xml) parent.build_pdf() + parent = models.Book.objects.get(pk=parent.pk) self.assertTrue(path.exists(parent.pdf_file.path)) def test_custom_pdf(self): + from catalogue.tasks import build_custom_pdf out = models.get_dynamic_path(None, 'test-custom', ext='pdf') absoulute_path = path.join(settings.MEDIA_ROOT, out) if not path.exists(path.dirname(absoulute_path)): makedirs(path.dirname(absoulute_path)) - self.book.build_pdf(customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out) + build_custom_pdf(self.book.id, + customizations=['nofootnotes', '13pt', 'a4paper'], file_name=out) self.assertTrue(path.exists(absoulute_path)) diff --git a/apps/catalogue/tests/tags.py b/apps/catalogue/tests/tags.py index a47e426a5..3eab3da4d 100644 --- a/apps/catalogue/tests/tags.py +++ b/apps/catalogue/tests/tags.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- +from django.core.files.base import ContentFile +from django.test import Client from catalogue import models from catalogue.test_utils import * -from django.core.files.base import ContentFile + class BooksByTagTests(WLTestCase): """ tests the /katalog/category/tag page for found books """ @@ -63,7 +65,6 @@ class BooksByTagTests(WLTestCase): ['Child']) -from django.test import Client class TagRelatedTagsTests(WLTestCase): """ tests the /katalog/category/tag/ page for related tags """ @@ -173,7 +174,7 @@ class CleanTagRelationTests(WLTestCase): """ - book = models.Book.from_text_and_meta(ContentFile(book_text), book_info) + self.book = models.Book.from_text_and_meta(ContentFile(book_text), book_info) def test_delete_objects(self): """ there should be no related tags left after deleting some objects """ @@ -190,8 +191,8 @@ class CleanTagRelationTests(WLTestCase): """ there should be no tag relations left after deleting tags """ models.Tag.objects.all().delete() - cats = self.client.get('/katalog/lektura/book/').context['categories'] - self.assertEqual(cats, {}) + self.assertEqual(len(self.book.related_info()['tags']), 0) + self.assertEqual(len(self.book.related_themes()), 0) self.assertEqual(models.Tag.intermediary_table_model.objects.all().count(), 0, "orphaned TagRelation objects left") @@ -219,13 +220,14 @@ class TestIdenticalTag(WLTestCase): def test_book_tags(self): """ there should be all related tags in relevant categories """ - models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info) + book = models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info) - context = self.client.get('/katalog/lektura/tag/').context + related_info = book.related_info() + related_themes = book.related_themes() for category in 'author', 'kind', 'genre', 'epoch': - self.assertTrue('tag' in [tag.slug for tag in context['categories'][category]], + self.assertTrue('tag' in [tag[1] for tag in related_info['tags'][category]], 'missing related tag for %s' % category) - self.assertTrue('tag' in [tag.slug for tag in context['book_themes']]) + self.assertTrue('tag' in [tag.slug for tag in related_themes]) def test_qualified_url(self): models.Book.from_text_and_meta(ContentFile(self.book_text), self.book_info) @@ -259,27 +261,28 @@ class BookTagsTests(WLTestCase): """ % info.title.encode('utf-8') - book = models.Book.from_text_and_meta(ContentFile(book_text), info) + models.Book.from_text_and_meta(ContentFile(book_text), info) def test_book_tags(self): """ book should have own tags and whole tree's themes """ - context = self.client.get('/katalog/lektura/parent/').context + book = models.Book.objects.get(slug='parent') + related_info = book.related_info() + related_themes = book.related_themes() - self.assertEqual([tag.name for tag in context['categories']['author']], - ['Common Man']) - self.assertEqual([tag.name for tag in context['categories']['kind']], - ['Kind']) - self.assertEqual([(tag.name, tag.count) for tag in context['book_themes']], + self.assertEqual(related_info['tags']['author'], + [('Common Man', 'common-man')]) + self.assertEqual(related_info['tags']['kind'], + [('Kind', 'kind')]) + self.assertEqual([(tag.name, tag.count) for tag in related_themes], [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)]) def test_main_page_tags(self): """ test main page tags and counts """ - - context = self.client.get('/katalog/').context - - self.assertEqual([(tag.name, tag.count) for tag in context['categories']['author']], + from catalogue.templatetags.catalogue_tags import catalogue_menu + menu = catalogue_menu() + self.assertEqual([(tag.name, tag.book_count) for tag in menu['author']], [('Jim Lazy', 1), ('Common Man', 1)]) - self.assertEqual([(tag.name, tag.count) for tag in context['fragment_tags']], + self.assertEqual([(tag.name, tag.book_count) for tag in menu['theme']], [('ChildTheme', 1), ('ParentTheme', 1), ('Theme', 2)]) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index da4a3aec3..647bc9f37 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -37,6 +37,11 @@ urlpatterns += patterns('catalogue.views', url(r'^jtags/$', 'json_tags_starting_with', name='jhint'), #url(r'^szukaj/$', 'search', name='old_search'), + url(r'^custompdf/(?P%s)/$' % SLUG, CustomPDFFormView(), name='custom_pdf_form'), + + url(r'^audiobooki/(?Pmp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'), + + # zip url(r'^zip/pdf\.zip$', 'download_zip', {'format': 'pdf', 'slug': None}, 'download_zip_pdf'), url(r'^zip/epub\.zip$', 'download_zip', {'format': 'epub', 'slug': None}, 'download_zip_epub'), @@ -51,11 +56,6 @@ urlpatterns += patterns('catalogue.views', url(r'^lektura/(?P%s)/motyw/(?P[a-zA-Z0-9-]+)/$' % SLUG, 'book_fragments', name='book_fragments'), + # This should be the last pattern. url(r'^(?P[a-zA-Z0-9-/]*)/$', 'tagged_object_list', name='tagged_object_list'), - - url(r'^audiobooki/(?Pmp3|ogg|daisy|all).xml$', AudiobookFeed(), name='audiobook_feed'), - - url(r'^custompdf$', CustomPDFFormView(), name='custom_pdf_form'), - url(r'^custompdf/(?P%s).pdf' % SLUG, 'download_custom_pdf'), - ) diff --git a/apps/catalogue/utils.py b/apps/catalogue/utils.py index a8a12e5f2..29f40d16b 100644 --- a/apps/catalogue/utils.py +++ b/apps/catalogue/utils.py @@ -9,23 +9,18 @@ import re import time from base64 import urlsafe_b64encode -from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect +from django.http import HttpResponse from django.core.files.uploadedfile import UploadedFile -from django.core.files.base import File from django.core.files.storage import DefaultStorage from django.utils.encoding import force_unicode from django.utils.hashcompat import sha_constructor from django.conf import settings -from celery.task import task from os import mkdir, path, unlink from errno import EEXIST, ENOENT from fcntl import flock, LOCK_EX from zipfile import ZipFile -from traceback import print_exc from reporting.utils import read_chunks -from celery.task import task -import catalogue.models # Use the system (hardware-based) random number generator if it exists. if hasattr(random, 'SystemRandom'): @@ -84,7 +79,7 @@ class LockFile(object): self.lock.close() -@task +#@task def create_zip(paths, zip_slug): """ Creates a zip in MEDIA_ROOT/zip directory containing files from path. @@ -140,25 +135,6 @@ class AttachmentHttpResponse(HttpResponse): for chunk in read_chunks(f): self.write(chunk) -@task -def async_build_pdf(book_id, customizations, file_name): - """ - A celery task to generate pdf files. - Accepts the same args as Book.build_pdf, but with book id as first parameter - instead of Book instance - """ - try: - book = catalogue.models.Book.objects.get(id=book_id) - print "will gen %s" % DefaultStorage().path(file_name) - if not DefaultStorage().exists(file_name): - book.build_pdf(customizations=customizations, file_name=file_name) - print "done." - except Exception, e: - print "Error during pdf creation: %s" % e - print_exc - raise e - - class MultiQuerySet(object): def __init__(self, *args, **kwargs): self.querysets = args @@ -260,3 +236,24 @@ def truncate_html_words(s, num, end_text='...'): out += '' % tag # Return string return out + + +def customizations_hash(customizations): + customizations.sort() + return hash(tuple(customizations)) + + +def get_customized_pdf_path(book, customizations): + """ + Returns a MEDIA_ROOT relative path for a customized pdf. The name will contain a hash of customization options. + """ + h = customizations_hash(customizations) + return 'book/%s/%s-custom-%s.pdf' % (book.slug, book.slug, h) + + +def clear_custom_pdf(book): + """ + Returns a list of paths to generated customized pdf of a book + """ + from waiter.utils import clear_cache + clear_cache('book/%s' % book.slug) diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index 7ead47115..d2176bf6b 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -7,7 +7,7 @@ import itertools from django.conf import settings from django.template import RequestContext -from django.shortcuts import render_to_response, get_object_or_404 +from django.shortcuts import render_to_response, get_object_or_404, redirect from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect from django.core.urlresolvers import reverse from django.db.models import Q @@ -22,15 +22,12 @@ from ajaxable.utils import JSONResponse, AjaxableFormView from catalogue import models from catalogue import forms -from catalogue.utils import (split_tags, AttachmentHttpResponse, - async_build_pdf, MultiQuerySet) +from catalogue.utils import split_tags, MultiQuerySet from pdcounter import models as pdcounter_models from pdcounter import views as pdcounter_views from suggest.forms import PublishingSuggestForm from picture.models import Picture -from os import path - staff_required = user_passes_test(lambda user: user.is_staff) @@ -531,43 +528,18 @@ def download_zip(request, format, slug=None): return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?=')) -def download_custom_pdf(request, slug, method='GET'): - book = get_object_or_404(models.Book, slug=slug) - - if request.method == method: - form = forms.CustomPDFForm(method == 'GET' and request.GET or request.POST) - if form.is_valid(): - cust = form.customizations - pdf_file = models.get_customized_pdf_path(book, cust) - - if not path.exists(pdf_file): - result = async_build_pdf.delay(book.id, cust, pdf_file) - result.wait() - return AttachmentHttpResponse(file_name=("%s.pdf" % book.slug), file_path=pdf_file, mimetype="application/pdf") - else: - raise Http404(_('Incorrect customization options for PDF')) - else: - raise Http404(_('Bad method')) - - class CustomPDFFormView(AjaxableFormView): form_class = forms.CustomPDFForm title = ugettext_lazy('Download custom PDF') submit = ugettext_lazy('Download') + honeypot = True - def __call__(self, request): - from copy import copy - if request.method == 'POST': - request.GET = copy(request.GET) - request.GET['next'] = "%s?%s" % (reverse('catalogue.views.download_custom_pdf', args=[request.GET.get('slug')]), - request.POST.urlencode()) - return super(CustomPDFFormView, self).__call__(request) + def form_args(self, request, obj): + """Override to parse view args and give additional args to the form.""" + return (obj,), {} - def get_object(self, request): - return get_object_or_404(models.Book, slug=request.GET.get('slug')) + def get_object(self, request, slug, *args, **kwargs): + return get_object_or_404(models.Book, slug=slug) def context_description(self, request, obj): return obj.pretty_title() - - def success(self, *args): - pass diff --git a/apps/dictionary/models.py b/apps/dictionary/models.py index 1d2fbba39..6238ccbf2 100644 --- a/apps/dictionary/models.py +++ b/apps/dictionary/models.py @@ -3,7 +3,7 @@ # Copyright © Fundacja Nowoczesna Polska. See NOTICE for more information. # from django.db import models - +from celery.task import task from sortify import sortify from catalogue.models import Book @@ -19,15 +19,17 @@ class Note(models.Model): ordering = ['sort_key'] -def notes_from_book(sender, **kwargs): - from librarian import html - - Note.objects.filter(book=sender).delete() - if sender.html_file: - for anchor, text_str, html_str in html.extract_annotations(sender.html_file.path): - Note.objects.create(book=sender, anchor=anchor, +@task(ignore_result=True) +def build_notes(book_id): + book = Book.objects.get(pk=book_id) + Note.objects.filter(book=book).delete() + if book.html_file: + from librarian import html + for anchor, text_str, html_str in html.extract_annotations(book.html_file.path): + Note.objects.create(book=book, anchor=anchor, html=html_str, sort_key=sortify(text_str).strip()[:128]) - -# always re-extract notes after making a HTML in a Book -Book.html_built.connect(notes_from_book) + +@Book.html_built.connect +def notes_from_book(sender, **kwargs): + build_notes.delat(sender) diff --git a/apps/picture/tests/picture_import.py b/apps/picture/tests/picture_import.py index 91fb35f71..202acdd65 100644 --- a/apps/picture/tests/picture_import.py +++ b/apps/picture/tests/picture_import.py @@ -1,16 +1,10 @@ # -*- coding: utf-8 -*- from __future__ import with_statement -from django.core.files.base import ContentFile, File -from catalogue.test_utils import * -from catalogue import models -from librarian import WLURI +from os import path +from django.test import TestCase from picture.models import Picture -from nose.tools import raises -import tempfile -from os import unlink, path, makedirs - class PictureTest(TestCase): diff --git a/apps/social/templates/social/sets_form.html b/apps/social/templates/social/sets_form.html index 2ea1a867d..c7282fd9a 100755 --- a/apps/social/templates/social/sets_form.html +++ b/apps/social/templates/social/sets_form.html @@ -3,11 +3,13 @@
    +{% csrf_token %}
    +{% csrf_token %}
      {{ form.as_ul }} diff --git a/apps/suggest/templates/publishing_suggest.html b/apps/suggest/templates/publishing_suggest.html index 3e710008c..ea1d9264f 100755 --- a/apps/suggest/templates/publishing_suggest.html +++ b/apps/suggest/templates/publishing_suggest.html @@ -1,8 +1,11 @@ {% load i18n %} +{% load honeypot %} +

      {% trans "Didn't find a book? Make a suggestion." %}

      {% csrf_token %} +{% render_honeypot_field %}
      1. {{ form.contact.errors }} {{ form.contact }}
      2. diff --git a/apps/suggest/views.py b/apps/suggest/views.py index 15b65f24d..035074d0a 100644 --- a/apps/suggest/views.py +++ b/apps/suggest/views.py @@ -6,7 +6,6 @@ from django.utils.translation import ugettext_lazy as _ from ajaxable.utils import AjaxableFormView from suggest import forms -from suggest.models import Suggestion, PublishingSuggestion class PublishingSuggestionFormView(AjaxableFormView): @@ -14,6 +13,7 @@ class PublishingSuggestionFormView(AjaxableFormView): title = _('Report a bug or suggestion') template = "publishing_suggest.html" success_message = _('Report was sent successfully.') + honeypot = True class SuggestionFormView(AjaxableFormView): @@ -21,3 +21,4 @@ class SuggestionFormView(AjaxableFormView): title = _('Report a bug or suggestion') submit = _('Send report') success_message = _('Report was sent successfully.') + honeypot = True diff --git a/apps/waiter/__init__.py b/apps/waiter/__init__.py new file mode 100644 index 000000000..d3696b741 --- /dev/null +++ b/apps/waiter/__init__.py @@ -0,0 +1,8 @@ +""" +Celery waiter. + +Takes orders for files generated by async Celery tasks. +Serves the file when ready. Kindly asks the user to wait if not. + +Author: Radek Czajka +""" \ No newline at end of file diff --git a/apps/waiter/locale/pl/LC_MESSAGES/django.mo b/apps/waiter/locale/pl/LC_MESSAGES/django.mo new file mode 100644 index 000000000..8f8afb7de Binary files /dev/null and b/apps/waiter/locale/pl/LC_MESSAGES/django.mo differ diff --git a/apps/waiter/locale/pl/LC_MESSAGES/django.po b/apps/waiter/locale/pl/LC_MESSAGES/django.po new file mode 100644 index 000000000..bbc9f8dfa --- /dev/null +++ b/apps/waiter/locale/pl/LC_MESSAGES/django.po @@ -0,0 +1,67 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2012-03-22 15:54+0100\n" +"PO-Revision-Date: 2012-03-22 15:54+0100\n" +"Last-Translator: Radek Czajka \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n" + +#: templates/waiter/wait.html:7 +#: templates/waiter/wait.html.py:33 +msgid "The file is ready for download!" +msgstr "Plik jest gotowy do pobrania!" + +#: templates/waiter/wait.html:10 +#: templates/waiter/wait.html.py:42 +msgid "Your file is being prepared, please wait." +msgstr "Plik jest generowany, proszę czekać." + +#: templates/waiter/wait.html:12 +#: templates/waiter/wait.html.py:51 +msgid "Something went wrong." +msgstr "Coś poszło nie tak." + +#: templates/waiter/wait.html:36 +#, python-format +msgid "" +"Your file is ready!\n" +" If the download doesn't start in a few seconds,\n" +" feel free to use this direct link." +msgstr "" +"Twój plik jest gotowy!\n" +"Jeśli pobieranie nie zacznie się w ciągu kilku sekund,\n" +"skorzystaj z tego bezpośredniego linku." + +#: templates/waiter/wait.html:45 +#, python-format +msgid "The file you requested was: %(d)s." +msgstr "Zamówiony plik to: %(d)s." + +#: templates/waiter/wait.html:47 +msgid "" +"Be aware: Generating the file can take a while.\n" +" Please be patient, or bookmark this page and come back later.

        " +msgstr "" +"Uwaga: Generowanie pliku może trwać dłuższą chwilę.\n" +"Poczekaj cierpliwie, albo dodaj tę stronę do zakładek i wróć później.

        " + +#: templates/waiter/wait.html:55 +#, python-format +msgid "" +"Something seems to have gone wrong while generating your file.\n" +" Please order it again or complain to us about it." +msgstr "" +"Wygląda na to, że coś poszło źle podczas generowania Twojego pliku.\n" +"Spróbuj zamówić go jeszcze raz albo napisz do nas." + diff --git a/apps/waiter/migrations/0001_initial.py b/apps/waiter/migrations/0001_initial.py new file mode 100644 index 000000000..a75884bac --- /dev/null +++ b/apps/waiter/migrations/0001_initial.py @@ -0,0 +1,39 @@ +# encoding: utf-8 +import datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + +class Migration(SchemaMigration): + + def forwards(self, orm): + + # Adding model 'WaitedFile' + db.create_table('waiter_waitedfile', ( + ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), + ('path', self.gf('django.db.models.fields.CharField')(unique=True, max_length=255, db_index=True)), + ('task_id', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=128, null=True, blank=True)), + ('task', self.gf('picklefield.fields.PickledObjectField')(null=True)), + ('description', self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True)), + )) + db.send_create_signal('waiter', ['WaitedFile']) + + + def backwards(self, orm): + + # Deleting model 'WaitedFile' + db.delete_table('waiter_waitedfile') + + + models = { + 'waiter.waitedfile': { + 'Meta': {'object_name': 'WaitedFile'}, + 'description': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '255', 'db_index': 'True'}), + 'task': ('picklefield.fields.PickledObjectField', [], {'null': 'True'}), + 'task_id': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '128', 'null': 'True', 'blank': 'True'}) + } + } + + complete_apps = ['waiter'] diff --git a/apps/waiter/migrations/__init__.py b/apps/waiter/migrations/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/waiter/models.py b/apps/waiter/models.py new file mode 100644 index 000000000..10f92897e --- /dev/null +++ b/apps/waiter/models.py @@ -0,0 +1,65 @@ +from os.path import join, isfile +from django.core.urlresolvers import reverse +from django.db import models +from waiter.settings import WAITER_URL, WAITER_MAX_QUEUE +from waiter.utils import check_abspath +from picklefield import PickledObjectField + + +class WaitedFile(models.Model): + path = models.CharField(max_length=255, unique=True, db_index=True) + task_id = models.CharField(max_length=128, db_index=True, null=True, blank=True) + task = PickledObjectField(null=True, editable=False) + description = models.CharField(max_length=255, null=True, blank=True) + + @classmethod + def exists(cls, path): + """Returns opened file or None. + + `path` is relative to WAITER_ROOT. + Won't open a path leading outside of WAITER_ROOT. + """ + abs_path = check_abspath(path) + # Pre-fetch objects for deletion to avoid minor race condition + relevant = [o.id for o in cls.objects.filter(path=path)] + if isfile(abs_path): + cls.objects.filter(id__in=relevant).delete() + return True + else: + return False + + @classmethod + def can_order(cls, path): + return (cls.objects.filter(path=path).exists() or + cls.exists(path) or + cls.objects.count() < WAITER_MAX_QUEUE + ) + + def is_stale(self): + if self.task is None: + # Race; just let the other task roll. + return False + if self.task.status not in (u'PENDING', u'STARTED', u'SUCCESS', u'RETRY'): + return True + return False + + @classmethod + def order(cls, path, task_creator, description=None): + """ + Returns an URL for the user to follow. + If the file is ready, returns download URL. + If not, starts preparing it and returns waiting URL. + + task_creator: function taking a path and generating the file; + description: a string or string proxy with a description for user; + """ + already = cls.exists(path) + if not already: + waited, created = cls.objects.get_or_create(path=path) + if created or waited.is_stale(): + waited.task = task_creator(check_abspath(path)) + waited.task_id = waited.task.task_id + waited.description = description + waited.save() + return reverse("waiter", args=[path]) + return join(WAITER_URL, path) diff --git a/apps/waiter/settings.py b/apps/waiter/settings.py new file mode 100644 index 000000000..fef4a7e49 --- /dev/null +++ b/apps/waiter/settings.py @@ -0,0 +1,18 @@ +from os.path import join +from django.conf import settings + +try: + WAITER_ROOT = settings.WAITER_ROOT +except AttributeError: + WAITER_ROOT = join(settings.MEDIA_ROOT, 'waiter') + +try: + WAITER_URL = settings.WAITER_URL +except AttributeError: + WAITER_URL = join(settings.MEDIA_URL, 'waiter') + +try: + WAITER_MAX_QUEUE = settings.WAITER_MAX_QUEUE +except AttributeError: + WAITER_MAX_QUEUE = 20 + diff --git a/apps/waiter/tasks.py b/apps/waiter/tasks.py new file mode 100644 index 000000000..4c3933ef1 --- /dev/null +++ b/apps/waiter/tasks.py @@ -0,0 +1,7 @@ +from celery.signals import task_postrun +from waiter.models import WaitedFile + + +def task_delete_after(task_id=None, **kwargs): + WaitedFile.objects.filter(task_id=task_id).delete() +task_postrun.connect(task_delete_after) diff --git a/apps/waiter/templates/waiter/wait.html b/apps/waiter/templates/waiter/wait.html new file mode 100644 index 000000000..e15bd6455 --- /dev/null +++ b/apps/waiter/templates/waiter/wait.html @@ -0,0 +1,93 @@ +{% extends "base.html" %} +{% load i18n %} +{% load url from future %} + +{% block titleextra %} +{% if file_url %} + {% trans "The file is ready for download!" %} +{% else %} + {% if waiting %} + {% trans "Your file is being prepared, please wait." %} + {% else %} + {% trans "Something went wrong." %} + {% endif %} +{% endif %} +{% endblock %} + + +{% block extrahead %} +{% if file_url %} + +{% else %} + {% if waiting %} + + {% endif %} +{% endif %} +{% endblock %} + + +{% block body %} +{% if file_url %} +

        {% trans "The file is ready for download!" %}

        + +
        +

        {% blocktrans %}Your file is ready! + If the download doesn't start in a few seconds, + feel free to use this direct link.{% endblocktrans %}

        +
        +{% else %} + {% if waiting %} +

        {% trans 'Please wait' %} + {% trans "Your file is being prepared, please wait." %}

        + +
        +

        {% blocktrans with d=waiting.description %}The file you requested was: {{d}}.{% endblocktrans %}

        + +

        {% blocktrans %}Be aware: Generating the file can take a while. + Please be patient, or bookmark this page and come back later.

        {% endblocktrans %} +
        + {% else %} +

        {% trans "Something went wrong." %}

        + +
        + {% url 'suggest' as s %} +

        {% blocktrans %}Something seems to have gone wrong while generating your file. + Please order it again or complain to us about it.{% endblocktrans %}

        +
        + {% endif %} +{% endif %} + +{% endblock %} + +{% block extrabody %} +{% if waiting %} + +{% endif %} +{% endblock %} diff --git a/apps/waiter/urls.py b/apps/waiter/urls.py new file mode 100644 index 000000000..484ef3e44 --- /dev/null +++ b/apps/waiter/urls.py @@ -0,0 +1,5 @@ +from django.conf.urls.defaults import * + +urlpatterns = patterns('waiter.views', + url(r'^(?P.*)$', 'wait', name='waiter'), +) diff --git a/apps/waiter/utils.py b/apps/waiter/utils.py new file mode 100644 index 000000000..0957e9d80 --- /dev/null +++ b/apps/waiter/utils.py @@ -0,0 +1,17 @@ +from os.path import abspath, join, exists +from shutil import rmtree +from waiter.settings import WAITER_ROOT + + +def check_abspath(path): + abs_path = abspath(join(WAITER_ROOT, path)) + if not abs_path.startswith(WAITER_ROOT): + raise ValueError('Path not inside WAITER_ROOT.') + return abs_path + + +def clear_cache(path): + abs_path = check_abspath(path) + if exists(abs_path): + rmtree(abs_path) + diff --git a/apps/waiter/views.py b/apps/waiter/views.py new file mode 100644 index 000000000..e38bd8f1d --- /dev/null +++ b/apps/waiter/views.py @@ -0,0 +1,19 @@ +from os.path import join +from waiter.models import WaitedFile +from waiter.settings import WAITER_URL +from django.shortcuts import render, get_object_or_404 +from django.http import HttpResponse + +def wait(request, path): + if WaitedFile.exists(path): + file_url = join(WAITER_URL, path) + else: + file_url = "" + waiting = get_object_or_404(WaitedFile, path=path) + if waiting.is_stale(): + waiting = None + + if request.is_ajax(): + return HttpResponse(file_url) + else: + return render(request, "waiter/wait.html", locals()) diff --git a/apps/wolnelektury_core/templates/admin/base_site.html b/apps/wolnelektury_core/templates/admin/base_site.html index 23cfb59d8..ff381b554 100644 --- a/apps/wolnelektury_core/templates/admin/base_site.html +++ b/apps/wolnelektury_core/templates/admin/base_site.html @@ -5,7 +5,18 @@ {% block branding %}

        {% trans "Site administration" %} - WolneLektury.pl

        -

        {% trans "Translations" %}

        +

        + + promobox | + cytaty | + info | + książki | + tagi | + kolekcje | + licznik domeny | + sponsorzy | + sugestie +

        {% endblock %} {% block nav-global %}{% endblock %} \ No newline at end of file diff --git a/apps/wolnelektury_core/templates/admin/catalogue/book/change_list.html b/apps/wolnelektury_core/templates/admin/catalogue/book/change_list.html index 09e567a5d..0ec95b24d 100644 --- a/apps/wolnelektury_core/templates/admin/catalogue/book/change_list.html +++ b/apps/wolnelektury_core/templates/admin/catalogue/book/change_list.html @@ -3,6 +3,7 @@ {% block content %} + {% csrf_token %}

        {{ block.super }} diff --git a/apps/wolnelektury_core/templates/auth/login.html b/apps/wolnelektury_core/templates/auth/login.html index 7fbd570c0..6d71cd5b4 100644 --- a/apps/wolnelektury_core/templates/auth/login.html +++ b/apps/wolnelektury_core/templates/auth/login.html @@ -3,6 +3,7 @@
        +{% csrf_token %}
          {{ form.as_ul }} diff --git a/apps/wolnelektury_core/templates/auth/login_register.html b/apps/wolnelektury_core/templates/auth/login_register.html index f879d4674..689c1bace 100755 --- a/apps/wolnelektury_core/templates/auth/login_register.html +++ b/apps/wolnelektury_core/templates/auth/login_register.html @@ -1,5 +1,6 @@ {% extends "auth/login.html" %} {% load i18n %} +{% load honeypot %} {% block extra %} @@ -9,6 +10,8 @@ +{% csrf_token %} +{% honeypot_render_field %}
            {{ register_form.as_ul }} diff --git a/apps/wolnelektury_core/templates/piston/authorize_token.html b/apps/wolnelektury_core/templates/piston/authorize_token.html index 4e4520734..0fd06f82c 100755 --- a/apps/wolnelektury_core/templates/piston/authorize_token.html +++ b/apps/wolnelektury_core/templates/piston/authorize_token.html @@ -10,6 +10,7 @@

            {% blocktrans %}Confirm to authorize access to Wolne Lektury as user {{ user}}.{% endblocktrans %}

            + {% csrf_token %} {{ form.as_p }} diff --git a/apps/wolnelektury_core/templates/superbase.html b/apps/wolnelektury_core/templates/superbase.html index a4a7830e2..f67d9f384 100644 --- a/apps/wolnelektury_core/templates/superbase.html +++ b/apps/wolnelektury_core/templates/superbase.html @@ -130,6 +130,7 @@
            {% for lang in LANGUAGES %}
            + {% csrf_token %}