Skip to content

Instantly share code, notes, and snippets.

Embed
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

This comment has been minimized.

Copy link

commented Oct 3, 2016

Excellent snippet, thanks!

@Marakai

This comment has been minimized.

Copy link

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!

@aniruddha-adhikary

This comment has been minimized.

Copy link

commented Nov 9, 2016

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

@Proper-Job

This comment has been minimized.

Copy link

commented Feb 28, 2017

@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)
@Proper-Job

This comment has been minimized.

Copy link

commented Mar 30, 2017

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

This comment has been minimized.

Copy link

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?

@Allan-Nava

This comment has been minimized.

Copy link

commented Jan 17, 2018

Good snippet!

@vyruss

This comment has been minimized.

Copy link

commented Apr 2, 2018

Thanks very much for this @danni & @Proper-Job.

@igerko

This comment has been minimized.

Copy link

commented Dec 19, 2018

If you are using base field IntegerField or other than CharField, you will need to convert values in list to python.

class ChoiceArrayField(ArrayField):
    """
    A field that allows us to store an array of choices.
    Uses Django's Postgres ArrayField
    and a MultipleChoiceField for its formfield.
    """

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

    def to_python(self, value):
        res = super().to_python(value)
        if isinstance(res, list):
            value = [self.base_field.to_python(val) for val in res]
        return value
@gghildyal

This comment has been minimized.

Copy link

commented Apr 29, 2019

Thanks for this snippet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.