From d8064d15672cffc141bbed386ab97bdbf0d9df62 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 23 Nov 2017 18:15:41 +0100 Subject: [PATCH] Add email_filter signal --- doc/development/api/general.rst | 2 +- src/pretix/base/services/mail.py | 15 ++++++- src/pretix/base/signals.py | 74 +++++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/doc/development/api/general.rst b/doc/development/api/general.rst index f456e4a876..b5fefc85a6 100644 --- a/doc/development/api/general.rst +++ b/doc/development/api/general.rst @@ -11,7 +11,7 @@ Core ---- .. automodule:: pretix.base.signals - :members: periodic_task, event_live_issues, event_copy_data + :members: periodic_task, event_live_issues, event_copy_data, email_filter Order events """""""""""" diff --git a/src/pretix/base/services/mail.py b/src/pretix/base/services/mail.py index 87141dae63..7b5057eb72 100644 --- a/src/pretix/base/services/mail.py +++ b/src/pretix/base/services/mail.py @@ -15,6 +15,7 @@ from inlinestyler.utils import inline_css 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.signals import email_filter from pretix.celery_app import app from pretix.multidomain.urlreverse import build_absolute_uri @@ -150,7 +151,8 @@ def mail(email: str, subject: str, template: Union[str, LazyI18nString], sender=sender, event=event.id if event else None, 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 ) if invoices: @@ -164,7 +166,8 @@ 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) -> bool: + event: int=None, headers: dict=None, bcc: List[str]=None, invoices: List[int]=None, + order: int=None) -> bool: email = EmailMultiAlternatives(subject, body, sender, to=to, bcc=bcc, headers=headers) if html is not None: email.attach_alternative(inline_css(html), "text/html") @@ -183,6 +186,14 @@ def mail_send_task(*args, to: List[str], subject: str, body: str, html: str, sen else: backend = get_connection(fail_silently=False) + if event: + if order: + try: + order = event.orders.get(pk=order) + except Order.DoesNotExist: + order = None + email = email_filter.send_chained(event, 'message', message=email, order=order) + try: backend.send_messages([email]) except Exception: diff --git a/src/pretix/base/signals.py b/src/pretix/base/signals.py index 48e12b7df3..ec97639fbc 100644 --- a/src/pretix/base/signals.py +++ b/src/pretix/base/signals.py @@ -25,6 +25,24 @@ class EventPluginSignal(django.dispatch.Signal): Event. """ + def _is_active(self, sender, receiver): + # Find the Django application this belongs to + searchpath = receiver.__module__ + core_module = any([searchpath.startswith(cm) for cm in settings.CORE_MODULES]) + app = None + if not core_module: + while True: + app = app_cache.get(searchpath) + if "." not in searchpath or app: + break + searchpath, _ = searchpath.rsplit(".", 1) + + # Only fire receivers from active plugins and core modules + if core_module or (sender and app and app.name in sender.get_plugins()): + if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors: + return True + return False + def send(self, sender: Event, **named) -> List[Tuple[Callable, Any]]: """ Send signal from sender to all connected receivers that belong to @@ -43,24 +61,35 @@ class EventPluginSignal(django.dispatch.Signal): _populate_app_cache() for receiver in self._live_receivers(sender): - # Find the Django application this belongs to - searchpath = receiver.__module__ - core_module = any([searchpath.startswith(cm) for cm in settings.CORE_MODULES]) - app = None - if not core_module: - while True: - app = app_cache.get(searchpath) - if "." not in searchpath or app: - break - searchpath, _ = searchpath.rsplit(".", 1) - - # Only fire receivers from active plugins and core modules - if core_module or (sender and app and app.name in sender.get_plugins()): - if not hasattr(app, 'compatibility_errors') or not app.compatibility_errors: - response = receiver(signal=self, sender=sender, **named) - responses.append((receiver, response)) + if self._is_active(sender, receiver): + response = receiver(signal=self, sender=sender, **named) + responses.append((receiver, response)) return sorted(responses, key=lambda r: (receiver.__module__, receiver.__name__)) + def send_chained(self, sender: Event, chain_kwarg_name, **named) -> List[Tuple[Callable, Any]]: + """ + Send signal from sender to all connected receivers. The return value of the first receiver + will be used as the keyword argument specified by ``chain_kwarg_name`` in the input to the + second receiver and so on. The return value of the last receiver is returned by this method. + + sender is required to be an instance of ``pretix.base.models.Event``. + """ + if sender and not isinstance(sender, Event): + raise ValueError("Sender needs to be an event.") + + response = named.get(chain_kwarg_name) + if not self.receivers or self.sender_receivers_cache.get(sender) is NO_RECEIVERS: + return response + + if not app_cache: + _populate_app_cache() + + for receiver in self._live_receivers(sender): + if self._is_active(sender, receiver): + named[chain_kwarg_name] = response + response = receiver(signal=self, sender=sender, **named) + return response + class DeprecatedSignal(django.dispatch.Signal): @@ -277,3 +306,16 @@ a download will not be offered. As with all event-plugin signals, the ``sender`` keyword argument will contain the event. """ + +email_filter = EventPluginSignal( + providing_args=['message', 'order'] +) +""" +This signal allows you to implement a middleware-style filter on all outgoing emails. You are expected to +return a (possibly modified) copy of the message object passed to you. + +As with all event-plugin signals, the ``sender`` keyword argument will contain the event. +The ``message`` argument will contian an ``EmailMultiAlternatives`` object. +If the email is associated with a specific order, the ``order`` argument will be passed as well, otherwise +it will be ``None``. +"""