import json import logging from io import BytesIO from django.contrib import messages from django.contrib.staticfiles import finders from django.core.files import File from django.core.files.storage import default_storage from django.db import transaction from django.http import Http404 from django.shortcuts import redirect from django.templatetags.static import static from django.urls import reverse from django.utils.functional import cached_property from django.utils.translation import gettext, ugettext_lazy as _ from django.views import View from django.views.generic import CreateView, DeleteView, DetailView, ListView from reportlab.lib import pagesizes from reportlab.pdfgen import canvas from pretix.base.models import ( CachedCombinedTicket, CachedFile, CachedTicket, OrderPosition, ) from pretix.base.pdf import Renderer from pretix.control.permissions import EventPermissionRequiredMixin from pretix.control.views.pdf import BaseEditorView from pretix.plugins.ticketoutputpdf.forms import TicketLayoutForm from pretix.plugins.ticketoutputpdf.ticketoutput import PdfTicketOutput from .models import TicketLayout logger = logging.getLogger(__name__) class EditorView(BaseEditorView): title = _('Default ticket layout') def get_output(self, *args, **kwargs): return PdfTicketOutput(self.request.event, *args, **kwargs) def save_layout(self): super().save_layout() CachedTicket.objects.filter( order_position__order__event=self.request.event, provider='pdf' ).delete() CachedCombinedTicket.objects.filter( order__event=self.request.event, provider='pdf' ).delete() def get_layout_settings_key(self): return 'ticketoutput_pdf_layout' def get_background_settings_key(self): return 'ticketoutput_pdf_background' def get_default_background(self): return static('pretixpresale/pdf/ticket_default_a4.pdf') def generate(self, p: OrderPosition, override_layout=None, override_background=None): prov = self.get_output( override_layout=override_layout, override_background=override_background ) fname, mimet, data = prov.generate(p) return fname, mimet, data def get_current_layout(self): prov = self.get_output() return ( self.request.event.settings.get(self.get_layout_settings_key(), as_type=list) or prov._default_layout() ) class LayoutListView(EventPermissionRequiredMixin, ListView): model = TicketLayout permission = ('can_change_event_settings') template_name = 'pretixplugins/ticketoutputpdf/index.html' context_object_name = 'layouts' def get_queryset(self): return self.request.event.ticket_layouts.prefetch_related('item_assignments') class LayoutCreate(EventPermissionRequiredMixin, CreateView): model = TicketLayout form_class = TicketLayoutForm template_name = 'pretixplugins/ticketoutputpdf/edit.html' permission = 'can_change_event_settings' context_object_name = 'layout' success_url = '/ignored' @transaction.atomic def form_valid(self, form): form.instance.event = self.request.event if not self.request.event.ticket_layouts.filter(default=True).exists(): form.instance.default = True messages.success(self.request, _('The new ticket layout has been created.')) super().form_valid(form) form.instance.log_action('pretix.plugins.ticketoutputpdf.layout.added', user=self.request.user, data=dict(form.cleaned_data)) return redirect(reverse('plugins:ticketoutputpdf:edit', kwargs={ 'organizer': self.request.event.organizer.slug, 'event': self.request.event.slug, 'layout': form.instance.pk })) def form_invalid(self, form): messages.error(self.request, _('We could not save your changes. See below for details.')) return super().form_invalid(form) def get_context_data(self, **kwargs): return super().get_context_data(**kwargs) class LayoutSetDefault(EventPermissionRequiredMixin, DetailView): model = TicketLayout permission = 'can_change_event_settings' def get_object(self, queryset=None) -> TicketLayout: try: return self.request.event.ticket_layouts.get( id=self.kwargs['layout'] ) except TicketLayout.DoesNotExist: raise Http404(_("The requested layout does not exist.")) @transaction.atomic def post(self, request, *args, **kwargs): messages.success(self.request, _('Your changes have been saved.')) obj = self.get_object() self.request.event.ticket_layouts.exclude(pk=obj.pk).update(default=False) obj.default = True obj.save(update_fields=['default']) return redirect(self.get_success_url()) def get_success_url(self) -> str: return reverse('plugins:ticketoutputpdf:index', kwargs={ 'organizer': self.request.event.organizer.slug, 'event': self.request.event.slug, }) class LayoutDelete(EventPermissionRequiredMixin, DeleteView): model = TicketLayout template_name = 'pretixplugins/ticketoutputpdf/delete.html' permission = 'can_change_event_settings' context_object_name = 'layout' def get_object(self, queryset=None) -> TicketLayout: try: return self.request.event.ticket_layouts.get( id=self.kwargs['layout'] ) except TicketLayout.DoesNotExist: raise Http404(_("The requested layout does not exist.")) @transaction.atomic def delete(self, request, *args, **kwargs): self.object = self.get_object() self.object.log_action(action='pretix.plugins.ticketoutputpdf.layout.deleted', user=request.user) self.object.delete() if not self.request.event.ticket_layouts.filter(default=True).exists(): f = self.request.event.ticket_layouts.first() if f: f.default = True f.save(update_fields=['default']) messages.success(self.request, _('The selected ticket layout been deleted.')) return redirect(self.get_success_url()) def get_success_url(self) -> str: return reverse('plugins:ticketoutputpdf:index', kwargs={ 'organizer': self.request.event.organizer.slug, 'event': self.request.event.slug, }) class LayoutGetDefault(EventPermissionRequiredMixin, View): permission = 'can_change_event_settings' def get(self, request, *args, **kwargs): layout = self.request.event.ticket_layouts.get_or_create( default=True, defaults={ 'name': gettext('Default layout'), } )[0] return redirect(reverse('plugins:ticketoutputpdf:edit', kwargs={ 'organizer': self.request.event.organizer.slug, 'event': self.request.event.slug, 'layout': layout.pk })) class LayoutEditorView(BaseEditorView): @cached_property def layout(self): try: return self.request.event.ticket_layouts.get( id=self.kwargs['layout'] ) except TicketLayout.DoesNotExist: raise Http404(_("The requested layout does not exist.")) @property def title(self): return _('Ticket PDF layout: {}').format(self.layout) def save_layout(self): self.layout.layout = self.request.POST.get("data") self.layout.save(update_fields=['layout']) self.layout.log_action(action='pretix.plugins.ticketoutputpdf.layout.changed', user=self.request.user, data={'layout': self.request.POST.get("data")}) CachedTicket.objects.filter( order_position__order__event=self.request.event, provider='pdf' ).delete() CachedCombinedTicket.objects.filter( order__event=self.request.event, provider='pdf' ).delete() def get_default_background(self): return static('pretixpresale/pdf/ticket_default_a4.pdf') def generate(self, op: OrderPosition, override_layout=None, override_background=None): Renderer._register_fonts() buffer = BytesIO() if override_background: bgf = default_storage.open(override_background.name, "rb") elif isinstance(self.layout.background, File) and self.layout.background.name: bgf = default_storage.open(self.layout.background.name, "rb") else: bgf = open(finders.find('pretixpresale/pdf/ticket_default_a4.pdf'), "rb") r = Renderer( self.request.event, override_layout or self.get_current_layout(), bgf, ) p = canvas.Canvas(buffer, pagesize=pagesizes.A4) r.draw_page(p, op.order, op) p.save() outbuffer = r.render_background(buffer, 'Ticket') return 'ticket.pdf', 'application/pdf', outbuffer.read() def get_current_layout(self): return json.loads(self.layout.layout) def get_current_background(self): return self.layout.background.url if self.layout.background else self.get_default_background() def save_background(self, f: CachedFile): if self.layout.background: self.layout.background.delete() self.layout.background.save('background.pdf', f.file) CachedTicket.objects.filter( order_position__order__event=self.request.event, provider='pdf' ).delete() CachedCombinedTicket.objects.filter( order__event=self.request.event, provider='pdf' ).delete()