Improve inline-image handling

* Add filename param to attach_inline_image

* Add attach_inline_image_file function
  (parallels EmailMessage.attach and attach_file)

* Use `Content-Disposition: inline` to decide
  whether an attachment should be handled inline
  (whether or not it's an image, and whether or not
  it has a Content-ID)

* Stop conflating filename and Content-ID, for
  ESPs that allow both. (Solves problem where
  Google Inbox was displaying inline images
  as attachments when sent through SendGrid.)
This commit is contained in:
medmunds
2016-03-11 19:14:11 -08:00
parent 701726c59d
commit 54827579d3
10 changed files with 132 additions and 60 deletions

View File

@@ -88,14 +88,14 @@ Changes to settings
the values from :setting:`ANYMAIL_SEND_DEFAULTS`.
``MANDRILL_SUBACCOUNT``
Use :attr:`esp_extra` in :setting:`ANYMAIL_MANDRILL_SEND_DEFAULTS`:
Use :setting:`ANYMAIL_MANDRILL_SEND_DEFAULTS`:
.. code-block:: python
ANYMAIL = {
...
"MANDRILL_SEND_DEFAULTS": {
"esp_extra": {"subaccount": "<your subaccount>"}
"subaccount": "<your subaccount>"
}
}
@@ -149,3 +149,17 @@ Changes to EmailMessage attributes
to your code. In the future, the Mandrill-only attributes
will be moved into the
:attr:`~anymail.message.AnymailMessage.esp_extra` dict.
**Inline images**
Djrill (incorrectly) used the presence of a :mailheader:`Content-ID`
header to decide whether to treat an image as inline. Anymail
looks for :mailheader:`Content-Disposition: inline`.
If you were constructing MIMEImage inline image attachments
for your Djrill messages, in addition to setting the Content-ID,
you should also add::
image.add_header('Content-Disposition', 'inline')
Or better yet, use Anymail's new :ref:`inline-images`
helper functions to attach your inline images.

View File

@@ -182,7 +182,15 @@ ESP send options (AnymailMessage)
:class:`AnymailMessageMixin` objects. Unlike the attributes above,
they can't be used on an arbitrary :class:`~django.core.mail.EmailMessage`.)
.. method:: attach_inline_image(content, subtype=None, idstring="img", domain=None)
.. method:: attach_inline_image_file(path, subtype=None, idstring="img", domain=None)
Attach an inline (embedded) image to the message and return its :mailheader:`Content-ID`.
This calls :func:`attach_inline_image_file` on the message. See :ref:`inline-images`
for details and an example.
.. method:: attach_inline_image(content, filename=None, subtype=None, idstring="img", domain=None)
Attach an inline (embedded) image to the message and return its :mailheader:`Content-ID`.
@@ -303,9 +311,17 @@ ESP send status
Inline images
-------------
Anymail includes a convenience function to simplify attaching inline images to email.
Anymail includes convenience functions to simplify attaching inline images to email.
.. function:: attach_inline_image(message, content, subtype=None, idstring="img", domain=None)
These functions work with *any* Django :class:`~django.core.mail.EmailMessage` --
they're not specific to Anymail email backends. You can use them with messages sent
through Django's SMTP backend or any other that properly supports MIME attachments.
(Both functions are also available as convenience methods on Anymail's
:class:`~anymail.message.AnymailMessage` and :class:`~anymail.message.AnymailMessageMixin`
classes.)
.. function:: attach_inline_image_file(message, path, subtype=None, idstring="img", domain=None)
Attach an inline (embedded) image to the message and return its :mailheader:`Content-ID`.
@@ -315,23 +331,20 @@ Anymail includes a convenience function to simplify attaching inline images to e
.. code-block:: python
from django.core.mail import EmailMultiAlternatives
from anymail.message import attach_inline_image
# read image content -- be sure to open the file in binary mode:
with f = open("path/to/picture.jpg", "rb"):
raw_image_data = f.read()
from anymail.message import attach_inline_image_file
message = EmailMultiAlternatives( ... )
cid = attach_inline_image(message, raw_image_data)
cid = attach_inline_image_file(message, 'path/to/picture.jpg')
html = '... <img alt="Picture" src="cid:%s"> ...' % cid
message.attach_alternative(html, "text/html")
message.attach_alternative(html, 'text/html')
message.send()
`message` must be an :class:`~django.core.mail.EmailMessage` (or subclass) object.
`content` must be the binary image data (e.g., read from a file).
`path` must be the pathname to an image file. (Its basename will also be used as the
attachment's filename, which may be visible in some email clients.)
`subtype` is an optional MIME :mimetype:`image` subtype, e.g., `"png"` or `"jpg"`.
By default, this is determined automatically from the content.
@@ -342,14 +355,23 @@ Anymail includes a convenience function to simplify attaching inline images to e
(But be aware the default `domain` can leak your server's local hostname
in the resulting email.)
This function works with *any* Django :class:`~django.core.mail.EmailMessage` --
it's not specific to Anymail email backends. You can use it with messages sent
through Django's SMTP backend or any other that properly supports MIME attachments.
(This function is also available as the
:meth:`~anymail.message.AnymailMessage.attach_inline_image` method
on Anymail's :class:`~anymail.message.AnymailMessage` and
:class:`~anymail.message.AnymailMessageMixin` classes.)
.. function:: attach_inline_image(message, content, filename=None, subtype=None, idstring="img", domain=None)
This is a version of :func:`attach_inline_image_file` that accepts raw
image data, rather than reading it from a file.
`message` must be an :class:`~django.core.mail.EmailMessage` (or subclass) object.
`content` must be the binary image data
`filename` is an optional `str` that will be used as as the attachment's
filename -- e.g., `"picture.jpg"`. This may be visible in email clients that
choose to display the image as an attachment as well as making it available
for inline use (this is up to the email client). It should be a base filename,
without any path info.
`subtype`, `idstring` and `domain` are as described in :func:`attach_inline_image_file`
.. _send-defaults:

View File

@@ -73,14 +73,20 @@ will send.
.. rubric:: Inline images
If your message has any image attachments with :mailheader:`Content-ID` headers,
Anymail will tell your ESP to treat them as inline images rather than ordinary
attached files.
If your message has any attachments with :mailheader:`Content-Disposition: inline`
headers, Anymail will tell your ESP to treat them as inline rather than ordinary
attached files. If you want to reference an attachment from an `<img>` in your
HTML source, the attachment also needs a :mailheader:`Content-ID` header.
You can construct an inline image attachment yourself with Python's
:class:`python:email.mime.image.MIMEImage`, or you can use the convenience
function :func:`~message.attach_inline_image` included with
Anymail. See :ref:`inline-images` in the "Anymail additions" section.
Anymail's comes with :func:`~message.attach_inline_image` and
:func:`~message.attach_inline_image_file` convenience functions that
do the right thing. See :ref:`inline-images` in the "Anymail additions" section.
(If you prefer to do the work yourself, Python's :class:`~email.mime.image.MIMEImage`
and :meth:`~email.message.Message.add_header` should be helpful.)
Even if you mark an attachment as inline, some email clients may decide to also
display it as an attachment. This is largely outside your control.
.. _message-headers: