Django: повторно использовать поля формы без наследования? - PullRequest
5 голосов
/ 03 марта 2011

Если у меня есть две формы, основанные на разных базовых классах (скажем, 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 ]

5 голосов
/ 03 марта 2011

Вы можете использовать множественное наследование, также известное как 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, например:this:

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__ копирует объявленные поля в атрибут field

Обратите внимание, что я не гуру Python и не разработчик djangoи что метаклассы опасны.Поэтому, если вы столкнетесь со странным поведением, лучше придерживайтесь более подробного подхода, описанного выше:)

Удачи!

1 голос
/ 03 марта 2011

Как насчет подхода фабричного стиля?

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

В этом отношении, вы можете изменить здесь функцию фабрики, чтобы посмотреть на существующий класс формы и выбрать нужные атрибуты, чтобы вы не делалине теряю декларативный синтаксис ...

0 голосов
/ 25 августа 2015

Я только что сделал фрагмент, который решает эту проблему сухим способом:

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

Используется хрустящая форма, но та же идея может быть использована и без хрустящей формы. Идея состоит в том, чтобы использовать несколько форм под одним тегом формы.

0 голосов
/ 06 июля 2012

Создать подкласс IntegerField

class AirspeedField(forms.IntegerField):
    def __init__():
        super(AirspeedField, self).__init__(some_important_details_here)
0 голосов
/ 03 марта 2011

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. Поле отображается в полях dict экземпляра формы независимо от того, находится оно в Meta.fields или нет.

У меня есть обычная форма, которая также использует этот шаблон:

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

class FooGenresForm(forms.Form):
    genres = genres_field

Я вижу, что мои поля dict работают.

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>}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...