Django:重用表单字段而不继承?

时间:2011-03-03 17:52:26

标签: django django-forms dry

如果我有两种形式,基于不同的基类(比如Form和ModelForm),但我想在两者中使用几个字段,我可以以干燥的方式重用它们吗?

考虑以下情况:

class AfricanSwallowForm(forms.ModelForm):
    airspeed_velocity = forms.IntegerField(some_important_details_here)
    is_migratory = forms.BooleanField(more_important_details)

    class Meta:
        model = AfricanBird

class EuropeanSwallowForm(forms.Form):
    airspeed_velocity = forms.IntegerField(some_important_details_here)
    is_migratory = forms.BooleanField(more_important_details)

....有没有办法可以重用字段airspeed_velocity和is_migratory?想象一下,我有几十种这样的形式。如果我一遍又一遍地写这些代码,代码将会浸泡。

(假设,为了这个问题的目的,我不能或不会将airspeed_velocity和is_migratory转换为模型AfricanBird的字段。)

5 个答案:

答案 0 :(得分:5)

您可以使用多重继承aka mixins 来分解Form和ModelForm中使用的字段。

class SwallowFormFields:
    airspeed_velocity = forms.IntegerField( ... )
    is_migratory = forms.BooleanField( ... )

class AfricanSwallowForm(forms.ModelForm, SwallowFormFields):
    class Meta:
        model = AfricanBird

class EuropeanSwallowForm(forms.Form, SwallowFormFields):
    pass

<强>更新

由于这不适用于Django元编程,您需要创建一个自定义__init__构造函数,将继承的字段添加到对象的字段列表中,或者您可以在类定义中显式添加引用:

class SwallowFormFields:
    airspeed_velocity = forms.IntegerField()
    is_migratory = forms.BooleanField()

class AfricanSwallowForm(forms.ModelForm):
    airspeed_velocity = SwallowFormFields.airspeed_velocity
    is_migratory = SwallowFormFields.is_migratory
    class Meta:
        model = AfricanSwallow

class EuropeanSwallowForm(forms.Form):
    airspeed_velocity = SwallowFormFields.airspeed_velocity
    is_migratory = SwallowFormFields.is_migratory

<强>更新

当然,您不必将共享字段嵌套到类中 - 您也可以将它们简单地定义为全局...

airspeed_velocity = forms.IntegerField()
is_migratory = forms.BooleanField()

class AfricanSwallowForm(forms.ModelForm):
    airspeed_velocity = airspeed_velocity
    is_migratory = is_migratory
    class Meta:
        model = AfricanSwallow

class EuropeanSwallowForm(forms.Form):
    airspeed_velocity = airspeed_velocity
    is_migratory = is_migratory

<强>更新

好的,如果你真的想干到最大,你必须使用元类

所以你可以这样做:

from django.forms.models import ModelForm, ModelFormMetaclass
from django.forms.forms import get_declared_fields, DeclarativeFieldsMetaclass
from django.utils.copycompat import deepcopy

class MixinFormMetaclass(ModelFormMetaclass, DeclarativeFieldsMetaclass):
    def __new__(cls, name, bases, attrs):

        # default __init__ that calls all base classes
        def init_all(self, *args, **kwargs):
            for base in bases:
                super(base, self).__init__(*args, **kwargs)
        attrs.setdefault('__init__', init_all)

        # collect declared fields
        attrs['declared_fields'] = get_declared_fields(bases, attrs, False)

        # create the class
        new_cls = super(MixinFormMetaclass, cls).__new__(cls, name, bases, attrs)
        return new_cls

class MixinForm(object):
    __metaclass__ = MixinFormMetaclass
    def __init__(self, *args, **kwargs):
        self.fields = deepcopy(self.declared_fields)

您现在可以从MixinForm派生您的表单域集合,如下所示:

class SwallowFormFields(MixinForm):
    airspeed_velocity = forms.IntegerField()
    is_migratory = forms.BooleanField()

class MoreFormFields(MixinForm):
    is_endangered = forms.BooleanField()

然后将它们添加到基类列表中,如下所示:

class EuropeanSwallowForm(forms.Form, SwallowFormFields, MoreFormFields):
    pass

class AfricanSwallowForm(forms.ModelForm, SwallowFormFields):
    class Meta:
        model = AfricanSwallow

那它做了什么?

  • 元类收集MixinForm
  • 中声明的所有字段
  • 然后添加自定义__init__构造函数,以确保MixinForm的__init__方法被神奇地调用。 (否则你必须明确地调用它。)
  • MixinForm.__init__将声明的字段复制到字段属性

请注意,我既不是Python大师也不是django开发人员,而且元类很危险。因此,如果您遇到奇怪的行为,请更好地坚持以上更详细的方法:)

祝你好运!

答案 1 :(得分:1)

工厂式方法怎么样?

def form_factory(class_name, base, field_dict):
    always_has = {
        'airspeed_velocity': forms.IntegerField(some_important_details_here),
        'is_migratory': forms.BooleanField(more_important_details)
    }
    always_has.update(field_dict)
    return type(class_name, (base,), always_has)

def meta_factory(form_model):
    class Meta:
        model = form_model
    return Meta

AfricanSwallowForm = form_factory('AfricanSwallowForm', forms.ModelForm, {
        'other' = forms.IntegerField(some_important_details_here),
        'Meta': meta_factory(AfricanBird),
    })

EuropeanSwallowForm = form_factory('EuropeanSwallowForm', forms.Form, {
        'and_a_different' = forms.IntegerField(some_important_details_here),
    })

就此而言,您可以在此处修改工厂函数以查看现有的表单类并选择所需的属性,这样您就不会丢失声明性语法......

答案 2 :(得分:0)

<击>

<击>
class SwallowForm(forms.Form):
    airspeed_velocity = forms.IntegerField()
    is_migratory = forms.BooleanField()

class AfricanSwallowForm(forms.ModelForm, SwallowForm):
    class Meta:
        model = AfricanSwallow

class EuropeanSwallowForm(forms.Form, SwallowForm):
    ...

应该有效。

我有一些运行良好的代码,并且字段attr看起来像这样。

languages_field = forms.ModelMultipleChoiceField(
        queryset=Language.objects.all(),
        widget=forms.CheckboxSelectMultiple,
        required=False
)

class FooLanguagesForm(forms.ModelForm):
    languages = languages_field

    class Meta:
        model = Foo
        fields = ('languages', )

请注意,我仍然使用Meta中的字段元组。该字段显示在表单实例的字段中,无论它是否在Meta.fields中。

我有一个使用此模式的常规表单:

genres_field = forms.ModelMultipleChoiceField(
        queryset=blah,
        widget=forms.CheckboxSelectMultiple,
        #required=False,
)

class FooGenresForm(forms.Form):
    genres = genres_field

我看到我的田地字典正在运作。

In [6]: f = FooLanguagesForm()

In [7]: f.fields
Out[7]: {'languages': <django.forms.models.ModelMultipleChoiceField object at 0x1024be450>}

In [8]: f2 = FooGenresForm()

In [9]: f2.fields
Out[9]: {'genres': <django.forms.models.ModelMultipleChoiceField object at 0x1024be3d0>}

答案 3 :(得分:0)

创建IntegerField的子类

class AirspeedField(forms.IntegerField):
    def __init__():
        super(AirspeedField, self).__init__(some_important_details_here)

答案 4 :(得分:-1)

我刚刚制作了一个解决这个问题的片段:

https://djangosnippets.org/snippets/10523/

它使用了酥脆的形状,但可以使用相同的想法而没有酥脆的形式。我们的想法是在同一个表单标签下使用多个表单。