Commit 6ab67919 authored by Marc Tamlyn's avatar Marc Tamlyn
Browse files

Made nested deconstruction support both forms of deconstruct()

Nested deconstruction should (silently) handle Field.deconstruct() as
well as other arbitrary deconstructable objects. This allows having a
field in the deconstruction of another field.
parent 60416260
Loading
Loading
Loading
Loading
+5 −4
Original line number Diff line number Diff line
@@ -67,20 +67,21 @@ class MigrationAutodetector(object):
            if not model._meta.proxy and model._meta.managed and al not in self.to_state.real_apps:
                new_model_keys.append((al, mn))

        def _deep_deconstruct(obj, field=True):
        def _deep_deconstruct(obj):
            """
            Recursive deconstruction for a field and its arguments.
            """
            if not hasattr(obj, 'deconstruct'):
                return obj
            deconstructed = obj.deconstruct()
            if field:
            if isinstance(obj, models.Field):
                # we have a field which also returns a name
                deconstructed = deconstructed[1:]
            name, args, kwargs = deconstructed
            return (
                name,
                [_deep_deconstruct(value, field=False) for value in args],
                dict([(key, _deep_deconstruct(value, field=False))
                [_deep_deconstruct(value) for value in args],
                dict([(key, _deep_deconstruct(value))
                      for key, value in kwargs.items()])
            )

+12 −0
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ class AutodetectorTests(TestCase):
    author_name_default = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200, default='Ada Lovelace'))])
    author_name_deconstructable_1 = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200, default=DeconstructableObject()))])
    author_name_deconstructable_2 = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200, default=DeconstructableObject()))])
    author_name_deconstructable_3 = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200, default=models.IntegerField()))])
    author_name_deconstructable_4 = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200, default=models.IntegerField()))])
    author_with_book = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("book", models.ForeignKey("otherapp.Book"))])
    author_renamed_with_book = ModelState("testapp", "Writer", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("book", models.ForeignKey("otherapp.Book"))])
    author_with_publisher_string = ModelState("testapp", "Author", [("id", models.AutoField(primary_key=True)), ("name", models.CharField(max_length=200)), ("publisher_name", models.CharField(max_length=200))])
@@ -576,6 +578,16 @@ class AutodetectorTests(TestCase):
        changes = autodetector._detect_changes()
        self.assertEqual(changes, {})

    def test_deconstruct_field_kwarg(self):
        """
        Field instances are handled correctly by nested deconstruction.
        """
        before = self.make_project_state([self.author_name_deconstructable_3])
        after = self.make_project_state([self.author_name_deconstructable_4])
        autodetector = MigrationAutodetector(before, after)
        changes = autodetector._detect_changes()
        self.assertEqual(changes, {})

    def test_replace_string_with_foreignkey(self):
        """
        Adding an FK in the same "spot" as a deleted CharField should work. (#22300).