Using a mixin with a Django form class

后端 未结 4 2014
眼角桃花
眼角桃花 2020-12-29 04:06

I\'m thinking about creating a mixin form class so that I can add a common set of fields to a variety of otherwise very different forms. Just using it as a base class won\'t

相关标签:
4条回答
  • 2020-12-29 04:29
    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)
    
    0 讨论(0)
  • 2020-12-29 04:47

    Just providing some clarity on @Adam Dobrawy's answer, which helped me:

    This doesn't work:

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

    This does:

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

    This behaviour is probably related to how django collects fields during class instantiation or something.. I haven't bothered to get into weeds of it. I just found this tidbit lets me write my mixin in nice readable way, without any extra django-form-specific crud.

    0 讨论(0)
  • 2020-12-29 04:48

    The issue is that your NoteFormMixin is deriving from object instead of forms.Form. You need to change it to be like so:

    class NoteFormMixin(forms.Form):
        note = forms.CharField()
    
    0 讨论(0)
  • 2020-12-29 04:50

    Patrick Altman's solution is only true with regular forms - if you try this with ModelForm's you'll get stuck either with metaclass conflicts or with some of the fields missing.

    The simplest and shortest solution I found was in an attachment to Django' ticket #7018 - thank you, bear330 :o)

    You'll need:

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

    Then you define your mixin as a usual ModelForm, but without declaring 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
    

    And, finally - the final ModelForm, reusing the features of ModelFormMixin. You should define Meta and all the usual stuff. In the final forms you can just call super(...) when you're overriding methods (see below).

    NOTE: The final form must have ParentsIncludedModelFormMetaclass set as a metaclass

    NOTE: The order of classes is important - put the mixin first, then the 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
    

    Keep in mind that this only works if both classes are ModelForms. If you try to mix and match Form and ModelFrom with this technique, it's not going to be pretty at all :o)

    0 讨论(0)
提交回复
热议问题