From e9975ed3cd3243d78b89fe2b44a152263ba5a602 Mon Sep 17 00:00:00 2001 From: Gavin Wahl Date: Tue, 2 Dec 2014 18:52:58 -0700 Subject: [PATCH] [1.7.x] Fixed #23950 -- Prevented calling deconstruct on classes in MigrationWriter. Backport of dee4d23f7e703aec2d1244e4facbf7f4c88deed5 from master --- django/db/migrations/writer.py | 28 ++++++++++++++-------------- docs/releases/1.7.2.txt | 3 +++ tests/migrations/test_writer.py | 11 +++++++++++ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/django/db/migrations/writer.py b/django/db/migrations/writer.py index 53ec4143354..074a615afe0 100644 --- a/django/db/migrations/writer.py +++ b/django/db/migrations/writer.py @@ -329,6 +329,20 @@ class MigrationWriter(object): elif isinstance(value, models.Field): attr_name, path, args, kwargs = value.deconstruct() return cls.serialize_deconstructed(path, args, kwargs) + # Classes + elif isinstance(value, type): + special_cases = [ + (models.Model, "models.Model", []), + ] + for case, string, imports in special_cases: + if case is value: + return string, set(imports) + if hasattr(value, "__module__"): + module = value.__module__ + if module == six.moves.builtins.__name__: + return value.__name__, set() + else: + return "%s.%s" % (module, value.__name__), {"import %s" % module} # Anything that knows how to deconstruct itself. elif hasattr(value, 'deconstruct'): return cls.serialize_deconstructed(*value.deconstruct()) @@ -362,20 +376,6 @@ class MigrationWriter(object): "For more information, see https://docs.djangoproject.com/en/1.7/topics/migrations/#serializing-values" % (value.__name__, module_name)) return "%s.%s" % (module_name, value.__name__), set(["import %s" % module_name]) - # Classes - elif isinstance(value, type): - special_cases = [ - (models.Model, "models.Model", []), - ] - for case, string, imports in special_cases: - if case is value: - return string, set(imports) - if hasattr(value, "__module__"): - module = value.__module__ - if module == six.moves.builtins.__name__: - return value.__name__, set() - else: - return "%s.%s" % (module, value.__name__), set(["import %s" % module]) # Other iterables elif isinstance(value, collections.Iterable): imports = set() diff --git a/docs/releases/1.7.2.txt b/docs/releases/1.7.2.txt index 39e89a25d18..a537207924a 100644 --- a/docs/releases/1.7.2.txt +++ b/docs/releases/1.7.2.txt @@ -101,3 +101,6 @@ Bugfixes * Fixed ``runserver`` crash when socket error message contained Unicode characters (:ticket:`23946`). + +* Fixed serialization of ``type`` when adding a ``deconstruct()`` method + (:ticket:`23950`). diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index cb9ed5d0d5e..dc551acc6b9 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -340,3 +340,14 @@ class WriterTests(TestCase): 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)") + + def test_deconstruct_class_arguments(self): + # Yes, it doesn't make sense to use a class as a default for a + # CharField. It does make sense for custom fields though, for example + # an enumfield that takes the enum class as an argument. + class DeconstructableInstances(object): + def deconstruct(self): + return ('DeconstructableInstances', [], {}) + + string = MigrationWriter.serialize(models.CharField(default=DeconstructableInstances))[0] + self.assertEqual(string, "models.CharField(default=migrations.test_writer.DeconstructableInstances)")