Fix migration enum serialization to use enum name, not value

Ensure migrations use enum names for CharFields to avoid errors
with translatable values. This prevents migration failures when
enum values are translated, addressing issues with Django's translation
framework and Enum usage.
This commit is contained in:
utkarsh.arya@zomato.com 2025-11-15 23:01:35 +00:00
parent e02f67ef2d
commit 745e0d9437
2 changed files with 42 additions and 11 deletions

View file

@ -120,9 +120,16 @@ class EnumSerializer(BaseSerializer):
def serialize(self): def serialize(self):
enum_class = self.value.__class__ enum_class = self.value.__class__
module = enum_class.__module__ module = enum_class.__module__
v_string, v_imports = serializer_factory(self.value.value).serialize() # For IntEnum and IntFlag, the value is stable and should be used directly
imports = {'import %s' % module, *v_imports} # For other Enum types, use name-based access to handle mutable values
return "%s.%s(%s)" % (module, enum_class.__name__, v_string), imports # (like translatable strings) that may change between runs
if isinstance(self.value, (enum.IntEnum, enum.IntFlag)):
v_string, v_imports = serializer_factory(self.value.value).serialize()
imports = {'import %s' % module, *v_imports}
return "%s.%s(%s)" % (module, enum_class.__name__, v_string), imports
else:
imports = {'import %s' % module}
return "%s.%s[%r]" % (module, enum_class.__name__, self.value.name), imports
class FloatSerializer(BaseSimpleSerializer): class FloatSerializer(BaseSimpleSerializer):

View file

@ -267,11 +267,11 @@ class WriterTests(SimpleTestCase):
self.assertSerializedResultEqual( self.assertSerializedResultEqual(
TextEnum.A, TextEnum.A,
("migrations.test_writer.TextEnum('a-value')", {'import migrations.test_writer'}) ("migrations.test_writer.TextEnum['A']", {'import migrations.test_writer'})
) )
self.assertSerializedResultEqual( self.assertSerializedResultEqual(
BinaryEnum.A, BinaryEnum.A,
("migrations.test_writer.BinaryEnum(b'a-value')", {'import migrations.test_writer'}) ("migrations.test_writer.BinaryEnum['A']", {'import migrations.test_writer'})
) )
self.assertSerializedResultEqual( self.assertSerializedResultEqual(
IntEnum.B, IntEnum.B,
@ -283,18 +283,18 @@ class WriterTests(SimpleTestCase):
self.assertEqual( self.assertEqual(
string, string,
"models.CharField(choices=[" "models.CharField(choices=["
"('a-value', migrations.test_writer.TextEnum('a-value')), " "('a-value', migrations.test_writer.TextEnum['A']), "
"('value-b', migrations.test_writer.TextEnum('value-b'))], " "('value-b', migrations.test_writer.TextEnum['B'])], "
"default=migrations.test_writer.TextEnum('value-b'))" "default=migrations.test_writer.TextEnum['B'])"
) )
field = models.CharField(default=BinaryEnum.B, choices=[(m.value, m) for m in BinaryEnum]) field = models.CharField(default=BinaryEnum.B, choices=[(m.value, m) for m in BinaryEnum])
string = MigrationWriter.serialize(field)[0] string = MigrationWriter.serialize(field)[0]
self.assertEqual( self.assertEqual(
string, string,
"models.CharField(choices=[" "models.CharField(choices=["
"(b'a-value', migrations.test_writer.BinaryEnum(b'a-value')), " "(b'a-value', migrations.test_writer.BinaryEnum['A']), "
"(b'value-b', migrations.test_writer.BinaryEnum(b'value-b'))], " "(b'value-b', migrations.test_writer.BinaryEnum['B'])], "
"default=migrations.test_writer.BinaryEnum(b'value-b'))" "default=migrations.test_writer.BinaryEnum['B'])"
) )
field = models.IntegerField(default=IntEnum.A, choices=[(m.value, m) for m in IntEnum]) field = models.IntegerField(default=IntEnum.A, choices=[(m.value, m) for m in IntEnum])
string = MigrationWriter.serialize(field)[0] string = MigrationWriter.serialize(field)[0]
@ -306,6 +306,30 @@ class WriterTests(SimpleTestCase):
"default=migrations.test_writer.IntEnum(1))" "default=migrations.test_writer.IntEnum(1))"
) )
def test_serialize_enums_with_translated_values(self):
"""
Test that enum serialization uses name-based access instead of
value-based constructor to handle translatable enum values.
"""
class TranslatedEnum(enum.Enum):
GOOD = _('Good')
BAD = _('Bad')
# Enum should be serialized using name-based access
self.assertSerializedResultEqual(
TranslatedEnum.GOOD,
("migrations.test_writer.TranslatedEnum['GOOD']", {'import migrations.test_writer'})
)
self.assertSerializedResultEqual(
TranslatedEnum.BAD,
("migrations.test_writer.TranslatedEnum['BAD']", {'import migrations.test_writer'})
)
# Test with a field
field = models.CharField(default=TranslatedEnum.GOOD, max_length=128)
string = MigrationWriter.serialize(field)[0]
self.assertIn("default=migrations.test_writer.TranslatedEnum['GOOD']", string)
def test_serialize_choices(self): def test_serialize_choices(self):
class TextChoices(models.TextChoices): class TextChoices(models.TextChoices):
A = 'A', 'A value' A = 'A', 'A value'