Fixed #23365 -- Added support for timezone-aware datetimes to migrations.
This commit is contained in:
parent
12809e1609
commit
a407b846b4
|
@ -5,7 +5,7 @@ import os
|
|||
import sys
|
||||
|
||||
from django.apps import apps
|
||||
from django.utils import datetime_safe, six
|
||||
from django.utils import datetime_safe, six, timezone
|
||||
from django.utils.six.moves import input
|
||||
|
||||
from .loader import MIGRATIONS_MODULE_NAME
|
||||
|
@ -108,7 +108,8 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|||
sys.exit(3)
|
||||
else:
|
||||
print("Please enter the default value now, as valid Python")
|
||||
print("The datetime module is available, so you can do e.g. datetime.date.today()")
|
||||
print("The datetime and django.utils.timezone modules are "
|
||||
"available, so you can do e.g. timezone.now()")
|
||||
while True:
|
||||
if six.PY3:
|
||||
# Six does not correctly abstract over the fact that
|
||||
|
@ -123,7 +124,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
|||
sys.exit(1)
|
||||
else:
|
||||
try:
|
||||
return eval(code, {}, {"datetime": datetime_safe})
|
||||
return eval(code, {}, {"datetime": datetime_safe, "timezone": timezone})
|
||||
except (SyntaxError, NameError) as e:
|
||||
print("Invalid input: %s" % e)
|
||||
return None
|
||||
|
|
|
@ -16,6 +16,7 @@ from django.db.migrations.loader import MigrationLoader
|
|||
from django.utils import datetime_safe, six
|
||||
from django.utils.encoding import force_text
|
||||
from django.utils.functional import Promise
|
||||
from django.utils.timezone import utc
|
||||
|
||||
|
||||
COMPILED_REGEX_TYPE = type(re.compile(''))
|
||||
|
@ -164,6 +165,20 @@ class MigrationWriter(object):
|
|||
|
||||
return (MIGRATION_TEMPLATE % items).encode("utf8")
|
||||
|
||||
@staticmethod
|
||||
def serialize_datetime(value):
|
||||
"""
|
||||
Returns a serialized version of a datetime object that is valid,
|
||||
executable python code. It converts timezone-aware values to utc with
|
||||
an 'executable' utc representation of tzinfo.
|
||||
"""
|
||||
if value.tzinfo is not None and value.tzinfo != utc:
|
||||
value = value.astimezone(utc)
|
||||
value_repr = repr(value).replace("<UTC>", "utc")
|
||||
if isinstance(value, datetime_safe.datetime):
|
||||
value_repr = "datetime.%s" % value_repr
|
||||
return value_repr
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
return "%s.py" % self.migration.name
|
||||
|
@ -268,12 +283,11 @@ class MigrationWriter(object):
|
|||
return "{%s}" % (", ".join("%s: %s" % (k, v) for k, v in strings)), imports
|
||||
# Datetimes
|
||||
elif isinstance(value, datetime.datetime):
|
||||
value_repr = cls.serialize_datetime(value)
|
||||
imports = ["import datetime"]
|
||||
if value.tzinfo is not None:
|
||||
raise ValueError("Cannot serialize datetime values with timezones. Either use a callable value for default or remove the timezone.")
|
||||
value_repr = repr(value)
|
||||
if isinstance(value, datetime_safe.datetime):
|
||||
value_repr = "datetime.%s" % value_repr
|
||||
return value_repr, {"import datetime"}
|
||||
imports.append("from django.utils.timezone import utc")
|
||||
return value_repr, set(imports)
|
||||
# Dates
|
||||
elif isinstance(value, datetime.date):
|
||||
value_repr = repr(value)
|
||||
|
|
|
@ -260,6 +260,8 @@ Management Commands
|
|||
* The :djadminopt:`--name` option for :djadmin:`makemigrations` allows you to
|
||||
to give the migration(s) a custom name instead of a generated one.
|
||||
|
||||
* :djadmin:`makemigrations` can now serialize timezone-aware values.
|
||||
|
||||
Models
|
||||
^^^^^^
|
||||
|
||||
|
|
|
@ -543,12 +543,17 @@ Django can serialize the following:
|
|||
- ``int``, ``long``, ``float``, ``bool``, ``str``, ``unicode``, ``bytes``, ``None``
|
||||
- ``list``, ``set``, ``tuple``, ``dict``
|
||||
- ``datetime.date``, ``datetime.time``, and ``datetime.datetime`` instances
|
||||
(include those that are timezone-aware)
|
||||
- ``decimal.Decimal`` instances
|
||||
- Any Django field
|
||||
- Any function or method reference (e.g. ``datetime.datetime.today``) (must be in module's top-level scope)
|
||||
- Any class reference (must be in module's top-level scope)
|
||||
- Anything with a custom ``deconstruct()`` method (:ref:`see below <custom-deconstruct-method>`)
|
||||
|
||||
.. versionchanged:: 1.8
|
||||
|
||||
Support for serializing timezone-aware datetimes was added.
|
||||
|
||||
Django can serialize the following on Python 3 only:
|
||||
|
||||
- Unbound methods used from within the class body (see below)
|
||||
|
|
|
@ -16,7 +16,7 @@ from django.conf import settings
|
|||
from django.utils import datetime_safe, six
|
||||
from django.utils.deconstruct import deconstructible
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.timezone import get_default_timezone
|
||||
from django.utils.timezone import get_default_timezone, utc, FixedOffset
|
||||
|
||||
import custom_migration_operations.operations
|
||||
import custom_migration_operations.more_operations
|
||||
|
@ -101,8 +101,8 @@ class WriterTests(TestCase):
|
|||
self.assertSerializedEqual(datetime.date.today())
|
||||
self.assertSerializedEqual(datetime.date.today)
|
||||
self.assertSerializedEqual(datetime.datetime.now().time())
|
||||
with self.assertRaises(ValueError):
|
||||
self.assertSerializedEqual(datetime.datetime(2012, 1, 1, 1, 1, tzinfo=get_default_timezone()))
|
||||
self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=get_default_timezone()))
|
||||
self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=FixedOffset(180)))
|
||||
safe_date = datetime_safe.date(2014, 3, 31)
|
||||
string, imports = MigrationWriter.serialize(safe_date)
|
||||
self.assertEqual(string, repr(datetime.date(2014, 3, 31)))
|
||||
|
@ -111,6 +111,10 @@ class WriterTests(TestCase):
|
|||
string, imports = MigrationWriter.serialize(safe_datetime)
|
||||
self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31)))
|
||||
self.assertEqual(imports, {'import datetime'})
|
||||
timezone_aware_datetime = datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)
|
||||
string, imports = MigrationWriter.serialize(timezone_aware_datetime)
|
||||
self.assertEqual(string, "datetime.datetime(2012, 1, 1, 1, 1, tzinfo=utc)")
|
||||
self.assertEqual(imports, {'import datetime', 'from django.utils.timezone import utc'})
|
||||
# Django fields
|
||||
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
||||
self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
|
||||
|
@ -312,3 +316,22 @@ class WriterTests(TestCase):
|
|||
result['custom_migration_operations'].operations.TestOperation,
|
||||
result['custom_migration_operations'].more_operations.TestOperation
|
||||
)
|
||||
|
||||
def test_serialize_datetime(self):
|
||||
"""
|
||||
#23365 -- Timezone-aware datetimes should be allowed.
|
||||
"""
|
||||
# naive datetime
|
||||
naive_datetime = datetime.datetime(2014, 1, 1, 1, 1)
|
||||
self.assertEqual(MigrationWriter.serialize_datetime(naive_datetime),
|
||||
"datetime.datetime(2014, 1, 1, 1, 1)")
|
||||
|
||||
# datetime with utc timezone
|
||||
utc_datetime = datetime.datetime(2014, 1, 1, 1, 1, tzinfo=utc)
|
||||
self.assertEqual(MigrationWriter.serialize_datetime(utc_datetime),
|
||||
"datetime.datetime(2014, 1, 1, 1, 1, tzinfo=utc)")
|
||||
|
||||
# datetime with FixedOffset tzinfo
|
||||
fixed_offset_datetime = datetime.datetime(2014, 1, 1, 1, 1, tzinfo=FixedOffset(180))
|
||||
self.assertEqual(MigrationWriter.serialize_datetime(fixed_offset_datetime),
|
||||
"datetime.datetime(2013, 12, 31, 22, 1, tzinfo=utc)")
|
||||
|
|
Loading…
Reference in New Issue