Compare commits

..

2 Commits

Author SHA1 Message Date
Raphael Michel
9693625758 Fix failing tests 2025-08-22 09:23:07 +02:00
Raphael Michel
7ecc64ec73 Bump version to 2025.8.0.dev0 2025-08-20 13:04:14 +02:00
17 changed files with 91 additions and 256 deletions

View File

@@ -42,7 +42,7 @@ dependencies = [
"django-filter==25.1", "django-filter==25.1",
"django-formset-js-improved==0.5.0.3", "django-formset-js-improved==0.5.0.3",
"django-formtools==2.5.1", "django-formtools==2.5.1",
"django-hierarkey==2.0.*,>=2.0.1", "django-hierarkey==2.0.*",
"django-hijack==3.7.*", "django-hijack==3.7.*",
"django-i18nfield==1.10.*", "django-i18nfield==1.10.*",
"django-libsass==0.9", "django-libsass==0.9",

View File

@@ -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__ = "2025.7.2" __version__ = "2025.8.0.dev0"

View File

@@ -133,11 +133,11 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
def template_name(self): def template_name(self):
raise NotImplementedError() raise NotImplementedError()
def compile_markdown(self, plaintext, context=None): def compile_markdown(self, plaintext):
return markdown_compile_email(plaintext, context=context) return markdown_compile_email(plaintext)
def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str: def render(self, plain_body: str, plain_signature: str, subject: str, order, position, context) -> str:
body_md = self.compile_markdown(plain_body, context) body_md = self.compile_markdown(plain_body)
if context: if context:
body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML) body_md = format_map(body_md, context=context, mode=SafeFormatter.MODE_RICH_TO_HTML)
htmlctx = { htmlctx = {

View File

@@ -222,7 +222,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
'invoice_company': '' 'invoice_company': ''
}) })
renderer = ClassicMailRenderer(None, organizer) renderer = ClassicMailRenderer(None, organizer)
body_plain = render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN) content_plain = body_plain = render_mail(template, context)
subject = str(subject).format_map(TolerantDict(context)) subject = str(subject).format_map(TolerantDict(context))
sender = ( sender = (
sender or sender or
@@ -316,7 +316,6 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
with override(timezone): with override(timezone):
try: try:
content_plain = render_mail(template, context, placeholder_mode=None)
if plain_text_only: if plain_text_only:
body_html = None body_html = None
elif 'context' in inspect.signature(renderer.render).parameters: elif 'context' in inspect.signature(renderer.render).parameters:
@@ -714,11 +713,11 @@ def mail_send(*args, **kwargs):
mail_send_task.apply_async(args=args, kwargs=kwargs) mail_send_task.apply_async(args=args, kwargs=kwargs)
def render_mail(template, context, placeholder_mode=SafeFormatter.MODE_RICH_TO_PLAIN): def render_mail(template, context):
if isinstance(template, LazyI18nString): if isinstance(template, LazyI18nString):
body = str(template) body = str(template)
if context and placeholder_mode: if context:
body = format_map(body, context, mode=placeholder_mode) body = format_map(body, context, mode=SafeFormatter.MODE_IGNORE_RICH)
else: else:
tpl = get_template(template) tpl = get_template(template)
body = tpl.render(context) body = tpl.render(context)

View File

@@ -26,7 +26,7 @@ from decimal import Decimal
from django.dispatch import receiver from django.dispatch import receiver
from django.utils.formats import date_format from django.utils.formats import date_format
from django.utils.html import escape, mark_safe from django.utils.html import escape
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@@ -123,10 +123,6 @@ class BaseRichTextPlaceholder(BaseTextPlaceholder):
def identifier(self): def identifier(self):
return self._identifier return self._identifier
@property
def allowed_in_plain_content(self):
return False
@property @property
def required_context(self): def required_context(self):
return self._args return self._args
@@ -198,33 +194,6 @@ class SimpleButtonPlaceholder(BaseRichTextPlaceholder):
return f'{text}: {url}' return f'{text}: {url}'
class MarkdownTextPlaceholder(BaseRichTextPlaceholder):
def __init__(self, identifier, args, func, sample, inline):
super().__init__(identifier, args)
self._func = func
self._sample = sample
self._snippet = inline
@property
def allowed_in_plain_content(self):
return self._snippet
def render_plain(self, **context):
return self._func(**{k: context[k] for k in self._args})
def render_html(self, **context):
return mark_safe(markdown_compile_email(self.render_plain(**context), snippet=self._snippet))
def render_sample_plain(self, event):
if callable(self._sample):
return self._sample(event)
else:
return self._sample
def render_sample_html(self, event):
return mark_safe(markdown_compile_email(self.render_sample_plain(event), snippet=self._snippet))
class PlaceholderContext(SafeFormatter): class PlaceholderContext(SafeFormatter):
""" """
Holds the contextual arguments and corresponding list of available placeholders for formatting Holds the contextual arguments and corresponding list of available placeholders for formatting
@@ -605,7 +574,7 @@ def base_placeholders(sender, **kwargs):
'invoice_company', ['invoice_address'], lambda invoice_address: invoice_address.company or '', 'invoice_company', ['invoice_address'], lambda invoice_address: invoice_address.company or '',
_('Sample Corporation') _('Sample Corporation')
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join( 'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
'* {} - {}'.format( '* {} - {}'.format(
order.full_code, order.full_code,
@@ -635,7 +604,6 @@ def base_placeholders(sender, **kwargs):
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd', 'hash': 'stuvwxy2z'} {'code': 'OPKSB', 'secret': '09pjdksflosk3njd', 'hash': 'stuvwxy2z'}
] ]
), ),
inline=False,
), ),
SimpleFunctionalTextPlaceholder( SimpleFunctionalTextPlaceholder(
'hours', ['event', 'waiting_list_entry'], lambda event, waiting_list_entry: 'hours', ['event', 'waiting_list_entry'], lambda event, waiting_list_entry:
@@ -650,13 +618,12 @@ def base_placeholders(sender, **kwargs):
'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code, 'code', ['waiting_list_voucher'], lambda waiting_list_voucher: waiting_list_voucher.code,
'68CYU2H6ZTP3WLK5' '68CYU2H6ZTP3WLK5'
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br> # join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list), 'voucher_list', ['voucher_list'], lambda voucher_list: ' \n'.join(voucher_list),
'68CYU2H6ZTP3WLK5 \n7MB94KKPVEPSMVF2', ' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
inline=False,
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br> # join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_url_list', ['event', 'voucher_list'], 'voucher_url_list', ['event', 'voucher_list'],
lambda event, voucher_list: ' \n'.join([ lambda event, voucher_list: ' \n'.join([
@@ -671,7 +638,6 @@ def base_placeholders(sender, **kwargs):
) + '?voucher=' + c ) + '?voucher=' + c
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2'] for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
]), ]),
inline=False,
), ),
SimpleFunctionalTextPlaceholder( SimpleFunctionalTextPlaceholder(
'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={ 'url', ['event', 'voucher_list'], lambda event, voucher_list: build_absolute_uri(event, 'presale:event.index', kwargs={
@@ -690,13 +656,13 @@ def base_placeholders(sender, **kwargs):
'comment', ['comment'], lambda comment: comment, 'comment', ['comment'], lambda comment: comment,
_('An individual text with a reason can be inserted here.'), _('An individual text with a reason can be inserted here.'),
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
'payment_info', ['order', 'payments'], _placeholder_payments, 'payment_info', ['order', 'payments'], _placeholder_payments,
_('The amount has been charged to your card.'), inline=False, _('The amount has been charged to your card.'),
), ),
MarkdownTextPlaceholder( SimpleFunctionalTextPlaceholder(
'payment_info', ['payment_info'], lambda payment_info: payment_info, 'payment_info', ['payment_info'], lambda payment_info: payment_info,
_('Please transfer money to this bank account: 9999-9999-9999-9999'), inline=False, _('Please transfer money to this bank account: 9999-9999-9999-9999'),
), ),
SimpleFunctionalTextPlaceholder( SimpleFunctionalTextPlaceholder(
'attendee_name', ['position'], lambda position: position.attendee_name, 'attendee_name', ['position'], lambda position: position.attendee_name,
@@ -753,13 +719,13 @@ def base_placeholders(sender, **kwargs):
)) ))
for k, v in sender.meta_data.items(): for k, v in sender.meta_data.items():
ph.append(MarkdownTextPlaceholder( ph.append(SimpleFunctionalTextPlaceholder(
'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k], 'meta_%s' % k, ['event'], lambda event, k=k: event.meta_data[k],
v, inline=True, v
)) ))
ph.append(MarkdownTextPlaceholder( ph.append(SimpleFunctionalTextPlaceholder(
'meta_%s' % k, ['event_or_subevent'], lambda event_or_subevent, k=k: event_or_subevent.meta_data[k], 'meta_%s' % k, ['event_or_subevent'], lambda event_or_subevent, k=k: event_or_subevent.meta_data[k],
v, inline=True, v
)) ))
return ph return ph
@@ -787,7 +753,7 @@ def get_available_placeholders(event, base_parameters, rich=False):
if not isinstance(val, (list, tuple)): if not isinstance(val, (list, tuple)):
val = [val] val = [val]
for v in val: for v in val:
if isinstance(v, BaseRichTextPlaceholder) and not rich and not v.allowed_in_plain_content: if isinstance(v, BaseRichTextPlaceholder) and not rich:
continue continue
if all(rp in base_parameters for rp in v.required_context): if all(rp in base_parameters for rp in v.required_context):
params[v.identifier] = v params[v.identifier] = v
@@ -809,13 +775,13 @@ def get_sample_context(event, context_parameters, rich=True):
) )
) )
elif str(sample).strip().startswith('* ') or str(sample).startswith(' '): elif str(sample).strip().startswith('* ') or str(sample).startswith(' '):
context_dict[k] = mark_safe('<div class="placeholder" title="{}">{}</div>'.format( context_dict[k] = '<div class="placeholder" title="{}">{}</div>'.format(
lbl, lbl,
markdown_compile_email(str(sample)) markdown_compile_email(str(sample))
)) )
else: else:
context_dict[k] = mark_safe('<span class="placeholder" title="{}">{}</span>'.format( context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
lbl, lbl,
escape(sample) escape(sample)
)) )
return context_dict return context_dict

View File

@@ -44,7 +44,6 @@ from django.conf import settings
from django.core import signing from django.core import signing
from django.urls import reverse from django.urls import reverse
from django.utils.functional import SimpleLazyObject from django.utils.functional import SimpleLazyObject
from django.utils.html import escape
from django.utils.http import url_has_allowed_host_and_scheme from django.utils.http import url_has_allowed_host_and_scheme
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from markdown import Extension from markdown import Extension
@@ -53,8 +52,6 @@ from markdown.postprocessors import Postprocessor
from markdown.treeprocessors import UnescapeTreeprocessor from markdown.treeprocessors import UnescapeTreeprocessor
from tlds import tld_set from tlds import tld_set
from pretix.helpers.format import SafeFormatter, format_map
register = template.Library() register = template.Library()
ALLOWED_TAGS_SNIPPET = { ALLOWED_TAGS_SNIPPET = {
@@ -297,28 +294,16 @@ class LinkifyAndCleanExtension(Extension):
) )
def markdown_compile_email(source, allowed_tags=None, allowed_attributes=ALLOWED_ATTRIBUTES, snippet=False, context=None): def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes=ALLOWED_ATTRIBUTES):
if allowed_tags is None:
allowed_tags = ALLOWED_TAGS_SNIPPET if snippet else ALLOWED_TAGS
context_callbacks = []
if context:
# This is a workaround to fix placeholders in URL targets
def context_callback(attrs, new=False):
if (None, "href") in attrs and "{" in attrs[None, "href"]:
# Do not use MODE_RICH_TO_HTML to avoid recursive linkification
attrs[None, "href"] = escape(format_map(attrs[None, "href"], context=context, mode=SafeFormatter.MODE_RICH_TO_PLAIN))
return attrs
context_callbacks.append(context_callback)
linker = bleach.Linker( linker = bleach.Linker(
url_re=URL_RE, url_re=URL_RE,
email_re=EMAIL_RE, email_re=EMAIL_RE,
callbacks=context_callbacks + DEFAULT_CALLBACKS + [truelink_callback, abslink_callback], callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True parse_email=True
) )
exts = [ return markdown.markdown(
source,
extensions=[
'markdown.extensions.sane_lists', 'markdown.extensions.sane_lists',
'markdown.extensions.tables', 'markdown.extensions.tables',
EmailNl2BrExtension(), EmailNl2BrExtension(),
@@ -327,14 +312,9 @@ def markdown_compile_email(source, allowed_tags=None, allowed_attributes=ALLOWED
tags=set(allowed_tags), tags=set(allowed_tags),
attributes=allowed_attributes, attributes=allowed_attributes,
protocols=ALLOWED_PROTOCOLS, protocols=ALLOWED_PROTOCOLS,
strip=snippet, strip=False,
) )
] ]
if snippet:
exts.append(SnippetExtension())
return markdown.markdown(
source,
extensions=exts
) )

View File

@@ -308,8 +308,8 @@ class VoucherBulkForm(VoucherForm):
) )
Recipient = namedtuple('Recipient', 'email number name tag') Recipient = namedtuple('Recipient', 'email number name tag')
def _set_field_placeholders(self, fn, base_parameters, rich=False): def _set_field_placeholders(self, fn, base_parameters):
placeholders = get_available_placeholders(self.instance.event, base_parameters, rich=rich) placeholders = get_available_placeholders(self.instance.event, base_parameters)
ht = format_placeholders_help_text(placeholders, self.instance.event) ht = format_placeholders_help_text(placeholders, self.instance.event)
if self.fields[fn].help_text: if self.fields[fn].help_text:
@@ -345,7 +345,7 @@ class VoucherBulkForm(VoucherForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._set_field_placeholders('send_subject', ['event', 'name']) self._set_field_placeholders('send_subject', ['event', 'name'])
self._set_field_placeholders('send_message', ['event', 'voucher_list', 'name'], rich=True) self._set_field_placeholders('send_message', ['event', 'voucher_list', 'name'])
with language(self.instance.event.settings.locale, self.instance.event.settings.region): with language(self.instance.event.settings.locale, self.instance.event.settings.region):
for f in ("send_subject", "send_message"): for f in ("send_subject", "send_message"):

View File

@@ -69,13 +69,12 @@
{% if show_meta %} {% if show_meta %}
<span class="text-muted text-sm">{{ plugin.version }}</span> <span class="text-muted text-sm">{{ plugin.version }}</span>
{% endif %} {% endif %}
{% if is_active or plugin.level == "event" %} {% if is_active and level == "organizer" %}
{% if plugin.level == "organizer" %}
<span class="label label-success" data-is-active> <span class="label label-success" data-is-active>
<span class="fa fa-check" aria-hidden="true"></span> <span class="fa fa-check" aria-hidden="true"></span>
{% trans "Active" %} {% trans "Active" %}
</span> </span>
{% elif events_total and events_counter == events_total %} {% elif events_counter == events_total %}
<span class="label label-success" data-is-active> <span class="label label-success" data-is-active>
<span class="fa fa-check" aria-hidden="true"></span> <span class="fa fa-check" aria-hidden="true"></span>
{% trans "Active (all events)" %} {% trans "Active (all events)" %}
@@ -99,7 +98,6 @@
{% endblocktrans %} {% endblocktrans %}
</span> </span>
{% endif %} {% endif %}
{% endif %}
</h4> </h4>
{% include "pretixcontrol/event/fragment_plugin_description.html" with plugin=plugin %} {% include "pretixcontrol/event/fragment_plugin_description.html" with plugin=plugin %}
</div> </div>

View File

@@ -22,8 +22,6 @@
import logging import logging
from string import Formatter from string import Formatter
from django.utils.html import conditional_escape
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -42,10 +40,11 @@ class SafeFormatter(Formatter):
Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and Customized version of ``str.format`` that (a) behaves just like ``str.format_map`` and
(b) does not allow any unwanted shenanigans like attribute access or format specifiers. (b) does not allow any unwanted shenanigans like attribute access or format specifiers.
""" """
MODE_IGNORE_RICH = 0
MODE_RICH_TO_PLAIN = 1 MODE_RICH_TO_PLAIN = 1
MODE_RICH_TO_HTML = 2 MODE_RICH_TO_HTML = 2
def __init__(self, context, raise_on_missing=False, mode=MODE_RICH_TO_PLAIN): def __init__(self, context, raise_on_missing=False, mode=MODE_IGNORE_RICH):
self.context = context self.context = context
self.raise_on_missing = raise_on_missing self.raise_on_missing = raise_on_missing
self.mode = mode self.mode = mode
@@ -56,26 +55,22 @@ class SafeFormatter(Formatter):
def get_value(self, key, args, kwargs): def get_value(self, key, args, kwargs):
if not self.raise_on_missing and key not in self.context: if not self.raise_on_missing and key not in self.context:
return '{' + str(key) + '}' return '{' + str(key) + '}'
return self.context[key] r = self.context[key]
if isinstance(r, PlainHtmlAlternativeString):
def _prepare_value(self, value): if self.mode == self.MODE_IGNORE_RICH:
if isinstance(value, PlainHtmlAlternativeString): return '{' + str(key) + '}'
if self.mode == self.MODE_RICH_TO_PLAIN: elif self.mode == self.MODE_RICH_TO_PLAIN:
return value.plain return r.plain
elif self.mode == self.MODE_RICH_TO_HTML: elif self.mode == self.MODE_RICH_TO_HTML:
return value.html return r.html
else: return r
value = str(value)
if self.mode == self.MODE_RICH_TO_HTML:
value = conditional_escape(value)
return value
def format_field(self, value, format_spec): def format_field(self, value, format_spec):
# Ignore format_spec # Ignore format_spec
return super().format_field(self._prepare_value(value), '') return super().format_field(value, '')
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_RICH_TO_PLAIN): def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_IGNORE_RICH):
if not isinstance(template, str): if not isinstance(template, str):
template = str(template) template = str(template)
return SafeFormatter(context, raise_on_missing, mode=mode).format(template) return SafeFormatter(context, raise_on_missing, mode=mode).format(template)

View File

@@ -33,8 +33,6 @@
# License for the specific language governing permissions and limitations under the License. # License for the specific language governing permissions and limitations under the License.
import os import os
import re
from email.mime.text import MIMEText
import pytest import pytest
from django.conf import settings from django.conf import settings
@@ -42,9 +40,7 @@ from django.core import mail as djmail
from django.utils.timezone import now from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_scopes import scope from django_scopes import scope
from i18nfield.strings import LazyI18nString
from pretix.base.email import get_email_context
from pretix.base.models import Event, Organizer, User from pretix.base.models import Event, Organizer, User
from pretix.base.services.mail import mail from pretix.base.services.mail import mail
@@ -52,14 +48,10 @@ from pretix.base.services.mail import mail
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy')
prop1 = o.meta_properties.get_or_create(name="Test")[0]
prop2 = o.meta_properties.get_or_create(name="Website")[0]
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now() date_from=now()
) )
event.meta_values.update_or_create(property=prop1, defaults={'value': "*Beep*"})
event.meta_values.update_or_create(property=prop2, defaults={'value': "https://example.com"})
user = User.objects.create_user('dummy@dummy.dummy', 'dummy') user = User.objects.create_user('dummy@dummy.dummy', 'dummy')
user.email = 'dummy@dummy.dummy' user.email = 'dummy@dummy.dummy'
user.save() user.save()
@@ -166,95 +158,8 @@ def test_send_mail_with_user_locale(env):
def test_sendmail_placeholder(env): def test_sendmail_placeholder(env):
djmail.outbox = [] djmail.outbox = []
event, user, organizer = env event, user, organizer = env
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', {"event": event.name}, event) mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', {"event": event}, event)
assert len(djmail.outbox) == 1 assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email] assert djmail.outbox[0].to == [user.email]
assert djmail.outbox[0].subject == 'Dummy Test subject' assert djmail.outbox[0].subject == 'Dummy Test subject'
def _extract_html(mail):
for content, mimetype in mail.alternatives:
if "multipart/related" in mimetype:
for sp in content._payload:
if isinstance(sp, MIMEText):
return sp._payload
break
elif "text/html" in mimetype:
return content
@pytest.mark.django_db
def test_placeholder_html_rendering_from_template(env):
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong>"
event.save()
mail('dummy@dummy.dummy', '{event} Test subject', 'mailtest.txt', get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456",
), event)
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
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;' 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 re.search(
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
)
@pytest.mark.django_db
def test_placeholder_html_rendering_from_string(env):
template = LazyI18nString({
"en": "Event name: {event}\n\nPayment info:\n{payment_info}\n\n**Meta**: {meta_Test}\n\n"
"Event website: [{event}](https://example.org/{event_slug})\n\n"
"Other website: [{event}]({meta_Website})"
})
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong>"
event.save()
mail('dummy@dummy.dummy', '{event} Test subject', template, get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456",
), event)
assert len(djmail.outbox) == 1
assert djmail.outbox[0].to == [user.email]
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 '&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;' 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 re.search(
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
)

View File

@@ -40,5 +40,6 @@ def test_format_alternatives():
) )
} }
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_IGNORE_RICH) == "Foo {bar}"
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_PLAIN) == "Foo plain text" assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_PLAIN) == "Foo plain text"
assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_HTML) == "Foo <span>HTML version</span>" assert format_map("Foo {bar}", ctx, mode=SafeFormatter.MODE_RICH_TO_HTML) == "Foo <span>HTML version</span>"

View File

@@ -35,7 +35,7 @@ from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'

View File

@@ -36,7 +36,7 @@ from pretix.plugins.banktransfer.models import BankImportJob, BankTransaction
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer' date_from=now(), plugins='pretix.plugins.banktransfer'

View File

@@ -53,7 +53,7 @@ from pretix.plugins.banktransfer.tasks import process_banktransfers
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal' date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'

View File

@@ -34,7 +34,7 @@ from pretix.base.models import (
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal' date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'

View File

@@ -35,7 +35,7 @@ from pretix.plugins.banktransfer.views import (
@pytest.fixture @pytest.fixture
def env(): def env():
o = Organizer.objects.create(name='Dummy', slug='dummy') o = Organizer.objects.create(name='Dummy', slug='dummy', plugins='pretix.plugins.banktransfer')
event = Event.objects.create( event = Event.objects.create(
organizer=o, name='Dummy', slug='dummy', organizer=o, name='Dummy', slug='dummy',
date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal' date_from=now(), plugins='pretix.plugins.banktransfer,pretix.plugins.paypal'

View File

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