Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#30774 closed Bug (fixed)

Migrations uses value of enum object instead of its name.

Reported by: oasl Owned by: Hasan Ramezani
Component: Migrations Version: dev
Severity: Normal Keywords: Enum, Migrations
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by oasl)

When using Enum object as a default value for a CharField, the generated migration file uses the value of the Enum object instead of the its name. This causes a problem when using Django translation on the value of the Enum object.

The problem is that, when the Enum object value get translated to the users language, the old migration files raise an error stating that the Enum does not have the corresponding value. (because the Enum value is translated to another language)

Example:

Let say we have this code in models.py:

from enum import Enum
from django.utils.translation import gettext_lazy as _
from django.db import models


class Status(Enum):
    GOOD = _('Good') # 'Good' will be translated
    BAD = _('Bad') # 'Bad' will be translated

    def __str__(self):
        return self.name

class Item(models.Model):
    status = models.CharField(default=Status.GOOD, max_length=128)

In the generated migration file, the code will be:

...
('status', models.CharField(default=Status('Good'), max_length=128))
...

After the translation, 'Good' will be translated to another word and it will not be part of the Status Enum class any more, so the migration file will raise the error on the previous line:

ValueError: 'Good' is not a valid Status

Shouldn't the code generated by the migration uses the name of the Status Enum 'GOOD', not the value of it, since it is changeable?

It should be:

('status', models.CharField(default=Status['GOOD'], max_length=128))

This will be correct regardless of the translated word

Change History (13)

comment:1 by oasl, 5 years ago

Description: modified (diff)

comment:2 by Mariusz Felisiak, 5 years ago

Resolution: needsinfo
Status: newclosed
Summary: Migrations uses value of enum object instead of its nameMigrations uses value of enum object instead of its name.
Version: 2.2master

Thanks for this report, however I'm not sure how translated values can brake migrations. Can you provide a sample project to reproduce this issue? Migrations with translatable strings works fine for me:

>>> class TextEnum(enum.Enum):
...     C = _('translatable value')
...
>>> TextEnum(_('translatable value'))
<TextEnum.C: 'translatable value'>
>>> TextEnum('translatable value')
<TextEnum.C: 'translatable value'>

comment:3 by oasl, 5 years ago

Resolution: needsinfo
Status: closednew

To experience the bug:

In any Django project, set the default value of a CharField as an enum object:

class EnumClass(Enum):
     VALUE = _('Value')

where:
VALUE: is the constant enum object name
'Value': is the translatable enum object value

In the model:

field = models.CharField(default=EnumClass.VALUE, max_length=128)

then run: python manage.py makemigrations

In the generated migration file, you will notice that the default value of the field is set to: EnumClass('Value'), so it calls the enum object by its value not it is constant name.
run: python manage.py migrate

In the settings.py file:

LANGUAGE_CODE = 'fr-FR' # set it to any language code other than English

Run the project after generating, translating, and compiling the messages file (see: message-files)

The project will raise the error: ValueError: 'Value' is not a valid EnumClass , on the generated migration file.

Version 0, edited 5 years ago by oasl (next)

comment:4 by Mariusz Felisiak, 5 years ago

Triage Stage: UnreviewedAccepted

This use case looks quite niche for me, i.e. I would expect to store a unified values (the same for all languages) and translate only labels visible for users, however I agree that we can fix this.

comment:5 by Hasan Ramezani, 5 years ago

Owner: changed from nobody to Hasan Ramezani
Status: newassigned

comment:6 by Hasan Ramezani, 5 years ago

Here is the diff based on the @oasl solution

Shouldn't the code generated by the migration uses the name of the Status Enum 'GOOD', not the value of it, since it is changeable?
It should be:
('status', models.CharField(default=Status['GOOD'], max_length=128))
diff --git a/django/db/migrations/serializer.py b/django/db/migrations/serializer.py
index 27b5cbd379..b00c6f0df2 100644
--- a/django/db/migrations/serializer.py
+++ b/django/db/migrations/serializer.py
@@ -120,9 +120,9 @@ class EnumSerializer(BaseSerializer):
     def serialize(self):
         enum_class = self.value.__class__
         module = enum_class.__module__
-        v_string, v_imports = serializer_factory(self.value.value).serialize()
+        _, 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
+        return "%s.%s['%s']" % (module, enum_class.__name__, self.value), imports
 

@felixxm, what do you think?

comment:7 by Mariusz Felisiak, 5 years ago

You cannot use a string representation of self.value i.e. 'EnumClass.GOOD', IMO we should use a name property:

return "%s.%s[%r]" % (module, enum_class.__name__, self.value.name), imports

comment:8 by Hasan Ramezani, 5 years ago

Has patch: set
Last edited 5 years ago by Mariusz Felisiak (previous) (diff)

comment:9 by Mariusz Felisiak, 5 years ago

Needs tests: set

comment:10 by Hasan Ramezani, 5 years ago

Needs tests: unset

comment:11 by Mariusz Felisiak, 5 years ago

Triage Stage: AcceptedReady for checkin

comment:12 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

Resolution: fixed
Status: assignedclosed

In f0adf3b9:

Fixed #30774 -- Made serialization in migrations use members names for Enums.

comment:13 by Mariusz Felisiak <felisiak.mariusz@…>, 5 years ago

In df0c2ac3:

[3.0.x] Fixed #30774 -- Made serialization in migrations use members names for Enums.

Backport of f0adf3b9b7a19cdee05368ff0c0c2d087f011180 from master

Note: See TracTickets for help on using tickets.
Back to Top