import json from collections import OrderedDict from io import BytesIO from typing import Tuple from django import forms from django.conf import settings from django.contrib.staticfiles import finders from django.core.files import File from django.core.files.base import ContentFile from django.core.files.storage import default_storage from django.db.models import Exists, OuterRef from django.db.models.functions import Coalesce from django.utils.translation import ugettext as _ from jsonfallback.functions import JSONExtract from PyPDF2 import PdfFileMerger from reportlab.lib import pagesizes from reportlab.pdfgen import canvas from pretix.base.exporter import BaseExporter from pretix.base.i18n import language from pretix.base.models import Order, OrderPosition from pretix.base.pdf import Renderer from pretix.base.services.orders import OrderError from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.plugins.badges.models import BadgeItem, BadgeLayout def _renderer(event, layout): if layout is None: return None if isinstance(layout.background, File) and layout.background.name: bgf = default_storage.open(layout.background.name, "rb") else: bgf = open(finders.find('pretixplugins/badges/badge_default_a6l.pdf'), "rb") return Renderer(event, json.loads(layout.layout), bgf) def render_pdf(event, positions): Renderer._register_fonts() renderermap = { bi.item_id: _renderer(event, bi.layout) for bi in BadgeItem.objects.select_related('layout').filter(item__event=event) } try: default_renderer = _renderer(event, event.badge_layouts.get(default=True)) except BadgeLayout.DoesNotExist: default_renderer = None merger = PdfFileMerger() any = False for op in positions: r = renderermap.get(op.item_id, default_renderer) if not r: continue any = True with language(op.order.locale): buffer = BytesIO() p = canvas.Canvas(buffer, pagesize=pagesizes.A4) r.draw_page(p, op.order, op) p.save() outbuffer = r.render_background(buffer, 'Badge') merger.append(ContentFile(outbuffer.read())) outbuffer = BytesIO() merger.write(outbuffer) merger.close() outbuffer.seek(0) if not any: raise OrderError(_("None of the selected products is configured to print badges.")) return outbuffer class BadgeExporter(BaseExporter): identifier = "badges" verbose_name = _("Attendee badges") @property def export_form_fields(self): name_scheme = PERSON_NAME_SCHEMES[self.event.settings.name_scheme] d = OrderedDict( [ ('items', forms.ModelMultipleChoiceField( queryset=self.event.items.annotate( no_badging=Exists(BadgeItem.objects.filter(item=OuterRef('pk'), layout__isnull=True)) ).exclude(no_badging=True), label=_('Limit to products'), widget=forms.CheckboxSelectMultiple( attrs={'class': 'scrolling-multiple-choice'} ), initial=self.event.items.filter(admission=True) )), ('include_pending', forms.BooleanField( label=_('Include pending orders'), required=False )), ('order_by', forms.ChoiceField( label=_('Sort by'), choices=[ ('name', _('Attendee name')), ('code', _('Order code')), ] + ([ ('name:{}'.format(k), _('Attendee name: {part}').format(part=label)) for k, label, w in name_scheme['fields'] ] if settings.JSON_FIELD_AVAILABLE and len(name_scheme['fields']) > 1 else []), )), ] ) return d def render(self, form_data: dict) -> Tuple[str, str, str]: qs = OrderPosition.objects.filter( order__event=self.event, item_id__in=form_data['items'] ).prefetch_related( 'answers', 'answers__question' ).select_related('order', 'item', 'variation', 'addon_to') if form_data.get('include_pending'): qs = qs.filter(order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING]) else: qs = qs.filter(order__status__in=[Order.STATUS_PAID]) if form_data.get('order_by') == 'name': qs = qs.order_by('attendee_name_cached', 'order__code') elif form_data.get('order_by') == 'code': qs = qs.order_by('order__code') elif form_data.get('order_by', '').startswith('name:'): part = form_data['order_by'][5:] qs = qs.annotate( resolved_name=Coalesce('attendee_name_parts', 'addon_to__attendee_name_parts', 'order__invoice_address__name_parts') ).annotate( resolved_name_part=JSONExtract('resolved_name', part) ).order_by( 'resolved_name_part' ) outbuffer = render_pdf(self.event, qs) return 'badges.pdf', 'application/pdf', outbuffer.read()