diff options
| author | Rudy Mutter <rudy@yetihq.com> | 2014-09-06 13:42:36 -0700 |
|---|---|---|
| committer | Loic Bistuer <loic.bistuer@gmail.com> | 2014-10-09 22:39:17 +0700 |
| commit | e31be40f163e7d478946530acb1068c6deadb845 (patch) | |
| tree | 33aa414c5789e46c1ddcb4d648d16297139ac04e | |
| parent | d8306650071369ee9d7cad71120d13c22e900a10 (diff) | |
[1.7.x] Fixed #23365 -- Added support for timezone-aware datetimes to migrations.
Backport of a407b846b4 from master
| -rw-r--r-- | django/db/migrations/questioner.py | 7 | ||||
| -rw-r--r-- | django/db/migrations/writer.py | 24 | ||||
| -rw-r--r-- | docs/releases/1.7.1.txt | 2 | ||||
| -rwxr-xr-x | docs/topics/migrations.txt | 5 | ||||
| -rw-r--r-- | tests/migrations/test_writer.py | 29 |
5 files changed, 56 insertions, 11 deletions
diff --git a/django/db/migrations/questioner.py b/django/db/migrations/questioner.py index 0476ba749f..3b31796cbc 100644 --- a/django/db/migrations/questioner.py +++ b/django/db/migrations/questioner.py @@ -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 diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index 82396c1033..894c6b3009 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -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 @@ -267,12 +282,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, set(["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) diff --git a/docs/releases/1.7.1.txt b/docs/releases/1.7.1.txt index 01edc4c8f5..68370dd44a 100644 --- a/docs/releases/1.7.1.txt +++ b/docs/releases/1.7.1.txt @@ -106,3 +106,5 @@ Bugfixes * Made :func:`~django.utils.http.urlsafe_base64_decode` return the proper type (byte string) on Python 3 (:ticket:`23333`). + +* :djadmin:`makemigrations` can now serialize timezone-aware values (:ticket:`23365`). diff --git a/docs/topics/migrations.txt b/docs/topics/migrations.txt index 739e8ed165..1321d5908d 100755 --- a/docs/topics/migrations.txt +++ b/docs/topics/migrations.txt @@ -536,12 +536,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.7.1 + + 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) diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index 2b7ceddb2d..0503270ed2 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -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)") |
