{{{
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).
--
Ticket URL: <https://code.djangoproject.com/ticket/33714>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.
Comment (by Jaap Roes):
We have some workarounds in place to make it work in our projects.
Instead of `ArrayField` we 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.
--
Ticket URL: <https://code.djangoproject.com/ticket/33714#comment:1>
* status: new => closed
* resolution: => duplicate
Comment:
I think we can treat this as a duplicate of #32328.
--
Ticket URL: <https://code.djangoproject.com/ticket/33714#comment:2>
Comment (by 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.
--
Ticket URL: <https://code.djangoproject.com/ticket/33714#comment:3>
Comment (by Mariusz Felisiak):
Replying to [comment:3 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.
--
Ticket URL: <https://code.djangoproject.com/ticket/33714#comment:4>