Django Admin 中的动态字段

发布于 2024-12-13 19:09:04 字数 963 浏览 0 评论 0原文

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

与 jacobian 1 的博客文章相关,这就是我想到的with:

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)

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

I want to have additional fields regarding value of one field. Therefor I build a custom admin form to add some new fields.

Related to the blogpost of jacobian 1 this is what I came up with:

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)

But the additional field 'foo' does not show up in the admin. If I add the field like this, all works fine but is not as dynamic as required, to add the fields regarding the value of another field of the model

class ProductAdminForm(forms.ModelForm):

    foo = forms.IntegerField(label="foo")

    class Meta:
        model = Product

class ProductAdmin(admin.ModelAdmin):
    form = ProductAdminForm

admin.site.register(Product, ProductAdmin)

So is there any initialize method that i have to trigger again to make the new field working? Or is there any other attempt?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

就此别过 2024-12-20 19:09:05

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

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

Here is a solution to the problem. Thanks to koniiiik i tried to solve this by extending the *get_fieldsets* method

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

If you use multiple fieldsets be sure to add the to the right fieldset by using the appropriate index.

天气好吗我好吗 2024-12-20 19:09:05

上面接受的答案适用于旧版本的 django,这就是我的做法。现在这个问题在后来的 django 版本中已经被打破了(我现在使用的是 1.68,但现在已经很旧了)。

它现在被破坏的原因是因为从 ModelAdmin.get_fieldsets() 返回的字段集中的任何字段最终都会作为 fields=parameter 传递到 modelform_factory()< /code>,这会给你一个错误,因为列表中的字段不存在(并且在实例化你的表单并调用它的 __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

The accepted answer above worked in older versions of django, and that's how I was doing it. This has now broken in later django versions (I am on 1.68 at the moment, but even that is old now).

The reason it is now broken is because any fields within fieldsets you return from ModelAdmin.get_fieldsets() are ultimately passed as the fields=parameter to modelform_factory(), which will give you an error because the fields on your list do not exist (and will not exist until your form is instantiated and its __init__ is called).

In order to fix this, we must override ModelAdmin.get_form() and supply a list of fields that does not include any extra fields that will be added later. The default behavior of get_form is to call get_fieldsets() for this information, and we must prevent that from happening:

# 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
深海夜未眠 2024-12-20 19:09:05

也许我有点晚了...但是,我正在使用 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。

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

Maybe I am a bit late... However, I am using Django 3.0 and also wanted to dynamically ad some custom fields to the form, depending on the request.

I end up with a solution similar to the one described by @tehfink combined with @little_birdie.

However, just updating self.form.declared_fields as suggested didn't help. The result of this procedure is, that the list of custom fields defined in self.form.declared_fields always grows from request to request.

I solved this by initialising this dictionary first:

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

where custom_attribute.field is a form field instance.

Additionally, it was required to define a ModelForm, wherein during initialisation the custom fields have been added dynamically as well:

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

and use this ModelForm in the ModelAdmin.

Afterwards, the newly defined attributes can be used in, e.g., a fieldset.

音盲 2024-12-20 19:09:05

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

This works for adding dynamic fields in Django 1.9.3, using just a ModelAdmin class (no ModelForm) and by overriding get_fields. I don't know yet how robust it is:

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
可是我不能没有你 2024-12-20 19:09:05

虽然 Jacob 的帖子可能适用于常规的 ModelForm(尽管它已经存在一年半多了),但管理却有些不同。

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

然而,您的自定义类并未在类级别定义额外的表单字段,而是在实例化后动态添加一个表单字段 - 这对于 ModelAdmin 来说已经来不及识别了这个变化。

解决问题的一种方法可能是子类 ModelAdmin 并重写其 get_fieldsets 方法以实际实例化 ModelForm 类并获取字段列表来自实例而不是类。不过,您必须记住,这可能比默认实现要慢一些。

While Jacob's post might work all right for regular ModelForms (even though it's more than a year and a half old), the admin is a somewhat different matter.

All the declarative way of defining models, forms ModelAdmins and whatnot makes heavy use of metaclasses and class introspection. Same with the admin – when you tell a ModelAdmin to use a specific form istead of creating a default one, it introspects the class. It gets the list of fields and other stuff from the class itself without instantiating it.

Your custom class, however, does not define the extra form field at class level, instead it dynamically adds one after it has been instantiated – that's too late for the ModelAdmin to recognize this change.

One way to go about your problem might be to subclass ModelAdmin and override its get_fieldsets method to actually instantiate the ModelForm class and get the list of fields from the instance instead of the class. You'll have to keep in mind, though, that this might be somewhat slower than the default implementation.

妳是的陽光 2024-12-20 19:09:05

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

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

You can create dynamic fields and fieldset using the form meta class. Sample code is given below. Add the loop logic as per you requirements.

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
青瓷清茶倾城歌 2024-12-20 19:09:05

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

Stephan's answer is elegant, but when I used in in dj1.6 it required the field to be a tuple.
The complete solution looked like this:

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
渔村楼浪 2024-12-20 19:09:05

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

not sure why that's not working, but could a possible workaround be to define the field statically (on the form) and then override it in the __init__?

一紙繁鸢 2024-12-20 19:09:05

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

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

我用的是1.10版本。也许有些事情已经改变了。

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

感谢大家)))

I for a long time could not solve a problem with dynamic addition of fields.
The solution "little_birdie" really works. Thank you Birdie))
The only nuance is:
"Self.declared_fieldsets" should be replaced with "self.fieldsets".

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

I used version 1.10. Perhaps something has changed.

If someone finds an even simpler and elegant solution, show here.

Thanks to all )))

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文