Normalize choices values consistently for fields and widgets.
(value, *, depth=0)
| 70 | |
| 71 | |
| 72 | def normalize_choices(value, *, depth=0): |
| 73 | """Normalize choices values consistently for fields and widgets.""" |
| 74 | # Avoid circular import when importing django.forms. |
| 75 | from django.db.models.enums import ChoicesType |
| 76 | |
| 77 | match value: |
| 78 | case BaseChoiceIterator() | Promise() | bytes() | str(): |
| 79 | # Avoid prematurely normalizing iterators that should be lazy. |
| 80 | # Because string-like types are iterable, return early to avoid |
| 81 | # iterating over them in the guard for the Iterable case below. |
| 82 | return value |
| 83 | case ChoicesType(): |
| 84 | # Choices enumeration helpers already output in canonical form. |
| 85 | return value.choices |
| 86 | case Mapping() if depth < 2: |
| 87 | value = value.items() |
| 88 | case Iterator() if depth < 2: |
| 89 | # Although Iterator would be handled by the Iterable case below, |
| 90 | # the iterator would be consumed prematurely while checking that |
| 91 | # its elements are not string-like in the guard, so we handle it |
| 92 | # separately. |
| 93 | pass |
| 94 | case Iterable() if depth < 2 and not any( |
| 95 | isinstance(x, (Promise, bytes, str)) for x in value |
| 96 | ): |
| 97 | # String-like types are iterable, so the guard above ensures that |
| 98 | # they're handled by the default case below. |
| 99 | pass |
| 100 | case Callable() if depth == 0: |
| 101 | # If at the top level, wrap callables to be evaluated lazily. |
| 102 | return CallableChoiceIterator(value) |
| 103 | case Callable() if depth < 2: |
| 104 | value = value() |
| 105 | case _: |
| 106 | return value |
| 107 | |
| 108 | try: |
| 109 | # Recursive call to convert any nested values to a list of 2-tuples. |
| 110 | return [(k, normalize_choices(v, depth=depth + 1)) for k, v in value] |
| 111 | except (TypeError, ValueError): |
| 112 | # Return original value for the system check to raise if it has items |
| 113 | # that are not iterable or not 2-tuples: |
| 114 | # - TypeError: cannot unpack non-iterable <type> object |
| 115 | # - ValueError: <not enough / too many> values to unpack |
| 116 | return value |