Django model form saves m2m after instance

806 Views Asked by At

I am having an issue with the way Django class-based forms save a form. I am using a form.ModelForm for one of my models which has some many-to-many relationships.

In the model's save method I check the value of some of these relationships to modify other attributes:

class MyModel(models.Model):
  def save(self, *args, **kwargs):
    if self.m2m_relationship.exists():
      self.some_attribute = False
    super(MyModel, self).save(*args, **kwargs)

Even if I populated some data in the m2m relationship in my form, I self.m2m_relationship when saving the model and surprisingly it was an empty QuerySet. I eventually found out the following:

The form.save() method is called to save the form, it belongs to the BaseModelForm class. Then this method returns save_instance, a function in forms\models.py. This function defines a local function save_m2m() which saves many-to-many relationships in a form.

Here's the thing, check out the order save_instance chooses when saving and instance and m2m:

instance.save()
save_m2m()

Obviously the issue is here. The instance's save method is called first, that's why self.m2m_relationship was an empty QuerySet. It just doesn't exist yet.

What can I do about it? I can't just change the order in the save_instance function because it is part of Django and I might break something else.

2

There are 2 best solutions below

7
RemcoGerlich On BEST ANSWER

Daniel's answer gives the reason for this behaviour, you won't be able to fix it.

But there is the m2m_changed signal that is sent whenever something changes about the m2m relationship, and maybe you can use that:

from django.db.models import signals

@signals.receiver(signals.m2m_changed, sender=MyModel.m2m_relationship.through)
def handle_m2m_changed(sender, instance, action, **kwargs):
    if action == 'post_add':
        # Do your check here

But note the docs say that instance "can be an instance of the sender, or of the class the ManyToManyField is related to".

I don't know how that works exactly, but you can try out which you get and then adapt the code.

1
Daniel Roseman On

But it would be impossible to do it any other way.

A many-to-many relationship is not a field on the instance, it is an entry in a linking table. There is no possible way to save that relationship before the instance itself exists, as it won't have an ID to enter into that linking table.