mirror of
https://github.com/pacnpal/django-anymail.git
synced 2025-12-20 03:41:05 -05:00
Reformat code with automated tools
Apply standardized code style
This commit is contained in:
@@ -7,18 +7,28 @@ from email.mime.image import MIMEImage
|
||||
from email.mime.text import MIMEText
|
||||
|
||||
from django.http import QueryDict
|
||||
from django.test import SimpleTestCase, RequestFactory, override_settings
|
||||
from django.test import RequestFactory, SimpleTestCase, override_settings
|
||||
from django.utils.text import format_lazy
|
||||
from django.utils.translation import gettext_lazy
|
||||
|
||||
from anymail.exceptions import AnymailInvalidAddress, _LazyError
|
||||
from anymail.utils import (
|
||||
parse_address_list, parse_single_address, EmailAddress,
|
||||
UNSET,
|
||||
Attachment,
|
||||
is_lazy, force_non_lazy, force_non_lazy_dict, force_non_lazy_list,
|
||||
update_deep, UNSET,
|
||||
get_request_uri, get_request_basic_auth, parse_rfc2822date, querydict_getfirst,
|
||||
CaseInsensitiveCasePreservingDict)
|
||||
CaseInsensitiveCasePreservingDict,
|
||||
EmailAddress,
|
||||
force_non_lazy,
|
||||
force_non_lazy_dict,
|
||||
force_non_lazy_list,
|
||||
get_request_basic_auth,
|
||||
get_request_uri,
|
||||
is_lazy,
|
||||
parse_address_list,
|
||||
parse_rfc2822date,
|
||||
parse_single_address,
|
||||
querydict_getfirst,
|
||||
update_deep,
|
||||
)
|
||||
|
||||
|
||||
class ParseAddressListTests(SimpleTestCase):
|
||||
@@ -48,36 +58,46 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
def test_obsolete_display_name(self):
|
||||
# you can get away without the quotes if there are no commas or parens
|
||||
# (but it's not recommended)
|
||||
parsed_list = parse_address_list(['Display Name <test@example.com>'])
|
||||
parsed_list = parse_address_list(["Display Name <test@example.com>"])
|
||||
self.assertEqual(len(parsed_list), 1)
|
||||
parsed = parsed_list[0]
|
||||
self.assertEqual(parsed.addr_spec, "test@example.com")
|
||||
self.assertEqual(parsed.display_name, "Display Name")
|
||||
self.assertEqual(parsed.address, 'Display Name <test@example.com>')
|
||||
self.assertEqual(parsed.address, "Display Name <test@example.com>")
|
||||
|
||||
def test_unicode_display_name(self):
|
||||
parsed_list = parse_address_list(['"Unicode \N{HEAVY BLACK HEART}" <test@example.com>'])
|
||||
parsed_list = parse_address_list(
|
||||
['"Unicode \N{HEAVY BLACK HEART}" <test@example.com>']
|
||||
)
|
||||
self.assertEqual(len(parsed_list), 1)
|
||||
parsed = parsed_list[0]
|
||||
self.assertEqual(parsed.addr_spec, "test@example.com")
|
||||
self.assertEqual(parsed.display_name, "Unicode \N{HEAVY BLACK HEART}")
|
||||
# formatted display-name automatically shifts to quoted-printable/base64 for non-ascii chars:
|
||||
self.assertEqual(parsed.address, '=?utf-8?b?VW5pY29kZSDinaQ=?= <test@example.com>')
|
||||
# formatted display-name automatically shifts
|
||||
# to quoted-printable/base64 for non-ascii chars:
|
||||
self.assertEqual(
|
||||
parsed.address, "=?utf-8?b?VW5pY29kZSDinaQ=?= <test@example.com>"
|
||||
)
|
||||
|
||||
def test_invalid_display_name(self):
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Invalid email address 'webmaster'"):
|
||||
parse_address_list(['webmaster'])
|
||||
with self.assertRaisesMessage(
|
||||
AnymailInvalidAddress, "Invalid email address 'webmaster'"
|
||||
):
|
||||
parse_address_list(["webmaster"])
|
||||
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Maybe missing quotes around a display-name?"):
|
||||
with self.assertRaisesMessage(
|
||||
AnymailInvalidAddress, "Maybe missing quotes around a display-name?"
|
||||
):
|
||||
# this parses as multiple email addresses, because of the comma:
|
||||
parse_address_list(['Display Name, Inc. <test@example.com>'])
|
||||
parse_address_list(["Display Name, Inc. <test@example.com>"])
|
||||
|
||||
def test_idn(self):
|
||||
parsed_list = parse_address_list(["idn@\N{ENVELOPE}.example.com"])
|
||||
self.assertEqual(len(parsed_list), 1)
|
||||
parsed = parsed_list[0]
|
||||
self.assertEqual(parsed.addr_spec, "idn@\N{ENVELOPE}.example.com")
|
||||
self.assertEqual(parsed.address, "idn@xn--4bi.example.com") # punycode-encoded domain
|
||||
# punycode-encoded domain:
|
||||
self.assertEqual(parsed.address, "idn@xn--4bi.example.com")
|
||||
self.assertEqual(parsed.username, "idn")
|
||||
self.assertEqual(parsed.domain, "\N{ENVELOPE}.example.com")
|
||||
|
||||
@@ -88,23 +108,23 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
|
||||
def test_empty_address(self):
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list([''])
|
||||
parse_address_list([""])
|
||||
|
||||
def test_whitespace_only_address(self):
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list([' '])
|
||||
parse_address_list([" "])
|
||||
|
||||
def test_invalid_address(self):
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list(['localonly'])
|
||||
parse_address_list(["localonly"])
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list(['localonly@'])
|
||||
parse_address_list(["localonly@"])
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list(['@domainonly'])
|
||||
parse_address_list(["@domainonly"])
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list(['<localonly@>'])
|
||||
parse_address_list(["<localonly@>"])
|
||||
with self.assertRaises(AnymailInvalidAddress):
|
||||
parse_address_list(['<@domainonly>'])
|
||||
parse_address_list(["<@domainonly>"])
|
||||
|
||||
def test_email_list(self):
|
||||
parsed_list = parse_address_list(["first@example.com", "second@example.com"])
|
||||
@@ -126,11 +146,12 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
# the bare "Display Name" below should *not* get merged with
|
||||
# the email in the second item
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Display Name"):
|
||||
parse_address_list(['"Display Name"', '<valid@example.com>'])
|
||||
parse_address_list(['"Display Name"', "<valid@example.com>"])
|
||||
|
||||
def test_invalid_with_unicode(self):
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress,
|
||||
"Invalid email address '\N{ENVELOPE}'"):
|
||||
with self.assertRaisesMessage(
|
||||
AnymailInvalidAddress, "Invalid email address '\N{ENVELOPE}'"
|
||||
):
|
||||
parse_address_list(["\N{ENVELOPE}"])
|
||||
|
||||
def test_single_string(self):
|
||||
@@ -140,7 +161,9 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
self.assertEqual(parsed_list[0].addr_spec, "one@example.com")
|
||||
|
||||
def test_lazy_strings(self):
|
||||
parsed_list = parse_address_list([gettext_lazy('"Example, Inc." <one@example.com>')])
|
||||
parsed_list = parse_address_list(
|
||||
[gettext_lazy('"Example, Inc." <one@example.com>')]
|
||||
)
|
||||
self.assertEqual(len(parsed_list), 1)
|
||||
self.assertEqual(parsed_list[0].display_name, "Example, Inc.")
|
||||
self.assertEqual(parsed_list[0].addr_spec, "one@example.com")
|
||||
@@ -154,7 +177,9 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
parsed = parse_single_address("one@example.com")
|
||||
self.assertEqual(parsed.address, "one@example.com")
|
||||
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Only one email address is allowed; found 2"):
|
||||
with self.assertRaisesMessage(
|
||||
AnymailInvalidAddress, "Only one email address is allowed; found 2"
|
||||
):
|
||||
parse_single_address("one@example.com, two@example.com")
|
||||
|
||||
with self.assertRaisesMessage(AnymailInvalidAddress, "Invalid email address"):
|
||||
@@ -174,8 +199,10 @@ class ParseAddressListTests(SimpleTestCase):
|
||||
_ = EmailAddress(name, addr)
|
||||
|
||||
def test_email_address_repr(self):
|
||||
self.assertEqual("EmailAddress('Name', 'addr@example.com')",
|
||||
repr(EmailAddress('Name', 'addr@example.com')))
|
||||
self.assertEqual(
|
||||
"EmailAddress('Name', 'addr@example.com')",
|
||||
repr(EmailAddress("Name", "addr@example.com")),
|
||||
)
|
||||
|
||||
|
||||
class NormalizedAttachmentTests(SimpleTestCase):
|
||||
@@ -192,25 +219,32 @@ class NormalizedAttachmentTests(SimpleTestCase):
|
||||
self.assertFalse(att.inline)
|
||||
self.assertIsNone(att.content_id)
|
||||
self.assertEqual(att.cid, "")
|
||||
self.assertEqual(repr(att), "Attachment<image/x-emoticon, len=3, name='emoticon.txt'>")
|
||||
self.assertEqual(
|
||||
repr(att), "Attachment<image/x-emoticon, len=3, name='emoticon.txt'>"
|
||||
)
|
||||
|
||||
def test_content_disposition_inline(self):
|
||||
image = MIMEImage(b";-)", "x-emoticon")
|
||||
image["Content-Disposition"] = 'inline'
|
||||
image["Content-Disposition"] = "inline"
|
||||
att = Attachment(image, "ascii")
|
||||
self.assertIsNone(att.name)
|
||||
self.assertEqual(att.content, b";-)")
|
||||
self.assertTrue(att.inline) # even without the Content-ID
|
||||
self.assertIsNone(att.content_id)
|
||||
self.assertEqual(att.cid, "")
|
||||
self.assertEqual(repr(att), "Attachment<inline, image/x-emoticon, len=3, content_id=None>")
|
||||
self.assertEqual(
|
||||
repr(att), "Attachment<inline, image/x-emoticon, len=3, content_id=None>"
|
||||
)
|
||||
|
||||
image["Content-ID"] = "<abc123@example.net>"
|
||||
att = Attachment(image, "ascii")
|
||||
self.assertEqual(att.content_id, "<abc123@example.net>")
|
||||
self.assertEqual(att.cid, "abc123@example.net")
|
||||
self.assertEqual(repr(att),
|
||||
"Attachment<inline, image/x-emoticon, len=3, content_id='<abc123@example.net>'>")
|
||||
self.assertEqual(
|
||||
repr(att),
|
||||
"Attachment<inline, image/x-emoticon, len=3,"
|
||||
" content_id='<abc123@example.net>'>",
|
||||
)
|
||||
|
||||
def test_content_id_implies_inline(self):
|
||||
"""A MIME object with a Content-ID should be assumed to be inline"""
|
||||
@@ -219,8 +253,11 @@ class NormalizedAttachmentTests(SimpleTestCase):
|
||||
att = Attachment(image, "ascii")
|
||||
self.assertTrue(att.inline)
|
||||
self.assertEqual(att.content_id, "<abc123@example.net>")
|
||||
self.assertEqual(repr(att),
|
||||
"Attachment<inline, image/x-emoticon, len=3, content_id='<abc123@example.net>'>")
|
||||
self.assertEqual(
|
||||
repr(att),
|
||||
"Attachment<inline, image/x-emoticon, len=3,"
|
||||
" content_id='<abc123@example.net>'>",
|
||||
)
|
||||
|
||||
# ... but not if explicit Content-Disposition says otherwise
|
||||
image["Content-Disposition"] = "attachment"
|
||||
@@ -246,7 +283,7 @@ class LazyCoercionTests(SimpleTestCase):
|
||||
self.assertFalse(is_lazy("text not lazy"))
|
||||
self.assertFalse(is_lazy(b"bytes not lazy"))
|
||||
self.assertFalse(is_lazy(None))
|
||||
self.assertFalse(is_lazy({'dict': "not lazy"}))
|
||||
self.assertFalse(is_lazy({"dict": "not lazy"}))
|
||||
self.assertFalse(is_lazy(["list", "not lazy"]))
|
||||
self.assertFalse(is_lazy(object()))
|
||||
self.assertFalse(is_lazy([gettext_lazy("doesn't recurse")]))
|
||||
@@ -257,10 +294,20 @@ class LazyCoercionTests(SimpleTestCase):
|
||||
self.assertEqual(result, "text")
|
||||
|
||||
def test_format_lazy(self):
|
||||
self.assertTrue(is_lazy(format_lazy("{0}{1}",
|
||||
gettext_lazy("concatenation"), gettext_lazy("is lazy"))))
|
||||
result = force_non_lazy(format_lazy("{first}/{second}",
|
||||
first=gettext_lazy("text"), second=gettext_lazy("format")))
|
||||
self.assertTrue(
|
||||
is_lazy(
|
||||
format_lazy(
|
||||
"{0}{1}", gettext_lazy("concatenation"), gettext_lazy("is lazy")
|
||||
)
|
||||
)
|
||||
)
|
||||
result = force_non_lazy(
|
||||
format_lazy(
|
||||
"{first}/{second}",
|
||||
first=gettext_lazy("text"),
|
||||
second=gettext_lazy("format"),
|
||||
)
|
||||
)
|
||||
self.assertIsInstance(result, str)
|
||||
self.assertEqual(result, "text/format")
|
||||
|
||||
@@ -279,11 +326,12 @@ class LazyCoercionTests(SimpleTestCase):
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_force_dict(self):
|
||||
result = force_non_lazy_dict({'a': 1, 'b': gettext_lazy("b"),
|
||||
'c': {'c1': gettext_lazy("c1")}})
|
||||
self.assertEqual(result, {'a': 1, 'b': "b", 'c': {'c1': "c1"}})
|
||||
self.assertIsInstance(result['b'], str)
|
||||
self.assertIsInstance(result['c']['c1'], str)
|
||||
result = force_non_lazy_dict(
|
||||
{"a": 1, "b": gettext_lazy("b"), "c": {"c1": gettext_lazy("c1")}}
|
||||
)
|
||||
self.assertEqual(result, {"a": 1, "b": "b", "c": {"c1": "c1"}})
|
||||
self.assertIsInstance(result["b"], str)
|
||||
self.assertIsInstance(result["c"]["c1"], str)
|
||||
|
||||
def test_force_list(self):
|
||||
result = force_non_lazy_list([0, gettext_lazy("b"), "c"])
|
||||
@@ -295,26 +343,28 @@ class UpdateDeepTests(SimpleTestCase):
|
||||
"""Test utils.update_deep"""
|
||||
|
||||
def test_updates_recursively(self):
|
||||
first = {'a': {'a1': 1, 'aa': {}}, 'b': "B"}
|
||||
second = {'a': {'a2': 2, 'aa': {'aa1': 11}}}
|
||||
first = {"a": {"a1": 1, "aa": {}}, "b": "B"}
|
||||
second = {"a": {"a2": 2, "aa": {"aa1": 11}}}
|
||||
result = update_deep(first, second)
|
||||
self.assertEqual(first, {'a': {'a1': 1, 'a2': 2, 'aa': {'aa1': 11}}, 'b': "B"})
|
||||
self.assertIsNone(result) # modifies first in place; doesn't return it (same as dict.update())
|
||||
self.assertEqual(first, {"a": {"a1": 1, "a2": 2, "aa": {"aa1": 11}}, "b": "B"})
|
||||
# modifies first in place; doesn't return it (same as dict.update()):
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_overwrites_sequences(self):
|
||||
"""Only mappings are handled recursively; sequences are considered atomic"""
|
||||
first = {'a': [1, 2]}
|
||||
second = {'a': [3]}
|
||||
first = {"a": [1, 2]}
|
||||
second = {"a": [3]}
|
||||
update_deep(first, second)
|
||||
self.assertEqual(first, {'a': [3]})
|
||||
self.assertEqual(first, {"a": [3]})
|
||||
|
||||
def test_handles_non_dict_mappings(self):
|
||||
"""Mapping types in general are supported"""
|
||||
from collections import OrderedDict, defaultdict
|
||||
first = OrderedDict(a=OrderedDict(a1=1), c={'c1': 1})
|
||||
|
||||
first = OrderedDict(a=OrderedDict(a1=1), c={"c1": 1})
|
||||
second = defaultdict(None, a=dict(a2=2))
|
||||
update_deep(first, second)
|
||||
self.assertEqual(first, {'a': {'a1': 1, 'a2': 2}, 'c': {'c1': 1}})
|
||||
self.assertEqual(first, {"a": {"a1": 1, "a2": 2}, "c": {"c1": 1}})
|
||||
|
||||
|
||||
@override_settings(ALLOWED_HOSTS=[".example.com"])
|
||||
@@ -327,68 +377,89 @@ class RequestUtilsTests(SimpleTestCase):
|
||||
|
||||
@staticmethod
|
||||
def basic_auth(username, password):
|
||||
"""Return HTTP_AUTHORIZATION header value for basic auth with username, password"""
|
||||
credentials = base64.b64encode("{}:{}".format(username, password).encode('utf-8')).decode('utf-8')
|
||||
"""
|
||||
Return HTTP_AUTHORIZATION header value for basic auth with username, password
|
||||
"""
|
||||
credentials = base64.b64encode(
|
||||
"{}:{}".format(username, password).encode("utf-8")
|
||||
).decode("utf-8")
|
||||
return "Basic {}".format(credentials)
|
||||
|
||||
def test_get_request_basic_auth(self):
|
||||
# without auth:
|
||||
request = self.request_factory.post('/path/to/?query',
|
||||
HTTP_HOST='www.example.com',
|
||||
HTTP_SCHEME='https')
|
||||
request = self.request_factory.post(
|
||||
"/path/to/?query", HTTP_HOST="www.example.com", HTTP_SCHEME="https"
|
||||
)
|
||||
self.assertIsNone(get_request_basic_auth(request))
|
||||
|
||||
# with basic auth:
|
||||
request = self.request_factory.post('/path/to/?query',
|
||||
HTTP_HOST='www.example.com',
|
||||
HTTP_AUTHORIZATION=self.basic_auth('user', 'pass'))
|
||||
request = self.request_factory.post(
|
||||
"/path/to/?query",
|
||||
HTTP_HOST="www.example.com",
|
||||
HTTP_AUTHORIZATION=self.basic_auth("user", "pass"),
|
||||
)
|
||||
self.assertEqual(get_request_basic_auth(request), "user:pass")
|
||||
|
||||
# with some other auth
|
||||
request = self.request_factory.post('/path/to/?query',
|
||||
HTTP_HOST='www.example.com',
|
||||
HTTP_AUTHORIZATION="Bearer abcde12345")
|
||||
request = self.request_factory.post(
|
||||
"/path/to/?query",
|
||||
HTTP_HOST="www.example.com",
|
||||
HTTP_AUTHORIZATION="Bearer abcde12345",
|
||||
)
|
||||
self.assertIsNone(get_request_basic_auth(request))
|
||||
|
||||
def test_get_request_uri(self):
|
||||
# without auth:
|
||||
request = self.request_factory.post('/path/to/?query', secure=True,
|
||||
HTTP_HOST='www.example.com')
|
||||
self.assertEqual(get_request_uri(request),
|
||||
"https://www.example.com/path/to/?query")
|
||||
request = self.request_factory.post(
|
||||
"/path/to/?query", secure=True, HTTP_HOST="www.example.com"
|
||||
)
|
||||
self.assertEqual(
|
||||
get_request_uri(request), "https://www.example.com/path/to/?query"
|
||||
)
|
||||
|
||||
# with basic auth:
|
||||
request = self.request_factory.post('/path/to/?query', secure=True,
|
||||
HTTP_HOST='www.example.com',
|
||||
HTTP_AUTHORIZATION=self.basic_auth('user', 'pass'))
|
||||
self.assertEqual(get_request_uri(request),
|
||||
"https://user:pass@www.example.com/path/to/?query")
|
||||
request = self.request_factory.post(
|
||||
"/path/to/?query",
|
||||
secure=True,
|
||||
HTTP_HOST="www.example.com",
|
||||
HTTP_AUTHORIZATION=self.basic_auth("user", "pass"),
|
||||
)
|
||||
self.assertEqual(
|
||||
get_request_uri(request), "https://user:pass@www.example.com/path/to/?query"
|
||||
)
|
||||
|
||||
@override_settings(SECURE_PROXY_SSL_HEADER=('HTTP_X_FORWARDED_PROTO', 'https'),
|
||||
USE_X_FORWARDED_HOST=True)
|
||||
@override_settings(
|
||||
SECURE_PROXY_SSL_HEADER=("HTTP_X_FORWARDED_PROTO", "https"),
|
||||
USE_X_FORWARDED_HOST=True,
|
||||
)
|
||||
def test_get_request_uri_with_proxy(self):
|
||||
request = self.request_factory.post('/path/to/?query', secure=False,
|
||||
HTTP_HOST='web1.internal',
|
||||
HTTP_X_FORWARDED_PROTO='https',
|
||||
HTTP_X_FORWARDED_HOST='secret.example.com:8989',
|
||||
HTTP_AUTHORIZATION=self.basic_auth('user', 'pass'))
|
||||
self.assertEqual(get_request_uri(request),
|
||||
"https://user:pass@secret.example.com:8989/path/to/?query")
|
||||
request = self.request_factory.post(
|
||||
"/path/to/?query",
|
||||
secure=False,
|
||||
HTTP_HOST="web1.internal",
|
||||
HTTP_X_FORWARDED_PROTO="https",
|
||||
HTTP_X_FORWARDED_HOST="secret.example.com:8989",
|
||||
HTTP_AUTHORIZATION=self.basic_auth("user", "pass"),
|
||||
)
|
||||
self.assertEqual(
|
||||
get_request_uri(request),
|
||||
"https://user:pass@secret.example.com:8989/path/to/?query",
|
||||
)
|
||||
|
||||
|
||||
class QueryDictUtilsTests(SimpleTestCase):
|
||||
def test_querydict_getfirst(self):
|
||||
q = QueryDict("a=one&a=two&a=three")
|
||||
q.getfirst = querydict_getfirst.__get__(q)
|
||||
self.assertEqual(q.getfirst('a'), "one")
|
||||
self.assertEqual(q.getfirst("a"), "one")
|
||||
|
||||
# missing key exception:
|
||||
with self.assertRaisesMessage(KeyError, "not a key"):
|
||||
q.getfirst("not a key")
|
||||
|
||||
# defaults:
|
||||
self.assertEqual(q.getfirst('not a key', "beta"), "beta")
|
||||
self.assertIsNone(q.getfirst('not a key', None))
|
||||
self.assertEqual(q.getfirst("not a key", "beta"), "beta")
|
||||
self.assertIsNone(q.getfirst("not a key", None))
|
||||
|
||||
|
||||
class ParseRFC2822DateTests(SimpleTestCase):
|
||||
@@ -406,9 +477,11 @@ class ParseRFC2822DateTests(SimpleTestCase):
|
||||
self.assertIsNotNone(dt.tzinfo) # aware
|
||||
|
||||
def test_without_timezones(self):
|
||||
dt = parse_rfc2822date("Tue, 24 Oct 2017 10:11:35 -0000") # "no timezone information"
|
||||
# "no timezone information":
|
||||
dt = parse_rfc2822date("Tue, 24 Oct 2017 10:11:35 -0000")
|
||||
self.assertEqual(dt.isoformat(), "2017-10-24T10:11:35")
|
||||
self.assertIsNone(dt.tzinfo) # naive (compare with +0000 version in previous test)
|
||||
# naive (compare with +0000 version in previous test):
|
||||
self.assertIsNone(dt.tzinfo)
|
||||
|
||||
dt = parse_rfc2822date("Tue, 24 Oct 2017 10:11:35")
|
||||
self.assertEqual(dt.isoformat(), "2017-10-24T10:11:35")
|
||||
|
||||
Reference in New Issue
Block a user