Django: save(commit=False) on formset CBV is triggering customized model save() actions

I’m using CBV CreateView to display a couple of pages with formsets to the user.

When the model behind a given formset/CreateView is a common one (it will became clearer later), everything works fine using the following logic on the view:

class Create(CreateView):
    ...

    def form_valid(self, formset):
        instances = formset.save(commit=False)
        for instance in instances:
            instance.user = self.request.user
            instance.save()

        return super(Create, self).form_valid(formset)

However, on one of the models, I had to add extra actions to the model save() method. Namely, I need to create child objects when the parents are saved. Something like:

class Parent(models.Model):
    ...
        
    def save(self, *args, **kwargs):
        super().save(*args, **kwargs)
        self.child_set.create(...., *args, **kwargs)

In this particular case, the child object is being created twice and I believe that the formset.save(commit=False) is the culprit.

I tried replacing the child_set.create() for

        child = Child(...parameters, parent=self)
        child.save(*args, **kwargs)

But it yields the same result. How can I prevent that?

Answer

The .form_valid(…) method [Django-doc] of a CreateView [Django-doc], will call .save() on the form, and this will thus invoke a new round of saving all objects.

You can set the .user of the instances, and then let the CreateView save these instances. This thus means that you implement this as:

class Create(CreateView):
    # …

    def form_valid(self, formset):
        instances = formset.save(commit=False)
        for instance in instances:
            instance.user = self.request.user
            # no instance.save()
        #                  ↓ this will save the instances
        return super().form_valid(formset)

That being said, it might be better to work with a .get_or_create(…) [Django-doc] over a .create(…) [Django-doc], since now you will create a Child object each time you save the Parent object, which is likely not the intended effect.