From ad96210192aef4b4a64de6f768cae2d86417e5e1 Mon Sep 17 00:00:00 2001 From: "utkarsh.arya@zomato.com" Date: Sat, 15 Nov 2025 22:59:12 +0000 Subject: [PATCH] Fix makemigrations path for inner/nested classes Use __qualname__ to serialize inner classes with their full path, ensuring migrations reference the correct containing class. Fixes incorrect migration code for nested fields and Enums. --- django/db/migrations/serializer.py | 4 +-- tests/migrations/test_writer.py | 39 +++++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/django/db/migrations/serializer.py b/django/db/migrations/serializer.py index 7d226955d5..de0adff859 100644 --- a/django/db/migrations/serializer.py +++ b/django/db/migrations/serializer.py @@ -143,7 +143,7 @@ class FunctionTypeSerializer(BaseSerializer): if getattr(self.value, "__self__", None) and isinstance(self.value.__self__, type): klass = self.value.__self__ module = klass.__module__ - return "%s.%s.%s" % (module, klass.__name__, self.value.__name__), {"import %s" % module} + return "%s.%s.%s" % (module, klass.__qualname__, self.value.__name__), {"import %s" % module} # Further error checking if self.value.__name__ == '': raise ValueError("Cannot serialize function: lambda") @@ -269,7 +269,7 @@ class TypeSerializer(BaseSerializer): if module == builtins.__name__: return self.value.__name__, set() else: - return "%s.%s" % (module, self.value.__name__), {"import %s" % module} + return "%s.%s" % (module, self.value.__qualname__), {"import %s" % module} class UUIDSerializer(BaseSerializer): diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index ca924f35ec..9a02421a8d 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -61,6 +61,14 @@ class IntEnum(enum.IntEnum): B = 2 +class OuterClass: + """Module-level outer class for testing nested class serialization.""" + class InnerClass(models.CharField): + def __init__(self, *args, **kwargs): + kwargs.setdefault('max_length', 20) + super().__init__(*args, **kwargs) + + class OperationWriterTests(SimpleTestCase): def test_empty_signature(self): @@ -731,7 +739,13 @@ class WriterTests(SimpleTestCase): return ('DeconstructibleInstances', [], {}) string = MigrationWriter.serialize(models.CharField(default=DeconstructibleInstances))[0] - self.assertEqual(string, "models.CharField(default=migrations.test_writer.DeconstructibleInstances)") + # Since DeconstructibleInstances is defined locally (in a function), it will have + # '' in its __qualname__, resulting in a fully qualified path + self.assertEqual( + string, + "models.CharField(default=migrations.test_writer.WriterTests." + "test_deconstruct_class_arguments..DeconstructibleInstances)" + ) def test_register_serializer(self): class ComplexSerializer(BaseSerializer): @@ -747,3 +761,26 @@ class WriterTests(SimpleTestCase): def test_register_non_serializer(self): with self.assertRaisesMessage(ValueError, "'TestModel1' must inherit from 'BaseSerializer'."): MigrationWriter.register_serializer(complex, TestModel1) + + def test_serialize_nested_class(self): + """ + Test serialization of nested/inner classes. + When a custom field is defined as an inner class, the serializer should + use the full qualified name including the outer class. + Regression test for issue where makemigrations incorrectly serialized + inner classes without their outer class qualifier. + """ + # Test serializing the type itself + string, imports = MigrationWriter.serialize(OuterClass.InnerClass) + self.assertEqual(string, "migrations.test_writer.OuterClass.InnerClass") + self.assertEqual(imports, {"import migrations.test_writer"}) + + # Test serializing an instance of the nested field + field = OuterClass.InnerClass(max_length=20) + string, imports = MigrationWriter.serialize(field) + # The field instance should reference the correct nested class path + self.assertIn("migrations.test_writer.OuterClass.InnerClass", string) + + # The serialized string should NOT be just "InnerClass" without the outer class + self.assertNotIn("models.InnerClass", string) + self.assertNotIn("test_writer.InnerClass(", string)