Moving to Django 3.0's Field.choices Enumeration Types

New blog post:


Thanks, I had been wondering if moving to enumeration types was worth the hassle, but after reading your post I think I’ll give it a go.

1 Like

Hey @adamchainz. I really liked this post.

One follow-up thought, I’ve been considering but not down to, is how you might get around the no named groups limitation? From the docs:

Overriding ChoiceWidget.optgroups() would let you do it for a form, but I don’t know how elegant it would end up being. (It seems the subclass would have to know the choices in play, which is OK, but…)

I don’t know if you’ve thought about it at all? (Against APIs and such, groups fade away, but I often like them for forms.)

1 Like

I didn’t think about it, no.

It might be possible to add a special grouping attribute to choices classes?

class Status(models.TextChoices):
    UNPUBLISHED = 'UN', 'Unpublished'
    PUBLISHED = 'PB', 'Published'
    grouping = [
        ['Visible', [PUBLISHED]],
        ['Hidden', [UNPUBLISHED]],

But that’s just me bikeshedding

1 Like

There’s an older implementation that includes grouping, (it was written for Python 2, so not enum-based, but it was actually the inspiration for the way Choices were done). We could adopt more ideas from there.

@orf any feels on this?

That library is a bit of a blast from the past! IMO enum ‘groups’ and ‘named groups’ are related but distinct.

‘groups’ are an important feature - basically named fields that are not included as part of the choice display. This has been pretty common in all projects I’ve worked on, and it’s just a way to group common sets of attributes together that can be checked with a simple contains. i.e field.value in MoonLandings.FAILED_MOON_LANDINGS. FAILED_MOON_LANDINGS would be a list of MoonLandings values, and it feels natural for it to be included on the enum itself. Without it you end up with a bunch of lists scattered around and not kept in one place.

Named groups are similar but are used to influence the display of the widget itself.

I think we could add a Meta to the Choices object to accomodate these?

class MyChoices(models.TextChoices):
    WHATEVER = "1"
    SOMETHING = "2"

    class Meta:
        GROUPING = [
             ['Something': [WHATEVER, SOMETHING]]

Unfortunately the Meta class can’t refer to the names in the outer class being defined:

In [11]: class MyChoices(Enum):
    ...:     WHATEVER = "1"
    ...:     SOMETHING = "2"
    ...:     class Meta:
    ...:         GROUPING = [
    ...:              ['Something', [WHATEVER, SOMETHING]]
    ...:         ]
NameError                                 Traceback (most recent call last)
<ipython-input-11-3fe4f79f24e8> in <module>
<ipython-input-11-3fe4f79f24e8> in Meta()
      5     class Meta:
      7         GROUPING = [
      8              ['Something', [WHATEVER, SOMETHING]]

NameError: name 'WHATEVER' is not defined

We could use strings to refer to them (SOME_GROUP = {'WHATEVER', 'SOMETHING'}) or special names in the main class. Given that enums already use some special names, and it’s cleaner syntax, I am in favour of the latter.

Damn, that’s quite annoying. I’m not a fan of using strings here as you lose some nice properties (auto-completion, refactoring etc).

What about this: We special case enum values that are either sets, lists or dictionaries?

class MoonMissions(Enum):
    APOLLO_10 = 1
    APOLLO_11 = 2
    EXPLORER_33 = 3

        "Failed": FAILED_MISSIONS,
        "Successful": SUCCESSFUL_MISSIONS
         ["USA", [APOLLO_10, APOLLO_11, EXPLORER_33]]

Side note: Now that dictionaries are insertion ordered in Python 3.7+ (and on cPython 3.6+), the general grouping syntax could now use dictionaries instead of the list-of-lists we’ve had before (which I assume is due to unordered dictionaries?).

The above syntax would keep the nice ability to just do field.value in MoonMissions.SUCCESSFUL_MISSIONS without too much metaclass magic. The obvious downside would be that defining an enum with list/dictionary values would not be possible, but is that a common use case at all?

Seems like a nice syntax. Want to make a ticket?

We can work for list/dict/set Enums by inspecting if the current enum inherits from one and disabling the functionality.

Sure thing, I’ll create one today :+1:

I’ve created two tickets here:

1 Like