#27910 closed New feature (fixed)
Allow using an Enum class in model Field choices
Reported by: | Marcel Hellwig | Owned by: | Nick Pope |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | dev |
Severity: | Normal | Keywords: | enum model choices |
Cc: | Tom Forbes, Ryan Hiebert | Triage Stage: | Accepted |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
I will simply stick to your example here: https://docs.djangoproject.com/en/dev/ref/models/fields/#choices
I want to limit the input to certain choices so I create a list/tuple of tuples/lists. Since Python 3.4 there is a class called Enum and some nice decorators like @unique.
Currently, if you want to use this you have to do 1/2 dirty hacks, descriped in this article, which I would prefer over the current solution.
It would be nice to have native support for the Enum class, e.g. you can directly pass the class to choices (instead of using the class method choices() ), also when refering to an element in the enum just using Student.Freshmann
instead of Student.Freshmann.value
A simple example would be this:
from enum import Enum class Student(models.Model): class YearInSchoolChoices(Enum): Freshman = 'FR' Sophomore = 'SO' Junior = 'JR' Senior = 'SR' year_in_school = models.CharField( max_length=2, choices=YearInSchoolChoices, default=YearInSchoolChoices.Freshman, ) def is_upperclass(self): return self.year_in_school in (self.YearInSchoolChoices.Junior, self.YearInSchoolChoices.Senior)
Also this could be adopted (if accepted) to any type of choices in django, e.g. Choicefield etc.
Change History (25)
comment:2 by , 8 years ago
To be honest, I haven't though about this, because I don't translate them. Hmm.. Good question. Implicit translation?! :/ Decorator? That are not so good ideas.
Or you could swap the positions, e.g. FR = _("Freshman")
, because you'll never translate the key which is stored in the DB, right?!
comment:3 by , 8 years ago
Or you could swap the positions.
That would work for text based fields (CharField
, TextField
) but what about choices defined for a integer based field?
I feel like choices are a different concept than enums and that while the later is useful for referring to choices values as constants, choices and label mapping should be defined independently.
class StudentType(enum.Enum): Freshman = 'FR' Sophomore = 'SO' Junior = 'JR' Senior = 'SR' class Student(models.Model): type = models.CharField(choices=[ (StudentType.Freshman, _('Freshman')), (StudentType.Sophomore, _('Sophomore')), (StudentType.Junior, _('Junior')), (StudentType.Senior, _('Senior')), ])
comment:4 by , 8 years ago
Hmm.. your solution won't bring any advantages over the current one, so.. I don't like that concept. I think the first one was okay, but we will have Problems when you want to use translation or any non-variable-names. So.. should be discard this idea (although I really do like it!)
comment:5 by , 8 years ago
Resolution: | → wontfix |
---|---|
Status: | new → closed |
Summary: | Use Enum class in model choices → Allow using an Enum class in model Field choices |
I agree with Simon's analysis. An automatic translation based on the enum value doesn't seem like a proper separate of concerns.
comment:6 by , 7 years ago
I know this is a closed ticket, but a pattern I've has success with is a custom enum
subclass that combines the values with the display:
class StudentType(EnumChoices): Freshman = 'FR', _('Freshman') Sophomore = 'SO', _('Sophomore') Junior = 'JR', _('Junior') Senior = 'SR', _('Senior')
It's somewhat simple to do, the EnumChoices class splits the initial enum values into value, display, and exposes the display text as an attribute: StudentType.Freshman.display
(and in the str method). list(StudentType) returns a list of tuples that is suitable for choices
.
Could this not be an OK compromise? In the applications I've seen with large numbers of choices managing the display values gets to be a burden, the values should indeed be separate from the display and not auto-generated but there is a tangible benefit in keeping their definitions as close as possible together. Splitting the definition of an enum out into two parts in the django-way seems to violate DRY, no?
comment:7 by , 7 years ago
Cc: | added |
---|
comment:8 by , 7 years ago
I like the solution Tim brought up. Maybe making the second item on the enum optional, so that you could still write
Freshman = 'FR',
and for default just use an untranslated name of the field (e.g. Freshman).
How do you like it?
comment:9 by , 6 years ago
Cc: | added |
---|
comment:10 by , 6 years ago
Resolution: | wontfix |
---|---|
Status: | closed → new |
Reopening, following the discussion on the developers list and oral discussions with technical-team members on the DC-EU sprints.
comment:11 by , 6 years ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
Version: | 1.10 → master |
comment:12 by , 6 years ago
Triage Stage: | Unreviewed → Accepted |
---|
Given the extended discussion on django-developers to re-open this ticket I'll move it to "accepted": (group) review will decide finally if we're going to merge. (But if I read the thread right, the consensus seemed to be in favour...)
comment:13 by , 6 years ago
Has patch: | set |
---|
comment:14 by , 5 years ago
This ticket really ought to consider the use of database level enums for the field. (e.g. https://www.postgresql.org/docs/9.1/datatype-enum.html ). They provide a lot of efficiency (storage and searching), while allowing the DB to contain human readable values (from the point of view of anyone querying it), and also having tight data integrity - you get the best of everything. MySQL also has support, but it looks a bit different.
django_enum implements this, but it requires a significant number of patches to Django machinery, which django_enum does by monkey patching. In particular, you need to track changes to the enum in order to generate 'CREATE TYPE' and 'ALTER TYPE' (for Postgres at least). It would be great to have a solution in Django core so that we could avoid that kind of monkey patching.
We might want to consider this as a separate ticket, but it would be a shame to solve the problem half-way. At the very least we should ensure that the solution we adopt would be future compatible with using DB-level enums.
There may well be some good ideas from Mike Bayer's post about enums that we can make use of. His DeclEnum
base class is very similar in interface to EnumChoices
, which is a good sign at least!
comment:15 by , 5 years ago
This ticket really ought to consider the use of database level enums for the field. (e.g. https://www.postgresql.org/docs/9.1/datatype-enum.html ). They provide a lot of efficiency (storage and searching), while allowing the DB to contain human readable values (from the point of view of anyone querying it), and also having tight data integrity - you get the best of everything. MySQL also has support, but it looks a bit different.
Just wanted to drop a note to mention that PostgreSQL's enum type doesn't support value removal (as of 11) and that MySQL stores enum as tinyint index of the values so if you perform an ordering change you pretty much corrupt your column. The index based value on MySQL poses notable problems when joining similar values across tables or ordering by such a column. I believe these two reasons make uses database level enums in a database agnostic way really challenging.
comment:16 by , 5 years ago
Needs documentation: | set |
---|
comment:18 by , 5 years ago
Needs documentation: | unset |
---|
Do you have a suggestion about how translated choices labels should be defined?