Compare commits

..

1 Commits

Author SHA1 Message Date
Phin Wolkwitz
c5200da5a6 Hide info from invisible fields in confirmation step 2025-11-20 14:56:08 +01:00
13 changed files with 109 additions and 310 deletions

View File

@@ -24,7 +24,6 @@ from itertools import groupby
from smtplib import SMTPResponseException
from typing import TypeVar
import bleach
import css_inline
from django.conf import settings
from django.core.mail.backends.smtp import EmailBackend
@@ -35,10 +34,7 @@ from django.utils.translation import get_language, gettext_lazy as _
from pretix.base.models import Event
from pretix.base.signals import register_html_mail_renderers
from pretix.base.templatetags.rich_text import (
DEFAULT_CALLBACKS, EMAIL_RE, URL_RE, abslink_callback,
markdown_compile_email, truelink_callback,
)
from pretix.base.templatetags.rich_text import markdown_compile_email
from pretix.helpers.format import SafeFormatter, format_map
from pretix.base.services.placeholders import ( # noqa
@@ -137,24 +133,13 @@ class TemplateBasedMailRenderer(BaseHTMLMailRenderer):
def template_name(self):
raise NotImplementedError()
def compile_markdown(self, plaintext, context=None):
return markdown_compile_email(plaintext, context=context)
def compile_markdown(self, plaintext):
return markdown_compile_email(plaintext)
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:
linker = bleach.Linker(
url_re=URL_RE,
email_re=EMAIL_RE,
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
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)
htmlctx = {
'site': settings.PRETIX_INSTANCE_NAME,
'site_url': settings.SITE_URL,

View File

@@ -89,6 +89,8 @@ class User2FADeviceAddForm(forms.Form):
class UserPasswordChangeForm(forms.Form):
error_messages = {
'pw_current': _("Please enter your current password if you want to change your email address "
"or password."),
'pw_current_wrong': _("The current password you entered was not correct."),
'pw_mismatch': _("Please enter the same password twice"),
'rate_limit': _("For security reasons, please wait 5 minutes before you try again."),
@@ -101,19 +103,19 @@ class UserPasswordChangeForm(forms.Form):
attrs={'autocomplete': 'username'},
))
old_pw = forms.CharField(max_length=255,
required=True,
required=False,
label=_("Your current password"),
widget=forms.PasswordInput(
attrs={'autocomplete': 'current-password'},
))
new_pw = forms.CharField(max_length=255,
required=True,
required=False,
label=_("New password"),
widget=forms.PasswordInput(
attrs={'autocomplete': 'new-password'},
))
new_pw_repeat = forms.CharField(max_length=255,
required=True,
required=False,
label=_("Repeat new password"),
widget=forms.PasswordInput(
attrs={'autocomplete': 'new-password'},
@@ -128,7 +130,7 @@ class UserPasswordChangeForm(forms.Form):
def clean_old_pw(self):
old_pw = self.cleaned_data.get('old_pw')
if settings.HAS_REDIS:
if old_pw and settings.HAS_REDIS:
from django_redis import get_redis_connection
rc = get_redis_connection("redis")
cnt = rc.incr('pretix_pwchange_%s' % self.user.pk)
@@ -139,7 +141,7 @@ class UserPasswordChangeForm(forms.Form):
code='rate_limit',
)
if not check_password(old_pw, self.user.password):
if old_pw and not check_password(old_pw, self.user.password):
raise forms.ValidationError(
self.error_messages['pw_current_wrong'],
code='pw_current_wrong',
@@ -149,22 +151,17 @@ class UserPasswordChangeForm(forms.Form):
def clean_new_pw(self):
password1 = self.cleaned_data.get('new_pw', '')
if validate_password(password1, user=self.user) is not None:
if password1 and validate_password(password1, user=self.user) is not None:
raise forms.ValidationError(
_(password_validators_help_texts()),
code='pw_invalid'
)
if self.user.check_password(password1):
raise forms.ValidationError(
self.error_messages['pw_equal'],
code='pw_equal',
)
return password1
def clean_new_pw_repeat(self):
password1 = self.cleaned_data.get('new_pw')
password2 = self.cleaned_data.get('new_pw_repeat')
if password1 != password2:
if password1 and password1 != password2:
raise forms.ValidationError(
self.error_messages['pw_mismatch'],
code='pw_mismatch'

View File

@@ -222,7 +222,7 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
'invoice_company': ''
})
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))
sender = (
sender or
@@ -316,7 +316,6 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
with override(timezone):
try:
content_plain = render_mail(template, context, placeholder_mode=None)
if plain_text_only:
body_html = None
elif 'context' in inspect.signature(renderer.render).parameters:
@@ -752,11 +751,11 @@ def mail_send(*args, **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):
body = str(template)
if context and placeholder_mode:
body = format_map(body, context, mode=placeholder_mode)
if context:
body = format_map(body, context, mode=SafeFormatter.MODE_IGNORE_RICH)
else:
tpl = get_template(template)
body = tpl.render(context)

View File

@@ -26,7 +26,7 @@ from decimal import Decimal
from django.dispatch import receiver
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.translation import gettext_lazy as _
@@ -123,10 +123,6 @@ class BaseRichTextPlaceholder(BaseTextPlaceholder):
def identifier(self):
return self._identifier
@property
def allowed_in_plain_content(self):
return False
@property
def required_context(self):
return self._args
@@ -198,33 +194,6 @@ class SimpleButtonPlaceholder(BaseRichTextPlaceholder):
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):
"""
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 '',
_('Sample Corporation')
),
MarkdownTextPlaceholder(
SimpleFunctionalTextPlaceholder(
'orders', ['event', 'orders'], lambda event, orders: '\n' + '\n\n'.join(
'* {} - {}'.format(
order.full_code,
@@ -635,7 +604,6 @@ def base_placeholders(sender, **kwargs):
{'code': 'OPKSB', 'secret': '09pjdksflosk3njd', 'hash': 'stuvwxy2z'}
]
),
inline=False,
),
SimpleFunctionalTextPlaceholder(
'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,
'68CYU2H6ZTP3WLK5'
),
MarkdownTextPlaceholder(
SimpleFunctionalTextPlaceholder(
# 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),
'68CYU2H6ZTP3WLK5 \n7MB94KKPVEPSMVF2',
inline=False,
' 68CYU2H6ZTP3WLK5\n 7MB94KKPVEPSMVF2'
),
MarkdownTextPlaceholder(
SimpleFunctionalTextPlaceholder(
# join vouchers with two spaces at end of line so markdown-parser inserts a <br>
'voucher_url_list', ['event', 'voucher_list'],
lambda event, voucher_list: ' \n'.join([
@@ -671,7 +638,6 @@ def base_placeholders(sender, **kwargs):
) + '?voucher=' + c
for c in ['68CYU2H6ZTP3WLK5', '7MB94KKPVEPSMVF2']
]),
inline=False,
),
SimpleFunctionalTextPlaceholder(
'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,
_('An individual text with a reason can be inserted here.'),
),
MarkdownTextPlaceholder(
SimpleFunctionalTextPlaceholder(
'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,
_('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(
'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():
ph.append(MarkdownTextPlaceholder(
ph.append(SimpleFunctionalTextPlaceholder(
'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],
v, inline=True,
v
))
return ph
@@ -787,7 +753,7 @@ def get_available_placeholders(event, base_parameters, rich=False):
if not isinstance(val, (list, tuple)):
val = [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
if all(rp in base_parameters for rp in v.required_context):
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(' '):
context_dict[k] = mark_safe('<div class="placeholder" title="{}">{}</div>'.format(
context_dict[k] = '<div class="placeholder" title="{}">{}</div>'.format(
lbl,
markdown_compile_email(str(sample))
))
)
else:
context_dict[k] = mark_safe('<span class="placeholder" title="{}">{}</span>'.format(
context_dict[k] = '<span class="placeholder" title="{}">{}</span>'.format(
lbl,
escape(sample)
))
)
return context_dict

View File

@@ -44,7 +44,6 @@ from django.conf import settings
from django.core import signing
from django.urls import reverse
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.safestring import mark_safe
from markdown import Extension
@@ -53,8 +52,6 @@ from markdown.postprocessors import Postprocessor
from markdown.treeprocessors import UnescapeTreeprocessor
from tlds import tld_set
from pretix.helpers.format import SafeFormatter, format_map
register = template.Library()
@@ -324,44 +321,27 @@ class LinkifyAndCleanExtension(Extension):
)
def markdown_compile_email(source, allowed_tags=None, allowed_attributes=ALLOWED_ATTRIBUTES, snippet=False, context=None):
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)
def markdown_compile_email(source, allowed_tags=ALLOWED_TAGS, allowed_attributes=ALLOWED_ATTRIBUTES):
linker = bleach.Linker(
url_re=URL_RE,
email_re=EMAIL_RE,
callbacks=context_callbacks + DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
callbacks=DEFAULT_CALLBACKS + [truelink_callback, abslink_callback],
parse_email=True
)
exts = [
'markdown.extensions.sane_lists',
'markdown.extensions.tables',
EmailNl2BrExtension(),
LinkifyAndCleanExtension(
linker,
tags=set(allowed_tags),
attributes=allowed_attributes,
protocols=ALLOWED_PROTOCOLS,
strip=snippet,
)
]
if snippet:
exts.append(SnippetExtension())
return markdown.markdown(
source,
extensions=exts
extensions=[
'markdown.extensions.sane_lists',
'markdown.extensions.tables',
EmailNl2BrExtension(),
LinkifyAndCleanExtension(
linker,
tags=set(allowed_tags),
attributes=allowed_attributes,
protocols=ALLOWED_PROTOCOLS,
strip=False,
)
]
)

View File

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

View File

@@ -22,8 +22,6 @@
import logging
from string import Formatter
from django.utils.html import conditional_escape
logger = logging.getLogger(__name__)
@@ -42,14 +40,14 @@ class SafeFormatter(Formatter):
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.
"""
MODE_IGNORE_RICH = 0
MODE_RICH_TO_PLAIN = 1
MODE_RICH_TO_HTML = 2
def __init__(self, context, raise_on_missing=False, mode=MODE_RICH_TO_PLAIN, linkifier=None):
def __init__(self, context, raise_on_missing=False, mode=MODE_IGNORE_RICH):
self.context = context
self.raise_on_missing = raise_on_missing
self.mode = mode
self.linkifier = linkifier
def get_field(self, field_name, args, kwargs):
return self.get_value(field_name, args, kwargs), field_name
@@ -57,28 +55,22 @@ class SafeFormatter(Formatter):
def get_value(self, key, args, kwargs):
if not self.raise_on_missing and key not in self.context:
return '{' + str(key) + '}'
return self.context[key]
def _prepare_value(self, value):
if isinstance(value, PlainHtmlAlternativeString):
if self.mode == self.MODE_RICH_TO_PLAIN:
return value.plain
r = self.context[key]
if isinstance(r, PlainHtmlAlternativeString):
if self.mode == self.MODE_IGNORE_RICH:
return '{' + str(key) + '}'
elif self.mode == self.MODE_RICH_TO_PLAIN:
return r.plain
elif self.mode == self.MODE_RICH_TO_HTML:
return value.html
else:
value = str(value)
if self.mode == self.MODE_RICH_TO_HTML:
value = conditional_escape(value)
if self.linkifier:
value = self.linkifier.linkify(value)
return value
return r.html
return r
def format_field(self, value, 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, linkifier=None):
def format_map(template, context, raise_on_missing=False, mode=SafeFormatter.MODE_IGNORE_RICH):
if not isinstance(template, str):
template = str(template)
return SafeFormatter(context, raise_on_missing, mode=mode, linkifier=linkifier).format(template)
return SafeFormatter(context, raise_on_missing, mode=mode).format(template)

View File

@@ -56,6 +56,7 @@ from django.utils.translation import (
from django.views.generic.base import TemplateResponseMixin
from django_scopes import scopes_disabled
from pretix.base.invoicing.transmission import get_transmission_types
from pretix.base.models import Customer, Membership, Order
from pretix.base.models.items import Question
from pretix.base.models.orders import (
@@ -1545,6 +1546,18 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
ctx['invoice_address_asked'] = self.address_asked
ctx['customer'] = self.cart_customer
transmission_visible = False
for transmission_type in get_transmission_types():
if (
transmission_type.identifier == self.invoice_address.transmission_type and
transmission_type.invoice_address_form_fields_visible(
country=self.invoice_address.country, is_business=self.invoice_address.is_business
)
):
transmission_visible = True
break
ctx['show_transmission_type'] = transmission_visible
self.cart_session['shown_total'] = str(ctx['cart']['total'])
email = self.cart_session.get('contact_form_data', {}).get('email')

View File

@@ -111,10 +111,12 @@
<dt>{% trans "Internal reference" %}</dt>
<dd>{{ addr.internal_reference }}</dd>
{% endif %}
{% for k, v in addr.describe_transmission %}
<dt>{{ k }}</dt>
<dd>{{ v }}</dd>
{% endfor %}
{% if show_transmission_type %}
{% for k, v in addr.describe_transmission %}
<dt>{{ k }}</dt>
<dd>{{ v }}</dd>
{% endfor %}
{% endif %}
</dl>
</div>
</div>

View File

@@ -33,8 +33,6 @@
# License for the specific language governing permissions and limitations under the License.
import os
import re
from email.mime.text import MIMEText
import pytest
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.translation import gettext_lazy as _
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.services.mail import mail
@@ -52,14 +48,10 @@ from pretix.base.services.mail import mail
@pytest.fixture
def env():
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(
organizer=o, name='Dummy', slug='dummy',
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.email = 'dummy@dummy.dummy'
user.save()
@@ -166,109 +158,8 @@ def test_send_mail_with_user_locale(env):
def test_sendmail_placeholder(env):
djmail.outbox = []
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 djmail.outbox[0].to == [user.email]
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})\n\n"
"URL: {url}\n\n"
"URL with text: <a href=\"{url}\">Test</a>"
})
djmail.outbox = []
event, user, organizer = env
event.name = "<strong>event & co. kg</strong>"
event.save()
ctx = get_email_context(
event=event,
payment_info="**IBAN**: 123 \n**BIC**: 456",
)
ctx["url"] = "https://google.com"
mail('dummy@dummy.dummy', '{event} Test subject', template, ctx, 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 'URL: https://google.com' in djmail.outbox[0].body
assert 'URL with text: <a href="https://google.com">Test</a>' 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
)
assert re.search(
r'URL: <a href="https://google.com" rel="noopener" style="[^"]+" target="_blank">https://google.com</a>',
html
)
assert re.search(
r'URL with text: <a href="https://google.com" rel="noopener" style="[^"]+" target="_blank">Test</a>',
html
)

View File

@@ -42,8 +42,8 @@ from pretix.testutils.mock import mocker_context
class UserSettingsTest(SoupTest):
def setUp(self):
super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9')
self.client.login(email='dummy@dummy.dummy', password='old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9')
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
doc = self.get_doc('/control/settings')
self.form_data = extract_form_fields(doc.select('form[data-testid="usersettingsform"]')[0])
@@ -74,8 +74,8 @@ class UserSettingsTest(SoupTest):
class UserEmailChangeTest(SoupTest):
def setUp(self):
super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9')
self.client.login(email='dummy@dummy.dummy', password='old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9')
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
session = self.client.session
session['pretix_auth_login_time'] = int(time.time())
session.save()
@@ -92,7 +92,7 @@ class UserEmailChangeTest(SoupTest):
self.assertEqual(response.status_code, 302)
response = self.client.post('/control/reauth/?next=/control/settings/email/change', {
'password': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9'
'password': 'barfoofoo'
})
self.assertIn('/control/settings/email/change', response['Location'])
self.assertEqual(response.status_code, 302)
@@ -151,8 +151,8 @@ class UserEmailChangeTest(SoupTest):
class UserPasswordChangeTest(SoupTest):
def setUp(self):
super().setUp()
self.user = User.objects.create_user('dummy@dummy.dummy', 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9')
self.client.login(email='dummy@dummy.dummy', password='old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9')
self.user = User.objects.create_user('dummy@dummy.dummy', 'barfoofoo')
self.client.login(email='dummy@dummy.dummy', password='barfoofoo')
doc = self.get_doc('/control/settings/password/change')
self.form_data = extract_form_fields(doc.select('.container-fluid form')[0])
@@ -163,23 +163,10 @@ class UserPasswordChangeTest(SoupTest):
def test_change_password_require_password(self):
doc = self.save({
'new_pw': 'f00barbarbar',
'new_pw_repeat': 'f00barbarbar',
'new_pw': 'foo',
'new_pw_repeat': 'foo',
})
assert doc.select(".alert-danger")
assert "This field is required." in doc.select(".has-error")[0].text
pw = self.user.password
self.user = User.objects.get(pk=self.user.pk)
assert self.user.password == pw
def test_change_password_old_password_wrong(self):
doc = self.save({
'new_pw': 'f00barbarbar',
'new_pw_repeat': 'f00barbarbar',
'old_pw': 'lolwrong',
})
assert doc.select(".alert-danger")
assert "The current password you entered was not correct." in doc.select(".has-error")[0].text
pw = self.user.password
self.user = User.objects.get(pk=self.user.pk)
assert self.user.password == pw
@@ -190,7 +177,7 @@ class UserPasswordChangeTest(SoupTest):
self.save({
'new_pw': 'f00barbarbar',
'new_pw_repeat': 'f00barbarbar',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'barfoofoo',
})
pw = self.user.password
self.user = User.objects.get(pk=self.user.pk)
@@ -200,7 +187,7 @@ class UserPasswordChangeTest(SoupTest):
doc = self.save({
'new_pw': 'f00barbarbar',
'new_pw_repeat': 'f00barbarbar',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'barfoofoo',
})
assert doc.select(".alert-success")
self.user = User.objects.get(pk=self.user.pk)
@@ -210,10 +197,9 @@ class UserPasswordChangeTest(SoupTest):
doc = self.save({
'new_pw': 'foo',
'new_pw_repeat': 'foo',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'barfoofoo',
})
assert doc.select(".alert-danger")
assert "This password is too short." in doc.select(".has-error")[0].text
pw = self.user.password
self.user = User.objects.get(pk=self.user.pk)
assert self.user.password == pw
@@ -222,40 +208,37 @@ class UserPasswordChangeTest(SoupTest):
doc = self.save({
'new_pw': 'dummy123',
'new_pw_repeat': 'dummy123',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'barfoofoo',
})
assert doc.select(".alert-danger")
assert "The password is too similar to the Email." in doc.select(".has-error")[0].text
pw = self.user.password
self.user = User.objects.get(pk=self.user.pk)
assert self.user.password == pw
def test_change_password_require_repeat(self):
doc = self.save({
'new_pw': 'foooooooooooooo1234',
'new_pw_repeat': 'baaaaaaaaaaaar1234',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'new_pw': 'foooooooooooooo',
'new_pw_repeat': 'baaaaaaaaaaaar',
'old_pw': 'barfoofoo',
})
assert doc.select(".alert-danger")
assert "Please enter the same password twice" in doc.select(".has-error")[0].text
pw = self.user.password
self.user = User.objects.get(pk=self.user.pk)
assert self.user.password == pw
def test_change_password_require_new(self):
doc = self.save({
'new_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'new_pw_repeat': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'new_pw': 'barfoofoo',
'new_pw_repeat': 'barfoofoo',
'old_pw': 'barfoofoo',
})
assert doc.select(".has-error")
assert "Your password may not be the same as" in doc.select(".has-error")[0].text
assert doc.select(".alert-danger")
def test_change_password_history(self):
doc = self.save({
'new_pw': 'qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'new_pw_repeat': 'qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9',
'old_pw': 'barfoofoo',
})
assert doc.select(".alert-success")
@@ -272,7 +255,6 @@ class UserPasswordChangeTest(SoupTest):
'old_pw': '9UQl4lSwHLMVUIMgw0L1X8XEFmyvdn',
})
assert doc.select(".alert-danger")
assert "Your password may not be the same as one of your 4 previous passwords." in doc.select(".has-error")[0].text
def test_needs_password_change_changed(self):
self.user.needs_password_change = True
@@ -280,7 +262,7 @@ class UserPasswordChangeTest(SoupTest):
self.save({
'new_pw': 'f00barbarbar',
'new_pw_repeat': 'f00barbarbar',
'old_pw': 'old_qvuSpukdKWUV7m7PoRrWwpCd2Taij9'
'old_pw': 'barfoofoo'
})
self.user.refresh_from_db()
assert self.user.needs_password_change is False

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_HTML) == "Foo <span>HTML version</span>"

View File

@@ -1,13 +1,4 @@
{% load i18n %}
This is a test file for sending mails.
Event name: {event}
{% get_current_language as 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})