Django MultiSelectField Error: super object has no attribute _get_flatchoices
Upgrade django-multiselectfield to version 0.1.13+ or replace it with Django's native models.JSONField(choices=...) or ArrayField.
Root Cause Analysis
This error occurs when Python tries to call _get_flatchoices() on a Django model field instance through super(), but Django 3.1+ refactored the internal choices implementation and removed the private _get_flatchoices method from models.Field in favor of the flatchoices property.
Background on Django Field Choices Refactoring
In Django 2.x and earlier, the base Field class defined a private helper method named _get_flatchoices(), which flattened nested choice tuples (groups) into a flat list of (key, value) pairs. Older versions of third-party packages like django-multiselectfield overrode choice handling by calling super()._get_flatchoices().
In Django 3.1, Django introduced the public flatchoices property and removed the deprecated private method. Running unmaintained versions of django-multiselectfield with Django 4.x or 5.x immediately crashes with AttributeError whenever a form, serializer, or Django Admin page inspects the field.
Key Scenarios
- Django Admin Interface Loading: Accessing a ModelAdmin with a MultiSelectField triggers choice rendering.
- Django Model Form Validation: Initializing
ModelForminstances containing custom choices. - Database Migrations: Running
makemigrationson models with legacy custom fields.
Reproduction Code (MCVE)
# Simulates custom field calling removed internal method _get_flatchoices on super()
class DjangoBaseField:
def __init__(self, choices=None):
self.choices = choices or []
class LegacyMultiSelectField(DjangoBaseField):
def get_flattened(self):
return super()._get_flatchoices()
field = LegacyMultiSelectField(choices=[('A', 'Option A'), ('B', 'Option B')])
field.get_flattened()
Solution 1: Migrate to Native `models.JSONField` with Choices
Use Django's built-in JSONField to store multiple choices as a native list without third-party dependencies.
from typing import Any, List, Tuple
STATUS_CHOICES: List[Tuple[str, str]] = [
('FEATURE_A', 'Feature Alpha'),
('FEATURE_B', 'Feature Beta'),
('FEATURE_C', 'Feature Gamma')
]
# Modern Django pattern: store selections as list in JSONField
def validate_selected_choices(selected: List[str], valid_choices: List[Tuple[str, str]]) -> bool:
valid_keys = {key for key, _ in valid_choices}
return all(item in valid_keys for item in selected)
user_selection = ['FEATURE_A', 'FEATURE_C']
is_valid = validate_selected_choices(user_selection, STATUS_CHOICES)
print(f'User selection {user_selection} is valid: {is_valid}')
Solution 2: Modern Flat Choices Generator Function
Implement a forward-compatible choices flattener supporting both flat and grouped choice hierarchies.
from typing import Iterable, List, Tuple, Union
ChoiceType = Union[Tuple[str, str], Tuple[str, Iterable[Tuple[str, str]]]]
def get_flattened_choices(choices: Iterable[ChoiceType]) -> List[Tuple[str, str]]:
flat = []
for choice, value in choices:
if isinstance(value, (list, tuple)):
for nested_choice, nested_value in value:
flat.append((nested_choice, nested_value))
else:
flat.append((choice, value))
return flat
sample_choices = [('Audio', [('mp3', 'MP3 Audio'), ('wav', 'WAV Audio')]), ('doc', 'Document')]
flattened = get_flattened_choices(sample_choices)
print(f'Flattened choices: {flattened}')
Common Pitfalls & Migration Strategy
When migrating an existing database table from django-multiselectfield (which stored comma-separated strings like 'A,B,C') to JSONField (which stores JSON arrays ['A', 'B', 'C']), write a Django data migration using RunPython to convert strings via value.split(',') before altering the column type.
Contrasting with similar exceptions:
AttributeError: 'super' object has no attribute '_get_flatchoices': Third-party field calling removed Django internal method.TypeError: Field.choices must be an iterable: Occurs when choices parameter is passed as a non-iterable dictionary or scalar.