Django Admin中的动态字段

问题描述 投票:15回答:8

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

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

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

django forms dynamic admin
8个回答
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

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


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

5
投票

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

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

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

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


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
投票

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

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

2
投票

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

0
投票

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


0
投票

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

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

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

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

谢谢大家 )))

© www.soinside.com 2019 - 2024. All rights reserved.