Commit 121fd109 authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #5524 -- Do not remove cleaned_data when a form fails validation

cleaned_data is no longer deleted when form validation fails but only
contains the data that did validate.
Thanks to the various contributors to this patch (see ticket).
parent 10f979fd
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -317,7 +317,7 @@ class WizardTests(TestCase):

        class WizardWithProcessStep(TestWizardClass):
            def process_step(self, request, form, step):
                that.assertTrue(hasattr(form, 'cleaned_data'))
                that.assertTrue(form.is_valid())
                reached[0] = True

        wizard = WizardWithProcessStep([WizardPageOneForm,
+0 −2
Original line number Diff line number Diff line
@@ -271,8 +271,6 @@ class BaseForm(StrAndUnicode):
        self._clean_fields()
        self._clean_form()
        self._post_clean()
        if self._errors:
            del self.cleaned_data

    def _clean_fields(self):
        for name, field in self.fields.items():
+12 −13
Original line number Diff line number Diff line
@@ -506,7 +506,7 @@ class BaseModelFormSet(BaseFormSet):
        all_unique_checks = set()
        all_date_checks = set()
        for form in self.forms:
            if not hasattr(form, 'cleaned_data'):
            if not form.is_valid():
                continue
            exclude = form._get_validation_exclusions()
            unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
@@ -518,21 +518,21 @@ class BaseModelFormSet(BaseFormSet):
        for uclass, unique_check in all_unique_checks:
            seen_data = set()
            for form in self.forms:
                # if the form doesn't have cleaned_data then we ignore it,
                # it's already invalid
                if not hasattr(form, "cleaned_data"):
                if not form.is_valid():
                    continue
                # get data for each field of each of unique_check
                row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data])
                if row_data and not None in row_data:
                    # if we've aready seen it then we have a uniqueness failure
                    # if we've already seen it then we have a uniqueness failure
                    if row_data in seen_data:
                        # poke error messages into the right places and mark
                        # the form as invalid
                        errors.append(self.get_unique_error_message(unique_check))
                        form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
                        del form.cleaned_data
                        break
                        # remove the data from the cleaned_data dict since it was invalid
                        for field in unique_check:
                            if field in form.cleaned_data:
                                del form.cleaned_data[field]
                    # mark the data as seen
                    seen_data.add(row_data)
        # iterate over each of the date checks now
@@ -540,9 +540,7 @@ class BaseModelFormSet(BaseFormSet):
            seen_data = set()
            uclass, lookup, field, unique_for = date_check
            for form in self.forms:
                # if the form doesn't have cleaned_data then we ignore it,
                # it's already invalid
                if not hasattr(self, 'cleaned_data'):
                if not form.is_valid():
                    continue
                # see if we have data for both fields
                if (form.cleaned_data and form.cleaned_data[field] is not None
@@ -556,14 +554,15 @@ class BaseModelFormSet(BaseFormSet):
                    else:
                        date_data = (getattr(form.cleaned_data[unique_for], lookup),)
                    data = (form.cleaned_data[field],) + date_data
                    # if we've aready seen it then we have a uniqueness failure
                    # if we've already seen it then we have a uniqueness failure
                    if data in seen_data:
                        # poke error messages into the right places and mark
                        # the form as invalid
                        errors.append(self.get_date_error_message(date_check))
                        form._errors[NON_FIELD_ERRORS] = self.error_class([self.get_form_error()])
                        del form.cleaned_data
                        break
                        # remove the data from the cleaned_data dict since it was invalid
                        del form.cleaned_data[field]
                    # mark the data as seen
                    seen_data.add(data)
        if errors:
            raise ValidationError(errors)
+12 −9
Original line number Diff line number Diff line
@@ -199,8 +199,8 @@ Note that any text-based field -- such as ``CharField`` or ``EmailField`` --
always cleans the input into a Unicode string. We'll cover the encoding
implications later in this document.

If your data does *not* validate, your ``Form`` instance will not have a
``cleaned_data`` attribute::
If your data does *not* validate, the ``cleaned_data`` dictionary contains
only the valid fields::

    >>> data = {'subject': '',
    ...         'message': 'Hi there',
@@ -210,9 +210,12 @@ If your data does *not* validate, your ``Form`` instance will not have a
    >>> f.is_valid()
    False
    >>> f.cleaned_data
    Traceback (most recent call last):
    ...
    AttributeError: 'ContactForm' object has no attribute 'cleaned_data'
    {'cc_myself': True, 'message': u'Hi there'}

.. versionchanged:: 1.5

Until Django 1.5, the ``cleaned_data`` attribute wasn't defined at all when
the ``Form`` didn't validate.

``cleaned_data`` will always *only* contain a key for fields defined in the
``Form``, even if you pass extra data when you define the ``Form``. In this
@@ -232,9 +235,9 @@ but ``cleaned_data`` contains only the form's fields::
    >>> f.cleaned_data # Doesn't contain extra_field_1, etc.
    {'cc_myself': True, 'message': u'Hi there', 'sender': u'foo@example.com', 'subject': u'hello'}

``cleaned_data`` will include a key and value for *all* fields defined in the
``Form``, even if the data didn't include a value for fields that are not
required. In this example, the data dictionary doesn't include a value for the
When the ``Form`` is valid, ``cleaned_data`` will include a key and value for
*all* its fields, even if the data didn't include a value for some optional
fields. In this example, the data dictionary doesn't include a value for the
``nick_name`` field, but ``cleaned_data`` includes it, with an empty value::

    >>> class OptionalPersonForm(Form):
+6 −4
Original line number Diff line number Diff line
@@ -362,7 +362,9 @@ Secondly, once we have decided that the combined data in the two fields we are
considering aren't valid, we must remember to remove them from the
``cleaned_data``.

In fact, Django will currently completely wipe out the ``cleaned_data``
dictionary if there are any errors in the form. However, this behavior may
change in the future, so it's not a bad idea to clean up after yourself in the
first place.
.. versionchanged:: 1.5

Django used to remove the ``cleaned_data`` attribute entirely if there were
any errors in the form. Since version 1.5, ``cleaned_data`` is present even if
the form doesn't validate, but it contains only field values that did
validate.
Loading