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.
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 22:59:12 +00:00
parent 89d41cba39
commit ad96210192
2 changed files with 40 additions and 3 deletions

View file

@ -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__ == '<lambda>':
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):

View file

@ -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
# '<locals>' in its __qualname__, resulting in a fully qualified path
self.assertEqual(
string,
"models.CharField(default=migrations.test_writer.WriterTests."
"test_deconstruct_class_arguments.<locals>.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)