mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Inbound: improve inline content handling
* refactor: derive `AnymailInboundMessage` from `email.message.EmailMessage` rather than legacy Python 2.7 `email.message.Message` * feat(inbound): replace confusing `inline_attachments` with `content_id_map` and `inlines`; rename `is_inline_attachment` to `is_inline`; deprecate old names Closes #328 --------- Co-authored-by: Mike Edmunds <medmunds@gmail.com>
This commit is contained in:
@@ -1,31 +1,33 @@
|
||||
import warnings
|
||||
from base64 import b64decode
|
||||
from email.message import Message
|
||||
from email.message import EmailMessage
|
||||
from email.parser import BytesParser, Parser
|
||||
from email.policy import default as default_policy
|
||||
from email.utils import unquote
|
||||
|
||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||
|
||||
from .exceptions import AnymailDeprecationWarning
|
||||
from .utils import angle_wrap, parse_address_list, parse_rfc2822date
|
||||
|
||||
|
||||
class AnymailInboundMessage(Message):
|
||||
class AnymailInboundMessage(EmailMessage):
|
||||
"""
|
||||
A normalized, parsed inbound email message.
|
||||
|
||||
A subclass of email.message.Message, with some additional
|
||||
convenience properties, plus helpful methods backported
|
||||
from Python 3.6+ email.message.EmailMessage (or really, MIMEPart)
|
||||
A subclass of email.message.EmailMessage, with some additional
|
||||
convenience properties.
|
||||
"""
|
||||
|
||||
# Why Python email.message.Message rather than django.core.mail.EmailMessage?
|
||||
# Why Python email.message.EmailMessage rather than django.core.mail.EmailMessage?
|
||||
# Django's EmailMessage is really intended for constructing a (limited subset of)
|
||||
# Message to send; Message is better designed for representing arbitrary messages:
|
||||
# an EmailMessage to send; Python's EmailMessage is better designed for representing
|
||||
# arbitrary messages:
|
||||
#
|
||||
# * Message is easily parsed from raw mime (which is an inbound format provided
|
||||
# by many ESPs), and can accurately represent any mime email received
|
||||
# * Message can represent repeated header fields (e.g., "Received") which
|
||||
# are common in inbound messages
|
||||
# * Python's EmailMessage is easily parsed from raw mime (which is an inbound format
|
||||
# provided by many ESPs), and can accurately represent any mime email received
|
||||
# * Python's EmailMessage can represent repeated header fields (e.g., "Received")
|
||||
# which are common in inbound messages
|
||||
# * Django's EmailMessage defaults a bunch of properties in ways that aren't helpful
|
||||
# (e.g., from_email from settings)
|
||||
|
||||
@@ -103,13 +105,30 @@ class AnymailInboundMessage(Message):
|
||||
"""list of attachments (as MIMEPart objects); excludes inlines"""
|
||||
return [part for part in self.walk() if part.is_attachment()]
|
||||
|
||||
@property
|
||||
def inlines(self):
|
||||
"""list of inline parts (as MIMEPart objects)"""
|
||||
return [part for part in self.walk() if part.is_inline()]
|
||||
|
||||
@property
|
||||
def inline_attachments(self):
|
||||
"""DEPRECATED: use content_id_map instead"""
|
||||
warnings.warn(
|
||||
"inline_attachments has been renamed to content_id_map and will be removed"
|
||||
" in the near future.",
|
||||
AnymailDeprecationWarning,
|
||||
)
|
||||
|
||||
return self.content_id_map
|
||||
|
||||
@property
|
||||
def content_id_map(self):
|
||||
"""dict of Content-ID: attachment (as MIMEPart objects)"""
|
||||
|
||||
return {
|
||||
unquote(part["Content-ID"]): part
|
||||
for part in self.walk()
|
||||
if part.is_inline_attachment() and part["Content-ID"] is not None
|
||||
if part.is_inline() and part["Content-ID"] is not None
|
||||
}
|
||||
|
||||
def get_address_header(self, header):
|
||||
@@ -143,13 +162,19 @@ class AnymailInboundMessage(Message):
|
||||
return part.get_content_text()
|
||||
return None
|
||||
|
||||
# Hoisted from email.message.MIMEPart
|
||||
def is_attachment(self):
|
||||
return self.get_content_disposition() == "attachment"
|
||||
def is_inline(self):
|
||||
return self.get_content_disposition() == "inline"
|
||||
|
||||
# New for Anymail
|
||||
def is_inline_attachment(self):
|
||||
return self.get_content_disposition() == "inline"
|
||||
"""DEPRECATED: use in_inline instead"""
|
||||
warnings.warn(
|
||||
"is_inline_attachment has been renamed to is_inline and will be removed"
|
||||
" in the near future.",
|
||||
AnymailDeprecationWarning,
|
||||
)
|
||||
|
||||
return self.is_inline()
|
||||
|
||||
def get_content_bytes(self):
|
||||
"""Return the raw payload bytes"""
|
||||
@@ -331,7 +356,7 @@ class AnymailInboundMessage(Message):
|
||||
|
||||
if attachments is not None:
|
||||
for attachment in attachments:
|
||||
if attachment.is_inline_attachment():
|
||||
if attachment.is_inline():
|
||||
related.attach(attachment)
|
||||
else:
|
||||
msg.attach(attachment)
|
||||
|
||||
Reference in New Issue
Block a user