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

View File

@@ -496,7 +496,7 @@ class Order(LoggedModel):
def send_mail(self, subject: str, template: Union[str, LazyI18nString],
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,
auth=None):
auth=None, attach_tickets=False):
"""
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 headers: Dictionary with additional mail headers
: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
@@ -525,7 +526,7 @@ class Order(LoggedModel):
mail(
recipient, subject, template, context,
self.event, self.locale, self, headers, sender,
invoices=invoices
invoices=invoices, attach_tickets=attach_tickets
)
except SendMailException:
raise
@@ -991,7 +992,8 @@ class OrderPayment(models.Model):
self.order.send_mail(
email_subject, email_template, email_context,
'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:
logger.exception('Order paid email could not be sent')

View File

@@ -14,6 +14,7 @@ from pretix.base.email import ClassicMailRenderer
from pretix.base.i18n import language
from pretix.base.models import Event, Invoice, InvoiceAddress, Order
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.celery_app import app
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],
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.
@@ -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 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
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,
headers=headers,
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:
@@ -168,7 +173,7 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString],
@app.task
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,
order: int=None) -> bool:
order: int=None, attach_tickets=False) -> bool:
email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers)
if html is not None:
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:
logger.exception('Could not attach invoice to email')
pass
if event:
event = Event.objects.get(id=event)
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)
except Order.DoesNotExist:
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)
try:

View File

@@ -633,7 +633,8 @@ def _perform_order(event: str, payment_provider: str, position_ids: List[str],
order.send_mail(
email_subject, email_template, email_context,
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:
logger.exception('Order received email could not be sent')
@@ -735,7 +736,8 @@ def send_download_reminders(sender, **kwargs):
try:
o.send_mail(
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:
logger.exception('Reminder email could not be sent')

View File

@@ -1,5 +1,7 @@
import logging
import os
from datetime import timedelta
from decimal import Decimal
from django.core.files.base import ContentFile
from django.utils.timezone import now
@@ -11,10 +13,12 @@ from pretix.base.models import (
OrderPosition,
)
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.helpers.database import rolledback_transaction
logger = logging.getLogger(__name__)
@app.task(base=ProfiledTask)
def generate(order_position: str, provider: str):
@@ -97,7 +101,8 @@ def preview(event: int, provider: str):
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:
ct = CachedTicket.objects.filter(
order_position=pos, provider=identifier
@@ -109,15 +114,20 @@ def get_cachedticket_for_position(pos, identifier):
ct = CachedTicket.objects.create(
order_position=pos, provider=identifier,
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 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
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:
ct = CachedCombinedTicket.objects.filter(
order=order, provider=identifier
@@ -129,9 +139,67 @@ def get_cachedticket_for_order(order, identifier):
ct = CachedCombinedTicket.objects.create(
order=order, provider=identifier,
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 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
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

View File

@@ -866,7 +866,8 @@ class OrderResendLink(OrderView):
email_subject = _('Your order: %(code)s') % {'code': self.order.code}
self.order.send_mail(
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:
messages.error(self.request, _('There was an error sending the mail. Please try again later.'))