Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Multi Choice Django Array Field
from django import forms
from django.contrib.postgres.fields import ArrayField
class ChoiceArrayField(ArrayField):
"""
A field that allows us to store an array of choices.
Uses Django 1.9's postgres ArrayField
and a MultipleChoiceField for its formfield.
Usage:
choices = ChoiceArrayField(models.CharField(max_length=...,
choices=(...,)),
default=[...])
"""
def formfield(self, **kwargs):
defaults = {
'form_class': forms.MultipleChoiceField,
'choices': self.base_field.choices,
}
defaults.update(kwargs)
# Skip our parent's formfield implementation completely as we don't
# care for it.
# pylint:disable=bad-super-call
return super(ArrayField, self).formfield(**defaults)

ropable commented Oct 3, 2016

Excellent snippet, thanks!

Marakai commented Nov 7, 2016

Bless you! after slamming my head against a wall for nearly an entire day, your snippet gave me a breakthrough! Now, if you could add an example of using it with a field widget you found useful, this here backend guy thrown into doing frontend code would be eternally grateful!

Thanks a lot @danni! You're the hero.

@danni That's kind of problematic with the base_field being anything but a CharField. Wouldn't it be better to use a TypedMultipleChoiceField thus?

def formfield(self, **kwargs):
        defaults = {
            'form_class': forms.TypedMultipleChoiceField,
            'choices': self.base_field.choices,
            'coerce': self.base_field.to_python
        }
        defaults.update(kwargs)
        # Skip our parent's formfield implementation completely as we don't
        # care for it.
        # pylint:disable=bad-super-call
        return super(ArrayField, self).formfield(**defaults)

Actually starting on Django > 1.10.2 the custom field also needs a custom widget like below. Otherwise an empty selection won't be written back to the model.

from django import forms
from django.contrib.postgres.fields import ArrayField
from django.forms import SelectMultiple


class ArraySelectMultiple(SelectMultiple):

    def value_omitted_from_data(self, data, files, name):
        return False


class ChoiceArrayField(ArrayField):

    def formfield(self, **kwargs):
        defaults = {
            'form_class': forms.TypedMultipleChoiceField,
            'choices': self.base_field.choices,
            'coerce': self.base_field.to_python,
            'widget': ArraySelectMultiple
        }
        defaults.update(kwargs)
        # Skip our parent's formfield implementation completely as we don't care for it.
        # pylint:disable=bad-super-call
        return super(ArrayField, self).formfield(**defaults)

kholidfu commented Apr 13, 2017

Hi @danni, @Proper-Job

Using Django 1.11, this is what I have:

from django.contrib.postgres.fields import ArrayField
from django.forms import SelectMultiple


class FacilitiesArrayField(ArrayField):

    def formfield(self, **kwargs):
        defaults = {
            'form_class': forms.MultipleChoiceField,
            'choices': self.base_field.choices,
            'widget': forms.CheckboxSelectMultiple,
        }
        defaults.update(kwargs)
        return super(ArrayField, self).formfield(**defaults)

I've succeeded entering the data (checked via ./manage.py shell), but when take a look back my django-admin, none checked..

Any ideas?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment