Compare commits

..

1 Commits

Author SHA1 Message Date
Richard Schreiber
e009498386 Move request.GET.items to ctx 2026-02-12 07:22:35 +01:00
17 changed files with 145 additions and 33360 deletions

View File

@@ -39,7 +39,7 @@ from pretix.base.templatetags.rich_text import (
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
markdown_compile_email, truelink_callback,
)
from pretix.helpers.format import FormattedString, SafeFormatter, format_map
from pretix.helpers.format import SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa
get_available_placeholders, PlaceholderContext
@@ -141,7 +141,6 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
return markdown_compile_email(plaintext, context=context)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
apply_format_map = not isinstance(plain_body, FormattedString)
body_md = self.compile_markdown(plain_body, context)
if context:
linker = bleach.Linker(
@@ -150,13 +149,12 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
if apply_format_map:
body_md = format_map(
body_md,
context=context,
mode=SafeFormatter.MODE_RICH_TO_HTML,
linkifier=linker
)
body_md = format_map(
body_md,
context=context,
mode=SafeFormatter.MODE_RICH_TO_HTML,
linkifier=linker
)
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,

View File

@@ -87,7 +87,7 @@ from pretix.base.timemachine import time_machine_now
from ...helpers import OF_SELF
from ...helpers.countries import CachedCountries, FastCountryField
from ...helpers.format import FormattedString, format_map
from ...helpers.format import format_map
from ...helpers.names import build_name
from ...testutils.middleware import debugflags_var
from ._transactions import (
@@ -1178,8 +1178,7 @@ class Order(LockModel, LoggedModel):
recipient = position.attendee_email
email_content = render_mail(template, context)
if not isinstance(subject, FormattedString):
subject = format_map(subject, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender,
@@ -2908,8 +2907,7 @@ class OrderPosition(AbstractPosition):
with language(self.order.locale, self.order.event.settings.region):
recipient = self.attendee_email
email_content = render_mail(template, context)
if not isinstance(subject, FormattedString):
subject = format_map(subject, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender,

View File

@@ -81,9 +81,7 @@ from pretix.base.signals import (
)
from pretix.celery_app import app
from pretix.helpers import OF_SELF
from pretix.helpers.format import (
FormattedString, PlainHtmlAlternativeString, SafeFormatter, format_map,
)
from pretix.helpers.format import SafeFormatter, format_map
from pretix.helpers.hierarkey import clean_filename
from pretix.multidomain.urlreverse import build_absolute_uri
from pretix.presale.ical import get_private_icals
@@ -220,9 +218,6 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
if email == INVALID_ADDRESS:
return
if isinstance(template, FormattedString):
raise TypeError("Cannot pass an already formatted body template")
if no_order_links and not plain_text_only:
raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
@@ -256,28 +251,23 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
if event and attach_tickets and not event.settings.mail_attach_tickets:
attach_tickets = False
with language(locale), override(timezone):
with language(locale):
if isinstance(context, dict) and order:
_autoextend_context(context, order)
# Build raw content
content_plain = render_mail(template, context, placeholder_mode=None)
body_plain = render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN)
if settings_holder:
signature = str(settings_holder.settings.get('mail_text_signature'))
else:
signature = ""
# Build full plain-text body
if not isinstance(content_plain, FormattedString):
body_plain = format_map(content_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
else:
body_plain = content_plain
body_plain = _wrap_plain_body(body_plain, signature, event, order, position, no_order_links)
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
# Build subject
if not isinstance(subject, FormattedString):
subject = format_map(subject, context)
subject = str(subject).format_map(TolerantDict(context))
subject = raw_subject = subject.replace('\n', ' ').replace('\r', '')[:900]
if settings_holder:
subject = prefix_subject(settings_holder, subject)
@@ -296,24 +286,26 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
else:
renderer = ClassicMailRenderer(None, organizer)
try:
if 'context' in inspect.signature(renderer.render).parameters:
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
elif 'position' in inspect.signature(renderer.render).parameters:
# Backwards compatibility
warnings.warn('Email renderer called without context argument because context argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
else:
# Backwards compatibility
warnings.warn('Email renderer called without position argument because position argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order)
except:
logger.exception('Could not render HTML body')
body_html = None
with override(timezone):
content_plain = render_mail(template, context, placeholder_mode=None)
try:
if 'context' in inspect.signature(renderer.render).parameters:
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
elif 'position' in inspect.signature(renderer.render).parameters:
# Backwards compatibility
warnings.warn('Email renderer called without context argument because context argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
else:
# Backwards compatibility
warnings.warn('Email renderer called without position argument because position argument is not '
'supported.',
DeprecationWarning)
body_html = renderer.render(content_plain, signature, raw_subject, order)
except:
logger.exception('Could not render HTML body')
body_html = None
m = OutgoingMail.objects.create(
organizer=organizer,
@@ -798,12 +790,7 @@ def render_mail(template, context, placeholder_mode: Optional[int]=SafeFormatter
body = format_map(body, context, mode=placeholder_mode)
else:
tpl = get_template(template)
context = {
# Known bug, should behave differently for plain and HTML but we'll fix after security release
k: v.html if isinstance(v, PlainHtmlAlternativeString) else v
for k, v in context.items()
}
body = FormattedString(tpl.render(context))
body = tpl.render(context)
return body
@@ -949,7 +936,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
body_plain += "\r\n\r\n-- \r\n"
if signature:
signature = format_map(signature, {"event": event.name if event else ''})
signature = signature.format(event=event.name if event else '')
body_plain += signature
body_plain += "\r\n\r\n-- \r\n"
@@ -961,7 +948,7 @@ def _wrap_plain_body(content_plain, signature, event, order, position, no_order_
body_plain += _(
"You can view your order details at the following URL:\n{orderurl}."
).replace("\n", "\r\n").format(
orderurl=build_absolute_uri(
event=event.name, orderurl=build_absolute_uri(
order.event, 'presale:event.order.position', kwargs={
'order': order.code,
'secret': position.web_secret,

View File

@@ -8,6 +8,9 @@
<h1>{% trans "Not found" %}</h1>
<p>{% trans "I'm afraid we could not find the the resource you requested." %}</p>
<p>{{ exception }}</p>
<p class="links">
<a id='goback' href='#'>{% trans "Take a step back" %}</a>
</p>
{% if request.user.is_staff and not staff_session %}
<form action="{% url 'control:user.sudo' %}?next={{ request.path|add:"?"|add:request.GET.urlencode|urlencode }}" method="post">
<p>

View File

@@ -22,7 +22,6 @@
import logging
from string import Formatter
from django.core.exceptions import SuspiciousOperation
from django.utils.html import conditional_escape
logger = logging.getLogger(__name__)
@@ -38,17 +37,6 @@ class PlainHtmlAlternativeString:
return f"PlainHtmlAlternativeString('{self.plain}', '{self.html}')"
class FormattedString(str):
"""
A str subclass that has been specifically marked as "already formatted" for email rendering
purposes to avoid duplicate formatting.
"""
__slots__ = ()
def __str__(self):
return self
class SafeFormatter(Formatter):
"""
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
@@ -89,19 +77,8 @@ class SafeFormatter(Formatter):
# Ignore format_spec
return super().format_field(self._prepare_value(value), '')
def convert_field(self, value, conversion):
# Ignore any conversions
if conversion is None:
return value
else:
return str(value)
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None) -> FormattedString:
if isinstance(template, FormattedString):
raise SuspiciousOperation("Calling format_map() on an already formatted string is likely unsafe.")
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN, linkifier=None):
if not isinstance(template, str):
template = str(template)
return FormattedString(
SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
)
return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2026-02-12 20:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"PO-Revision-Date: 2026-02-06 07:41+0000\n"
"Last-Translator: Ryo Tagami <rtagami@airstrip.jp>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix/"
"ja/>\n"
"Language: ja\n"
@@ -2544,7 +2544,7 @@ msgstr "終了日"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:12
#: pretix/presale/templates/pretixpresale/organizers/customer_membership.html:91
msgid "Product"
msgstr "品"
msgstr "品"
#: pretix/base/exporters/orderlist.py:652 pretix/base/models/vouchers.py:315
#: pretix/control/templates/pretixcontrol/vouchers/bulk.html:5
@@ -4048,7 +4048,7 @@ msgstr "複数の一致する製品が見つかりました。"
#: pretix/base/modelimport_vouchers.py:205 pretix/base/models/items.py:1257
#: pretix/base/models/vouchers.py:266 pretix/base/models/waitinglist.py:100
msgid "Product variation"
msgstr "品バリエーション"
msgstr "品バリエーション"
#: pretix/base/modelimport_orders.py:161
msgid "The variation can be specified by its internal ID or full name."
@@ -9144,8 +9144,8 @@ msgid ""
"The voucher code used for one of the items in your cart has already been too "
"often. We adjusted the price of the item in your cart."
msgstr ""
"カート内のアイテムに使用されバウチャーコードは、すでに使用回数の上限に達し"
"ています。カート内のアイテムの価格を調整しました。"
"カートの商品の一つに使用されているバウチャーコードはに使用回数を超えていま"
"す。カート内の商品価格を調整しました。"
#: pretix/base/services/orders.py:182
msgid ""
@@ -24181,7 +24181,7 @@ msgstr "高度な検索"
#: pretix/control/templates/pretixcontrol/orders/index.html:102
#, python-format
msgid "List filtered by answers to question \"%(question)s\"."
msgstr "質問%(question)sへの回答で絞り込まれたリストです。"
msgstr "質問 \"%(question)s\" への回答で絞り込まれたリスト"
#: pretix/control/templates/pretixcontrol/orders/index.html:107
msgid "Remove filter"
@@ -34229,8 +34229,8 @@ msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"カート内のアイテムの予約が解除されました。在庫がある限り、引き続き注文を完了"
"できます。"
"カート内の商品の確保期限が切れました。在庫があれば、このまま注文を完了するこ"
"とができます。"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:514
#: pretix/presale/templates/pretixpresale/fragment_modals.html:48
@@ -34243,7 +34243,7 @@ msgstr "確保が更新されました"
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:532
msgid "Overview of your ordered products."
msgstr "注文した製品の概要"
msgstr "注文した製品の概要"
#: pretix/presale/templates/pretixpresale/event/fragment_cart_box.html:50
msgid "Continue with order process"

View File

@@ -8,8 +8,8 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 09:10+0000\n"
"PO-Revision-Date: 2026-02-12 20:00+0000\n"
"Last-Translator: Yasunobu YesNo Kawaguchi <kawaguti@gmail.com>\n"
"PO-Revision-Date: 2026-02-06 07:41+0000\n"
"Last-Translator: Ryo Tagami <rtagami@airstrip.jp>\n"
"Language-Team: Japanese <https://translate.pretix.eu/projects/pretix/pretix-"
"js/ja/>\n"
"Language: ja\n"
@@ -456,11 +456,11 @@ msgstr "="
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:99
msgid "Product"
msgstr "品"
msgstr "品"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:103
msgid "Product variation"
msgstr "品バリエーション"
msgstr "品バリエーション"
#: pretix/static/pretixcontrol/js/ui/checkinrules.js:107
msgid "Gate"
@@ -726,8 +726,8 @@ msgid ""
"The items in your cart are no longer reserved for you. You can still "
"complete your order as long as theyre available."
msgstr ""
"カート内のアイテムの予約が解除されました。在庫がある限り、引き続き注文を完了"
"できます。"
"カート内の商品の確保期限が切れました。在庫があれば、このまま注文を完了するこ"
"とができます。"
#: pretix/static/pretixpresale/js/ui/cart.js:49
msgid "Cart expired"

View File

@@ -7,7 +7,7 @@ msgstr ""
"Project-Id-Version: 1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2026-02-12 00:00+0000\n"
"PO-Revision-Date: 2026-02-05 23:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch <https://translate.pretix.eu/projects/pretix/pretix/nl/>"
"\n"
@@ -7993,11 +7993,11 @@ msgid ""
"12345 Any City\n"
"Atlantis"
msgstr ""
"Piet Janssen\n"
"Piet Peeters\n"
"Voorbeeldbedrijf\n"
"Sesamstraat 42\n"
"1234 AB Amsterdam\n"
"Nederland"
"12345 Ergens\n"
"Niemandsland"
#: pretix/base/pdf.py:198
msgid "Attendee street"

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2026-02-12 00:00+0000\n"
"PO-Revision-Date: 2026-02-05 23:00+0000\n"
"Last-Translator: Ruud Hendrickx <ruud@leckxicon.eu>\n"
"Language-Team: Dutch (informal) <https://translate.pretix.eu/projects/pretix/"
"pretix/nl_Informal/>\n"
@@ -8002,9 +8002,9 @@ msgid ""
"Atlantis"
msgstr ""
"Piet Janssen\n"
"Voorbeeldbedrijf\n"
"Voorbeeld bedrijf\n"
"Sesamstraat 42\n"
"1234 AB Amsterdam\n"
"1234AB Amsterdam\n"
"Nederland"
#: pretix/base/pdf.py:198
@@ -21786,9 +21786,9 @@ msgid ""
"copy all products, categories, quotas, and questions as well as general "
"event settings."
msgstr ""
"Wil je de configuratie van een ander evenement kopiëren? We kopiëren alle "
"producten, categorieën, quota en vragen, evenals de algemene "
"evenementinstellingen."
"Wil je de instellingen van een ander evenement kopiëren? We zullen alle "
"producten, categorieën, quota en vragen overnemen, samen met de algemene "
"evenementsinstellingen."
#: pretix/control/templates/pretixcontrol/events/create_copy.html:13
msgid ""
@@ -21796,25 +21796,25 @@ msgid ""
"need to change some settings manually, e.g. date and time settings and texts "
"that contain the event name."
msgstr ""
"Controleer alle instellingen zorgvuldig. Waarschijnlijk moet je nog enkele "
"instellingen handmatig wijzigen, bijvoorbeeld de datum- en tijdinstellingen "
"en teksten die de naam van het evenement bevatten."
"Controleer alle instellingen! Je moet waarschijnlijk nog steeds wat "
"instellingen handmatig aanpassen, bijvoorbeeld datum- en tijdsinstellingen, "
"en teksten die de evenementsnaam bevatten."
#: pretix/control/templates/pretixcontrol/events/create_foundation.html:7
msgid "Event type"
msgstr "Type evenement"
msgstr "Evenementssoort"
#: pretix/control/templates/pretixcontrol/events/create_foundation.html:13
msgid "Singular event or non-event shop"
msgstr "Eenmalig evenement of geen evenement"
msgstr "Enkel evenement of winkel die niet voor een evenement is"
#: pretix/control/templates/pretixcontrol/events/create_foundation.html:15
msgid ""
"An event with individual configuration. If you create more events later, you "
"can copy the event to save yourself some work."
msgstr ""
"Een evenement met eigen instellingen. Als je later meer evenementen "
"aanmaakt, kan je de instellingen van dit evenement kopiëren."
"Een evenement met eigen instellingen. Als je later meer evenementen aanmaakt "
"kan je de instellingen van dit evenement kopiëren."
#: pretix/control/templates/pretixcontrol/events/create_foundation.html:21
msgid ""
@@ -21834,8 +21834,8 @@ msgid ""
"A series of events that share the same configuration. They can still be "
"different in their dates, locations, prices, and capacities."
msgstr ""
"Een reeks evenementen met dezelfde configuratie. Ze kunnen nog steeds "
"verschillen qua data, locaties, prijzen en capaciteiten."
"Een reeks evenementen die dezelfde instellingen delen. De evenementen in de "
"reeks kunnen verschillen in hun datums, prijzen en capaciteiten."
#: pretix/control/templates/pretixcontrol/events/create_foundation.html:40
msgid ""
@@ -21861,8 +21861,9 @@ msgid ""
"The list below shows all events you have administrative access to. Click on "
"the event name to access event details."
msgstr ""
"De onderstaande lijst toont alle evenementen waar je beheerderstoegang toe "
"hebt. Klik op de naam van het evenement om de details ervan te bekijken."
"De lijst hieronder toont alle evenementen waar je administratieve toegang "
"toe hebt. Klik op de evenementsnaam om de details van het evenement te "
"openen."
#: pretix/control/templates/pretixcontrol/events/index.html:12
#: pretix/control/templates/pretixcontrol/organizers/detail.html:18
@@ -21922,7 +21923,7 @@ msgstr "Aantallen bijgewerkt op %(date)s"
#: pretix/control/templates/pretixcontrol/fragment_quota_box_paid.html:3
#, python-format
msgid "Currently available: %(num)s"
msgstr "Nu beschikbaar: %(num)s"
msgstr "Op dit moment beschikbaar: %(num)s"
#: pretix/control/templates/pretixcontrol/global_license.html:8
msgid ""
@@ -21972,7 +21973,7 @@ msgstr "Installatiegegevens"
#: pretix/control/templates/pretixcontrol/global_license.html:34
msgid "Installed plugins"
msgstr "Geïnstalleerde plug-ins"
msgstr "Geïnstalleerde plugins"
#: pretix/control/templates/pretixcontrol/global_license.html:40
msgid "Public information"

View File

@@ -8,7 +8,7 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-01-26 13:19+0000\n"
"PO-Revision-Date: 2026-02-14 06:10+0000\n"
"PO-Revision-Date: 2026-02-05 14:34+0000\n"
"Last-Translator: Nate Horst <nate@agcthailand.org>\n"
"Language-Team: Thai <https://translate.pretix.eu/projects/pretix/pretix/th/>"
"\n"
@@ -1000,7 +1000,7 @@ msgstr "โดเมนอีเมลที่ใช้สั่งซื้อ
#: pretix/plugins/reports/exporters.py:900
#: pretix/plugins/ticketoutputpdf/exporters.py:96
msgid "Order code"
msgstr "รหัสการลงทะเบียน"
msgstr "รหัสการสั่งซื้อ"
#: pretix/base/datasync/sourcefields.py:380
msgid "Event and order code"
@@ -7190,7 +7190,7 @@ msgstr "สินค้าที่ซื้อแล้ว"
#: pretix/base/services/placeholders.py:424
#: pretix/base/templates/pretixbase/email/order_details.html:151
msgid "View order details"
msgstr "ดูรายละเอียดการลงทะเบียน"
msgstr "ดูรายละเอียดคำสั่งซื้อ"
#: pretix/base/notifications.py:234
#, python-brace-format
@@ -11567,7 +11567,7 @@ msgstr ""
#: pretix/base/settings.py:2856 pretix/base/settings.py:2872
#, python-brace-format
msgid "Your ticket is ready for download: {code}"
msgstr "ตั๋วของคุณพร้อมสำหรับการดาวน์โหลดแล้ว: {code}"
msgstr ""
#: pretix/base/settings.py:2860
#, python-brace-format
@@ -12477,7 +12477,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/base.html:222
#: pretix/presale/templates/pretixpresale/organizers/base.html:100
msgid "Contact"
msgstr "ติดต่อ"
msgstr ""
#: pretix/base/templates/pretixbase/email/shred_completed.txt:2
#, python-format
@@ -12548,23 +12548,21 @@ msgstr ""
#: pretix/base/templates/source.html:5 pretix/base/templates/source.html:9
msgid "Source code"
msgstr "ซอร์สโค้ด (Source code)"
msgstr ""
#: pretix/base/templates/source.html:10
msgid ""
"This site is powered by free software. If you want to read the license terms "
"or obtain the source code, follow these links or instructions:"
msgstr ""
"เว็บไซต์นี้ขับเคลื่อนโดยซอฟต์แวร์เสรี หากคุณต้องการอ่านเงื่อนไขการอนุญาตใช้งานหรือรับซอร์สโค้ด โปร"
"ดไปที่ลิงก์หรือคำแนะนำต่อไปนี้:"
#: pretix/base/ticketoutput.py:182
msgid "Enable ticket format"
msgstr "เปิดใช้งานรูปแบบตั๋ว"
msgstr ""
#: pretix/base/ticketoutput.py:200
msgid "Download ticket"
msgstr "ดาวน์โหลดตั๋ว"
msgstr ""
#: pretix/base/timeframes.py:49
msgctxt "reporting_timeframe"
@@ -19829,7 +19827,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:7
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:40
msgid "Ticket download"
msgstr "ดาวน์โหลดตั๋ว"
msgstr ""
#: pretix/control/templates/pretixcontrol/event/tickets.html:11
msgid "Download settings"
@@ -21762,27 +21760,27 @@ msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:410
msgid "Change products"
msgstr "เปลี่ยนรายการสินค้า"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:415
#: pretix/presale/templates/pretixpresale/event/order.html:197
msgid "Ordered items"
msgstr "รายการสินค้าที่สั่งซื้อ"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:434
#, python-format
msgid "Denied scan: %(date)s"
msgstr "การสแกนถูกปฏิเสธ: %(date)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:439
#, python-format
msgid "Exit scan: %(date)s"
msgstr "สแกนขาออก: %(date)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:446
#, python-format
msgid "Entry scan: %(date)s"
msgstr "สแกนขาเข้า: %(date)s"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:465
#: pretix/presale/templates/pretixpresale/event/fragment_cart.html:55
@@ -21924,7 +21922,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_confirm.html:90
#: pretix/presale/templates/pretixpresale/event/order.html:318
msgid "ZIP code and city"
msgstr "รหัสไปรษณีย์ และชื่อเมือง"
msgstr ""
#: pretix/control/templates/pretixcontrol/order/index.html:1047
msgid "Valid EU VAT ID"
@@ -30728,46 +30726,46 @@ msgstr ""
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:65
msgid "Download tickets (PDF)"
msgstr "ดาวน์โหลดตั๋ว (PDF)"
msgstr ""
#: pretix/plugins/ticketoutputpdf/ticketoutput.py:66
msgid "Download ticket (PDF)"
msgstr "ดาวน์โหลดตั๋ว (PDF)"
msgstr ""
#: pretix/plugins/ticketoutputpdf/views.py:62
msgid "Default ticket layout"
msgstr "รูปแบบตั๋วมาตรฐาน"
msgstr ""
#: pretix/plugins/ticketoutputpdf/views.py:119
msgid "The new ticket layout has been created."
msgstr "สร้างรูปแบบตั๋วใหม่เรียบร้อยแล้ว"
msgstr ""
#: pretix/plugins/ticketoutputpdf/views.py:168
#: pretix/plugins/ticketoutputpdf/views.py:198
#: pretix/plugins/ticketoutputpdf/views.py:246
msgid "The requested layout does not exist."
msgstr "ไม่พบรูปแบบตั๋วที่ร้องขอ"
msgstr ""
#: pretix/plugins/ticketoutputpdf/views.py:210
msgid "The selected ticket layout been deleted."
msgstr "รูปแบบตั๋วที่เลือกถูกลบออกแล้ว"
msgstr ""
#: pretix/plugins/ticketoutputpdf/views.py:250
#, python-brace-format
msgid "Ticket PDF layout: {}"
msgstr "รูปแบบไฟล์ PDF ของตั๋ว: {}"
msgstr ""
#: pretix/plugins/webcheckin/apps.py:30 pretix/plugins/webcheckin/apps.py:33
msgid "Web-based check-in"
msgstr "การเช็คอินผ่านเว็บ"
msgstr ""
#: pretix/plugins/webcheckin/apps.py:38
msgid "Turn your browser into a check-in device to perform access control."
msgstr "เปลี่ยนเบราว์เซอร์ของคุณให้เป็นอุปกรณ์เช็คอินเพื่อควบคุมการเข้างาน"
msgstr ""
#: pretix/plugins/webcheckin/apps.py:40 pretix/plugins/webcheckin/signals.py:36
msgid "Web Check-in"
msgstr "เว็บเช็คอิน (Web Check-in)"
msgstr ""
#: pretix/presale/checkoutflow.py:117
msgctxt "checkoutflow"
@@ -31190,7 +31188,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/base.html:242
#: pretix/presale/templates/pretixpresale/organizers/base.html:120
msgid "Imprint"
msgstr "ข้อมูลทางกฎหมาย (Imprint)"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/checkout_addons.html:12
msgid ""
@@ -32077,25 +32075,24 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:76
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:82
msgid "Please have your ticket ready when entering the event."
msgstr "โปรดเตรียมตั๋วของคุณให้พร้อมเมื่อเข้าสู่งาน"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:85
msgid "Download your tickets using the buttons below."
msgstr "ดาวน์โหลดตั๋วของคุณโดยใช้ปุ่มด้านล่างนี้"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_downloads.html:94
#, python-format
msgid "You will be able to download your tickets here starting on %(date)s."
msgstr ""
"คุณจะสามารถดาวน์โหลดตั๋วของคุณได้ที่นี่ ตั้งแต่วันที่ %(date)s เป็นต้นไป"
#: pretix/presale/templates/pretixpresale/event/fragment_event_info.html:7
msgid "Where does the event happen?"
msgstr "กิจกรรมจัดขึ้นที่ไหน?"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_event_info.html:17
msgid "When does the event happen?"
msgstr "กิจกรรมจัดขึ้นเมื่อไหร่?"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/fragment_event_info.html:26
#, python-format
@@ -32494,7 +32491,7 @@ msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:287
msgid "Your information"
msgstr "ข้อมูลของคุณ"
msgstr ""
#: pretix/presale/templates/pretixpresale/event/order.html:290
msgid "Change your information"

View File

@@ -1,14 +1,14 @@
{% extends "pretixpresale/event/base.html" %}
{% load i18n %}
{% load bootstrap3 %}
{% block title %}{% trans "Resend order link" %}{% endblock %}
{% block title %}{% trans "Resend order links" %}{% endblock %}
{% block custom_header %}
{{ block.super }}
<meta name="robots" content="noindex, nofollow">
{% endblock %}
{% block content %}
<h2>
{% trans "Resend order link" %}
{% trans "Resend order links" %}
</h2>
<p>
{% blocktrans trimmed %}

View File

@@ -1510,10 +1510,7 @@ class OrderChangeMixin:
'max_count': iao.max_count,
'iao': iao,
'items': [i for i in items if not i.require_voucher],
'items_missing': {
k: v for k, v in current_addon_products_missing.items()
if v and k[0].category_id == iao.addon_category_id
},
'items_missing': {k: v for k, v in current_addon_products_missing.items() if v},
})
return positions

View File

@@ -252,7 +252,7 @@ Vue.component('availbox', {
variation: Object
},
mounted: function() {
if (!this.$root.cart_exists && this.$root.itemnum === 1 && (!this.$root.categories[0].items[0].has_variations || this.$root.categories[0].items[0].variations.length < 2) && !this.$root.has_seating_plan ? 1 : 0) {
if (this.$root.itemnum === 1 && (!this.$root.categories[0].items[0].has_variations || this.$root.categories[0].items[0].variations.length < 2) && !this.$root.has_seating_plan ? 1 : 0) {
this.$refs.quantity.value = 1;
if (this.order_max === 1) {
this.$refs.quantity.checked = true;

View File

@@ -32,26 +32,21 @@
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under the License.
import datetime
import os
import re
from decimal import Decimal
from email.mime.text import MIMEText
import pytest
from django.conf import settings
from django.core import mail as djmail
from django.test import override_settings
from django.utils.html import escape
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django_scopes import scope, scopes_disabled
from django_scopes import scope
from i18nfield.strings import LazyI18nString
from pretix.base.email import get_email_context
from pretix.base.models import (
Event, InvoiceAddress, Order, Organizer, OutgoingMail, User,
)
from pretix.base.models import Event, Organizer, OutgoingMail, User
from pretix.base.services.mail import mail, mail_send_task
@@ -73,45 +68,6 @@ def env():
yield event, user, o
@pytest.fixture
@scopes_disabled()
def item(env):
return env[0].items.create(name="Budget Ticket", default_price=23)
@pytest.fixture
@scopes_disabled()
def order(env, item):
event, _, _ = env
o = Order.objects.create(
code="FOO",
event=event,
email="dummy@dummy.test",
status=Order.STATUS_PENDING,
secret="k24fiuwvu8kxz3y1",
sales_channel=event.organizer.sales_channels.get(identifier="web"),
datetime=datetime.datetime(2017, 12, 1, 10, 0, 0, tzinfo=datetime.timezone.utc),
expires=datetime.datetime(2017, 12, 10, 10, 0, 0, tzinfo=datetime.timezone.utc),
total=23,
locale="en",
)
o.positions.create(
order=o,
item=item,
variation=None,
price=Decimal("23"),
attendee_email="peter@example.org",
attendee_name_parts={"given_name": "Peter", "family_name": "Miller"},
secret="z3fsn8jyufm5kpk768q69gkbyr5f4h6w",
pseudonymization_id="ABCDEFGHKL",
)
InvoiceAddress.objects.create(
order=o,
name_parts={"given_name": "Peter", "family_name": "Miller"},
)
return o
@pytest.mark.django_db
def test_send_mail_with_prefix(env):
djmail.outbox = []
@@ -323,7 +279,7 @@ def _extract_html(mail):
def test_placeholder_html_rendering_from_template(env):
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong> {currency}"
event.name = "<strong>event & co. kg</strong>"
event.save()
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
event=event,
@@ -332,26 +288,25 @@ def test_placeholder_html_rendering_from_template(env):
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
# Known bug for now: These should not have HTML for the plain body, but we'll fix this safter the security release
assert escape('Event name: <strong>event & co. kg</strong> {currency}') in djmail.outbox[0].body
assert '<strong>IBAN</strong>: 123<br>\n<strong>BIC</strong>: 456' in djmail.outbox[0].body
assert '**Meta**: <em>Beep</em>' in djmail.outbox[0].body
assert escape('Event website: [<strong>event & co. kg</strong> {currency}](https://example.org/dummy)') in djmail.outbox[0].body
# todo: assert '&lt;' not in djmail.outbox[0].body
# todo: assert '&amp;' not in djmail.outbox[0].body
assert 'Unevaluated placeholder: {currency}' in djmail.outbox[0].body
assert 'EUR' not in djmail.outbox[0].body
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
assert '**Meta**: *Beep*' in djmail.outbox[0].body
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
assert '&lt;' not in djmail.outbox[0].body
assert '&amp;' not in djmail.outbox[0].body
html = _extract_html(djmail.outbox[0])
assert '<strong>event' not in html
assert 'Event name: &lt;strong&gt;event &amp; co. kg&lt;/strong&gt; {currency}' in html
assert 'Event name: &lt;strong&gt;event &amp; co. kg&lt;/strong&gt;' in html
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
assert '<strong>Meta</strong>: <em>Beep</em>' in html
assert 'Unevaluated placeholder: {currency}' in html
assert 'EUR' not in html
assert re.search(
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">'
r'&lt;strong&gt;event &amp; co. kg&lt;/strong&gt; {currency}</a>',
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
assert re.search(
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
@@ -369,7 +324,7 @@ def test_placeholder_html_rendering_from_string(env):
})
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong> {currency}"
event.name = "<strong>event & co. kg</strong>"
event.save()
ctx = get_email_context(
event=event,
@@ -380,9 +335,9 @@ def test_placeholder_html_rendering_from_string(env):
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
assert 'Event name: <strong>event & co. kg</strong> {currency}' in djmail.outbox[0].body
assert 'Event website: [<strong>event & co. kg</strong> {currency}](https://example.org/dummy)' in djmail.outbox[0].body
assert 'Other website: [<strong>event & co. kg</strong> {currency}](https://example.com)' in djmail.outbox[0].body
assert 'Event name: <strong>event & co. kg</strong>' in djmail.outbox[0].body
assert 'Event website: [<strong>event & co. kg</strong>](https://example.org/dummy)' in djmail.outbox[0].body
assert 'Other website: [<strong>event & co. kg</strong>](https://example.com)' in djmail.outbox[0].body
assert '**IBAN**: 123 \n**BIC**: 456' in djmail.outbox[0].body
assert '**Meta**: *Beep*' in djmail.outbox[0].body
assert 'URL: https://google.com' in djmail.outbox[0].body
@@ -397,13 +352,11 @@ def test_placeholder_html_rendering_from_string(env):
assert '<strong>IBAN</strong>: 123<br/>\n<strong>BIC</strong>: 456' in html
assert '<strong>Meta</strong>: <em>Beep</em>' in html
assert re.search(
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">'
r'&lt;strong&gt;event &amp; co. kg&lt;/strong&gt; {currency}</a>',
r'Event website: <a href="https://example.org/dummy" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
assert re.search(
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">'
r'&lt;strong&gt;event &amp; co. kg&lt;/strong&gt; {currency}</a>',
r'Other website: <a href="https://example.com" rel="noopener" style="[^"]+" target="_blank">&lt;strong&gt;event &amp; co. kg&lt;/strong&gt;</a>',
html
)
assert re.search(
@@ -424,141 +377,3 @@ def test_placeholder_html_rendering_from_string(env):
r'style="[^"]+" target="_blank">Link &amp; Text</a>',
html
)
@pytest.mark.django_db
def test_nested_placeholder_inclusion_full_process(env, order):
# Test that it is not possible to sneak in a placeholder like {url_cancel} inside a user-controlled
# placeholder value like {invoice_company}
event, user, organizer = env
position = order.positions.get()
order.invoice_address.company = "{url_cancel} Corp"
order.invoice_address.save()
event.settings.mail_text_resend_link = LazyI18nString({"en": "Ticket for {invoice_company}"})
event.settings.mail_subject_resend_link_attendee = LazyI18nString({"en": "Ticket for {invoice_company}"})
djmail.outbox = []
position.resend_link()
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [position.attendee_email]
assert "Ticket for {url_cancel} Corp" == djmail.outbox[0].subject
assert "/cancel" not in djmail.outbox[0].body
assert "/order" not in djmail.outbox[0].body
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
for part in (html, plain):
assert "Ticket for {url_cancel} Corp" in part
assert "/order/" not in part
assert "/cancel" not in part
@pytest.mark.django_db
def test_nested_placeholder_inclusion_mail_service(env):
# test that it is not possible to have placeholders within the values of placeholders when
# the mail() function is called directly
template = LazyI18nString("Event name: {event}")
djmail.outbox = []
event, user, organizer = env
event.name = "event & {currency} co. kg"
event.slug = "event-co-ag-slug"
event.save()
mail(
"dummy@dummy.dummy",
"{event} Test subject",
template,
get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456 {event}",
),
event,
)
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
for part in (html, plain, djmail.outbox[0].subject):
assert "event & {currency} co. kg" in part or "event &amp; {currency} co. kg" in part
assert "EUR" not in part
@pytest.mark.django_db
@pytest.mark.parametrize("tpl", [
"Event: {event.__class__}",
"Event: {{event.__class__}}",
"Event: {{{event.__class__}}}",
])
def test_variable_inclusion_from_string_full_process(env, tpl, order):
# Test that it is not possible to use placeholders that leak system information in templates
# when run through system processes
event, user, organizer = env
event.name = "event & co. kg"
event.save()
position = order.positions.get()
event.settings.mail_text_resend_link = LazyI18nString({"en": tpl})
event.settings.mail_subject_resend_link_attendee = LazyI18nString({"en": tpl})
position.resend_link()
assert len(djmail.outbox) == 1
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
for part in (html, plain, djmail.outbox[0].subject):
assert "{event.__class__}" in part
assert "LazyI18nString" not in part
@pytest.mark.django_db
@pytest.mark.parametrize("tpl", [
"Event: {event.__class__}",
"Event: {{event.__class__}}",
"Event: {{{event.__class__}}}",
])
def test_variable_inclusion_from_string_mail_service(env, tpl):
# Test that it is not possible to use placeholders that leak system information in templates
# when run through mail() directly
event, user, organizer = env
event.name = "event & co. kg"
event.save()
djmail.outbox = []
mail(
"dummy@dummy.dummy",
tpl,
LazyI18nString(tpl),
get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456\n" + tpl,
),
event,
)
assert len(djmail.outbox) == 1
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
for part in (html, plain, djmail.outbox[0].subject):
assert "{event.__class__}" in part
assert "LazyI18nString" not in part
@pytest.mark.django_db
def test_escaped_braces_mail_services(env):
# Test that braces can be escaped by doubling
template = LazyI18nString("Event name: -{{currency}}-")
djmail.outbox = []
event, user, organizer = env
event.name = "event & co. kg"
event.save()
mail(
"dummy@dummy.dummy",
"-{{currency}}- Test subject",
template,
get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456 {event}",
),
event,
)
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
html, plain = _extract_html(djmail.outbox[0]), djmail.outbox[0].body
for part in (html, plain, djmail.outbox[0].subject):
assert "EUR" not in part
assert "-{currency}-" in part

View File

@@ -29,8 +29,6 @@ def test_format_map():
assert format_map("Foo {baz}", {"bar": 3}) == "Foo {baz}"
assert format_map("Foo {bar.__module__}", {"bar": 3}) == "Foo {bar.__module__}"
assert format_map("Foo {bar!s}", {"bar": 3}) == "Foo 3"
assert format_map("Foo {bar!r}", {"bar": '3'}) == "Foo 3"
assert format_map("Foo {bar!a}", {"bar": '3'}) == "Foo 3"
assert format_map("Foo {bar:<20}", {"bar": 3}) == "Foo 3"

View File

@@ -1,13 +1,13 @@
{% load i18n %}
This is a test file for sending mails.
Event name: {{ event }}
Unevaluated placeholder: {currency}
Event name: {event}
{% get_current_language as LANGUAGE_CODE %}
The language code used for rendering this email is {{ LANGUAGE_CODE }}.
Payment info:
{{ payment_info }}
{payment_info}
**Meta**: {{ meta_Test }}
**Meta**: {meta_Test}
Event website: [{{event}}](https://example.org/{{event_slug}})
Event website: [{event}](https://example.org/{event_slug})
Other website: [{event}]({meta_Website})