Search code examples
pythondjangodjango-modelsmany-to-many

save() saves all fields except ManyToMany field


I have a model "Contest" with one m2m field called "teams" which is related to a model "Team". I overrided the method save. In my save() function (the one that's overriding), I need a queryset (in my save overrinding function) with all the objects related to my team m2m field. The code is self.teams.all() but it won't work because my models is not yet registered in database right ? So I call super().save(*args, **kwargs) now my models is saved and I can get my queryset ? I can't. The queryset is empty, even if I registered team(s). <QuerySet []> Why does super.save() save immediately all the fields except the m2m ? I use exclusively the django admin web site to create models. No manage.py shell or form.

My model :

class Contest(models.Model):
name = models.CharField(max_length=16, primary_key=True, unique=True, default="InactiveContest", blank=True)  # Ex: PSGvMNC_09/2017
id = models.IntegerField(default=1)
teams = models.ManyToManyField(Team, verbose_name="opposants")
date = models.DateTimeField(blank=True)
winner = models.ForeignKey(Team, verbose_name='gagnant', related_name='winner', on_delete=models.SET_NULL, blank=True, null=True)
loser = models.ForeignKey(Team, verbose_name='perdant', related_name='loser', on_delete=models.SET_NULL, blank=True, null=True)
bet = models.IntegerField(verbose_name='Nombre de paris', default=0, blank=True, null=0)
active = models.BooleanField(default=False)

def save(self, *args, **kwargs):
    if self._state.adding:
        self.active = False
        # Django's id field immitation
        last_id = Contest.objects.all().aggregate(Max('id')).get('id__max')
        if last_id is not None:
            self.id = last_id + 1
    super().save(*args, **kwargs)
print(Contest.objects.get(id=self.id)) # Works --> model saved in db... in theory
    queryset = self.teams.all() # Empty all the time !

Once save() (the overrinding one) has been executed one time, the problem is solved and next times (modifications) I can get my queryset so I could just use self._state.adding but this method obliges me to save 2 times (creation, editing). I need to understand why super().save(*args, **kwargs)behaves like this and how can I solve this ?


Solution

  • The reason is that implementation of ManyToManyField does not use the database table of the model. It uses third table for connection between two models.

    Each model has its own database table. In your example

    model Contest -> table app_contest
    model Team   -> table app_team
    

    However, app_contest does not have field teams in it. And app_team does not contain field contest.

    Instead, there is a third table e.g. called app_contest_team that consists of three columns:

    • id
    • contest_id
    • team_id

    And stores pairs of contest_id + team_id that represent connection between contests and teams.

    So queryset = self.teams.all() is equal to the SQL expression:

    SELECT * FROM app_teams WHERE id in 
        (SELECT team_id from app_contest_teams WHERE contest_id = '{self.id}');
    

    Thus, you can see that you cannot anyhow manipulate ManyToManyField without having an instance initially saved in database. Because only after inserting into table app_contest it receives unique ID which can be later used for creating entries in the third table app_contest_teams.

    And also because of that - save() is not required when you add new object to ManyToManyField because table of the model is not affected