mirror of
https://github.com/pretix/pretix.git
synced 2026-08-11 10:57:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2923799b0a | ||
|
|
690c462d95 | ||
|
|
fb15d5d61d | ||
|
|
721ad93edd | ||
|
|
c96240834b | ||
|
|
393fc2b73b | ||
|
|
39ee4f0663 | ||
|
|
49452db739 | ||
|
|
327d69f6d3 | ||
|
|
d5fa817623 | ||
|
|
a5f1296934 | ||
|
|
866a5cde42 | ||
|
|
f88a560d5f | ||
|
|
4019f956a8 | ||
|
|
c75c847393 | ||
|
|
4e3d3b1f22 |
@@ -31,6 +31,7 @@ RUN apt-get update && \
|
|||||||
mkdir /etc/pretix && \
|
mkdir /etc/pretix && \
|
||||||
mkdir /data && \
|
mkdir /data && \
|
||||||
useradd -ms /bin/bash -d /pretix -u 15371 pretixuser && \
|
useradd -ms /bin/bash -d /pretix -u 15371 pretixuser && \
|
||||||
|
chmod 0755 /pretix && \
|
||||||
echo 'pretixuser ALL=(ALL) NOPASSWD:SETENV: /usr/bin/supervisord' >> /etc/sudoers && \
|
echo 'pretixuser ALL=(ALL) NOPASSWD:SETENV: /usr/bin/supervisord' >> /etc/sudoers && \
|
||||||
mkdir /static && \
|
mkdir /static && \
|
||||||
mkdir /etc/supervisord
|
mkdir /etc/supervisord
|
||||||
|
|||||||
@@ -19,4 +19,4 @@
|
|||||||
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
# 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/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
__version__ = "2026.4.0"
|
__version__ = "2026.4.4"
|
||||||
|
|||||||
@@ -61,18 +61,23 @@ class ReusableMediaExporter(OrganizerLevelExportMixin, ListExporter):
|
|||||||
yield headers
|
yield headers
|
||||||
yield self.ProgressSetTotal(total=media.count())
|
yield self.ProgressSetTotal(total=media.count())
|
||||||
|
|
||||||
|
can_read_giftcards = self.permission_holder.has_organizer_permission(self.organizer, 'organizer.giftcards:read')
|
||||||
|
|
||||||
for medium in media.iterator(chunk_size=1000):
|
for medium in media.iterator(chunk_size=1000):
|
||||||
row = [
|
giftcard_secret = medium.linked_giftcard.secret if medium.linked_giftcard_id else ''
|
||||||
|
if giftcard_secret and not can_read_giftcards:
|
||||||
|
giftcard_secret = giftcard_secret[:3] + "…"
|
||||||
|
|
||||||
|
yield [
|
||||||
medium.type,
|
medium.type,
|
||||||
medium.identifier,
|
medium.identifier,
|
||||||
_('Yes') if medium.active else _('No'),
|
_('Yes') if medium.active else _('No'),
|
||||||
date_format(medium.expires, 'SHORT_DATETIME_FORMAT') if medium.expires else '',
|
date_format(medium.expires, 'SHORT_DATETIME_FORMAT') if medium.expires else '',
|
||||||
medium.customer.identifier if medium.customer_id else '',
|
medium.customer.identifier if medium.customer_id else '',
|
||||||
f"{medium.linked_orderposition.order.code}-{medium.linked_orderposition.positionid}" if medium.linked_orderposition_id else '',
|
f"{medium.linked_orderposition.order.code}-{medium.linked_orderposition.positionid}" if medium.linked_orderposition_id else '',
|
||||||
medium.linked_giftcard.secret if medium.linked_giftcard_id else '',
|
giftcard_secret,
|
||||||
medium.notes,
|
medium.notes,
|
||||||
]
|
]
|
||||||
yield row
|
|
||||||
|
|
||||||
def get_filename(self):
|
def get_filename(self):
|
||||||
return f'{self.organizer.slug}_media'
|
return f'{self.organizer.slug}_media'
|
||||||
|
|||||||
@@ -22,9 +22,7 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import re
|
|
||||||
import textwrap
|
import textwrap
|
||||||
import unicodedata
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
@@ -58,8 +56,8 @@ from pretix.base.services.currencies import SOURCE_NAMES
|
|||||||
from pretix.base.signals import register_invoice_renderers
|
from pretix.base.signals import register_invoice_renderers
|
||||||
from pretix.base.templatetags.money import money_filter
|
from pretix.base.templatetags.money import money_filter
|
||||||
from pretix.helpers.reportlab import (
|
from pretix.helpers.reportlab import (
|
||||||
FontFallbackParagraph, ThumbnailingImageReader, register_ttf_font_if_new,
|
FontFallbackParagraph, PlainTextParagraph, ThumbnailingImageReader,
|
||||||
reshaper,
|
normalize_text, register_ttf_font_if_new, reshaper,
|
||||||
)
|
)
|
||||||
from pretix.presale.style import get_fonts
|
from pretix.presale.style import get_fonts
|
||||||
|
|
||||||
@@ -259,18 +257,8 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
|||||||
register_ttf_font_if_new(family + ' B I', finders.find(styles['bolditalic']['truetype']))
|
register_ttf_font_if_new(family + ' B I', finders.find(styles['bolditalic']['truetype']))
|
||||||
|
|
||||||
def _normalize(self, text):
|
def _normalize(self, text):
|
||||||
# reportlab does not support unicode combination characters
|
# alias kept for plugin compatibility
|
||||||
# It's important we do this before we use ArabicReshaper
|
return normalize_text(text)
|
||||||
text = unicodedata.normalize("NFKC", text)
|
|
||||||
|
|
||||||
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
|
||||||
# to resolve all ligatures and python-bidi to switch RTL texts.
|
|
||||||
try:
|
|
||||||
text = "<br />".join(get_display(reshaper.reshape(l)) for l in re.split("<br ?/>", text))
|
|
||||||
except:
|
|
||||||
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def _upper(self, val):
|
def _upper(self, val):
|
||||||
# We uppercase labels, but not in every language
|
# We uppercase labels, but not in every language
|
||||||
@@ -351,10 +339,15 @@ class BaseReportlabInvoiceRenderer(BaseInvoiceRenderer):
|
|||||||
return 'invoice.pdf', 'application/pdf', buffer.read()
|
return 'invoice.pdf', 'application/pdf', buffer.read()
|
||||||
|
|
||||||
def _clean_text(self, text, tags=None):
|
def _clean_text(self, text, tags=None):
|
||||||
return self._normalize(bleach.clean(
|
# For backwards compatibility with customer content, we need to support tags like <br> and <b> in a few text
|
||||||
text,
|
# fields. Therefore, we can't use PlainTextParagraph for these, but run bleach instead to limit the allowed
|
||||||
tags=set(tags) if tags else set()
|
# tags.
|
||||||
).strip().replace('<br>', '<br />').replace('\n', '<br />\n'))
|
return self._normalize(
|
||||||
|
bleach.clean(
|
||||||
|
text,
|
||||||
|
tags=set(tags) if tags else set()
|
||||||
|
).strip().replace('<br>', '<br />').replace('\n', '<br />\n')
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class PaidMarker(Flowable):
|
class PaidMarker(Flowable):
|
||||||
@@ -405,8 +398,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
invoice_to_top = 52 * mm
|
invoice_to_top = 52 * mm
|
||||||
|
|
||||||
def _draw_invoice_to(self, canvas):
|
def _draw_invoice_to(self, canvas):
|
||||||
p = FontFallbackParagraph(self._clean_text(self.invoice.address_invoice_to),
|
p = PlainTextParagraph(self.invoice.address_invoice_to, style=self.stylesheet['Normal'])
|
||||||
style=self.stylesheet['Normal'])
|
|
||||||
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
|
p.wrapOn(canvas, self.invoice_to_width, self.invoice_to_height)
|
||||||
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
|
p_size = p.wrap(self.invoice_to_width, self.invoice_to_height)
|
||||||
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top)
|
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - p_size[1] - self.invoice_to_top)
|
||||||
@@ -417,8 +409,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
invoice_from_top = 17 * mm
|
invoice_from_top = 17 * mm
|
||||||
|
|
||||||
def _draw_invoice_from(self, canvas):
|
def _draw_invoice_from(self, canvas):
|
||||||
p = FontFallbackParagraph(
|
p = PlainTextParagraph(
|
||||||
self._clean_text(self.invoice.full_invoice_from),
|
self.invoice.full_invoice_from,
|
||||||
style=self.stylesheet['InvoiceFrom']
|
style=self.stylesheet['InvoiceFrom']
|
||||||
)
|
)
|
||||||
p.wrapOn(canvas, self.invoice_from_width, self.invoice_from_height)
|
p.wrapOn(canvas, self.invoice_from_width, self.invoice_from_height)
|
||||||
@@ -548,13 +540,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
def _draw_event(self, canvas):
|
def _draw_event(self, canvas):
|
||||||
def shorten(txt):
|
def shorten(txt):
|
||||||
txt = str(txt)
|
txt = str(txt)
|
||||||
txt = bleach.clean(txt, tags=set()).strip()
|
p = PlainTextParagraph(txt, style=self.stylesheet['Normal'])
|
||||||
p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
|
||||||
p_size = p.wrap(self.event_width, self.event_height)
|
p_size = p.wrap(self.event_width, self.event_height)
|
||||||
|
|
||||||
while p_size[1] > 2 * self.stylesheet['Normal'].leading:
|
while p_size[1] > 2 * self.stylesheet['Normal'].leading:
|
||||||
txt = ' '.join(txt.replace('…', '').split()[:-1]) + '…'
|
txt = ' '.join(txt.replace('…', '').split()[:-1]) + '…'
|
||||||
p = FontFallbackParagraph(self._normalize(txt.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
p = PlainTextParagraph(txt, style=self.stylesheet['Normal'])
|
||||||
p_size = p.wrap(self.event_width, self.event_height)
|
p_size = p.wrap(self.event_width, self.event_height)
|
||||||
return txt
|
return txt
|
||||||
|
|
||||||
@@ -572,7 +563,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
else:
|
else:
|
||||||
p_str = shorten(self.invoice.event.name)
|
p_str = shorten(self.invoice.event.name)
|
||||||
|
|
||||||
p = FontFallbackParagraph(self._normalize(p_str.strip().replace('\n', '<br />\n')), style=self.stylesheet['Normal'])
|
p = PlainTextParagraph(p_str, style=self.stylesheet['Normal'])
|
||||||
p.wrapOn(canvas, self.event_width, self.event_height)
|
p.wrapOn(canvas, self.event_width, self.event_height)
|
||||||
p_size = p.wrap(self.event_width, self.event_height)
|
p_size = p.wrap(self.event_width, self.event_height)
|
||||||
p.drawOn(canvas, self.event_left, self.pagesize[1] - self.event_top - p_size[1])
|
p.drawOn(canvas, self.event_left, self.pagesize[1] - self.event_top - p_size[1])
|
||||||
@@ -645,39 +636,37 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
|
|
||||||
type_info_text = self.invoice.transmission_type_instance.pdf_info_text()
|
type_info_text = self.invoice.transmission_type_instance.pdf_info_text()
|
||||||
if type_info_text:
|
if type_info_text:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(PlainTextParagraph(
|
||||||
type_info_text,
|
type_info_text,
|
||||||
self.stylesheet['WarningBlock']
|
self.stylesheet['WarningBlock']
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.invoice.custom_field:
|
if self.invoice.custom_field:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(PlainTextParagraph(
|
||||||
'{}: {}'.format(
|
'{}: {}'.format(
|
||||||
self._clean_text(str(self.invoice.event.settings.invoice_address_custom_field)),
|
str(self.invoice.event.settings.invoice_address_custom_field),
|
||||||
self._clean_text(self.invoice.custom_field),
|
self.invoice.custom_field,
|
||||||
),
|
),
|
||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.invoice.internal_reference:
|
if self.invoice.internal_reference:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(PlainTextParagraph(
|
||||||
self._normalize(pgettext('invoice', 'Customer reference: {reference}').format(
|
pgettext('invoice', 'Customer reference: {reference}').format(
|
||||||
reference=self._clean_text(self.invoice.internal_reference),
|
reference=self.invoice.internal_reference,
|
||||||
)),
|
),
|
||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.invoice.invoice_to_vat_id:
|
if self.invoice.invoice_to_vat_id:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(PlainTextParagraph(
|
||||||
self._normalize(pgettext('invoice', 'Customer VAT ID')) + ': ' +
|
pgettext('invoice', 'Customer VAT ID') + ': ' + self.invoice.invoice_to_vat_id,
|
||||||
self._clean_text(self.invoice.invoice_to_vat_id),
|
|
||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.invoice.invoice_to_beneficiary:
|
if self.invoice.invoice_to_beneficiary:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(PlainTextParagraph(
|
||||||
self._normalize(pgettext('invoice', 'Beneficiary')) + ':<br />' +
|
pgettext('invoice', 'Beneficiary') + ':\n' + self.invoice.invoice_to_beneficiary,
|
||||||
self._clean_text(self.invoice.invoice_to_beneficiary),
|
|
||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -707,11 +696,11 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
|
|
||||||
story = [
|
story = [
|
||||||
NextPageTemplate('FirstPage'),
|
NextPageTemplate('FirstPage'),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
self._normalize(
|
(
|
||||||
pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU'
|
pgettext('invoice', 'Tax Invoice') if str(self.invoice.invoice_from_country) == 'AU'
|
||||||
else pgettext('invoice', 'Invoice')
|
else pgettext('invoice', 'Invoice')
|
||||||
) if not self.invoice.is_cancellation else self._normalize(pgettext('invoice', 'Cancellation')),
|
) if not self.invoice.is_cancellation else pgettext('invoice', 'Cancellation'),
|
||||||
self.stylesheet['Heading1']
|
self.stylesheet['Heading1']
|
||||||
),
|
),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
@@ -733,17 +722,17 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
]
|
]
|
||||||
if has_taxes:
|
if has_taxes:
|
||||||
tdata = [(
|
tdata = [(
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
|
PlainTextParagraph(pgettext('invoice', 'Description'), self.stylesheet['Bold']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
|
PlainTextParagraph(pgettext('invoice', 'Qty'), self.stylesheet['BoldRightNoSplit']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['BoldRightNoSplit']),
|
PlainTextParagraph(pgettext('invoice', 'Tax rate'), self.stylesheet['BoldRightNoSplit']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net')), self.stylesheet['BoldRightNoSplit']),
|
PlainTextParagraph(pgettext('invoice', 'Net'), self.stylesheet['BoldRightNoSplit']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross')), self.stylesheet['BoldRightNoSplit']),
|
PlainTextParagraph(pgettext('invoice', 'Gross'), self.stylesheet['BoldRightNoSplit']),
|
||||||
)]
|
)]
|
||||||
else:
|
else:
|
||||||
tdata = [(
|
tdata = [(
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Description')), self.stylesheet['Bold']),
|
PlainTextParagraph(pgettext('invoice', 'Description'), self.stylesheet['Bold']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Qty')), self.stylesheet['BoldRightNoSplit']),
|
PlainTextParagraph(pgettext('invoice', 'Qty'), self.stylesheet['BoldRightNoSplit']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Amount')), self.stylesheet['BoldRightNoSplit']),
|
PlainTextParagraph(pgettext('invoice', 'Amount'), self.stylesheet['BoldRightNoSplit']),
|
||||||
)]
|
)]
|
||||||
|
|
||||||
def _group_key(line):
|
def _group_key(line):
|
||||||
@@ -780,8 +769,8 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
max_height = self.stylesheet['Normal'].leading * 5
|
max_height = self.stylesheet['Normal'].leading * 5
|
||||||
p_style = self.stylesheet['Normal']
|
p_style = self.stylesheet['Normal']
|
||||||
for __ in range(1000):
|
for __ in range(1000):
|
||||||
p = FontFallbackParagraph(
|
p = PlainTextParagraph(
|
||||||
self._clean_text(curr_description, tags=['br']),
|
curr_description,
|
||||||
p_style
|
p_style
|
||||||
)
|
)
|
||||||
h = p.wrap(max_width, doc.height)[1]
|
h = p.wrap(max_width, doc.height)[1]
|
||||||
@@ -862,7 +851,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
# Group together at the end of the invoice
|
# Group together at the end of the invoice
|
||||||
request_show_service_date = period_line
|
request_show_service_date = period_line
|
||||||
elif period_line:
|
elif period_line:
|
||||||
description_p_list.append(FontFallbackParagraph(
|
description_p_list.append(PlainTextParagraph(
|
||||||
period_line,
|
period_line,
|
||||||
self.stylesheet['Fineprint']
|
self.stylesheet['Fineprint']
|
||||||
))
|
))
|
||||||
@@ -874,7 +863,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
net_price=money_filter(net_value, self.invoice.event.currency),
|
net_price=money_filter(net_value, self.invoice.event.currency),
|
||||||
gross_price=money_filter(gross_value, self.invoice.event.currency),
|
gross_price=money_filter(gross_value, self.invoice.event.currency),
|
||||||
)
|
)
|
||||||
description_p_list.append(FontFallbackParagraph(
|
description_p_list.append(PlainTextParagraph(
|
||||||
single_price_line,
|
single_price_line,
|
||||||
self.stylesheet['Fineprint']
|
self.stylesheet['Fineprint']
|
||||||
))
|
))
|
||||||
@@ -883,11 +872,11 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
description_p_list.pop(0),
|
description_p_list.pop(0),
|
||||||
str(len(lines)),
|
str(len(lines)),
|
||||||
localize(tax_rate) + " %",
|
localize(tax_rate) + " %",
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
money_filter(net_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||||
self.stylesheet['NormalRight']
|
self.stylesheet['NormalRight']
|
||||||
),
|
),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||||
self.stylesheet['NormalRight']
|
self.stylesheet['NormalRight']
|
||||||
),
|
),
|
||||||
@@ -904,14 +893,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
single_price_line = pgettext('invoice', 'Single price: {price}').format(
|
single_price_line = pgettext('invoice', 'Single price: {price}').format(
|
||||||
price=money_filter(gross_value, self.invoice.event.currency),
|
price=money_filter(gross_value, self.invoice.event.currency),
|
||||||
)
|
)
|
||||||
description_p_list.append(FontFallbackParagraph(
|
description_p_list.append(PlainTextParagraph(
|
||||||
single_price_line,
|
single_price_line,
|
||||||
self.stylesheet['Fineprint']
|
self.stylesheet['Fineprint']
|
||||||
))
|
))
|
||||||
tdata.append((
|
tdata.append((
|
||||||
description_p_list.pop(0),
|
description_p_list.pop(0),
|
||||||
str(len(lines)),
|
str(len(lines)),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
money_filter(gross_value * len(lines), self.invoice.event.currency).replace('\xa0', ' '),
|
||||||
self.stylesheet['NormalRight']
|
self.stylesheet['NormalRight']
|
||||||
),
|
),
|
||||||
@@ -944,12 +933,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
|
|
||||||
if has_taxes:
|
if has_taxes:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '', '', '',
|
PlainTextParagraph(pgettext('invoice', 'Invoice total'), self.stylesheet['Bold']), '', '', '',
|
||||||
money_filter(total, self.invoice.event.currency)
|
money_filter(total, self.invoice.event.currency)
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Invoice total')), self.stylesheet['Bold']), '',
|
PlainTextParagraph(pgettext('invoice', 'Invoice total'), self.stylesheet['Bold']), '',
|
||||||
money_filter(total, self.invoice.event.currency)
|
money_filter(total, self.invoice.event.currency)
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -958,12 +947,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
pending_sum = self.invoice.order.pending_sum
|
pending_sum = self.invoice.order.pending_sum
|
||||||
if pending_sum != total:
|
if pending_sum != total:
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Received payments')), self.stylesheet['Normal'])] +
|
[PlainTextParagraph(pgettext('invoice', 'Received payments'), self.stylesheet['Normal'])] +
|
||||||
(['', '', ''] if has_taxes else ['']) +
|
(['', '', ''] if has_taxes else ['']) +
|
||||||
[money_filter(pending_sum - total, self.invoice.event.currency)]
|
[money_filter(pending_sum - total, self.invoice.event.currency)]
|
||||||
)
|
)
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Outstanding payments')), self.stylesheet['Bold'])] +
|
[PlainTextParagraph(pgettext('invoice', 'Outstanding payments'), self.stylesheet['Bold'])] +
|
||||||
(['', '', ''] if has_taxes else ['']) +
|
(['', '', ''] if has_taxes else ['']) +
|
||||||
[money_filter(pending_sum, self.invoice.event.currency)]
|
[money_filter(pending_sum, self.invoice.event.currency)]
|
||||||
)
|
)
|
||||||
@@ -980,12 +969,12 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
s=Sum('amount')
|
s=Sum('amount')
|
||||||
)['s'] or Decimal('0.00')
|
)['s'] or Decimal('0.00')
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Paid by gift card')), self.stylesheet['Normal'])] +
|
[PlainTextParagraph(pgettext('invoice', 'Paid by gift card'), self.stylesheet['Normal'])] +
|
||||||
(['', '', ''] if has_taxes else ['']) +
|
(['', '', ''] if has_taxes else ['']) +
|
||||||
[money_filter(giftcard_sum, self.invoice.event.currency)]
|
[money_filter(giftcard_sum, self.invoice.event.currency)]
|
||||||
)
|
)
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[FontFallbackParagraph(self._normalize(pgettext('invoice', 'Remaining amount')), self.stylesheet['Bold'])] +
|
[PlainTextParagraph(pgettext('invoice', 'Remaining amount'), self.stylesheet['Bold'])] +
|
||||||
(['', '', ''] if has_taxes else ['']) +
|
(['', '', ''] if has_taxes else ['']) +
|
||||||
[money_filter(total - giftcard_sum, self.invoice.event.currency)]
|
[money_filter(total - giftcard_sum, self.invoice.event.currency)]
|
||||||
)
|
)
|
||||||
@@ -1008,14 +997,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
story.append(Spacer(1, 10 * mm))
|
story.append(Spacer(1, 10 * mm))
|
||||||
|
|
||||||
if request_show_service_date:
|
if request_show_service_date:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(PlainTextParagraph(
|
||||||
self._normalize(pgettext('invoice', 'Invoice period: {daterange}').format(daterange=request_show_service_date)),
|
pgettext('invoice', 'Invoice period: {daterange}').format(daterange=request_show_service_date),
|
||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.invoice.payment_provider_text:
|
if self.invoice.payment_provider_text:
|
||||||
story.append(FontFallbackParagraph(
|
story.append(FontFallbackParagraph(
|
||||||
self._normalize(self.invoice.payment_provider_text),
|
self._clean_text(self.invoice.payment_provider_text, tags=['br', 'b']),
|
||||||
self.stylesheet['Normal']
|
self.stylesheet['Normal']
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -1039,10 +1028,10 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
('FONTNAME', (0, 0), (-1, -1), self.font_regular),
|
('FONTNAME', (0, 0), (-1, -1), self.font_regular),
|
||||||
]
|
]
|
||||||
thead = [
|
thead = [
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax rate')), self.stylesheet['Fineprint']),
|
PlainTextParagraph(pgettext('invoice', 'Tax rate'), self.stylesheet['Fineprint']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Net value')), self.stylesheet['FineprintRight']),
|
PlainTextParagraph(pgettext('invoice', 'Net value'), self.stylesheet['FineprintRight']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Gross value')), self.stylesheet['FineprintRight']),
|
PlainTextParagraph(pgettext('invoice', 'Gross value'), self.stylesheet['FineprintRight']),
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Tax')), self.stylesheet['FineprintRight']),
|
PlainTextParagraph(pgettext('invoice', 'Tax'), self.stylesheet['FineprintRight']),
|
||||||
''
|
''
|
||||||
]
|
]
|
||||||
tdata = [thead]
|
tdata = [thead]
|
||||||
@@ -1053,7 +1042,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
continue
|
continue
|
||||||
tax = taxvalue_map[idx]
|
tax = taxvalue_map[idx]
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
PlainTextParagraph(localize(rate) + " % " + name, self.stylesheet['Fineprint']),
|
||||||
money_filter(gross - tax, self.invoice.event.currency),
|
money_filter(gross - tax, self.invoice.event.currency),
|
||||||
money_filter(gross, self.invoice.event.currency),
|
money_filter(gross, self.invoice.event.currency),
|
||||||
money_filter(tax, self.invoice.event.currency),
|
money_filter(tax, self.invoice.event.currency),
|
||||||
@@ -1072,7 +1061,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
table.setStyle(TableStyle(tstyledata))
|
table.setStyle(TableStyle(tstyledata))
|
||||||
story.append(Spacer(5 * mm, 5 * mm))
|
story.append(Spacer(5 * mm, 5 * mm))
|
||||||
story.append(KeepTogether([
|
story.append(KeepTogether([
|
||||||
FontFallbackParagraph(self._normalize(pgettext('invoice', 'Included taxes')), self.stylesheet['FineprintHeading']),
|
PlainTextParagraph(pgettext('invoice', 'Included taxes'), self.stylesheet['FineprintHeading']),
|
||||||
table
|
table
|
||||||
]))
|
]))
|
||||||
|
|
||||||
@@ -1089,7 +1078,7 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
net = gross - tax
|
net = gross - tax
|
||||||
|
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(self._normalize(localize(rate) + " % " + name), self.stylesheet['Fineprint']),
|
PlainTextParagraph(localize(rate) + " % " + name, self.stylesheet['Fineprint']),
|
||||||
fmt(net), fmt(gross), fmt(tax), ''
|
fmt(net), fmt(gross), fmt(tax), ''
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -1098,13 +1087,13 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
|
|
||||||
story.append(KeepTogether([
|
story.append(KeepTogether([
|
||||||
Spacer(1, height=2 * mm),
|
Spacer(1, height=2 * mm),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
self._normalize(pgettext(
|
pgettext(
|
||||||
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
|
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
|
||||||
'{date}, this corresponds to:'
|
'{date}, this corresponds to:'
|
||||||
).format(rate=localize(self.invoice.foreign_currency_rate),
|
).format(rate=localize(self.invoice.foreign_currency_rate),
|
||||||
authority=SOURCE_NAMES.get(self.invoice.foreign_currency_source, "?"),
|
authority=SOURCE_NAMES.get(self.invoice.foreign_currency_source, "?"),
|
||||||
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"))),
|
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT")),
|
||||||
self.stylesheet['Fineprint']
|
self.stylesheet['Fineprint']
|
||||||
),
|
),
|
||||||
Spacer(1, height=3 * mm),
|
Spacer(1, height=3 * mm),
|
||||||
@@ -1113,14 +1102,14 @@ class ClassicInvoiceRenderer(BaseReportlabInvoiceRenderer):
|
|||||||
elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate:
|
elif self.invoice.foreign_currency_display and self.invoice.foreign_currency_rate:
|
||||||
foreign_total = round_decimal(total * self.invoice.foreign_currency_rate)
|
foreign_total = round_decimal(total * self.invoice.foreign_currency_rate)
|
||||||
story.append(Spacer(1, 5 * mm))
|
story.append(Spacer(1, 5 * mm))
|
||||||
story.append(FontFallbackParagraph(self._normalize(
|
story.append(PlainTextParagraph(
|
||||||
pgettext(
|
pgettext(
|
||||||
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
|
'invoice', 'Using the conversion rate of 1:{rate} as published by the {authority} on '
|
||||||
'{date}, the invoice total corresponds to {total}.'
|
'{date}, the invoice total corresponds to {total}.'
|
||||||
).format(rate=localize(self.invoice.foreign_currency_rate),
|
).format(rate=localize(self.invoice.foreign_currency_rate),
|
||||||
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"),
|
date=date_format(self.invoice.foreign_currency_rate_date, "SHORT_DATE_FORMAT"),
|
||||||
authority=SOURCE_NAMES.get(self.invoice.foreign_currency_source, "?"),
|
authority=SOURCE_NAMES.get(self.invoice.foreign_currency_source, "?"),
|
||||||
total=fmt(foreign_total))),
|
total=fmt(foreign_total)),
|
||||||
self.stylesheet['Fineprint']
|
self.stylesheet['Fineprint']
|
||||||
))
|
))
|
||||||
|
|
||||||
@@ -1162,11 +1151,8 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
|||||||
def _draw_invoice_from(self, canvas):
|
def _draw_invoice_from(self, canvas):
|
||||||
if not self.invoice.invoice_from:
|
if not self.invoice.invoice_from:
|
||||||
return
|
return
|
||||||
c = [
|
c = self.invoice.address_invoice_from.strip().split('\n')
|
||||||
self._clean_text(l)
|
p = PlainTextParagraph(' · '.join(c), style=self.stylesheet['Sender'])
|
||||||
for l in self.invoice.address_invoice_from.strip().split('\n')
|
|
||||||
]
|
|
||||||
p = FontFallbackParagraph(self._normalize(' · '.join(c)), style=self.stylesheet['Sender'])
|
|
||||||
p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm)
|
p.wrapOn(canvas, self.invoice_to_width, 15.7 * mm)
|
||||||
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm)
|
p.drawOn(canvas, self.invoice_to_left, self.pagesize[1] - self.invoice_to_top + 2 * mm)
|
||||||
super()._draw_invoice_from(canvas)
|
super()._draw_invoice_from(canvas)
|
||||||
@@ -1225,8 +1211,8 @@ class Modern1Renderer(ClassicInvoiceRenderer):
|
|||||||
_draw(pgettext('invoice', 'Order code'), self.invoice.order.full_code, value_size, self.left_margin, 45 * mm, **kwargs)
|
_draw(pgettext('invoice', 'Order code'), self.invoice.order.full_code, value_size, self.left_margin, 45 * mm, **kwargs)
|
||||||
]
|
]
|
||||||
|
|
||||||
p = FontFallbackParagraph(
|
p = PlainTextParagraph(
|
||||||
self._normalize(date_format(self.invoice.date, "DATE_FORMAT")),
|
date_format(self.invoice.date, "DATE_FORMAT"),
|
||||||
style=ParagraphStyle(name=f'Normal{value_size}', fontName=self.font_regular, fontSize=value_size, leading=value_size * 1.2)
|
style=ParagraphStyle(name=f'Normal{value_size}', fontName=self.font_regular, fontSize=value_size, leading=value_size * 1.2)
|
||||||
)
|
)
|
||||||
w = stringWidth(p.text, p.frags[0].fontName, p.frags[0].fontSize)
|
w = stringWidth(p.text, p.frags[0].fontName, p.frags[0].fontSize)
|
||||||
@@ -1283,7 +1269,7 @@ class Modern1SimplifiedRenderer(Modern1Renderer):
|
|||||||
i = []
|
i = []
|
||||||
|
|
||||||
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
|
if not self.invoice.event.has_subevents and self.invoice.event.settings.show_dates_on_frontpage:
|
||||||
i.append(FontFallbackParagraph(
|
i.append(PlainTextParagraph(
|
||||||
pgettext('invoice', 'Event date: {date_range}').format(
|
pgettext('invoice', 'Event date: {date_range}').format(
|
||||||
date_range=self.invoice.event.get_date_range_display(),
|
date_range=self.invoice.event.get_date_range_display(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1056,7 +1056,7 @@ class Renderer:
|
|||||||
except:
|
except:
|
||||||
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
|
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
|
||||||
|
|
||||||
p = Paragraph(text, style=style)
|
p = Paragraph(text, style=style) # not using AutoEscapeParagraph is safe as we escape above
|
||||||
return p, ad, lineheight
|
return p, ad, lineheight
|
||||||
|
|
||||||
def _draw_textcontainer(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
|
def _draw_textcontainer(self, canvas: Canvas, op: OrderPosition, order: Order, o: dict):
|
||||||
|
|||||||
@@ -2,13 +2,14 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load rich_text %}
|
{% load rich_text %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
{% load wrap_in %}
|
||||||
{% block title %}{% trans "Redirect" %}{% endblock %}
|
{% block title %}{% trans "Redirect" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<i class="fa fa-link fa-fw big-icon"></i>
|
<i class="fa fa-link fa-fw big-icon"></i>
|
||||||
<div class="error-details">
|
<div class="error-details">
|
||||||
<h1>{% trans "Redirect" %}</h1>
|
<h1>{% trans "Redirect" %}</h1>
|
||||||
<h3>
|
<h3>
|
||||||
{% blocktrans trimmed with host="<strong>"|add:hostname|add:"</strong>"|safe %}
|
{% blocktrans trimmed with host=hostname|wrap_in:'strong' %}
|
||||||
The link you clicked on wants to redirect you to a destination on the website {{ host }}.
|
The link you clicked on wants to redirect you to a destination on the website {{ host }}.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
{% blocktrans trimmed %}
|
{% blocktrans trimmed %}
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ from pretix import __version__
|
|||||||
from pretix.base.models import Order, OrderPayment, Transaction
|
from pretix.base.models import Order, OrderPayment, Transaction
|
||||||
from pretix.base.plugins import get_all_plugins
|
from pretix.base.plugins import get_all_plugins
|
||||||
from pretix.base.templatetags.money import money_filter
|
from pretix.base.templatetags.money import money_filter
|
||||||
|
from pretix.helpers.reportlab import PlainTextParagraph
|
||||||
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
||||||
from pretix.settings import DATA_DIR
|
from pretix.settings import DATA_DIR
|
||||||
|
|
||||||
@@ -79,23 +80,23 @@ class SysReport(ReportlabExportMixin):
|
|||||||
style_small.fontSize = 6
|
style_small.fontSize = 6
|
||||||
|
|
||||||
story = [
|
story = [
|
||||||
Paragraph("System report", headlinestyle),
|
PlainTextParagraph("System report", headlinestyle),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
Paragraph("Usage", subheadlinestyle),
|
PlainTextParagraph("Usage", subheadlinestyle),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
self._usage_table(),
|
self._usage_table(),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
Paragraph("Installed versions", subheadlinestyle),
|
PlainTextParagraph("Installed versions", subheadlinestyle),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
self._tech_table(),
|
self._tech_table(),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
Paragraph("Plugins", subheadlinestyle),
|
PlainTextParagraph("Plugins", subheadlinestyle),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
Paragraph(self._get_plugin_versions(), style_small),
|
PlainTextParagraph(self._get_plugin_versions(), style_small),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
Paragraph("Custom templates", subheadlinestyle),
|
PlainTextParagraph("Custom templates", subheadlinestyle),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
Paragraph(self._get_custom_templates(), style_small),
|
PlainTextParagraph(self._get_custom_templates(), style_small),
|
||||||
Spacer(1, 5 * mm),
|
Spacer(1, 5 * mm),
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -121,13 +122,13 @@ class SysReport(ReportlabExportMixin):
|
|||||||
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
|
("RIGHTPADDING", (-1, 0), (-1, -1), 0),
|
||||||
]
|
]
|
||||||
tdata = [
|
tdata = [
|
||||||
[Paragraph("Site URL:", style), Paragraph(settings.SITE_URL, style)],
|
[PlainTextParagraph("Site URL:", style), Paragraph(settings.SITE_URL, style)],
|
||||||
[Paragraph("pretix version:", style), Paragraph(__version__, style)],
|
[PlainTextParagraph("pretix version:", style), Paragraph(__version__, style)],
|
||||||
[Paragraph("Python version:", style), Paragraph(sys.version, style)],
|
[PlainTextParagraph("Python version:", style), Paragraph(sys.version, style)],
|
||||||
[Paragraph("Platform:", style), Paragraph(platform.platform(), style)],
|
[PlainTextParagraph("Platform:", style), Paragraph(platform.platform(), style)],
|
||||||
[
|
[
|
||||||
Paragraph("Database engine:", style),
|
PlainTextParagraph("Database engine:", style),
|
||||||
Paragraph(settings.DATABASES["default"]["ENGINE"], style),
|
PlainTextParagraph(settings.DATABASES["default"]["ENGINE"], style),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
table = Table(tdata, colWidths=colwidths, repeatRows=0)
|
table = Table(tdata, colWidths=colwidths, repeatRows=0)
|
||||||
@@ -206,7 +207,7 @@ class SysReport(ReportlabExportMixin):
|
|||||||
year_last = now().year
|
year_last = now().year
|
||||||
tdata = [
|
tdata = [
|
||||||
[
|
[
|
||||||
Paragraph(l, style_small_head)
|
PlainTextParagraph(l, style_small_head)
|
||||||
for l in (
|
for l in (
|
||||||
"Time frame",
|
"Time frame",
|
||||||
"Currency",
|
"Currency",
|
||||||
@@ -257,19 +258,19 @@ class SysReport(ReportlabExportMixin):
|
|||||||
|
|
||||||
tdata.append(
|
tdata.append(
|
||||||
(
|
(
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
date_format(first_day, "M Y")
|
date_format(first_day, "M Y")
|
||||||
+ " – "
|
+ " – "
|
||||||
+ date_format(after_day - timedelta(days=1), "M Y"),
|
+ date_format(after_day - timedelta(days=1), "M Y"),
|
||||||
style_small,
|
style_small,
|
||||||
),
|
),
|
||||||
Paragraph(c, style_small),
|
PlainTextParagraph(c, style_small),
|
||||||
Paragraph(str(orders_count), style_small) if i == 0 else "",
|
PlainTextParagraph(str(orders_count), style_small) if i == 0 else "",
|
||||||
Paragraph(money_filter(revenue_data.get("s_net") or 0, c), style_small),
|
PlainTextParagraph(money_filter(revenue_data.get("s_net") or 0, c), style_small),
|
||||||
Paragraph(str(testmode_count), style_small) if i == 0 else "",
|
PlainTextParagraph(str(testmode_count), style_small) if i == 0 else "",
|
||||||
Paragraph(str(unconfirmed_count), style_small) if i == 0 else "",
|
PlainTextParagraph(str(unconfirmed_count), style_small) if i == 0 else "",
|
||||||
Paragraph(str(revenue_data.get("c") or 0), style_small),
|
PlainTextParagraph(str(revenue_data.get("c") or 0), style_small),
|
||||||
Paragraph(money_filter(revenue_data.get("s_gross") or 0, c), style_small),
|
PlainTextParagraph(money_filter(revenue_data.get("s_gross") or 0, c), style_small),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -19,9 +19,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<script type="application/json" id="editor-data">
|
{{ layout|json_script:"editor-data" }}
|
||||||
{{ layout|safe }}
|
|
||||||
</script>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-9">
|
<div class="col-md-9">
|
||||||
<div class="panel panel-default panel-pdf-editor">
|
<div class="panel panel-default panel-pdf-editor">
|
||||||
|
|||||||
@@ -284,7 +284,7 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
|
|||||||
ctx['pdf'] = self.get_current_background()
|
ctx['pdf'] = self.get_current_background()
|
||||||
ctx['variables'] = self.get_variables()
|
ctx['variables'] = self.get_variables()
|
||||||
ctx['images'] = self.get_images()
|
ctx['images'] = self.get_images()
|
||||||
ctx['layout'] = json.dumps(self.get_current_layout())
|
ctx['layout'] = self.get_current_layout()
|
||||||
ctx['title'] = self.title
|
ctx['title'] = self.title
|
||||||
ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
|
ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
|
||||||
ctx['maxfilesize'] = self.maxfilesize
|
ctx['maxfilesize'] = self.maxfilesize
|
||||||
|
|||||||
@@ -29,3 +29,8 @@ class PretixHelpersConfig(AppConfig):
|
|||||||
def ready(self):
|
def ready(self):
|
||||||
from .monkeypatching import monkeypatch_all_at_ready
|
from .monkeypatching import monkeypatch_all_at_ready
|
||||||
monkeypatch_all_at_ready()
|
monkeypatch_all_at_ready()
|
||||||
|
|
||||||
|
# Ensure reportlab does not make any calls to the internet or the local disk
|
||||||
|
from reportlab import rl_config
|
||||||
|
rl_config.trustedHosts = []
|
||||||
|
rl_config.trustedSchemes = ['data']
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ from datetime import datetime
|
|||||||
from http import cookies
|
from http import cookies
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import SuspiciousFileOperation
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from requests.adapters import HTTPAdapter
|
from requests.adapters import HTTPAdapter
|
||||||
from urllib3.connection import HTTPConnection, HTTPSConnection
|
from urllib3.connection import HTTPConnection, HTTPSConnection
|
||||||
@@ -40,6 +41,10 @@ from urllib3.util.connection import (
|
|||||||
)
|
)
|
||||||
from urllib3.util.timeout import _DEFAULT_TIMEOUT
|
from urllib3.util.timeout import _DEFAULT_TIMEOUT
|
||||||
|
|
||||||
|
from pretix.helpers.reportlab import ThumbnailingImageReader
|
||||||
|
|
||||||
|
_cgnat_net = ipaddress.ip_network('100.64.0.0/10')
|
||||||
|
|
||||||
|
|
||||||
def monkeypatch_vobject_performance():
|
def monkeypatch_vobject_performance():
|
||||||
"""
|
"""
|
||||||
@@ -226,9 +231,27 @@ def monkeypatch_cookie_morsel():
|
|||||||
cookies.Morsel._reserved.setdefault("partitioned", "Partitioned")
|
cookies.Morsel._reserved.setdefault("partitioned", "Partitioned")
|
||||||
|
|
||||||
|
|
||||||
|
def monkeypatch_reportlab_imagereader():
|
||||||
|
from reportlab.lib import utils
|
||||||
|
old_init = utils.ImageReader.__init__
|
||||||
|
|
||||||
|
def new_init(self, fileName, ident=None): # noqa
|
||||||
|
if not isinstance(fileName, Image.Image) and not hasattr(fileName, 'read') and not hasattr(fileName, 'str'):
|
||||||
|
if not isinstance(self, ThumbnailingImageReader):
|
||||||
|
# ThumbnailingImageReader is only used by us explicitly and not by using <img> in html, so it is safe
|
||||||
|
raise SuspiciousFileOperation("reportlab should not be reading images from disk")
|
||||||
|
|
||||||
|
return types.MethodType(old_init, self)(
|
||||||
|
fileName, ident
|
||||||
|
)
|
||||||
|
|
||||||
|
utils.ImageReader.__init__ = new_init
|
||||||
|
|
||||||
|
|
||||||
def monkeypatch_all_at_ready():
|
def monkeypatch_all_at_ready():
|
||||||
monkeypatch_vobject_performance()
|
monkeypatch_vobject_performance()
|
||||||
monkeypatch_pillow_safer()
|
monkeypatch_pillow_safer()
|
||||||
monkeypatch_requests_timeout()
|
monkeypatch_requests_timeout()
|
||||||
monkeypatch_urllib3_ssrf_protection()
|
monkeypatch_urllib3_ssrf_protection()
|
||||||
monkeypatch_cookie_morsel()
|
monkeypatch_cookie_morsel()
|
||||||
|
monkeypatch_reportlab_imagereader()
|
||||||
|
|||||||
@@ -20,14 +20,19 @@
|
|||||||
# <https://www.gnu.org/licenses/>.
|
# <https://www.gnu.org/licenses/>.
|
||||||
#
|
#
|
||||||
import logging
|
import logging
|
||||||
|
import re
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
from arabic_reshaper import ArabicReshaper
|
from arabic_reshaper import ArabicReshaper
|
||||||
|
from bidi import get_display
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
|
from django.utils.html import escape
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from reportlab.lib.styles import ParagraphStyle
|
from reportlab.lib.styles import ParagraphStyle
|
||||||
from reportlab.lib.utils import ImageReader
|
from reportlab.lib.utils import ImageReader
|
||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
from reportlab.platypus import Paragraph
|
from reportlab.platypus import Paragraph
|
||||||
|
|
||||||
from pretix.presale.style import get_fonts
|
from pretix.presale.style import get_fonts
|
||||||
@@ -70,6 +75,20 @@ reshaper = SimpleLazyObject(lambda: ArabicReshaper(configuration={
|
|||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_text(text: str) -> str:
|
||||||
|
# reportlab does not support unicode combination characters
|
||||||
|
# It's important we do this before we use ArabicReshaper
|
||||||
|
text = unicodedata.normalize("NFKC", text)
|
||||||
|
|
||||||
|
# reportlab does not support RTL, ligature-heavy scripts like Arabic. Therefore, we use ArabicReshaper
|
||||||
|
# to resolve all ligatures and python-bidi to switch RTL texts.
|
||||||
|
try:
|
||||||
|
text = "\n".join(get_display(reshaper.reshape(l)) for l in re.split("\n", text))
|
||||||
|
except:
|
||||||
|
logger.exception('Reshaping/Bidi fixes failed on string {}'.format(repr(text)))
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
class FontFallbackParagraph(Paragraph):
|
class FontFallbackParagraph(Paragraph):
|
||||||
def __init__(self, text, style=None, *args, **kwargs):
|
def __init__(self, text, style=None, *args, **kwargs):
|
||||||
if style is None:
|
if style is None:
|
||||||
@@ -87,6 +106,8 @@ class FontFallbackParagraph(Paragraph):
|
|||||||
if not text:
|
if not text:
|
||||||
return True
|
return True
|
||||||
font = pdfmetrics.getFont(font_name)
|
font = pdfmetrics.getFont(font_name)
|
||||||
|
if not isinstance(font, TTFont):
|
||||||
|
return True
|
||||||
return all(
|
return all(
|
||||||
ord(c) in font.face.charToGlyph or not c.isprintable()
|
ord(c) in font.face.charToGlyph or not c.isprintable()
|
||||||
for c in text
|
for c in text
|
||||||
@@ -102,6 +123,24 @@ class FontFallbackParagraph(Paragraph):
|
|||||||
return family
|
return family
|
||||||
|
|
||||||
|
|
||||||
|
class PlainTextParagraph(FontFallbackParagraph):
|
||||||
|
def __init__(self, text, style=None, linebreaks=True, *args, **kwargs):
|
||||||
|
if not isinstance(text, str):
|
||||||
|
if hasattr(text, '__html__'):
|
||||||
|
raise ValueError("It is contradictory to pass escaped content to PlainTextParagraph")
|
||||||
|
text = str(text)
|
||||||
|
|
||||||
|
# Normalize unicode and apply reshaping
|
||||||
|
text = normalize_text(text)
|
||||||
|
|
||||||
|
# Escape any HTML in the text
|
||||||
|
text = escape(text)
|
||||||
|
|
||||||
|
if linebreaks:
|
||||||
|
text = text.strip().replace("\n", "<br />\n")
|
||||||
|
super().__init__(text, style, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def register_ttf_font_if_new(name, path):
|
def register_ttf_font_if_new(name, path):
|
||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.pdfbase import pdfmetrics
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-today pretix 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/>.
|
||||||
|
#
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
|
register = template.Library()
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@register.filter
|
||||||
|
def wrap_in(content, tag_name):
|
||||||
|
return format_html(f'<{tag_name}>{{}}</{tag_name}>', content)
|
||||||
@@ -64,7 +64,7 @@ from pretix.base.timeframes import (
|
|||||||
from pretix.control.forms.widgets import Select2
|
from pretix.control.forms.widgets import Select2
|
||||||
from pretix.helpers.filenames import safe_for_filename
|
from pretix.helpers.filenames import safe_for_filename
|
||||||
from pretix.helpers.iter import chunked_iterable
|
from pretix.helpers.iter import chunked_iterable
|
||||||
from pretix.helpers.reportlab import FontFallbackParagraph
|
from pretix.helpers.reportlab import PlainTextParagraph
|
||||||
from pretix.helpers.templatetags.jsonfield import JSONExtract
|
from pretix.helpers.templatetags.jsonfield import JSONExtract
|
||||||
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
||||||
|
|
||||||
@@ -344,7 +344,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
|||||||
]
|
]
|
||||||
|
|
||||||
story = [
|
story = [
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
cl.name,
|
cl.name,
|
||||||
headlinestyle
|
headlinestyle
|
||||||
),
|
),
|
||||||
@@ -352,7 +352,7 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
|||||||
if cl.subevent:
|
if cl.subevent:
|
||||||
story += [
|
story += [
|
||||||
Spacer(1, 3 * mm),
|
Spacer(1, 3 * mm),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
'{} ({} {})'.format(
|
'{} ({} {})'.format(
|
||||||
cl.subevent.name,
|
cl.subevent.name,
|
||||||
cl.subevent.get_date_range_display(),
|
cl.subevent.get_date_range_display(),
|
||||||
@@ -382,10 +382,10 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
|||||||
headrowstyle.fontName = 'OpenSansBd'
|
headrowstyle.fontName = 'OpenSansBd'
|
||||||
for q in questions:
|
for q in questions:
|
||||||
txt = str(q.question)
|
txt = str(q.question)
|
||||||
p = FontFallbackParagraph(txt, headrowstyle)
|
p = PlainTextParagraph(txt, headrowstyle)
|
||||||
while p.wrap(colwidths[len(tdata[0])], 5000)[1] > 30 * mm:
|
while p.wrap(colwidths[len(tdata[0])], 5000)[1] > 30 * mm:
|
||||||
txt = txt[:len(txt) - 50] + "..."
|
txt = txt[:len(txt) - 50] + "..."
|
||||||
p = FontFallbackParagraph(txt, headrowstyle)
|
p = PlainTextParagraph(txt, headrowstyle)
|
||||||
tdata[0].append(p)
|
tdata[0].append(p)
|
||||||
|
|
||||||
qs = self._get_queryset(cl, form_data)
|
qs = self._get_queryset(cl, form_data)
|
||||||
@@ -432,8 +432,8 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
|||||||
CBFlowable(bool(op.last_checked_in)) if not op.blocked else '—',
|
CBFlowable(bool(op.last_checked_in)) if not op.blocked else '—',
|
||||||
'✘' if op.order.status != Order.STATUS_PAID else '✔',
|
'✘' if op.order.status != Order.STATUS_PAID else '✔',
|
||||||
op.order.code,
|
op.order.code,
|
||||||
FontFallbackParagraph(name, self.get_style()),
|
PlainTextParagraph(name, self.get_style()),
|
||||||
FontFallbackParagraph(bleach.clean(str(item), tags={'br'}).strip().replace('<br>', '<br/>'), self.get_style()),
|
PlainTextParagraph(bleach.clean(str(item), tags={'br'}).strip().replace('<br>', '<br/>'), self.get_style()),
|
||||||
]
|
]
|
||||||
acache = {}
|
acache = {}
|
||||||
if op.addon_to:
|
if op.addon_to:
|
||||||
@@ -444,10 +444,10 @@ class PDFCheckinList(ReportlabExportMixin, CheckInListMixin, BaseExporter):
|
|||||||
for q in questions:
|
for q in questions:
|
||||||
txt = acache.get(q.pk, '')
|
txt = acache.get(q.pk, '')
|
||||||
txt = bleach.clean(txt, tags={'br'}).strip().replace('<br>', '<br/>')
|
txt = bleach.clean(txt, tags={'br'}).strip().replace('<br>', '<br/>')
|
||||||
p = FontFallbackParagraph(txt, self.get_style())
|
p = PlainTextParagraph(txt, self.get_style())
|
||||||
while p.wrap(colwidths[len(row)], 5000)[1] > 50 * mm:
|
while p.wrap(colwidths[len(row)], 5000)[1] > 50 * mm:
|
||||||
txt = txt[:len(txt) - 50] + "..."
|
txt = txt[:len(txt) - 50] + "..."
|
||||||
p = FontFallbackParagraph(txt, self.get_style())
|
p = PlainTextParagraph(txt, self.get_style())
|
||||||
row.append(p)
|
row.append(p)
|
||||||
if op.order.status != Order.STATUS_PAID:
|
if op.order.status != Order.STATUS_PAID:
|
||||||
tstyledata += [
|
tstyledata += [
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from reportlab.lib import colors, pagesizes
|
|||||||
from reportlab.lib.enums import TA_CENTER, TA_RIGHT
|
from reportlab.lib.enums import TA_CENTER, TA_RIGHT
|
||||||
from reportlab.lib.units import mm
|
from reportlab.lib.units import mm
|
||||||
from reportlab.platypus import (
|
from reportlab.platypus import (
|
||||||
KeepTogether, PageTemplate, Paragraph, Spacer, Table, TableStyle,
|
KeepTogether, PageTemplate, Spacer, Table, TableStyle,
|
||||||
)
|
)
|
||||||
|
|
||||||
from pretix.base.exporter import BaseExporter
|
from pretix.base.exporter import BaseExporter
|
||||||
@@ -49,7 +49,7 @@ from pretix.base.timeframes import (
|
|||||||
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
|
resolve_timeframe_to_datetime_start_inclusive_end_exclusive,
|
||||||
)
|
)
|
||||||
from pretix.control.forms.filter import get_all_payment_providers
|
from pretix.control.forms.filter import get_all_payment_providers
|
||||||
from pretix.helpers.reportlab import FontFallbackParagraph
|
from pretix.helpers.reportlab import PlainTextParagraph
|
||||||
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
from pretix.plugins.reports.exporters import ReportlabExportMixin
|
||||||
|
|
||||||
|
|
||||||
@@ -311,13 +311,13 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
|
|
||||||
tdata = [
|
tdata = [
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(self._transaction_group_header_label(), tstyle_bold),
|
PlainTextParagraph(self._transaction_group_header_label(), tstyle_bold),
|
||||||
FontFallbackParagraph(_("Price"), tstyle_bold_right),
|
PlainTextParagraph(_("Price"), tstyle_bold_right),
|
||||||
FontFallbackParagraph(_("Tax rate"), tstyle_bold_right),
|
PlainTextParagraph(_("Tax rate"), tstyle_bold_right),
|
||||||
FontFallbackParagraph("#", tstyle_bold_right),
|
PlainTextParagraph("#", tstyle_bold_right),
|
||||||
FontFallbackParagraph(_("Net total"), tstyle_bold_right),
|
PlainTextParagraph(_("Net total"), tstyle_bold_right),
|
||||||
FontFallbackParagraph(_("Tax total"), tstyle_bold_right),
|
PlainTextParagraph(_("Tax total"), tstyle_bold_right),
|
||||||
FontFallbackParagraph(_("Gross total"), tstyle_bold_right),
|
PlainTextParagraph(_("Gross total"), tstyle_bold_right),
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -347,12 +347,12 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
|
|
||||||
if e != last_group:
|
if e != last_group:
|
||||||
if last_group_head_idx > 0 and e is not None:
|
if last_group_head_idx > 0 and e is not None:
|
||||||
tdata[last_group_head_idx][4] = Paragraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
|
tdata[last_group_head_idx][4] = PlainTextParagraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
|
||||||
tdata[last_group_head_idx][5] = Paragraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
|
tdata[last_group_head_idx][5] = PlainTextParagraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
|
||||||
tdata[last_group_head_idx][6] = Paragraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
|
tdata[last_group_head_idx][6] = PlainTextParagraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
e,
|
e,
|
||||||
tstyle_bold,
|
tstyle_bold,
|
||||||
),
|
),
|
||||||
@@ -375,20 +375,20 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
text = self._transaction_row_label(r)
|
text = self._transaction_row_label(r)
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(text, tstyle),
|
PlainTextParagraph(text, tstyle),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(r["price"], currency)
|
money_filter(r["price"], currency)
|
||||||
if "price" in r and r["price"] is not None
|
if "price" in r and r["price"] is not None
|
||||||
else "",
|
else "",
|
||||||
tstyle_right,
|
tstyle_right,
|
||||||
),
|
),
|
||||||
Paragraph(localize(r["tax_rate"].normalize()) + " %", tstyle_right),
|
PlainTextParagraph(localize(r["tax_rate"].normalize()) + " %", tstyle_right),
|
||||||
Paragraph(str(r["sum_cont"]), tstyle_right),
|
PlainTextParagraph(str(r["sum_cont"]), tstyle_right),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(r["sum_price"] - r["sum_tax"], currency), tstyle_right
|
money_filter(r["sum_price"] - r["sum_tax"], currency), tstyle_right
|
||||||
),
|
),
|
||||||
Paragraph(money_filter(r["sum_tax"], currency), tstyle_right),
|
PlainTextParagraph(money_filter(r["sum_tax"], currency), tstyle_right),
|
||||||
Paragraph(money_filter(r["sum_price"], currency), tstyle_right),
|
PlainTextParagraph(money_filter(r["sum_price"], currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
sum_cnt_by_tax_rate[r["tax_rate"]] += r["sum_cont"]
|
sum_cnt_by_tax_rate[r["tax_rate"]] += r["sum_cont"]
|
||||||
@@ -398,19 +398,19 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
sum_tax_by_group += r["sum_tax"]
|
sum_tax_by_group += r["sum_tax"]
|
||||||
|
|
||||||
if last_group_head_idx > 0 and last_group is not None:
|
if last_group_head_idx > 0 and last_group is not None:
|
||||||
tdata[last_group_head_idx][4] = Paragraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
|
tdata[last_group_head_idx][4] = PlainTextParagraph(money_filter(sum_price_by_group - sum_tax_by_group, currency), tstyle_bold_right),
|
||||||
tdata[last_group_head_idx][5] = Paragraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
|
tdata[last_group_head_idx][5] = PlainTextParagraph(money_filter(sum_tax_by_group, currency), tstyle_bold_right),
|
||||||
tdata[last_group_head_idx][6] = Paragraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
|
tdata[last_group_head_idx][6] = PlainTextParagraph(money_filter(sum_price_by_group, currency), tstyle_bold_right),
|
||||||
|
|
||||||
if len(sum_tax_by_tax_rate) > 1:
|
if len(sum_tax_by_tax_rate) > 1:
|
||||||
for tax_rate in sorted(sum_tax_by_tax_rate.keys(), reverse=True):
|
for tax_rate in sorted(sum_tax_by_tax_rate.keys(), reverse=True):
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Sum"), tstyle),
|
PlainTextParagraph(_("Sum"), tstyle),
|
||||||
Paragraph("", tstyle_right),
|
PlainTextParagraph("", tstyle_right),
|
||||||
Paragraph(localize(tax_rate.normalize()) + " %", tstyle_right),
|
PlainTextParagraph(localize(tax_rate.normalize()) + " %", tstyle_right),
|
||||||
Paragraph("", tstyle_right),
|
PlainTextParagraph("", tstyle_right),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(
|
money_filter(
|
||||||
sum_price_by_tax_rate[tax_rate]
|
sum_price_by_tax_rate[tax_rate]
|
||||||
- sum_tax_by_tax_rate[tax_rate],
|
- sum_tax_by_tax_rate[tax_rate],
|
||||||
@@ -418,10 +418,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
),
|
),
|
||||||
tstyle_right,
|
tstyle_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(sum_tax_by_tax_rate[tax_rate], currency), tstyle_right
|
money_filter(sum_tax_by_tax_rate[tax_rate], currency), tstyle_right
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(sum_price_by_tax_rate[tax_rate], currency),
|
money_filter(sum_price_by_tax_rate[tax_rate], currency),
|
||||||
tstyle_right,
|
tstyle_right,
|
||||||
),
|
),
|
||||||
@@ -439,11 +439,11 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
|
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Sum"), tstyle_bold),
|
PlainTextParagraph(_("Sum"), tstyle_bold),
|
||||||
Paragraph("", tstyle_right),
|
PlainTextParagraph("", tstyle_right),
|
||||||
Paragraph("", tstyle_right),
|
PlainTextParagraph("", tstyle_right),
|
||||||
Paragraph("", tstyle_bold_right),
|
PlainTextParagraph("", tstyle_bold_right),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(
|
money_filter(
|
||||||
sum(sum_price_by_tax_rate.values())
|
sum(sum_price_by_tax_rate.values())
|
||||||
- sum(sum_tax_by_tax_rate.values()),
|
- sum(sum_tax_by_tax_rate.values()),
|
||||||
@@ -451,11 +451,11 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
),
|
),
|
||||||
tstyle_bold_right,
|
tstyle_bold_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(sum(sum_tax_by_tax_rate.values()), currency),
|
money_filter(sum(sum_tax_by_tax_rate.values()), currency),
|
||||||
tstyle_bold_right,
|
tstyle_bold_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(sum(sum_price_by_tax_rate.values()), currency),
|
money_filter(sum(sum_price_by_tax_rate.values()), currency),
|
||||||
tstyle_bold_right,
|
tstyle_bold_right,
|
||||||
),
|
),
|
||||||
@@ -493,10 +493,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
|
|
||||||
tdata = [
|
tdata = [
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Payment method"), tstyle_bold),
|
PlainTextParagraph(_("Payment method"), tstyle_bold),
|
||||||
FontFallbackParagraph(_("Payments"), tstyle_bold_right),
|
PlainTextParagraph(_("Payments"), tstyle_bold_right),
|
||||||
FontFallbackParagraph(_("Refunds"), tstyle_bold_right),
|
PlainTextParagraph(_("Refunds"), tstyle_bold_right),
|
||||||
FontFallbackParagraph(_("Total"), tstyle_bold_right),
|
PlainTextParagraph(_("Total"), tstyle_bold_right),
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -537,20 +537,20 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
for p in providers:
|
for p in providers:
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
Paragraph(provider_names.get(p, p), tstyle),
|
PlainTextParagraph(provider_names.get(p, p), tstyle),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
money_filter(payments_by_provider[p], currency)
|
money_filter(payments_by_provider[p], currency)
|
||||||
if p in payments_by_provider
|
if p in payments_by_provider
|
||||||
else "",
|
else "",
|
||||||
tstyle_right,
|
tstyle_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(refunds_by_provider[p], currency)
|
money_filter(refunds_by_provider[p], currency)
|
||||||
if p in refunds_by_provider
|
if p in refunds_by_provider
|
||||||
else "",
|
else "",
|
||||||
tstyle_right,
|
tstyle_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(
|
money_filter(
|
||||||
payments_by_provider.get(p, Decimal("0.00"))
|
payments_by_provider.get(p, Decimal("0.00"))
|
||||||
- refunds_by_provider.get(p, Decimal("0.00")),
|
- refunds_by_provider.get(p, Decimal("0.00")),
|
||||||
@@ -563,20 +563,20 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
|
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Sum"), tstyle_bold),
|
PlainTextParagraph(_("Sum"), tstyle_bold),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(
|
money_filter(
|
||||||
sum(payments_by_provider.values(), Decimal("0.00")), currency
|
sum(payments_by_provider.values(), Decimal("0.00")), currency
|
||||||
),
|
),
|
||||||
tstyle_bold_right,
|
tstyle_bold_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(
|
money_filter(
|
||||||
sum(refunds_by_provider.values(), Decimal("0.00")), currency
|
sum(refunds_by_provider.values(), Decimal("0.00")), currency
|
||||||
),
|
),
|
||||||
tstyle_bold_right,
|
tstyle_bold_right,
|
||||||
),
|
),
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
money_filter(
|
money_filter(
|
||||||
sum(payments_by_provider.values(), Decimal("0.00"))
|
sum(payments_by_provider.values(), Decimal("0.00"))
|
||||||
- sum(refunds_by_provider.values(), Decimal("0.00")),
|
- sum(refunds_by_provider.values(), Decimal("0.00")),
|
||||||
@@ -641,7 +641,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
open_before = tx_before - p_before + r_before
|
open_before = tx_before - p_before + r_before
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
_("Pending payments at {datetime}").format(
|
_("Pending payments at {datetime}").format(
|
||||||
datetime=date_format(
|
datetime=date_format(
|
||||||
(df_start - datetime.timedelta.resolution).astimezone(
|
(df_start - datetime.timedelta.resolution).astimezone(
|
||||||
@@ -653,7 +653,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
tstyle,
|
tstyle,
|
||||||
),
|
),
|
||||||
"",
|
"",
|
||||||
Paragraph(money_filter(open_before, currency), tstyle_right),
|
PlainTextParagraph(money_filter(open_before, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -670,30 +670,30 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
] or Decimal("0.00")
|
] or Decimal("0.00")
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Orders"), tstyle),
|
PlainTextParagraph(_("Orders"), tstyle),
|
||||||
Paragraph("+", tstyle_center),
|
PlainTextParagraph("+", tstyle_center),
|
||||||
Paragraph(money_filter(tx_during, currency), tstyle_right),
|
PlainTextParagraph(money_filter(tx_during, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Payments"), tstyle),
|
PlainTextParagraph(_("Payments"), tstyle),
|
||||||
Paragraph("-", tstyle_center),
|
PlainTextParagraph("-", tstyle_center),
|
||||||
Paragraph(money_filter(p_during, currency), tstyle_right),
|
PlainTextParagraph(money_filter(p_during, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Refunds"), tstyle),
|
PlainTextParagraph(_("Refunds"), tstyle),
|
||||||
Paragraph("+", tstyle_center),
|
PlainTextParagraph("+", tstyle_center),
|
||||||
Paragraph(money_filter(r_during, currency), tstyle_right),
|
PlainTextParagraph(money_filter(r_during, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
open_after = open_before + tx_during - p_during + r_during
|
open_after = open_before + tx_during - p_during + r_during
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
_("Pending payments at {datetime}").format(
|
_("Pending payments at {datetime}").format(
|
||||||
datetime=date_format(
|
datetime=date_format(
|
||||||
((df_end or now()) - datetime.timedelta.resolution).astimezone(
|
((df_end or now()) - datetime.timedelta.resolution).astimezone(
|
||||||
@@ -704,8 +704,8 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
),
|
),
|
||||||
tstyle_bold,
|
tstyle_bold,
|
||||||
),
|
),
|
||||||
Paragraph("=", tstyle_center),
|
PlainTextParagraph("=", tstyle_center),
|
||||||
Paragraph(money_filter(open_after, currency), tstyle_bold_right),
|
PlainTextParagraph(money_filter(open_after, currency), tstyle_bold_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
tstyledata += [
|
tstyledata += [
|
||||||
@@ -752,7 +752,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
)
|
)
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
_("Total gift card value at {datetime}").format(
|
_("Total gift card value at {datetime}").format(
|
||||||
datetime=date_format(
|
datetime=date_format(
|
||||||
(df_start - datetime.timedelta.resolution).astimezone(
|
(df_start - datetime.timedelta.resolution).astimezone(
|
||||||
@@ -763,7 +763,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
),
|
),
|
||||||
tstyle,
|
tstyle,
|
||||||
),
|
),
|
||||||
Paragraph(money_filter(tx_before, currency), tstyle_right),
|
PlainTextParagraph(money_filter(tx_before, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -774,8 +774,8 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
] or Decimal("0.00")
|
] or Decimal("0.00")
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Gift card transactions (credit)"), tstyle),
|
PlainTextParagraph(_("Gift card transactions (credit)"), tstyle),
|
||||||
Paragraph(money_filter(tx_during_pos, currency), tstyle_right),
|
PlainTextParagraph(money_filter(tx_during_pos, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -784,15 +784,15 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
] or Decimal("0.00")
|
] or Decimal("0.00")
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Gift card transactions (debit)"), tstyle),
|
PlainTextParagraph(_("Gift card transactions (debit)"), tstyle),
|
||||||
Paragraph(money_filter(tx_during_neg, currency), tstyle_right),
|
PlainTextParagraph(money_filter(tx_during_neg, currency), tstyle_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
open_after = tx_before + tx_during_pos + tx_during_neg
|
open_after = tx_before + tx_during_pos + tx_during_neg
|
||||||
tdata.append(
|
tdata.append(
|
||||||
[
|
[
|
||||||
Paragraph(
|
PlainTextParagraph(
|
||||||
_("Total gift card value at {datetime}").format(
|
_("Total gift card value at {datetime}").format(
|
||||||
datetime=date_format(
|
datetime=date_format(
|
||||||
((df_end or now()) - datetime.timedelta.resolution).astimezone(
|
((df_end or now()) - datetime.timedelta.resolution).astimezone(
|
||||||
@@ -803,7 +803,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
),
|
),
|
||||||
tstyle_bold,
|
tstyle_bold,
|
||||||
),
|
),
|
||||||
Paragraph(money_filter(open_after, currency), tstyle_bold_right),
|
PlainTextParagraph(money_filter(open_after, currency), tstyle_bold_right),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
tstyledata += [
|
tstyledata += [
|
||||||
@@ -854,10 +854,10 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
style_small.leading = 10
|
style_small.leading = 10
|
||||||
|
|
||||||
story = [
|
story = [
|
||||||
FontFallbackParagraph(self.verbose_name, style_h1),
|
PlainTextParagraph(self.verbose_name, style_h1),
|
||||||
Spacer(0, 3 * mm),
|
Spacer(0, 3 * mm),
|
||||||
FontFallbackParagraph(
|
PlainTextParagraph(
|
||||||
"<br />".join(escape(f) for f in self.describe_filters(form_data)),
|
"\n".join(escape(f) for f in self.describe_filters(form_data)),
|
||||||
style_small,
|
style_small,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
@@ -870,7 +870,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
if s:
|
if s:
|
||||||
story += [
|
story += [
|
||||||
Spacer(0, 3 * mm),
|
Spacer(0, 3 * mm),
|
||||||
FontFallbackParagraph(_("Orders") + c_head, style_h2),
|
PlainTextParagraph(_("Orders") + c_head, style_h2),
|
||||||
Spacer(0, 3 * mm),
|
Spacer(0, 3 * mm),
|
||||||
*s
|
*s
|
||||||
]
|
]
|
||||||
@@ -881,7 +881,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
if s:
|
if s:
|
||||||
story += [
|
story += [
|
||||||
Spacer(0, 8 * mm),
|
Spacer(0, 8 * mm),
|
||||||
FontFallbackParagraph(_("Payments") + c_head, style_h2),
|
PlainTextParagraph(_("Payments") + c_head, style_h2),
|
||||||
Spacer(0, 3 * mm),
|
Spacer(0, 3 * mm),
|
||||||
*s
|
*s
|
||||||
]
|
]
|
||||||
@@ -894,7 +894,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
Spacer(0, 8 * mm),
|
Spacer(0, 8 * mm),
|
||||||
KeepTogether(
|
KeepTogether(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Open items") + c_head, style_h2),
|
PlainTextParagraph(_("Open items") + c_head, style_h2),
|
||||||
Spacer(0, 3 * mm),
|
Spacer(0, 3 * mm),
|
||||||
*s
|
*s
|
||||||
]
|
]
|
||||||
@@ -912,7 +912,7 @@ class ReportExporter(ReportlabExportMixin, BaseExporter):
|
|||||||
Spacer(0, 8 * mm),
|
Spacer(0, 8 * mm),
|
||||||
KeepTogether(
|
KeepTogether(
|
||||||
[
|
[
|
||||||
FontFallbackParagraph(_("Gift cards") + c_head, style_h2),
|
PlainTextParagraph(_("Gift cards") + c_head, style_h2),
|
||||||
Spacer(0, 3 * mm),
|
Spacer(0, 3 * mm),
|
||||||
*s,
|
*s,
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ from pretix.base.timeframes import (
|
|||||||
)
|
)
|
||||||
from pretix.control.forms.filter import OverviewFilterForm
|
from pretix.control.forms.filter import OverviewFilterForm
|
||||||
from pretix.helpers.reportlab import (
|
from pretix.helpers.reportlab import (
|
||||||
FontFallbackParagraph, register_ttf_font_if_new,
|
PlainTextParagraph, register_ttf_font_if_new,
|
||||||
)
|
)
|
||||||
from pretix.presale.style import get_fonts
|
from pretix.presale.style import get_fonts
|
||||||
|
|
||||||
@@ -282,7 +282,7 @@ class OverviewReport(Report):
|
|||||||
headlinestyle.fontSize = 15
|
headlinestyle.fontSize = 15
|
||||||
headlinestyle.fontName = 'OpenSansBd'
|
headlinestyle.fontName = 'OpenSansBd'
|
||||||
story = [
|
story = [
|
||||||
FontFallbackParagraph(_('Orders by product') + ' ' + (_('(excl. taxes)') if net else _('(incl. taxes)')), headlinestyle),
|
PlainTextParagraph(_('Orders by product') + ' ' + (_('(excl. taxes)') if net else _('(incl. taxes)')), headlinestyle),
|
||||||
Spacer(1, 5 * mm)
|
Spacer(1, 5 * mm)
|
||||||
]
|
]
|
||||||
return story
|
return story
|
||||||
@@ -292,7 +292,7 @@ class OverviewReport(Report):
|
|||||||
if form_data.get('date_axis') and form_data.get('date_range'):
|
if form_data.get('date_axis') and form_data.get('date_range'):
|
||||||
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
|
d_start, d_end = resolve_timeframe_to_dates_inclusive(now(), form_data['date_range'], self.timezone)
|
||||||
story += [
|
story += [
|
||||||
FontFallbackParagraph(_('{axis} between {start} and {end}').format(
|
PlainTextParagraph(_('{axis} between {start} and {end}').format(
|
||||||
axis=dict(OverviewFilterForm(event=self.event).fields['date_axis'].choices)[form_data.get('date_axis')],
|
axis=dict(OverviewFilterForm(event=self.event).fields['date_axis'].choices)[form_data.get('date_axis')],
|
||||||
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '–',
|
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '–',
|
||||||
end=date_format(d_end, 'SHORT_DATE_FORMAT') if d_end else '–',
|
end=date_format(d_end, 'SHORT_DATE_FORMAT') if d_end else '–',
|
||||||
@@ -305,13 +305,13 @@ class OverviewReport(Report):
|
|||||||
subevent = self.event.subevents.get(pk=self.form_data.get('subevent'))
|
subevent = self.event.subevents.get(pk=self.form_data.get('subevent'))
|
||||||
except SubEvent.DoesNotExist:
|
except SubEvent.DoesNotExist:
|
||||||
subevent = self.form_data.get('subevent')
|
subevent = self.form_data.get('subevent')
|
||||||
story.append(FontFallbackParagraph(pgettext('subevent', 'Date: {}').format(subevent), self.get_style()))
|
story.append(PlainTextParagraph(pgettext('subevent', 'Date: {}').format(subevent), self.get_style()))
|
||||||
story.append(Spacer(1, 5 * mm))
|
story.append(Spacer(1, 5 * mm))
|
||||||
|
|
||||||
if form_data.get('subevent_date_range'):
|
if form_data.get('subevent_date_range'):
|
||||||
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['subevent_date_range'], self.timezone)
|
d_start, d_end = resolve_timeframe_to_datetime_start_inclusive_end_exclusive(now(), form_data['subevent_date_range'], self.timezone)
|
||||||
story += [
|
story += [
|
||||||
FontFallbackParagraph(_('{axis} between {start} and {end}').format(
|
PlainTextParagraph(_('{axis} between {start} and {end}').format(
|
||||||
axis=_('Event date'),
|
axis=_('Event date'),
|
||||||
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '–',
|
start=date_format(d_start, 'SHORT_DATE_FORMAT') if d_start else '–',
|
||||||
end=date_format(d_end - timedelta(hours=1), 'SHORT_DATE_FORMAT') if d_end else '–',
|
end=date_format(d_end - timedelta(hours=1), 'SHORT_DATE_FORMAT') if d_end else '–',
|
||||||
@@ -384,13 +384,13 @@ class OverviewReport(Report):
|
|||||||
tdata = [
|
tdata = [
|
||||||
[
|
[
|
||||||
_('Product'),
|
_('Product'),
|
||||||
FontFallbackParagraph(_('Canceled'), tstyle_th),
|
PlainTextParagraph(_('Canceled'), tstyle_th),
|
||||||
'',
|
'',
|
||||||
FontFallbackParagraph(_('Expired'), tstyle_th),
|
PlainTextParagraph(_('Expired'), tstyle_th),
|
||||||
'',
|
'',
|
||||||
FontFallbackParagraph(_('Approval pending'), tstyle_th),
|
PlainTextParagraph(_('Approval pending'), tstyle_th),
|
||||||
'',
|
'',
|
||||||
FontFallbackParagraph(_('Purchased'), tstyle_th),
|
PlainTextParagraph(_('Purchased'), tstyle_th),
|
||||||
'', '', '', '', ''
|
'', '', '', '', ''
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -421,14 +421,14 @@ class OverviewReport(Report):
|
|||||||
for tup in items_by_category:
|
for tup in items_by_category:
|
||||||
if tup[0]:
|
if tup[0]:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(str(tup[0]), tstyle_bold)
|
PlainTextParagraph(str(tup[0]), tstyle_bold)
|
||||||
])
|
])
|
||||||
for l, s in states:
|
for l, s in states:
|
||||||
tdata[-1].append(str(tup[0].num[l][0]))
|
tdata[-1].append(str(tup[0].num[l][0]))
|
||||||
tdata[-1].append(floatformat(tup[0].num[l][2 if net else 1], places))
|
tdata[-1].append(floatformat(tup[0].num[l][2 if net else 1], places))
|
||||||
for item in tup[1]:
|
for item in tup[1]:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(str(item), tstyle)
|
PlainTextParagraph(str(item), tstyle)
|
||||||
])
|
])
|
||||||
for l, s in states:
|
for l, s in states:
|
||||||
tdata[-1].append(str(item.num[l][0]))
|
tdata[-1].append(str(item.num[l][0]))
|
||||||
@@ -436,7 +436,7 @@ class OverviewReport(Report):
|
|||||||
if item.has_variations:
|
if item.has_variations:
|
||||||
for var in item.all_variations:
|
for var in item.all_variations:
|
||||||
tdata.append([
|
tdata.append([
|
||||||
FontFallbackParagraph(" " + str(var), tstyle)
|
PlainTextParagraph(" " + str(var), tstyle)
|
||||||
])
|
])
|
||||||
for l, s in states:
|
for l, s in states:
|
||||||
tdata[-1].append(str(var.num[l][0]))
|
tdata[-1].append(str(var.num[l][0]))
|
||||||
@@ -568,7 +568,7 @@ class OrderTaxListReportPDF(Report):
|
|||||||
tstyledata.append(('SPAN', (5 + 2 * i, 0), (6 + 2 * i, 0)))
|
tstyledata.append(('SPAN', (5 + 2 * i, 0), (6 + 2 * i, 0)))
|
||||||
|
|
||||||
story = [
|
story = [
|
||||||
FontFallbackParagraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle),
|
PlainTextParagraph(_('Orders by tax rate ({currency})').format(currency=self.event.currency), headlinestyle),
|
||||||
Spacer(1, 5 * mm)
|
Spacer(1, 5 * mm)
|
||||||
]
|
]
|
||||||
tdata = [
|
tdata = [
|
||||||
|
|||||||
@@ -229,6 +229,11 @@ class TicketRendererViewSet(viewsets.ViewSet):
|
|||||||
@action(detail=False, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
|
@action(detail=False, methods=['GET'], url_name='download', url_path='download/(?P<asyncid>[^/]+)/(?P<cfid>[^/]+)')
|
||||||
def download(self, *args, **kwargs):
|
def download(self, *args, **kwargs):
|
||||||
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
cf = get_object_or_404(CachedFile, id=kwargs['cfid'])
|
||||||
|
if not cf.allowed_for_session(self.request, "ticketoutputpdf-api"):
|
||||||
|
return Response(
|
||||||
|
{'status': 'failed', 'message': 'Unknown file ID or export failed'},
|
||||||
|
status=status.HTTP_410_GONE
|
||||||
|
)
|
||||||
if cf.file:
|
if cf.file:
|
||||||
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
|
resp = ChunkBasedFileResponse(cf.file.file, content_type=cf.type)
|
||||||
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
|
resp['Content-Disposition'] = 'attachment; filename="{}"'.format(cf.filename).encode("ascii", "ignore")
|
||||||
@@ -265,6 +270,7 @@ class TicketRendererViewSet(viewsets.ViewSet):
|
|||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
cf = CachedFile(web_download=False)
|
cf = CachedFile(web_download=False)
|
||||||
|
cf.bind_to_session(self.request, "ticketoutputpdf-api")
|
||||||
cf.date = now()
|
cf.date = now()
|
||||||
cf.expires = now() + timedelta(hours=24)
|
cf.expires = now() + timedelta(hours=24)
|
||||||
cf.save()
|
cf.save()
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ from django.http import HttpResponseNotAllowed, JsonResponse
|
|||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.html import conditional_escape
|
||||||
from django.utils.translation import (
|
from django.utils.translation import (
|
||||||
get_language, gettext_lazy as _, pgettext_lazy,
|
get_language, gettext_lazy as _, pgettext_lazy,
|
||||||
)
|
)
|
||||||
@@ -1634,7 +1635,7 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
meta_info = {
|
meta_info = {
|
||||||
'contact_form_data': self.cart_session.get('contact_form_data', {}),
|
'contact_form_data': self.cart_session.get('contact_form_data', {}),
|
||||||
'confirm_messages': [
|
'confirm_messages': [
|
||||||
str(m) for m in self.confirm_messages.values()
|
conditional_escape(str(m)) for m in self.confirm_messages.values()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
api_meta = {}
|
api_meta = {}
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ checkout_confirm_messages = EventPluginSignal()
|
|||||||
This signal is sent out to retrieve short messages that need to be acknowledged by the user before the
|
This signal is sent out to retrieve short messages that need to be acknowledged by the user before the
|
||||||
order can be completed. This is typically used for something like "accept the terms and conditions".
|
order can be completed. This is typically used for something like "accept the terms and conditions".
|
||||||
Receivers are expected to return a dictionary where the keys are globally unique identifiers for the
|
Receivers are expected to return a dictionary where the keys are globally unique identifiers for the
|
||||||
message and the values can be arbitrary HTML.
|
message and the values can be a SafeString containing arbitrary HTML, or a string that will be HTML-escaped.
|
||||||
|
|
||||||
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
As with all event plugin signals, the ``sender`` keyword argument will contain the event.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -176,7 +176,7 @@
|
|||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
<label for="input_confirm_{{ key }}">
|
<label for="input_confirm_{{ key }}">
|
||||||
<input type="checkbox" class="checkbox" value="yes" name="confirm_{{ key }}" id="input_confirm_{{ key }}" required>
|
<input type="checkbox" class="checkbox" value="yes" name="confirm_{{ key }}" id="input_confirm_{{ key }}" required>
|
||||||
{{ desc|safe }}
|
{{ desc }}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
{% load eventsignal %}
|
{% load eventsignal %}
|
||||||
{% load money %}
|
{% load money %}
|
||||||
{% load eventurl %}
|
{% load eventurl %}
|
||||||
|
{% load wrap_in %}
|
||||||
{% block title %}{% trans "Registration details" %}{% endblock %}
|
{% block title %}{% trans "Registration details" %}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2 class="h1">
|
<h2 class="h1">
|
||||||
@@ -48,7 +49,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<p>
|
<p>
|
||||||
{% blocktrans trimmed with email="<strong>"|add:order.email|add:"</strong>"|safe %}
|
{% blocktrans trimmed with email=order.email|wrap_in:"strong" %}
|
||||||
This order is managed for you by {{ email }}. Please contact them for any questions regarding
|
This order is managed for you by {{ email }}. Please contact them for any questions regarding
|
||||||
payment, cancellation or changes to this order.
|
payment, cancellation or changes to this order.
|
||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
|
|||||||
@@ -265,7 +265,7 @@ EMAIL_USE_TLS = config.getboolean('mail', 'tls', fallback=False)
|
|||||||
EMAIL_USE_SSL = config.getboolean('mail', 'ssl', fallback=False)
|
EMAIL_USE_SSL = config.getboolean('mail', 'ssl', fallback=False)
|
||||||
EMAIL_SUBJECT_PREFIX = '[pretix] '
|
EMAIL_SUBJECT_PREFIX = '[pretix] '
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
EMAIL_CUSTOM_SMTP_BACKEND = 'pretixbase.email.CheckPrivateNetworkSmtpBackend'
|
EMAIL_CUSTOM_SMTP_BACKEND = 'pretix.base.email.CheckPrivateNetworkSmtpBackend'
|
||||||
EMAIL_TIMEOUT = 60
|
EMAIL_TIMEOUT = 60
|
||||||
|
|
||||||
ADMINS = [('Admin', n) for n in config.get('mail', 'admins', fallback='').split(",") if n]
|
ADMINS = [('Admin', n) for n in config.get('mail', 'admins', fallback='').split(",") if n]
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-today pretix 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/>.
|
||||||
|
#
|
||||||
|
import pytest
|
||||||
|
from django.core.exceptions import SuspiciousFileOperation
|
||||||
|
from reportlab.platypus import Paragraph
|
||||||
|
|
||||||
|
|
||||||
|
def test_http_access_disabled(monkeypatch):
|
||||||
|
def guard(*args, **kwargs):
|
||||||
|
pytest.fail("No internet wanted!")
|
||||||
|
|
||||||
|
monkeypatch.setattr('socket.socket', guard)
|
||||||
|
|
||||||
|
with pytest.raises(SuspiciousFileOperation, match="should not be reading images from disk"):
|
||||||
|
Paragraph(
|
||||||
|
'<img src="https://static.pretix.cloud/static/pretixeu/img/opengraph.png"/>',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_access_disabled_scheme(monkeypatch):
|
||||||
|
with pytest.raises(SuspiciousFileOperation, match="should not be reading images from disk"):
|
||||||
|
Paragraph(
|
||||||
|
'<img src="file:///etc/passwd" />',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_file_access_disabled_direct(monkeypatch):
|
||||||
|
with pytest.raises(SuspiciousFileOperation, match="should not be reading images from disk"):
|
||||||
|
Paragraph(
|
||||||
|
'<img src="/etc/passwd" />',
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user