Использование миксина с классом формы Django

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

class NoteFormMixin(object):
    note = forms.CharField()

class MainForm(forms.Form):
    name = forms.CharField()
    age = forms.IntegerField()

class SpecialForm(MainForm, NoteFormMixin):
    favorite_color = forms.CharField()

Мой единственный вопрос: как это работает? Пока похоже, что если я использую миксин, то он не распознает поля, установленные из этого миксина:

>>> ff1 = SpecialForm()
>>> ff1.fields
{'name': <django.forms.fields.CharField object at 0x178d3110>, 'age': <django.forms.fields.IntegerField object at 0x178d3190>, 'favorite_color': <django.forms.fields.CharField object at 0x178d3210>}

Это просто то, что нельзя сделать?


person Jordan Reiter    schedule 18.08.2011    source источник
comment
Обратите внимание, что ваша иерархия наследования неверна. В Python базовый класс является самым правильным, тогда как классы Mixin должны стоять перед ним. Предполагая, что вы хотите, чтобы SpecialForm основывался на MainForm, правильным порядком будет SpecialForm(NoteFormMixin, MainForm).   -  person enpenax    schedule 23.07.2014


Ответы (3)


Проблема в том, что ваш NoteFormMixin происходит от объекта, а не от forms.Form. Вам нужно изменить его так:

class NoteFormMixin(forms.Form):
    note = forms.CharField()
person Patrick Altman    schedule 20.08.2011
comment
Есть ли способ сделать это, чтобы я мог использовать миксин как в классах form.ModelForm, так и в классах form.Form? - person Jordan Reiter; 22.08.2011
comment
Да, ты можешь это сделать. Пусть ваш миксин наследуется от объекта и динамически добавляет все поля в метод init миксина. Затем в методе init класса вашей реальной формы вызовите YourMixinClass.__init__(self, *args, **kwargs) - person mbrochh; 11.10.2011
comment
Обратите внимание, что это, вероятно, сделает его бесполезным в интерфейсе администратора. - person Izz ad-Din Ruhulessin; 17.04.2012
comment
Кажется, @JordanReiter's больше не проблема. Более новые версии Django позволяют использовать form.Form в миксине для подкласса form.ModelForm. Я делаю это прямо сейчас, и это работает именно так, как ожидалось. - person coredumperror; 18.05.2016

Решение Патрика Альтмана верно только для обычных форм. Если вы попробуете это с ModelForm, вы застрянете либо с конфликтами метаклассов, либо с отсутствием некоторых полей.

Самое простое и короткое решение, которое я нашел, было в приложении к тикету №7018 Django - спасибо, bear330 :о)

Вам понадобиться:

from django.forms.forms import get_declared_fields
. . .

class ParentsIncludedModelFormMetaclass(ModelFormMetaclass):
    """
        Thanks to bear330 - taken from https://code.djangoproject.com/attachment/ticket/7018/metaforms.py
    """

    def __new__(cls, name, bases, attrs):
        # We store attrs as ModelFormMetaclass.__new__ clears all fields from it
        attrs_copy = attrs.copy()
        new_class = super(ParentsIncludedModelFormMetaclass, cls).__new__(cls, name, bases, attrs)
        # All declared fields + model fields from parent classes
        fields_without_current_model = get_declared_fields(bases, attrs_copy, True)
        new_class.base_fields.update(fields_without_current_model)
        return new_class


def get_next_in_mro(current_class, class_to_find):
    """
        Small util - used to call get the next class in the MRO chain of the class
        You'll need this in your Mixins if you want to override a standard ModelForm method
    """
    mro = current_class.__mro__
    try:
        class_index = mro.index(class_to_find)
        return mro[class_index+1]
    except ValueError:
        raise TypeError('Could not find class %s in MRO of class %s' % (class_to_find.__name__, current_class.__name__))

Затем вы определяете свой миксин как обычную ModelForm, но без объявления Meta:

from django import forms
class ModelFormMixin(forms.ModelForm):

    field_in_mixin = forms.CharField(required=True, max_length=100, label=u"Field in mixin")
    . . .

    # if you need special logic in your __init__ override as usual, but make sure to
    # use get_next_in_mro() instead of super()
    def __init__(self, *args, **kwargs):
        #
        result = get_next_in_mro(self.__class__, ModelFormMixin).__init__(self, *args, **kwargs)

        # do your specific initializations - you have access to self.fields and all the usual stuff
        print "ModelFormMixin.__init__"

        return result

    def clean(self):
        result = get_next_in_mro(self.__class__, ModelFormMixin).clean(self)

        # do your specific cleaning
        print "ModelFormMixin.clean"

        return result

И, наконец, финальная ModelForm, повторно использующая возможности ModelFormMixin. Вы должны определить Meta и все обычные вещи. В окончательных формах вы можете просто вызвать super(...) при переопределении методов (см. ниже).

ПРИМЕЧАНИЕ. Окончательная форма должна иметь метакласс ParentsIncludedModelFormMetaclass.

ПРИМЕЧАНИЕ. Порядок классов важен — сначала поместите миксин, а затем ModelFrom.

class FinalModelForm(ModelFormMixin, forms.ModelForm):
    """
        The concrete form.
    """
    __metaclass__ = ParentsIncludedModelFormMetaclass

    class Meta:
        model = SomeModel

    field_in_final_form = forms.CharField(required=True, max_length=100, label=u"Field in final form")

    def clean(self):
        result = super(FinalModelForm, self).clean()

        # do your specific cleaning
        print "FinalModelForm.clean"

        return result

Имейте в виду, что это работает, только если оба класса являются ModelForms. Если вы попытаетесь смешивать и сочетать Form и ModelFrom с помощью этой техники, это будет совсем не красиво :о)

person Boris Chervenkov    schedule 20.05.2012

Просто внес некоторую ясность в ответ @Adam Dobrawy, который мне помог:

Это не работает:

class NoteFormMixin(object):
    note = forms.CharField()

Это делает:

class NoteFormMixin(object):
    def __init__(self, *args, **kwargs):
        super(NoteFormMixin, self).__init__(*args, **kwargs)
        self.fields['note'] = forms.CharField()

Это поведение, вероятно, связано с тем, как django собирает поля во время создания экземпляра класса или что-то в этом роде. Я не удосужился вникнуть в это. Я только что обнаружил, что этот лакомый кусочек позволяет мне написать мой миксин в удобном для чтения виде, без какой-либо дополнительной грязи, специфичной для django-form.

person Aaron    schedule 07.02.2020
comment
спасибо! это должен быть ответ. выход из лишнего мусора, специфичного для формы django, - это путь. - person greenie-beans; 21.01.2021