Uniform settings handling

For MANDRILL_API_KEY (e.g.,), look for these settings:
* ANYMAIL = { 'MANDRILL_API_KEY': '...' }
* ANYMAIL_MANDRILL_API_KEY = "..."
* MANDRILL_API_KEY = "..."

(the "bare" third version is used only for settings that
might be reasonably shared with other apps, like api keys)
This commit is contained in:
medmunds
2016-03-04 17:02:43 -08:00
parent 3b414a9619
commit 38729df93c
7 changed files with 67 additions and 67 deletions

View File

@@ -8,7 +8,7 @@ from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend
from ..exceptions import AnymailError, AnymailRequestsAPIError, AnymailSerializationError, AnymailUnsupportedFeature
from ..utils import Attachment, ParsedEmail, UNSET, combine, last
from ..utils import Attachment, ParsedEmail, UNSET, combine, last, get_anymail_setting
from .._version import __version__
@@ -19,7 +19,17 @@ class AnymailBaseBackend(BaseEmailBackend):
def __init__(self, *args, **kwargs):
super(AnymailBaseBackend, self).__init__(*args, **kwargs)
self.send_defaults = getattr(settings, "ANYMAIL_SEND_DEFAULTS", {})
self.unsupported_feature_errors = get_anymail_setting("UNSUPPORTED_FEATURE_ERRORS", True)
self.ignore_recipient_status = get_anymail_setting("IGNORE_RECIPIENT_STATUS", False)
# Merge SEND_DEFAULTS and <esp_name>_SEND_DEFAULTS settings
send_defaults = get_anymail_setting("SEND_DEFAULTS", {})
esp_send_defaults = get_anymail_setting("%s_SEND_DEFAULTS" % self.esp_name.upper(), None)
if esp_send_defaults is not None:
send_defaults = send_defaults.copy()
send_defaults.update(esp_send_defaults)
self.send_defaults = send_defaults
def open(self):
"""
@@ -296,9 +306,9 @@ class BasePayload(object):
setter(value)
def unsupported_feature(self, feature):
# future: check settings.ANYMAIL_UNSUPPORTED_FEATURE_ERRORS
if self.backend.unsupported_feature_errors:
raise AnymailUnsupportedFeature("%s does not support %s" % (self.esp_name, feature),
email_message=self.message)
email_message=self.message, payload=self, backend=self.backend)
#
# Attribute converters

View File

@@ -1,10 +1,7 @@
from datetime import date, datetime
from django.conf import settings
from ..exceptions import (AnymailImproperlyConfigured, AnymailRequestsAPIError,
AnymailRecipientsRefused, AnymailUnsupportedFeature)
from ..utils import last, combine
from ..exceptions import AnymailRequestsAPIError, AnymailRecipientsRefused
from ..utils import last, combine, get_anymail_setting
from .base import AnymailRequestsBackend, RequestsPayload
@@ -16,33 +13,12 @@ class MandrillBackend(AnymailRequestsBackend):
def __init__(self, **kwargs):
"""Init options from Django settings"""
api_url = getattr(settings, "MANDRILL_API_URL", "https://mandrillapp.com/api/1.0")
self.api_key = get_anymail_setting('MANDRILL_API_KEY', allow_bare=True)
api_url = get_anymail_setting("MANDRILL_API_URL", "https://mandrillapp.com/api/1.0")
if not api_url.endswith("/"):
api_url += "/"
super(MandrillBackend, self).__init__(api_url, **kwargs)
try:
self.api_key = settings.MANDRILL_API_KEY
except AttributeError:
raise AnymailImproperlyConfigured("Set MANDRILL_API_KEY in settings.py to use Anymail Mandrill backend")
# Djrill compat! MANDRILL_SETTINGS
try:
self.send_defaults.update(settings.MANDRILL_SETTINGS)
except AttributeError:
pass # no MANDRILL_SETTINGS setting
except (TypeError, ValueError): # e.g., not enumerable
raise AnymailImproperlyConfigured("MANDRILL_SETTINGS must be a dict or mapping")
# Djrill compat! MANDRILL_SUBACCOUNT
try:
self.send_defaults["subaccount"] = settings.MANDRILL_SUBACCOUNT
except AttributeError:
pass # no MANDRILL_SUBACCOUNT setting
self.ignore_recipient_status = getattr(settings, "MANDRILL_IGNORE_RECIPIENT_STATUS", False)
def build_message_payload(self, message):
return MandrillPayload(message, self.send_defaults, self)
@@ -151,18 +127,9 @@ class MandrillPayload(RequestsPayload):
def add_alternative(self, content, mimetype):
if mimetype != 'text/html':
raise AnymailUnsupportedFeature(
"Invalid alternative mimetype '%s'. "
"Mandrill only accepts plain text and html emails."
% mimetype,
email_message=self.message)
self.unsupported_feature("alternative part with mimetype '%s'" % mimetype)
if "html" in self.data["message"]:
raise AnymailUnsupportedFeature(
"Too many alternatives attached to the message. "
"Mandrill only accepts plain text and html emails.",
email_message=self.message)
self.unsupported_feature("multiple html parts")
self.data["message"]["html"] = content
def add_attachment(self, attachment):

View File

@@ -1,4 +1,3 @@
from django.core.exceptions import ImproperlyConfigured
import json
from requests import HTTPError
@@ -68,10 +67,6 @@ class AnymailError(Exception):
return description
class AnymailImproperlyConfigured(AnymailError, ImproperlyConfigured):
"""Exception for configuration problems"""
class AnymailAPIError(AnymailError):
"""Exception for unsuccessful response from ESP's API."""

1
anymail/models.py Normal file
View File

@@ -0,0 +1 @@
# this empty models.py is here for Django testrunner compatibility pre Django 1.6

View File

@@ -603,7 +603,7 @@ class DjrillRecipientsRefusedTests(DjrillBackendMockAPITestCase):
sent = msg.send()
self.assertEqual(sent, 1) # one message sent, successfully, to 2 of 4 recipients
@override_settings(MANDRILL_IGNORE_RECIPIENT_STATUS=True)
@override_settings(ANYMAIL_IGNORE_RECIPIENT_STATUS=True)
def test_settings_override(self):
"""Setting restores Djrill 1.x behavior"""
self.mock_post.return_value = self.MockResponse(status_code=200, raw=b"""

View File

@@ -12,31 +12,16 @@ class DjrillMandrillSubaccountTests(DjrillBackendMockAPITestCase):
data = self.get_api_call_data()
self.assertFalse('subaccount' in data['message'])
@override_settings(MANDRILL_SETTINGS={'subaccount': 'test_subaccount'})
@override_settings(ANYMAIL_MANDRILL_SEND_DEFAULTS={'subaccount': 'test_subaccount'})
def test_subaccount_setting(self):
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "test_subaccount")
@override_settings(MANDRILL_SETTINGS={'subaccount': 'global_setting_subaccount'})
@override_settings(ANYMAIL_MANDRILL_SEND_DEFAULTS={'subaccount': 'global_setting_subaccount'})
def test_subaccount_message_overrides_setting(self):
message = mail.EmailMessage('Subject', 'Body', 'from@example.com', ['to@example.com'])
message.subaccount = "individual_message_subaccount" # should override global setting
message.send()
data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")
# Djrill 1.x offered dedicated MANDRILL_SUBACCOUNT setting.
# In Djrill 2.x, you should use the MANDRILL_SETTINGS dict as in the earlier tests.
# But we still support the old setting for compatibility:
@override_settings(MANDRILL_SUBACCOUNT="legacy_setting_subaccount")
def test_subaccount_legacy_setting(self):
mail.send_mail('Subject', 'Body', 'from@example.com', ['to@example.com'])
data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "legacy_setting_subaccount")
message = mail.EmailMessage('Subject', 'Body', 'from@example.com', ['to@example.com'])
message.subaccount = "individual_message_subaccount" # should override legacy setting
message.send()
data = self.get_api_call_data()
self.assertEqual(data['message']['subaccount'], "individual_message_subaccount")

View File

@@ -4,6 +4,8 @@ from email.mime.base import MIMEBase
from email.utils import parseaddr
import six
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.mail.message import sanitize_address, DEFAULT_ATTACHMENT_MIME_TYPE
@@ -133,3 +135,43 @@ class Attachment(object):
if isinstance(content, six.text_type):
content = content.encode(self.encoding)
return b64encode(content).decode("ascii")
def get_anymail_setting(setting, default=UNSET, allow_bare=False):
"""Returns a Django Anymail setting.
Returns first of:
- settings.ANYMAIL[setting]
- settings.ANYMAIL_<setting>
- settings.<setting> (only if allow_bare)
- default if provided; else raises ImproperlyConfigured
ANYMAIL = { "MAILGUN_SEND_DEFAULTS" : { ... }, ... }
ANYMAIL_MAILGUN_SEND_DEFAULTS = { ... }
If allow_bare, allows settings.<setting> without the ANYMAIL_ prefix:
ANYMAIL = { "MAILGUN_API_KEY": "xyz", ... }
ANYMAIL_MAILGUN_API_KEY = "xyz"
MAILGUN_API_KEY = "xyz"
"""
anymail_setting = "ANYMAIL_%s" % setting
try:
return settings.ANYMAIL[setting]
except (AttributeError, KeyError):
try:
return getattr(settings, anymail_setting)
except AttributeError:
if allow_bare:
try:
return getattr(settings, setting)
except AttributeError:
pass
if default is UNSET:
message = "You must set %s or ANYMAIL = {'%s': ...}" % (anymail_setting, setting)
if allow_bare:
message += " or %s" % setting
message += " in your Django settings"
raise ImproperlyConfigured(message)
else:
return default