mirror of
https://github.com/pretix/pretix.git
synced 2025-12-11 01:22:28 +00:00
Compare commits
1 Commits
fix-datasy
...
mail-model
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77380214a8 |
@@ -49,7 +49,7 @@ from pretix.base.plugins import (
|
|||||||
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
PLUGIN_LEVEL_EVENT, PLUGIN_LEVEL_EVENT_ORGANIZER_HYBRID,
|
||||||
PLUGIN_LEVEL_ORGANIZER,
|
PLUGIN_LEVEL_ORGANIZER,
|
||||||
)
|
)
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import mail
|
||||||
from pretix.base.settings import validate_organizer_settings
|
from pretix.base.settings import validate_organizer_settings
|
||||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
@@ -363,24 +363,21 @@ class TeamInviteSerializer(serializers.ModelSerializer):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def _send_invite(self, instance):
|
def _send_invite(self, instance):
|
||||||
try:
|
mail(
|
||||||
mail(
|
instance.email,
|
||||||
instance.email,
|
_('pretix account invitation'),
|
||||||
_('pretix account invitation'),
|
'pretixcontrol/email/invitation.txt',
|
||||||
'pretixcontrol/email/invitation.txt',
|
{
|
||||||
{
|
'user': self,
|
||||||
'user': self,
|
'organizer': self.context['organizer'].name,
|
||||||
'organizer': self.context['organizer'].name,
|
'team': instance.team.name,
|
||||||
'team': instance.team.name,
|
'url': build_global_uri('control:auth.invite', kwargs={
|
||||||
'url': build_global_uri('control:auth.invite', kwargs={
|
'token': instance.token
|
||||||
'token': instance.token
|
})
|
||||||
})
|
},
|
||||||
},
|
event=None,
|
||||||
event=None,
|
locale=get_language_without_region() # TODO: expose?
|
||||||
locale=get_language_without_region() # TODO: expose?
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
pass # Already logged
|
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
if 'email' in validated_data:
|
if 'email' in validated_data:
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ from pretix.base.services.invoices import (
|
|||||||
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
|
generate_cancellation, generate_invoice, invoice_pdf, invoice_qualified,
|
||||||
regenerate_invoice, transmit_invoice,
|
regenerate_invoice, transmit_invoice,
|
||||||
)
|
)
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.services.orders import (
|
from pretix.base.services.orders import (
|
||||||
OrderChangeManager, OrderError, _order_placed_email,
|
OrderChangeManager, OrderError, _order_placed_email,
|
||||||
_order_placed_email_attendee, approve_order, cancel_order, deny_order,
|
_order_placed_email_attendee, approve_order, cancel_order, deny_order,
|
||||||
@@ -438,8 +437,6 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
|||||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
except PaymentException as e:
|
except PaymentException as e:
|
||||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
except SendMailException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return self.retrieve(request, [], **kwargs)
|
return self.retrieve(request, [], **kwargs)
|
||||||
return Response(
|
return Response(
|
||||||
@@ -633,10 +630,7 @@ class EventOrderViewSet(OrderViewSetMixin, viewsets.ModelViewSet):
|
|||||||
order = self.get_object()
|
order = self.get_object()
|
||||||
if not order.email:
|
if not order.email:
|
||||||
return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': 'There is no email address associated with this order.'}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
try:
|
order.resend_link(user=self.request.user, auth=self.request.auth)
|
||||||
order.resend_link(user=self.request.user, auth=self.request.auth)
|
|
||||||
except SendMailException:
|
|
||||||
return Response({'detail': _('There was an error sending the mail. Please try again later.')}, status=status.HTTP_503_SERVICE_UNAVAILABLE)
|
|
||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
status=status.HTTP_204_NO_CONTENT
|
status=status.HTTP_204_NO_CONTENT
|
||||||
@@ -1609,8 +1603,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
)
|
)
|
||||||
except Quota.QuotaExceededException:
|
except Quota.QuotaExceededException:
|
||||||
pass
|
pass
|
||||||
except SendMailException:
|
|
||||||
pass
|
|
||||||
|
|
||||||
serializer = OrderPaymentSerializer(r, context=serializer.context)
|
serializer = OrderPaymentSerializer(r, context=serializer.context)
|
||||||
|
|
||||||
@@ -1648,8 +1640,6 @@ class PaymentViewSet(CreateModelMixin, viewsets.ReadOnlyModelViewSet):
|
|||||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
except PaymentException as e:
|
except PaymentException as e:
|
||||||
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'detail': str(e)}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
except SendMailException:
|
|
||||||
pass
|
|
||||||
return self.retrieve(request, [], **kwargs)
|
return self.retrieve(request, [], **kwargs)
|
||||||
|
|
||||||
@action(detail=True, methods=['POST'])
|
@action(detail=True, methods=['POST'])
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ 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 SendMailException, mail, render_mail
|
from pretix.base.services.mail import mail, render_mail
|
||||||
from pretix.helpers.format import format_map
|
from pretix.helpers.format import format_map
|
||||||
|
|
||||||
|
|
||||||
@@ -133,41 +133,37 @@ class EmailTransmissionProvider(TransmissionProvider):
|
|||||||
template = invoice.order.event.settings.get('mail_text_order_invoice', as_type=LazyI18nString)
|
template = invoice.order.event.settings.get('mail_text_order_invoice', as_type=LazyI18nString)
|
||||||
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)
|
||||||
|
|
||||||
try:
|
# 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
|
subject = format_map(subject, context)
|
||||||
subject = format_map(subject, context)
|
email_content = render_mail(template, context)
|
||||||
email_content = render_mail(template, context)
|
mail(
|
||||||
mail(
|
[recipient],
|
||||||
[recipient],
|
subject,
|
||||||
subject,
|
template,
|
||||||
template,
|
context=context,
|
||||||
context=context,
|
event=invoice.order.event,
|
||||||
event=invoice.order.event,
|
locale=invoice.order.locale,
|
||||||
locale=invoice.order.locale,
|
order=invoice.order,
|
||||||
order=invoice.order,
|
invoices=[invoice],
|
||||||
invoices=[invoice],
|
attach_tickets=False,
|
||||||
attach_tickets=False,
|
auto_email=True,
|
||||||
auto_email=True,
|
attach_ical=False,
|
||||||
attach_ical=False,
|
plain_text_only=True,
|
||||||
plain_text_only=True,
|
no_order_links=True,
|
||||||
no_order_links=True,
|
)
|
||||||
)
|
invoice.order.log_action(
|
||||||
except SendMailException:
|
'pretix.event.order.email.invoice',
|
||||||
raise
|
user=None,
|
||||||
else:
|
auth=None,
|
||||||
invoice.order.log_action(
|
data={
|
||||||
'pretix.event.order.email.invoice',
|
'subject': subject,
|
||||||
user=None,
|
'message': email_content,
|
||||||
auth=None,
|
'position': None,
|
||||||
data={
|
'recipient': recipient,
|
||||||
'subject': subject,
|
'invoices': [invoice.pk],
|
||||||
'message': email_content,
|
'attach_tickets': False,
|
||||||
'position': None,
|
'attach_ical': False,
|
||||||
'recipient': recipient,
|
'attach_other_files': [],
|
||||||
'invoices': [invoice.pk],
|
'attach_cached_files': [],
|
||||||
'attach_tickets': False,
|
}
|
||||||
'attach_ical': False,
|
)
|
||||||
'attach_other_files': [],
|
|
||||||
'attach_cached_files': [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|||||||
112
src/pretix/base/migrations/0289_outgoingmail.py
Normal file
112
src/pretix/base/migrations/0289_outgoingmail.py
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# Generated by Django 4.2.17 on 2025-09-04 12:35
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("pretixbase", "0288_invoice_transmission"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="OutgoingMail",
|
||||||
|
fields=[
|
||||||
|
(
|
||||||
|
"id",
|
||||||
|
models.BigAutoField(
|
||||||
|
auto_created=True, primary_key=True, serialize=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("status", models.CharField(default="queued", max_length=200)),
|
||||||
|
("created", models.DateTimeField(auto_now_add=True)),
|
||||||
|
("sent", models.DateTimeField(blank=True, null=True)),
|
||||||
|
("error", models.TextField(null=True)),
|
||||||
|
("error_detail", models.TextField(null=True)),
|
||||||
|
("subject", models.TextField()),
|
||||||
|
("body_plain", models.TextField()),
|
||||||
|
("body_html", models.TextField()),
|
||||||
|
("sender", models.CharField(max_length=500)),
|
||||||
|
("headers", models.JSONField(default=dict)),
|
||||||
|
("to", models.JSONField(default=list)),
|
||||||
|
("cc", models.JSONField(default=list)),
|
||||||
|
("bcc", models.JSONField(default=list)),
|
||||||
|
("should_attach_tickets", models.BooleanField(default=False)),
|
||||||
|
("should_attach_ical", models.BooleanField(default=False)),
|
||||||
|
("should_attach_other_files", models.JSONField(default=list)),
|
||||||
|
("actual_attachments", models.JSONField(default=list)),
|
||||||
|
(
|
||||||
|
"customer",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="outgoing_mails",
|
||||||
|
to="pretixbase.customer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"event",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="outgoing_mails",
|
||||||
|
to="pretixbase.event",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"order",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="outgoing_mails",
|
||||||
|
to="pretixbase.order",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"orderposition",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="outgoing_mails",
|
||||||
|
to="pretixbase.orderposition",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"organizer",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.CASCADE,
|
||||||
|
related_name="outgoing_mails",
|
||||||
|
to="pretixbase.organizer",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"should_attach_cached_files",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="outgoing_mails", to="pretixbase.cachedfile"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"should_attach_invoices",
|
||||||
|
models.ManyToManyField(
|
||||||
|
related_name="outgoing_mails", to="pretixbase.invoice"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"user",
|
||||||
|
models.ForeignKey(
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
related_name="outgoing_mails",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"ordering": ("-created",),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -40,6 +40,7 @@ from .items import (
|
|||||||
SubEventItem, SubEventItemVariation, itempicture_upload_to,
|
SubEventItem, SubEventItemVariation, itempicture_upload_to,
|
||||||
)
|
)
|
||||||
from .log import LogEntry
|
from .log import LogEntry
|
||||||
|
from .mail import OutgoingMail
|
||||||
from .media import ReusableMedium
|
from .media import ReusableMedium
|
||||||
from .memberships import Membership, MembershipType
|
from .memberships import Membership, MembershipType
|
||||||
from .notifications import NotificationSetting
|
from .notifications import NotificationSetting
|
||||||
|
|||||||
@@ -331,27 +331,24 @@ class User(AbstractBaseUser, PermissionsMixin, LoggingMixin):
|
|||||||
return self.email
|
return self.email
|
||||||
|
|
||||||
def send_security_notice(self, messages, email=None):
|
def send_security_notice(self, messages, email=None):
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import mail
|
||||||
|
|
||||||
try:
|
with language(self.locale):
|
||||||
with language(self.locale):
|
msg = '- ' + '\n- '.join(str(m) for m in messages)
|
||||||
msg = '- ' + '\n- '.join(str(m) for m in messages)
|
|
||||||
|
|
||||||
mail(
|
mail(
|
||||||
email or self.email,
|
email or self.email,
|
||||||
_('Account information changed'),
|
_('Account information changed'),
|
||||||
'pretixcontrol/email/security_notice.txt',
|
'pretixcontrol/email/security_notice.txt',
|
||||||
{
|
{
|
||||||
'user': self,
|
'user': self,
|
||||||
'messages': msg,
|
'messages': msg,
|
||||||
'url': build_absolute_uri('control:user.settings')
|
'url': build_absolute_uri('control:user.settings')
|
||||||
},
|
},
|
||||||
event=None,
|
event=None,
|
||||||
user=self,
|
user=self,
|
||||||
locale=self.locale
|
locale=self.locale
|
||||||
)
|
)
|
||||||
except SendMailException:
|
|
||||||
pass # Already logged
|
|
||||||
|
|
||||||
def send_password_reset(self):
|
def send_password_reset(self):
|
||||||
from pretix.base.services.mail import mail
|
from pretix.base.services.mail import mail
|
||||||
|
|||||||
149
src/pretix/base/models/mail.py
Normal file
149
src/pretix/base/models/mail.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
#
|
||||||
|
# This file is part of pretix (Community Edition).
|
||||||
|
#
|
||||||
|
# Copyright (C) 2014-2020 Raphael Michel and contributors
|
||||||
|
# Copyright (C) 2020-2021 rami.io GmbH and contributors
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General
|
||||||
|
# Public License as published by the Free Software Foundation in version 3 of the License.
|
||||||
|
#
|
||||||
|
# ADDITIONAL TERMS APPLY: Pursuant to Section 7 of the GNU Affero General Public License, additional terms are
|
||||||
|
# applicable granting you additional permissions and placing additional restrictions on your usage of this software.
|
||||||
|
# Please refer to the pretix LICENSE file to obtain the full terms applicable to this work. If you did not receive
|
||||||
|
# this file, see <https://pretix.eu/about/en/license>.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
|
||||||
|
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||||
|
# details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License along with this program. If not, see
|
||||||
|
# <https://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
from django.core.mail import get_connection
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django_scopes import scope, scopes_disabled
|
||||||
|
|
||||||
|
|
||||||
|
class OutgoingMail(models.Model):
|
||||||
|
STATUS_QUEUED = "queued"
|
||||||
|
STATUS_INFLIGHT = "inflight"
|
||||||
|
STATUS_AWAWITING_RETRY = "awaiting_retry"
|
||||||
|
STATUS_FAILED = "failed"
|
||||||
|
STATUS_SENT = "sent"
|
||||||
|
STATUS_CHOICES = (
|
||||||
|
(STATUS_QUEUED, _("queued")),
|
||||||
|
(STATUS_INFLIGHT, _("being sent")),
|
||||||
|
(STATUS_AWAWITING_RETRY, _("awaiting retry")),
|
||||||
|
(STATUS_FAILED, _("failed")),
|
||||||
|
(STATUS_SENT, _("sent")),
|
||||||
|
)
|
||||||
|
|
||||||
|
status = models.CharField(max_length=200, choices=STATUS_CHOICES, default=STATUS_QUEUED)
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
sent = models.DateTimeField(null=True, blank=True)
|
||||||
|
error = models.TextField(null=True, blank=True)
|
||||||
|
error_detail = models.TextField(null=True, blank=True)
|
||||||
|
|
||||||
|
organizer = models.ForeignKey(
|
||||||
|
'pretixbase.Organizer',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
null=True, blank=True,
|
||||||
|
)
|
||||||
|
event = models.ForeignKey(
|
||||||
|
'pretixbase.Event',
|
||||||
|
on_delete=models.SET_NULL, # todo think, only for non-queued!
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
null=True, blank=True,
|
||||||
|
)
|
||||||
|
order = models.ForeignKey(
|
||||||
|
'pretixbase.Order',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
null=True, blank=True,
|
||||||
|
)
|
||||||
|
orderposition = models.ForeignKey(
|
||||||
|
'pretixbase.OrderPosition',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
null=True, blank=True,
|
||||||
|
)
|
||||||
|
customer = models.ForeignKey(
|
||||||
|
'pretixbase.Customer',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
null=True, blank=True,
|
||||||
|
)
|
||||||
|
user = models.ForeignKey(
|
||||||
|
'pretixbase.User',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
null=True, blank=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
subject = models.TextField()
|
||||||
|
body_plain = models.TextField()
|
||||||
|
body_html = models.TextField()
|
||||||
|
sender = models.CharField(max_length=500)
|
||||||
|
headers = models.JSONField(default=dict)
|
||||||
|
to = models.JSONField(default=list)
|
||||||
|
cc = models.JSONField(default=list)
|
||||||
|
bcc = models.JSONField(default=list)
|
||||||
|
|
||||||
|
should_attach_invoices = models.ManyToManyField(
|
||||||
|
'pretixbase.Invoice',
|
||||||
|
related_name='outgoing_mails'
|
||||||
|
)
|
||||||
|
should_attach_tickets = models.BooleanField(default=False)
|
||||||
|
should_attach_ical = models.BooleanField(default=False)
|
||||||
|
should_attach_cached_files = models.ManyToManyField(
|
||||||
|
'pretixbase.CachedFile',
|
||||||
|
related_name='outgoing_mails',
|
||||||
|
)
|
||||||
|
should_attach_other_files = models.JSONField(default=list)
|
||||||
|
|
||||||
|
actual_attachments = models.JSONField(default=list)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ('-created',)
|
||||||
|
|
||||||
|
def get_mail_backend(self):
|
||||||
|
if self.event:
|
||||||
|
return self.event.get_mail_backend()
|
||||||
|
elif self.organizer:
|
||||||
|
return self.organizer.get_mail_backend()
|
||||||
|
else:
|
||||||
|
return get_connection(fail_silently=False)
|
||||||
|
|
||||||
|
def scope_manager(self):
|
||||||
|
if self.organizer:
|
||||||
|
return scope(organizer=self.organizer) # noqa
|
||||||
|
else:
|
||||||
|
return scopes_disabled() # noqa
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.orderposition_id and not self.order_id:
|
||||||
|
self.order = self.orderposition.order
|
||||||
|
if self.order_id and not self.event_id:
|
||||||
|
self.event = self.order.event
|
||||||
|
if self.event_id and not self.organizer_id:
|
||||||
|
self.organizer = self.event.organizer
|
||||||
|
if self.customer_id and not self.organizer_id:
|
||||||
|
self.organizer = self.customer.organizer
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
|
def log_parameters(self):
|
||||||
|
if self.order:
|
||||||
|
error_log_action_type = 'pretix.event.order.email.error'
|
||||||
|
log_target = self.order
|
||||||
|
elif self.customer:
|
||||||
|
error_log_action_type = 'pretix.customer.email.error'
|
||||||
|
log_target = self.customer
|
||||||
|
elif self.user:
|
||||||
|
error_log_action_type = 'pretix.user.email.error'
|
||||||
|
log_target = self.user
|
||||||
|
else:
|
||||||
|
error_log_action_type = 'pretix.email.error'
|
||||||
|
log_target = None
|
||||||
|
return log_target, error_log_action_type
|
||||||
@@ -1162,9 +1162,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 (
|
from pretix.base.services.mail import mail, render_mail
|
||||||
SendMailException, 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
|
||||||
@@ -1174,35 +1172,31 @@ class Order(LockModel, LoggedModel):
|
|||||||
if position and position.attendee_email:
|
if position and position.attendee_email:
|
||||||
recipient = position.attendee_email
|
recipient = position.attendee_email
|
||||||
|
|
||||||
try:
|
email_content = render_mail(template, context)
|
||||||
email_content = render_mail(template, context)
|
subject = format_map(subject, context)
|
||||||
subject = format_map(subject, context)
|
mail(
|
||||||
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,
|
)
|
||||||
)
|
self.log_action(
|
||||||
except SendMailException:
|
log_entry_type,
|
||||||
raise
|
user=user,
|
||||||
else:
|
auth=auth,
|
||||||
self.log_action(
|
data={
|
||||||
log_entry_type,
|
'subject': subject,
|
||||||
user=user,
|
'message': email_content,
|
||||||
auth=auth,
|
'position': position.positionid if position else None,
|
||||||
data={
|
'recipient': recipient,
|
||||||
'subject': subject,
|
'invoices': [i.pk for i in invoices] if invoices else [],
|
||||||
'message': email_content,
|
'attach_tickets': attach_tickets,
|
||||||
'position': position.positionid if position else None,
|
'attach_ical': attach_ical,
|
||||||
'recipient': recipient,
|
'attach_other_files': attach_other_files,
|
||||||
'invoices': [i.pk for i in invoices] if invoices else [],
|
'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files 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):
|
||||||
@@ -1984,40 +1978,30 @@ class OrderPayment(models.Model):
|
|||||||
transmit_invoice.apply_async(args=(self.order.event_id, invoice.pk, False))
|
transmit_invoice.apply_async(args=(self.order.event_id, invoice.pk, False))
|
||||||
|
|
||||||
def _send_paid_mail_attendee(self, position, user):
|
def _send_paid_mail_attendee(self, position, user):
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
|
|
||||||
with language(self.order.locale, self.order.event.settings.region):
|
with language(self.order.locale, self.order.event.settings.region):
|
||||||
email_template = self.order.event.settings.mail_text_order_paid_attendee
|
email_template = self.order.event.settings.mail_text_order_paid_attendee
|
||||||
email_subject = self.order.event.settings.mail_subject_order_paid_attendee
|
email_subject = self.order.event.settings.mail_subject_order_paid_attendee
|
||||||
email_context = get_email_context(event=self.order.event, order=self.order, position=position)
|
email_context = get_email_context(event=self.order.event, order=self.order, position=position)
|
||||||
try:
|
position.send_mail(
|
||||||
position.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.order_paid', user,
|
||||||
'pretix.event.order.email.order_paid', user,
|
invoices=[],
|
||||||
invoices=[],
|
attach_tickets=True,
|
||||||
attach_tickets=True,
|
attach_ical=self.order.event.settings.mail_attach_ical
|
||||||
attach_ical=self.order.event.settings.mail_attach_ical
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order paid email could not be sent')
|
|
||||||
|
|
||||||
def _send_paid_mail(self, invoice, user, mail_text):
|
def _send_paid_mail(self, invoice, user, mail_text):
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
|
|
||||||
with language(self.order.locale, self.order.event.settings.region):
|
with language(self.order.locale, self.order.event.settings.region):
|
||||||
email_template = self.order.event.settings.mail_text_order_paid
|
email_template = self.order.event.settings.mail_text_order_paid
|
||||||
email_subject = self.order.event.settings.mail_subject_order_paid
|
email_subject = self.order.event.settings.mail_subject_order_paid
|
||||||
email_context = get_email_context(event=self.order.event, order=self.order, payment_info=mail_text)
|
email_context = get_email_context(event=self.order.event, order=self.order, payment_info=mail_text)
|
||||||
try:
|
self.order.send_mail(
|
||||||
self.order.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.order_paid', user,
|
||||||
'pretix.event.order.email.order_paid', user,
|
invoices=[invoice] if invoice else [],
|
||||||
invoices=[invoice] if invoice else [],
|
attach_tickets=True,
|
||||||
attach_tickets=True,
|
attach_ical=self.order.event.settings.mail_attach_ical
|
||||||
attach_ical=self.order.event.settings.mail_attach_ical
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order paid email could not be sent')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def refunded_amount(self):
|
def refunded_amount(self):
|
||||||
@@ -2835,45 +2819,39 @@ 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 (
|
from pretix.base.services.mail import mail, render_mail
|
||||||
SendMailException, 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
|
||||||
try:
|
email_content = render_mail(template, context)
|
||||||
email_content = render_mail(template, context)
|
subject = format_map(subject, context)
|
||||||
subject = format_map(subject, context)
|
mail(
|
||||||
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,
|
invoices=invoices,
|
||||||
invoices=invoices,
|
attach_tickets=attach_tickets,
|
||||||
attach_tickets=attach_tickets,
|
attach_ical=attach_ical,
|
||||||
attach_ical=attach_ical,
|
attach_other_files=attach_other_files,
|
||||||
attach_other_files=attach_other_files,
|
)
|
||||||
)
|
self.order.log_action(
|
||||||
except SendMailException:
|
log_entry_type,
|
||||||
raise
|
user=user,
|
||||||
else:
|
auth=auth,
|
||||||
self.order.log_action(
|
data={
|
||||||
log_entry_type,
|
'subject': subject,
|
||||||
user=user,
|
'message': email_content,
|
||||||
auth=auth,
|
'recipient': recipient,
|
||||||
data={
|
'invoices': [i.pk for i in invoices] if invoices else [],
|
||||||
'subject': subject,
|
'attach_tickets': attach_tickets,
|
||||||
'message': email_content,
|
'attach_ical': attach_ical,
|
||||||
'recipient': recipient,
|
'attach_other_files': attach_other_files,
|
||||||
'invoices': [i.pk for i in invoices] if invoices else [],
|
'attach_cached_files': [],
|
||||||
'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):
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ 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 SendMailException, mail, render_mail
|
from pretix.base.services.mail import mail, render_mail
|
||||||
|
|
||||||
from ...helpers.format import format_map
|
from ...helpers.format import format_map
|
||||||
from ...helpers.names import build_name
|
from ...helpers.names import build_name
|
||||||
@@ -265,34 +265,30 @@ 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
|
||||||
|
|
||||||
try:
|
email_content = render_mail(template, context)
|
||||||
email_content = render_mail(template, context)
|
subject = format_map(subject, context)
|
||||||
subject = format_map(subject, context)
|
mail(
|
||||||
mail(
|
recipient, subject, template, context,
|
||||||
recipient, subject, template, context,
|
self.event,
|
||||||
self.event,
|
self.locale,
|
||||||
self.locale,
|
headers=headers,
|
||||||
headers=headers,
|
sender=sender,
|
||||||
sender=sender,
|
auto_email=auto_email,
|
||||||
auto_email=auto_email,
|
attach_other_files=attach_other_files,
|
||||||
attach_other_files=attach_other_files,
|
attach_cached_files=attach_cached_files,
|
||||||
attach_cached_files=attach_cached_files,
|
)
|
||||||
)
|
self.log_action(
|
||||||
except SendMailException:
|
log_entry_type,
|
||||||
raise
|
user=user,
|
||||||
else:
|
auth=auth,
|
||||||
self.log_action(
|
data={
|
||||||
log_entry_type,
|
'subject': subject,
|
||||||
user=user,
|
'message': email_content,
|
||||||
auth=auth,
|
'recipient': recipient,
|
||||||
data={
|
'attach_other_files': attach_other_files,
|
||||||
'subject': subject,
|
'attach_cached_files': [cf.filename for cf in attach_cached_files] if attach_cached_files else [],
|
||||||
'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):
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ from pretix.base.models import (
|
|||||||
SubEvent, TaxRule, User, WaitingListEntry,
|
SubEvent, TaxRule, User, WaitingListEntry,
|
||||||
)
|
)
|
||||||
from pretix.base.services.locking import LockTimeoutException
|
from pretix.base.services.locking import LockTimeoutException
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import mail
|
||||||
from pretix.base.services.orders import (
|
from pretix.base.services.orders import (
|
||||||
OrderChangeManager, OrderError, _cancel_order, _try_auto_refund,
|
OrderChangeManager, OrderError, _cancel_order, _try_auto_refund,
|
||||||
)
|
)
|
||||||
@@ -51,17 +51,14 @@ logger = logging.getLogger(__name__)
|
|||||||
def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent):
|
def _send_wle_mail(wle: WaitingListEntry, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent):
|
||||||
with language(wle.locale, wle.event.settings.region):
|
with language(wle.locale, wle.event.settings.region):
|
||||||
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)
|
||||||
try:
|
mail(
|
||||||
mail(
|
wle.email,
|
||||||
wle.email,
|
format_map(subject, email_context),
|
||||||
format_map(subject, email_context),
|
message,
|
||||||
message,
|
email_context,
|
||||||
email_context,
|
wle.event,
|
||||||
wle.event,
|
locale=wle.locale
|
||||||
locale=wle.locale
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Waiting list canceled email could not be sent')
|
|
||||||
|
|
||||||
|
|
||||||
def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent,
|
def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, subevent: SubEvent,
|
||||||
@@ -75,14 +72,11 @@ 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)
|
real_subject = format_map(subject, email_context)
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
real_subject, message, email_context,
|
||||||
real_subject, message, email_context,
|
'pretix.event.order.email.event_canceled',
|
||||||
'pretix.event.order.email.event_canceled',
|
user,
|
||||||
user,
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order canceled email could not be sent')
|
|
||||||
|
|
||||||
for p in positions:
|
for p in positions:
|
||||||
if subevent and p.subevent_id != subevent.id:
|
if subevent and p.subevent_id != subevent.id:
|
||||||
@@ -95,15 +89,12 @@ def _send_mail(order: Order, subject: LazyI18nString, message: LazyI18nString, s
|
|||||||
refund_amount=refund_amount,
|
refund_amount=refund_amount,
|
||||||
position_or_address=p,
|
position_or_address=p,
|
||||||
order=order, position=p)
|
order=order, position=p)
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
real_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
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order canceled email could not be sent to attendee')
|
|
||||||
|
|
||||||
|
|
||||||
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
@app.task(base=ProfiledEventTask, bind=True, max_retries=5, default_retry_delay=1, throws=(OrderError,))
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import smtplib
|
|||||||
import warnings
|
import warnings
|
||||||
from email.mime.image import MIMEImage
|
from email.mime.image import MIMEImage
|
||||||
from email.utils import formataddr
|
from email.utils import formataddr
|
||||||
from typing import Any, Dict, List, Sequence, Union
|
from typing import Any, Dict, Sequence, Union
|
||||||
from urllib.parse import urljoin, urlparse
|
from urllib.parse import urljoin, urlparse
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
@@ -52,16 +52,13 @@ from celery import chain
|
|||||||
from celery.exceptions import MaxRetriesExceededError
|
from celery.exceptions import MaxRetriesExceededError
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.storage import default_storage
|
from django.core.files.storage import default_storage
|
||||||
from django.core.mail import (
|
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart
|
||||||
EmailMultiAlternatives, SafeMIMEMultipart, get_connection,
|
|
||||||
)
|
|
||||||
from django.core.mail.message import SafeMIMEText
|
from django.core.mail.message import SafeMIMEText
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.template.loader import get_template
|
from django.template.loader import get_template
|
||||||
from django.utils.html import escape
|
from django.utils.html import escape
|
||||||
from django.utils.timezone import now, override
|
from django.utils.timezone import now, override
|
||||||
from django.utils.translation import gettext as _, pgettext
|
from django.utils.translation import gettext as _, pgettext
|
||||||
from django_scopes import scope, scopes_disabled
|
|
||||||
from i18nfield.strings import LazyI18nString
|
from i18nfield.strings import LazyI18nString
|
||||||
from text_unidecode import unidecode
|
from text_unidecode import unidecode
|
||||||
|
|
||||||
@@ -69,13 +66,15 @@ from pretix.base.email import ClassicMailRenderer
|
|||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CachedFile, Customer, Event, Invoice, InvoiceAddress, Order, OrderPosition,
|
CachedFile, Customer, Event, Invoice, InvoiceAddress, Order, OrderPosition,
|
||||||
Organizer, User,
|
Organizer,
|
||||||
)
|
)
|
||||||
|
from pretix.base.models.mail import OutgoingMail
|
||||||
from pretix.base.services.invoices import invoice_pdf_task
|
from pretix.base.services.invoices import invoice_pdf_task
|
||||||
from pretix.base.services.tasks import TransactionAwareTask
|
from pretix.base.services.tasks import TransactionAwareTask
|
||||||
from pretix.base.services.tickets import get_tickets_for_order
|
from pretix.base.services.tickets import get_tickets_for_order
|
||||||
from pretix.base.signals import email_filter, global_email_filter
|
from pretix.base.signals import email_filter, global_email_filter
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.format import SafeFormatter, format_map
|
from pretix.helpers.format import SafeFormatter, format_map
|
||||||
from pretix.helpers.hierarkey import clean_filename
|
from pretix.helpers.hierarkey import clean_filename
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
@@ -92,6 +91,9 @@ class TolerantDict(dict):
|
|||||||
|
|
||||||
|
|
||||||
class SendMailException(Exception):
|
class SendMailException(Exception):
|
||||||
|
"""
|
||||||
|
Deprecated, not thrown any more.
|
||||||
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
@@ -203,161 +205,119 @@ def mail(email: Union[str, Sequence[str]], subject: str, template: Union[str, La
|
|||||||
if no_order_links and not plain_text_only:
|
if no_order_links and not plain_text_only:
|
||||||
raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
|
raise ValueError('If you set no_order_links, you also need to set plain_text_only.')
|
||||||
|
|
||||||
|
settings_holder = event or organizer
|
||||||
|
|
||||||
headers = headers or {}
|
headers = headers or {}
|
||||||
if auto_email:
|
if auto_email:
|
||||||
headers['X-Auto-Response-Suppress'] = 'OOF, NRN, AutoReply, RN'
|
headers['X-Auto-Response-Suppress'] = 'OOF, NRN, AutoReply, RN'
|
||||||
headers['Auto-Submitted'] = 'auto-generated'
|
headers['Auto-Submitted'] = 'auto-generated'
|
||||||
headers.setdefault('X-Mailer', 'pretix')
|
headers.setdefault('X-Mailer', 'pretix')
|
||||||
|
|
||||||
|
bcc = list(bcc or [])
|
||||||
|
if settings_holder and settings_holder.settings.mail_bcc:
|
||||||
|
for bcc_mail in settings_holder.settings.mail_bcc.split(','):
|
||||||
|
bcc.append(bcc_mail.strip())
|
||||||
|
|
||||||
|
if (settings_holder
|
||||||
|
and settings_holder.settings.mail_from in (settings.DEFAULT_FROM_EMAIL, settings.MAIL_FROM_ORGANIZERS)
|
||||||
|
and settings_holder.settings.contact_mail and not headers.get('Reply-To')):
|
||||||
|
headers['Reply-To'] = settings_holder.settings.contact_mail
|
||||||
|
|
||||||
|
if settings_holder:
|
||||||
|
timezone = settings_holder.timezone
|
||||||
|
elif user:
|
||||||
|
timezone = ZoneInfo(user.timezone)
|
||||||
|
else:
|
||||||
|
timezone = ZoneInfo(settings.TIME_ZONE)
|
||||||
|
|
||||||
|
if event and attach_tickets and not event.settings.mail_attach_tickets:
|
||||||
|
attach_tickets = False
|
||||||
|
|
||||||
with language(locale):
|
with language(locale):
|
||||||
if isinstance(context, dict) and order:
|
if isinstance(context, dict) and order:
|
||||||
try:
|
_autoextend_context(context, order)
|
||||||
context.update({
|
|
||||||
'invoice_name': order.invoice_address.name,
|
|
||||||
'invoice_company': order.invoice_address.company
|
|
||||||
})
|
|
||||||
except InvoiceAddress.DoesNotExist:
|
|
||||||
context.update({
|
|
||||||
'invoice_name': '',
|
|
||||||
'invoice_company': ''
|
|
||||||
})
|
|
||||||
renderer = ClassicMailRenderer(None, organizer)
|
|
||||||
content_plain = body_plain = render_mail(template, context)
|
|
||||||
subject = str(subject).format_map(TolerantDict(context))
|
|
||||||
sender = (
|
|
||||||
sender or
|
|
||||||
(event.settings.get('mail_from') if event else None) or
|
|
||||||
(organizer.settings.get('mail_from') if organizer else None) or
|
|
||||||
settings.MAIL_FROM
|
|
||||||
)
|
|
||||||
if event:
|
|
||||||
sender_name = clean_sender_name(event.settings.mail_from_name or str(event.name))
|
|
||||||
sender = formataddr((sender_name, sender))
|
|
||||||
elif organizer:
|
|
||||||
sender_name = clean_sender_name(organizer.settings.mail_from_name or str(organizer.name))
|
|
||||||
sender = formataddr((sender_name, sender))
|
|
||||||
else:
|
|
||||||
sender = formataddr((clean_sender_name(settings.PRETIX_INSTANCE_NAME), sender))
|
|
||||||
|
|
||||||
subject = raw_subject = str(subject).replace('\n', ' ').replace('\r', '')[:900]
|
|
||||||
signature = ""
|
|
||||||
|
|
||||||
bcc = list(bcc or [])
|
|
||||||
|
|
||||||
settings_holder = event or organizer
|
|
||||||
|
|
||||||
if event:
|
|
||||||
timezone = event.timezone
|
|
||||||
elif user:
|
|
||||||
timezone = ZoneInfo(user.timezone)
|
|
||||||
elif organizer:
|
|
||||||
timezone = organizer.timezone
|
|
||||||
else:
|
|
||||||
timezone = ZoneInfo(settings.TIME_ZONE)
|
|
||||||
|
|
||||||
|
# Build raw content
|
||||||
|
content_plain = render_mail(template, context)
|
||||||
if settings_holder:
|
if settings_holder:
|
||||||
if settings_holder.settings.mail_bcc:
|
|
||||||
for bcc_mail in settings_holder.settings.mail_bcc.split(','):
|
|
||||||
bcc.append(bcc_mail.strip())
|
|
||||||
|
|
||||||
if settings_holder.settings.mail_from in (settings.DEFAULT_FROM_EMAIL, settings.MAIL_FROM_ORGANIZERS) \
|
|
||||||
and settings_holder.settings.contact_mail and not headers.get('Reply-To'):
|
|
||||||
headers['Reply-To'] = settings_holder.settings.contact_mail
|
|
||||||
|
|
||||||
subject = prefix_subject(settings_holder, subject)
|
|
||||||
|
|
||||||
body_plain += "\r\n\r\n-- \r\n"
|
|
||||||
|
|
||||||
signature = str(settings_holder.settings.get('mail_text_signature'))
|
signature = str(settings_holder.settings.get('mail_text_signature'))
|
||||||
if signature:
|
else:
|
||||||
signature = signature.format(event=event.name if event else '')
|
signature = ""
|
||||||
body_plain += signature
|
|
||||||
body_plain += "\r\n\r\n-- \r\n"
|
|
||||||
|
|
||||||
if event:
|
|
||||||
renderer = event.get_html_mail_renderer()
|
|
||||||
|
|
||||||
if order and order.testmode:
|
|
||||||
subject = "[TESTMODE] " + subject
|
|
||||||
|
|
||||||
if order and position and not no_order_links:
|
|
||||||
body_plain += _(
|
|
||||||
"You are receiving this email because someone placed an order for {event} for you."
|
|
||||||
).format(event=event.name)
|
|
||||||
body_plain += "\r\n"
|
|
||||||
body_plain += _(
|
|
||||||
"You can view your order details at the following URL:\n{orderurl}."
|
|
||||||
).replace("\n", "\r\n").format(
|
|
||||||
event=event.name, orderurl=build_absolute_uri(
|
|
||||||
order.event, 'presale:event.order.position', kwargs={
|
|
||||||
'order': order.code,
|
|
||||||
'secret': position.web_secret,
|
|
||||||
'position': position.positionid,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif order and not no_order_links:
|
|
||||||
body_plain += _(
|
|
||||||
"You are receiving this email because you placed an order for {event}."
|
|
||||||
).format(event=event.name)
|
|
||||||
body_plain += "\r\n"
|
|
||||||
body_plain += _(
|
|
||||||
"You can view your order details at the following URL:\n{orderurl}."
|
|
||||||
).replace("\n", "\r\n").format(
|
|
||||||
event=event.name, orderurl=build_absolute_uri(
|
|
||||||
order.event, 'presale:event.order.open', kwargs={
|
|
||||||
'order': order.code,
|
|
||||||
'secret': order.secret,
|
|
||||||
'hash': order.email_confirm_secret()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
body_plain += "\r\n"
|
|
||||||
|
|
||||||
with override(timezone):
|
|
||||||
try:
|
|
||||||
if plain_text_only:
|
|
||||||
body_html = None
|
|
||||||
elif 'context' in inspect.signature(renderer.render).parameters:
|
|
||||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
|
|
||||||
elif 'position' in inspect.signature(renderer.render).parameters:
|
|
||||||
# Backwards compatibility
|
|
||||||
warnings.warn('Email renderer called without context argument because context argument is not '
|
|
||||||
'supported.',
|
|
||||||
DeprecationWarning)
|
|
||||||
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
|
||||||
else:
|
|
||||||
# Backwards compatibility
|
|
||||||
warnings.warn('Email renderer called without position argument because position argument is not '
|
|
||||||
'supported.',
|
|
||||||
DeprecationWarning)
|
|
||||||
body_html = renderer.render(content_plain, signature, raw_subject, order)
|
|
||||||
except:
|
|
||||||
logger.exception('Could not render HTML body')
|
|
||||||
body_html = None
|
|
||||||
|
|
||||||
|
# Build full plain-text body
|
||||||
|
body_plain = _wrap_plain_body(content_plain, signature, event, order, position, no_order_links)
|
||||||
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
body_plain = format_map(body_plain, context, mode=SafeFormatter.MODE_RICH_TO_PLAIN)
|
||||||
|
|
||||||
send_task = mail_send_task.si(
|
# Build subject
|
||||||
|
subject = str(subject).format_map(TolerantDict(context))
|
||||||
|
subject = raw_subject = subject.replace('\n', ' ').replace('\r', '')[:900]
|
||||||
|
if settings_holder:
|
||||||
|
subject = prefix_subject(settings_holder, subject)
|
||||||
|
if (order and order.testmode) or (not order and event and event.testmode):
|
||||||
|
subject = "[TESTMODE] " + subject
|
||||||
|
|
||||||
|
# Build sender
|
||||||
|
sender = _full_sender(sender, event, organizer)
|
||||||
|
|
||||||
|
# Build HTML body
|
||||||
|
if plain_text_only:
|
||||||
|
body_html = None
|
||||||
|
else:
|
||||||
|
if event:
|
||||||
|
renderer = event.get_html_mail_renderer()
|
||||||
|
else:
|
||||||
|
renderer = ClassicMailRenderer(None, organizer)
|
||||||
|
|
||||||
|
with override(timezone):
|
||||||
|
try:
|
||||||
|
if 'context' in inspect.signature(renderer.render).parameters:
|
||||||
|
body_html = renderer.render(content_plain, signature, raw_subject, order, position, context)
|
||||||
|
elif 'position' in inspect.signature(renderer.render).parameters:
|
||||||
|
# Backwards compatibility
|
||||||
|
warnings.warn('Email renderer called without context argument because context argument is not '
|
||||||
|
'supported.',
|
||||||
|
DeprecationWarning)
|
||||||
|
body_html = renderer.render(content_plain, signature, raw_subject, order, position)
|
||||||
|
else:
|
||||||
|
# Backwards compatibility
|
||||||
|
warnings.warn('Email renderer called without position argument because position argument is not '
|
||||||
|
'supported.',
|
||||||
|
DeprecationWarning)
|
||||||
|
body_html = renderer.render(content_plain, signature, raw_subject, order)
|
||||||
|
except:
|
||||||
|
logger.exception('Could not render HTML body')
|
||||||
|
body_html = None
|
||||||
|
|
||||||
|
m = OutgoingMail.objects.create(
|
||||||
|
organizer=organizer,
|
||||||
|
event=event,
|
||||||
|
order=order,
|
||||||
|
orderposition=position,
|
||||||
|
customer=customer,
|
||||||
|
user=user,
|
||||||
to=[email] if isinstance(email, str) else list(email),
|
to=[email] if isinstance(email, str) else list(email),
|
||||||
cc=cc,
|
cc=cc or [],
|
||||||
bcc=bcc,
|
bcc=bcc or [],
|
||||||
subject=subject,
|
subject=subject,
|
||||||
body=body_plain,
|
body_plain=body_plain,
|
||||||
html=body_html,
|
body_html=body_html,
|
||||||
sender=sender,
|
sender=sender,
|
||||||
event=event.id if event else None,
|
|
||||||
headers=headers,
|
headers=headers,
|
||||||
invoices=[i.pk for i in invoices] if invoices and not position else [],
|
should_attach_tickets=attach_tickets,
|
||||||
order=order.pk if order else None,
|
should_attach_ical=attach_ical,
|
||||||
position=position.pk if position else None,
|
should_attach_other_files=attach_other_files or [],
|
||||||
attach_tickets=attach_tickets,
|
)
|
||||||
attach_ical=attach_ical,
|
if invoices and not position:
|
||||||
user=user.pk if user else None,
|
m.should_attach_invoices.add(*invoices)
|
||||||
organizer=organizer.pk if organizer else None,
|
if attach_cached_files:
|
||||||
customer=customer.pk if customer else None,
|
for cf in attach_cached_files:
|
||||||
attach_cached_files=[(cf.id if isinstance(cf, CachedFile) else cf) for cf in attach_cached_files] if attach_cached_files else [],
|
if not isinstance(cf, CachedFile):
|
||||||
attach_other_files=attach_other_files,
|
m.should_attach_cached_files.add(CachedFile.objects.get(pk=cf))
|
||||||
|
else:
|
||||||
|
m.should_attach_cached_files.add(cf)
|
||||||
|
|
||||||
|
send_task = mail_send_task.si(
|
||||||
|
outgoing_mail=m.id
|
||||||
)
|
)
|
||||||
|
|
||||||
if invoices:
|
if invoices:
|
||||||
@@ -392,176 +352,177 @@ class CustomEmail(EmailMultiAlternatives):
|
|||||||
|
|
||||||
|
|
||||||
@app.task(base=TransactionAwareTask, bind=True, acks_late=True)
|
@app.task(base=TransactionAwareTask, bind=True, acks_late=True)
|
||||||
def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: str, sender: str,
|
def mail_send_task(self, *args, outgoing_mail: int) -> bool:
|
||||||
event: int = None, position: int = None, headers: dict = None, cc: List[str] = None, bcc: List[str] = None,
|
with transaction.atomic():
|
||||||
invoices: List[int] = None, order: int = None, attach_tickets=False, user=None,
|
outgoing_mail = OutgoingMail.objects.select_for_update(of=OF_SELF).get(pk=outgoing_mail)
|
||||||
organizer=None, customer=None, attach_ical=False, attach_cached_files: List[int] = None,
|
if outgoing_mail.status == OutgoingMail.STATUS_INFLIGHT:
|
||||||
attach_other_files: List[str] = None) -> bool:
|
logger.info("Ignoring job for inflight email")
|
||||||
email = CustomEmail(subject, body, sender, to=to, cc=cc, bcc=bcc, headers=headers)
|
return False
|
||||||
if html is not None:
|
elif outgoing_mail.status in (OutgoingMail.STATUS_SENT, OutgoingMail.STATUS_FAILED):
|
||||||
|
logger.info(f"Ignoring job for email in final state {outgoing_mail.status}")
|
||||||
|
return False
|
||||||
|
outgoing_mail.status = OutgoingMail.STATUS_INFLIGHT
|
||||||
|
outgoing_mail.save(update_fields=["status"])
|
||||||
|
|
||||||
|
email = CustomEmail(
|
||||||
|
subject=outgoing_mail.subject,
|
||||||
|
body=outgoing_mail.body_plain,
|
||||||
|
from_email=outgoing_mail.sender,
|
||||||
|
to=outgoing_mail.to,
|
||||||
|
cc=outgoing_mail.cc,
|
||||||
|
bcc=outgoing_mail.bcc,
|
||||||
|
headers=outgoing_mail.headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Rewrite all <img> tags from real URLs or data URLs to inline attachments refered to by content ID
|
||||||
|
if outgoing_mail.body_html is not None:
|
||||||
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
|
html_message = SafeMIMEMultipart(_subtype='related', encoding=settings.DEFAULT_CHARSET)
|
||||||
html_with_cid, cid_images = replace_images_with_cid_paths(html)
|
html_with_cid, cid_images = replace_images_with_cid_paths(outgoing_mail.body_html)
|
||||||
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
|
html_message.attach(SafeMIMEText(html_with_cid, 'html', settings.DEFAULT_CHARSET))
|
||||||
attach_cid_images(html_message, cid_images, verify_ssl=True)
|
attach_cid_images(html_message, cid_images, verify_ssl=True)
|
||||||
email.attach_alternative(html_message, "multipart/related")
|
email.attach_alternative(html_message, "multipart/related")
|
||||||
|
|
||||||
log_target = None
|
log_target, error_log_action_type = outgoing_mail.log_parameters()
|
||||||
|
invoices_attached = []
|
||||||
|
actual_attachments = []
|
||||||
|
|
||||||
if user:
|
with outgoing_mail.scope_manager():
|
||||||
user = User.objects.get(pk=user)
|
# Attach tickets
|
||||||
error_log_action_type = 'pretix.user.email.error'
|
if outgoing_mail.should_attach_tickets and outgoing_mail.order:
|
||||||
log_target = user
|
with language(outgoing_mail.order.locale, outgoing_mail.event.settings.region):
|
||||||
|
args = []
|
||||||
if event:
|
attach_size = 0
|
||||||
with scopes_disabled():
|
for name, ct in get_tickets_for_order(outgoing_mail.order, base_position=outgoing_mail.orderposition):
|
||||||
event = Event.objects.get(id=event)
|
|
||||||
organizer = event.organizer
|
|
||||||
backend = event.get_mail_backend()
|
|
||||||
cm = lambda: scope(organizer=event.organizer) # noqa
|
|
||||||
elif organizer:
|
|
||||||
with scopes_disabled():
|
|
||||||
organizer = Organizer.objects.get(id=organizer)
|
|
||||||
backend = organizer.get_mail_backend()
|
|
||||||
cm = lambda: scope(organizer=organizer) # noqa
|
|
||||||
else:
|
|
||||||
backend = get_connection(fail_silently=False)
|
|
||||||
cm = lambda: scopes_disabled() # noqa
|
|
||||||
|
|
||||||
with cm():
|
|
||||||
if customer:
|
|
||||||
customer = Customer.objects.get(pk=customer)
|
|
||||||
if not user:
|
|
||||||
error_log_action_type = 'pretix.customer.email.error'
|
|
||||||
log_target = customer
|
|
||||||
|
|
||||||
if event:
|
|
||||||
if order:
|
|
||||||
try:
|
|
||||||
order = event.orders.get(pk=order)
|
|
||||||
error_log_action_type = 'pretix.event.order.email.error'
|
|
||||||
log_target = order
|
|
||||||
except Order.DoesNotExist:
|
|
||||||
order = None
|
|
||||||
else:
|
|
||||||
with language(order.locale, event.settings.region):
|
|
||||||
if not event.settings.mail_attach_tickets:
|
|
||||||
attach_tickets = False
|
|
||||||
if position:
|
|
||||||
try:
|
|
||||||
position = order.positions.get(pk=position)
|
|
||||||
except OrderPosition.DoesNotExist:
|
|
||||||
attach_tickets = False
|
|
||||||
if attach_tickets:
|
|
||||||
args = []
|
|
||||||
attach_size = 0
|
|
||||||
for name, ct in get_tickets_for_order(order, base_position=position):
|
|
||||||
try:
|
|
||||||
content = ct.file.read()
|
|
||||||
args.append((name, content, ct.type))
|
|
||||||
attach_size += len(content)
|
|
||||||
except:
|
|
||||||
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out
|
|
||||||
# why (probably some race condition with ticket cache invalidation?), so retry later.
|
|
||||||
try:
|
|
||||||
self.retry(max_retries=5, countdown=60)
|
|
||||||
except MaxRetriesExceededError:
|
|
||||||
# Well then, something is really wrong, let's send it without attachment before we
|
|
||||||
# don't sent at all
|
|
||||||
logger.exception('Could not attach invoice to email')
|
|
||||||
pass
|
|
||||||
|
|
||||||
if attach_size < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1:
|
|
||||||
# Do not attach more than (limit - 1 MB) in tickets (1MB space for invoice, email itself, …),
|
|
||||||
# it will bounce way to often.
|
|
||||||
for a in args:
|
|
||||||
try:
|
|
||||||
email.attach(*a)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
order.log_action(
|
|
||||||
'pretix.event.order.email.attachments.skipped',
|
|
||||||
data={
|
|
||||||
'subject': 'Attachments skipped',
|
|
||||||
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
|
|
||||||
'recipient': '',
|
|
||||||
'invoices': [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if attach_ical:
|
|
||||||
fname = re.sub('[^a-zA-Z0-9 ]', '-', unidecode(pgettext('attachment_filename', 'Calendar invite')))
|
|
||||||
for i, cal in enumerate(get_private_icals(event, [position] if position else order.positions.all())):
|
|
||||||
email.attach('{}{}.ics'.format(fname, f'-{i + 1}' if i > 0 else ''), cal.serialize(), 'text/calendar')
|
|
||||||
|
|
||||||
email = email_filter.send_chained(event, 'message', message=email, order=order, user=user)
|
|
||||||
|
|
||||||
invoices_sent = []
|
|
||||||
if invoices:
|
|
||||||
invoices = Invoice.objects.filter(pk__in=invoices)
|
|
||||||
for inv in invoices:
|
|
||||||
if inv.file:
|
|
||||||
try:
|
try:
|
||||||
# We try to give the invoice a more human-readable name, e.g. "Invoice_ABC-123.pdf" instead of
|
content = ct.file.read()
|
||||||
# just "ABC-123.pdf", but we only do so if our currently selected language allows to do this
|
args.append((name, content, ct.type))
|
||||||
# as ASCII text. For example, we would not want a "فاتورة_" prefix for our filename since this
|
attach_size += len(content)
|
||||||
# has shown to cause deliverability problems of the email and deliverability wins.
|
|
||||||
with language(order.locale if order else inv.locale, event.settings.region if event else None):
|
|
||||||
filename = pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf'
|
|
||||||
if not re.match("^[a-zA-Z0-9-_%./,&:# ]+$", filename):
|
|
||||||
filename = inv.number.replace(' ', '_') + '.pdf'
|
|
||||||
filename = re.sub("[^a-zA-Z0-9-_.]+", "_", filename)
|
|
||||||
with language(inv.order.locale):
|
|
||||||
email.attach(
|
|
||||||
filename,
|
|
||||||
inv.file.file.read(),
|
|
||||||
'application/pdf'
|
|
||||||
)
|
|
||||||
invoices_sent.append(inv)
|
|
||||||
except:
|
except:
|
||||||
logger.exception('Could not attach invoice to email')
|
# This sometimes fails e.g. with FileNotFoundError. We haven't been able to figure out
|
||||||
pass
|
# why (probably some race condition with ticket cache invalidation?), so retry later.
|
||||||
|
try:
|
||||||
|
self.retry(max_retries=5, countdown=60)
|
||||||
|
except MaxRetriesExceededError:
|
||||||
|
# Well then, something is really wrong, let's send it without attachment before we
|
||||||
|
# don't sent at all
|
||||||
|
logger.exception('Could not attach invoice to email')
|
||||||
|
pass
|
||||||
|
|
||||||
if attach_other_files:
|
if attach_size < settings.FILE_UPLOAD_MAX_SIZE_EMAIL_ATTACHMENT - 1:
|
||||||
for fname in attach_other_files:
|
# Do not attach more than (limit - 1 MB) in tickets (1MB space for invoice, email itself, …),
|
||||||
ftype, _ = mimetypes.guess_type(fname)
|
# it will bounce way to often.
|
||||||
data = default_storage.open(fname).read()
|
for a in args:
|
||||||
|
try:
|
||||||
|
email.attach(*a)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
outgoing_mail.order.log_action(
|
||||||
|
'pretix.event.order.email.attachments.skipped',
|
||||||
|
data={
|
||||||
|
'subject': 'Attachments skipped',
|
||||||
|
'message': 'Attachment have not been send because {} bytes are likely too large to arrive.'.format(attach_size),
|
||||||
|
'recipient': '',
|
||||||
|
'invoices': [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attach calendar files
|
||||||
|
if outgoing_mail.should_attach_ical and outgoing_mail.order:
|
||||||
|
fname = re.sub('[^a-zA-Z0-9 ]', '-', unidecode(pgettext('attachment_filename', 'Calendar invite')))
|
||||||
|
icals = get_private_icals(
|
||||||
|
outgoing_mail.event,
|
||||||
|
[outgoing_mail.orderposition] if outgoing_mail.orderposition else outgoing_mail.order.positions.all()
|
||||||
|
)
|
||||||
|
for i, cal in enumerate(icals):
|
||||||
|
name = '{}{}.ics'.format(fname, f'-{i + 1}' if i > 0 else '')
|
||||||
|
content = cal.serialize()
|
||||||
|
mimetype = 'text/calendar'
|
||||||
|
email.attach(name, content, mimetype)
|
||||||
|
|
||||||
|
for inv in outgoing_mail.should_attach_invoices.all():
|
||||||
|
if inv.file:
|
||||||
|
try:
|
||||||
|
# We try to give the invoice a more human-readable name, e.g. "Invoice_ABC-123.pdf" instead of
|
||||||
|
# just "ABC-123.pdf", but we only do so if our currently selected language allows to do this
|
||||||
|
# as ASCII text. For example, we would not want a "فاتورة_" prefix for our filename since this
|
||||||
|
# has shown to cause deliverability problems of the email and deliverability wins.
|
||||||
|
with language(outgoing_mail.order.locale if outgoing_mail.order else inv.locale, outgoing_mail.event.settings.region):
|
||||||
|
filename = pgettext('invoice', 'Invoice {num}').format(num=inv.number).replace(' ', '_') + '.pdf'
|
||||||
|
if not re.match("^[a-zA-Z0-9-_%./,&:# ]+$", filename):
|
||||||
|
filename = inv.number.replace(' ', '_') + '.pdf'
|
||||||
|
filename = re.sub("[^a-zA-Z0-9-_.]+", "_", filename)
|
||||||
|
content = inv.file.file.read()
|
||||||
|
with language(inv.order.locale):
|
||||||
|
email.attach(
|
||||||
|
filename,
|
||||||
|
content,
|
||||||
|
'application/pdf'
|
||||||
|
)
|
||||||
|
invoices_attached.append(inv)
|
||||||
|
except:
|
||||||
|
logger.exception('Could not attach invoice to email')
|
||||||
|
pass
|
||||||
|
|
||||||
|
for fname in outgoing_mail.should_attach_other_files:
|
||||||
|
ftype, _ = mimetypes.guess_type(fname)
|
||||||
|
data = default_storage.open(fname).read()
|
||||||
|
try:
|
||||||
|
email.attach(
|
||||||
|
clean_filename(os.path.basename(fname)),
|
||||||
|
data,
|
||||||
|
ftype
|
||||||
|
)
|
||||||
|
except:
|
||||||
|
logger.exception('Could not attach file to email')
|
||||||
|
pass
|
||||||
|
|
||||||
|
for cf in outgoing_mail.should_attach_cached_files.all():
|
||||||
|
if cf.file:
|
||||||
try:
|
try:
|
||||||
email.attach(
|
email.attach(
|
||||||
clean_filename(os.path.basename(fname)),
|
cf.filename,
|
||||||
data,
|
cf.file.file.read(),
|
||||||
ftype
|
cf.type,
|
||||||
)
|
)
|
||||||
except:
|
except:
|
||||||
logger.exception('Could not attach file to email')
|
logger.exception('Could not attach file to email')
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if attach_cached_files:
|
if outgoing_mail.event:
|
||||||
for cf in CachedFile.objects.filter(id__in=attach_cached_files):
|
with outgoing_mail.scope_manager():
|
||||||
if cf.file:
|
email = email_filter.send_chained(
|
||||||
try:
|
outgoing_mail.event, 'message', message=email, order=outgoing_mail.order, user=outgoing_mail.user
|
||||||
email.attach(
|
)
|
||||||
cf.filename,
|
|
||||||
cf.file.file.read(),
|
|
||||||
cf.type,
|
|
||||||
)
|
|
||||||
except:
|
|
||||||
logger.exception('Could not attach file to email')
|
|
||||||
pass
|
|
||||||
|
|
||||||
email = global_email_filter.send_chained(event, 'message', message=email, user=user, order=order,
|
email = global_email_filter.send_chained(
|
||||||
organizer=organizer, customer=customer)
|
outgoing_mail.event, 'message', message=email, user=outgoing_mail.user, order=outgoing_mail.order,
|
||||||
|
organizer=outgoing_mail.organizer, customer=outgoing_mail.customer
|
||||||
|
)
|
||||||
|
|
||||||
|
outgoing_mail.actual_attachments = [
|
||||||
|
{
|
||||||
|
"name": a[0],
|
||||||
|
"size": len(a[1]),
|
||||||
|
"type": a[2],
|
||||||
|
} for a in email.attachments
|
||||||
|
]
|
||||||
|
|
||||||
|
backend = outgoing_mail.get_mail_backend()
|
||||||
try:
|
try:
|
||||||
backend.send_messages([email])
|
backend.send_messages([email])
|
||||||
except (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused) as e:
|
except Exception as e:
|
||||||
if e.smtp_code in (101, 111, 421, 422, 431, 432, 442, 447, 452):
|
logger.exception('Error sending email')
|
||||||
if e.smtp_code == 432 and settings.HAS_REDIS:
|
retry_strategy = _retry_strategy(e)
|
||||||
# This is likely Microsoft Exchange Online which has a pretty bad rate limit of max. 3 concurrent
|
err, err_detail = _format_error(e)
|
||||||
# SMTP connections which is *easily* exceeded with many celery threads. Just retrying with exponential
|
|
||||||
# backoff won't be good enough if we have a lot of emails, instead we'll need to make sure our retry
|
outgoing_mail.error = err
|
||||||
# intervals scatter such that the email won't all be retried at the same time again and cause the
|
outgoing_mail.error_detail = err_detail
|
||||||
# same problem.
|
outgoing_mail.sent = now()
|
||||||
# See also https://docs.microsoft.com/en-us/exchange/troubleshoot/send-emails/smtp-submission-improvements
|
|
||||||
|
# Run retries
|
||||||
|
try:
|
||||||
|
if retry_strategy == "microsoft_concurrency" and settings.HAS_REDIS:
|
||||||
from django_redis import get_redis_connection
|
from django_redis import get_redis_connection
|
||||||
|
|
||||||
redis_key = "pretix_mail_retry_" + hashlib.sha1(f"{getattr(backend, 'username', '_')}@{getattr(backend, 'host', '_')}".encode()).hexdigest()
|
redis_key = "pretix_mail_retry_" + hashlib.sha1(f"{getattr(backend, 'username', '_')}@{getattr(backend, 'host', '_')}".encode()).hexdigest()
|
||||||
@@ -571,100 +532,59 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
|||||||
|
|
||||||
max_retries = 10
|
max_retries = 10
|
||||||
retry_after = min(30 + cnt * 10, 1800)
|
retry_after = min(30 + cnt * 10, 1800)
|
||||||
else:
|
|
||||||
# Most likely some other kind of temporary failure, retry again (but pretty soon)
|
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
|
||||||
|
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
|
||||||
|
self.retry(max_retries=max_retries, countdown=retry_after) # throws RetryException, ends function flow
|
||||||
|
elif retry_strategy in ("microsoft_concurrency", "quick"):
|
||||||
max_retries = 5
|
max_retries = 5
|
||||||
retry_after = [10, 30, 60, 300, 900, 900][self.request.retries]
|
retry_after = [10, 30, 60, 300, 900, 900][self.request.retries]
|
||||||
|
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
|
||||||
|
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
|
||||||
|
self.retry(max_retries=max_retries, countdown=retry_after) # throws RetryException, ends function flow
|
||||||
|
|
||||||
try:
|
elif retry_strategy == "slow":
|
||||||
self.retry(max_retries=max_retries, countdown=retry_after)
|
outgoing_mail.status = OutgoingMail.STATUS_AWAWITING_RETRY
|
||||||
except MaxRetriesExceededError:
|
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
|
||||||
if log_target:
|
self.retry(max_retries=5, countdown=[60, 300, 600, 1200, 1800, 1800][self.request.retries]) # throws RetryException, ends function flow
|
||||||
log_target.log_action(
|
|
||||||
error_log_action_type,
|
|
||||||
data={
|
|
||||||
'subject': 'SMTP code {}, max retries exceeded'.format(e.smtp_code),
|
|
||||||
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
|
|
||||||
'recipient': '',
|
|
||||||
'invoices': [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
logger.exception('Error sending email')
|
except MaxRetriesExceededError:
|
||||||
|
if log_target:
|
||||||
|
log_target.log_action(
|
||||||
|
error_log_action_type,
|
||||||
|
data={
|
||||||
|
'subject': f'{err} (max retries exceeded)',
|
||||||
|
'message': err_detail,
|
||||||
|
'recipient': '',
|
||||||
|
'invoices': [],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# If we reach this, it's a non-retryable error
|
||||||
|
outgoing_mail.status = OutgoingMail.STATUS_FAILED
|
||||||
|
outgoing_mail.sent = now()
|
||||||
|
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent"])
|
||||||
if log_target:
|
if log_target:
|
||||||
log_target.log_action(
|
log_target.log_action(
|
||||||
error_log_action_type,
|
error_log_action_type,
|
||||||
data={
|
data={
|
||||||
'subject': 'SMTP code {}'.format(e.smtp_code),
|
'subject': err,
|
||||||
'message': e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error),
|
'message': err_detail,
|
||||||
'recipient': '',
|
'recipient': '',
|
||||||
'invoices': [],
|
'invoices': [],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
return False
|
||||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
|
||||||
except smtplib.SMTPRecipientsRefused as e:
|
|
||||||
smtp_codes = [a[0] for a in e.recipients.values()]
|
|
||||||
|
|
||||||
if not any(c >= 500 for c in smtp_codes) or any(b'Message is too large' in a[1] for a in e.recipients.values()):
|
|
||||||
# This is not a permanent failure (mailbox full, service unavailable), retry later, but with large
|
|
||||||
# intervals. One would think that "Message is too lage" is a permanent failure, but apparently it is not.
|
|
||||||
# We have documented cases of emails to Microsoft returning the error occasionally and then later
|
|
||||||
# allowing the very same email.
|
|
||||||
try:
|
|
||||||
self.retry(max_retries=5, countdown=[60, 300, 600, 1200, 1800, 1800][self.request.retries])
|
|
||||||
except MaxRetriesExceededError:
|
|
||||||
# ignore and go on with logging the error
|
|
||||||
pass
|
|
||||||
|
|
||||||
logger.exception('Error sending email')
|
|
||||||
if log_target:
|
|
||||||
message = []
|
|
||||||
for e, val in e.recipients.items():
|
|
||||||
message.append(f'{e}: {val[0]} {val[1].decode()}')
|
|
||||||
|
|
||||||
log_target.log_action(
|
|
||||||
error_log_action_type,
|
|
||||||
data={
|
|
||||||
'subject': 'SMTP error',
|
|
||||||
'message': '\n'.join(message),
|
|
||||||
'recipient': '',
|
|
||||||
'invoices': [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
|
||||||
except Exception as e:
|
|
||||||
if isinstance(e, OSError) and not isinstance(e, smtplib.SMTPNotSupportedError):
|
|
||||||
try:
|
|
||||||
self.retry(max_retries=5, countdown=[10, 30, 60, 300, 900, 900][self.request.retries])
|
|
||||||
except MaxRetriesExceededError:
|
|
||||||
if log_target:
|
|
||||||
log_target.log_action(
|
|
||||||
error_log_action_type,
|
|
||||||
data={
|
|
||||||
'subject': 'Internal error',
|
|
||||||
'message': f'Max retries exceeded after error "{str(e)}"',
|
|
||||||
'recipient': '',
|
|
||||||
'invoices': [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
raise e
|
|
||||||
if log_target:
|
|
||||||
log_target.log_action(
|
|
||||||
error_log_action_type,
|
|
||||||
data={
|
|
||||||
'subject': 'Internal error',
|
|
||||||
'message': str(e),
|
|
||||||
'recipient': '',
|
|
||||||
'invoices': [],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
logger.exception('Error sending email')
|
|
||||||
raise SendMailException('Failed to send an email to {}.'.format(to))
|
|
||||||
else:
|
else:
|
||||||
for i in invoices_sent:
|
outgoing_mail.status = OutgoingMail.STATUS_SENT
|
||||||
|
outgoing_mail.error = None
|
||||||
|
outgoing_mail.error_detail = None
|
||||||
|
outgoing_mail.actual_attachments = actual_attachments
|
||||||
|
outgoing_mail.sent = now()
|
||||||
|
outgoing_mail.save(upate_fields=["status", "error", "error_detail", "sent", "actual_attachments"])
|
||||||
|
|
||||||
|
for i in invoices_attached:
|
||||||
if i.transmission_type == "email":
|
if i.transmission_type == "email":
|
||||||
# Mark invoice as sent when it was sent to the requested address *either* at the time of invoice
|
# Mark invoice as sent when it was sent to the requested address *either* at the time of invoice
|
||||||
# creation *or* as of right now.
|
# creation *or* as of right now.
|
||||||
@@ -675,7 +595,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
|||||||
expected_recipients.append((i.order.invoice_address.transmission_info or {}).get("transmission_email_address") or i.order.email)
|
expected_recipients.append((i.order.invoice_address.transmission_info or {}).get("transmission_email_address") or i.order.email)
|
||||||
except InvoiceAddress.DoesNotExist:
|
except InvoiceAddress.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
if not any(t in expected_recipients for t in to):
|
if not any(t in expected_recipients for t in outgoing_mail.to):
|
||||||
continue
|
continue
|
||||||
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
|
if i.transmission_status != Invoice.TRANSMISSION_STATUS_COMPLETED:
|
||||||
i.transmission_date = now()
|
i.transmission_date = now()
|
||||||
@@ -684,7 +604,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
|||||||
i.transmission_info = {
|
i.transmission_info = {
|
||||||
"sent": [
|
"sent": [
|
||||||
{
|
{
|
||||||
"recipients": to,
|
"recipients": outgoing_mail.to,
|
||||||
"datetime": now().isoformat(),
|
"datetime": now().isoformat(),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -696,7 +616,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
|||||||
elif i.transmission_provider == "email_pdf":
|
elif i.transmission_provider == "email_pdf":
|
||||||
i.transmission_info["sent"].append(
|
i.transmission_info["sent"].append(
|
||||||
{
|
{
|
||||||
"recipients": to,
|
"recipients": outgoing_mail.to,
|
||||||
"datetime": now().isoformat(),
|
"datetime": now().isoformat(),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -710,7 +630,7 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st
|
|||||||
"transmission_provider": "email_pdf",
|
"transmission_provider": "email_pdf",
|
||||||
"transmission_type": "email",
|
"transmission_type": "email",
|
||||||
"data": {
|
"data": {
|
||||||
"recipients": [to],
|
"recipients": outgoing_mail.to,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -833,3 +753,121 @@ def normalize_image_url(url):
|
|||||||
else:
|
else:
|
||||||
url = urljoin(settings.MEDIA_URL, url)
|
url = urljoin(settings.MEDIA_URL, url)
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
|
def _autoextend_context(context, order):
|
||||||
|
try:
|
||||||
|
context.update({
|
||||||
|
'invoice_name': order.invoice_address.name,
|
||||||
|
'invoice_company': order.invoice_address.company
|
||||||
|
})
|
||||||
|
except InvoiceAddress.DoesNotExist:
|
||||||
|
context.update({
|
||||||
|
'invoice_name': '',
|
||||||
|
'invoice_company': ''
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
def _full_sender(sender_address, event, organizer):
|
||||||
|
sender_address = (
|
||||||
|
sender_address or
|
||||||
|
(event.settings.get('mail_from') if event else None) or
|
||||||
|
(organizer.settings.get('mail_from') if organizer else None) or
|
||||||
|
settings.MAIL_FROM
|
||||||
|
)
|
||||||
|
if event:
|
||||||
|
sender_name = event.settings.mail_from_name or str(event.name)
|
||||||
|
elif organizer:
|
||||||
|
sender_name = organizer.settings.mail_from_name or str(organizer.name)
|
||||||
|
else:
|
||||||
|
sender_name = settings.PRETIX_INSTANCE_NAME
|
||||||
|
|
||||||
|
sender = formataddr((clean_sender_name(sender_name), sender_address))
|
||||||
|
return sender
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_plain_body(content_plain, signature, event, order, position, no_order_links):
|
||||||
|
body_plain = content_plain
|
||||||
|
body_plain += "\r\n\r\n-- \r\n"
|
||||||
|
|
||||||
|
if signature:
|
||||||
|
signature = signature.format(event=event.name if event else '')
|
||||||
|
body_plain += signature
|
||||||
|
body_plain += "\r\n\r\n-- \r\n"
|
||||||
|
|
||||||
|
if event and order and position and not no_order_links:
|
||||||
|
body_plain += _(
|
||||||
|
"You are receiving this email because someone placed an order for {event} for you."
|
||||||
|
).format(event=event.name)
|
||||||
|
body_plain += "\r\n"
|
||||||
|
body_plain += _(
|
||||||
|
"You can view your order details at the following URL:\n{orderurl}."
|
||||||
|
).replace("\n", "\r\n").format(
|
||||||
|
event=event.name, orderurl=build_absolute_uri(
|
||||||
|
order.event, 'presale:event.order.position', kwargs={
|
||||||
|
'order': order.code,
|
||||||
|
'secret': position.web_secret,
|
||||||
|
'position': position.positionid,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif event and order and not no_order_links:
|
||||||
|
body_plain += _(
|
||||||
|
"You are receiving this email because you placed an order for {event}."
|
||||||
|
).format(event=event.name)
|
||||||
|
body_plain += "\r\n"
|
||||||
|
body_plain += _(
|
||||||
|
"You can view your order details at the following URL:\n{orderurl}."
|
||||||
|
).replace("\n", "\r\n").format(
|
||||||
|
event=event.name, orderurl=build_absolute_uri(
|
||||||
|
order.event, 'presale:event.order.open', kwargs={
|
||||||
|
'order': order.code,
|
||||||
|
'secret': order.secret,
|
||||||
|
'hash': order.email_confirm_secret()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
body_plain += "\r\n"
|
||||||
|
|
||||||
|
return body_plain
|
||||||
|
|
||||||
|
|
||||||
|
def _retry_strategy(e: Exception):
|
||||||
|
if isinstance(e, (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused)):
|
||||||
|
if e.smtp_code == 432:
|
||||||
|
# This is likely Microsoft Exchange Online which has a pretty bad rate limit of max. 3 concurrent
|
||||||
|
# SMTP connections which is *easily* exceeded with many celery threads. Just retrying with exponential
|
||||||
|
# backoff won't be good enough if we have a lot of emails, instead we'll need to make sure our retry
|
||||||
|
# intervals scatter such that the email won't all be retried at the same time again and cause the
|
||||||
|
# same problem.
|
||||||
|
# See also https://docs.microsoft.com/en-us/exchange/troubleshoot/send-emails/smtp-submission-improvements
|
||||||
|
return "microsoft_concurrncy"
|
||||||
|
|
||||||
|
if e.smtp_code in (101, 111, 421, 422, 431, 432, 442, 447, 452):
|
||||||
|
return "quick"
|
||||||
|
|
||||||
|
elif isinstance(e, smtplib.SMTPRecipientsRefused):
|
||||||
|
smtp_codes = [a[0] for a in e.recipients.values()]
|
||||||
|
|
||||||
|
if not any(c >= 500 for c in smtp_codes) or any(b'Message is too large' in a[1] for a in e.recipients.values()):
|
||||||
|
# This is not a permanent failure (mailbox full, service unavailable), retry later, but with large
|
||||||
|
# intervals. One would think that "Message is too lage" is a permanent failure, but apparently it is not.
|
||||||
|
# We have documented cases of emails to Microsoft returning the error occasionally and then later
|
||||||
|
# allowing the very same email.
|
||||||
|
return "slow"
|
||||||
|
|
||||||
|
elif isinstance(e, OSError) and not isinstance(e, smtplib.SMTPNotSupportedError):
|
||||||
|
# Most likely some other kind of temporary failure, retry again (but pretty soon)
|
||||||
|
return "quick"
|
||||||
|
|
||||||
|
|
||||||
|
def _format_error(e: Exception):
|
||||||
|
if isinstance(e, (smtplib.SMTPResponseException, smtplib.SMTPSenderRefused)):
|
||||||
|
return 'SMTP code {}'.format(e.smtp_code), e.smtp_error.decode() if isinstance(e.smtp_error, bytes) else str(e.smtp_error)
|
||||||
|
elif isinstance(e, smtplib.SMTPRecipientsRefused):
|
||||||
|
message = []
|
||||||
|
for e, val in e.recipients.items():
|
||||||
|
message.append(f'{e}: {val[0]} {val[1].decode()}')
|
||||||
|
return 'SMTP recipients refudes', '\n'.join(message)
|
||||||
|
else:
|
||||||
|
return 'Internal error', str(e)
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ from pretix.base.services.invoices import (
|
|||||||
from pretix.base.services.locking import (
|
from pretix.base.services.locking import (
|
||||||
LOCK_TRUST_WINDOW, LockTimeoutException, lock_objects,
|
LOCK_TRUST_WINDOW, LockTimeoutException, lock_objects,
|
||||||
)
|
)
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.services.memberships import (
|
from pretix.base.services.memberships import (
|
||||||
create_membership, validate_memberships_in_order,
|
create_membership, validate_memberships_in_order,
|
||||||
)
|
)
|
||||||
@@ -421,33 +420,27 @@ def approve_order(order, user=None, send_mail: bool=True, auth=None, force=False
|
|||||||
email_attendee_subject = order.event.settings.mail_subject_order_approved_attendee
|
email_attendee_subject = order.event.settings.mail_subject_order_approved_attendee
|
||||||
|
|
||||||
email_context = get_email_context(event=order.event, order=order)
|
email_context = get_email_context(event=order.event, order=order)
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.order_approved', user,
|
||||||
'pretix.event.order.email.order_approved', user,
|
attach_tickets=True,
|
||||||
attach_tickets=True,
|
attach_ical=order.event.settings.mail_attach_ical and (
|
||||||
attach_ical=order.event.settings.mail_attach_ical and (
|
not order.event.settings.mail_attach_ical_paid_only or
|
||||||
not order.event.settings.mail_attach_ical_paid_only or
|
order.total == Decimal('0.00') or
|
||||||
order.total == Decimal('0.00') or
|
order.valid_if_pending
|
||||||
order.valid_if_pending
|
),
|
||||||
),
|
invoices=[invoice] if invoice and transmit_invoice_mail else []
|
||||||
invoices=[invoice] if invoice and transmit_invoice_mail else []
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order approved email could not be sent')
|
|
||||||
|
|
||||||
if email_attendees:
|
if email_attendees:
|
||||||
for p in order.positions.all():
|
for p in order.positions.all():
|
||||||
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:
|
||||||
email_attendee_context = get_email_context(event=order.event, order=order, position=p)
|
email_attendee_context = get_email_context(event=order.event, order=order, position=p)
|
||||||
try:
|
p.send_mail(
|
||||||
p.send_mail(
|
email_attendee_subject, email_attendee_template, email_attendee_context,
|
||||||
email_attendee_subject, email_attendee_template, email_attendee_context,
|
'pretix.event.order.email.order_approved', user,
|
||||||
'pretix.event.order.email.order_approved', user,
|
attach_tickets=True,
|
||||||
attach_tickets=True,
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order approved email could not be sent to attendee')
|
|
||||||
|
|
||||||
return order.pk
|
return order.pk
|
||||||
|
|
||||||
@@ -484,13 +477,10 @@ def deny_order(order, comment='', user=None, send_mail: bool=True, auth=None):
|
|||||||
email_template = order.event.settings.mail_text_order_denied
|
email_template = order.event.settings.mail_text_order_denied
|
||||||
email_subject = order.event.settings.mail_subject_order_denied
|
email_subject = order.event.settings.mail_subject_order_denied
|
||||||
email_context = get_email_context(event=order.event, order=order, comment=comment)
|
email_context = get_email_context(event=order.event, order=order, comment=comment)
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.order_denied', user
|
||||||
'pretix.event.order.email.order_denied', user
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order denied email could not be sent')
|
|
||||||
|
|
||||||
return order.pk
|
return order.pk
|
||||||
|
|
||||||
@@ -637,14 +627,11 @@ def _cancel_order(order, user=None, send_mail: bool=True, api_token=None, device
|
|||||||
email_template = order.event.settings.mail_text_order_canceled
|
email_template = order.event.settings.mail_text_order_canceled
|
||||||
email_subject = order.event.settings.mail_subject_order_canceled
|
email_subject = order.event.settings.mail_subject_order_canceled
|
||||||
email_context = get_email_context(event=order.event, order=order, comment=comment or "")
|
email_context = get_email_context(event=order.event, order=order, comment=comment or "")
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.order_canceled', user,
|
||||||
'pretix.event.order.email.order_canceled', user,
|
invoices=transmit_invoices_mail,
|
||||||
invoices=transmit_invoices_mail,
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order canceled email could not be sent')
|
|
||||||
|
|
||||||
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING)):
|
for p in order.payments.filter(state__in=(OrderPayment.PAYMENT_STATE_CREATED, OrderPayment.PAYMENT_STATE_PENDING)):
|
||||||
try:
|
try:
|
||||||
@@ -1099,46 +1086,40 @@ def _order_placed_email(event: Event, order: Order, email_template, subject_temp
|
|||||||
log_entry: str, invoice, payments: List[OrderPayment], is_free=False):
|
log_entry: str, invoice, payments: List[OrderPayment], is_free=False):
|
||||||
email_context = get_email_context(event=event, order=order, payments=payments)
|
email_context = get_email_context(event=event, order=order, payments=payments)
|
||||||
|
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
subject_template, email_template, email_context,
|
||||||
subject_template, email_template, email_context,
|
log_entry,
|
||||||
log_entry,
|
invoices=[invoice] if invoice else [],
|
||||||
invoices=[invoice] if invoice else [],
|
attach_tickets=True,
|
||||||
attach_tickets=True,
|
attach_ical=event.settings.mail_attach_ical and (
|
||||||
attach_ical=event.settings.mail_attach_ical and (
|
not event.settings.mail_attach_ical_paid_only or
|
||||||
not event.settings.mail_attach_ical_paid_only or
|
is_free or
|
||||||
is_free or
|
order.valid_if_pending
|
||||||
order.valid_if_pending
|
),
|
||||||
),
|
attach_other_files=[a for a in [
|
||||||
attach_other_files=[a for a in [
|
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
||||||
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
] if a],
|
||||||
] if a],
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order received email could not be sent')
|
|
||||||
|
|
||||||
|
|
||||||
def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosition, email_template, subject_template,
|
def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosition, email_template, subject_template,
|
||||||
log_entry: str, is_free=False):
|
log_entry: str, is_free=False):
|
||||||
email_context = get_email_context(event=event, order=order, position=position)
|
email_context = get_email_context(event=event, order=order, position=position)
|
||||||
|
|
||||||
try:
|
position.send_mail(
|
||||||
position.send_mail(
|
subject_template, email_template, email_context,
|
||||||
subject_template, email_template, email_context,
|
log_entry,
|
||||||
log_entry,
|
invoices=[],
|
||||||
invoices=[],
|
attach_tickets=True,
|
||||||
attach_tickets=True,
|
attach_ical=event.settings.mail_attach_ical and (
|
||||||
attach_ical=event.settings.mail_attach_ical and (
|
not event.settings.mail_attach_ical_paid_only or
|
||||||
not event.settings.mail_attach_ical_paid_only or
|
is_free or
|
||||||
is_free or
|
order.valid_if_pending
|
||||||
order.valid_if_pending
|
),
|
||||||
),
|
attach_other_files=[a for a in [
|
||||||
attach_other_files=[a for a in [
|
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
||||||
event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
] if a],
|
||||||
] if a],
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order received email could not be sent to attendee')
|
|
||||||
|
|
||||||
|
|
||||||
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
|
def _perform_order(event: Event, payment_requests: List[dict], position_ids: List[str],
|
||||||
@@ -1460,13 +1441,10 @@ def send_expiry_warnings(sender, **kwargs):
|
|||||||
email_template = settings.mail_text_order_pending_warning
|
email_template = settings.mail_text_order_pending_warning
|
||||||
email_subject = settings.mail_subject_order_pending_warning
|
email_subject = settings.mail_subject_order_pending_warning
|
||||||
|
|
||||||
try:
|
o.send_mail(
|
||||||
o.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.expire_warning_sent'
|
||||||
'pretix.event.order.email.expire_warning_sent'
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Reminder email could not be sent')
|
|
||||||
|
|
||||||
|
|
||||||
@receiver(signal=periodic_task)
|
@receiver(signal=periodic_task)
|
||||||
@@ -1527,14 +1505,11 @@ def send_download_reminders(sender, **kwargs):
|
|||||||
email_template = event.settings.mail_text_download_reminder
|
email_template = event.settings.mail_text_download_reminder
|
||||||
email_subject = event.settings.mail_subject_download_reminder
|
email_subject = event.settings.mail_subject_download_reminder
|
||||||
email_context = get_email_context(event=event, order=o)
|
email_context = get_email_context(event=event, order=o)
|
||||||
try:
|
o.send_mail(
|
||||||
o.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.download_reminder_sent',
|
||||||
'pretix.event.order.email.download_reminder_sent',
|
attach_tickets=True
|
||||||
attach_tickets=True
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Reminder email could not be sent')
|
|
||||||
|
|
||||||
if event.settings.mail_send_download_reminder_attendee:
|
if event.settings.mail_send_download_reminder_attendee:
|
||||||
for p in positions:
|
for p in positions:
|
||||||
@@ -1548,14 +1523,11 @@ def send_download_reminders(sender, **kwargs):
|
|||||||
email_template = event.settings.mail_text_download_reminder_attendee
|
email_template = event.settings.mail_text_download_reminder_attendee
|
||||||
email_subject = event.settings.mail_subject_download_reminder_attendee
|
email_subject = event.settings.mail_subject_download_reminder_attendee
|
||||||
email_context = get_email_context(event=event, order=o, position=p)
|
email_context = get_email_context(event=event, order=o, position=p)
|
||||||
try:
|
o.send_mail(
|
||||||
o.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.download_reminder_sent',
|
||||||
'pretix.event.order.email.download_reminder_sent',
|
attach_tickets=True, position=p
|
||||||
attach_tickets=True, position=p
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Reminder email could not be sent to attendee')
|
|
||||||
|
|
||||||
|
|
||||||
def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
|
def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
|
||||||
@@ -1563,13 +1535,10 @@ def notify_user_changed_order(order, user=None, auth=None, invoices=[]):
|
|||||||
email_template = order.event.settings.mail_text_order_changed
|
email_template = order.event.settings.mail_text_order_changed
|
||||||
email_context = get_email_context(event=order.event, order=order)
|
email_context = get_email_context(event=order.event, order=order)
|
||||||
email_subject = order.event.settings.mail_subject_order_changed
|
email_subject = order.event.settings.mail_subject_order_changed
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices, attach_tickets=True,
|
||||||
'pretix.event.order.email.order_changed', user, auth=auth, invoices=invoices, attach_tickets=True,
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Order changed email could not be sent')
|
|
||||||
|
|
||||||
|
|
||||||
class OrderChangeManager:
|
class OrderChangeManager:
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ from django.utils.translation import gettext_lazy as _
|
|||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.models import CachedFile, Event, User, cachedfile_name
|
from pretix.base.models import CachedFile, Event, User, cachedfile_name
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import mail
|
||||||
from pretix.base.services.tasks import ProfiledEventTask
|
from pretix.base.services.tasks import ProfiledEventTask
|
||||||
from pretix.base.shredder import ShredError
|
from pretix.base.shredder import ShredError
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
@@ -171,21 +171,18 @@ def shred(self, event: Event, fileid: str, confirm_code: str, user: int=None, lo
|
|||||||
|
|
||||||
if user:
|
if user:
|
||||||
with language(user.locale):
|
with language(user.locale):
|
||||||
try:
|
mail(
|
||||||
mail(
|
user.email,
|
||||||
user.email,
|
_('Data shredding completed'),
|
||||||
_('Data shredding completed'),
|
'pretixbase/email/shred_completed.txt',
|
||||||
'pretixbase/email/shred_completed.txt',
|
{
|
||||||
{
|
'user': user,
|
||||||
'user': user,
|
'organizer': event.organizer.name,
|
||||||
'organizer': event.organizer.name,
|
'event': str(event.name),
|
||||||
'event': str(event.name),
|
'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'),
|
||||||
'start_time': date_format(parse(indexdata['time']).astimezone(event.timezone), 'SHORT_DATETIME_FORMAT'),
|
'shredders': ', '.join([str(s.verbose_name) for s in shredders])
|
||||||
'shredders': ', '.join([str(s.verbose_name) for s in shredders])
|
},
|
||||||
},
|
event=None,
|
||||||
event=None,
|
user=user,
|
||||||
user=user,
|
locale=user.locale,
|
||||||
locale=user.locale,
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
pass # Already logged
|
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ from pretix.base.forms.auth import (
|
|||||||
)
|
)
|
||||||
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
|
from pretix.base.metrics import pretix_failed_logins, pretix_successful_logins
|
||||||
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
|
from pretix.base.models import TeamInvite, U2FDevice, User, WebAuthnDevice
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.helpers.http import get_client_ip, redirect_to_url
|
from pretix.helpers.http import get_client_ip, redirect_to_url
|
||||||
from pretix.helpers.security import handle_login_source
|
from pretix.helpers.security import handle_login_source
|
||||||
|
|
||||||
@@ -342,9 +341,6 @@ class Forgot(TemplateView):
|
|||||||
except User.DoesNotExist:
|
except User.DoesNotExist:
|
||||||
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
|
logger.warning('Backend password reset for unregistered e-mail \"' + email + '\" requested.')
|
||||||
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Sending password reset email to \"' + email + '\" failed.')
|
|
||||||
|
|
||||||
except RepeatedResetDenied:
|
except RepeatedResetDenied:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -96,9 +96,7 @@ from pretix.base.services.invoices import (
|
|||||||
invoice_qualified, regenerate_invoice, transmit_invoice,
|
invoice_qualified, regenerate_invoice, transmit_invoice,
|
||||||
)
|
)
|
||||||
from pretix.base.services.locking import LockTimeoutException
|
from pretix.base.services.locking import LockTimeoutException
|
||||||
from pretix.base.services.mail import (
|
from pretix.base.services.mail import prefix_subject, render_mail
|
||||||
SendMailException, prefix_subject, render_mail,
|
|
||||||
)
|
|
||||||
from pretix.base.services.orders import (
|
from pretix.base.services.orders import (
|
||||||
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
|
OrderChangeManager, OrderError, approve_order, cancel_order, deny_order,
|
||||||
extend_order, mark_order_expired, mark_order_refunded,
|
extend_order, mark_order_expired, mark_order_refunded,
|
||||||
@@ -1054,10 +1052,6 @@ class OrderPaymentConfirm(OrderView):
|
|||||||
messages.error(self.request, str(e))
|
messages.error(self.request, str(e))
|
||||||
except PaymentException as e:
|
except PaymentException as e:
|
||||||
messages.error(self.request, str(e))
|
messages.error(self.request, str(e))
|
||||||
except SendMailException:
|
|
||||||
messages.warning(self.request,
|
|
||||||
_('The payment has been marked as complete, but we were unable to send a '
|
|
||||||
'confirmation mail.'))
|
|
||||||
else:
|
else:
|
||||||
messages.success(self.request, _('The payment has been marked as complete.'))
|
messages.success(self.request, _('The payment has been marked as complete.'))
|
||||||
else:
|
else:
|
||||||
@@ -1527,9 +1521,6 @@ class OrderTransition(OrderView):
|
|||||||
'message': str(e)
|
'message': str(e)
|
||||||
})
|
})
|
||||||
messages.error(self.request, str(e))
|
messages.error(self.request, str(e))
|
||||||
except SendMailException:
|
|
||||||
messages.warning(self.request, _('The order has been marked as paid, but we were unable to send a '
|
|
||||||
'confirmation mail.'))
|
|
||||||
else:
|
else:
|
||||||
messages.success(self.request, _('The payment has been created successfully.'))
|
messages.success(self.request, _('The payment has been created successfully.'))
|
||||||
elif self.order.cancel_allowed() and to == 'c':
|
elif self.order.cancel_allowed() and to == 'c':
|
||||||
@@ -1748,15 +1739,11 @@ class OrderResendLink(OrderView):
|
|||||||
permission = 'can_change_orders'
|
permission = 'can_change_orders'
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
try:
|
if 'position' in kwargs:
|
||||||
if 'position' in kwargs:
|
p = get_object_or_404(self.order.positions, pk=kwargs['position'])
|
||||||
p = get_object_or_404(self.order.positions, pk=kwargs['position'])
|
p.resend_link(user=self.request.user)
|
||||||
p.resend_link(user=self.request.user)
|
else:
|
||||||
else:
|
self.order.resend_link(user=self.request.user)
|
||||||
self.order.resend_link(user=self.request.user)
|
|
||||||
except SendMailException:
|
|
||||||
messages.error(self.request, _('There was an error sending the mail. Please try again later.'))
|
|
||||||
return redirect(self.get_order_url())
|
|
||||||
|
|
||||||
messages.success(self.request, _('The email has been queued to be sent.'))
|
messages.success(self.request, _('The email has been queued to be sent.'))
|
||||||
return redirect(self.get_order_url())
|
return redirect(self.get_order_url())
|
||||||
@@ -2399,24 +2386,18 @@ class OrderSendMail(EventPermissionRequiredMixin, OrderViewMixin, FormView):
|
|||||||
}
|
}
|
||||||
return self.get(self.request, *self.args, **self.kwargs)
|
return self.get(self.request, *self.args, **self.kwargs)
|
||||||
else:
|
else:
|
||||||
try:
|
order.send_mail(
|
||||||
order.send_mail(
|
form.cleaned_data['subject'], email_template,
|
||||||
form.cleaned_data['subject'], email_template,
|
email_context, 'pretix.event.order.email.custom_sent',
|
||||||
email_context, 'pretix.event.order.email.custom_sent',
|
self.request.user, auto_email=False,
|
||||||
self.request.user, auto_email=False,
|
attach_tickets=form.cleaned_data.get('attach_tickets', False),
|
||||||
attach_tickets=form.cleaned_data.get('attach_tickets', False),
|
invoices=form.cleaned_data.get('attach_invoices', []),
|
||||||
invoices=form.cleaned_data.get('attach_invoices', []),
|
attach_other_files=[a for a in [
|
||||||
attach_other_files=[a for a in [
|
self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
||||||
self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
] if a] if form.cleaned_data.get('attach_new_order', False) else [],
|
||||||
] if a] if form.cleaned_data.get('attach_new_order', False) else [],
|
)
|
||||||
)
|
messages.success(self.request,
|
||||||
messages.success(self.request,
|
_('Your message has been queued and will be sent to {}.'.format(order.email)))
|
||||||
_('Your message has been queued and will be sent to {}.'.format(order.email)))
|
|
||||||
except SendMailException:
|
|
||||||
messages.error(
|
|
||||||
self.request,
|
|
||||||
_('Failed to send mail to the following user: {}'.format(order.email))
|
|
||||||
)
|
|
||||||
return super(OrderSendMail, self).form_valid(form)
|
return super(OrderSendMail, self).form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@@ -2469,23 +2450,19 @@ class OrderPositionSendMail(OrderSendMail):
|
|||||||
}
|
}
|
||||||
return self.get(self.request, *self.args, **self.kwargs)
|
return self.get(self.request, *self.args, **self.kwargs)
|
||||||
else:
|
else:
|
||||||
try:
|
position.send_mail(
|
||||||
position.send_mail(
|
form.cleaned_data['subject'],
|
||||||
form.cleaned_data['subject'],
|
email_template,
|
||||||
email_template,
|
email_context,
|
||||||
email_context,
|
'pretix.event.order.position.email.custom_sent',
|
||||||
'pretix.event.order.position.email.custom_sent',
|
self.request.user,
|
||||||
self.request.user,
|
attach_tickets=form.cleaned_data.get('attach_tickets', False),
|
||||||
attach_tickets=form.cleaned_data.get('attach_tickets', False),
|
attach_other_files=[a for a in [
|
||||||
attach_other_files=[a for a in [
|
self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
||||||
self.request.event.settings.get('mail_attachment_new_order', as_type=str, default='')[len('file://'):]
|
] if a] if form.cleaned_data.get('attach_new_order', False) else [],
|
||||||
] if a] if form.cleaned_data.get('attach_new_order', False) else [],
|
)
|
||||||
)
|
messages.success(self.request,
|
||||||
messages.success(self.request,
|
_('Your message has been queued and will be sent to {}.'.format(position.attendee_email)))
|
||||||
_('Your message has been queued and will be sent to {}.'.format(position.attendee_email)))
|
|
||||||
except SendMailException:
|
|
||||||
messages.error(self.request,
|
|
||||||
_('Failed to send mail to the following user: {}'.format(position.attendee_email)))
|
|
||||||
return super(OrderSendMail, self).form_valid(form)
|
return super(OrderSendMail, self).form_valid(form)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ from pretix.base.plugins import (
|
|||||||
PLUGIN_LEVEL_ORGANIZER,
|
PLUGIN_LEVEL_ORGANIZER,
|
||||||
)
|
)
|
||||||
from pretix.base.services.export import multiexport, scheduled_organizer_export
|
from pretix.base.services.export import multiexport, scheduled_organizer_export
|
||||||
from pretix.base.services.mail import SendMailException, mail, prefix_subject
|
from pretix.base.services.mail import mail, prefix_subject
|
||||||
from pretix.base.signals import register_multievent_data_exporters
|
from pretix.base.signals import register_multievent_data_exporters
|
||||||
from pretix.base.templatetags.rich_text import markdown_compile_email
|
from pretix.base.templatetags.rich_text import markdown_compile_email
|
||||||
from pretix.base.views.tasks import AsyncAction
|
from pretix.base.views.tasks import AsyncAction
|
||||||
@@ -1036,24 +1036,21 @@ class TeamMemberView(OrganizerDetailViewMixin, OrganizerPermissionRequiredMixin,
|
|||||||
return ctx
|
return ctx
|
||||||
|
|
||||||
def _send_invite(self, instance):
|
def _send_invite(self, instance):
|
||||||
try:
|
mail(
|
||||||
mail(
|
instance.email,
|
||||||
instance.email,
|
_('pretix account invitation'),
|
||||||
_('pretix account invitation'),
|
'pretixcontrol/email/invitation.txt',
|
||||||
'pretixcontrol/email/invitation.txt',
|
{
|
||||||
{
|
'user': self,
|
||||||
'user': self,
|
'organizer': self.request.organizer.name,
|
||||||
'organizer': self.request.organizer.name,
|
'team': instance.team.name,
|
||||||
'team': instance.team.name,
|
'url': build_global_uri('control:auth.invite', kwargs={
|
||||||
'url': build_global_uri('control:auth.invite', kwargs={
|
'token': instance.token
|
||||||
'token': instance.token
|
})
|
||||||
})
|
},
|
||||||
},
|
event=None,
|
||||||
event=None,
|
locale=self.request.LANGUAGE_CODE
|
||||||
locale=self.request.LANGUAGE_CODE
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
pass # Already logged
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ from hijack import signals
|
|||||||
|
|
||||||
from pretix.base.auth import get_auth_backends
|
from pretix.base.auth import get_auth_backends
|
||||||
from pretix.base.models import User
|
from pretix.base.models import User
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.control.forms.filter import UserFilterForm
|
from pretix.control.forms.filter import UserFilterForm
|
||||||
from pretix.control.forms.users import UserEditForm
|
from pretix.control.forms.users import UserEditForm
|
||||||
from pretix.control.permissions import AdministratorPermissionRequiredMixin
|
from pretix.control.permissions import AdministratorPermissionRequiredMixin
|
||||||
@@ -139,11 +138,7 @@ class UserResetView(AdministratorPermissionRequiredMixin, RecentAuthenticationRe
|
|||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
|
self.object = get_object_or_404(User, pk=self.kwargs.get("id"))
|
||||||
try:
|
self.object.send_password_reset()
|
||||||
self.object.send_password_reset()
|
|
||||||
except SendMailException:
|
|
||||||
messages.error(request, _('There was an error sending the mail. Please try again later.'))
|
|
||||||
return redirect(self.get_success_url())
|
|
||||||
|
|
||||||
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
|
self.object.log_action('pretix.control.auth.user.forgot_password.mail_sent',
|
||||||
user=request.user)
|
user=request.user)
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ from django_countries.fields import Country
|
|||||||
from geoip2.errors import AddressNotFoundError
|
from geoip2.errors import AddressNotFoundError
|
||||||
|
|
||||||
from pretix.base.i18n import language
|
from pretix.base.i18n import language
|
||||||
from pretix.base.services.mail import SendMailException, mail
|
from pretix.base.services.mail import mail
|
||||||
from pretix.helpers.http import get_client_ip
|
from pretix.helpers.http import get_client_ip
|
||||||
from pretix.helpers.urls import build_absolute_uri
|
from pretix.helpers.urls import build_absolute_uri
|
||||||
|
|
||||||
@@ -159,21 +159,18 @@ def handle_login_source(user, request):
|
|||||||
})
|
})
|
||||||
if user.known_login_sources.count() > 1:
|
if user.known_login_sources.count() > 1:
|
||||||
# Do not send on first login or first login after introduction of this feature:
|
# Do not send on first login or first login after introduction of this feature:
|
||||||
try:
|
with language(user.locale):
|
||||||
with language(user.locale):
|
mail(
|
||||||
mail(
|
user.email,
|
||||||
user.email,
|
_('Login from new source detected'),
|
||||||
_('Login from new source detected'),
|
'pretixcontrol/email/login_notice.txt',
|
||||||
'pretixcontrol/email/login_notice.txt',
|
{
|
||||||
{
|
'source': src,
|
||||||
'source': src,
|
'country': Country(str(country)).name if country else _('Unknown country'),
|
||||||
'country': Country(str(country)).name if country else _('Unknown country'),
|
'instance': settings.PRETIX_INSTANCE_NAME,
|
||||||
'instance': settings.PRETIX_INSTANCE_NAME,
|
'url': build_absolute_uri('control:user.settings')
|
||||||
'url': build_absolute_uri('control:user.settings')
|
},
|
||||||
},
|
event=None,
|
||||||
event=None,
|
user=user,
|
||||||
user=user,
|
locale=user.locale
|
||||||
locale=user.locale
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
pass # Not much we can do
|
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ from pretix.base.models import (
|
|||||||
)
|
)
|
||||||
from pretix.base.payment import PaymentException
|
from pretix.base.payment import PaymentException
|
||||||
from pretix.base.services.locking import LockTimeoutException
|
from pretix.base.services.locking import LockTimeoutException
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.services.orders import change_payment_provider
|
from pretix.base.services.orders import change_payment_provider
|
||||||
from pretix.base.services.tasks import TransactionAwareTask
|
from pretix.base.services.tasks import TransactionAwareTask
|
||||||
from pretix.celery_app import app
|
from pretix.celery_app import app
|
||||||
@@ -70,13 +69,10 @@ def notify_incomplete_payment(o: Order):
|
|||||||
email_context = get_email_context(event=o.event, order=o, pending_sum=o.pending_sum)
|
email_context = get_email_context(event=o.event, order=o, pending_sum=o.pending_sum)
|
||||||
email_subject = o.event.settings.mail_subject_order_incomplete_payment
|
email_subject = o.event.settings.mail_subject_order_incomplete_payment
|
||||||
|
|
||||||
try:
|
o.send_mail(
|
||||||
o.send_mail(
|
email_subject, email_template, email_context,
|
||||||
email_subject, email_template, email_context,
|
'pretix.event.order.email.expire_warning_sent'
|
||||||
'pretix.event.order.email.expire_warning_sent'
|
)
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
logger.exception('Reminder email could not be sent')
|
|
||||||
|
|
||||||
|
|
||||||
def cancel_old_payments(order):
|
def cancel_old_payments(order):
|
||||||
@@ -279,9 +275,6 @@ def _handle_transaction(trans: BankTransaction, matches: tuple, event: Event = N
|
|||||||
except Quota.QuotaExceededException:
|
except Quota.QuotaExceededException:
|
||||||
# payment confirmed but order status could not be set, no longer problem of this plugin
|
# payment confirmed but order status could not be set, no longer problem of this plugin
|
||||||
cancel_old_payments(order)
|
cancel_old_payments(order)
|
||||||
except SendMailException:
|
|
||||||
# payment confirmed but order status could not be set, no longer problem of this plugin
|
|
||||||
cancel_old_payments(order)
|
|
||||||
else:
|
else:
|
||||||
cancel_old_payments(order)
|
cancel_old_payments(order)
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,6 @@ from localflavor.generic.forms import BICFormField, IBANFormField
|
|||||||
|
|
||||||
from pretix.base.forms.widgets import DatePickerWidget
|
from pretix.base.forms.widgets import DatePickerWidget
|
||||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
from pretix.base.templatetags.money import money_filter
|
from pretix.base.templatetags.money import money_filter
|
||||||
from pretix.control.permissions import (
|
from pretix.control.permissions import (
|
||||||
@@ -160,11 +159,6 @@ class ActionView(View):
|
|||||||
p.confirm(user=self.request.user)
|
p.confirm(user=self.request.user)
|
||||||
except Quota.QuotaExceededException:
|
except Quota.QuotaExceededException:
|
||||||
pass
|
pass
|
||||||
except SendMailException:
|
|
||||||
return JsonResponse({
|
|
||||||
'status': 'error',
|
|
||||||
'message': _('Problem sending email.')
|
|
||||||
})
|
|
||||||
trans.state = BankTransaction.STATE_VALID
|
trans.state = BankTransaction.STATE_VALID
|
||||||
trans.save()
|
trans.save()
|
||||||
trans.order.payments.filter(
|
trans.order.payments.filter(
|
||||||
|
|||||||
@@ -57,7 +57,6 @@ from pretix.base.decimal import round_decimal
|
|||||||
from pretix.base.forms import SecretKeySettingsField
|
from pretix.base.forms import SecretKeySettingsField
|
||||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||||
from pretix.base.payment import BasePaymentProvider, PaymentException
|
from pretix.base.payment import BasePaymentProvider, PaymentException
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
from pretix.multidomain.urlreverse import build_absolute_uri
|
from pretix.multidomain.urlreverse import build_absolute_uri
|
||||||
from pretix.plugins.paypal.api import Api
|
from pretix.plugins.paypal.api import Api
|
||||||
@@ -468,9 +467,6 @@ class Paypal(BasePaymentProvider):
|
|||||||
payment_obj.confirm()
|
payment_obj.confirm()
|
||||||
except Quota.QuotaExceededException as e:
|
except Quota.QuotaExceededException as e:
|
||||||
raise PaymentException(str(e))
|
raise PaymentException(str(e))
|
||||||
|
|
||||||
except SendMailException:
|
|
||||||
messages.warning(request, _('There was an error sending the confirmation mail.'))
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def payment_pending_render(self, request, payment) -> str:
|
def payment_pending_render(self, request, payment) -> str:
|
||||||
|
|||||||
@@ -54,7 +54,6 @@ from pretix.base.forms import SecretKeySettingsField
|
|||||||
from pretix.base.forms.questions import guess_country
|
from pretix.base.forms.questions import guess_country
|
||||||
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
from pretix.base.models import Event, Order, OrderPayment, OrderRefund, Quota
|
||||||
from pretix.base.payment import BasePaymentProvider, PaymentException
|
from pretix.base.payment import BasePaymentProvider, PaymentException
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
from pretix.helpers import OF_SELF
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
from pretix.helpers.urls import build_absolute_uri as build_global_uri
|
||||||
@@ -821,9 +820,6 @@ class PaypalMethod(BasePaymentProvider):
|
|||||||
payment.confirm()
|
payment.confirm()
|
||||||
except Quota.QuotaExceededException as e:
|
except Quota.QuotaExceededException as e:
|
||||||
raise PaymentException(str(e))
|
raise PaymentException(str(e))
|
||||||
|
|
||||||
except SendMailException:
|
|
||||||
messages.warning(request, _('There was an error sending the confirmation mail.'))
|
|
||||||
finally:
|
finally:
|
||||||
if 'payment_paypal_oid' in request.session:
|
if 'payment_paypal_oid' in request.session:
|
||||||
del request.session['payment_paypal_oid']
|
del request.session['payment_paypal_oid']
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ from pretix.base.models import (
|
|||||||
fields,
|
fields,
|
||||||
)
|
)
|
||||||
from pretix.base.models.base import LoggingMixin
|
from pretix.base.models.base import LoggingMixin
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduledMail(models.Model):
|
class ScheduledMail(models.Model):
|
||||||
@@ -180,13 +179,10 @@ class ScheduledMail(models.Model):
|
|||||||
invoice_address=ia,
|
invoice_address=ia,
|
||||||
event_or_subevent=self.subevent or e,
|
event_or_subevent=self.subevent or e,
|
||||||
)
|
)
|
||||||
try:
|
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||||
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
attach_ical=self.rule.attach_ical,
|
||||||
attach_ical=self.rule.attach_ical,
|
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
||||||
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
o_sent = True
|
||||||
o_sent = True
|
|
||||||
except SendMailException:
|
|
||||||
... # ¯\_(ツ)_/¯
|
|
||||||
|
|
||||||
if send_to_attendees:
|
if send_to_attendees:
|
||||||
if not self.rule.all_products:
|
if not self.rule.all_products:
|
||||||
@@ -195,31 +191,28 @@ class ScheduledMail(models.Model):
|
|||||||
positions = [p for p in positions if p.subevent_id == self.subevent_id]
|
positions = [p for p in positions if p.subevent_id == self.subevent_id]
|
||||||
|
|
||||||
for p in positions:
|
for p in positions:
|
||||||
try:
|
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
|
||||||
if p.attendee_email and (p.attendee_email != o.email or not o_sent):
|
email_ctx = get_email_context(
|
||||||
email_ctx = get_email_context(
|
event=e,
|
||||||
event=e,
|
order=o,
|
||||||
order=o,
|
invoice_address=ia,
|
||||||
invoice_address=ia,
|
position=p,
|
||||||
position=p,
|
event_or_subevent=self.subevent or e,
|
||||||
event_or_subevent=self.subevent or e,
|
)
|
||||||
)
|
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||||
p.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
attach_ical=self.rule.attach_ical,
|
||||||
attach_ical=self.rule.attach_ical,
|
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
|
||||||
log_entry_type='pretix.plugins.sendmail.rule.order.position.email.sent')
|
elif not o_sent and o.email:
|
||||||
elif not o_sent and o.email:
|
email_ctx = get_email_context(
|
||||||
email_ctx = get_email_context(
|
event=e,
|
||||||
event=e,
|
order=o,
|
||||||
order=o,
|
invoice_address=ia,
|
||||||
invoice_address=ia,
|
event_or_subevent=self.subevent or e,
|
||||||
event_or_subevent=self.subevent or e,
|
)
|
||||||
)
|
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
||||||
o.send_mail(self.rule.subject, self.rule.template, email_ctx,
|
attach_ical=self.rule.attach_ical,
|
||||||
attach_ical=self.rule.attach_ical,
|
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
||||||
log_entry_type='pretix.plugins.sendmail.rule.order.email.sent')
|
o_sent = True
|
||||||
o_sent = True
|
|
||||||
except SendMailException:
|
|
||||||
... # ¯\_(ツ)_/¯
|
|
||||||
|
|
||||||
self.last_successful_order_id = o.pk
|
self.last_successful_order_id = o.pk
|
||||||
|
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ from pretix.base.i18n import language
|
|||||||
from pretix.base.models import (
|
from pretix.base.models import (
|
||||||
CachedFile, Checkin, Event, InvoiceAddress, Order, User,
|
CachedFile, Checkin, Event, InvoiceAddress, Order, User,
|
||||||
)
|
)
|
||||||
from pretix.base.services.mail import SendMailException, 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
|
from pretix.helpers.format import format_map
|
||||||
@@ -53,7 +53,6 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
|||||||
recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
|
recipients: str, filter_checkins: bool, not_checked_in: bool, checkin_lists: list,
|
||||||
attachments: list = None, attach_tickets: bool = False,
|
attachments: list = None, attach_tickets: bool = False,
|
||||||
attach_ical: bool = False) -> None:
|
attach_ical: bool = False) -> None:
|
||||||
failures = []
|
|
||||||
user = User.objects.get(pk=user) if user else None
|
user = User.objects.get(pk=user) if user else None
|
||||||
orders = Order.objects.filter(pk__in=objects, event=event)
|
orders = Order.objects.filter(pk__in=objects, event=event)
|
||||||
subject = LazyI18nString(subject)
|
subject = LazyI18nString(subject)
|
||||||
@@ -114,70 +113,64 @@ def send_mails_to_orders(event: Event, user: int, subject: dict, message: dict,
|
|||||||
if subevents_to and p.subevent.date_from >= subevents_to:
|
if subevents_to and p.subevent.date_from >= subevents_to:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
try:
|
|
||||||
with language(o.locale, event.settings.region):
|
|
||||||
email_context = get_email_context(event=event, order=o, invoice_address=ia, position=p)
|
|
||||||
mail(
|
|
||||||
p.attendee_email,
|
|
||||||
subject,
|
|
||||||
message,
|
|
||||||
email_context,
|
|
||||||
event,
|
|
||||||
locale=o.locale,
|
|
||||||
order=o,
|
|
||||||
position=p,
|
|
||||||
attach_tickets=attach_tickets,
|
|
||||||
attach_ical=attach_ical,
|
|
||||||
attach_cached_files=attachments
|
|
||||||
)
|
|
||||||
o.log_action(
|
|
||||||
'pretix.plugins.sendmail.order.email.sent.attendee',
|
|
||||||
user=user,
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
except SendMailException:
|
|
||||||
failures.append(p.attendee_email)
|
|
||||||
|
|
||||||
if send_to_order and o.email:
|
|
||||||
try:
|
|
||||||
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, position=p)
|
||||||
mail(
|
mail(
|
||||||
o.email,
|
p.attendee_email,
|
||||||
subject,
|
subject,
|
||||||
message,
|
message,
|
||||||
email_context,
|
email_context,
|
||||||
event,
|
event,
|
||||||
locale=o.locale,
|
locale=o.locale,
|
||||||
order=o,
|
order=o,
|
||||||
|
position=p,
|
||||||
attach_tickets=attach_tickets,
|
attach_tickets=attach_tickets,
|
||||||
attach_ical=attach_ical,
|
attach_ical=attach_ical,
|
||||||
attach_cached_files=attachments,
|
attach_cached_files=attachments
|
||||||
)
|
)
|
||||||
o.log_action(
|
o.log_action(
|
||||||
'pretix.plugins.sendmail.order.email.sent',
|
'pretix.plugins.sendmail.order.email.sent.attendee',
|
||||||
user=user,
|
user=user,
|
||||||
data={
|
data={
|
||||||
|
'position': p.positionid,
|
||||||
'subject': format_map(subject.localize(o.locale), email_context),
|
'subject': format_map(subject.localize(o.locale), email_context),
|
||||||
'message': format_map(message.localize(o.locale), email_context),
|
'message': format_map(message.localize(o.locale), email_context),
|
||||||
'recipient': o.email,
|
'recipient': p.attendee_email,
|
||||||
'attach_tickets': attach_tickets,
|
'attach_tickets': attach_tickets,
|
||||||
'attach_ical': attach_ical,
|
'attach_ical': attach_ical,
|
||||||
'attach_other_files': [],
|
'attach_other_files': [],
|
||||||
'attach_cached_files': attachments_for_log,
|
'attach_cached_files': attachments_for_log,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
except SendMailException:
|
|
||||||
failures.append(o.email)
|
if send_to_order and o.email:
|
||||||
|
with language(o.locale, event.settings.region):
|
||||||
|
email_context = get_email_context(event=event, order=o, invoice_address=ia)
|
||||||
|
mail(
|
||||||
|
o.email,
|
||||||
|
subject,
|
||||||
|
message,
|
||||||
|
email_context,
|
||||||
|
event,
|
||||||
|
locale=o.locale,
|
||||||
|
order=o,
|
||||||
|
attach_tickets=attach_tickets,
|
||||||
|
attach_ical=attach_ical,
|
||||||
|
attach_cached_files=attachments,
|
||||||
|
)
|
||||||
|
o.log_action(
|
||||||
|
'pretix.plugins.sendmail.order.email.sent',
|
||||||
|
user=user,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.task(base=ProfiledEventTask, acks_late=True)
|
@app.task(base=ProfiledEventTask, acks_late=True)
|
||||||
|
|||||||
@@ -71,7 +71,6 @@ from pretix.base.payment import (
|
|||||||
BasePaymentProvider, PaymentException, WalletQueries,
|
BasePaymentProvider, PaymentException, WalletQueries,
|
||||||
)
|
)
|
||||||
from pretix.base.plugins import get_all_plugins
|
from pretix.base.plugins import get_all_plugins
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.settings import SettingsSandbox
|
from pretix.base.settings import SettingsSandbox
|
||||||
from pretix.helpers import OF_SELF
|
from pretix.helpers import OF_SELF
|
||||||
from pretix.helpers.countries import CachedCountries
|
from pretix.helpers.countries import CachedCountries
|
||||||
@@ -980,9 +979,6 @@ class StripeMethod(BasePaymentProvider):
|
|||||||
payment.confirm()
|
payment.confirm()
|
||||||
except Quota.QuotaExceededException as e:
|
except Quota.QuotaExceededException as e:
|
||||||
raise PaymentException(str(e))
|
raise PaymentException(str(e))
|
||||||
|
|
||||||
except SendMailException:
|
|
||||||
raise PaymentException(_('There was an error sending the confirmation mail.'))
|
|
||||||
elif intent.status == 'processing':
|
elif intent.status == 'processing':
|
||||||
if request:
|
if request:
|
||||||
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
|
messages.warning(request, _('Your payment is pending completion. We will inform you as soon as the '
|
||||||
|
|||||||
@@ -1643,11 +1643,6 @@ class ConfirmStep(CartMixin, AsyncAction, TemplateFlowStep):
|
|||||||
order = Order.objects.get(id=value)
|
order = Order.objects.get(id=value)
|
||||||
return self.get_order_url(order)
|
return self.get_order_url(order)
|
||||||
|
|
||||||
def get_error_message(self, exception):
|
|
||||||
if exception.__class__.__name__ == 'SendMailException':
|
|
||||||
return _('There was an error sending the confirmation mail. Please try again later.')
|
|
||||||
return super().get_error_message(exception)
|
|
||||||
|
|
||||||
def get_error_url(self):
|
def get_error_url(self):
|
||||||
return self.get_step_url(self.request)
|
return self.get_step_url(self.request)
|
||||||
|
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ from pretix.base.services.invoices import (
|
|||||||
generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task,
|
generate_cancellation, generate_invoice, invoice_pdf, invoice_pdf_task,
|
||||||
invoice_qualified,
|
invoice_qualified,
|
||||||
)
|
)
|
||||||
from pretix.base.services.mail import SendMailException
|
|
||||||
from pretix.base.services.orders import (
|
from pretix.base.services.orders import (
|
||||||
OrderChangeManager, OrderError, _try_auto_refund, cancel_order,
|
OrderChangeManager, OrderError, _try_auto_refund, cancel_order,
|
||||||
change_payment_provider, error_messages,
|
change_payment_provider, error_messages,
|
||||||
@@ -595,10 +594,7 @@ class OrderPayChangeMethod(EventViewMixin, OrderDetailMixin, TemplateView):
|
|||||||
amount=Decimal('0.00'),
|
amount=Decimal('0.00'),
|
||||||
fee=None
|
fee=None
|
||||||
)
|
)
|
||||||
try:
|
p.confirm()
|
||||||
p.confirm()
|
|
||||||
except SendMailException:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
p._mark_order_paid(
|
p._mark_order_paid(
|
||||||
payment_refund_sum=self.order.payment_refund_sum
|
payment_refund_sum=self.order.payment_refund_sum
|
||||||
|
|||||||
@@ -32,8 +32,6 @@
|
|||||||
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations under the License.
|
# License for the specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
@@ -42,7 +40,7 @@ from django.views import View
|
|||||||
from django.views.generic import TemplateView
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
from pretix.base.email import get_email_context
|
from pretix.base.email import get_email_context
|
||||||
from pretix.base.services.mail import INVALID_ADDRESS, SendMailException, mail
|
from pretix.base.services.mail import INVALID_ADDRESS, mail
|
||||||
from pretix.helpers.http import redirect_to_url
|
from pretix.helpers.http import redirect_to_url
|
||||||
from pretix.multidomain.urlreverse import eventreverse
|
from pretix.multidomain.urlreverse import eventreverse
|
||||||
from pretix.presale.forms.user import ResendLinkForm
|
from pretix.presale.forms.user import ResendLinkForm
|
||||||
@@ -83,13 +81,7 @@ class ResendLinkView(EventViewMixin, TemplateView):
|
|||||||
subject = self.request.event.settings.mail_subject_resend_all_links
|
subject = self.request.event.settings.mail_subject_resend_all_links
|
||||||
template = self.request.event.settings.mail_text_resend_all_links
|
template = self.request.event.settings.mail_text_resend_all_links
|
||||||
context = get_email_context(event=self.request.event, orders=orders)
|
context = get_email_context(event=self.request.event, orders=orders)
|
||||||
try:
|
mail(user, subject, template, context, event=self.request.event, locale=self.request.LANGUAGE_CODE)
|
||||||
mail(user, subject, template, context, event=self.request.event, locale=self.request.LANGUAGE_CODE)
|
|
||||||
except SendMailException:
|
|
||||||
logger = logging.getLogger('pretix.presale.user')
|
|
||||||
logger.exception('A mail resending order links to {} could not be sent.'.format(user))
|
|
||||||
messages.error(self.request, _('We have trouble sending emails right now, please check back later.'))
|
|
||||||
return self.get(request, *args, **kwargs)
|
|
||||||
|
|
||||||
messages.success(self.request, _('If there were any orders by this user, they will receive an email with their order codes.'))
|
messages.success(self.request, _('If there were any orders by this user, they will receive an email with their order codes.'))
|
||||||
return redirect_to_url(eventreverse(self.request.event, 'presale:event.index'))
|
return redirect_to_url(eventreverse(self.request.event, 'presale:event.index'))
|
||||||
|
|||||||
10
src/pretix/testutils/mail.py
Normal file
10
src/pretix/testutils/mail.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import smtplib
|
||||||
|
|
||||||
|
from django.core.mail.backends.locmem import EmailBackend
|
||||||
|
|
||||||
|
|
||||||
|
class FailingEmailBackend(EmailBackend):
|
||||||
|
def send_messages(self, email_messages):
|
||||||
|
raise smtplib.SMTPRecipientsRefused({
|
||||||
|
'recipient@example.org': (450, b'Recipient unknown')
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user