diff --git a/src/pretix/base/models/orders.py b/src/pretix/base/models/orders.py index 6799ad34dd..9de6beecc0 100644 --- a/src/pretix/base/models/orders.py +++ b/src/pretix/base/models/orders.py @@ -723,7 +723,8 @@ class Order(LockModel, 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, attach_tickets=False, position: 'OrderPosition'=None, auto_email=True): + auth=None, attach_tickets=False, position: 'OrderPosition'=None, auto_email=True, + attach_ical=False): """ Sends an email to the user that placed this order. Basically, this method does two things: @@ -740,6 +741,7 @@ class Order(LockModel, LoggedModel): :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 + :param attach_ical: Attach relevant ICS files :param position: An order position this refers to. If given, no invoices will be attached, the tickets will 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. @@ -763,7 +765,7 @@ class Order(LockModel, LoggedModel): recipient, subject, template, context, self.event, self.locale, self, headers=headers, sender=sender, invoices=invoices, attach_tickets=attach_tickets, - position=position, auto_email=auto_email + position=position, auto_email=auto_email, attach_ical=attach_ical ) except SendMailException: raise @@ -779,6 +781,7 @@ class Order(LockModel, LoggedModel): 'recipient': recipient, 'invoices': [i.pk for i in invoices] if invoices else [], 'attach_tickets': attach_tickets, + 'attach_ical': attach_ical, } ) @@ -790,7 +793,7 @@ class Order(LockModel, LoggedModel): self.send_mail( email_subject, email_template, email_context, 'pretix.event.order.email.resend', user=user, auth=auth, - attach_tickets=True + attach_tickets=True, ) @property @@ -1334,7 +1337,7 @@ class OrderPayment(models.Model): email_subject, email_template, email_context, 'pretix.event.order.email.order_paid', user, invoices=[], position=position, - attach_tickets=True + attach_tickets=True, attach_ical=True ) except SendMailException: logger.exception('Order paid email could not be sent') @@ -1351,7 +1354,7 @@ class OrderPayment(models.Model): 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 [], - attach_tickets=True + attach_tickets=True, attach_ical=True ) except SendMailException: logger.exception('Order paid email could not be sent') diff --git a/src/pretix/base/services/mail.py b/src/pretix/base/services/mail.py index 528f341e6f..86d99c4489 100644 --- a/src/pretix/base/services/mail.py +++ b/src/pretix/base/services/mail.py @@ -31,6 +31,7 @@ from pretix.base.services.tickets import get_tickets_for_order from pretix.base.signals import email_filter, global_email_filter from pretix.celery_app import app from pretix.multidomain.urlreverse import build_absolute_uri +from pretix.presale.ical import get_ical logger = logging.getLogger('pretix.base.mail') INVALID_ADDRESS = 'invalid-pretix-mail-address' @@ -50,7 +51,7 @@ 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, position: OrderPosition=None, headers: dict=None, sender: str=None, - invoices: list=None, attach_tickets=False, auto_email=True, user=None): + invoices: list=None, attach_tickets=False, auto_email=True, user=None, attach_ical=False): """ Sends out an email to a user. The mail will be sent synchronously or asynchronously depending on the installation. @@ -85,6 +86,8 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString], :param attach_tickets: Whether to attach tickets to this email, if they are available to download. + :param attach_ical: Whether to attach relevant ``.ics`` files to this email + :param auto_email: Whether this email is auto-generated :param user: The user this email is sent to @@ -216,6 +219,7 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString], order=order.pk if order else None, position=position.pk if position else None, attach_tickets=attach_tickets, + attach_ical=attach_ical, user=user.pk if user else None ) @@ -231,7 +235,8 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString], @app.task(base=TransactionAwareTask, bind=True) def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: str, sender: str, event: int=None, position: int=None, headers: dict=None, bcc: List[str]=None, - invoices: List[int]=None, order: int=None, attach_tickets=False, user=None) -> bool: + invoices: List[int]=None, order: int=None, attach_tickets=False, user=None, + attach_ical=False) -> bool: email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers) if html is not None: html_with_cid, cid_images = replace_images_with_cid_paths(html) @@ -301,6 +306,20 @@ def mail_send_task(self, *args, to: List[str], subject: str, body: str, html: st 'invoices': [], } ) + if attach_ical: + ical_events = set() + if event.has_subevents: + if position: + ical_events.add(position.subevent) + else: + for p in order.positions.all(): + ical_events.add(p.subevent) + else: + ical_events.add(order.event) + + for i, e in enumerate(ical_events): + cal = get_ical([e]) + email.attach('event-{}.ics'.format(i), cal.serialize(), 'text/calendar') email = email_filter.send_chained(event, 'message', message=email, order=order, user=user) diff --git a/src/pretix/base/services/orders.py b/src/pretix/base/services/orders.py index 11dff6e4c6..ca0c4f2ccc 100644 --- a/src/pretix/base/services/orders.py +++ b/src/pretix/base/services/orders.py @@ -707,7 +707,8 @@ def _order_placed_email(event: Event, order: Order, pprov: BasePaymentProvider, email_subject, email_template, email_context, log_entry, invoices=[invoice] if invoice and event.settings.invoice_email_attachment else [], - attach_tickets=True + attach_tickets=True, + attach_ical=event.settings.mail_attach_ical ) except SendMailException: logger.exception('Order received email could not be sent') @@ -723,7 +724,8 @@ def _order_placed_email_attendee(event: Event, order: Order, position: OrderPosi log_entry, invoices=[], attach_tickets=True, - position=position + position=position, + attach_ical=event.settings.mail_attach_ical ) except SendMailException: logger.exception('Order received email could not be sent to attendee') diff --git a/src/pretix/base/settings.py b/src/pretix/base/settings.py index 790d346a3e..8e0069ff49 100644 --- a/src/pretix/base/settings.py +++ b/src/pretix/base/settings.py @@ -297,6 +297,10 @@ DEFAULTS = { 'default': 'classic', 'type': str }, + 'mail_attach_ical': { + 'default': 'False', + 'type': bool + }, 'mail_prefix': { 'default': None, 'type': str diff --git a/src/pretix/control/forms/event.py b/src/pretix/control/forms/event.py index 9a32ad66be..090476dd79 100644 --- a/src/pretix/control/forms/event.py +++ b/src/pretix/control/forms/event.py @@ -976,6 +976,11 @@ class MailSettingsForm(SettingsForm): required=False, max_length=255 ) + mail_attach_ical = forms.BooleanField( + label=_("Attach calendar files"), + help_text=_("If enabled, we will attach an .ics calendar file to order confirmation emails."), + required=False + ) mail_text_signature = I18nFormField( label=_("Signature"), diff --git a/src/pretix/control/templates/pretixcontrol/event/mail.html b/src/pretix/control/templates/pretixcontrol/event/mail.html index 50ea8a09b7..0988ebaccc 100644 --- a/src/pretix/control/templates/pretixcontrol/event/mail.html +++ b/src/pretix/control/templates/pretixcontrol/event/mail.html @@ -16,6 +16,7 @@ {% bootstrap_field form.mail_from_name layout="control" %} {% bootstrap_field form.mail_text_signature layout="control" %} {% bootstrap_field form.mail_bcc layout="control" %} + {% bootstrap_field form.mail_attach_ical layout="control" %}