From e774c7b7d57acbb4fb2429810b23c6180704997f Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Sun, 22 Mar 2015 00:06:35 +0100 Subject: [PATCH] Prototype for PDF generation --- .../pretixpresale/pdf/ticket_default_a4.pdf | Bin 0 -> 8977 bytes src/pretix/presale/urls.py | 2 + src/pretix/presale/views/order.py | 78 ++++++++++++++++-- src/requirements/production.txt | 2 + 4 files changed, 77 insertions(+), 5 deletions(-) create mode 100644 src/pretix/presale/static/pretixpresale/pdf/ticket_default_a4.pdf diff --git a/src/pretix/presale/static/pretixpresale/pdf/ticket_default_a4.pdf b/src/pretix/presale/static/pretixpresale/pdf/ticket_default_a4.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bf1774f9a51e9b8d3f9cd1fbf8ecb47336acca8b GIT binary patch literal 8977 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 M!eL;LQjo^^e@|oVWdHyG literal 0 HcmV?d00001 diff --git a/src/pretix/presale/urls.py b/src/pretix/presale/urls.py index ce17de4a4..27eb0895f 100644 --- a/src/pretix/presale/urls.py +++ b/src/pretix/presale/urls.py @@ -22,6 +22,8 @@ urlpatterns = [ name='event.order.cancel'), url(r'^order/(?P[^/]+)/modify$', pretix.presale.views.order.OrderModify.as_view(), name='event.order.modify'), + url(r'^order/(?P[^/]+)/download$', pretix.presale.views.order.OrderDownload.as_view(), + name='event.order.download'), url(r'^login$', pretix.presale.views.event.EventLogin.as_view(), name='event.checkout.login'), url(r'^logout$', pretix.presale.views.event.EventLogout.as_view(), name='event.logout'), url(r'^orders$', pretix.presale.views.event.EventOrders.as_view(), name='event.orders'), diff --git a/src/pretix/presale/views/order.py b/src/pretix/presale/views/order.py index 29ad10e40..b9e56caac 100644 --- a/src/pretix/presale/views/order.py +++ b/src/pretix/presale/views/order.py @@ -1,17 +1,22 @@ -from datetime import datetime -from itertools import groupby +from io import StringIO, BytesIO from django.contrib import messages from django.core.urlresolvers import reverse from django.shortcuts import redirect -from django.utils.timezone import now from django.utils.translation import ugettext_lazy as _ from django.utils.functional import cached_property -from django.views.generic import TemplateView -from django.http import HttpResponseNotFound, HttpResponseForbidden +from django.views.generic import TemplateView, View +from django.http import HttpResponseNotFound, HttpResponseForbidden, HttpResponse from pretix.base.models import Order, OrderPosition from pretix.base.signals import register_payment_providers from pretix.presale.views import EventViewMixin, EventLoginRequiredMixin, CartDisplayMixin from pretix.presale.views.checkout import QuestionsViewMixin +from reportlab.graphics.shapes import Drawing +from reportlab.pdfgen import canvas +from reportlab.lib import pagesizes, units +from reportlab.graphics.barcode.qr import QrCodeWidget +from reportlab.graphics import renderPDF +from PyPDF2 import PdfFileWriter, PdfFileReader +from django.contrib.staticfiles import finders class OrderDetailMixin: @@ -140,3 +145,66 @@ class OrderCancel(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, ctx = super().get_context_data(**kwargs) ctx['order'] = self.order return ctx + + +class OrderDownload(EventViewMixin, EventLoginRequiredMixin, OrderDetailMixin, + View): + + def get(self, request, *args, **kwargs): + if self.order.status != Order.STATUS_PAID: + return HttpResponseForbidden(_('Order is not paid')) + response = HttpResponse(content_type='application/pdf') + response['Content-Disposition'] = 'inline; filename="order%s%s.pdf"' % (request.event.slug, self.order.code) + + pagesize = request.event.settings.get('ticketpdf_pagesize', default='A4') + if hasattr(pagesizes, pagesize): + pagesize = getattr(pagesizes, pagesize) + else: + pagesize = pagesizes.A4 + defaultfname = finders.find('pretixpresale/pdf/ticket_default_a4.pdf') + fname = request.event.settings.get('ticketpdf_background', default=defaultfname) + # TODO: Handle file objects + + buffer = BytesIO() + p = canvas.Canvas(buffer, pagesize=pagesize) + + for op in self.order.positions.all().select_related('item', 'variation'): + p.setFont("Helvetica", 22) + p.drawString(15 * units.mm, 235 * units.mm, request.event.name) + + p.setFont("Helvetica", 17) + item = op.item.name + if op.variation: + item += " – " + str(op.variation) + p.drawString(15 * units.mm, 220 * units.mm, item) + + p.setFont("Helvetica", 17) + p.drawString(15 * units.mm, 210 * units.mm, "%s %s" % (str(op.price), request.event.currency)) + + reqs = 80 * units.mm + qrw = QrCodeWidget(op.identity, barLevel='H') + b = qrw.getBounds() + w = b[2] - b[0] + h = b[3] - b[1] + d = Drawing(reqs, reqs, transform=[reqs / w, 0, 0, reqs / h, 0, 0]) + d.add(qrw) + renderPDF.draw(d, p, 10 * units.mm, 130 * units.mm) + + p.setFont("Helvetica", 11) + p.drawString(15 * units.mm, 130 * units.mm, op.identity) + + p.showPage() + + p.save() + + buffer.seek(0) + new_pdf = PdfFileReader(buffer) + output = PdfFileWriter() + for page in new_pdf.pages: + bg_pdf = PdfFileReader(open(fname, "rb")) + bg_page = bg_pdf.getPage(0) + bg_page.mergePage(page) + output.addPage(bg_page) + + output.write(response) + return response diff --git a/src/requirements/production.txt b/src/requirements/production.txt index 629a7d5e5..e0c7b88e2 100644 --- a/src/requirements/production.txt +++ b/src/requirements/production.txt @@ -6,6 +6,8 @@ django-bootstrap3 -e git+https://github.com/pretix/django-formset-js.git@master#egg=django-formset-js -e git+https://github.com/pretix/cleanerversion.git@pretix#egg=cleanerversion django-compressor +reportlab +-e git+https://github.com/pretix/PyPDF2.git@pretix#egg=PyPDF2 # Deployment / static file compilation requirements BeautifulSoup4