Search code examples
pythondjangodjango-orminline-formset

Django Many To Many Management with inline formset


I can't seem to find a good way to solve this situation. I have two models Office and People with a many to many relationship through a Contact model with additional fields.

Now in my views (CreateView and UpdateView), I am using inline formset to manage the relationship.

My problem here is with UpdateView, how do I update the many to many relationship? I can add new items. But how to delete existing ones? The formset generates a checkbox DELETE but I get lost in the code. How to use it?

One way could be to delete all the corresponding rows in the through model and recreates new ones with data submitted from the form but I believe there should be a more efficient way to do it.

Can someone help?

Here's my current code:

def post(self, request, *args, **kwargs):
    self.object = self.get_object()
    form_class = self.get_form_class()
    form = self.get_form(form_class)
    formset = OfficeContactInline(request.POST, instance=self.object)        
    if form.is_valid() and formset.is_valid():
        self.object = form.save()
        contacts = formset.save(commit=False)
        for contact in contacts:
            contact.office = self.object
            contact.save()
        formset.save_m2m()
        return HttpResponseRedirect(self.get_success_url())
    else:
        return render(request, self.template_name, self.get_context_data(form=form, formset=formset))

Solution

  • I finally found a solution to my problem. There actually is a change in behavior with Django 1.7: formset.save(commit=False) no longer deletes deleted items (checkbox checked). You therefore have to use formset.deleted_objects to do it: working code below

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        form_class = self.get_form_class()
        form = self.get_form(form_class)
        formset = OfficeContactInline(request.POST, instance=self.object)        
        if form.is_valid() and formset.is_valid():
            self.object = form.save()
            contacts = formset.save(commit=False)
            # New with Django 1.7
            for del_contact in formset.deleted_objects:
                del_contact.delete()
    
            for contact in contacts:
                contact.office = self.object
                contact.save()
            formset.save_m2m()
            return HttpResponseRedirect(self.get_success_url())
        else:
            return render(request, self.template_name, self.get_context_data(form=form, formset=formset))
    

    This is documented here: https://docs.djangoproject.com/en/1.7/topics/forms/formsets/#django.forms.formsets.BaseFormSet.can_delete