Django Admin中的动态字段

时间:2011-11-04 09:22:45

标签: django forms dynamic admin

我希望有一个关于一个字段值的其他字段。因此,我构建了一个自定义管理表单来添加一些新字段。

与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)

那么有什么初始化方法我必须再次触发才能使新字段工作?或者还有其他尝试吗?

9 个答案:

答案 0 :(得分:16)

这是问题的解决方案。感谢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

如果使用多个字段集,请务必使用适当的索引将其添加到右侧字段集。

答案 1 :(得分:6)

这适用于在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

答案 2 :(得分:5)

虽然雅各布的帖子可能适用于常规ModelForm(即使它超过一年半),但管理员却有点不同。

定义模型的所有声明方式,形式ModelAdmins和诸如此类的东西大量使用元类和类内省。与管理员相同 - 当您告诉ModelAdmin使用特定表单而不是创建默认表单时,它会内省。它从类本身获取字段列表和其他内容,而不实例化它。

但是,您的自定义类没有在类级别定义额外的表单字段,而是在实例化之后动态添加一个 - 这对于ModelAdmin来说为时已晚这种变化。

解决问题的一种方法可能是继承ModelAdmin并覆盖其get_fieldsets方法以实际实例化ModelForm类并从实例中获取字段列表而不是类。但是,您必须记住,这可能比默认实现慢一些。

答案 3 :(得分:5)

上面接受的答案适用于旧版本的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

答案 4 :(得分:4)

您可以使用表单元类创建动态字段和字段集。示例代码如下。根据您的要求添加循环逻辑。

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

答案 5 :(得分:2)

斯蒂芬的回答很优雅,但是当我在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

答案 6 :(得分:1)

也许我来晚了...但是,我使用的是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。

此后,可以在例如字段集中使用新定义的属性。

答案 7 :(得分:0)

不确定为什么不起作用,但是可能的解决办法是静态地(在表单上)定义字段,然后在__init__中覆盖它?

答案 8 :(得分:0)

我很长时间无法解决动态添加字段的问题。 解决方案“little_birdie”确实有效。谢谢Birdie)) 唯一的细微差别是: “Self.declared_fieldsets”应替换为“self.fieldsets”。

#kwargs['fields'] =  flatten_fieldsets(self.declared_fieldsets)
kwargs['fields'] =  flatten_fieldsets(self.fieldsets)

我使用的是版本1.10。也许有些事情发生了变化。

如果有人找到更简单优雅的解决方案,请点击此处。

感谢所有人)))