Normalize send_at date/datetime/timestamp in BasePayload.

Interpret dates and naive datetimes as Django's
current_timezone (rather than UTC like Djrill did).
This should be more likely to behave as expected
when running with a non-UTC TIME_ZONE setting.
This commit is contained in:
medmunds
2016-03-05 11:22:14 -08:00
parent 0a5bca1426
commit a6c0eb5974
3 changed files with 64 additions and 35 deletions

View File

@@ -1,5 +1,8 @@
from datetime import date, datetime
from django.conf import settings
from django.core.mail.backends.base import BaseEmailBackend
from django.utils.timezone import is_naive, get_current_timezone, make_aware, utc
from ..exceptions import AnymailError, AnymailUnsupportedFeature
from ..utils import Attachment, ParsedEmail, UNSET, combine, last, get_anymail_setting
@@ -191,7 +194,7 @@ class BasePayload(object):
anymail_message_attrs = (
# Anymail expando-props
('metadata', combine, None),
('send_at', last, None), # normalize to datetime?
('send_at', last, 'aware_datetime'),
('tags', combine, None),
('track_clicks', last, None),
('track_opens', last, None),
@@ -219,6 +222,7 @@ class BasePayload(object):
if not callable(converter):
converter = getattr(self, converter)
value = converter(value)
if value is not UNSET:
if attr == 'body':
setter = self.set_html_body if message.content_subtype == 'html' else self.set_text_body
else:
@@ -246,6 +250,29 @@ class BasePayload(object):
str_encoding = self.message.encoding or settings.DEFAULT_CHARSET
return [Attachment(attachment, str_encoding) for attachment in attachments]
def aware_datetime(self, value):
"""Converts a date or datetime or timestamp to an aware datetime.
Naive datetimes are assumed to be in Django's current_timezone.
Dates are interpreted as midnight that date, in Django's current_timezone.
Integers are interpreted as POSIX timestamps (which are inherently UTC).
Anything else (e.g., str) is returned unchanged, which won't be portable.
"""
if isinstance(value, datetime):
dt = value
else:
if isinstance(value, date):
dt = datetime(value.year, value.month, value.day) # naive, midnight
else:
try:
dt = datetime.utcfromtimestamp(value).replace(tzinfo=utc)
except (TypeError, ValueError):
return value
if is_naive(dt):
dt = make_aware(dt, get_current_timezone())
return dt
#
# Abstract implementation
#

View File

@@ -57,21 +57,15 @@ def _expand_merge_vars(vardict):
def encode_date_for_mandrill(dt):
"""Format a date or datetime for use as a Mandrill API date field
"""Format a datetime for use as a Mandrill API date field
datetime becomes "YYYY-MM-DD HH:MM:SS"
converted to UTC, if timezone-aware
microseconds removed
date becomes "YYYY-MM-DD 00:00:00"
anything else gets returned intact
Mandrill expects "YYYY-MM-DD HH:MM:SS" in UTC
"""
if isinstance(dt, datetime):
dt = dt.replace(microsecond=0)
if dt.utcoffset() is not None:
dt = (dt - dt.utcoffset()).replace(tzinfo=None)
return dt.isoformat(' ')
elif isinstance(dt, date):
return dt.isoformat() + ' 00:00:00'
else:
return dt
@@ -128,9 +122,10 @@ class MandrillPayload(RequestsPayload):
def add_alternative(self, content, mimetype):
if mimetype != 'text/html':
self.unsupported_feature("alternative part with mimetype '%s'" % mimetype)
if "html" in self.data["message"]:
elif "html" in self.data["message"]:
self.unsupported_feature("multiple html parts")
self.data["message"]["html"] = content
else:
self.set_html_body(content)
def add_attachment(self, attachment):
key = "images" if attachment.inline else "attachments"