【发布时间】:2015-10-04 05:37:39
【问题描述】:
有没有办法让 ArrayField 的管理小部件允许添加和删除对象?似乎默认情况下,它只显示一个文本字段,并使用逗号分隔其值。
除了不方便之外,如果数组的基本字段是 Char/TextField,则 AFAICT 不允许在数组中的任何文本中包含逗号。
【问题讨论】:
标签: django django-admin
有没有办法让 ArrayField 的管理小部件允许添加和删除对象?似乎默认情况下,它只显示一个文本字段,并使用逗号分隔其值。
除了不方便之外,如果数组的基本字段是 Char/TextField,则 AFAICT 不允许在数组中的任何文本中包含逗号。
【问题讨论】:
标签: django django-admin
我对此不以为然 (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 更好的管理 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):
...
这将显示如下内容:
【讨论】:
django-select2 提供了一种使用Select2 呈现ArrayField 的方法。在他们的文档中,该示例适用于ArrayField:
渲染已经选择的值:
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
}
【讨论】:
这是已被接受的解决方案的更好版本。使用“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 Admin M2M filter_horizontal 小部件的版本,而不是标准的 HTML 多选。
我们仅在 Admin 站点中使用 Django 表单,这对我们有用,但如果在 Admin 之外使用管理小部件 FilteredSelectMultiple 可能会损坏。另一种方法是覆盖ModelAdmin.get_form 来为数组字段实例化正确的表单类和小部件。 ModelAdmin.formfields_overrides 是不够的,因为您需要实例化设置位置参数的小部件,如代码 sn-p 所示。
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)
【讨论】:
super(ArrayField, self) 简化为 super()
super() 等价于super(ChoiceArrayField, self),但使用super(ArrayField, self) 我们有意跳过继承链中的ArrayField.formfield 实现。这段代码是从之前对该问题的回复中复制而来的。
为您的模型编写一个表单类并将 forms.MultipleChoiceField 用于 ArrayField:
class ModelForm(forms.ModelForm):
my_array_field = forms.MultipleChoiceField(
choices=[1, 2, 3]
)
class Meta:
exclude = ()
model = Model
在您的管理类中使用 ModelForm:
class ModelAdmin(admin.ModelAdmin):
form = ModelForm
exclude = ()
fields = (
'my_array_field',
)
【讨论】: