mirror of
https://github.com/pretix/pretix.git
synced 2026-09-13 16:14:40 +00:00
@@ -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')
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.'))
|
||||||
|
|||||||
Reference in New Issue
Block a user