Badges: Allow to disable per-product

This commit is contained in:
Raphael Michel
2019-02-01 16:20:30 +01:00
parent c9415cba2b
commit f77b551aa6
7 changed files with 113 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ from django.contrib.staticfiles import finders
from django.core.files import File from django.core.files import File
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.core.files.storage import default_storage from django.core.files.storage import default_storage
from django.db.models import Exists, OuterRef
from django.db.models.functions import Coalesce from django.db.models.functions import Coalesce
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from jsonfallback.functions import JSONExtract from jsonfallback.functions import JSONExtract
@@ -20,11 +21,14 @@ from pretix.base.exporter import BaseExporter
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Order, OrderPosition from pretix.base.models import Order, OrderPosition
from pretix.base.pdf import Renderer from pretix.base.pdf import Renderer
from pretix.base.services.orders import OrderError
from pretix.base.settings import PERSON_NAME_SCHEMES from pretix.base.settings import PERSON_NAME_SCHEMES
from pretix.plugins.badges.models import BadgeItem, BadgeLayout from pretix.plugins.badges.models import BadgeItem, BadgeLayout
def _renderer(event, layout): def _renderer(event, layout):
if layout is None:
return None
if isinstance(layout.background, File) and layout.background.name: if isinstance(layout.background, File) and layout.background.name:
bgf = default_storage.open(layout.background.name, "rb") bgf = default_storage.open(layout.background.name, "rb")
else: else:
@@ -45,10 +49,12 @@ def render_pdf(event, positions):
default_renderer = None default_renderer = None
merger = PdfFileMerger() merger = PdfFileMerger()
any = False
for op in positions: for op in positions:
r = renderermap.get(op.item_id, default_renderer) r = renderermap.get(op.item_id, default_renderer)
if not r: if not r:
continue continue
any = True
with language(op.order.locale): with language(op.order.locale):
buffer = BytesIO() buffer = BytesIO()
@@ -62,6 +68,8 @@ def render_pdf(event, positions):
merger.write(outbuffer) merger.write(outbuffer)
merger.close() merger.close()
outbuffer.seek(0) outbuffer.seek(0)
if not any:
raise OrderError(_("None of the selected products is configured to print badges."))
return outbuffer return outbuffer
@@ -76,7 +84,9 @@ class BadgeExporter(BaseExporter):
[ [
('items', ('items',
forms.ModelMultipleChoiceField( 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'), label=_('Limit to products'),
widget=forms.CheckboxSelectMultiple( widget=forms.CheckboxSelectMultiple(
attrs={'class': 'scrolling-multiple-choice'} attrs={'class': 'scrolling-multiple-choice'}

View File

@@ -1,4 +1,6 @@
from django import forms from django import forms
from django.forms import Field
from django.forms.models import ModelChoiceIterator
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from pretix.plugins.badges.models import BadgeItem, BadgeLayout from pretix.plugins.badges.models import BadgeItem, BadgeLayout
@@ -10,10 +12,40 @@ class BadgeLayoutForm(forms.ModelForm):
fields = ('name',) 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): class BadgeItemForm(forms.ModelForm):
layout = BadgeLayoutChoiceField(queryset=BadgeLayout.objects.none())
class Meta: class Meta:
model = BadgeItem model = BadgeItem
fields = ('layout',) fields = ('layout',)
exclude = ('layout',)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
event = kwargs.pop('event') event = kwargs.pop('event')
@@ -22,6 +54,10 @@ class BadgeItemForm(forms.ModelForm):
self.fields['layout'].empty_label = _('(Event default)') self.fields['layout'].empty_label = _('(Event default)')
self.fields['layout'].queryset = event.badge_layouts.all() self.fields['layout'].queryset = event.badge_layouts.all()
self.fields['layout'].required = False 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): def save(self, commit=True):
if self.cleaned_data['layout'] is None: if self.cleaned_data['layout'] is None:
@@ -29,5 +65,9 @@ class BadgeItemForm(forms.ModelForm):
self.instance.delete() self.instance.delete()
else: else:
return return
elif self.cleaned_data['layout'] is NoLayoutSingleton:
self.instance.layout = None
self.instance.save()
else: else:
self.instance.layout = self.cleaned_data['layout']
return super().save(commit=commit) return super().save(commit=commit)

View File

@@ -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"}]'),
),
]

View File

@@ -46,6 +46,9 @@ class BadgeLayout(LoggedModel):
class BadgeItem(models.Model): 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', item = models.OneToOneField('pretixbase.Item', null=True, blank=True, related_name='badge_assignment',
on_delete=models.CASCADE) 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)

View File

@@ -1,5 +1,6 @@
import copy import copy
import json import json
from collections import defaultdict
from django.dispatch import receiver from django.dispatch import receiver
from django.template.loader import get_template from django.template.loader import get_template
@@ -97,11 +98,27 @@ def register_pdf(sender, **kwargs):
return BadgeExporter 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") @receiver(order_position_buttons, dispatch_uid="badges_control_order_buttons")
def control_order_position_info(sender: Event, position, request, order: Order, **kwargs): 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') template = get_template('pretixplugins/badges/control_order_position_buttons.html')
ctx = { ctx = {
'order': order, 'order': order,
'request': request, '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") @receiver(order_info, dispatch_uid="badges_control_order_info")
def control_order_info(sender: Event, request, order: Order, **kwargs): 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') template = get_template('pretixplugins/badges/control_order_info.html')

View File

@@ -6,6 +6,7 @@ from django.core.files.base import ContentFile
from pretix.base.models import ( from pretix.base.models import (
CachedFile, Event, OrderPosition, cachedfile_name, CachedFile, Event, OrderPosition, cachedfile_name,
) )
from pretix.base.services.orders import OrderError
from pretix.celery_app import app from pretix.celery_app import app
from .exporters import render_pdf from .exporters import render_pdf
@@ -13,7 +14,7 @@ from .exporters import render_pdf
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@app.task() @app.task(throws=(OrderError,))
def badges_create_pdf(fileid: int, event: int, positions: List[int]) -> int: def badges_create_pdf(fileid: int, event: int, positions: List[int]) -> int:
file = CachedFile.objects.get(id=fileid) file = CachedFile.objects.get(id=fileid)
event = Event.objects.get(id=event) event = Event.objects.get(id=event)

View File

@@ -192,6 +192,7 @@ class LayoutEditorView(BaseEditorView):
class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View): class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
task = badges_create_pdf task = badges_create_pdf
permission = 'can_view_orders' permission = 'can_view_orders'
known_errortypes = ['OrderError']
def get_success_message(self, value): def get_success_message(self, value):
return None return None