Django管理中的动态字段
我想有关于一个字段值的附加字段。因此,我构建了一个自定义管理表单来添加一些新字段 与雅各比的博客帖子相关的是我想到的:Django管理中的动态字段,django,forms,dynamic,admin,Django,Forms,Dynamic,Admin,我想有关于一个字段值的附加字段。因此,我构建了一个自定义管理表单来添加一些新字段 与雅各比的博客帖子相关的是我想到的: class ProductAdminForm(forms.ModelForm): class Meta: model = Product def __init__(self, *args, **kwargs): super(ProductAdminForm, self).__init__(*args, **kwargs)
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”没有显示在admin中。如果我像这样添加字段,所有这些都可以正常工作,但并不像需要的那样动态,以添加与模型的另一个字段的值有关的字段
class ProductAdminForm(forms.ModelForm):
foo = forms.IntegerField(label="foo")
class Meta:
model = Product
class ProductAdmin(admin.ModelAdmin):
form = ProductAdminForm
admin.site.register(Product, ProductAdmin)
那么,是否有任何初始化方法需要我再次触发才能使新字段正常工作?或者还有其他尝试吗?虽然Jacob的帖子可能适用于常规的
ModelForm
s(即使它已经有一年半的历史了),但管理是一件有点不同的事情
所有定义模型、表单、模型管理员等的声明方式都大量使用元类和类内省。与admin相同–当您告诉一个ModelAdmin
使用一个特定的表单,而不是创建一个默认表单时,它会对类进行内省。它从类本身获取字段和其他内容的列表,而不实例化它
但是,您的自定义类并没有在类级别定义额外的表单字段,而是在实例化后动态添加一个表单字段–这对于ModelAdmin
来说太晚了,无法识别此更改
解决问题的一种方法可能是将
ModelAdmin
子类化,并重写其get\u fieldset
方法,以实际实例化ModelForm
类,并从实例而不是类中获取字段列表。不过,您必须记住,这可能比默认实现慢一些。不确定为什么这不起作用,但可能的解决方法是静态定义字段(在表单上),然后在\uuu init\uuuu
中重写它。这是问题的解决方案。多亏了Koniik,我尝试通过扩展*get_fieldset*方法来解决这个问题
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
如果您使用多个字段集,请确保通过使用适当的索引将添加到正确的字段集。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
您可以使用表单元类创建动态字段和字段集。下面给出了示例代码。根据您的需求添加循环逻辑
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
上述公认的答案适用于django的旧版本,我就是这样做的。这在后来的django版本中已经被打破了(我现在使用的是1.68,但现在它已经过时了) 它现在被破坏的原因是,您从
ModelAdmin.get\u fieldset()
返回的字段集中的任何字段最终都会作为fields=参数
传递给modelform\u factory()
,这将给您一个错误,因为列表中的字段不存在(在实例化表单并调用其\uuuuu init\uuuuu
之前,它将不存在)
为了解决这个问题,我们必须覆盖ModelAdmin.get\u form()
,并提供一个字段列表,其中不包括以后添加的任何额外字段。get\u form
的默认行为是调用get\u fieldset()
获取此信息,我们必须防止这种情况发生:
# 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.3中添加动态字段,只需使用ModelAdmin类(无ModelForm)并覆盖
get\u 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
我很长一段时间都无法解决动态添加字段的问题。 “小鸟”这个解决方案真的很有效。谢谢你,小鸟 唯一的细微差别是: “Self.declared_fieldset”应替换为“Self.fieldset” 我用的是1.10版。也许有些事情已经改变了 如果有人找到了更简单、更优雅的解决方案,请在这里展示
谢谢大家。)也许我迟到了一点。。。但是,我使用的是Django3.0,还希望根据请求动态地将一些自定义字段添加到表单中 我最终得到了一个类似于@tehfink和@little_birdie所描述的解决方案 然而,仅仅按照建议更新self.form.declared_字段并没有帮助。此过程的结果是,
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
之后,新定义的属性可用于例如字段集。
为GlobalLabel指定的未知字段(foo)。检查GlobalLabelAdmin类的字段/字段集/排除属性。
我收到这个错误,我不知道为什么。。。你能帮帮我吗?@bhushya:你能解决这个问题吗?我也无法在django 1.9.3中使用它,例如:django.core.exceptions.FieldError:为MyModel指定的未知字段(dynamicfield1,dynamicfield2)
@tehfink您似乎还没有在模型中定义字段。。你能在pastebin.com上发布你的模型结构并分享链接吗?@bhushya:你说得对;字段(dynamicfield1等)未在我的模型上定义。像在原来的问题中一样,我想在ModelForm
中动态添加字段,而上面提到的get\u fieldset
覆盖在Django 1.9中似乎不起作用。3@bhushya:我找到了Django 1.9.3的一个潜在解决方案,发布在下面,Django 1.9Hmm中的ModelAdmin.declared\u fieldset
。。嗯,我想当我把服务器升级到1.9时,我会有一些工作要做;)但幸运的是,我已经在我的应用程序中的其他地方复制了大部分管理功能…还有django.cont
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