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