#33714 closed New feature (duplicate)
Better admin support for ArrayFields where the base_field has choices
| Reported by: | Jaap Roes | Owned by: | nobody |
|---|---|---|---|
| Component: | contrib.admin | Version: | dev |
| Severity: | Normal | Keywords: | |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
Currently the admin doesn't really work well when registering a model that has a ArrayField of which the base_field has choices e.g.:
class Post(model.Model):
category = ArrayField(
models.CharField(choices=[
('FASHION', 'Fashion'),
('STYLE', 'Style'),
('SPORTS', 'Sports'),
('FUN', 'Fun'),
], max_length=8),
size=2,
blank=True,
default=list
)
For example, the edit form uses a simple text input to edit the choices instead of a MultipleChoiceField. Adding the field to list_display will show the raw values, not the display names. Adding the field to list_filter will only show filter options of combinations used in the database (stringified arrays).
Change History (4)
comment:1 by , 3 years ago
comment:2 by , 3 years ago
| Resolution: | → duplicate |
|---|---|
| Status: | new → closed |
I think we can treat this as a duplicate of #32328.
follow-up: 4 comment:3 by , 3 years ago
I don't agree that this is a duplicate. This is about a much narrower case where an ArrayField is used to wrap another field with choices. Basically using the ArrayField as a M2M where the "other" side is a fixed set of options, instead of a database table. The most similar ticket I can find is #24858, which has been resolved but for a much more esoteric case (imho) where the ArrayField itself has choices.
comment:4 by , 3 years ago
Replying to Jaap Roes:
I don't agree that this is a duplicate. This is about a much narrower case where an ArrayField is used to wrap another field with choices. Basically using the ArrayField as a M2M where the "other" side is a fixed set of options, instead of a database table. The most similar ticket I can find is #24858, which has been resolved but for a much more esoteric case (imho) where the ArrayField itself has choices.
My understanding is that we could have a better widget. I don't think it's worth improving only this rather niche case.
We have some workarounds in place to make it work in our projects.
Instead of
ArrayFieldwe use:from django import forms from django.contrib.postgres.fields import ArrayField class ChoiceArrayField(ArrayField): def formfield(self, **kwargs): defaults = { "form_class": forms.MultipleChoiceField, "choices": self.base_field.choices, **kwargs, } # Bypass the ArrayField's formfield, because we don't want it to pass the unexpected # base_field to our selected form_class. return super(ArrayField, self).formfield(**defaults)For list filters we use:
class ChoiceArrayFieldListFilter(admin.SimpleListFilter): field_name = NotImplemented # Set by subclasses (ChoiceArrayFieldListFilter.for_field) def __init__(self, request, params, model, model_admin): field = model._meta.get_field(self.field_name) self.parameter_name = field.name self.title = field.verbose_name self._choices = field.base_field.choices super().__init__(request, params, model, model_admin) def lookups(self, request, model_admin): return self._choices def queryset(self, request, queryset): if value := self.value(): queryset = queryset.filter(**{ f'{self.field_name}__contains': [value] }) return queryset @classmethod def for_field(cls, field_name): return type(f'{field_name.title()}ListFilter', (ChoiceArrayFieldListFilter,), { 'field_name': field_name })and for list display purposes we define methods on the admin class that call this little helper function:
def _get_array_field_display(obj, field_name): field = obj._meta.get_field(field_name) choice_lookup = dict(field.base_field.flatchoices) return "; ".join(str(choice_lookup.get(value)) for value in getattr(obj, field_name))It would be nice if we can get rid of these workarounds and have this working in Django Admin without any extra code.