Compare commits

..

1 Commits

Author SHA1 Message Date
Raphael Michel
b6642b0e88 Error pages: Load event theme if available (Z#23224853) 2026-03-09 18:09:51 +01:00
27 changed files with 150 additions and 207 deletions

View File

@@ -1415,7 +1415,6 @@ class BaseInvoiceAddressForm(forms.ModelForm):
if not data.get(r): if not data.get(r):
raise ValidationError({r: _("This field is required for the selected type of invoice transmission.")}) raise ValidationError({r: _("This field is required for the selected type of invoice transmission.")})
transmission_type.validate_invoice_address_data(data)
self.instance.transmission_type = transmission_type.identifier self.instance.transmission_type = transmission_type.identifier
self.instance.transmission_info = transmission_type.form_data_to_transmission_info(data) self.instance.transmission_info = transmission_type.form_data_to_transmission_info(data)
elif transmission_type.is_exclusive(self.event, data.get("country"), data.get("is_business")): elif transmission_type.is_exclusive(self.event, data.get("country"), data.get("is_business")):

View File

@@ -33,7 +33,8 @@ from pretix.base.invoicing.transmission import (
transmission_types, transmission_types,
) )
from pretix.base.models import Invoice, InvoiceAddress from pretix.base.models import Invoice, InvoiceAddress
from pretix.base.services.mail import mail from pretix.base.services.mail import mail, render_mail
from pretix.helpers.format import format_map
@transmission_types.new() @transmission_types.new()
@@ -133,7 +134,9 @@ class EmailTransmissionProvider(TransmissionProvider):
subject = invoice.order.event.settings.get('mail_subject_order_invoice', as_type=LazyI18nString) subject = invoice.order.event.settings.get('mail_subject_order_invoice', as_type=LazyI18nString)
# Do not set to completed because that is done by the email sending task # Do not set to completed because that is done by the email sending task
outgoing_mail = mail( subject = format_map(subject, context)
email_content = render_mail(template, context)
mail(
[recipient], [recipient],
subject, subject,
template, template,
@@ -148,10 +151,19 @@ class EmailTransmissionProvider(TransmissionProvider):
plain_text_only=True, plain_text_only=True,
no_order_links=True, no_order_links=True,
) )
if outgoing_mail: invoice.order.log_action(
invoice.order.log_action( 'pretix.event.order.email.invoice',
'pretix.event.order.email.invoice', user=None,
user=None, auth=None,
auth=None, data={
data=outgoing_mail.log_data() 'subject': subject,
) 'message': email_content,
'position': None,
'recipient': recipient,
'invoices': [invoice.pk],
'attach_tickets': False,
'attach_ical': False,
'attach_other_files': [],
'attach_cached_files': [],
}
)

View File

@@ -204,12 +204,6 @@ class PeppolTransmissionType(TransmissionType):
} }
return base | {"transmission_peppol_participant_id"} return base | {"transmission_peppol_participant_id"}
def validate_invoice_address_data(self, address_data: dict):
# Special case Belgium: If a Belgian business ID is used as Peppol ID, it should match the VAT ID
if address_data.get("transmission_peppol_participant_id").startswith("0208:") and address_data.get("vat_id"):
if address_data["vat_id"].removeprefix("BE") != address_data["transmission_peppol_participant_id"].removeprefix("0208:"):
raise ValidationError({"transmission_peppol_participant_id": _("The Peppol participant ID does not match your VAT ID.")})
def pdf_watermark(self) -> str: def pdf_watermark(self) -> str:
return pgettext("peppol_invoice", "Visual copy") return pgettext("peppol_invoice", "Visual copy")

View File

@@ -24,7 +24,7 @@ from typing import Optional
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_countries.fields import Country from django_countries.fields import Country
from pretix.base.models import Invoice from pretix.base.models import Invoice, InvoiceAddress
from pretix.base.signals import EventPluginRegistry, Registry from pretix.base.signals import EventPluginRegistry, Registry
@@ -89,7 +89,7 @@ class TransmissionType:
def invoice_address_form_fields_visible(self, country: Country, is_business: bool) -> set: def invoice_address_form_fields_visible(self, country: Country, is_business: bool) -> set:
return set(self.invoice_address_form_fields.keys()) return set(self.invoice_address_form_fields.keys())
def validate_invoice_address_data(self, address_data: dict): def validate_address(self, ia: InvoiceAddress):
pass pass
@property @property

View File

@@ -220,20 +220,3 @@ class OutgoingMail(models.Model):
error_log_action_type = 'pretix.email.error' error_log_action_type = 'pretix.email.error'
log_target = None log_target = None
return log_target, error_log_action_type return log_target, error_log_action_type
def log_data(self):
return {
"subject": self.subject,
"message": self.body_plain,
"to": self.to,
"cc": self.cc,
"bcc": self.bcc,
"invoices": [i.pk for i in self.should_attach_invoices.all()],
"attach_tickets": self.should_attach_tickets,
"attach_ical": self.should_attach_ical,
"attach_other_files": self.should_attach_other_files,
"attach_cached_files": [cf.filename for cf in self.should_attach_cached_files.all()],
"position": self.orderposition.positionid if self.orderposition else None,
}

View File

@@ -87,6 +87,7 @@ from pretix.base.timemachine import time_machine_now
from ...helpers import OF_SELF from ...helpers import OF_SELF
from ...helpers.countries import CachedCountries, FastCountryField from ...helpers.countries import CachedCountries, FastCountryField
from ...helpers.format import FormattedString, format_map
from ...helpers.names import build_name from ...helpers.names import build_name
from ...testutils.middleware import debugflags_var from ...testutils.middleware import debugflags_var
from ._transactions import ( from ._transactions import (
@@ -1166,7 +1167,7 @@ class Order(LockModel, LoggedModel):
only be attached for this position and child positions, the link will only point to the only be attached for this position and child positions, the link will only point to the
position and the attendee email will be used if available. position and the attendee email will be used if available.
""" """
from pretix.base.services.mail import mail from pretix.base.services.mail import mail, render_mail
if not self.email and not (position and position.attendee_email): if not self.email and not (position and position.attendee_email):
return return
@@ -1176,20 +1177,32 @@ class Order(LockModel, LoggedModel):
if position and position.attendee_email: if position and position.attendee_email:
recipient = position.attendee_email recipient = position.attendee_email
outgoing_mail = mail( email_content = render_mail(template, context)
if not isinstance(subject, FormattedString):
subject = format_map(subject, context)
mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.locale, self, headers=headers, sender=sender, self.event, self.locale, self, headers=headers, sender=sender,
invoices=invoices, attach_tickets=attach_tickets, invoices=invoices, attach_tickets=attach_tickets,
position=position, auto_email=auto_email, attach_ical=attach_ical, position=position, auto_email=auto_email, attach_ical=attach_ical,
attach_other_files=attach_other_files, attach_cached_files=attach_cached_files, attach_other_files=attach_other_files, attach_cached_files=attach_cached_files,
) )
if outgoing_mail: self.log_action(
self.log_action( log_entry_type,
log_entry_type, user=user,
user=user, auth=auth,
auth=auth, data={
data=outgoing_mail.log_data(), 'subject': subject,
) 'message': email_content,
'position': position.positionid if position else None,
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': attach_other_files,
'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
}
)
def resend_link(self, user=None, auth=None): def resend_link(self, user=None, auth=None):
with language(self.locale, self.event.settings.region): with language(self.locale, self.event.settings.region):
@@ -2887,14 +2900,17 @@ class OrderPosition(AbstractPosition):
:param attach_tickets: Attach tickets of this order, if they are existing and ready to download :param attach_tickets: Attach tickets of this order, if they are existing and ready to download
:param attach_ical: Attach relevant ICS files :param attach_ical: Attach relevant ICS files
""" """
from pretix.base.services.mail import mail from pretix.base.services.mail import mail, render_mail
if not self.attendee_email: if not self.attendee_email:
return return
with language(self.order.locale, self.order.event.settings.region): with language(self.order.locale, self.order.event.settings.region):
recipient = self.attendee_email recipient = self.attendee_email
outgoing_mail = mail( email_content = render_mail(template, context)
if not isinstance(subject, FormattedString):
subject = format_map(subject, context)
mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.order.locale, order=self.order, headers=headers, sender=sender, self.event, self.order.locale, order=self.order, headers=headers, sender=sender,
position=self, position=self,
@@ -2903,13 +2919,21 @@ class OrderPosition(AbstractPosition):
attach_ical=attach_ical, attach_ical=attach_ical,
attach_other_files=attach_other_files, attach_other_files=attach_other_files,
) )
if outgoing_mail: self.order.log_action(
self.order.log_action( log_entry_type,
log_entry_type, user=user,
user=user, auth=auth,
auth=auth, data={
data=outgoing_mail.log_data(), 'subject': subject,
) 'message': email_content,
'recipient': recipient,
'invoices': [i.pk for i in invoices] if invoices else [],
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': attach_other_files,
'attach_cached_files': [],
}
)
def resend_link(self, user=None, auth=None): def resend_link(self, user=None, auth=None):

View File

@@ -34,9 +34,10 @@ from phonenumber_field.modelfields import PhoneNumberField
from pretix.base.email import get_email_context from pretix.base.email import get_email_context
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import User, Voucher from pretix.base.models import User, Voucher
from pretix.base.services.mail import mail from pretix.base.services.mail import mail, render_mail
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from ...helpers.format import format_map
from ...helpers.names import build_name from ...helpers.names import build_name
from .base import LoggedModel from .base import LoggedModel
from .event import Event, SubEvent from .event import Event, SubEvent
@@ -272,7 +273,9 @@ class WaitingListEntry(LoggedModel):
with language(self.locale, self.event.settings.region): with language(self.locale, self.event.settings.region):
recipient = self.email recipient = self.email
outgoing_mail = mail( email_content = render_mail(template, context)
subject = format_map(subject, context)
mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.event,
self.locale, self.locale,
@@ -282,13 +285,18 @@ class WaitingListEntry(LoggedModel):
attach_other_files=attach_other_files, attach_other_files=attach_other_files,
attach_cached_files=attach_cached_files, attach_cached_files=attach_cached_files,
) )
if outgoing_mail: self.log_action(
self.log_action( log_entry_type,
log_entry_type, user=user,
user=user, auth=auth,
auth=auth, data={
data=outgoing_mail.log_data(), 'subject': subject,
) 'message': email_content,
'recipient': recipient,
'attach_other_files': attach_other_files,
'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
}
)
@staticmethod @staticmethod
def clean_itemvar(event, item, variation): def clean_itemvar(event, item, variation):

View File

@@ -1295,7 +1295,6 @@ class ManualPayment(BasePaymentProvider):
def format_map(self, order, payment): def format_map(self, order, payment):
return { return {
# Possible placeholder injection, we should make sure to never include user-controlled variables here
'order': order.code, 'order': order.code,
'amount': payment.amount, 'amount': payment.amount,
'currency': self.event.currency, 'currency': self.event.currency,

View File

@@ -45,6 +45,7 @@ from pretix.base.services.tax import split_fee_for_taxes
from pretix.base.templatetags.money import money_filter from pretix.base.templatetags.money import money_filter
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers import OF_SELF from pretix.helpers import OF_SELF
from pretix.helpers.format import format_map
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -54,7 +55,7 @@ def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: Lazy
email_context = get_email_context(event_or_subevent=subevent or wle.event, event=wle.event) email_context = get_email_context(event_or_subevent=subevent or wle.event, event=wle.event)
mail( mail(
wle.email, wle.email,
str(subject), format_map(subject, email_context),
message, message,
email_context, email_context,
wle.event, wle.event,
@@ -72,8 +73,9 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
email_context = get_email_context(event_or_subevent=subevent or order.event, refund_amount=refund_amount, email_context = get_email_context(event_or_subevent=subevent or order.event, refund_amount=refund_amount,
order=order, position_or_address=ia, event=order.event) order=order, position_or_address=ia, event=order.event)
real_subject = format_map(subject, email_context)
order.send_mail( order.send_mail(
subject, message, email_context, real_subject, message, email_context,
'pretix.event.order.email.event_canceled', 'pretix.event.order.email.event_canceled',
user, user,
) )
@@ -83,13 +85,14 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
continue continue
if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email: if p.addon_to_id is None and p.attendee_email and p.attendee_email != order.email:
real_subject = format_map(subject, email_context)
email_context = get_email_context(event_or_subevent=p.subevent or order.event, email_context = get_email_context(event_or_subevent=p.subevent or order.event,
event=order.event, event=order.event,
refund_amount=refund_amount, refund_amount=refund_amount,
position_or_address=p, position_or_address=p,
order=order, position=p) order=order, position=p)
order.send_mail( order.send_mail(
subject, message, email_context, real_subject, message, email_context,
'pretix.event.order.email.event_canceled', 'pretix.event.order.email.event_canceled',
position=p, position=p,
user=user user=user

View File

@@ -149,13 +149,13 @@ def prefix_subject(settings_holder, subject, highlight=False):
return subject return subject
def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString], template: Union[str, LazyI18nString], def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any] = None, event: Event = None, locale: str = None, order: Order = None, context: Dict[str, Any] = None, event: Event = None, locale: str = None, order: Order = None,
position: OrderPosition = None, *, headers: dict = None, sender: str = None, organizer: Organizer = None, position: OrderPosition = None, *, headers: dict = None, sender: str = None, organizer: Organizer = None,
customer: Customer = None, invoices: Sequence = None, attach_tickets=False, auto_email=True, user=None, customer: Customer = None, invoices: Sequence = None, attach_tickets=False, auto_email=True, user=None,
attach_ical=False, attach_cached_files: Sequence = None, attach_other_files: list=None, attach_ical=False, attach_cached_files: Sequence = None, attach_other_files: list=None,
plain_text_only=False, no_order_links=False, cc: Sequence[str]=None, bcc: Sequence[str]=None, plain_text_only=False, no_order_links=False, cc: Sequence[str]=None, bcc: Sequence[str]=None,
sensitive: bool=False) -> Optional[OutgoingMail]: sensitive: bool=False):
""" """
Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation. Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
@@ -335,26 +335,14 @@ def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString],
should_attach_other_files=attach_other_files or [], should_attach_other_files=attach_other_files or [],
sensitive=sensitive, sensitive=sensitive,
) )
m._prefetched_objects_cache = {}
if invoices and not position: if invoices and not position:
m.should_attach_invoices.add(*invoices) m.should_attach_invoices.add(*invoices)
# Hack: For logging, we'll later make a `should_attach_invoices.all()` call. We can prevent a useless
# DB query by filling the cache
m._prefetched_objects_cache[m.should_attach_invoices.prefetch_cache_name] = invoices
else:
m._prefetched_objects_cache[m.should_attach_invoices.prefetch_cache_name] = Invoice.objects.none()
if attach_cached_files: if attach_cached_files:
cf_list = []
for cf in attach_cached_files: for cf in attach_cached_files:
if not isinstance(cf, CachedFile): if not isinstance(cf, CachedFile):
cf = CachedFile.objects.get(pk=cf) m.should_attach_cached_files.add(CachedFile.objects.get(pk=cf))
m.should_attach_cached_files.add(cf) else:
cf_list.append(cf) m.should_attach_cached_files.add(cf)
# Hack: For logging, we'll later make a `should_attach_cached_files.all()` call. We can prevent a useless
# DB query by filling the cache
m._prefetched_objects_cache[m.should_attach_cached_files.prefetch_cache_name] = cf_list
else:
m._prefetched_objects_cache[m.should_attach_cached_files.prefetch_cache_name] = CachedFile.objects.none()
send_task = mail_send_task.si( send_task = mail_send_task.si(
outgoing_mail=m.id outgoing_mail=m.id
@@ -376,8 +364,6 @@ def mail(email: Union[str, Sequence[str]], subject: Union[str, FormattedString],
lambda: chain(*task_chain).apply_async() lambda: chain(*task_chain).apply_async()
) )
return m
class CustomEmail(EmailMultiAlternatives): class CustomEmail(EmailMultiAlternatives):
def _create_mime_attachment(self, content, mimetype): def _create_mime_attachment(self, content, mimetype):

View File

@@ -1799,6 +1799,8 @@ class OrderChangeManager:
tax_rule = tax_rules.get(pos.pk, pos.tax_rule) tax_rule = tax_rules.get(pos.pk, pos.tax_rule)
if not tax_rule: if not tax_rule:
continue continue
if not pos.price:
continue
try: try:
new_rate = tax_rule.tax_rate_for(ia) new_rate = tax_rule.tax_rate_for(ia)
@@ -1815,9 +1817,7 @@ class OrderChangeManager:
override_tax_rate=new_rate, override_tax_code=new_code) override_tax_rate=new_rate, override_tax_code=new_code)
self._totaldiff_guesstimate += new_tax.gross - pos.price self._totaldiff_guesstimate += new_tax.gross - pos.price
self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price)) self._operations.append(self.PriceOperation(pos, new_tax, new_tax.gross - pos.price))
if pos.price: self._invoice_dirty = True
# We do not consider the invoice dirty if only 0€-valued taxes are changed
self._invoice_dirty = True
def cancel_fee(self, fee: OrderFee): def cancel_fee(self, fee: OrderFee):
self._totaldiff_guesstimate -= fee.value self._totaldiff_guesstimate -= fee.value

View File

@@ -39,7 +39,7 @@ def vouchers_send(event: Event, vouchers: list, subject: str, message: str, reci
with language(event.settings.locale): with language(event.settings.locale):
email_context = get_email_context(event=event, name=r.get('name') or '', email_context = get_email_context(event=event, name=r.get('name') or '',
voucher_list=[v.code for v in voucher_list]) voucher_list=[v.code for v in voucher_list])
outgoing_mail = mail( mail(
r['email'], r['email'],
subject, subject,
LazyI18nString(message), LazyI18nString(message),
@@ -60,8 +60,8 @@ def vouchers_send(event: Event, vouchers: list, subject: str, message: str, reci
data={ data={
'recipient': r['email'], 'recipient': r['email'],
'name': r.get('name'), 'name': r.get('name'),
'subject': outgoing_mail.subject, 'subject': subject,
'message': outgoing_mail.body_plain, 'message': message,
}, },
save=False save=False
)) ))

View File

@@ -363,7 +363,7 @@ class EmailAddressShredder(BaseDataShredder):
le.save(update_fields=['data', 'shredded']) le.save(update_fields=['data', 'shredded'])
else: else:
shred_log_fields(le, banlist=[ shred_log_fields(le, banlist=[
'recipient', 'message', 'subject', 'full_mail', 'old_email', 'new_email', 'bcc', 'cc', 'recipient', 'message', 'subject', 'full_mail', 'old_email', 'new_email'
]) ])

View File

@@ -2,7 +2,6 @@
{% load i18n %} {% load i18n %}
{% load urlreplace %} {% load urlreplace %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load static %}
{% block title %}{% trans "Events" %}{% endblock %} {% block title %}{% trans "Events" %}{% endblock %}
{% block content %} {% block content %}
<h1>{% trans "Events" %}</h1> <h1>{% trans "Events" %}</h1>
@@ -75,7 +74,6 @@
<a href="?{% url_replace request 'ordering' 'organizer' %}"><i class="fa fa-caret-up"></i></a> <a href="?{% url_replace request 'ordering' 'organizer' %}"><i class="fa fa-caret-up"></i></a>
</th> </th>
{% endif %} {% endif %}
<th>{% trans "Sales channels" %}</th>
<th> <th>
{% trans "Start date" %} {% trans "Start date" %}
<a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a> <a href="?{% url_replace request 'ordering' '-date_from' %}"><i class="fa fa-caret-down"></i></a>
@@ -110,21 +108,6 @@
{% endfor %} {% endfor %}
</td> </td>
{% if not hide_orga %}<td>{{ e.organizer }}</td>{% endif %} {% if not hide_orga %}<td>{{ e.organizer }}</td>{% endif %}
<td>
{% for c in e.organizer.sales_channels.all %}
{% if e.all_sales_channels or c in e.limit_sales_channels.all %}
{% if "." in c.icon %}
<img src="{% static c.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ c.label }}">
{% else %}
<span class="fa fa-fw fa-{{ c.icon }} text-muted"
data-toggle="tooltip" title="{{ c.label }}"></span>
{% endif %}
{% else %}
<span class="fa fa-fw"></span>
{% endif %}
{% endfor %}
</td>
<td class="event-date-col"> <td class="event-date-col">
{% if e.has_subevents %} {% if e.has_subevents %}
<span class="fa fa-fw- fa-calendar"></span> <span class="fa fa-fw- fa-calendar"></span>

View File

@@ -24,9 +24,7 @@
{% if log.display %} {% if log.display %}
<br/><span class="fa fa-fw fa-comment-o"></span> {{ log.display }} <br/><span class="fa fa-fw fa-comment-o"></span> {{ log.display }}
{% endif %} {% endif %}
{% if log.parsed_data.to %} {% if log.parsed_data.recipient %}
<br/><span class="fa fa-fw fa-envelope-o"></span> {{ log.parsed_data.to|join:", " }}
{% elif log.parsed_data.recipient %} {# legacy #}
<br/><span class="fa fa-fw fa-envelope-o"></span> {{ log.parsed_data.recipient }} <br/><span class="fa fa-fw fa-envelope-o"></span> {{ log.parsed_data.recipient }}
{% endif %} {% endif %}
</p> </p>

View File

@@ -1,7 +1,6 @@
{% extends "pretixcontrol/organizers/base.html" %} {% extends "pretixcontrol/organizers/base.html" %}
{% load i18n %} {% load i18n %}
{% load bootstrap3 %} {% load bootstrap3 %}
{% load static %}
{% block inner %} {% block inner %}
<h1> <h1>
{% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %} {% blocktrans with name=request.organizer.name %}Organizer: {{ name }}{% endblocktrans %}
@@ -63,7 +62,6 @@
<thead> <thead>
<tr> <tr>
<th>{% trans "Event name" %}</th> <th>{% trans "Event name" %}</th>
<th>{% trans "Sales channels" %}</th>
<th> <th>
{% trans "Start date" %} {% trans "Start date" %}
/ /
@@ -79,30 +77,10 @@
<td> <td>
<strong><a <strong><a
href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong> href="{% url "control:event.index" organizer=e.organizer.slug event=e.slug %}">{{ e.name }}</a></strong>
<br> <br><small>{{ e.slug }}</small>
<small> {% for k, v in e.meta_data.items %}
{{ e.slug }} {% if v %}
</small> <small class="text-muted">&middot; {{ k }}: {{ v }}</small>
<small class="text-muted">
{% for k, v in e.meta_data.items %}
{% if v %}
&middot; {{ k }}: {{ v }}
{% endif %}
{% endfor %}
</small>
</td>
<td>
{% for c in sales_channels %}
{% if e.all_sales_channels or c in e.limit_sales_channels.all %}
{% if "." in c.icon %}
<img src="{% static c.icon %}" class="fa-like-image"
data-toggle="tooltip" title="{{ c.label }}">
{% else %}
<span class="fa fa-fw fa-{{ c.icon }} text-muted"
data-toggle="tooltip" title="{{ c.label }}"></span>
{% endif %}
{% else %}
<span class="fa fa-fw"></span>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</td> </td>

View File

@@ -264,17 +264,12 @@
The paper size will match the PDF. The paper size will match the PDF.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p class="text-center"> <p>
<span class="btn btn-default fileinput-button background-button btn-block"> <span class="btn btn-default fileinput-button background-button btn-block">
<i class="fa fa-upload"></i> <i class="fa fa-upload"></i>
<span>{% trans "Upload PDF as background" %}</span> <span>{% trans "Upload PDF as background" %}</span>
<input id="fileupload" type="file" name="background" accept="application/pdf"> <input id="fileupload" type="file" name="background" accept="application/pdf">
</span> </span>
<small class="text-muted">
{% blocktrans trimmed with size=maxfilesize|filesizeformat %}
max. {{ size }}, smaller is better
{% endblocktrans %}
</small>
</p> </p>
<p class="text-center"> <p class="text-center">
<a class="btn btn-link background-download-button" href="{{ pdf }}" target="_blank"> <a class="btn btn-link background-download-button" href="{{ pdf }}" target="_blank">

View File

@@ -67,12 +67,7 @@ class EventList(PaginationMixin, ListView):
def get_queryset(self): def get_queryset(self):
qs = self.request.user.get_events_with_any_permission(self.request).prefetch_related( qs = self.request.user.get_events_with_any_permission(self.request).prefetch_related(
'organizer', 'organizer', '_settings_objects', 'organizer___settings_objects', 'organizer__meta_properties',
'organizer__sales_channels',
'_settings_objects',
'organizer___settings_objects',
'organizer__meta_properties',
'limit_sales_channels',
Prefetch( Prefetch(
'meta_values', 'meta_values',
EventMetaValue.objects.select_related('property'), EventMetaValue.objects.select_related('property'),

View File

@@ -2421,9 +2421,9 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
with language(order.locale, self.request.event.settings.region): with language(order.locale, self.request.event.settings.region):
email_context = get_email_context(event=order.event, order=order) email_context = get_email_context(event=order.event, order=order)
email_template = LazyI18nString(form.cleaned_data['message']) email_template = LazyI18nString(form.cleaned_data['message'])
email_subject = format_map(str(form.cleaned_data['subject']), email_context)
email_content = render_mail(email_template, email_context)
if self.request.POST.get('action') == 'preview': if self.request.POST.get('action') == 'preview':
email_subject = format_map(form.cleaned_data['subject'], email_context)
email_content = render_mail(email_template, email_context)
self.preview_output = { self.preview_output = {
'subject': mark_safe(_('Subject: {subject}').format( 'subject': mark_safe(_('Subject: {subject}').format(
subject=prefix_subject(order.event, escape(email_subject), highlight=True) subject=prefix_subject(order.event, escape(email_subject), highlight=True)
@@ -2485,9 +2485,9 @@ class OrderPositionSendMail(OrderSendMail):
with language(position.order.locale, self.request.event.settings.region): with language(position.order.locale, self.request.event.settings.region):
email_context = get_email_context(event=position.order.event, order=position.order, position=position) email_context = get_email_context(event=position.order.event, order=position.order, position=position)
email_template = LazyI18nString(form.cleaned_data['message']) email_template = LazyI18nString(form.cleaned_data['message'])
email_subject = format_map(str(form.cleaned_data['subject']), email_context)
email_content = render_mail(email_template, email_context)
if self.request.POST.get('action') == 'preview': if self.request.POST.get('action') == 'preview':
email_subject = format_map(str(form.cleaned_data['subject']), email_context)
email_content = render_mail(email_template, email_context)
self.preview_output = { self.preview_output = {
'subject': mark_safe(_('Subject: {subject}').format( 'subject': mark_safe(_('Subject: {subject}').format(
subject=prefix_subject(position.order.event, escape(email_subject), highlight=True)) subject=prefix_subject(position.order.event, escape(email_subject), highlight=True))

View File

@@ -207,7 +207,6 @@ class OrganizerDetail(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin
'organizer').prefetch_related( 'organizer').prefetch_related(
'organizer', '_settings_objects', 'organizer___settings_objects', 'organizer', '_settings_objects', 'organizer___settings_objects',
'organizer__meta_properties', 'organizer__meta_properties',
'limit_sales_channels',
Prefetch( Prefetch(
'meta_values', 'meta_values',
EventMetaValue.objects.select_related('property'), EventMetaValue.objects.select_related('property'),
@@ -238,7 +237,6 @@ class OrganizerDetail(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin
self.filter_form['meta_{}'.format(p.name)] for p in self.filter_form['meta_{}'.format(p.name)] for p in
self.organizer.meta_properties.filter(filter_allowed=True) self.organizer.meta_properties.filter(filter_allowed=True)
] ]
ctx['sales_channels'] = self.request.organizer.sales_channels.all()
return ctx return ctx

View File

@@ -292,7 +292,6 @@ class BaseEditorView(EventPermissionRequiredMixin, TemplateView):
ctx['layout'] = json.dumps(self.get_current_layout()) ctx['layout'] = json.dumps(self.get_current_layout())
ctx['title'] = self.title ctx['title'] = self.title
ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales] ctx['locales'] = [p for p in settings.LANGUAGES if p[0] in self.request.event.settings.locales]
ctx['maxfilesize'] = self.maxfilesize
return ctx return ctx

View File

@@ -131,7 +131,7 @@ class VoucherList(PaginationMixin, EventPermissionRequiredMixin, ListView):
elif v.quota: elif v.quota:
prod = _('Any product in quota "{quota}"').format(quota=str(v.quota.name)) prod = _('Any product in quota "{quota}"').format(quota=str(v.quota.name))
else: else:
prod = "" prod = _('Any product')
row = [ row = [
v.code, v.code,
v.valid_until.isoformat() if v.valid_until else "", v.valid_until.isoformat() if v.valid_until else "",

View File

@@ -38,10 +38,13 @@ from i18nfield.strings import LazyI18nString
from pretix.base.email import get_email_context from pretix.base.email import get_email_context
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Checkin, Event, InvoiceAddress, Order, User from pretix.base.models import (
CachedFile, Checkin, Event, InvoiceAddress, Order, User,
)
from pretix.base.services.mail import mail from pretix.base.services.mail import mail
from pretix.base.services.tasks import ProfiledEventTask from pretix.base.services.tasks import ProfiledEventTask
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers.format import format_map
def _chunks(lst, n): def _chunks(lst, n):
@@ -61,6 +64,7 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
user = User.objects.get(pk=user) if user else None user = User.objects.get(pk=user) if user else None
subject = LazyI18nString(subject) subject = LazyI18nString(subject)
message = LazyI18nString(message) message = LazyI18nString(message)
attachments_for_log = [cf.filename for cf in CachedFile.objects.filter(pk__in=attachments)] if attachments else []
def _send_to_order(o): def _send_to_order(o):
send_to_order = recipients in ('both', 'orders') send_to_order = recipients in ('both', 'orders')
@@ -118,7 +122,7 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
with language(o.locale, event.settings.region): with language(o.locale, event.settings.region):
email_context = get_email_context(event=event, order=o, invoice_address=ia, position=p) email_context = get_email_context(event=event, order=o, invoice_address=ia, position=p)
outgoing_mail = mail( mail(
p.attendee_email, p.attendee_email,
subject, subject,
message, message,
@@ -131,17 +135,25 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
attach_ical=attach_ical, attach_ical=attach_ical,
attach_cached_files=attachments attach_cached_files=attachments
) )
if outgoing_mail: o.log_action(
o.log_action( 'pretix.plugins.sendmail.order.email.sent.attendee',
'pretix.plugins.sendmail.order.email.sent.attendee', user=user,
user=user, data={
data=outgoing_mail.log_data(), 'position': p.positionid,
) 'subject': format_map(subject.localize(o.locale), email_context),
'message': format_map(message.localize(o.locale), email_context),
'recipient': p.attendee_email,
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': [],
'attach_cached_files': attachments_for_log,
}
)
if send_to_order and o.email: if send_to_order and o.email:
with language(o.locale, event.settings.region): with language(o.locale, event.settings.region):
email_context = get_email_context(event=event, order=o, invoice_address=ia) email_context = get_email_context(event=event, order=o, invoice_address=ia)
outgoing_mail = mail( mail(
o.email, o.email,
subject, subject,
message, message,
@@ -153,12 +165,19 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
attach_ical=attach_ical, attach_ical=attach_ical,
attach_cached_files=attachments, attach_cached_files=attachments,
) )
if outgoing_mail: o.log_action(
o.log_action( 'pretix.plugins.sendmail.order.email.sent',
'pretix.plugins.sendmail.order.email.sent', user=user,
user=user, data={
data=outgoing_mail.log_data(), 'subject': format_map(subject.localize(o.locale), email_context),
) 'message': format_map(message.localize(o.locale), email_context),
'recipient': o.email,
'attach_tickets': attach_tickets,
'attach_ical': attach_ical,
'attach_other_files': [],
'attach_cached_files': attachments_for_log,
}
)
for chunk in _chunks(objects, 1000): for chunk in _chunks(objects, 1000):
orders = Order.objects.filter(pk__in=chunk, event=event) orders = Order.objects.filter(pk__in=chunk, event=event)

View File

@@ -13,7 +13,7 @@
{% csrf_token %} {% csrf_token %}
<div class="panel-group" id="questions_accordion"> <div class="panel-group" id="questions_accordion">
{% if invoice_address_asked or event.settings.invoice_name_required %} {% if invoice_address_asked or event.settings.invoice_name_required %}
{% if invoice_address_asked and not request.GET.generate_invoice == "true" and not invoice_generation_selfservice %} {% if invoice_address_asked and not request.GET.generate_invoice == "true" and not event.settings.invoice_reissue_after_modify %}
<div class="alert alert-info"> <div class="alert alert-info">
{% blocktrans trimmed %} {% blocktrans trimmed %}
Modifying your invoice address will not automatically generate a new invoice. Modifying your invoice address will not automatically generate a new invoice.

View File

@@ -909,21 +909,6 @@ class OrderModify(EventViewMixin, OrderDetailMixin, OrderQuestionsViewMixin, Tem
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return super().get(request, *args, **kwargs) return super().get(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = super().get_context_data(
**kwargs,
)
ctx['invoice_generation_selfservice'] = (
self.request.event.settings.invoice_reissue_after_modify or
(
can_generate_invoice(self.request.event, self.order, ignore_payments=True) and
not self.order.invoices.exists()
)
)
return ctx
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
self.request = request self.request = request
self.kwargs = kwargs self.kwargs = kwargs

View File

@@ -157,7 +157,7 @@ DATABASES = {
'HOST': config.get('database', 'host', fallback=''), 'HOST': config.get('database', 'host', fallback=''),
'PORT': config.get('database', 'port', fallback=''), 'PORT': config.get('database', 'port', fallback=''),
'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120, 'CONN_MAX_AGE': 0 if db_backend == 'sqlite3' else 120,
'CONN_HEALTH_CHECKS': db_backend != 'sqlite3', 'CONN_HEALTH_CHECKS': db_backend != 'sqlite3', # Will only be used from Django 4.1 onwards
'DISABLE_SERVER_SIDE_CURSORS': db_disable_server_side_cursors, 'DISABLE_SERVER_SIDE_CURSORS': db_disable_server_side_cursors,
'OPTIONS': db_options, 'OPTIONS': db_options,
'TEST': {} 'TEST': {}
@@ -179,21 +179,6 @@ if config.has_section('replica'):
} }
DATABASE_ROUTERS = ['pretix.helpers.database.ReplicaRouter'] DATABASE_ROUTERS = ['pretix.helpers.database.ReplicaRouter']
if config.has_section('dbreadonly'):
DATABASES['readonly'] = {
'ENGINE': 'django.db.backends.' + db_backend,
'NAME': config.get('dbreadonly', 'name', fallback=DATABASES['default']['NAME']),
'USER': config.get('dbreadonly', 'user', fallback=DATABASES['default']['USER']),
'PASSWORD': config.get('dbreadonly', 'password', fallback=DATABASES['default']['PASSWORD']),
'HOST': config.get('dbreadonly', 'host', fallback=DATABASES['default']['HOST']),
'PORT': config.get('dbreadonly', 'port', fallback=DATABASES['default']['PORT']),
'CONN_MAX_AGE': 0, # do not spam primary with open connections as long as readonly is only used occasionally
'CONN_HEALTH_CHECKS': db_backend != 'sqlite3',
'DISABLE_SERVER_SIDE_CURSORS': db_disable_server_side_cursors,
'OPTIONS': db_options,
'TEST': {}
}
STATIC_URL = config.get('urls', 'static', fallback='/static/') STATIC_URL = config.get('urls', 'static', fallback='/static/')
MEDIA_URL = config.get('urls', 'media', fallback='/media/') MEDIA_URL = config.get('urls', 'media', fallback='/media/')

View File

@@ -173,8 +173,8 @@ $(function () {
if (!dependents.transmission_peppol_participant_id.val()) { if (!dependents.transmission_peppol_participant_id.val()) {
const fill_peppol_id = function () { const fill_peppol_id = function () {
const vatId = dependents.vat_id.val(); const vatId = dependents.vat_id.val();
if (vatId && vatId.startsWith("BE") && dependents.transmission_type.val() === "peppol") { if (vatId && vatId.startsWith("BE") && dependents.transmission_type.val() === "peppol" && autofill_peppol_id) {
dependents.transmission_peppol_participant_id.val("0208:" + vatId.substring(2)) dependents.transmission_peppol_participant_id.val("0201:" + vatId.substring(2))
} }
} }
dependents.vat_id.add(dependents.transmission_type).on("change", fill_peppol_id); dependents.vat_id.add(dependents.transmission_type).on("change", fill_peppol_id);