mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 11:51:05 -05:00
* Un-hardcode status message_id in test backend For the test EmailBackend, get message ID's based on array position in `mail.outbox`, so that tests can predict the message ID. * Add a console backend for use in development Adds an EmailBackend derived from both Anymail's test backend and Django's console backend, to provide anymail statuses and signal handling while printing messages to the console. For use during development on localhost. Closes #87
150 lines
5.1 KiB
Python
150 lines
5.1 KiB
Python
from django.core import mail
|
|
|
|
from .base import AnymailBaseBackend, BasePayload
|
|
from ..exceptions import AnymailAPIError
|
|
from ..message import AnymailRecipientStatus
|
|
from ..utils import get_anymail_setting
|
|
|
|
|
|
class EmailBackend(AnymailBaseBackend):
|
|
"""
|
|
Anymail backend that simulates sending messages, useful for testing.
|
|
|
|
Sent messages are collected in django.core.mail.outbox (as with Django's locmem backend).
|
|
|
|
In addition:
|
|
* Anymail send params parsed from the message will be attached to the outbox message
|
|
as a dict in the attr `anymail_test_params`
|
|
* If the caller supplies an `anymail_test_response` attr on the message, that will be
|
|
used instead of the default "sent" response. It can be either an AnymailRecipientStatus
|
|
or an instance of AnymailAPIError (or a subclass) to raise an exception.
|
|
"""
|
|
|
|
esp_name = "Test"
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(EmailBackend, self).__init__(*args, **kwargs)
|
|
if not hasattr(mail, 'outbox'):
|
|
mail.outbox = [] # see django.core.mail.backends.locmem
|
|
|
|
def get_esp_message_id(self, message):
|
|
# Get a unique ID for the message. The message must have been added to
|
|
# the outbox first.
|
|
return mail.outbox.index(message)
|
|
|
|
def build_message_payload(self, message, defaults):
|
|
return TestPayload(backend=self, message=message, defaults=defaults)
|
|
|
|
def post_to_esp(self, payload, message):
|
|
# Keep track of the sent messages and params (for test cases)
|
|
message.anymail_test_params = payload.params
|
|
mail.outbox.append(message)
|
|
try:
|
|
# Tests can supply their own message.test_response:
|
|
response = message.anymail_test_response
|
|
if isinstance(response, AnymailAPIError):
|
|
raise response
|
|
except AttributeError:
|
|
# Default is to return 'sent' for each recipient
|
|
status = AnymailRecipientStatus(
|
|
message_id=self.get_esp_message_id(message),
|
|
status='sent'
|
|
)
|
|
response = {
|
|
'recipient_status': {email: status for email in payload.recipient_emails}
|
|
}
|
|
return response
|
|
|
|
def parse_recipient_status(self, response, payload, message):
|
|
try:
|
|
return response['recipient_status']
|
|
except KeyError:
|
|
raise AnymailAPIError('Unparsable test response')
|
|
|
|
|
|
class TestPayload(BasePayload):
|
|
# For test purposes, just keep a dict of the params we've received.
|
|
# (This approach is also useful for native API backends -- think of
|
|
# payload.params as collecting kwargs for esp_native_api.send().)
|
|
|
|
def init_payload(self):
|
|
self.params = {}
|
|
self.recipient_emails = []
|
|
|
|
def set_from_email(self, email):
|
|
self.params['from'] = email
|
|
|
|
def set_to(self, emails):
|
|
self.params['to'] = emails
|
|
self.recipient_emails += [email.addr_spec for email in emails]
|
|
|
|
def set_cc(self, emails):
|
|
self.params['cc'] = emails
|
|
self.recipient_emails += [email.addr_spec for email in emails]
|
|
|
|
def set_bcc(self, emails):
|
|
self.params['bcc'] = emails
|
|
self.recipient_emails += [email.addr_spec for email in emails]
|
|
|
|
def set_subject(self, subject):
|
|
self.params['subject'] = subject
|
|
|
|
def set_reply_to(self, emails):
|
|
self.params['reply_to'] = emails
|
|
|
|
def set_extra_headers(self, headers):
|
|
self.params['extra_headers'] = headers
|
|
|
|
def set_text_body(self, body):
|
|
self.params['text_body'] = body
|
|
|
|
def set_html_body(self, body):
|
|
self.params['html_body'] = body
|
|
|
|
def add_alternative(self, content, mimetype):
|
|
self.unsupported_feature("alternative part with type '%s'" % mimetype)
|
|
|
|
def add_attachment(self, attachment):
|
|
self.params.setdefault('attachments', []).append(attachment)
|
|
|
|
def set_metadata(self, metadata):
|
|
self.params['metadata'] = metadata
|
|
|
|
def set_send_at(self, send_at):
|
|
self.params['send_at'] = send_at
|
|
|
|
def set_tags(self, tags):
|
|
self.params['tags'] = tags
|
|
|
|
def set_track_clicks(self, track_clicks):
|
|
self.params['track_clicks'] = track_clicks
|
|
|
|
def set_track_opens(self, track_opens):
|
|
self.params['track_opens'] = track_opens
|
|
|
|
def set_template_id(self, template_id):
|
|
self.params['template_id'] = template_id
|
|
|
|
def set_merge_data(self, merge_data):
|
|
self.params['merge_data'] = merge_data
|
|
|
|
def set_merge_global_data(self, merge_global_data):
|
|
self.params['merge_global_data'] = merge_global_data
|
|
|
|
def set_esp_extra(self, extra):
|
|
# Merge extra into params
|
|
self.params.update(extra)
|
|
|
|
|
|
class _EmailBackendWithRequiredSetting(EmailBackend):
|
|
"""Test backend with a required setting `sample_setting`.
|
|
|
|
Intended only for internal use by Anymail settings tests.
|
|
"""
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
esp_name = self.esp_name
|
|
self.sample_setting = get_anymail_setting('sample_setting', esp_name=esp_name,
|
|
kwargs=kwargs, allow_bare=True)
|
|
super(_EmailBackendWithRequiredSetting, self).__init__(*args, **kwargs)
|