Badges: Create templates for common paper sizes (#3660)

* Badges: Create templates for common paper sizes

* Add more sizes

* format lazy
This commit is contained in:
Raphael Michel
2023-11-03 12:37:20 +01:00
committed by GitHub
parent ec2085f125
commit 0400b577bb
4 changed files with 273 additions and 14 deletions

View File

@@ -25,9 +25,18 @@ from django.forms.models import ModelChoiceIterator
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from pretix.plugins.badges.models import BadgeItem, BadgeLayout from pretix.plugins.badges.models import BadgeItem, BadgeLayout
from pretix.plugins.badges.templates import TEMPLATES
class BadgeLayoutForm(forms.ModelForm): class BadgeLayoutForm(forms.ModelForm):
template = forms.ChoiceField(
label=_('Template'),
help_text=_('You can modify the layout or change to a different page size in the next step.'),
choices=((k, v['label']) for k, v in TEMPLATES.items()),
widget=forms.RadioSelect,
initial='a6l',
)
class Meta: class Meta:
model = BadgeLayout model = BadgeLayout
fields = ('name',) fields = ('name',)

View File

@@ -0,0 +1,237 @@
#
# This file is part of pretix (Community Edition).
#
# Copyright (C) 2014-2020 Raphael Michel and contributors
# Copyright (C) 2020-2021 rami.io GmbH and contributors
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
# Public License as published by the Free Software Foundation in version 3 of the License.
#
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
# this file, see <https://pretix.eu/about/en/license>.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
# <https://www.gnu.org/licenses/>.
#
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _
from reportlab.lib import pagesizes
from reportlab.lib.units import mm
def _simple_template(w, h):
name_size = max(min(20, w / 20), 12) # Heuristic for font size
company_size = name_size - 2
return [
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "%.2f" % (((h - company_size * 1.5 - name_size) / 2 + company_size * 1.5) / mm),
"fontsize": name_size,
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": True,
"italic": False,
"width": "%.2f" % (w / mm - 10),
"downward": False,
"content": "attendee_name",
"text": "John Doe",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "%.2f" % ((((h - company_size * 1.5 - name_size) / 2) + company_size) / mm),
"fontsize": company_size,
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": False,
"italic": False,
"width": "%.2f" % (w / mm - 10),
"downward": True,
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
]
TEMPLATES = {
"a6l": {
"label": _("A6 landscape"),
"pagesize": pagesizes.landscape(pagesizes.A6),
"layout": _simple_template(*pagesizes.landscape(pagesizes.A6)),
},
"a6p": {
"label": _("A6 portrait"),
"pagesize": pagesizes.portrait(pagesizes.A6),
"layout": _simple_template(*pagesizes.portrait(pagesizes.A6)),
},
"a7l": {
"label": _("A7 landscape"),
"pagesize": pagesizes.landscape(pagesizes.A7),
"layout": _simple_template(*pagesizes.landscape(pagesizes.A7)),
},
"a7p": {
"label": _("A7 portrait"),
"pagesize": pagesizes.portrait(pagesizes.A7),
"layout": _simple_template(*pagesizes.portrait(pagesizes.A7)),
},
"82x203butterfly": {
"label": format_lazy(_("{width} x {height} mm butterfly badge"), width=82, height=203),
"pagesize": (82 * mm, 203 * mm),
"layout": [
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "152.55",
"fontsize": "20.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": True,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_name",
"text": "John Doe",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "5.00",
"bottom": "144.55",
"fontsize": "18.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": False,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 0,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "77.10",
"bottom": "34.68",
"fontsize": "20.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": True,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_name",
"text": "John Doe",
"text_i18n": {},
"rotation": 180,
"align": "center",
},
{
"type": "textarea",
"page": 1,
"locale": "",
"left": "77.06",
"bottom": "44.28",
"fontsize": "18.0",
"lineheight": "1",
"color": [0, 0, 0, 1],
"fontfamily": "Open Sans",
"bold": False,
"italic": False,
"width": "72.00",
"downward": False,
"content": "attendee_company",
"text": "Sample company",
"text_i18n": {},
"rotation": 180,
"align": "center",
},
],
},
"100x50": {
"label": format_lazy(_("{width} x {height} mm label"), width=100, height=50),
"pagesize": (100 * mm, 50 * mm),
"layout": _simple_template(100 * mm, 50 * mm),
},
"83x50": {
"label": format_lazy(_("{width} x {height} mm label"), width=83, height=50),
"pagesize": (83 * mm, 50 * mm),
"layout": _simple_template(83 * mm, 50 * mm),
},
"80x50": {
"label": format_lazy(_("{width} x {height} mm label"), width=80, height=50),
"pagesize": (80 * mm, 50 * mm),
"layout": _simple_template(80 * mm, 50 * mm),
},
"75x52": {
"label": format_lazy(_("{width} x {height} mm label"), width=75, height=52),
"pagesize": (75 * mm, 52 * mm),
"layout": _simple_template(75 * mm, 52 * mm),
},
"70x36": {
"label": format_lazy(_("{width} x {height} mm label"), width=70, height=36),
"pagesize": (70 * mm, 36 * mm),
"layout": _simple_template(70 * mm, 36 * mm),
},
"63x29": {
"label": format_lazy(_("{width} x {height} mm label"), width=63, height=29),
"pagesize": (63.5 * mm, 29.6 * mm),
"layout": _simple_template(63.5 * mm, 29.6 * mm),
},
"60x90": {
"label": format_lazy(_("{width} x {height} mm label"), width=60, height=90),
"pagesize": (60 * mm, 90 * mm),
"layout": _simple_template(60 * mm, 90 * mm),
},
"54x90": {
"label": format_lazy(_("{width} x {height} mm label"), width=54, height=90),
"pagesize": (54 * mm, 90 * mm),
"layout": _simple_template(54 * mm, 90 * mm),
},
"50x80": {
"label": format_lazy(_("{width} x {height} mm label"), width=50, height=80),
"pagesize": (50 * mm, 80 * mm),
"layout": _simple_template(50 * mm, 80 * mm),
},
"40x75": {
"label": format_lazy(_("{width} x {height} mm label"), width=40, height=75),
"pagesize": (40 * mm, 75 * mm),
"layout": _simple_template(40 * mm, 75 * mm),
},
"40x40": {
"label": format_lazy(_("{width} x {height} mm label"), width=40, height=40),
"pagesize": (40 * mm, 40 * mm),
"layout": _simple_template(40 * mm, 40 * mm),
},
}

View File

@@ -18,21 +18,12 @@
{% csrf_token %} {% csrf_token %}
{% bootstrap_form_errors form %} {% bootstrap_form_errors form %}
{% bootstrap_field form.name layout="control" %} {% bootstrap_field form.name layout="control" %}
<div class="form-group"> {% if form.template %}
<label class="col-md-3 control-label"> {% bootstrap_field form.template layout="control" %}
{% trans "Badge design" %} {% endif %}
</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"> <div class="form-group submit-group">
<button type="submit" class="btn btn-primary btn-save"> <button type="submit" class="btn btn-primary btn-save">
{% trans "Save" %} {% trans "Save & continue" %}
</button> </button>
</div> </div>
</form> </form>

View File

@@ -23,9 +23,11 @@ import json
from datetime import timedelta from datetime import timedelta
from io import BytesIO from io import BytesIO
from _decimal import Decimal
from django.contrib import messages from django.contrib import messages
from django.contrib.staticfiles import finders 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.storage import default_storage from django.core.files.storage import default_storage
from django.db import transaction from django.db import transaction
from django.http import Http404 from django.http import Http404
@@ -37,6 +39,7 @@ from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.views import View from django.views import View
from django.views.generic import CreateView, DetailView, ListView from django.views.generic import CreateView, DetailView, ListView
from pypdf import PdfWriter
from reportlab.lib import pagesizes from reportlab.lib import pagesizes
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
@@ -51,6 +54,7 @@ from pretix.plugins.badges.tasks import badges_create_pdf
from ...helpers.compat import CompatDeleteView from ...helpers.compat import CompatDeleteView
from .models import BadgeLayout from .models import BadgeLayout
from .templates import TEMPLATES
class LayoutListView(EventPermissionRequiredMixin, ListView): class LayoutListView(EventPermissionRequiredMixin, ListView):
@@ -71,14 +75,32 @@ class LayoutCreate(EventPermissionRequiredMixin, CreateView):
context_object_name = 'layout' context_object_name = 'layout'
success_url = '/ignored' success_url = '/ignored'
def get_form(self, form_class=None):
form = super().get_form(form_class)
if self.copy_from:
del form.fields['template']
return form
@transaction.atomic @transaction.atomic
def form_valid(self, form): def form_valid(self, form):
form.instance.event = self.request.event form.instance.event = self.request.event
if not self.request.event.badge_layouts.filter(default=True).exists(): if not self.request.event.badge_layouts.filter(default=True).exists():
form.instance.default = True form.instance.default = True
messages.success(self.request, _('The new badge layout has been created.')) messages.success(self.request, _('The new badge layout has been created.'))
if not self.copy_from:
form.instance.layout = json.dumps(TEMPLATES[form.cleaned_data["template"]]["layout"])
super().form_valid(form) super().form_valid(form)
if form.instance.background and form.instance.background.name: if not self.copy_from:
p = PdfWriter()
p.add_blank_page(
width=Decimal('%.5f' % TEMPLATES[form.cleaned_data["template"]]["pagesize"][0]),
height=Decimal('%.5f' % TEMPLATES[form.cleaned_data["template"]]["pagesize"][1]),
)
buffer = BytesIO()
p.write(buffer)
buffer.seek(0)
form.instance.background.save('background.pdf', ContentFile(buffer.read()))
elif form.instance.background and form.instance.background.name:
form.instance.background.save('background.pdf', form.instance.background) form.instance.background.save('background.pdf', form.instance.background)
form.instance.log_action('pretix.plugins.badges.layout.added', user=self.request.user, form.instance.log_action('pretix.plugins.badges.layout.added', user=self.request.user,
data=dict(form.cleaned_data)) data=dict(form.cleaned_data))