Ste*_*yer 16 forms django admin dynamic
我想要有关于一个字段值的其他字段.因此,我构建了一个自定义管理表单来添加一些新字段.
与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)
Run Code Online (Sandbox Code Playgroud)
但附加字段'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)
Run Code Online (Sandbox Code Playgroud)
那么有什么初始化方法我必须再次触发以使新字段工作?或者还有其他尝试吗?
Ste*_*yer 18
这是问题的解决方案.感谢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
Run Code Online (Sandbox Code Playgroud)
如果使用多个字段集,请确保使用适当的索引将其添加到右侧字段集.
小智 9
也许我有点晚了...但是,我正在使用 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
Run Code Online (Sandbox Code Playgroud)
其中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
Run Code Online (Sandbox Code Playgroud)
并在 ModelAdmin 中使用此 ModelForm。
之后,新定义的属性可以在例如字段集中使用。
这适用于在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
Run Code Online (Sandbox Code Playgroud)
虽然雅各布的帖子可能适用于常规ModelForm
s(即使它超过一年半),但管理员却有点不同.
定义模型的所有声明方式,形式ModelAdmins和诸如此类的东西大量使用元类和类内省.同样的,在管理-当你告诉一个ModelAdmin
使用创建一个默认的一种具体形式istead,内省的类.它从类本身获取字段列表和其他内容,而不实例化它.
但是,您的自定义类不会在类级别定义额外的表单字段,而是在实例化后动态添加一个- 这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
Run Code Online (Sandbox Code Playgroud)
上面接受的答案适用于旧版本的django,这就是我的做法.现在已经打破了以后的django版本(我目前在1.68上,但即使现在已经很老了).
它现在被破坏的原因是因为从ModelAdmin.get_fieldsets()返回的字段集中的任何字段最终都作为fields =参数传递给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
Run Code Online (Sandbox Code Playgroud)