Django Model MultipleChoice

Question:

I know there isn’t MultipleChoiceField for a Model, you can only use it on Forms.

Today I face an issue when analyzing a new project related with Multiple Choices.

I would like to have a field like a CharField with choices with the option of multiple choice.

I solved this issue other times by creating a CharField and managed the multiple choices in the form with a forms.MultipleChoiceField and store the choices separated by commas.

In this project, due to configuration, I cannot do it as I mention above, I need to do it in the Models, and I prefer NOT to edit the Django admin form neither use forms. I need a Model Field with multiple choices option

  • Have someone solved anything like this via Models ?

Maybe overriding some of the models function or using a custom widget… I don’t know, I’m kinda lost here.


Edit

I’m aware off simple choices, I would like to have something like:

class MODEL(models.Model):
    MY_CHOICES = (
        ('a', 'Hola'),
        ('b', 'Hello'),
        ('c', 'Bonjour'),
        ('d', 'Boas'),
    )
    ...
    ...
    my_field = models.CharField(max_length=1, choices=MY_CHOICES)
    ...

but with the capability of saving multiple choices not only 1 choice.

Asked By: AlvaroAV

||

Answers:

You need to think about how you are going to store the data at a database level. This will dictate your solution.

Presumably, you want a single column in a table that is storing multiple values. This will also force you to think about how you will serialize – for example, you can’t simply do comma separated if you need to store strings that might contain commas.

However, you are probably best off using a solution like django-multiselectfield

Answered By: spookylukey

From the two, https://pypi.python.org/pypi/django-select-multiple-field/ looks more well rounded and complete. It even has a nice set of unittests.

The problem I found is that it throws a Django 1.10 deprecation warning in the class that implements the model field.

I fixed this and sent a PR. The latest code, until they merge my PR (if they ever decide to hehe) is in my fork of the repo, here: https://github.com/matiasherranz/django-select-multiple-field

Cheers!

M.-

Answered By: Matias Herranz

In case You are using Postgres consider using ArrayField.

from django.db import models
from django.contrib.postgres.fields import ArrayField

class WhateverModel(models.Model):
    WHATEVER_CHOICE = u'1'
    SAMPLE_CHOICES = (
        (WHATEVER_CHOICE, u'one'),
    )
    choices = ArrayField(
        models.CharField(choices=SAMPLE_CHOICES, max_length=2, blank=True, default=WHATEVER_CHOICE),
    )
Answered By: lechup

If you want the widget to look like a text input and still be able to allow selecting several options from suggestions, you might be looking for Select2. There is also django-select2 that integrates it with Django Forms and Admin.

Answered By: Risadinha

The easiest way I found (just I use eval() to convert string gotten from input to tuple to read again for form instance or other place)

This trick works very well

#model.py
class ClassName(models.Model):
    field_name = models.CharField(max_length=100)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if self.field_name:
            self.field_name= eval(self.field_name)



#form.py
CHOICES = [('pi', 'PI'), ('ci', 'CI')]

class ClassNameForm(forms.ModelForm):
    field_name = forms.MultipleChoiceField(choices=CHOICES)

    class Meta:
        model = ClassName
        fields = ['field_name',]

#view.py
def viewfunction(request, pk):
    ins = ClassName.objects.get(pk=pk)

    form = ClassNameForm(instance=ins)
    if request.method == 'POST':
        form = form (request.POST, instance=ins)
        if form.is_valid():
            form.save()
            ...
Answered By: younesfmgtc

In Your Case, I used ManyToManyField

It Will be something like that:

class MY_CHOICES(models.Model)
    choice = models.CharField(max_length=154, unique=True)

class MODEL(models.Model):
    ...
    ...
    my_field = models.ManyToManyField(MY_CHOICES)

So, now you can select multiple choices

Answered By: Desert Camel

You can use an IntegerField for the model and powers of two for the choices (a bitmap field). I’m not sure why Django doesn’t have this already built-in.

class MyModel(models.Model):
    A = 1
    B = 2
    C = 4
    MY_CHOICES = ((A, "foo"), (B, "bar"), (C, "baz"))
    my_field = models.IntegerField(default=0)


from functools import reduce


class MyForm(forms.ModelForm):
    class Meta:
        model = MyModel
    
    # it can be set to required=True if needed
    my_multi_field = forms.TypedMultipleChoiceField(
        coerce=int, choices=MyModel.MY_CHOICES, required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['my_multi_field'].initial = [
            c for c, _ in MyModel.MY_CHOICES
            if self.instance.my_field & c
        ]

    def save(self, *args, **kwargs):
        self.instance.my_field = reduce(
            lambda x, y: x | y,
            self.cleaned_data.get('my_multi_field', []),
            0)
        return super().save(*args, **kwargs)

It can be queried like this: MyModel.objects.filter(my_field=MyModel.A | MyModel.C) to get all records with A and C set.

Answered By: nitely

Postgres only.

Quite late but for those who come across this
based on @lechup answer i came across this gist.
Take a look at that gist there more improved versions there

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)

Which then i saw it in another production code in one of my other projects.. it worked so well that i thought it was from Django’s default fields. I was googling just to find the Django docs that i came here. 🙂

Answered By: Amir Heshmati
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.