mirror of https://github.com/django/django.git
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
|
import sys
|
||||||
|
|
||||||
from django.apps import apps
|
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 django.utils.six.moves import input
|
||||||
|
|
||||||
from .loader import MIGRATIONS_MODULE_NAME
|
from .loader import MIGRATIONS_MODULE_NAME
|
||||||
|
@ -108,7 +108,8 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
||||||
sys.exit(3)
|
sys.exit(3)
|
||||||
else:
|
else:
|
||||||
print("Please enter the default value now, as valid Python")
|
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:
|
while True:
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
# Six does not correctly abstract over the fact that
|
# Six does not correctly abstract over the fact that
|
||||||
|
@ -123,7 +124,7 @@ class InteractiveMigrationQuestioner(MigrationQuestioner):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
return eval(code, {}, {"datetime": datetime_safe})
|
return eval(code, {}, {"datetime": datetime_safe, "timezone": timezone})
|
||||||
except (SyntaxError, NameError) as e:
|
except (SyntaxError, NameError) as e:
|
||||||
print("Invalid input: %s" % e)
|
print("Invalid input: %s" % e)
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -16,6 +16,7 @@ from django.db.migrations.loader import MigrationLoader
|
||||||
from django.utils import datetime_safe, six
|
from django.utils import datetime_safe, six
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.functional import Promise
|
from django.utils.functional import Promise
|
||||||
|
from django.utils.timezone import utc
|
||||||
|
|
||||||
|
|
||||||
COMPILED_REGEX_TYPE = type(re.compile(''))
|
COMPILED_REGEX_TYPE = type(re.compile(''))
|
||||||
|
@ -164,6 +165,20 @@ class MigrationWriter(object):
|
||||||
|
|
||||||
return (MIGRATION_TEMPLATE % items).encode("utf8")
|
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
|
@property
|
||||||
def filename(self):
|
def filename(self):
|
||||||
return "%s.py" % self.migration.name
|
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
|
return "{%s}" % (", ".join("%s: %s" % (k, v) for k, v in strings)), imports
|
||||||
# Datetimes
|
# Datetimes
|
||||||
elif isinstance(value, datetime.datetime):
|
elif isinstance(value, datetime.datetime):
|
||||||
|
value_repr = cls.serialize_datetime(value)
|
||||||
|
imports = ["import datetime"]
|
||||||
if value.tzinfo is not None:
|
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.")
|
imports.append("from django.utils.timezone import utc")
|
||||||
value_repr = repr(value)
|
return value_repr, set(imports)
|
||||||
if isinstance(value, datetime_safe.datetime):
|
|
||||||
value_repr = "datetime.%s" % value_repr
|
|
||||||
return value_repr, {"import datetime"}
|
|
||||||
# Dates
|
# Dates
|
||||||
elif isinstance(value, datetime.date):
|
elif isinstance(value, datetime.date):
|
||||||
value_repr = repr(value)
|
value_repr = repr(value)
|
||||||
|
|
|
@ -260,6 +260,8 @@ Management Commands
|
||||||
* The :djadminopt:`--name` option for :djadmin:`makemigrations` allows you to
|
* The :djadminopt:`--name` option for :djadmin:`makemigrations` allows you to
|
||||||
to give the migration(s) a custom name instead of a generated one.
|
to give the migration(s) a custom name instead of a generated one.
|
||||||
|
|
||||||
|
* :djadmin:`makemigrations` can now serialize timezone-aware values.
|
||||||
|
|
||||||
Models
|
Models
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -543,12 +543,17 @@ Django can serialize the following:
|
||||||
- ``int``, ``long``, ``float``, ``bool``, ``str``, ``unicode``, ``bytes``, ``None``
|
- ``int``, ``long``, ``float``, ``bool``, ``str``, ``unicode``, ``bytes``, ``None``
|
||||||
- ``list``, ``set``, ``tuple``, ``dict``
|
- ``list``, ``set``, ``tuple``, ``dict``
|
||||||
- ``datetime.date``, ``datetime.time``, and ``datetime.datetime`` instances
|
- ``datetime.date``, ``datetime.time``, and ``datetime.datetime`` instances
|
||||||
|
(include those that are timezone-aware)
|
||||||
- ``decimal.Decimal`` instances
|
- ``decimal.Decimal`` instances
|
||||||
- Any Django field
|
- Any Django field
|
||||||
- Any function or method reference (e.g. ``datetime.datetime.today``) (must be in module's top-level scope)
|
- 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)
|
- Any class reference (must be in module's top-level scope)
|
||||||
- Anything with a custom ``deconstruct()`` method (:ref:`see below <custom-deconstruct-method>`)
|
- 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:
|
Django can serialize the following on Python 3 only:
|
||||||
|
|
||||||
- Unbound methods used from within the class body (see below)
|
- 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 import datetime_safe, six
|
||||||
from django.utils.deconstruct import deconstructible
|
from django.utils.deconstruct import deconstructible
|
||||||
from django.utils.translation import ugettext_lazy as _
|
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.operations
|
||||||
import custom_migration_operations.more_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.date.today)
|
self.assertSerializedEqual(datetime.date.today)
|
||||||
self.assertSerializedEqual(datetime.datetime.now().time())
|
self.assertSerializedEqual(datetime.datetime.now().time())
|
||||||
with self.assertRaises(ValueError):
|
self.assertSerializedEqual(datetime.datetime(2014, 1, 1, 1, 1, tzinfo=get_default_timezone()))
|
||||||
self.assertSerializedEqual(datetime.datetime(2012, 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)
|
safe_date = datetime_safe.date(2014, 3, 31)
|
||||||
string, imports = MigrationWriter.serialize(safe_date)
|
string, imports = MigrationWriter.serialize(safe_date)
|
||||||
self.assertEqual(string, repr(datetime.date(2014, 3, 31)))
|
self.assertEqual(string, repr(datetime.date(2014, 3, 31)))
|
||||||
|
@ -111,6 +111,10 @@ class WriterTests(TestCase):
|
||||||
string, imports = MigrationWriter.serialize(safe_datetime)
|
string, imports = MigrationWriter.serialize(safe_datetime)
|
||||||
self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31)))
|
self.assertEqual(string, repr(datetime.datetime(2014, 3, 31, 16, 4, 31)))
|
||||||
self.assertEqual(imports, {'import datetime'})
|
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
|
# Django fields
|
||||||
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
self.assertSerializedFieldEqual(models.CharField(max_length=255))
|
||||||
self.assertSerializedFieldEqual(models.TextField(null=True, blank=True))
|
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'].operations.TestOperation,
|
||||||
result['custom_migration_operations'].more_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