定义ModelForm元类的正确方法

1 投票
2 回答
637 浏览
提问于 2025-04-17 14:25

我想做的是创建一个动态的ModelForm,它可以根据某个类属性生成额外的字段,以便在ModelAdmin中使用。就像这样:

class MyModelForm(forms.ModelForm):
    config_fields = ('book_type', 'is_featured', 'current_price__is_sale')

class MyModelAdmin(admin.ModelAdmin):
    form = MyModelForm

在这个例子中,MyModelForm会根据config_fields这个属性生成字段,通过一些自省(也就是查看对象的属性和方法)。到目前为止,我的做法大致是这样的(参考了这个答案 https://stackoverflow.com/a/6581949/677985):

class ConfigForm(type):
    def __new__(cls, name, bases, attrs):
        if 'config_fields' in attrs:
            for config_field in attrs['config_fields']:

                # ... (removed for clarity)

                attrs.update(fields)
        return type(name, bases, attrs)

class MyModelForm(forms.ModelForm):
    __metaclass__ = ConfigForm
    config_fields = ('book_type', 'is_featured', 'current_price__is_sale')

这个方法基本上能用,但我对它有几个不太满意的地方:

  1. 验证似乎不太有效,不过这现在不是主要问题
  2. 我不太明白为什么需要“if config_field in attrs:”这个条件,但它确实是需要的
  3. 我更希望MyModelForm能继承,而不是设置__metaclass__属性,这样基类就可以更方便地重用,也能让我更容易地重写clean和__init__方法。

我尝试实现第三个点,结果是额外的字段没有在管理表单中显示出来。如果有人能帮我解决这个问题,或者至少给我指个方向,我会非常感激。

我知道使用元类来做这个可能有点过于复杂,而且我猜问题的一部分在于ModelForm的继承链中已经有一两个元类了。所以如果有人有其他方法能实现同样的效果,我也会很高兴。

2 个回答

0

这样说吧,

基本上,任何一个继承了你的 StepForm 的表单都会有你想要的 元类,在这个例子中是 StepFormMetaclass。请注意,如果你在某个 form.py 文件里定义了这个表单,你需要在 ___init___.py 文件中导入这个表单,这样在 Django 启动时它才能被执行。

from django.forms.forms import DeclarativeFieldsMetaclass


class StepFormMetaclass(DeclarativeFieldsMetaclass):
    .......
    def __new__(meta_class, name, bases, attributes):
        .....
        return DeclarativeFieldsMetaclass.__new__(meta_class, name, bases, attributes)

class StepForm(six.with_metaclass(StepFormMetaclass, forms.Form, StepFormMixin)):
    def __init__(self, *args, **kwargs):

        super(StepForm, self).__init__(*args, **kwargs)


    def as_p(self):
        return ......
0

我觉得 ModelForm 本身已经有一个元类了,但你通过设置自己的元类把它覆盖掉了。这就是为什么你没有得到验证或者其他 ModelForm 自带的好处。

其实,你可以直接使用 type 来创建你的 ModelForm,这样可以描述你想要的类型,同时还能让 ModelForm 的元类正常工作。

举个例子:

    config_fields = ('book_type', 'is_featured', 'current_price__is_sale')
    # the below is an example, you need more work to construct the proper attrs
    attrs = dict((f, forms.SomeField) for f in config_fields)
    ConfigModelForm = type('DynamicModelForm', (forms.ModelForm,), attrs)

    class MyModelAdmin(admin.ModelAdmin):
        form = ConfigModelForm

如果需要的话,你可以把前面的部分放在一个函数里,然后在你的 ModelAdmin 中调用这个函数来设置你的表单属性。

想了解更多关于使用 type 的内容,可以看看 我在这里的回答,里面有链接和讨论。

撰写回答