8000 [1.7.x] Fixed #23365 -- Added support for timezone-aware datetimes to… · alex-python/django@e31be40 · GitHub
[go: up one dir, main page]

Skip to content

Commit e31be40

Browse files
Rudy Mutterloic
authored andcommitted
[1.7.x] Fixed #23365 -- Added support for timezone-aware datetimes to migrations.
Backport of a407b84 from master
1 parent d830665 commit e31be40

File tree

5 files changed

+56
-11
lines changed

5 files changed

+56
-11
lines changed

django/db/migrations/questioner.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import sys
66

77
from django.apps import apps
8-
from django.utils import datetime_safe, six
8+
from django.utils import datetime_safe, six, timezone
99
from django.utils.six.moves import input
1010

1111
from .loader import MIGRATIONS_MODULE_NAME
@@ -108,7 +108,8 @@ def ask_not_null_addition(self, field_name, model_name):
108108
sys.exit(3)
109109
else:
110110
print("Please enter the default value now, as valid Python")
111-
print("The datetime module is available, so you can do e.g. datetime.date.today()")
111+
print("The datetime and django.utils.timezone modules are "
112+
"available, so you can do e.g. timezone.now()")
112113
while True:
113114
if six.PY3:
114115
# Six does not correctly abstract over the fact that
@@ -123,7 +124,7 @@ def ask_not_null_addition(self, field_name, model_name):
123124
sys.exit(1)
124125
else:
125126
try:
126-
return eval(code, {}, {"datetime": datetime_safe})
127+
return eval(code, {}, {"datetime": datetime_safe, "timezone": timezone})
127128
except (SyntaxError, NameError) as e:
128129
print("Invalid input: %s" % e)
129130
return None

django/db/migrations/writer.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from django.utils import datetime_safe, six
1717
from django.utils.encoding import force_text
1818
from django.utils.functional import Promise
19+
from django.utils.timezone import utc
1920

2021

2122
COMPILED_REGEX_TYPE = type(re.compile(''))
@@ -164,6 +165,20 @@ def as_string(self):
164165

165166
return (MIGRATION_TEMPLATE % items).encode("utf8")
166167

168+
@staticmethod
169+
def serialize_datetime(value):
170+
"""
171+
Returns a serialized version of a datetime object that is valid,
172+
executable python code. It converts timezone-aware values to utc with
173+
an 'executable' utc representation of tzinfo.
174+
"""
175+
if value.tzinfo is not None and value.tzinfo != utc:
176+
value = value.astimezone(utc)
177+
value_repr = repr(value).replace("<UTC>", "utc")
178+
if isinstance(value, datetime_safe.datetime):
179+
value_repr = "datetime.%s" % value_repr
180+
return value_repr
181+
167182
@property
168183
def filename(self):
169184
return "%s.py" % self.migration.name
@@ -267,12 +282,11 @@ def serialize(cls, value):
267282
return "{%s}" % (", ".join("%s: %s" % (k, v) for k, v in strings)), imports
268283
# Datetimes
269284
elif isinstance(value, datetime.datetime):
285+
value_repr = cls.serialize_datetime(value)
286+
imports = ["import datetime"]
270287
if value.tzinfo is not None:
271-
raise ValueError("Cannot serialize datetime values with timezones. Either use a callable value for default or remove the timezone.")
272-
value_repr = repr(value)
273-
if isinstance(value, datetime_safe.datetime):
274-
value_repr = "datetime.%s" % value_repr
275-
return value_repr, set(["import datetime"])
288+
imports.append("from django.utils.timezone import utc")
289+
return value_repr, set(imports)
276290
# Dates
277291
elif isinstance(value, datetime.date):
278292
value_repr = repr(value)

docs/releases/1.7.1.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,5 @@ Bugfixes
106106

107107
* Made :func:`~django.utils.http.urlsafe_base64_decode` return the proper
108108
type (byte string) on Python 3 (:ticket:`23333`).
109+
110+
* :djadmin:`makemigrations` can now serialize timezone-aware values (:ticket:`23365`).

docs/topics/migrations.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,12 +536,17 @@ Django can serialize the following:
536536
- ``int``, ``long``, ``float``, ``bool``, ``str``, ``unicode``, ``bytes``, ``None``
537537
- ``list``, ``set``, ``tuple``, ``dict``
538538
- ``datetime.date``, ``datetime.time``, and ``datetime.datetime`` instances
539+
(include those that are timezone-aware)
539540
- ``decimal.Decimal`` instances
540541
- Any Django field
541542
- Any function or method reference (e.g. ``datetime.datetime.today``) (must be in module's top-level scope)
542543
- Any class reference (must be in module's top-level scope)
543544
- Anything with a custom ``deconstruct()`` method (:ref:`see below <custom-deconstruct-method>`)
544545

546+
.. versionchanged:: 1.7.1
547+
548+
Support for serializing timezone-aware datetimes was added.
549+
545550
Django can serialize the following on Python 3 only:
546551

547552
- Unbound methods used from within the class body (see below)

tests/migrations/test_writer.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from django.utils import datetime_safe, six
1717
from django.utils.deconstruct import deconstructible
1818
from django.utils.translation import ugettext_lazy as _
19-
from django.utils.timezone import get_default_timezone
19+
from django.utils.timezone import get_default_timezone, utc, FixedOffset
2020

2121
import custom_migration_operations.operations
2222
import custom_migration_operations.more_operations
@@ -101,8 +101,8 @@ def test_serialize(self):
101101
self.assertSerializedEqual(datetime.date.today())
102102
self.assertSerializedEqual(datetime.date.today)
103103
self.assertSerializedEqual(datetime.datetime.now().time())
104-
with self.assertRaises(ValueError):
105-
self.assertSerializedEqual(datetime.datetime(2012, 1, 1, 1, 1, tzinfo=get_default_timezone()))
104+
self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=get_default_timezone()))
105+
self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=FixedOffset(180)))
106106
safe_date = datetime_safe.date(2014, 3, 31)
107107
string, imports = MigrationWriter.serialize(safe_date)
108108
self.assertEqual(string, repr(datetime.date(2014, 3, 31)))
@@ -111,6 +111,10 @@ def test_serialize(self):
111111
string, imports = MigrationWriter.serialize(safe_datetime)
112112
self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31)))
113113
self.assertEqual(imports, {'import datetime'})
114+
timezone_aware_datetime = datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)
115+
string, imports = MigrationWriter.serialize(timezone_aware_datetime)
116+
self.assertEqual(string, "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)")
117+
self.assertEqual(imports, {'import datetime', 'from django.utils.timezone import utc'})
114118
# Django fields
115119
self.assertSerializedFieldEqual(models.CharField(max_length=255))
116120
self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
@@ -312,3 +316,22 @@ def test_custom_operation(self):
312316
result['custom_migration_operations'].operations.TestOperation,
313317
result['custom_migration_operations'].more_operations.TestOperation
314318
)
319+
320+
def test_serialize_datetime(self):
321+
"""
322+
#23365 -- Timezone-aware datetimes should be allowed.
323+
"""
324+
# naive datetime
325+
naive_datetime = datetime.datetime(2014, 1, 1, 1, 1)
326+
self.assertEqual(MigrationWriter.serialize_datetime(naive_datetime),
327+
"datetime.datetime(2014, 1, 1, 1, 1)")
328+
329+
# datetime with utc timezone
330+
utc_datetime = datetime.datetime(2014, 1, 1, 1, 1, tzinfo=utc)
331+
self.assertEqual(MigrationWriter.serialize_datetime(utc_datetime),
332+
"datetime.datetime(2014, 1, 1, 1, 1, tzinfo=utc)")
333+
334+
# datetime with FixedOffset tzinfo
335+
fixed_offset_datetime = datetime.datetime(2014, 1, 1, 1, 1, tzinfo=FixedOffset(180))
336+
self.assertEqual(MigrationWriter.serialize_datetime(fixed_offset_datetime),
337+
"datetime.datetime(2013, 12, 31, 22, 1, tzinfo=utc)")

0 commit comments

Comments
 (0)
0