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

发布于 2024-10-19 21:55:00 字数 698 浏览 3 评论 0原文

如果我有两个基于不同基类(例如 Form 和 ModelForm)的表单,但我想在这两个表单中使用一些字段,我可以以 DRY 方式重用它们吗?

考虑以下场景:

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_migatory 字段?想象一下我有几十个此类表格。如果我一遍又一遍地写这些代码,代码将会变得很糟糕。

(假设,出于这个问题的目的,我不能或不会将 airspeed_velocity 和 is_migatory 转换为模型 AfricaBird 的字段。)

If I have two forms, based on different base classes (say, Form and ModelForm), but I want to use a few fields in both, can I reuse them in a DRY way?

Consider the following scenario:

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)

....is there a way I can just reuse the fields airspeed_velocity and is_migratory? Imagine I have a couple dozen of these type of forms. The code will be soaking if I write these over and over again.

(Assume, for the purposes of this question, that I can't or won't turn airspeed_velocity and is_migratory into fields of the model AfricanBird.)

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

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

发布评论

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

评论(5

红玫瑰 2024-10-26 21:55:00

您可以使用多重继承(又名 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 开发人员,并且元类是危险的。因此,如果您遇到奇怪的行为,最好坚持使用上面更详细的方法:)

祝您好运!

You could use multiple inheritance aka mixins, to factor out the fields that are used in both Form and 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

UPDATE:

Since this does not work with Django metaprogramming, you either need to create a custom __init__ constructor that adds the inherited fields to the object's fields list or you can add the references explicitly inside the class definition:

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

UPDATE:

Of course you don't have to nest your shared fields into a class -- you could also simply define them as globals ...

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

UPDATE:

Okay, if you really want to DRY to the max, you have to go with the metaclasses.

So here is how you may do it:

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)

You can now derive your collections of formfields from MixinForm like this:

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

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

Then add them to the list of base classes like this:

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

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

So what does it do?

  • The metaclass collects all the fields declared in your MixinForm
  • It then adds custom __init__ constructors, to make sure that the __init__ method of the MixinForm gets magically called. (Otherwise you would have to call it explicitly.)
  • MixinForm.__init__ copies the declared fields int the field attribute

Please note that I am neither a Python guru nor a django developer, and that metaclasses are dangerous. So if you encounter weird behaviour better stick with the more verbose approach above :)

Good Luck!

得不到的就毁灭 2024-10-26 21:55:00

工厂式的方法怎么样?

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),
    })

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

How about a factory-style approach?

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),
    })

For that matter, you could modify the factory function here to look into an existing form class and pick out the attributes you want, so that you don't lose the declarative syntax...

他夏了夏天 2024-10-26 21:55:00

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>}

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):
    ...

Should work.

I have some long running code that works and has the fields attr that looks like this.

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', )

Notice that I still use the fields tuple in Meta. The field shows up in the fields dict on the form instance whether or not it is in the Meta.fields.

I have a regular form that uses this pattern as well:

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

class FooGenresForm(forms.Form):
    genres = genres_field

I see that my fields dict is working.

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>}
放低过去 2024-10-26 21:55:00

创建 IntegerField 的子类

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

Create a subclass of the IntegerField

class AirspeedField(forms.IntegerField):
    def __init__():
        super(AirspeedField, self).__init__(some_important_details_here)
罪歌 2024-10-26 21:55:00

我刚刚制作了一个以干方式解决此问题的代码片段:

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

它使用crispy-form,但不使用crispy-forms也可以使用相同的想法。这个想法是在同一个表单标签下使用多个表单。

I have just made a snippet that resolves this issue in a DRY way:

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

It uses crispy-form, but the same idea can be used without crispy-forms. The idea is to use multiple forms under the same form tag.

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