From e0164e6ca0588a270b77d892702e54ef62c31de2 Mon Sep 17 00:00:00 2001 From: Marcin Koziej Date: Thu, 1 Dec 2011 16:56:04 +0100 Subject: [PATCH] Customized pdf basically works. --- apps/catalogue/forms.py | 49 +++++++++++++++++++ apps/catalogue/models.py | 47 +++++++++++------- apps/catalogue/tests/book_import.py | 12 ++++- apps/catalogue/urls.py | 2 + apps/catalogue/utils.py | 25 +++++++++- apps/catalogue/views.py | 24 ++++++++- wolnelektury/static/css/master.css | 5 ++ wolnelektury/static/js/catalogue.js | 5 +- .../templates/catalogue/book_detail.html | 10 ++++ 9 files changed, 157 insertions(+), 22 deletions(-) diff --git a/apps/catalogue/forms.py b/apps/catalogue/forms.py index 391e3e429..094aaaf23 100644 --- a/apps/catalogue/forms.py +++ b/apps/catalogue/forms.py @@ -96,3 +96,52 @@ class DownloadFormatsForm(forms.Form): def __init__(self, *args, **kwargs): 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')), + ) + +PDF_FONT_SIZES = ( + ('11pt', _('Default')), + ('13pt', _('Big')) + ) + + +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")) + + @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']) + + c.sort() + + return c + diff --git a/apps/catalogue/models.py b/apps/catalogue/models.py index c420119f0..2e20717fc 100644 --- a/apps/catalogue/models.py +++ b/apps/catalogue/models.py @@ -23,6 +23,9 @@ from newtagging.models import TagBase, tags_updated from newtagging import managers from catalogue.fields import JSONField, OverwritingFileField from catalogue.utils import create_zip +from shutil import copy + +from os import path TAG_CATEGORIES = ( @@ -170,23 +173,25 @@ class Tag(TagBase): return '/'.join((Tag.categories_dict[self.category], self.slug)) +def get_dynamic_path(media, filename, ext=None, maxlen=100): + from slughifi import slughifi + + # how to put related book's slug here? + if not ext: + if media.type == 'daisy': + ext = 'daisy.zip' + else: + ext = media.type + if media is None or not media.name: + name = slughifi(filename.split(".")[0]) + else: + name = slughifi(media.name) + return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext) + + # TODO: why is this hard-coded ? def book_upload_path(ext=None, maxlen=100): - def get_dynamic_path(media, filename, ext=ext): - from slughifi import slughifi - - # how to put related book's slug here? - if not ext: - if media.type == 'daisy': - ext = 'daisy.zip' - else: - ext = media.type - if not media.name: - name = slughifi(filename.split(".")[0]) - else: - name = slughifi(media.name) - return 'book/%s/%s.%s' % (ext, name[:maxlen-len('book/%s/.%s' % (ext, ext))-4], ext) - return get_dynamic_path + return lambda *args: get_dynamic_path(*args, ext=ext, maxlen=maxlen) class BookMedia(models.Model): @@ -465,24 +470,30 @@ class Book(models.Model): has_daisy_file.short_description = 'DAISY' has_daisy_file.boolean = True - def build_pdf(self): + 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 tempfile import NamedTemporaryFile from os import unlink from django.core.files import File from librarian import pdf from catalogue.utils import ORMDocProvider, remove_zip + from django.core.files.move import file_move_safe try: pdf_file = NamedTemporaryFile(delete=False) pdf.transform(ORMDocProvider(self), file_path=str(self.xml_file.path), output_file=pdf_file, + customizations=customizations ) - self.pdf_file.save('%s.pdf' % self.slug, File(open(pdf_file.name))) + if file_name is None: + self.pdf_file.save('%s.pdf' % self.slug, File(open(pdf_file.name))) + else: + copy(pdf_file.name, path.join(settings.MEDIA_ROOT, get_dynamic_path(None, file_name, ext='pdf'))) finally: unlink(pdf_file.name) diff --git a/apps/catalogue/tests/book_import.py b/apps/catalogue/tests/book_import.py index f65d8807a..f6110d0be 100644 --- a/apps/catalogue/tests/book_import.py +++ b/apps/catalogue/tests/book_import.py @@ -7,7 +7,7 @@ from catalogue import models from nose.tools import raises import tempfile -from os import unlink,path +from os import unlink, path, makedirs class BookImportLogicTests(WLTestCase): @@ -258,3 +258,13 @@ class BookImportGenerateTest(WLTestCase): parent = models.Book.from_xml_file(input) parent.build_pdf() self.assertTrue(path.exists(parent.pdf_file.path)) + + def test_custom_pdf(self): + 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='test-custom') + self.assertTrue(path.exists(absoulute_path)) diff --git a/apps/catalogue/urls.py b/apps/catalogue/urls.py index c770892d9..324217a3c 100644 --- a/apps/catalogue/urls.py +++ b/apps/catalogue/urls.py @@ -40,5 +40,7 @@ urlpatterns = patterns('catalogue.views', 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/(?P[a-zA-Z0-9-]+).pdf', 'download_custom_pdf'), ) diff --git a/apps/catalogue/utils.py b/apps/catalogue/utils.py index 0134701a6..85a9dc274 100644 --- a/apps/catalogue/utils.py +++ b/apps/catalogue/utils.py @@ -8,6 +8,7 @@ import random import time from base64 import urlsafe_b64encode +from django.http import HttpResponse, HttpResponseRedirect, Http404, HttpResponsePermanentRedirect from django.core.files.uploadedfile import UploadedFile from django.utils.hashcompat import sha_constructor from django.conf import settings @@ -18,7 +19,9 @@ from fcntl import flock, LOCK_EX from zipfile import ZipFile from librarian import DocProvider - +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'): @@ -131,3 +134,23 @@ def remove_zip(zip_slug): except OSError as oe: if oe.errno != ENOENT: raise oe + + +class AttachmentHttpResponse(HttpResponse): + """Response serving a file to be downloaded. + """ + def __init__ (self, file_path, file_name, mimetype): + super(AttachmentHttpResponse, self).__init__(mimetype=mimetype) + self['Content-Disposition'] = 'attachment; filename=%s' % file_name + self.file_path = file_path + self.file_name = file_name + + with open(self.file_path) as f: + for chunk in read_chunks(f): + self.write(chunk) + +@task +def create_custom_pdf(book_id, customizations, file_name): + book = catalogue.models.Book.objects.get(id=book_id) + if not path.exists(file_name): + book.build_pdf(customizations=customizations, file_name=file_name) diff --git a/apps/catalogue/views.py b/apps/catalogue/views.py index c80853437..8169ad739 100644 --- a/apps/catalogue/views.py +++ b/apps/catalogue/views.py @@ -28,11 +28,12 @@ from django.views.generic.list_detail import object_list from catalogue import models from catalogue import forms -from catalogue.utils import split_tags +from catalogue.utils import split_tags, AttachmentHttpResponse, create_custom_pdf from pdcounter import models as pdcounter_models from pdcounter import views as pdcounter_views from suggest.forms import PublishingSuggestForm +from os import path staff_required = user_passes_test(lambda user: user.is_staff) @@ -253,6 +254,7 @@ def book_detail(request, slug): projects = sorted(projects) form = forms.SearchForm() + custom_pdf_form = forms.CustomPDFForm() return render_to_response('catalogue/book_detail.html', locals(), context_instance=RequestContext(request)) @@ -762,3 +764,23 @@ def download_zip(request, format, slug): else: raise Http404('No format specified for zip package') return HttpResponseRedirect(urlquote_plus(settings.MEDIA_URL + url, safe='/?=')) + + +def download_custom_pdf(request, slug): + book = models.Book.objects.get(slug=slug) + if request.method == 'GET': + form = forms.CustomPDFForm(request.GET) + if form.is_valid(): + cust = form.customizations + h = hash(tuple(cust)) + pdf_name = '%s-custom-%s' % (book.slug, h) + pdf_file = path.join(settings.MEDIA_ROOT, models.get_dynamic_path(None, pdf_name, ext='pdf')) + + if not path.exists(pdf_file): + result = create_custom_pdf.delay(book.id, cust, pdf_name) + 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')) diff --git a/wolnelektury/static/css/master.css b/wolnelektury/static/css/master.css index e788e5562..739ecee91 100644 --- a/wolnelektury/static/css/master.css +++ b/wolnelektury/static/css/master.css @@ -413,6 +413,11 @@ p .ac_input { padding: 0 10px 0 10px; } +#formats .wrap div.download .custom-pdf { + text-align: left; +} + + #czytamysluchajac { margin-top: 2.5em; } diff --git a/wolnelektury/static/js/catalogue.js b/wolnelektury/static/js/catalogue.js index ee8a045dc..e49f6b6a7 100644 --- a/wolnelektury/static/js/catalogue.js +++ b/wolnelektury/static/js/catalogue.js @@ -583,7 +583,10 @@ function serverTime() { } }); }*/ - + $("#custom-pdf-link").toggle( + function(ev) { $(".custom-pdf").show(); return false; }, + function(ev) { $(".custom-pdf").hide(); return false; } + ); }); })(jQuery) diff --git a/wolnelektury/templates/catalogue/book_detail.html b/wolnelektury/templates/catalogue/book_detail.html index ff7b5199a..5708b914e 100644 --- a/wolnelektury/templates/catalogue/book_detail.html +++ b/wolnelektury/templates/catalogue/book_detail.html @@ -61,6 +61,16 @@ {% for media in book.get_odt %} {% trans {% endfor %} + + {% if book.pdf_file %} +
{% trans "Dowload customized PDF" %}. + {% endif %} + {% if book.has_mp3_file or book.has_ogg_file or book.has_daisy_file %}

-- 2.20.1