mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Drop Python 2 and Django 1.11 support
Minimum supported versions are now Django 2.0, Python 3.5. This touches a lot of code, to: * Remove obsolete portability code and workarounds (six, backports of email parsers, test utils, etc.) * Use Python 3 syntax (class defs, raise ... from, etc.) * Correct inheritance for mixin classes * Fix outdated docs content and links * Suppress Python 3 "unclosed SSLSocket" ResourceWarnings that are beyond our control (in integration tests due to boto3, python-sparkpost)
This commit is contained in:
27
docs/conf.py
27
docs/conf.py
@@ -1,5 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Anymail documentation build configuration file, created by
|
||||
# sphinx-quickstart
|
||||
#
|
||||
@@ -50,9 +48,9 @@ source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'Anymail'
|
||||
project = 'Anymail'
|
||||
# noinspection PyShadowingBuiltins
|
||||
copyright = u'Anymail contributors (see AUTHORS.txt)'
|
||||
copyright = 'Anymail contributors (see AUTHORS.txt)'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
@@ -203,8 +201,8 @@ latex_elements = {
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title, author, documentclass [howto/manual]).
|
||||
latex_documents = [
|
||||
('index', 'Anymail.tex', u'Anymail Documentation',
|
||||
u'Anymail contributors (see AUTHORS.txt)', 'manual'),
|
||||
('index', 'Anymail.tex', 'Anymail Documentation',
|
||||
'Anymail contributors (see AUTHORS.txt)', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
@@ -233,8 +231,8 @@ latex_documents = [
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
('index', 'anymail', u'Anymail Documentation',
|
||||
[u'Anymail contributors (see AUTHORS.txt)'], 1)
|
||||
('index', 'anymail', 'Anymail Documentation',
|
||||
['Anymail contributors (see AUTHORS.txt)'], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
@@ -247,8 +245,8 @@ man_pages = [
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
('index', 'Anymail', u'Anymail Documentation',
|
||||
u'Anymail contributors (see AUTHORS.txt)', 'Anymail', 'Multi-ESP transactional email for Django.',
|
||||
('index', 'Anymail', 'Anymail Documentation',
|
||||
'Anymail contributors (see AUTHORS.txt)', 'Anymail', 'Multi-ESP transactional email for Django.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
@@ -270,14 +268,9 @@ extlinks = {
|
||||
# -- Options for Intersphinx ------------------------------------------------
|
||||
|
||||
intersphinx_mapping = {
|
||||
'python': ('https://docs.python.org/3.6', None),
|
||||
'python': ('https://docs.python.org/3.7', None),
|
||||
'django': ('https://docs.djangoproject.com/en/stable/', 'https://docs.djangoproject.com/en/stable/_objects/'),
|
||||
# Requests docs may be moving (Sep 2019):
|
||||
# see https://github.com/psf/requests/issues/5212
|
||||
# and https://github.com/psf/requests/issues/5214
|
||||
'requests': ('https://docs.python-requests.org/en/latest/',
|
||||
('https://docs.python-requests.org/en/latest/objects.inv',
|
||||
'https://requests.kennethreitz.org/en/latest/objects.inv')),
|
||||
'requests': ('https://requests.readthedocs.io/en/stable/', None),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ and Python versions. Tests are run at least once a week, to check whether ESP AP
|
||||
and other dependencies have changed out from under Anymail.
|
||||
|
||||
For local development, the recommended test command is
|
||||
:shell:`tox -e django22-py37-all,django111-py27-all,lint`, which tests a representative
|
||||
:shell:`tox -e django31-py38-all,django20-py35-all,lint`, which tests a representative
|
||||
combination of Python and Django versions. It also runs :pypi:`flake8` and other
|
||||
code-style checkers. Some other test options are covered below, but using this
|
||||
tox command catches most problems, and is a good pre-pull-request check.
|
||||
@@ -98,16 +98,16 @@ Or:
|
||||
$ python runtests.py tests.test_mailgun_backend tests.test_mailgun_webhooks
|
||||
|
||||
Or to test against multiple versions of Python and Django all at once, use :pypi:`tox`.
|
||||
You'll need at least Python 2.7 and Python 3.6 available. (If your system doesn't come
|
||||
with those, `pyenv`_ is a helpful way to install and manage multiple Python versions.)
|
||||
You'll need some version of Python 3 available. (If your system doesn't come
|
||||
with that, `pyenv`_ is a helpful way to install and manage multiple Python versions.)
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
$ pip install tox # (if you haven't already)
|
||||
$ tox -e django21-py36-all,django111-py27-all,lint # test recommended environments
|
||||
$ tox -e django31-py38-all,django20-py35-all,lint # test recommended environments
|
||||
|
||||
## you can also run just some test cases, e.g.:
|
||||
$ tox -e django21-py36-all,django111-py27-all tests.test_mailgun_backend tests.test_utils
|
||||
$ tox -e django31-py38-all,django20-py35-all tests.test_mailgun_backend tests.test_utils
|
||||
|
||||
## to test more Python/Django versions:
|
||||
$ tox --parallel auto # ALL 20+ envs! (in parallel if possible)
|
||||
@@ -121,7 +121,7 @@ API keys or other settings. For example:
|
||||
|
||||
$ export MAILGUN_TEST_API_KEY='your-Mailgun-API-key'
|
||||
$ export MAILGUN_TEST_DOMAIN='mail.example.com' # sending domain for that API key
|
||||
$ tox -e django21-py36-all tests.test_mailgun_integration
|
||||
$ tox -e django31-py38-all tests.test_mailgun_integration
|
||||
|
||||
Check the ``*_integration_tests.py`` files in the `tests source`_ to see which variables
|
||||
are required for each ESP. Depending on the supported features, the integration tests for
|
||||
@@ -180,7 +180,7 @@ Anymail's Sphinx conf sets up a few enhancements you can use in the docs:
|
||||
|
||||
.. _Django's added markup:
|
||||
https://docs.djangoproject.com/en/stable/internals/contributing/writing-documentation/#django-specific-markup
|
||||
.. _extlinks: http://www.sphinx-doc.org/en/stable/ext/extlinks.html
|
||||
.. _intersphinx: http://www.sphinx-doc.org/en/master/ext/intersphinx.html
|
||||
.. _extlinks: https://www.sphinx-doc.org/en/stable/usage/extensions/extlinks.html
|
||||
.. _intersphinx: https://www.sphinx-doc.org/en/stable/usage/extensions/intersphinx.html
|
||||
.. _Writing Documentation:
|
||||
https://docs.djangoproject.com/en/stable/internals/contributing/writing-documentation/
|
||||
|
||||
@@ -425,9 +425,9 @@ The event's :attr:`~anymail.signals.AnymailTrackingEvent.esp_event` field will b
|
||||
the parsed `Mailgun webhook payload`_ as a Python `dict` with ``"signature"`` and
|
||||
``"event-data"`` keys.
|
||||
|
||||
Anymail uses Mailgun's webhook `token` as its normalized
|
||||
Anymail uses Mailgun's webhook ``token`` as its normalized
|
||||
:attr:`~anymail.signals.AnymailTrackingEvent.event_id`, rather than Mailgun's
|
||||
event-data `id` (which is only guaranteed to be unique during a single day).
|
||||
event-data ``id`` (which is only guaranteed to be unique during a single day).
|
||||
If you need the event-data id, it can be accessed in your webhook handler as
|
||||
``event.esp_event["event-data"]["id"]``. (This can be helpful for working with
|
||||
Mailgun's other event APIs.)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
Mandrill
|
||||
========
|
||||
|
||||
Anymail integrates with the `Mandrill <http://mandrill.com/>`__
|
||||
Anymail integrates with the `Mandrill <https://mandrill.com/>`__
|
||||
transactional email service from MailChimp.
|
||||
|
||||
.. note:: **Limited Support for Mandrill**
|
||||
|
||||
@@ -29,9 +29,10 @@ often help you pinpoint the problem...
|
||||
**Double-check common issues**
|
||||
|
||||
* Did you add any required settings for your ESP to the `ANYMAIL` dict in your
|
||||
settings.py? (E.g., ``"SENDGRID_API_KEY"`` for SendGrid.) See :ref:`supported-esps`.
|
||||
settings.py? (E.g., ``"SENDGRID_API_KEY"`` for SendGrid.) Check the instructions
|
||||
for the ESP you're using under :ref:`supported-esps`.
|
||||
* Did you add ``'anymail'`` to the list of :setting:`INSTALLED_APPS` in settings.py?
|
||||
* Are you using a valid from address? Django's default is "webmaster@localhost",
|
||||
* Are you using a valid *from* address? Django's default is "webmaster@localhost",
|
||||
which most ESPs reject. Either specify the ``from_email`` explicitly on every message
|
||||
you send, or add :setting:`DEFAULT_FROM_EMAIL` to your settings.py.
|
||||
|
||||
@@ -61,8 +62,8 @@ Support
|
||||
|
||||
If you've gone through the troubleshooting above and still aren't sure what's wrong,
|
||||
the Anymail community is happy to help. Anymail is supported and maintained by the
|
||||
people who use it---like you! (The vast majority of Anymail contributors volunteer
|
||||
their time, and are not employees of any ESP.)
|
||||
people who use it---like you! (Anymail contributors volunteer their time, and are
|
||||
not employees of any ESP.)
|
||||
|
||||
Here's how to contact the Anymail community:
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ If you didn't set up webhooks when first installing Anymail, you'll need to
|
||||
(You should also review :ref:`securing-webhooks`.)
|
||||
|
||||
Once you've enabled webhooks, Anymail will send a ``anymail.signals.inbound``
|
||||
custom Django :mod:`signal <django.dispatch>` for each ESP inbound message it receives.
|
||||
custom Django :doc:`signal <django:topics/signals>` for each ESP inbound message it receives.
|
||||
You can connect your own receiver function to this signal for further processing.
|
||||
(This is very much like how Anymail handles :ref:`status tracking <event-tracking>`
|
||||
events for sent messages. Inbound events just use a different signal receiver
|
||||
@@ -73,7 +73,7 @@ invoke your signal receiver once, separately, for each message in the batch.
|
||||
:ref:`user-supplied content security <django:user-uploaded-content-security>`.
|
||||
|
||||
.. _using python-magic:
|
||||
http://blog.hayleyanderson.us/2015/07/18/validating-file-types-in-django/
|
||||
https://blog.hayleyanderson.us/2015/07/18/validating-file-types-in-django/
|
||||
|
||||
|
||||
.. _inbound-event:
|
||||
@@ -90,7 +90,7 @@ Normalized inbound event
|
||||
.. attribute:: message
|
||||
|
||||
An :class:`~anymail.inbound.AnymailInboundMessage` representing the email
|
||||
that was received. Most of what you're interested in will be on this `message`
|
||||
that was received. Most of what you're interested in will be on this :attr:`!message`
|
||||
attribute. See the full details :ref:`below <inbound-message>`.
|
||||
|
||||
.. attribute:: event_type
|
||||
@@ -290,8 +290,6 @@ Handling Inbound Attachments
|
||||
|
||||
Anymail converts each inbound attachment to a specialized MIME object with
|
||||
additional methods for handling attachments and integrating with Django.
|
||||
It also backports some helpful MIME methods from newer versions of Python
|
||||
to all versions supported by Anymail.
|
||||
|
||||
The attachment objects in an AnymailInboundMessage's
|
||||
:attr:`~AnymailInboundMessage.attachments` list and
|
||||
@@ -346,8 +344,6 @@ have these methods:
|
||||
.. method:: is_attachment()
|
||||
|
||||
Returns `True` for a (non-inline) attachment, `False` otherwise.
|
||||
(Anymail back-ports Python 3.4.2's :meth:`~email.message.EmailMessage.is_attachment` method
|
||||
to all supported versions.)
|
||||
|
||||
.. method:: is_inline_attachment()
|
||||
|
||||
@@ -360,9 +356,6 @@ have these methods:
|
||||
:mailheader:`Content-Disposition` header. The return value should be either "inline"
|
||||
or "attachment", or `None` if the attachment is somehow missing that header.
|
||||
|
||||
(Anymail back-ports Python 3.5's :meth:`~email.message.Message.get_content_disposition`
|
||||
method to all supported versions.)
|
||||
|
||||
.. method:: get_content_text(charset=None, errors='replace')
|
||||
|
||||
Returns the content of the attachment decoded to Unicode text.
|
||||
@@ -453,7 +446,7 @@ And they may then retry sending these "failed" events, which could
|
||||
cause duplicate processing in your code.
|
||||
If your signal receiver code might be slow, you should instead
|
||||
queue the event for later, asynchronous processing (e.g., using
|
||||
something like `Celery`_).
|
||||
something like :pypi:`celery`).
|
||||
|
||||
If your signal receiver function is defined within some other
|
||||
function or instance method, you *must* use the `weak=False`
|
||||
@@ -461,5 +454,3 @@ option when connecting it. Otherwise, it might seem to work at first,
|
||||
but will unpredictably stop being called at some point---typically
|
||||
on your production server, in a hard-to-debug way. See Django's
|
||||
docs on :doc:`signals <django:topics/signals>` for more information.
|
||||
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
|
||||
@@ -142,15 +142,15 @@ If you want to use Anymail's inbound or tracking webhooks:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from django.conf.urls import include, url
|
||||
from django.urls import include, re_path
|
||||
|
||||
urlpatterns = [
|
||||
...
|
||||
url(r'^anymail/', include('anymail.urls')),
|
||||
re_path(r'^anymail/', include('anymail.urls')),
|
||||
]
|
||||
|
||||
(You can change the "anymail" prefix in the first parameter to
|
||||
:func:`~django.conf.urls.url` if you'd like the webhooks to be served
|
||||
:func:`~django.urls.re_path` if you'd like the webhooks to be served
|
||||
at some other URL. Just match whatever you use in the webhook URL you give
|
||||
your ESP in the next step.)
|
||||
|
||||
@@ -186,7 +186,7 @@ See :ref:`event-tracking` for information on creating signal handlers and the
|
||||
status tracking events you can receive. See :ref:`inbound` for information on
|
||||
receiving inbound message events.
|
||||
|
||||
.. _mod_wsgi: http://modwsgi.readthedocs.io/en/latest/configuration-directives/WSGIPassAuthorization.html
|
||||
.. _mod_wsgi: https://modwsgi.readthedocs.io/en/latest/configuration-directives/WSGIPassAuthorization.html
|
||||
|
||||
|
||||
.. setting:: ANYMAIL
|
||||
@@ -227,10 +227,11 @@ if you are using other Django apps that work with the same ESP.)
|
||||
Finally, for complex use cases, you can override most settings on a per-instance
|
||||
basis by providing keyword args where the instance is initialized (e.g., in a
|
||||
:func:`~django.core.mail.get_connection` call to create an email backend instance,
|
||||
or in `View.as_view()` call to set up webhooks in a custom urls.py). To get the kwargs
|
||||
or in a `View.as_view()` call to set up webhooks in a custom urls.py). To get the kwargs
|
||||
parameter for a setting, drop "ANYMAIL" and the ESP name, and lowercase the rest:
|
||||
e.g., you can override ANYMAIL_MAILGUN_API_KEY by passing `api_key="abc"` to
|
||||
:func:`~django.core.mail.get_connection`. See :ref:`multiple-backends` for an example.
|
||||
e.g., you can override ANYMAIL_MAILGUN_API_KEY for a particular connection by calling
|
||||
``get_connection("anymail.backends.mailgun.EmailBackend", api_key="abc")``.
|
||||
See :ref:`multiple-backends` for an example.
|
||||
|
||||
There are specific Anymail settings for each ESP (like API keys and urls).
|
||||
See the :ref:`supported ESPs <supported-esps>` section for details.
|
||||
@@ -253,7 +254,7 @@ See :ref:`recipients-refused`.
|
||||
}
|
||||
|
||||
|
||||
.. rubric:: SEND_DEFAULTS and *ESP*\ _SEND_DEFAULTS`
|
||||
.. rubric:: SEND_DEFAULTS and *ESP*\ _SEND_DEFAULTS
|
||||
|
||||
A `dict` of default options to apply to all messages sent through Anymail.
|
||||
See :ref:`send-defaults`.
|
||||
|
||||
@@ -26,18 +26,18 @@ The first approach is usually the simplest. The other two can be
|
||||
helpful if you are working with Python development tools that
|
||||
offer type checking or other static code analysis.
|
||||
|
||||
Availability of these features varies by ESP, and there may be additional
|
||||
limitations even when an ESP does support a particular feature. Be sure
|
||||
to check Anymail's docs for your :ref:`specific ESP <supported-esps>`.
|
||||
If you try to use a feature your ESP does not offer, Anymail will raise
|
||||
an :ref:`unsupported feature <unsupported-features>` error.
|
||||
|
||||
|
||||
.. _anymail-send-options:
|
||||
|
||||
ESP send options (AnymailMessage)
|
||||
---------------------------------
|
||||
|
||||
Availability of each of these features varies by ESP, and there may be additional
|
||||
limitations even when an ESP does support a particular feature. Be sure
|
||||
to check Anymail's docs for your :ref:`specific ESP <supported-esps>`.
|
||||
If you try to use a feature your ESP does not offer, Anymail will raise
|
||||
an :ref:`unsupported feature <unsupported-features>` error.
|
||||
|
||||
.. class:: AnymailMessage
|
||||
|
||||
A subclass of Django's :class:`~django.core.mail.EmailMultiAlternatives`
|
||||
@@ -167,7 +167,7 @@ ESP send options (AnymailMessage)
|
||||
|
||||
ESPs have differing restrictions on tags. For portability,
|
||||
it's best to stick with strings that start with an alphanumeric
|
||||
character. (Also, Postmark only allows a single tag per message.)
|
||||
character. (Also, a few ESPs allow only a single tag per message.)
|
||||
|
||||
|
||||
.. caution::
|
||||
@@ -359,7 +359,7 @@ ESP send status
|
||||
* `'queued'` the ESP has accepted the message
|
||||
and will try to send it asynchronously
|
||||
* `'invalid'` the ESP considers the sender or recipient email invalid
|
||||
* `'rejected'` the recipient is on an ESP blacklist
|
||||
* `'rejected'` the recipient is on an ESP suppression list
|
||||
(unsubscribe, previous bounces, etc.)
|
||||
* `'failed'` the attempt to send failed for some other reason
|
||||
* `'unknown'` anything else
|
||||
@@ -402,7 +402,8 @@ ESP send status
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# This will work with a requests-based backend:
|
||||
# This will work with a requests-based backend,
|
||||
# for an ESP whose send API provides a JSON response:
|
||||
message.anymail_status.esp_response.json()
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ email using Django's default SMTP :class:`~django.core.mail.backends.smtp.EmailB
|
||||
switching to Anymail will be easy. Anymail is designed to "just work" with Django.
|
||||
|
||||
If you're not familiar with Django's email functions, please take a look at
|
||||
":mod:`sending email <django.core.mail>`" in the Django docs first.
|
||||
:doc:`django:topics/email` in the Django docs first.
|
||||
|
||||
Anymail supports most of the functionality of Django's :class:`~django.core.mail.EmailMessage`
|
||||
and :class:`~django.core.mail.EmailMultiAlternatives` classes.
|
||||
@@ -39,8 +39,8 @@ function with the ``html_message`` parameter:
|
||||
send_mail("Subject", "text body", "from@example.com",
|
||||
["to@example.com"], html_message="<html>html body</html>")
|
||||
|
||||
However, many Django email capabilities -- and additional Anymail features --
|
||||
are only available when working with an :class:`~django.core.mail.EmailMultiAlternatives`
|
||||
However, many Django email capabilities---and additional Anymail features---are only
|
||||
available when working with an :class:`~django.core.mail.EmailMultiAlternatives`
|
||||
object. Use its :meth:`~django.core.mail.EmailMultiAlternatives.attach_alternative`
|
||||
method to send HTML:
|
||||
|
||||
@@ -168,7 +168,8 @@ raise :exc:`~exceptions.AnymailUnsupportedFeature`.
|
||||
.. setting:: ANYMAIL_IGNORE_UNSUPPORTED_FEATURES
|
||||
|
||||
If you'd like to silently ignore :exc:`~exceptions.AnymailUnsupportedFeature`
|
||||
errors and send the messages anyway, set :setting:`!ANYMAIL_IGNORE_UNSUPPORTED_FEATURES`
|
||||
errors and send the messages anyway, set
|
||||
:setting:`"IGNORE_UNSUPPORTED_FEATURES" <ANYMAIL_IGNORE_UNSUPPORTED_FEATURES>`
|
||||
to `True` in your settings.py:
|
||||
|
||||
.. code-block:: python
|
||||
@@ -197,15 +198,16 @@ If a single message is sent to multiple recipients, and *any* recipient is valid
|
||||
You can still examine the message's :attr:`~message.AnymailMessage.anymail_status`
|
||||
property after the send to determine the status of each recipient.
|
||||
|
||||
You can disable this exception by setting :setting:`ANYMAIL_IGNORE_RECIPIENT_STATUS`
|
||||
to `True` in your settings.py, which will cause Anymail to treat any non-API-error response
|
||||
from your ESP as a successful send.
|
||||
You can disable this exception by setting
|
||||
:setting:`"IGNORE_RECIPIENT_STATUS" <ANYMAIL_IGNORE_RECIPIENT_STATUS>` to `True` in
|
||||
your settings.py `ANYMAIL` dict, which will cause Anymail to treat *any*
|
||||
response from your ESP (other than an API error) as a successful send.
|
||||
|
||||
.. note::
|
||||
|
||||
Many ESPs don't check recipient status during the send API call. For example,
|
||||
Most ESPs don't check recipient status during the send API call. For example,
|
||||
Mailgun always queues sent messages, so you'll never catch
|
||||
:exc:`AnymailRecipientsRefused` with the Mailgun backend.
|
||||
|
||||
For those ESPs, use Anymail's :ref:`delivery event tracking <event-tracking>`
|
||||
if you need to be notified of sends to blacklisted or invalid emails.
|
||||
You can use Anymail's :ref:`delivery event tracking <event-tracking>`
|
||||
if you need to be notified of sends to suppression-listed or invalid emails.
|
||||
|
||||
@@ -211,7 +211,7 @@ for use as merge data:
|
||||
# Do something this instead:
|
||||
message.merge_global_data = {
|
||||
'PRODUCT': product.name, # assuming name is a CharField
|
||||
'TOTAL_COST': "%.2f" % total_cost,
|
||||
'TOTAL_COST': "{cost:0.2f}".format(cost=total_cost),
|
||||
'SHIP_DATE': ship_date.strftime('%B %d, %Y') # US-style "March 15, 2015"
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ Webhook support is optional. If you haven't yet, you'll need to
|
||||
project. (You may also want to review :ref:`securing-webhooks`.)
|
||||
|
||||
Once you've enabled webhooks, Anymail will send an ``anymail.signals.tracking``
|
||||
custom Django :mod:`signal <django.dispatch>` for each ESP tracking event it receives.
|
||||
custom Django :doc:`signal <django:topics/signals>` for each ESP tracking event it receives.
|
||||
You can connect your own receiver function to this signal for further processing.
|
||||
|
||||
Be sure to read Django's `listening to signals`_ docs for information on defining
|
||||
@@ -40,7 +40,7 @@ Example:
|
||||
event.recipient, event.click_url))
|
||||
|
||||
You can define individual signal receivers, or create one big one for all
|
||||
event types, which ever you prefer. You can even handle the same event
|
||||
event types, whichever you prefer. You can even handle the same event
|
||||
in multiple receivers, if that makes your code cleaner. These
|
||||
:ref:`signal receiver functions <signal-receivers>` are documented
|
||||
in more detail below.
|
||||
@@ -189,8 +189,8 @@ Normalized tracking event
|
||||
.. attribute:: mta_response
|
||||
|
||||
If available, a `str` with a raw (intended for email administrators) response
|
||||
from the receiving MTA. Otherwise `None`. Often includes SMTP response codes,
|
||||
but the exact format varies by ESP (and sometimes receiving MTA).
|
||||
from the receiving mail transfer agent. Otherwise `None`. Often includes SMTP
|
||||
response codes, but the exact format varies by ESP (and sometimes receiving MTA).
|
||||
|
||||
.. attribute:: user_agent
|
||||
|
||||
@@ -203,7 +203,7 @@ Normalized tracking event
|
||||
|
||||
.. attribute:: esp_event
|
||||
|
||||
The "raw" event data from the ESP, deserialized into a python data structure.
|
||||
The "raw" event data from the ESP, deserialized into a Python data structure.
|
||||
For most ESPs this is either parsed JSON (as a `dict`), or HTTP POST fields
|
||||
(as a Django :class:`~django.http.QueryDict`).
|
||||
|
||||
@@ -230,7 +230,7 @@ Your Anymail signal receiver must be a function with this signature:
|
||||
:param AnymailTrackingEvent event: The normalized tracking event.
|
||||
Almost anything you'd be interested in
|
||||
will be in here.
|
||||
:param str esp_name: e.g., "SendMail" or "Postmark". If you are working
|
||||
:param str esp_name: e.g., "SendGrid" or "Postmark". If you are working
|
||||
with multiple ESPs, you can use this to distinguish
|
||||
ESP-specific handling in your shared event processing.
|
||||
:param \**kwargs: Required by Django's signal mechanism
|
||||
@@ -259,7 +259,7 @@ And will retry sending the "failed" events, which could cause duplicate
|
||||
processing in your code.
|
||||
If your signal receiver code might be slow, you should instead
|
||||
queue the event for later, asynchronous processing (e.g., using
|
||||
something like `Celery`_).
|
||||
something like :pypi:`celery`).
|
||||
|
||||
If your signal receiver function is defined within some other
|
||||
function or instance method, you *must* use the `weak=False`
|
||||
@@ -268,7 +268,6 @@ but will unpredictably stop being called at some point---typically
|
||||
on your production server, in a hard-to-debug way. See Django's
|
||||
`listening to signals`_ docs for more information.
|
||||
|
||||
.. _Celery: http://www.celeryproject.org/
|
||||
.. _listening to signals:
|
||||
https://docs.djangoproject.com/en/stable/topics/signals/#listening-to-signals
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ ESP's templating languages and merge capabilities are generally not compatible
|
||||
with each other, which can make it hard to move email templates between them.
|
||||
|
||||
But since you're working in Django, you already have access to the
|
||||
extremely-full-featured :mod:`Django templating system <django.template>`.
|
||||
extremely-full-featured :doc:`Django templating system <django:topics/templates>`.
|
||||
You don't even have to use Django's template syntax: it supports other
|
||||
template languages (like Jinja2).
|
||||
|
||||
@@ -15,7 +15,7 @@ You're probably already using Django's templating system for your HTML pages,
|
||||
so it can be an easy decision to use it for your email, too.
|
||||
|
||||
To compose email using *Django* templates, you can use Django's
|
||||
:func:`~django.template.loaders.django.template.loader.render_to_string`
|
||||
:func:`~django.template.loader.render_to_string`
|
||||
template shortcut to build the body and html.
|
||||
|
||||
Example that builds an email from the templates ``message_subject.txt``,
|
||||
@@ -24,16 +24,14 @@ Example that builds an email from the templates ``message_subject.txt``,
|
||||
.. code-block:: python
|
||||
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
from django.template import Context
|
||||
from django.template.loader import render_to_string
|
||||
|
||||
merge_data = {
|
||||
'ORDERNO': "12345", 'TRACKINGNO': "1Z987"
|
||||
}
|
||||
|
||||
plaintext_context = Context(autoescape=False) # HTML escaping not appropriate in plaintext
|
||||
subject = render_to_string("message_subject.txt", merge_data, plaintext_context)
|
||||
text_body = render_to_string("message_body.txt", merge_data, plaintext_context)
|
||||
subject = render_to_string("message_subject.txt", merge_data).strip()
|
||||
text_body = render_to_string("message_body.txt", merge_data)
|
||||
html_body = render_to_string("message_body.html", merge_data)
|
||||
|
||||
msg = EmailMultiAlternatives(subject=subject, from_email="store@example.com",
|
||||
@@ -41,6 +39,9 @@ Example that builds an email from the templates ``message_subject.txt``,
|
||||
msg.attach_alternative(html_body, "text/html")
|
||||
msg.send()
|
||||
|
||||
Tip: use Django's :ttag:`{% autoescape off %}<autoescape>` template tag in your
|
||||
plaintext ``.txt`` templates to avoid inappropriate HTML escaping.
|
||||
|
||||
|
||||
Helpful add-ons
|
||||
---------------
|
||||
@@ -48,8 +49,6 @@ Helpful add-ons
|
||||
These (third-party) packages can be helpful for building your email
|
||||
in Django:
|
||||
|
||||
.. TODO: flesh this out
|
||||
|
||||
* :pypi:`django-templated-mail`, :pypi:`django-mail-templated`, or :pypi:`django-mail-templated-simple`
|
||||
for building messages from sets of Django templates.
|
||||
* :pypi:`premailer` for inlining css before sending
|
||||
|
||||
@@ -73,10 +73,10 @@ Basic usage is covered in the
|
||||
:ref:`webhooks configuration <webhooks-configuration>` docs.
|
||||
|
||||
If something posts to your webhooks without the required shared
|
||||
secret as basic auth in the HTTP_AUTHORIZATION header, Anymail will
|
||||
secret as basic auth in the HTTP *Authorization* header, Anymail will
|
||||
raise an :exc:`AnymailWebhookValidationFailure` error, which is
|
||||
a subclass of Django's :exc:`~django.core.exceptions.SuspiciousOperation`.
|
||||
This will result in an HTTP 400 response, without further processing
|
||||
This will result in an HTTP 400 "bad request" response, without further processing
|
||||
the data or calling your signal receiver function.
|
||||
|
||||
In addition to a single "random:random" string, you can give a list
|
||||
|
||||
Reference in New Issue
Block a user