Django 多选模型

我知道对于 模特来说没有 MultipleChoiceField,你只能在表单上使用它。

今天,我在分析一个与多项选择相关的新项目时遇到了一个问题。

我想有一个领域喜欢 CharFieldchoices与多项选择的选项。

其他时候,我通过创建一个 CharField来解决这个问题,并用 forms.MultipleChoiceField管理表单中的多个选项,并存储用逗号分隔的选项。

在这个项目中,由于配置,我不能做到这一点,因为我上面提到,我需要在模型,我喜欢 没有编辑 Django 管理表 都不是使用形式。我需要一个多选择的模型字段选项

  • 有人通过模型解决过类似的问题吗?

也许重写一些模型函数或者使用自定义小部件... 我不知道,我有点迷路了。


剪辑

我知道 很简单的选择,我想有这样的东西:

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

而且具有节省 多重选择的能力,不仅仅是一种选择。

134430 次浏览

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

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.-

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),
)

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.

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()
...

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

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.

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. :)