diff --git a/src/pretix/plugins/badges/exporters.py b/src/pretix/plugins/badges/exporters.py index 9288223d65..57b2b42778 100644 --- a/src/pretix/plugins/badges/exporters.py +++ b/src/pretix/plugins/badges/exporters.py @@ -9,6 +9,7 @@ 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 @@ -20,11 +21,14 @@ 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: @@ -45,10 +49,12 @@ def render_pdf(event, positions): 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() @@ -62,6 +68,8 @@ def render_pdf(event, positions): 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 @@ -76,7 +84,9 @@ class BadgeExporter(BaseExporter): [ ('items', forms.ModelMultipleChoiceField( - queryset=self.event.items.all(), + 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'} diff --git a/src/pretix/plugins/badges/forms.py b/src/pretix/plugins/badges/forms.py index 151625c53e..52d664dfd1 100644 --- a/src/pretix/plugins/badges/forms.py +++ b/src/pretix/plugins/badges/forms.py @@ -1,4 +1,6 @@ from django import forms +from django.forms import Field +from django.forms.models import ModelChoiceIterator from django.utils.translation import ugettext_lazy as _ from pretix.plugins.badges.models import BadgeItem, BadgeLayout @@ -10,10 +12,40 @@ class BadgeLayoutForm(forms.ModelForm): fields = ('name',) +NoLayoutSingleton = BadgeLayout(pk='-') + + +class BadgeLayoutIterator(ModelChoiceIterator): + + def __iter__(self): + yield ("-", _("(Do not print badges)")) + yield from super().__iter__() + + def __len__(self): + return super().__len__() + 1 + + +class BadgeLayoutChoiceField(forms.ModelChoiceField): + iterator = BadgeLayoutIterator + + def to_python(self, value): + if value == '-': + return NoLayoutSingleton + return super().to_python(value) + + def validate(self, value): + if value == '-': + return '-' + return Field.validate(self, value) + + class BadgeItemForm(forms.ModelForm): + layout = BadgeLayoutChoiceField(queryset=BadgeLayout.objects.none()) + class Meta: model = BadgeItem fields = ('layout',) + exclude = ('layout',) def __init__(self, *args, **kwargs): event = kwargs.pop('event') @@ -22,6 +54,10 @@ class BadgeItemForm(forms.ModelForm): self.fields['layout'].empty_label = _('(Event default)') self.fields['layout'].queryset = event.badge_layouts.all() self.fields['layout'].required = False + if self.instance.pk and not self.instance.layout_id: + self.initial['layout'] = NoLayoutSingleton + elif self.instance.layout: + self.initial['layout'] = self.instance.layout def save(self, commit=True): if self.cleaned_data['layout'] is None: @@ -29,5 +65,9 @@ class BadgeItemForm(forms.ModelForm): self.instance.delete() else: return + elif self.cleaned_data['layout'] is NoLayoutSingleton: + self.instance.layout = None + self.instance.save() else: + self.instance.layout = self.cleaned_data['layout'] return super().save(commit=commit) diff --git a/src/pretix/plugins/badges/migrations/0002_auto_20190201_1424.py b/src/pretix/plugins/badges/migrations/0002_auto_20190201_1424.py new file mode 100644 index 0000000000..e0844fbfac --- /dev/null +++ b/src/pretix/plugins/badges/migrations/0002_auto_20190201_1424.py @@ -0,0 +1,33 @@ +# Generated by Django 2.1.5 on 2019-02-01 14:24 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('badges', '0001_initial'), + ] + + operations = [ + migrations.AlterModelOptions( + name='badgelayout', + options={'ordering': ('name',)}, + ), + migrations.AlterField( + model_name='badgeitem', + name='layout', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='item_assignments', to='badges.BadgeLayout'), + ), + migrations.AlterField( + model_name='badgelayout', + name='default', + field=models.BooleanField(default=False, verbose_name='Default'), + ), + migrations.AlterField( + model_name='badgelayout', + name='layout', + field=models.TextField(default='[{"type":"textarea","left":"13.09","bottom":"49.73","fontsize":"23.6","color":[0,0,0,1],"fontfamily":"Open Sans","bold":true,"italic":false,"width":"121.83","content":"attendee_name","text":"Max Mustermann","align":"center"}]'), + ), + ] diff --git a/src/pretix/plugins/badges/models.py b/src/pretix/plugins/badges/models.py index 384d2842bb..dadce25ebe 100644 --- a/src/pretix/plugins/badges/models.py +++ b/src/pretix/plugins/badges/models.py @@ -46,6 +46,9 @@ class BadgeLayout(LoggedModel): class BadgeItem(models.Model): + # If no BadgeItem exists => use default + # If BadgeItem exists with layout=None => don't print item = models.OneToOneField('pretixbase.Item', null=True, blank=True, related_name='badge_assignment', on_delete=models.CASCADE) - layout = models.ForeignKey('BadgeLayout', on_delete=models.CASCADE, related_name='item_assignments') + layout = models.ForeignKey('BadgeLayout', on_delete=models.CASCADE, related_name='item_assignments', + null=True, blank=True) diff --git a/src/pretix/plugins/badges/signals.py b/src/pretix/plugins/badges/signals.py index 951c7e67be..3b29ec386a 100644 --- a/src/pretix/plugins/badges/signals.py +++ b/src/pretix/plugins/badges/signals.py @@ -1,5 +1,6 @@ import copy import json +from collections import defaultdict from django.dispatch import receiver from django.template.loader import get_template @@ -97,11 +98,27 @@ def register_pdf(sender, **kwargs): return BadgeExporter +def _cached_rendermap(event): + if hasattr(event, '_cached_rendermap'): + return event._cached_renderermap + renderermap = { + bi.item_id: bi.layout_id + for bi in BadgeItem.objects.select_related('layout').filter(item__event=event) + } + try: + default_renderer = event.badge_layouts.get(default=True).pk + except BadgeLayout.DoesNotExist: + default_renderer = None + event._cached_renderermap = defaultdict(lambda: default_renderer) + event._cached_renderermap.update(renderermap) + return event._cached_renderermap + + @receiver(order_position_buttons, dispatch_uid="badges_control_order_buttons") def control_order_position_info(sender: Event, position, request, order: Order, **kwargs): - + if _cached_rendermap(sender)[position.item_id] is None: + return '' template = get_template('pretixplugins/badges/control_order_position_buttons.html') - ctx = { 'order': order, 'request': request, @@ -113,6 +130,9 @@ def control_order_position_info(sender: Event, position, request, order: Order, @receiver(order_info, dispatch_uid="badges_control_order_info") def control_order_info(sender: Event, request, order: Order, **kwargs): + cm = _cached_rendermap(sender) + if all(cm[p.item_id] is None for p in order.positions.all()): + return '' template = get_template('pretixplugins/badges/control_order_info.html') diff --git a/src/pretix/plugins/badges/tasks.py b/src/pretix/plugins/badges/tasks.py index 5c3d7a092e..a1cd1a0174 100644 --- a/src/pretix/plugins/badges/tasks.py +++ b/src/pretix/plugins/badges/tasks.py @@ -6,6 +6,7 @@ from django.core.files.base import ContentFile from pretix.base.models import ( CachedFile, Event, OrderPosition, cachedfile_name, ) +from pretix.base.services.orders import OrderError from pretix.celery_app import app from .exporters import render_pdf @@ -13,7 +14,7 @@ from .exporters import render_pdf logger = logging.getLogger(__name__) -@app.task() +@app.task(throws=(OrderError,)) def badges_create_pdf(fileid: int, event: int, positions: List[int]) -> int: file = CachedFile.objects.get(id=fileid) event = Event.objects.get(id=event) diff --git a/src/pretix/plugins/badges/views.py b/src/pretix/plugins/badges/views.py index 50e229c24a..a1911a89fb 100644 --- a/src/pretix/plugins/badges/views.py +++ b/src/pretix/plugins/badges/views.py @@ -192,6 +192,7 @@ class LayoutEditorView(BaseEditorView): class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View): task = badges_create_pdf permission = 'can_view_orders' + known_errortypes = ['OrderError'] def get_success_message(self, value): return None