【问题标题】:Using a mixin with a Django form class将 mixin 与 Django 表单类一起使用
【发布时间】:2011-10-30 04:37:09
【问题描述】:

我正在考虑创建一个 mixin 表单类,以便可以将一组通用字段添加到各种不同的表单中。仅将其用作基类是行不通的,因为我希望能够使用其他形式作为基类,如下所示:

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

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

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

我唯一的问题是:这是如何工作的?到目前为止,如果我使用 mixin,那么它就无法识别从该 mixin 设置的字段:

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

这只是不能做的事情吗?

【问题讨论】:

  • 请注意你的继承层次是错误的。在 Python 中,基类是最正确的,而 Mixin 类应该在此之前。假设您希望SpecialForm 基于MainForm,正确的顺序是SpecialForm(NoteFormMixin, MainForm)

标签: django django-forms mixins


【解决方案1】:

问题是您的 NoteFormMixin 是从 object 而不是 forms.Form 派生的。你需要把它改成这样:

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

【讨论】:

  • 有什么办法可以在form.ModelFormform.Form 类中使用mixin?
  • 是的,您可以这样做。让您的 Mixin 从对象派生并在 Mixin 的 init 方法中动态添加所有字段。然后,在您的真实表单类的 init 方法中调用 YourMixinClass.__init__(self, *args, **kwargs)
  • 请注意,这可能会使它在管理界面中无用。
  • 看来@JordanReiter's 不再是问题了。较新版本的 Django 允许您在 form.ModelForm 子类的 mixin 中使用 form.Form。我现在正在这样做,它的工作原理完全符合预期。
【解决方案2】:

Patrick Altman 的解决方案仅适用于 常规 表单 - 如果您尝试使用 ModelForm 进行此操作,您将遇到元类冲突或缺少某些字段。

我发现的最简单和最短的解决方案是 Django 的附件ticket #7018 - 谢谢,bear330 :o)

你需要:

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__))

然后你将你的 mixin 定义为一个普通的 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 设置为元类

注意:类的顺序很重要 - 将 mixin 放在首位,然后是 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,它根本不会漂亮:o)

【讨论】:

    【解决方案3】:
    class TextFormMixin(object):
        def __init__(self, *args, **kwargs):
            super(TextFormMixin, self).__init__(*args, **kwargs)
            self.fields['text'] = forms.CharField(widget=forms.Textarea, required=True)
    
        def clean_text(self):
            if not ('{{EMAIL}}' in self.cleaned_data.get('text', '')):
                raise ValidationError("You have to put {{EMAIL}} in message body.")
            return self.cleaned_data.get('text', '')
    
        def get_text(self):
            return self.cleaned_dat['text'].replace('{{EMAIL}}', self.case.get_email())
    
    
    class NewCaseForm(TextFormMixin, forms.ModelForm):
        pass
    class ReplyForm(TextFormMixin, forms.Form):
        to = forms.CharField(max_length=50)
        subject = forms.CharField(max_length=50)
    

    【讨论】:

      【解决方案4】:

      只是对@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 在类实例化期间如何收集字段或其他东西有关.. 我没有费心去研究它。我刚刚发现这个花絮可以让我以可读性好的方式编写我的 mixin,而无需任何额外的 django-form-specific crud。

      【讨论】:

      • 谢谢!这应该是答案。省略“额外的 django-form-specific crud”是要走的路。
      猜你喜欢
      • 2015-05-09
      • 1970-01-01
      • 2017-10-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-06
      • 2017-10-24
      相关资源
      最近更新 更多