Loading django/db/migrations/questioner.py +4 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading django/db/migrations/writer.py +19 −5 Original line number Diff line number Diff line Loading @@ -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('')) Loading Loading @@ -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 Loading Loading @@ -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) Loading docs/releases/1.8.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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 ^^^^^^ Loading docs/topics/migrations.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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) Loading tests/migrations/test_writer.py +26 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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))) Loading @@ -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)) Loading Loading @@ -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
django/db/migrations/questioner.py +4 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading
django/db/migrations/writer.py +19 −5 Original line number Diff line number Diff line Loading @@ -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('')) Loading Loading @@ -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 Loading Loading @@ -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) Loading
docs/releases/1.8.txt +2 −0 Original line number Diff line number Diff line Loading @@ -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 ^^^^^^ Loading
docs/topics/migrations.txt +5 −0 Original line number Diff line number Diff line Loading @@ -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) Loading
tests/migrations/test_writer.py +26 −3 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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))) Loading @@ -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)) Loading Loading @@ -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)")