forked from CGM_Public/pretix_original
28
src/pretix/plugins/badges/__init__.py
Normal file
28
src/pretix/plugins/badges/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
|
||||
from pretix import __version__ as version
|
||||
|
||||
|
||||
class BadgesApp(AppConfig):
|
||||
name = 'pretix.plugins.badges'
|
||||
verbose_name = _("Badges")
|
||||
|
||||
class PretixPluginMeta:
|
||||
name = _("Badges")
|
||||
author = _("the pretix team")
|
||||
version = version
|
||||
description = _("This plugin allows you to generate badges or name tags for your attendees.")
|
||||
|
||||
def ready(self):
|
||||
from . import signals # NOQA
|
||||
|
||||
def installed(self, event):
|
||||
if not event.badge_layouts.exists():
|
||||
event.badge_layouts.create(
|
||||
name=ugettext('Default'),
|
||||
default=True,
|
||||
)
|
||||
|
||||
|
||||
default_app_config = 'pretix.plugins.badges.BadgesApp'
|
||||
103
src/pretix/plugins/badges/exporters.py
Normal file
103
src/pretix/plugins/badges/exporters.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
from io import BytesIO
|
||||
from typing import Tuple
|
||||
|
||||
from django import forms
|
||||
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.utils.translation import ugettext as _
|
||||
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.plugins.badges.models import BadgeItem, BadgeLayout
|
||||
|
||||
|
||||
def _renderer(event, layout):
|
||||
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()
|
||||
|
||||
for op in positions:
|
||||
r = renderermap.get(op.item_id, default_renderer)
|
||||
if not r:
|
||||
continue
|
||||
|
||||
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)
|
||||
return outbuffer
|
||||
|
||||
|
||||
class BadgeExporter(BaseExporter):
|
||||
identifier = "badges"
|
||||
verbose_name = _("Attendee badges")
|
||||
|
||||
@property
|
||||
def export_form_fields(self):
|
||||
d = OrderedDict(
|
||||
[
|
||||
('items',
|
||||
forms.ModelMultipleChoiceField(
|
||||
queryset=self.event.items.all(),
|
||||
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
|
||||
)),
|
||||
]
|
||||
)
|
||||
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])
|
||||
|
||||
outbuffer = render_pdf(self.event, qs)
|
||||
return 'badges.pdf', 'application/pdf', outbuffer.read()
|
||||
32
src/pretix/plugins/badges/forms.py
Normal file
32
src/pretix/plugins/badges/forms.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.plugins.badges.models import BadgeItem, BadgeLayout
|
||||
|
||||
|
||||
class BadgeLayoutForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BadgeLayout
|
||||
fields = ('name',)
|
||||
|
||||
|
||||
class BadgeItemForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = BadgeItem
|
||||
fields = ('layout',)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
event = kwargs.pop('event')
|
||||
super().__init__(*args, **kwargs)
|
||||
self.fields['layout'].label = _('Badge layout')
|
||||
self.fields['layout'].queryset = event.badge_layouts.all()
|
||||
self.fields['layout'].required = False
|
||||
|
||||
def save(self, commit=True):
|
||||
if self.cleaned_data['layout'] is None:
|
||||
if self.instance.pk:
|
||||
self.instance.delete()
|
||||
else:
|
||||
return
|
||||
else:
|
||||
return super().save(commit=commit)
|
||||
48
src/pretix/plugins/badges/migrations/0001_initial.py
Normal file
48
src/pretix/plugins/badges/migrations/0001_initial.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.11 on 2018-04-16 08:04
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
import pretix.base.models.base
|
||||
import pretix.plugins.badges.models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('pretixbase', '0088_auto_20180328_1217'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='BadgeItem',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('item', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='badge_assignment', to='pretixbase.Item')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='BadgeLayout',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('default', models.BooleanField(default=True, verbose_name='Default')),
|
||||
('name', models.CharField(max_length=190, verbose_name='Name')),
|
||||
('layout', models.TextField()),
|
||||
('background', models.FileField(blank=True, max_length=255, null=True, upload_to=pretix.plugins.badges.models.bg_name)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='badge_layouts', to='pretixbase.Event')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-default', 'name'),
|
||||
},
|
||||
bases=(models.Model, pretix.base.models.base.LoggingMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='badgeitem',
|
||||
name='layout',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='badges.BadgeLayout'),
|
||||
),
|
||||
]
|
||||
0
src/pretix/plugins/badges/migrations/__init__.py
Normal file
0
src/pretix/plugins/badges/migrations/__init__.py
Normal file
51
src/pretix/plugins/badges/models.py
Normal file
51
src/pretix/plugins/badges/models.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import string
|
||||
|
||||
from django.db import models
|
||||
from django.utils.crypto import get_random_string
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import LoggedModel
|
||||
|
||||
|
||||
def bg_name(instance, filename: str) -> str:
|
||||
secret = get_random_string(length=16, allowed_chars=string.ascii_letters + string.digits)
|
||||
return 'pub/{org}/{ev}/badges/{id}-{secret}.pdf'.format(
|
||||
org=instance.event.organizer.slug,
|
||||
ev=instance.event.slug,
|
||||
id=instance.pk,
|
||||
secret=secret
|
||||
)
|
||||
|
||||
|
||||
class BadgeLayout(LoggedModel):
|
||||
event = models.ForeignKey(
|
||||
'pretixbase.Event',
|
||||
on_delete=models.CASCADE,
|
||||
related_name='badge_layouts'
|
||||
)
|
||||
default = models.BooleanField(
|
||||
verbose_name=_('Default'),
|
||||
default=False,
|
||||
)
|
||||
name = models.CharField(
|
||||
max_length=190,
|
||||
verbose_name=_('Name')
|
||||
)
|
||||
layout = 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"}]'
|
||||
)
|
||||
background = models.FileField(null=True, blank=True, upload_to=bg_name, max_length=255)
|
||||
|
||||
class Meta:
|
||||
ordering = ("name",)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class BadgeItem(models.Model):
|
||||
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')
|
||||
129
src/pretix/plugins/badges/signals.py
Normal file
129
src/pretix/plugins/badges/signals.py
Normal file
@@ -0,0 +1,129 @@
|
||||
import copy
|
||||
|
||||
from django.dispatch import receiver
|
||||
from django.template.loader import get_template
|
||||
from django.urls import resolve, reverse
|
||||
from django.utils.html import escape
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from pretix.base.models import Event, Order
|
||||
from pretix.base.signals import (
|
||||
event_copy_data, item_copy_data, logentry_display, logentry_object_link,
|
||||
register_data_exporters,
|
||||
)
|
||||
from pretix.control.signals import item_forms, nav_event, order_info
|
||||
from pretix.plugins.badges.forms import BadgeItemForm
|
||||
from pretix.plugins.badges.models import BadgeItem, BadgeLayout
|
||||
|
||||
|
||||
@receiver(nav_event, dispatch_uid="badges_nav")
|
||||
def control_nav_import(sender, request=None, **kwargs):
|
||||
url = resolve(request.path_info)
|
||||
p = (
|
||||
request.user.has_event_permission(request.organizer, request.event, 'can_change_settings')
|
||||
or request.user.has_event_permission(request.organizer, request.event, 'can_view_orders')
|
||||
)
|
||||
if not p:
|
||||
return []
|
||||
return [
|
||||
{
|
||||
'label': _('Badges'),
|
||||
'url': reverse('plugins:badges:index', kwargs={
|
||||
'event': request.event.slug,
|
||||
'organizer': request.event.organizer.slug,
|
||||
}),
|
||||
'active': url.namespace == 'plugins:badges',
|
||||
'icon': 'id-card',
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@receiver(item_forms, dispatch_uid="badges_item_forms")
|
||||
def control_item_forms(sender, request, item, **kwargs):
|
||||
try:
|
||||
inst = BadgeItem.objects.get(item=item)
|
||||
except BadgeItem.DoesNotExist:
|
||||
inst = BadgeItem(item=item)
|
||||
return BadgeItemForm(
|
||||
instance=inst,
|
||||
event=sender,
|
||||
data=(request.POST if request.method == "POST" else None),
|
||||
prefix="badgeitem"
|
||||
)
|
||||
|
||||
|
||||
@receiver(item_copy_data, dispatch_uid="badges_item_copy")
|
||||
def copy_item(sender, source, target, **kwargs):
|
||||
try:
|
||||
inst = BadgeItem.objects.get(item=source)
|
||||
BadgeItem.objects.create(item=target, layout=inst.layout)
|
||||
except BadgeItem.DoesNotExist:
|
||||
pass
|
||||
|
||||
|
||||
@receiver(signal=event_copy_data, dispatch_uid="badges_copy_data")
|
||||
def event_copy_data_receiver(sender, other, item_map, **kwargs):
|
||||
layout_map = {}
|
||||
for bl in other.badge_layouts.all():
|
||||
oldid = bl.pk
|
||||
bl = copy.copy(bl)
|
||||
bl.pk = None
|
||||
bl.event = sender
|
||||
bl.save()
|
||||
layout_map[oldid] = bl
|
||||
|
||||
for bi in BadgeItem.objects.filter(item__event=other):
|
||||
BadgeItem.objects.create(item=item_map.get(bi.item_id), layout=layout_map.get(bi.layout_id))
|
||||
|
||||
|
||||
@receiver(register_data_exporters, dispatch_uid="badges_export_all")
|
||||
def register_pdf(sender, **kwargs):
|
||||
from .exporters import BadgeExporter
|
||||
return BadgeExporter
|
||||
|
||||
|
||||
@receiver(order_info, dispatch_uid="badges_control_order_info")
|
||||
def control_order_info(sender: Event, request, order: Order, **kwargs):
|
||||
|
||||
template = get_template('pretixplugins/badges/control_order_info.html')
|
||||
|
||||
ctx = {
|
||||
'order': order,
|
||||
'request': request,
|
||||
'event': sender,
|
||||
}
|
||||
return template.render(ctx, request=request)
|
||||
|
||||
|
||||
@receiver(signal=logentry_display, dispatch_uid="badges_logentry_display")
|
||||
def badges_logentry_display(sender, logentry, **kwargs):
|
||||
if not logentry.action_type.startswith('pretix.plugins.badges'):
|
||||
return
|
||||
|
||||
plains = {
|
||||
'pretix.plugins.badges.layout.added': _('Badge layout created.'),
|
||||
'pretix.plugins.badges.layout.deleted': _('Badge layout deleted.'),
|
||||
'pretix.plugins.badges.layout.changed': _('Badge layout changed.'),
|
||||
}
|
||||
|
||||
if logentry.action_type in plains:
|
||||
return plains[logentry.action_type]
|
||||
|
||||
|
||||
@receiver(signal=logentry_object_link, dispatch_uid="badges_logentry_object_link")
|
||||
def badges_logentry_object_link(sender, logentry, **kwargs):
|
||||
if not logentry.action_type.startswith('pretix.plugins.badges.layout') or not isinstance(logentry.content_object,
|
||||
BadgeLayout):
|
||||
return
|
||||
|
||||
a_text = _('Badge layout {val}')
|
||||
a_map = {
|
||||
'href': reverse('plugins:badges:edit', kwargs={
|
||||
'event': sender.slug,
|
||||
'organizer': sender.organizer.slug,
|
||||
'layout': logentry.content_object.id
|
||||
}),
|
||||
'val': escape(logentry.content_object.name),
|
||||
}
|
||||
a_map['val'] = '<a href="{href}">{val}</a>'.format_map(a_map)
|
||||
return a_text.format_map(a_map)
|
||||
Binary file not shown.
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="148mm"
|
||||
height="105mm"
|
||||
viewBox="0 0 148 105"
|
||||
version="1.1"
|
||||
id="svg1236"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
sodipodi:docname="badge_default_a6l.svg">
|
||||
<defs
|
||||
id="defs1230" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="-357.14286"
|
||||
inkscape:cy="560"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1916"
|
||||
inkscape:window-height="1023"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="36"
|
||||
inkscape:window-maximized="0" />
|
||||
<metadata
|
||||
id="metadata1233">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Ebene 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-192)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
24
src/pretix/plugins/badges/tasks.py
Normal file
24
src/pretix/plugins/badges/tasks.py
Normal file
@@ -0,0 +1,24 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
from django.core.files.base import ContentFile
|
||||
|
||||
from pretix.base.models import (
|
||||
CachedFile, Event, OrderPosition, cachedfile_name,
|
||||
)
|
||||
from pretix.celery_app import app
|
||||
|
||||
from .exporters import render_pdf
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.task()
|
||||
def badges_create_pdf(fileid: int, event: int, orders: List[int]) -> int:
|
||||
file = CachedFile.objects.get(id=fileid)
|
||||
event = Event.objects.get(id=event)
|
||||
|
||||
pdfcontent = render_pdf(event, OrderPosition.objects.filter(order_id__in=orders))
|
||||
file.file.save(cachedfile_name(file, file.filename), ContentFile(pdfcontent.read()))
|
||||
file.save()
|
||||
return file.pk
|
||||
@@ -0,0 +1,20 @@
|
||||
{% load i18n %}
|
||||
{% load eventurl %}
|
||||
{% load bootstrap3 %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">
|
||||
{% trans "Badges" %}
|
||||
</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url "plugins:badges:print" event=event.slug organizer=event.organizer.slug %}?code={{ order.code }}"
|
||||
method="post" data-asynctask data-asynctask-download data-asynctask-long class="form-inline">
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<span class="fa fa-print"></span>
|
||||
{% trans "Print badges" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,20 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}{% trans "Badges" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Badges" %}</h1>
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
<p>{% blocktrans %}Are you sure you want to delete the badge layout <strong>{{ layout }}</strong>?{% endblocktrans %}</p>
|
||||
<div class="form-group submit-group">
|
||||
<a href="{% url "plugins:badges:index" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn
|
||||
btn-default btn-cancel">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-danger btn-save">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,39 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap3 %}
|
||||
{% block title %}
|
||||
{% if layout %}
|
||||
{% blocktrans with name=layout.name %}Badge layout: {{ name }}{% endblocktrans %}
|
||||
{% else %}
|
||||
{% trans "Badge layout" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
{% if layout %}
|
||||
<h1>{% blocktrans with name=layout.name %}Badge layout: {{ name }}{% endblocktrans %}</h1>
|
||||
{% else %}
|
||||
<h1>{% trans "Badge layout" %}</h1>
|
||||
{% endif %}
|
||||
<form action="" method="post" class="form-horizontal">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form_errors form %}
|
||||
{% bootstrap_field form.name layout="control" %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">
|
||||
{% trans "Badge design" %}
|
||||
</label>
|
||||
<div class="col-md-9">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You can modify the design after you saved this page.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group submit-group">
|
||||
<button type="submit" class="btn btn-primary btn-save">
|
||||
{% trans "Save" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,81 @@
|
||||
{% extends "pretixcontrol/event/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load money %}
|
||||
{% block title %}{% trans "Badges" %}{% endblock %}
|
||||
{% block content %}
|
||||
<h1>{% trans "Badges" %}</h1>
|
||||
{% if layouts|length == 0 %}
|
||||
<div class="empty-collection">
|
||||
<p>
|
||||
{% blocktrans trimmed %}
|
||||
You haven't created any badge layouts yet.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<a href="{% url "plugins:badges:add" organizer=request.event.organizer.slug event=request.event.slug %}"
|
||||
class="btn btn-primary btn-lg"><i class="fa fa-plus"></i> {% trans "Create a new badge layout" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<a href="{% url "plugins:badges:add" organizer=request.event.organizer.slug event=request.event.slug %}" class="btn btn-default"><i class="fa fa-plus"></i> {% trans "Create a new badge layout" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url "control:event.orders.export" organizer=request.event.organizer.slug event=request.event.slug %}?identifier=badges" class="btn btn-primary"><i class="fa fa-print"></i> {% trans "Print badges" %}
|
||||
</a>
|
||||
</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover table-quotas">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "Name" %}</th>
|
||||
<th>{% trans "Default" %}</th>
|
||||
<th class="action-col-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in layouts %}
|
||||
<tr>
|
||||
<td>
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<strong><a href="{% url "plugins:badges:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}">
|
||||
{{ l.name }}
|
||||
</a></strong>
|
||||
{% else %}
|
||||
<strong>{{ l.name }}</strong>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if l.default %}
|
||||
<span class="text-success">
|
||||
<span class="fa fa-check"></span>
|
||||
{% trans "Default" %}
|
||||
</span>
|
||||
{% elif "can_change_event_settings" in request.eventpermset %}
|
||||
<form class="form-inline" method="post"
|
||||
action="{% url "plugins:badges:default" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}"
|
||||
>
|
||||
{% csrf_token %}
|
||||
<button class="btn btn-default btn-sm">
|
||||
{% trans "Make default" %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-right">
|
||||
{% if "can_change_event_settings" in request.eventpermset %}
|
||||
<a href="{% url "plugins:badges:edit" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-default btn-sm"><i class="fa fa-edit"></i></a>
|
||||
<a href="{% url "plugins:badges:delete" organizer=request.event.organizer.slug event=request.event.slug layout=l.id %}" class="btn btn-danger btn-sm"><i class="fa fa-trash"></i></a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% include "pretixcontrol/pagination.html" %}
|
||||
{% endblock %}
|
||||
21
src/pretix/plugins/badges/urls.py
Normal file
21
src/pretix/plugins/badges/urls.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import (
|
||||
LayoutCreate, LayoutDelete, LayoutEditorView, LayoutListView,
|
||||
LayoutSetDefault, OrderPrintDo,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/$',
|
||||
LayoutListView.as_view(), name='index'),
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/print$',
|
||||
OrderPrintDo.as_view(), name='print'),
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/add$',
|
||||
LayoutCreate.as_view(), name='add'),
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/default$',
|
||||
LayoutSetDefault.as_view(), name='default'),
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/delete$',
|
||||
LayoutDelete.as_view(), name='delete'),
|
||||
url(r'^control/event/(?P<organizer>[^/]+)/(?P<event>[^/]+)/badges/(?P<layout>\d+)/editor',
|
||||
LayoutEditorView.as_view(), name='edit'),
|
||||
]
|
||||
224
src/pretix/plugins/badges/views.py
Normal file
224
src/pretix/plugins/badges/views.py
Normal file
@@ -0,0 +1,224 @@
|
||||
import json
|
||||
from datetime import timedelta
|
||||
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 get_object_or_404, redirect
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.timezone import now
|
||||
from django.utils.translation import 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 CachedFile, OrderPosition
|
||||
from pretix.base.pdf import Renderer
|
||||
from pretix.base.views.async import AsyncAction
|
||||
from pretix.control.permissions import EventPermissionRequiredMixin
|
||||
from pretix.control.views.pdf import BaseEditorView
|
||||
from pretix.plugins.badges.forms import BadgeLayoutForm
|
||||
from pretix.plugins.badges.tasks import badges_create_pdf
|
||||
|
||||
from .models import BadgeLayout
|
||||
|
||||
|
||||
class LayoutListView(EventPermissionRequiredMixin, ListView):
|
||||
model = BadgeLayout
|
||||
permission = ('can_change_event_settings', 'can_view_orders')
|
||||
template_name = 'pretixplugins/badges/index.html'
|
||||
context_object_name = 'layouts'
|
||||
|
||||
def get_queryset(self):
|
||||
return self.request.event.badge_layouts.prefetch_related('item_assignments')
|
||||
|
||||
|
||||
class LayoutCreate(EventPermissionRequiredMixin, CreateView):
|
||||
model = BadgeLayout
|
||||
form_class = BadgeLayoutForm
|
||||
template_name = 'pretixplugins/badges/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.badge_layouts.filter(default=True).exists():
|
||||
form.instance.default = True
|
||||
messages.success(self.request, _('The new badge layout has been created.'))
|
||||
super().form_valid(form)
|
||||
form.instance.log_action('pretix.plugins.badges.layout.added', user=self.request.user,
|
||||
data=dict(form.cleaned_data))
|
||||
return redirect(reverse('plugins:badges: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 = BadgeLayout
|
||||
permission = 'can_change_event_settings'
|
||||
|
||||
def get_object(self, queryset=None) -> BadgeLayout:
|
||||
try:
|
||||
return self.request.event.badge_layouts.get(
|
||||
id=self.kwargs['layout']
|
||||
)
|
||||
except BadgeLayout.DoesNotExist:
|
||||
raise Http404(_("The requested badge 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.badge_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:badges:index', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
|
||||
class LayoutDelete(EventPermissionRequiredMixin, DeleteView):
|
||||
model = BadgeLayout
|
||||
template_name = 'pretixplugins/badges/delete.html'
|
||||
permission = 'can_change_event_settings'
|
||||
context_object_name = 'layout'
|
||||
|
||||
def get_object(self, queryset=None) -> BadgeLayout:
|
||||
try:
|
||||
return self.request.event.badge_layouts.get(
|
||||
id=self.kwargs['layout']
|
||||
)
|
||||
except BadgeLayout.DoesNotExist:
|
||||
raise Http404(_("The requested badge layout does not exist."))
|
||||
|
||||
@transaction.atomic
|
||||
def delete(self, request, *args, **kwargs):
|
||||
self.object = self.get_object()
|
||||
self.object.log_action(action='pretix.plugins.badges.layout.deleted', user=request.user)
|
||||
self.object.delete()
|
||||
if not self.request.event.badge_layouts.filter(default=True).exists():
|
||||
f = self.request.event.badge_layouts.first()
|
||||
if f:
|
||||
f.default = True
|
||||
f.save(update_fields=['default'])
|
||||
messages.success(self.request, _('The selected badge layout been deleted.'))
|
||||
return redirect(self.get_success_url())
|
||||
|
||||
def get_success_url(self) -> str:
|
||||
return reverse('plugins:badges:index', kwargs={
|
||||
'organizer': self.request.event.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
|
||||
class LayoutEditorView(BaseEditorView):
|
||||
@cached_property
|
||||
def layout(self):
|
||||
try:
|
||||
return self.request.event.badge_layouts.get(
|
||||
id=self.kwargs['layout']
|
||||
)
|
||||
except BadgeLayout.DoesNotExist:
|
||||
raise Http404(_("The requested badge layout does not exist."))
|
||||
|
||||
@property
|
||||
def title(self):
|
||||
return _('Badge 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.badges.layout.changed', user=self.request.user,
|
||||
data={'layout': self.request.POST.get("data")})
|
||||
|
||||
def get_default_background(self):
|
||||
return static('pretixplugins/badges/badge_default_a6l.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('pretixplugins/badges/badge_default_a6l.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, 'Badge')
|
||||
return 'badge.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)
|
||||
|
||||
|
||||
class OrderPrintDo(EventPermissionRequiredMixin, AsyncAction, View):
|
||||
task = badges_create_pdf
|
||||
permission = 'can_view_orders'
|
||||
|
||||
def get_success_message(self, value):
|
||||
return None
|
||||
|
||||
def get_success_url(self, value):
|
||||
return reverse('cachedfile.download', kwargs={'id': str(value)})
|
||||
|
||||
def get_error_url(self):
|
||||
return reverse('control:event.index', kwargs={
|
||||
'organizer': self.request.organizer.slug,
|
||||
'event': self.request.event.slug,
|
||||
})
|
||||
|
||||
def get_error_message(self, exception):
|
||||
if isinstance(exception, str):
|
||||
return exception
|
||||
return super().get_error_message(exception)
|
||||
|
||||
def post(self, request, *args, **kwargs):
|
||||
order = get_object_or_404(self.request.event.orders, code=request.GET.get("code"))
|
||||
cf = CachedFile()
|
||||
cf.date = now()
|
||||
cf.type = 'application/pdf'
|
||||
cf.expires = now() + timedelta(days=3)
|
||||
cf.save()
|
||||
return self.do(
|
||||
str(cf.id),
|
||||
self.request.event.pk,
|
||||
[order.pk],
|
||||
)
|
||||
Reference in New Issue
Block a user