mirror of
https://github.com/pretix/pretix.git
synced 2026-05-04 15:04:03 +00:00
Refactor and add signal layout_text_variables
This commit is contained in:
@@ -64,3 +64,9 @@ Dashboards
|
|||||||
|
|
||||||
.. automodule:: pretix.control.signals
|
.. automodule:: pretix.control.signals
|
||||||
:members: event_dashboard_widgets, user_dashboard_widgets
|
:members: event_dashboard_widgets, user_dashboard_widgets
|
||||||
|
|
||||||
|
Ticket designs
|
||||||
|
""""""""""""""
|
||||||
|
|
||||||
|
.. automodule:: pretix.plugins.ticketoutputpdf.signals
|
||||||
|
:members: layout_text_variables
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from django.template.loader import get_template
|
|||||||
from django.urls import resolve
|
from django.urls import resolve
|
||||||
|
|
||||||
from pretix.base.signals import (
|
from pretix.base.signals import (
|
||||||
register_data_exporters, register_ticket_outputs,
|
EventPluginSignal, register_data_exporters, register_ticket_outputs,
|
||||||
)
|
)
|
||||||
from pretix.control.signals import html_head
|
from pretix.control.signals import html_head
|
||||||
from pretix.presale.style import ( # NOQA: legacy import
|
from pretix.presale.style import ( # NOQA: legacy import
|
||||||
@@ -33,3 +33,22 @@ def html_head_presale(sender, request=None, **kwargs):
|
|||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
layout_text_variables = EventPluginSignal()
|
||||||
|
"""
|
||||||
|
This signal is sent out to collect variables that can be used to display text in PDF ticket layouts.
|
||||||
|
Receivers are expected to return a dictionary with globally unique identifiers as keys and more
|
||||||
|
dictionaries as values that contain keys like in the following example::
|
||||||
|
|
||||||
|
return {
|
||||||
|
"product": {
|
||||||
|
"label": _("Product name"),
|
||||||
|
"editor_sample": _("Sample product"),
|
||||||
|
"evaluate": lambda orderposition, order, event: str(orderposition.item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The evaluate member will be called with the order position, order and event as arguments. The event might
|
||||||
|
also be a subevent, if applicable.
|
||||||
|
"""
|
||||||
|
|||||||
@@ -158,35 +158,11 @@ var editor = {
|
|||||||
editor._update_toolbox_values();
|
editor._update_toolbox_values();
|
||||||
},
|
},
|
||||||
|
|
||||||
text_samples: {
|
|
||||||
"secret": "tdmruoekvkpbv1o2mv8xccvqcikvr58u",
|
|
||||||
"order": "A1B2C",
|
|
||||||
"item": gettext("Sample product"),
|
|
||||||
"variation": gettext("Sample variation"),
|
|
||||||
"itemvar": gettext("Sample product – sample variation"),
|
|
||||||
"item_description": gettext("Sample product description"),
|
|
||||||
"price": gettext("123.45 EUR"),
|
|
||||||
"attendee_name": gettext("John Doe"),
|
|
||||||
"invoice_name": gettext("John Doe"),
|
|
||||||
"invoice_company": gettext("Sample company"),
|
|
||||||
"event_name": gettext("Sample event name"),
|
|
||||||
"event_date": gettext("May 31st, 2017"),
|
|
||||||
"event_date_range": gettext("May 31st – June 4th, 2017"),
|
|
||||||
"event_begin_time": gettext("20:00"),
|
|
||||||
"event_admission_time": gettext("19:00"),
|
|
||||||
"event_begin": gettext("2017-05-31 20:00"),
|
|
||||||
"event_admission": gettext("2017-05-31 19:00"),
|
|
||||||
"event_location": gettext("Random City"),
|
|
||||||
"organizer": gettext("Event organizer company"),
|
|
||||||
"organizer_info_text": gettext("Event organizer info text"),
|
|
||||||
"addons": gettext("Addon 1\nAddon 2"),
|
|
||||||
},
|
|
||||||
|
|
||||||
_get_text_sample: function (key) {
|
_get_text_sample: function (key) {
|
||||||
if (key.startsWith('meta:')) {
|
if (key.startsWith('meta:')) {
|
||||||
return key.substr(5);
|
return key.substr(5);
|
||||||
}
|
}
|
||||||
return editor.text_samples[key];
|
return $('#toolbox-content option[value='+key+']').attr('data-sample');
|
||||||
},
|
},
|
||||||
|
|
||||||
_load_pdf: function (dump) {
|
_load_pdf: function (dump) {
|
||||||
@@ -406,7 +382,7 @@ var editor = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_add_text: function () {
|
_add_text: function () {
|
||||||
var text = new fabric.Textarea(editor.text_samples['item'], {
|
var text = new fabric.Textarea(editor._get_text_sample('item'), {
|
||||||
left: 100,
|
left: 100,
|
||||||
top: 100,
|
top: 100,
|
||||||
width: editor._mm2px(50),
|
width: editor._mm2px(50),
|
||||||
|
|||||||
@@ -279,27 +279,9 @@
|
|||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label>{% trans "Text content" %}</label><br>
|
<label>{% trans "Text content" %}</label><br>
|
||||||
<select class="input-block-level form-control" id="toolbox-content">
|
<select class="input-block-level form-control" id="toolbox-content">
|
||||||
<option value="secret">{% trans "Ticket code (barcode content)" %}</option>
|
{% for varname, var in variables.items %}
|
||||||
<option value="order">{% trans "Order code" %}</option>
|
<option data-sample="{{ var.editor_sample }}" value="{{ varname }}">{{ var.label }}</option>
|
||||||
<option value="item">{% trans "Product name" %}</option>
|
{% endfor %}
|
||||||
<option value="variation">{% trans "Variation name" %}</option>
|
|
||||||
<option value="item_description">{% trans "Product description" %}</option>
|
|
||||||
<option value="itemvar">{% trans "Product name and variation" %}</option>
|
|
||||||
<option value="price">{% trans "Price" %}</option>
|
|
||||||
<option value="attendee_name">{% trans "Attendee name" %}</option>
|
|
||||||
<option value="event_name">{% trans "Event name" %}</option>
|
|
||||||
<option value="event_date">{% trans "Event date" %}</option>
|
|
||||||
<option value="event_date_range">{% trans "Event date range" %}</option>
|
|
||||||
<option value="event_begin">{% trans "Event begin date and time" %}</option>
|
|
||||||
<option value="event_begin_time">{% trans "Event begin time" %}</option>
|
|
||||||
<option value="event_admission">{% trans "Event admission date and time" %}</option>
|
|
||||||
<option value="event_admission_time">{% trans "Event admission time" %}</option>
|
|
||||||
<option value="event_location">{% trans "Event location" %}</option>
|
|
||||||
<option value="invoice_name">{% trans "Invoice address: name" %}</option>
|
|
||||||
<option value="invoice_company">{% trans "Invoice address: company" %}</option>
|
|
||||||
<option value="addons">{% trans "List of Add-Ons" %}</option>
|
|
||||||
<option value="organizer">{% trans "Organizer name" %}</option>
|
|
||||||
<option value="organizer_info_text">{% trans "Organizer info text" %}</option>
|
|
||||||
{% for p in request.organizer.meta_properties.all %}
|
{% for p in request.organizer.meta_properties.all %}
|
||||||
<option value="meta:{{ p.name }}">
|
<option value="meta:{{ p.name }}">
|
||||||
{% trans "Event attribute:" %} {{ p.name }}
|
{% trans "Event attribute:" %} {{ p.name }}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import copy
|
import copy
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
import uuid
|
||||||
|
from collections import OrderedDict
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from django.contrib.staticfiles import finders
|
from django.contrib.staticfiles import finders
|
||||||
@@ -26,11 +27,141 @@ from reportlab.platypus import Paragraph
|
|||||||
|
|
||||||
from pretix.base.models import Order, OrderPosition
|
from pretix.base.models import Order, OrderPosition
|
||||||
from pretix.base.ticketoutput import BaseTicketOutput
|
from pretix.base.ticketoutput import BaseTicketOutput
|
||||||
from pretix.plugins.ticketoutputpdf.signals import get_fonts
|
from pretix.plugins.ticketoutputpdf.signals import (
|
||||||
|
get_fonts, layout_text_variables,
|
||||||
|
)
|
||||||
|
|
||||||
logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
|
logger = logging.getLogger('pretix.plugins.ticketoutputpdf')
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_VARIABLES = OrderedDict((
|
||||||
|
("secret", {
|
||||||
|
"label": _("Ticket code (barcode content)"),
|
||||||
|
"editor_sample": "tdmruoekvkpbv1o2mv8xccvqcikvr58u",
|
||||||
|
"evaluate": lambda orderposition, order, event: orderposition.secret
|
||||||
|
}),
|
||||||
|
("order", {
|
||||||
|
"label": _("Order code"),
|
||||||
|
"editor_sample": "A1B2C",
|
||||||
|
"evaluate": lambda orderposition, order, event: orderposition.order.code
|
||||||
|
}),
|
||||||
|
("item", {
|
||||||
|
"label": _("Product name"),
|
||||||
|
"editor_sample": _("Sample product"),
|
||||||
|
"evaluate": lambda orderposition, order, event: str(orderposition.item)
|
||||||
|
}),
|
||||||
|
("variation", {
|
||||||
|
"label": _("Variation name"),
|
||||||
|
"editor_sample": _("Sample variation"),
|
||||||
|
"evaluate": lambda op, order, event: str(op.variation) if op.variation else ''
|
||||||
|
}),
|
||||||
|
("item_description", {
|
||||||
|
"label": _("Product description"),
|
||||||
|
"editor_sample": _("Sample product – sample variation"),
|
||||||
|
"evaluate": lambda orderposition, order, event: (
|
||||||
|
'{} - {}'.format(orderposition.item, orderposition.variation)
|
||||||
|
if orderposition.variation else str(orderposition.item)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
("itemvar", {
|
||||||
|
"label": _("Product name and variation"),
|
||||||
|
"editor_sample": _("Sample product description"),
|
||||||
|
"evaluate": lambda orderposition, order, event: str(orderposition.item.description)
|
||||||
|
}),
|
||||||
|
("price", {
|
||||||
|
"label": _("Price"),
|
||||||
|
"editor_sample": _("123.45 EUR"),
|
||||||
|
"evaluate": lambda op, order, event: '{} {}'.format(event.currency, localize(op.price))
|
||||||
|
}),
|
||||||
|
("attendee_name", {
|
||||||
|
"label": _("Attendee name"),
|
||||||
|
"editor_sample": _("John Doe"),
|
||||||
|
"evaluate": lambda op, order, ev: op.attendee_name or (op.addon_to.attendee_name if op.addon_to else '')
|
||||||
|
}),
|
||||||
|
("event_name", {
|
||||||
|
"label": _("Event name"),
|
||||||
|
"editor_sample": _("Sample event name"),
|
||||||
|
"evaluate": lambda op, order, ev: str(ev.name)
|
||||||
|
}),
|
||||||
|
("event_date", {
|
||||||
|
"label": _("Event date"),
|
||||||
|
"editor_sample": _("May 31st, 2017"),
|
||||||
|
"evaluate": lambda op, order, ev: ev.get_date_from_display(show_times=False)
|
||||||
|
}),
|
||||||
|
("event_date_range", {
|
||||||
|
"label": _("Event date range"),
|
||||||
|
"editor_sample": _("May 31st – June 4th, 2017"),
|
||||||
|
"evaluate": lambda op, order, ev: ev.get_date_range_display()
|
||||||
|
}),
|
||||||
|
("event_begin", {
|
||||||
|
"label": _("Event begin date and time"),
|
||||||
|
"editor_sample": _("2017-05-31 20:00"),
|
||||||
|
"evaluate": lambda op, order, ev: ev.get_date_from_display(show_times=True)
|
||||||
|
}),
|
||||||
|
("event_begin_time", {
|
||||||
|
"label": _("Event begin time"),
|
||||||
|
"editor_sample": _("20:00"),
|
||||||
|
"evaluate": lambda op, order, ev: ev.get_time_from_display()
|
||||||
|
}),
|
||||||
|
("event_admission", {
|
||||||
|
"label": _("Event admission date and time"),
|
||||||
|
"editor_sample": _("2017-05-31 19:00"),
|
||||||
|
"evaluate": lambda op, order, ev: date_format(
|
||||||
|
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
|
||||||
|
"SHORT_DATETIME_FORMAT"
|
||||||
|
) if ev.date_admission else ""
|
||||||
|
}),
|
||||||
|
("event_admission_time", {
|
||||||
|
"label": _("Event admission time"),
|
||||||
|
"editor_sample": _("19:00"),
|
||||||
|
"evaluate": lambda op, order, ev: date_format(
|
||||||
|
ev.date_admission.astimezone(timezone(ev.settings.timezone)),
|
||||||
|
"TIME_FORMAT"
|
||||||
|
) if ev.date_admission else ""
|
||||||
|
}),
|
||||||
|
("event_location", {
|
||||||
|
"label": _("Event location"),
|
||||||
|
"editor_sample": _("Random City"),
|
||||||
|
"evaluate": lambda op, order, ev: str(ev.location).replace("\n", "<br/>\n")
|
||||||
|
}),
|
||||||
|
("invoice_name", {
|
||||||
|
"label": _("Invoice address: name"),
|
||||||
|
"editor_sample": _("John Doe"),
|
||||||
|
"evaluate": lambda op, order, ev: order.invoice_address.name if getattr(order, 'invoice_address') else ''
|
||||||
|
}),
|
||||||
|
("invoice_company", {
|
||||||
|
"label": _("Invocie address: company"),
|
||||||
|
"editor_sample": _("Sample company"),
|
||||||
|
"evaluate": lambda op, order, ev: order.invoice_address.company if getattr(order, 'invoice_address') else ''
|
||||||
|
}),
|
||||||
|
("addons", {
|
||||||
|
"label": _("List of Add-Ons"),
|
||||||
|
"editor_sample": _("Addon 1\nAddon 2"),
|
||||||
|
"evaluate": lambda op, order, ev: "<br/>".join([
|
||||||
|
'{} - {}'.format(p.item, p.variation) if p.variation else str(p.item)
|
||||||
|
for p in op.addons.select_related('item', 'variation')
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
("organizer", {
|
||||||
|
"label": _("Organizer name"),
|
||||||
|
"editor_sample": _("Event organizer company"),
|
||||||
|
"evaluate": lambda op, order, ev: str(order.event.organizer.name)
|
||||||
|
}),
|
||||||
|
("organizer_info_text", {
|
||||||
|
"label": _("Organizer info text"),
|
||||||
|
"editor_sample": _("Event organizer info text"),
|
||||||
|
"evaluate": lambda op, order, ev: str(order.event.settings.organizer_info_text)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def get_variables(event):
|
||||||
|
v = copy.copy(DEFAULT_VARIABLES)
|
||||||
|
for recv, res in layout_text_variables.send(sender=event):
|
||||||
|
v.update(res)
|
||||||
|
return v
|
||||||
|
|
||||||
|
|
||||||
class PdfTicketOutput(BaseTicketOutput):
|
class PdfTicketOutput(BaseTicketOutput):
|
||||||
identifier = 'pdf'
|
identifier = 'pdf'
|
||||||
verbose_name = _('PDF output')
|
verbose_name = _('PDF output')
|
||||||
@@ -39,6 +170,7 @@ class PdfTicketOutput(BaseTicketOutput):
|
|||||||
def __init__(self, event, override_layout=None, override_background=None):
|
def __init__(self, event, override_layout=None, override_background=None):
|
||||||
self.override_layout = override_layout
|
self.override_layout = override_layout
|
||||||
self.override_background = override_background
|
self.override_background = override_background
|
||||||
|
self.variables = get_variables(event)
|
||||||
super().__init__(event)
|
super().__init__(event)
|
||||||
|
|
||||||
def _register_fonts(self):
|
def _register_fonts(self):
|
||||||
@@ -71,61 +203,12 @@ class PdfTicketOutput(BaseTicketOutput):
|
|||||||
return o['text'].replace("\n", "<br/>\n")
|
return o['text'].replace("\n", "<br/>\n")
|
||||||
elif o['content'].startswith('meta:'):
|
elif o['content'].startswith('meta:'):
|
||||||
return ev.meta_data.get(o['content'][5:]) or ''
|
return ev.meta_data.get(o['content'][5:]) or ''
|
||||||
elif o['content'] == 'order':
|
elif o['content'] in self.variables:
|
||||||
return order.code
|
|
||||||
elif o['content'] == 'item':
|
|
||||||
return str(op.item)
|
|
||||||
elif o['content'] == 'item_description':
|
|
||||||
return str(op.item.description)
|
|
||||||
elif o['content'] == 'organizer':
|
|
||||||
return str(order.event.organizer.name)
|
|
||||||
elif o['content'] == 'organizer_info_text':
|
|
||||||
return str(order.event.settings.organizer_info_text)
|
|
||||||
elif o['content'] == 'secret':
|
|
||||||
return op.secret
|
|
||||||
elif o['content'] == 'variation':
|
|
||||||
return str(op.variation) if op.variation else ''
|
|
||||||
elif o['content'] == 'itemvar':
|
|
||||||
return '{} - {}'.format(op.item, op.variation) if op.variation else str(op.item)
|
|
||||||
elif o['content'] == 'price':
|
|
||||||
return '{} {}'.format(order.event.currency, localize(op.price))
|
|
||||||
elif o['content'] == 'attendee_name':
|
|
||||||
return op.attendee_name or (op.addon_to.attendee_name if op.addon_to else '')
|
|
||||||
elif o['content'] == 'event_name':
|
|
||||||
return str(ev.name)
|
|
||||||
elif o['content'] == 'event_location':
|
|
||||||
return str(ev.location).replace("\n", "<br/>\n")
|
|
||||||
elif o['content'] == 'event_date':
|
|
||||||
return ev.get_date_from_display(show_times=False)
|
|
||||||
elif o['content'] == 'event_date_range':
|
|
||||||
return ev.get_date_range_display()
|
|
||||||
elif o['content'] == 'event_begin':
|
|
||||||
return ev.get_date_from_display(show_times=True)
|
|
||||||
elif o['content'] == 'event_begin_time':
|
|
||||||
return ev.get_time_from_display()
|
|
||||||
elif o['content'] == 'event_admission':
|
|
||||||
if ev.date_admission:
|
|
||||||
tz = timezone(order.event.settings.timezone)
|
|
||||||
return date_format(ev.date_admission.astimezone(tz), "SHORT_DATETIME_FORMAT")
|
|
||||||
elif o['content'] == 'event_admission_time':
|
|
||||||
if ev.date_admission:
|
|
||||||
tz = timezone(order.event.settings.timezone)
|
|
||||||
return date_format(ev.date_admission.astimezone(tz), "TIME_FORMAT")
|
|
||||||
elif o['content'] == 'invoice_name':
|
|
||||||
try:
|
try:
|
||||||
return order.invoice_address.name
|
self.variables[o['content']]['evaluate'](op, order, ev)
|
||||||
except:
|
except:
|
||||||
return ""
|
logger.exception('Failed to process variable.')
|
||||||
elif o['content'] == 'invoice_company':
|
return '(error)'
|
||||||
try:
|
|
||||||
return order.invoice_address.company
|
|
||||||
except:
|
|
||||||
return ""
|
|
||||||
elif o['content'] == 'addons':
|
|
||||||
return "<br/>".join([
|
|
||||||
'{} - {}'.format(p.item, p.variation) if p.variation else str(p.item)
|
|
||||||
for p in op.addons.select_related('item', 'variation')
|
|
||||||
])
|
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def _draw_textarea(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
|
def _draw_textarea(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from pretix.control.permissions import EventPermissionRequiredMixin
|
|||||||
from pretix.helpers.database import rolledback_transaction
|
from pretix.helpers.database import rolledback_transaction
|
||||||
from pretix.plugins.ticketoutputpdf.signals import get_fonts
|
from pretix.plugins.ticketoutputpdf.signals import get_fonts
|
||||||
|
|
||||||
from .ticketoutput import PdfTicketOutput
|
from .ticketoutput import PdfTicketOutput, get_variables
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -156,6 +156,7 @@ class EditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
if self.request.event.settings.get('ticketoutput_{}_background'.format(self.identifier))
|
if self.request.event.settings.get('ticketoutput_{}_background'.format(self.identifier))
|
||||||
else static('pretixpresale/pdf/ticket_default_a4.pdf')
|
else static('pretixpresale/pdf/ticket_default_a4.pdf')
|
||||||
)
|
)
|
||||||
|
ctx['variables'] = get_variables(self.request.event)
|
||||||
ctx['layout'] = json.dumps(
|
ctx['layout'] = json.dumps(
|
||||||
self.request.event.settings.get('ticketoutput_{}_layout'.format(self.identifier), as_type=list)
|
self.request.event.settings.get('ticketoutput_{}_layout'.format(self.identifier), as_type=list)
|
||||||
or prov._default_layout()
|
or prov._default_layout()
|
||||||
|
|||||||
@@ -183,3 +183,10 @@ pre.mail-preview {
|
|||||||
padding-top: 7px;
|
padding-top: 7px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.inline-multiple-choice {
|
||||||
|
.checkbox {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user