How to delete form in fomset

I have inline formset:

JustificationFormset = inlineformset_factory(
    ClpPcn, Justification, form = JustificationForm,
    extra=0,
    validate_min=True,
    can_delete = True, can_delete_extra = True,
)

I am trying to delete forms marked as deleted in my view:

    def formset_valid(self, formset):
        justifications = formset.save(commit=False)
        for obj in formset.deleted_objects:
            obj.delete()
        for justification in justifications:
            justification.dossier = self.object
            justification.save()

The formset is created this way in my view:

    def get_formset(self):
        if self.request.method == "GET":
            return JustificationFormset(prefix='justification')
        else:
            return JustificationFormset(self.request.POST or None, prefix='justification')

However forms marked as DELETE are not deleted, they are still present in the formset. What am I doing wrong?

This is correct - this is how formsets work. The purpose of the delete flag in the formset is to allow the backend to delete the underlying data in the model associated with that form. The actual form is not to be deleted.

This is referenced and documented at:

From the documentation:

If you call formset.save(commit=False), objects will not be deleted automatically. You’ll need to call delete() on each of the formset.deleted_objects to actually delete them:

I thought that’s what I am doing in my view:

    def formset_valid(self, formset):
        justifications = formset.save(commit=False)
        for obj in formset.deleted_objects:
            obj.delete()
        for justification in justifications:
            justification.dossier = self.object
            justification.save()

Yes, that deletes the objects referenced by the form, but it does not delete the form itself.

I don’t understand. How can I delete the form then?

You don’t.

I’m not sure I’m understanding what your underlying objective is here, or why you think this is either necessary or desirable,

Everytime you’re creating the instance of the formset, you’re creating a new set of forms. It’s not like these forms persist across multiple view invocations.

How can I delete data saved in DELETED form? They are still in my database.

It should delete data from database?

Yes.

I think we may need to see the entire view at this point.

class ClpPcnEditParentView():
    model = ClpPcn
    form_class = ClpPcnForm
    template_name = 'clp_pcn_form.html'

    def form_valid(self, form):
        self.object = form.save(commit=False)

        # validate formset
        formset = self.get_formset()
        if not formset.is_valid():
            return self.render_to_response(self.get_context_data(form=form, justification=formset))

        # save object to db
        self.object.save()
        form.save_m2m()

        # save formset
        self.formset_valid(formset)

        return redirect(self.get_success_url())

    def formset_valid(self, formset):
        justifications = formset.save(commit=False)
        for obj in formset.deleted_objects:
            obj.delete()
        for justification in justifications:
            justification.dossier = self.object
            justification.save()

    def form_invalid(self, form):
        return self.render_to_response(self.get_context_data(form=form))

class ClpPcnCreateView(ClpPcnEditParentView, CreateView):
    def get_context_data(self, **kwargs):
        data = super(ClpPcnCreateView, self).get_context_data(**kwargs)
        if 'justification' not in data.keys():
            data['justification'] = self.get_formset()
        data['is_update'] = 0
        return data

    def get_formset(self):
        if self.request.method == "GET":
            return JustificationFormset(prefix='justification')
        else:
            return JustificationFormset(self.request.POST or None, prefix='justification')

    def get_success_url(self):
        return 'pcn:create-mixture'

Just making a guess here, but I’d be interested in determining whether the deletes are being issued and then the data is being resaved in the for justification in justifications loop, because you’re not checking to see if an instance of justification is to be deleted.

You’ve got a couple ways to test / verify this:

  • Add some print statements in formset_valid to show what’s being saved or deleted
  • Swap the order of the two for loops
  • Restructure your test such that you’re only looping over the forms once - if it’s in the formset.deleted_objects then delete it, otherwise, update and save it.

I have found out that the issue is in my UpdateView:

class ClpPcnUpdateView(ClpPcnEditParentView, UpdateView):
    def get_context_data(self, **kwargs):
        data = super(ClpPcnUpdateView, self).get_context_data(**kwargs)
        data['pcn_number'] = self.kwargs['pk']
        data['justification'] = self.get_formset()
        data['is_update'] = 1
        return data

    def get_formset(self):
        return JustificationFormset(self.request.POST or None, instance=self.object, prefix='justification')

    def get_success_url(self):
        return self.request.path_info

It seems that the problem is in get_formset(). When I try to save the formset it is invalid.

The problem was in my template. I use crispy forms and the hidden_fields were missing:

               {% for jform in justification.forms %}
                  {{ jform.management_form }}
                  <tr class="formset_row">
                     <td id="td_id_0">
                        {% crispy jform %}

So I added the hidden_fields and now it is working:

               {% for jform in justification.forms %}
                  {{ jform.management_form }}
                  <tr class="formset_row">
                     <td id="td_id_0">
                        {% for hidden in jform.hidden_fields %}
                           {{ hidden }}
                        {% endfor %}
                        {% crispy jform %}

Thank you for your time and navigating me!