DjangoのFormのcleanメソッドの呼び出し順

DjangoのFormでis_validが呼ばれたときの処理を読んでたのでメモ


まず、データが渡されたFormのインスタンスでis_validが呼ばれた場合、内部でfull_cleanがよばれる。


該当の箇所
django/forms/forms.py

    def _get_errors(self):
        "Returns an ErrorDict for the data provided for the form"
        if self._errors is None:
            self.full_clean()  
        return self._errors
    errors = property(_get_errors)

    def is_valid(self):
        """
        Returns True if the form has no errors. Otherwise, False. If errors are
        being ignored, returns False.
        """
        return self.is_bound and not bool(self.errors)


full_cleanは内部で_clean_fieldsを呼び、ここで各fieldのcleanメソッドが呼ばれる。
cleanが返した値はself.cleaned_dataに保存される

続いてclean_xxxxが定義されている場合は、呼び出しを行う。

full_cleanと_clean_fieldsの定義
django/forms/forms.py

    def full_clean(self):
        """
        Cleans all of self.data and populates self._errors and
        self.cleaned_data.
        """
        self._errors = ErrorDict()
        if not self.is_bound: # Stop further processing.
            return
        self.cleaned_data = {}
        # If the form is permitted to be empty, and none of the form data has
        # changed from the initial data, short circuit any validation.
        if self.empty_permitted and not self.has_changed():
            return
        self._clean_fields() 
        self._clean_form()
        self._post_clean()

    def _clean_fields(self):
        for name, field in self.fields.items():
            # value_from_datadict() gets the data from the data dictionaries.
            # Each widget type knows how to retrieve its own data, because some
            # widgets split data over several HTML fields.
            value = field.widget.value_from_datadict(self.data, self.files, self.add_prefix(name))
            try:
                if isinstance(field, FileField):
                    initial = self.initial.get(name, field.initial)
                    value = field.clean(value, initial)
                else:
                    value = field.clean(value)
                self.cleaned_data[name] = value
                if hasattr(self, 'clean_%s' % name):
                    value = getattr(self, 'clean_%s' % name)()
                    self.cleaned_data[name] = value
            except ValidationError as e:
                self._errors[name] = self.error_class(e.messages)
                if name in self.cleaned_data:
                    del self.cleaned_data[name]


次にfull_cleanから_clean_formが呼ばれる
ここで呼ばれるのがformのcleanメソッド。
エラーはNON_FIELD_ERRORSとしてセットされる。

django/forms/forms.py

    def _clean_form(self):
        try:
            self.cleaned_data = self.clean()
        except ValidationError as e:
            self._errors[NON_FIELD_ERRORS] = self.error_class(e.messages)


最後にfull_cleanから_post_cleanが呼ばれる。
ModelFormである場合は、Modelに実装したcleanメソッドが呼ばれるが、forms.Formの場合は何もしない

_post_cleanの定義
django/forms/forms.py

    def _post_clean(self):
        """
        An internal hook for performing additional cleaning after form cleaning
        is complete. Used for model validation in model forms.
        """
        pass


printデバッグで呼び出し順をみてみる

class SampleFormBase(forms.Form):

    def is_valid(self):
        print "is_valid"
        return super(SampleFormBase, self).is_valid()

    def full_clean(self):
        print "\tfull_clean"
        super(SampleFormBase, self).full_clean()

    def _clean_fields(self):
        print "\t\t_clean_fields"
        super(SampleFormBase, self)._clean_fields()

    def _clean_form(self):
        print "\t\t_clean_form"
        super(SampleFormBase, self)._clean_form()

    def _post_clean(self):
        print "\t\t_post_clean"
        super(SampleFormBase, self)._post_clean()


class SampleField(forms.CharField):
    def clean(self, value):
        print "\t\t\tSampleField.clean value={0}".format(value)
        return super(SampleField, self).clean(value)


class SampleForm(SampleFormBase):
    param1 = SampleField()
    param2 = SampleField()

    def clean_param1(self):
        print "\t\t\tSampleForm clean_param1"
        return self.cleaned_data['param1']

    def clean_param2(self):
        print "\t\t\tSampleFormclean_param2"
        return self.cleaned_data['param2']

    def clean(self):
        print "\t\t\tSampleForm clean"
        return self.cleaned_data
# is_boundでないのでcleanは呼ばれない
>>> f = SampleForm()
>>> f.is_valid()
False

# is_boundの場合
>>> f = SampleForm({'param1': 'param1', 'param2': 'param2'})
>>> f.is_valid()
is_valid
	full_clean
		_clean_fields
			SampleField.clean value=param1
			SampleForm clean_param1
			SampleField.clean value=param2
			SampleFormclean_param2
		_clean_form
			SampleForm clean
		_post_clean
True