Django Admin 中的动态字段
Posted
技术标签:
【中文标题】Django Admin 中的动态字段【英文标题】:Dynamic fields in Django Admin 【发布时间】:2011-12-21 20:49:52 【问题描述】:我想要关于一个字段的值的附加字段。因此,我构建了一个自定义管理表单来添加一些新字段。
与 jacobian 1 的博文相关,这是我想出的:
class ProductAdminForm(forms.ModelForm):
class Meta:
model = Product
def __init__(self, *args, **kwargs):
super(ProductAdminForm, self).__init__(*args, **kwargs)
self.fields['foo'] = forms.IntegerField(label="foo")
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
但附加字段“foo”没有显示在管理员中。如果我像这样添加字段,一切正常,但不像所需的那样动态,添加有关模型另一个字段的值的字段
class ProductAdminForm(forms.ModelForm):
foo = forms.IntegerField(label="foo")
class Meta:
model = Product
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
那么有没有什么我必须再次触发才能使新字段工作的初始化方法?还是有其他尝试?
【问题讨论】:
【参考方案1】:这是解决问题的方法。感谢 koniiiik 我试图通过扩展 *get_fieldsets* 方法来解决这个问题
class ProductAdmin(admin.ModelAdmin):
def get_fieldsets(self, request, obj=None):
fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
fieldsets[0][1]['fields'] += ['foo']
return fieldsets
如果您使用多个字段集,请务必使用适当的索引将其添加到正确的字段集。
【讨论】:
Unknown field(s) (foo) specified for GlobalLabel. Check fields/fieldsets/exclude attributes of class GlobalLabelAdmin.
我收到了这个错误,我不知道为什么...你能帮帮我吗?
@bhushya:你能解决这个问题吗?我也无法让它在 django 1.9.3 中工作,例如:django.core.exceptions.FieldError: Unknown field(s) (dynamicfield1, dynamicfield2) specified for MyModel
@tehfink 看来你没有在你的模型中定义字段..你能在 pastebin.com 上发布你的模型结构并分享链接吗?
@bhushya:你是对的;我的模型上没有定义字段(dynamicfield1 等)。就像在原始问题中一样,我想在ModelForm
中动态添加字段,而上面提到的get_fieldsets
覆盖似乎在Django 1.9.3 中不起作用
@bhushya:我找到了 Django 1.9.3 的潜在解决方案,发布在下面【参考方案2】:
上面接受的答案在旧版本的 django 中有效,我就是这样做的。这现在已经在以后的 django 版本中被打破了(我现在是 1.68,但现在即使是旧的)。
它现在被破坏的原因是因为您从ModelAdmin.get_fieldsets()
返回的字段集中的任何字段最终都作为fields=parameter
传递给modelform_factory()
,这会给您一个错误,因为您列表中的字段不存在(并且在您的表单被实例化并调用其__init__
之前不会存在)。
为了解决这个问题,我们必须覆盖 ModelAdmin.get_form()
并提供一个字段列表,其中不包含稍后将添加的任何额外字段。 get_form
的默认行为是调用 get_fieldsets()
获取此信息,我们必须防止这种情况发生:
# CHOOSE ONE
# newer versions of django use this
from django.contrib.admin.utils import flatten_fieldsets
# if above does not work, use this
from django.contrib.admin.util import flatten_fieldsets
class MyModelForm(ModelForm):
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
# add your dynamic fields here..
for fieldname in ('foo', 'bar', 'baz',):
self.fields[fieldname] = form.CharField()
class MyAdmin(ModelAdmin):
form = MyModelForm
fieldsets = [
# here you put the list of fieldsets you want displayed.. only
# including the ones that are not dynamic
]
def get_form(self, request, obj=None, **kwargs):
# By passing 'fields', we prevent ModelAdmin.get_form from
# looking up the fields itself by calling self.get_fieldsets()
# If you do not do this you will get an error from
# modelform_factory complaining about non-existent fields.
# use this line only for django before 1.9 (but after 1.5??)
kwargs['fields'] = flatten_fieldsets(self.declared_fieldsets)
# use this line only for django 1.9 and later
kwargs['fields'] = flatten_fieldsets(self.fieldsets)
return super(MyAdmin, self).get_form(request, obj, **kwargs)
def get_fieldsets(self, request, obj=None):
fieldsets = super(MyAdmin, self).get_fieldsets(request, obj)
newfieldsets = list(fieldsets)
fields = ['foo', 'bar', 'baz']
newfieldsets.append(['Dynamic Fields', 'fields': fields ])
return newfieldsets
【讨论】:
不幸的是,Django 1.9 中的ModelAdmin.declared_fieldsets
has been removed
嗯.. 好吧,我想当我将服务器升级到 1.9 时,我会有一些工作要做;)但幸运的是,我已经在我的应用程序的其他地方复制了大部分管理功能.. .
另外django.contrib.admin.util
现在是django.contrib.admin.utils
谢谢,否则我的回答是否仍然有效?如果是这样,我会纠正它。
嘿,django 2.5 怎么样。 kwargs['fields'] = flatten_fieldsets(self.fieldsets) flatten_fieldsets for name, opts in fieldsets: TypeError: 'NoneType' object is not iterable 我有一些问题【参考方案3】:
这适用于在 Django 1.9.3 中添加动态字段,仅使用 ModelAdmin 类(无 ModelForm)并覆盖 get_fields
。我还不知道它有多强大:
class MyModelAdmin(admin.ModelAdmin):
fields = [('title','status', ), 'description', 'contact_person',]
exclude = ['material']
def get_fields(self, request, obj=None):
gf = super(MyModelAdmin, self).get_fields(request, obj)
new_dynamic_fields = [
('test1', forms.CharField()),
('test2', forms.ModelMultipleChoiceField(MyModel.objects.all(), widget=forms.CheckboxSelectMultiple)),
]
#without updating get_fields, the admin form will display w/o any new fields
#without updating base_fields or declared_fields, django will throw an error: django.core.exceptions.FieldError: Unknown field(s) (test) specified for MyModel. Check fields/fieldsets/exclude attributes of class MyModelAdmin.
for f in new_dynamic_fields:
#`gf.append(f[0])` results in multiple instances of the new fields
gf = gf + [f[0]]
#updating base_fields seems to have the same effect
self.form.declared_fields.update(f[0]:f[1])
return gf
【讨论】:
【参考方案4】:虽然 Jacob 的帖子可能适用于普通的 ModelForm
s(即使它已经超过一年半了),但管理员是另一回事。
所有定义模型、表单 ModelAdmins 和诸如此类的声明方式都大量使用元类和类自省。与管理员相同 - 当您告诉 ModelAdmin
使用特定表单而不是创建默认表单时,它会内省 类。它从类本身获取字段列表和其他内容,而不实例化它。
然而,您的自定义类没有在类级别定义额外的表单字段,而是在实例化之后动态添加一个 - 这对于ModelAdmin
来说已经太迟了,无法识别这种变化.
解决问题的一种方法可能是将ModelAdmin
子类化并覆盖其get_fieldsets
方法以实际实例化ModelForm
类并从实例而不是类中获取字段列表。不过,您必须记住,这可能比默认实现要慢一些。
【讨论】:
【参考方案5】:您可以使用表单元类创建动态字段和字段集。示例代码如下。根据您的要求添加循环逻辑。
class CustomAdminFormMetaClass(ModelFormMetaclass):
"""
Metaclass for custom admin form with dynamic field
"""
def __new__(cls, name, bases, attrs):
for field in get_dynamic_fields: #add logic to get the fields
attrs[field] = forms.CharField(max_length=30) #add logic to the form field
return super(CustomAdminFormMetaClass, cls).__new__(cls, name, bases, attrs)
class CustomAdminForm(six.with_metaclass(CustomAdminFormMetaClass, forms.ModelForm)):
"""
Custom admin form
"""
class Meta:
model = ModelName
fields = "__all__"
class CustomAdmin(admin.ModelAdmin):
"""
Custom admin
"""
fieldsets = None
form = CustomAdminForm
def get_fieldsets(self, request, obj=None):
"""
Different fieldset for the admin form
"""
self.fieldsets = self.dynamic_fieldset(). #add logic to add the dynamic fieldset with fields
return super(CustomAdmin, self).get_fieldsets(request, obj)
def dynamic_fieldset(self):
"""
get the dynamic field sets
"""
fieldsets = []
for group in get_field_set_groups: #logic to get the field set group
fields = []
for field in get_group_fields: #logic to get the group fields
fields.append(field)
fieldset_values = "fields": tuple(fields), "classes": ['collapse']
fieldsets.append((group, fieldset_values))
fieldsets = tuple(fieldsets)
return fieldsets
【讨论】:
【参考方案6】:也许我有点晚了...但是,我使用的是 Django 3.0,并且还想根据请求动态地将一些自定义字段添加到表单中。
我最终得到了一个类似于@tehfink 结合@little_birdie 描述的解决方案。
但是,仅按照建议更新 self.form.declared_fields
并没有帮助。此过程的结果是,self.form.declared_fields
中定义的自定义字段列表总是随着请求而增长。
我首先通过初始化这个字典来解决这个问题:
class ModelAdminGetCustomFieldsMixin(object):
def get_fields(self, request, obj=None):
fields = super().get_fields(request, obj=None)
self.form.declared_fields =
if obj:
for custom_attribute in custom_attribute_list:
self.form.declared_fields.update(custom_attribute.name: custom_attribute.field)
return fields
其中custom_attribute.field
是一个表单字段实例。
此外,还需要定义一个 ModelForm,其中在初始化期间,自定义字段也已动态添加:
class SomeModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
for custom_attribute in custom_attribute_list:
self.fields[custom_attribute.name] = custom_attribute.field
并在 ModelAdmin 中使用此 ModelForm。
之后,新定义的属性可以用于例如字段集。
【讨论】:
嘿,感谢 Django 3.0 的更新,我是框架的新手。你是如何使用 SomeModelForm 实现第一类 ModelAdminGetCustomFieldsMixin 的。我希望根据下拉菜单中的选择在 Django admin 中显示此动态表单,该表单在将使用此动态表单的同一模型中选择 嗨弗朗西斯科,反之亦然:SomeModelForm
在ModelAdmin
中用作form
而ModelAdminGetCustomFieldsMixin
是一个mixin,它需要作为一个附加类来继承来自例如SomeModelAdmin(ModelAdminGetCustomFieldsMixin, ModelAdmin)
。页面本身是静态的。表格不会动态变化。您需要重新加载页面以更改表单或一堆 js。【参考方案7】:
Stephan 的回答很优雅,但是当我在 dj1.6 中使用时,它要求该字段是一个元组。 完整的解决方案如下所示:
class ProductForm(ModelForm):
foo = CharField(label='foo')
class ProductAdmin(admin.ModelAdmin):
form = ProductForm
def get_fieldsets(self, request, obj=None):
fieldsets = super(ProductAdmin, self).get_fieldsets(request, obj)
fieldsets[0][1]['fields'] += ('foo', )
return fieldsets
【讨论】:
【参考方案8】:不确定为什么这不起作用,但可能的解决方法是静态定义字段(在表单上),然后在 __init__
中覆盖它?
【讨论】:
【参考方案9】:我很长时间都无法解决动态添加字段的问题。 解决方案“little_birdie”确实有效。谢谢小鸟)) 唯一的细微差别是: “Self.declared_fieldsets”应替换为“self.fieldsets”。
#kwargs['fields'] = flatten_fieldsets(self.declared_fieldsets)
kwargs['fields'] = flatten_fieldsets(self.fieldsets)
我使用的是 1.10 版。也许有些事情发生了变化。
如果有人找到更简单、更优雅的解决方案,请在此处展示。
谢谢大家)))
【讨论】:
以上是关于Django Admin 中的动态字段的主要内容,如果未能解决你的问题,请参考以下文章
Django - Admin 中的 ForeignKey 字段初始值定义