Search code examples
pythondjangoformsetinline-formset

Where has cleaned_data vanished in Django 1.11?


I have created an inlineformset_factory as below :

formset = inlineformset_factory(Author, Book, form=BookForm,
                                formset=BaseBookFormSet,
                                can_order=False, can_delete=True,
                                extra=1, fields=('id', name)
                                )

BookForm is as below:

class BookForm(forms.ModelForm):
    name = forms.Charfield(required=True)

    def __init__(self, *args, **kwargs):
        super(BookForm, self).__init__(*args, **kwargs)
        self.helper = FormHelper()
        self.helper.form_tag = False
        self.helper.layout = Layout(
            Div(
                Field("id", type="hidden"),
                Field("name"),
                Field("DELETE")
                )
    )

    class Meta:
        model = Book
        fields = ('id', 'name')

    def clean_name(self):
        book_name = self.cleaned_data['name']
        try:
            book = Book.objects.get(name=book_name)
            return book
         except:
            return book_name

    def clean(self):
        cleaned_data = super(BookForm, self).clean()
        ... other operations on cleaned_data ...

    def has_changed(self):
        changed = super(BookForm, self).has_changed()
        cleaned_data = self.clean()
        ... other code here ...

This is throwing an error on submitting the form :

Exception Type: AttributeError
Exception Value: 'BookForm' object has no attribute 'cleaned_data'

when formset.is_valid() is called in views.py. Traceback first shows the line in has_changed where the self.clean is being called, and then the line in clean() where the super clean is being called.

This used to work fine in django 1.10.

When I tried printing dir(self) in Django 1.10 it does show 'cleaned_data' as one of the attributes where as in Django 1.11 it does not.

Where has the 'cleaned_data' vanished in Django 1.11?

EDIT: Adding traceback:

Traceback (most recent call last):
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
    response = self._get_response(request)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/views/generic/base.py", line 88, in dispatch
    return handler(request, *args, **kwargs)
  File "/vagrant/test_os/inventory/views.py", line 297, in post
    if formset.is_valid():
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/formsets.py", line 321, in is_valid
    self.errors
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/formsets.py", line 295, in errors
    self.full_clean()
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/formsets.py", line 345, in full_clean
    if not form.has_changed():
  File "/vagrant/test_os/inventory/forms.py", line 220, in has_changed
    cleaned_data = self.clean()
  File "/vagrant/test_os/inventory/forms.py", line 177, in clean
    cleaned_data = super(BookForm, self).clean()
  File "/home/vagrant/venv/local/lib/python2.7/site-packages/django/forms/models.py", line 344, in clean
    return self.cleaned_data
AttributeError: 'BookForm' object has no attribute 'cleaned_data'

Solution

  • Formsets were fixed in 1.11 (in #26844) to ignore empty forms when validating the minimum number of forms. As a side-effect, formsets now call form.has_changed() on each form before validating the form. Django expects form.has_changed() to be safe to call before the form is validated, and the default implementation is indeed safe to call.

    You have overridden form.has_changed() to call self.clean(), which now happens before the form is validated. Since form.clean() requires that the form is validated, this fails.

    Since form.full_clean() actually calls self.has_changed(), you can't simply validate the form from within form.has_changed(). You don't show what you do in has_changed(), but it would most likely be a good idea to put this code elsewhere.