diff --git a/pinaxcon/monkey_patch.py b/pinaxcon/monkey_patch.py index 343968d8..88a1cb35 100644 --- a/pinaxcon/monkey_patch.py +++ b/pinaxcon/monkey_patch.py @@ -12,7 +12,7 @@ class MonkeyPatchMiddleware(object): def do_monkey_patch(): patch_speaker_profile_form() - patch_accounts_to_send_bcc() + patch_mail_to_send_bcc() fix_sitetree_check_access_500s() never_cache_login_page() @@ -33,31 +33,91 @@ def patch_speaker_profile_form(): fields["accessibility"].widget = widgets.AceMarkdownEditor() -def patch_accounts_to_send_bcc(): - ''' Patches django-user-accounts' email functions to send a BCC e-mail to +def patch_mail_to_send_bcc(): + ''' Patches django.core.mail's message classes to send a BCC e-mail to the default BCC e-mail address. ''' - from account import hooks + from django.core.mail import message - # django-user-accounts always uses send_mail like: - # send_mail(subject, message, settings.DEFAULT_FROM_EMAIL, to) + ARG = "bcc" if hasattr(settings, "ENVELOPE_BCC_LIST"): bcc_email = settings.ENVELOPE_BCC_LIST else: - bcc_email = None + return # We don't need to do this patch. - def send_mail(subject, message, from_email, to): - email = EmailMultiAlternatives( - subject, - message, - from_email, - to, - bcc=bcc_email, - ) - email.send() + def bcc_arg_position(f): + ''' Returns the position of 'bcc' in the argument list to f, or None if + there is no such argument. ''' + co = f.__code__ + # The first co_argcount members of co_varnames are argument variables + for i, argname in enumerate(co.co_varnames[:co.co_argcount]): + if argname == ARG: + return i + else: + return None - hooks.send_mail = send_mail + def bcc_is_provided_positionally(f, a): + ''' Returns true if 'bcc' is provided as a positional argument to f, + when it is called with the argument list `a`. ''' + + return bcc_arg_position(f) < len(a) + + def patch_bcc_positional(f, a): + ''' Returns a copy of `a`, but with the bcc argument patched to include + our BCC list. ''' + + pos = bcc_arg_position(f) + bcc = a[pos] + + if bcc is not None: + bcc = list(bcc) + else: + bcc = [] + + bcc += bcc_email + + return tuple(a[:pos] + (bcc,) + a[pos + 1:]) + + + def patch_bcc_keyword(f, k): + ''' Adds our BCC list to the BCC list in the keyword arguments, and + returns the new version of the keyword arguments. + + Arguments: + f (callable): The function that we're patching. It should have an + argument called bcc. + k (dict): A dictionary of kwargs to be provided to EmailMessage. + It will be modified to add the BCC list specified in + settings.ENVELOPE_BCC_LIST, if provided. + ''' + + if ARG in k: + bcc = list(k[ARG]) + del k[ARG] + else: + bcc = [] + bcc += list(bcc_email) + k[ARG] = bcc + + return k + + to_wrap = message.EmailMessage.__init__ + + @wraps(to_wrap) + def email_message__init__(*a, **k): + + if bcc_is_provided_positionally(to_wrap, a): + a = patch_bcc_positional(to_wrap, a) + else: + k = patch_bcc_keyword(to_wrap, k) + + return to_wrap(*a, **k) + + message.EmailMessage.__init__ = email_message__init__ + + # Do not need to wrap EmailMultiAlternatives because it is a subclass of + # EmailMessage. def fix_sitetree_check_access_500s():