[Fixed]-Django – CreateView not saving form with nested formset

2👍

You aren’t currently processing the formset properly in your CreateView. form_valid in that view will only handle the parent form, not the formsets. What you should do is override the post method, and there you need to validate both the form and any formsets that are attached to it:

def post(self, request, *args, **kwargs):
    form = self.get_form()
    # Add as many formsets here as you want
    education_formset = EducationFormset(request.POST)
    # Now validate both the form and any formsets
    if form.is_valid() and education_formset.is_valid():
        # Note - we are passing the education_formset to form_valid. If you had more formsets
        # you would pass these as well.
        return self.form_valid(form, education_formset)
    else:
        return self.form_invalid(form)

Then you modify form_valid like so:

def form_valid(self, form, education_formset):
    with transaction.atomic():
        form.instance.employee.user = self.request.user
        self.object = form.save()
        # Now we process the education formset
        educations = education_formset.save(commit=False)
        for education in educations:
            education.instance = self.object
            education.save()
        # If you had more formsets, you would accept additional arguments and
        # process them as with the one above.
    # Don't call the super() method here - you will end up saving the form twice. Instead handle the redirect yourself.
    return HttpResponseRedirect(self.get_success_url())

They way you are currently using get_context_data() isn’t correct – remove that method completely. It should only be used to fetch context data for rendering a template. You shouldn’t call it from your form_valid() method. Instead you need to pass the formset to this method from the post() method as outlined above.

I’ve left a few additional comments in the sample code above which will hopefully help you figure this out.

2👍

Maybe you would like to see the package django-extra-views, the provides the view CreateWithInlinesView, witch allows you to create form with nested inlines like Django-admin inlines.

In your case, it would be something like that:

views.py

class EducationInline(InlineFormSetFactory):
    model = Education
    fields = ['course_title', 'institute_name']


class EmployeeCreateView(CreateWithInlinesView):
    model = Employee
    inlines = [EducationInline,]
    fields = ['about', 'street', 'city', 'cell_phone', 'landline']
    template_name = 'bars/crt.html'

crt.html

<form method="post">
  ...
  {{ form }}
  <table>
  {% for formset in inlines %}
    {{ formset.management_form }}
      {% for inline_form in formset %}
        <tr class="{% cycle 'row1' 'row2' %} formset_row-{{ formset.prefix }}">
          {{ inline_form }}
        </tr>
      {% endfor %}
  {% endfor %}
  </table>
  ...
  <input type="submit" value="Submit" />
</form>

<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js">
</script>
<script src="{% static 'js/jquery.formset.js' %}">
</script>
<script type="text/javascript">
    {% for formset in inlines %}
      $('.formset_row-{{ formset.prefix }}').formset({
          addText: 'add another',
          deleteText: 'remove',
          prefix: '{{ formset.prefix }}',
      });
    {% endfor %}
</script>

The view EmployeeCreateView will processing the forms for you as in Django-admin. From this point you can apply the style you want to the forms.

I recommend you visit the documentation for more information

EDITED: I added management_form and the js buttons to add/remove.

👤John

0👍

You said that there is an error but you are not showing it in your question. The error (and the whole traceback) is more important than anything you wrote (except may be from forms.py and views.py)

Your case is a little bit trickier because of the formsets and using multiple forms on the same CreateView. There are not many (or not many good ) examples on the internet. Untill you dig in the django code how inline formsets are working you will have troubles.

Ok straight to the point. Your problem is that formsets are not initialized with the same instance as your main form. And when your amin form saves the data to the database the instance in the formset is not changed and at the end you do not have the ID of the main object in order to be put as foreign key. Changing the instance attribute of a form attribute after init is not a good idea.

In normal forms if you chnage it after is_valid you will have unpredictable results.
For formsets changing instance attribute even directly after init will do nothig, becase the forms in the formset are already initialized with some instance, and changing it after will not help.
The good news is that you can change attributes of the instance after Formset is initialized, because all forms’s instance attributes will point to the same object after formset is initialized.

You have two options:

Instead of setting instance attribute if the formset, set only the instance.pk. (This just a guessing I have never do it but I think it should work. The problem is that it will look as hack).
Create a form that will initialize all forms/formsets at once. When it’s is_valid() method is called all fomrs should be validated. When it’s save() method is called all forms must be saved. Then you need to set form_class attribute of your CreateView to that form class. The only tricky part is that after your main form is initialized you need to initialize the others (formsests) with the instance of your first form. Also you need to set the forms/formsets as attributes of your form in order to have access to them in the template.
I’m using the second approach when I need to create an object with all it’s related objects. This will separate business logic from view logic because from the view perspective you just have a form which can be:

initialized with some data (in this case POST data)
checked for validity with is_valid()
can be saved with save() when it’s valid.
You preserve the form interface and if you made your form correctly you can even use it not only for creating but for updating objects together with their related objects and the views will be very simple.

Leave a comment