更好的 ArrayField 管理小部件?

Better ArrayField admin widget?

有什么方法可以让 ArrayField 的管理小部件允许添加和删除对象?似乎默认情况下,它只显示一个文本字段,并对其值使用逗号分隔。

除了不方便之外,AFAICT 在数组的基字段是 Char/TextField 的情况下,不允许以任何方式在数组的任何文本中包含逗号。

对于 OP 或任何正在寻找的人来说,在这些有用的位之间你应该好好去:

django-select2 提供了一种使用 Select2 呈现 ArrayField 的方法。在他们的文档中,该示例适用于 ArrayField:

http://django-select2.readthedocs.io/en/latest/django_select2.html#django_select2.forms.Select2TagWidget

要呈现已选择的值:

class ArrayFieldWidget(Select2TagWidget):

    def render_options(self, *args, **kwargs):
        try:
            selected_choices, = args
        except ValueError:  # Signature contained `choices` prior to Django 1.10
            choices, selected_choices = args
        output = ['<option></option>' if not self.is_required and not self.allow_multiple_selected else '']
        selected_choices = {force_text(v) for v in selected_choices.split(',')}
        choices = {(v, v) for v in selected_choices}
        for option_value, option_label in choices:
            output.append(self.render_option(selected_choices, option_value, option_label))
        return '\n'.join(output)

    def value_from_datadict(self, data, files, name):
        values = super().value_from_datadict(data, files, name)
        return ",".join(values)

要将小部件添加到您的表单:

class MyForm(ModelForm):

    class Meta:
        fields = ['my_array_field']
        widgets = {
            'my_array_field': ArrayFieldWidget
        }

我对此不以为然 (original source),但如果您使用 PostgreSQL 作为数据库并且乐于使用 Postgres 特定的 ArrayField 实现,则有一个更简单的选择:子类 ArrayField 在模型上并覆盖默认的管理小部件。基本实现如下(在 Django 1.9、1.10、1.11、2.0、2.1 和 2.2 中测试):

models.py

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


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


FUNCTION_CHOICES = (
    ('0', 'Planning'),
    ('1', 'Operation'),
    ('2', 'Reporting'),
)


class FunctionModel(models.Model):
    name = models.CharField(max_length=128, unique=True)
    function = ChoiceArrayField(
        base_field=models.CharField(max_length=256, choices=FUNCTION_CHOICES),
        default=list)

这是另一个使用 Django Admin M2M filter_horizontal 小部件的版本,而不是标准 HTML select 多个。

我们仅在管理站点中使用 Django 表单,这对我们有用,但如果在管理站点之外使用,管理小部件 FilteredSelectMultiple 可能会损坏。另一种方法是覆盖 ModelAdmin.get_form 以实例化适当的形式 class 和数组字段的小部件。 ModelAdmin.formfields_overrides 是不够的,因为您需要实例化设置位置参数的小部件,如代码片段所示。

from django.contrib.admin.widgets import FilteredSelectMultiple
from django.contrib.postgres.fields import ArrayField
from django.forms import MultipleChoiceField


class ChoiceArrayField(ArrayField):
    """
    A choices ArrayField that uses the `horizontal_filter` style of an M2M in the Admin

    Usage::

        class MyModel(models.Model):
            tags = ChoiceArrayField(
                models.TextField(choices=TAG_CHOICES),
                verbose_name="Tags",
                help_text="Some tags help",
                blank=True,
                default=list,
            )
    """

    def formfield(self, **kwargs):
        widget = FilteredSelectMultiple(self.verbose_name, False)
        defaults = {
            "form_class": MultipleChoiceField,
            "widget": widget,
            "choices": self.base_field.choices,
        }
        defaults.update(kwargs)
        # Skip our parent's formfield implementation completely as we don't
        # care for it.
        return super(ArrayField, self).formfield(**defaults)

为您的模型写一个表格 class 并为 ArrayField 使用 forms.MultipleChoiceField:

class ModelForm(forms.ModelForm):

    my_array_field = forms.MultipleChoiceField(
        choices=[1, 2, 3]
    )

    class Meta:
        exclude = ()
        model = Model

在您的管理中使用 ModelForm class:

class ModelAdmin(admin.ModelAdmin):
    form = ModelForm
    exclude = ()
    fields = (
        'my_array_field',
    )

这是一个已被接受的解决方案的改进版本。使用“CheckboxSelectMultiple”使其在管理页面中更有用。

class ChoiceArrayField(ArrayField):

    def formfield(self, **kwargs):
        defaults = {
            'form_class': forms.TypedMultipleChoiceField,
            'choices': self.base_field.choices,
            'coerce': self.base_field.to_python,
            'widget': forms.CheckboxSelectMultiple,
        }
        defaults.update(kwargs)

        return super(ArrayField, self).formfield(**defaults)

Django 更好的管理 ArrayField 包提供了这个功能。与上述解决方案相比的优势在于,它允许您动态添加新条目,而不是依赖于预定义的选择。

在此处查看文档:django-better-admin-arrayfield

它有一个直接替代 ArrayField 和一个简单的 mixin 来添加到管理模型。

# models.py
from django_better_admin_arrayfield.models.fields import ArrayField

class MyModel(models.Model):
    my_array_field = ArrayField(models.IntegerField(), null=True, blank=True)


# admin.py
from django_better_admin_arrayfield.admin.mixins import DynamicArrayMixin

@admin.register(MyModel)
class MyModelAdmin(admin.ModelAdmin, DynamicArrayMixin):
    ...

这将显示如下内容: