How to map Django TextChoices text to a choice?

Question:

Suppose I have this code, inspired from the Django docs about enumeration types:

class YearInSchool(models.TextChoices):
        FRESHMAN = 'FR', 'Freshman'
        SOPHOMORE = 'SO', 'Sophomore'
        JUNIOR = 'JR', 'Junior'
        SENIOR = 'SR', 'Senior'
        GRADUATE = 'GR', 'Graduate'

Now suppose I have the string "Sophomore". How do I get from that to YearInSchool.SOPHOMORE?

The only thing I can think of is a loop:

the_str = "Sophomore"
val = None
for val1, label in YearInSchool.choices:
    if label == the_str:
        val = YearInSchool(val1)
        break

assert YearInSchool.SOPHOMORE == val

That seems awkward. Is there a better way?

EDIT: Thanks for the answers folks! I’ll try them out. Just to provide more context, I am loading data from text files into a database, so the "Sophomore" is in a text file I’ve been provided that wasn’t created by me. So, I’m stretching the use case for TextChoices, but it seemed a reasonable way to tie text file input to a DB field.

Asked By: dfrankow

||

Answers:

You can use getattr:

the_str = 'Sophomore'
try:
    val = getattr(YearInSchool, the_str.upper())
except AttributeError:
    raise AttributeError(f'{the_str.upper()} not found in {YearInSchool.names}')
assert val == YearInSchool.SOPHOMORE
Answered By: TrueGopnik

I’m affraid Django doesn’t implement a similar method to get_FOO_display (described here in the doc) to achieve what you want, but I think models.TextChoices is not intended to be used this way.

For instance, a POST request should directly contain YearInSchool.FRESHMAN.value instead of YearInSchool.FRESHMAN.label (at least, this is the default behavior in django.forms), so that should not be your concern.

This being said, here is a one-liner solution which would raise a ValueError if the_str is not found:

val = YearInSchool.values[YearInSchool.labels.index(the_str)]

That is an integer value, so you have to wrap it to get the class:

choice_val = YearInSchool(YearInSchool.values[
    YearInSchool.labels.index(the_str)])

And another similar solution, twice as fast:

try:
    val = next(filter(lambda x: x[1] == the_str, YearInSchool.choices))[0]
except StopIteration as err:
    raise ValueError(f"{the_str} not found in {YearInSchool.labels}") from err
Answered By: scūriolus
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.