Files
django-anymail/anymail/backends/test.py
Charlie DeTar 771d4040df Un-hardcode message_id in test backend; add console backend
* 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
2018-01-28 12:25:05 -08:00

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)