Fix #917 -- Attach tickets to emails (#1034)

This commit is contained in:
Raphael Michel
2018-10-03 11:06:50 +02:00
committed by GitHub
parent 0787adcb8e
commit d99517c8d1
5 changed files with 104 additions and 16 deletions
+5 -3
View File
@@ -496,7 +496,7 @@ class Order(LoggedModel):
def send_mail(self, subject: str, template: Union[str, LazyI18nString], def send_mail(self, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent', context: Dict[str, Any]=None, log_entry_type: str='pretix.event.order.email.sent',
user: User=None, headers: dict=None, sender: str=None, invoices: list=None, user: User=None, headers: dict=None, sender: str=None, invoices: list=None,
auth=None): auth=None, attach_tickets=False):
""" """
Sends an email to the user that placed this order. Basically, this method does two things: Sends an email to the user that placed this order. Basically, this method does two things:
@@ -512,6 +512,7 @@ class Order(LoggedModel):
:param user: Administrative user who triggered this mail to be sent :param user: Administrative user who triggered this mail to be sent
:param headers: Dictionary with additional mail headers :param headers: Dictionary with additional mail headers
:param sender: Custom email sender. :param sender: Custom email sender.
:param attach_tickets: Attach tickets of this order, if they are existing and ready to download
""" """
from pretix.base.services.mail import SendMailException, mail, render_mail from pretix.base.services.mail import SendMailException, mail, render_mail
@@ -525,7 +526,7 @@ class Order(LoggedModel):
mail( mail(
recipient, subject, template, context, recipient, subject, template, context,
self.event, self.locale, self, headers, sender, self.event, self.locale, self, headers, sender,
invoices=invoices invoices=invoices, attach_tickets=attach_tickets
) )
except SendMailException: except SendMailException:
raise raise
@@ -991,7 +992,8 @@ class OrderPayment(models.Model):
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 and self.order.event.settings.invoice_email_attachment else [] invoices=[invoice] if invoice and self.order.event.settings.invoice_email_attachment else [],
attach_tickets=True
) )
except SendMailException: except SendMailException:
logger.exception('Order paid email could not be sent') logger.exception('Order paid email could not be sent')
+18 -3
View File
@@ -14,6 +14,7 @@ from pretix.base.email import ClassicMailRenderer
from pretix.base.i18n import language from pretix.base.i18n import language
from pretix.base.models import Event, Invoice, InvoiceAddress, Order from pretix.base.models import Event, Invoice, InvoiceAddress, Order
from pretix.base.services.invoices import invoice_pdf_task from pretix.base.services.invoices import invoice_pdf_task
from pretix.base.services.tickets import get_tickets_for_order
from pretix.base.signals import email_filter from pretix.base.signals import email_filter
from pretix.celery_app import app from pretix.celery_app import app
from pretix.multidomain.urlreverse import build_absolute_uri from pretix.multidomain.urlreverse import build_absolute_uri
@@ -35,7 +36,8 @@ class SendMailException(Exception):
def mail(email: str, subject: str, template: Union[str, LazyI18nString], def mail(email: str, subject: str, template: Union[str, LazyI18nString],
context: Dict[str, Any]=None, event: Event=None, locale: str=None, context: Dict[str, Any]=None, event: Event=None, locale: str=None,
order: Order=None, headers: dict=None, sender: str=None, invoices: list=None): order: Order=None, headers: dict=None, sender: str=None, invoices: list=None,
attach_tickets=False):
""" """
Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation. Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation.
@@ -65,6 +67,8 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
:param invoices: A list of invoices to attach to this email. :param invoices: A list of invoices to attach to this email.
:param attach_tickets: Whether to attach tickets to this email, if they are available to download.
:raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean :raises MailOrderException: on obvious, immediate failures. Not raising an exception does not necessarily mean
that the email has been sent, just that it has been queued by the email backend. that the email has been sent, just that it has been queued by the email backend.
""" """
@@ -153,7 +157,8 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
event=event.id if event else None, event=event.id if event else None,
headers=headers, headers=headers,
invoices=[i.pk for i in invoices] if invoices else [], invoices=[i.pk for i in invoices] if invoices else [],
order=order.pk if order else None order=order.pk if order else None,
attach_tickets=attach_tickets
) )
if invoices: if invoices:
@@ -168,7 +173,7 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
@app.task @app.task
def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sender: str, def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sender: str,
event: int=None, headers: dict=None, bcc: List[str]=None, invoices: List[int]=None, event: int=None, headers: dict=None, bcc: List[str]=None, invoices: List[int]=None,
order: int=None) -> bool: order: int=None, attach_tickets=False) -> bool:
email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers) email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers)
if html is not None: if html is not None:
email.attach_alternative(html, "text/html") email.attach_alternative(html, "text/html")
@@ -185,6 +190,7 @@ def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sen
except: except:
logger.exception('Could not attach invoice to email') logger.exception('Could not attach invoice to email')
pass pass
if event: if event:
event = Event.objects.get(id=event) event = Event.objects.get(id=event)
backend = event.get_mail_backend() backend = event.get_mail_backend()
@@ -197,6 +203,15 @@ def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sen
order = event.orders.get(pk=order) order = event.orders.get(pk=order)
except Order.DoesNotExist: except Order.DoesNotExist:
order = None order = None
else:
if attach_tickets:
for name, ct in get_tickets_for_order(order):
email.attach(
name,
ct.file.read(),
ct.type
)
email = email_filter.send_chained(event, 'message', message=email, order=order) email = email_filter.send_chained(event, 'message', message=email, order=order)
try: try:
+4 -2
View File
@@ -633,7 +633,8 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
order.send_mail( order.send_mail(
email_subject, email_template, email_context, email_subject, email_template, email_context,
log_entry, log_entry,
invoices=[invoice] if invoice and event.settings.invoice_email_attachment else [] invoices=[invoice] if invoice and event.settings.invoice_email_attachment else [],
attach_tickets=(payment_provider == 'free')
) )
except SendMailException: except SendMailException:
logger.exception('Order received email could not be sent') logger.exception('Order received email could not be sent')
@@ -735,7 +736,8 @@ def send_download_reminders(sender, **kwargs):
try: 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
) )
except SendMailException: except SendMailException:
logger.exception('Reminder email could not be sent') logger.exception('Reminder email could not be sent')
+75 -7
View File
@@ -1,5 +1,7 @@
import logging
import os import os
from datetime import timedelta from datetime import timedelta
from decimal import Decimal
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.utils.timezone import now from django.utils.timezone import now
@@ -11,10 +13,12 @@ from pretix.base.models import (
OrderPosition, OrderPosition,
) )
from pretix.base.services.tasks import ProfiledTask from pretix.base.services.tasks import ProfiledTask
from pretix.base.signals import register_ticket_outputs from pretix.base.signals import allow_ticket_download, register_ticket_outputs
from pretix.celery_app import app from pretix.celery_app import app
from pretix.helpers.database import rolledback_transaction from pretix.helpers.database import rolledback_transaction
logger = logging.getLogger(__name__)
@app.task(base=ProfiledTask) @app.task(base=ProfiledTask)
def generate(order_position: str, provider: str): def generate(order_position: str, provider: str):
@@ -97,7 +101,8 @@ def preview(event: int, provider: str):
return prov.generate(p) return prov.generate(p)
def get_cachedticket_for_position(pos, identifier): def get_cachedticket_for_position(pos, identifier, generate_async=True):
apply_method = 'apply_async' if generate_async else 'apply'
try: try:
ct = CachedTicket.objects.filter( ct = CachedTicket.objects.filter(
order_position=pos, provider=identifier order_position=pos, provider=identifier
@@ -109,15 +114,20 @@ def get_cachedticket_for_position(pos, identifier):
ct = CachedTicket.objects.create( ct = CachedTicket.objects.create(
order_position=pos, provider=identifier, order_position=pos, provider=identifier,
extension='', type='', file=None) extension='', type='', file=None)
generate.apply_async(args=(pos.id, identifier)) getattr(generate, apply_method)(args=(pos.id, identifier))
if not generate_async:
ct.refresh_from_db()
if not ct.file: if not ct.file:
if now() - ct.created > timedelta(minutes=5): if now() - ct.created > timedelta(minutes=5):
generate.apply_async(args=(pos.id, identifier)) getattr(generate, apply_method)(args=(pos.id, identifier))
if not generate_async:
ct.refresh_from_db()
return ct return ct
def get_cachedticket_for_order(order, identifier): def get_cachedticket_for_order(order, identifier, generate_async=True):
apply_method = 'apply_async' if generate_async else 'apply'
try: try:
ct = CachedCombinedTicket.objects.filter( ct = CachedCombinedTicket.objects.filter(
order=order, provider=identifier order=order, provider=identifier
@@ -129,9 +139,67 @@ def get_cachedticket_for_order(order, identifier):
ct = CachedCombinedTicket.objects.create( ct = CachedCombinedTicket.objects.create(
order=order, provider=identifier, order=order, provider=identifier,
extension='', type='', file=None) extension='', type='', file=None)
generate_order.apply_async(args=(order.id, identifier)) getattr(generate_order, apply_method)(args=(order.id, identifier))
if not generate_async:
ct.refresh_from_db()
if not ct.file: if not ct.file:
if now() - ct.created > timedelta(minutes=5): if now() - ct.created > timedelta(minutes=5):
generate_order.apply_async(args=(order.id, identifier)) getattr(generate_order, apply_method)(args=(order.id, identifier))
if not generate_async:
ct.refresh_from_db()
return ct return ct
def get_tickets_for_order(order):
can_download = all([r for rr, r in allow_ticket_download.send(order.event, order=order)])
if not can_download:
return []
if order.status != Order.STATUS_PAID and order.total != Decimal("0.00"):
return []
if (not order.event.settings.ticket_download
or (order.event.settings.ticket_download_date is not None
and now() < order.ticket_download_date)):
return []
providers = [
response(order.event)
for receiver, response
in register_ticket_outputs.send(order.event)
]
tickets = []
for p in providers:
if not p.is_enabled:
continue
if p.multi_download_enabled:
try:
ct = get_cachedticket_for_order(order, p.identifier, generate_async=False)
tickets.append((
"{}-{}-{}{}".format(
order.event.slug.upper(), order.code, ct.provider, ct.extension,
),
ct
))
except:
logger.exception('Failed to generate ticket.')
else:
for pos in order.positions.all():
if pos.addon_to and not order.event.settings.ticket_download_addons:
continue
if not pos.item.admission and not order.event.settings.ticket_download_nonadm:
continue
try:
ct = get_cachedticket_for_position(pos, p.identifier, generate_async=False)
tickets.append((
"{}-{}-{}-{}{}".format(
order.event.slug.upper(), order.code, pos.positionid, ct.provider, ct.extension,
),
ct
))
except:
logger.exception('Failed to generate ticket.')
return tickets
+2 -1
View File
@@ -866,7 +866,8 @@ class OrderResendLink(OrderView):
email_subject = _('Your order: %(code)s') % {'code': self.order.code} email_subject = _('Your order: %(code)s') % {'code': self.order.code}
self.order.send_mail( self.order.send_mail(
email_subject, email_template, email_context, email_subject, email_template, email_context,
'pretix.event.order.email.resend', user=self.request.user 'pretix.event.order.email.resend', user=self.request.user,
attach_tickets=True
) )
except SendMailException: except SendMailException:
messages.error(self.request, _('There was an error sending the mail. Please try again later.')) messages.error(self.request, _('There was an error sending the mail. Please try again later.'))