Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Django manytomany signals? [duplicate]

Let's say I have such model

class Event(models.Model)
    users_count = models.IntegerField(default=0)
    users = models.ManyToManyField(User)

How would you recommend to update users_count value if Event add/delete some users ?

like image 268
Pydev UA Avatar asked Dec 24 '09 14:12

Pydev UA


3 Answers

If possible in your case, you could introduce Participation model which would join Event and User:

class Participation(models.Model):
    user = models.ForeignKey(User)
    event = models.ForeignKey(Event)

class Event(models.Model):
    users = models.ManyToManyField(User, through='Participation')

And handle pre_save signal sent by Participation to update instance.event counts. It would simplify handling of m2m significantly. And in most cases, it turns out later on that some logic and data fits best in the middle model. If that's not your case, try a custom solution (you should not have many code paths adding Users to Events anyway).

like image 87
kibitzer Avatar answered Nov 17 '22 19:11

kibitzer


I have a fixed the problem using the built-in signal django.db.models.signals.m2m_changed.

In my case, I have to update a related instance of another model each time the ManyToMany change and as you know, overriding Model.save() does not works.

Here my (french & simplified) models :

class BaseSupport(EuidModel):
    nom = models.CharField(max_length=100, blank=True)
    periodicite = models.CharField('périodicité', max_length=16,
                                   choices=PERIODICITE_CHOICES)
    jours_de_parution_semaine = models.ManyToManyField('JourDeLaSemaine', blank=True)

    class Meta:
        abstract = True


class Support(BaseSupport):
    pass

    def save(self, *args, **kwargs):
        create_cahier_principal = False
        if not self.pk:
            create_cahier_principal = True
        super(Support, self).save(*args, **kwargs) 
        if create_cahier_principal:
            c = Cahier.objects.create(support=self,ordre=1, numero=1,
                                      nom=self.nom, nom_court=self.nom_court,
                                      euid=self.euid, periodicite=self.periodicite)



class Cahier(BaseSupport):
    """Ex : Cahier Saumon du Figaro Quotidien."""
    support = models.ForeignKey('Support', related_name='cahiers')
    ordre = models.PositiveSmallIntegerField()
    numero = models.PositiveSmallIntegerField(u'numéro', null=True, blank=True)


def sync_m2m_cahier_principal(sender, **kwargs):
    if kwargs['action'] not in ('post_add', 'post_clear', 'post_remove'):
        return
    support = kwargs['instance']
    cahier_principal = support.cahiers.get(euid=support.euid)
    cahier_principal.jours_de_parution_semaine.clear()
    if kwargs['action'] == 'post_clear':
        return 
    for jour in support.jours_de_parution_semaine.all():
        cahier_principal.jours_de_parution_semaine.add(jour)
m2m_changed.connect(sync_m2m_cahier_principal,
                    sender=Support.jours_de_parution_semaine.through)

Maybe this solution is a far from ideal but I hate monkey-patching Django !

like image 29
Stan Avatar answered Nov 17 '22 20:11

Stan


Overriding save() may not help you because the update to the M2M is not atomic and happens after the save of the Event instance (I haven't studied the delete() semantics but they are probably similar). This was discussed in another thread.

People are talking about and working on this problem. The best solution I have seen so far is this MonkeyPatch by gregoirecachet. I don't know if this will make it into 1.2 or not. Probably not since the Release Manager (James Bennett) is trying to get people to respect the freeze dates (a major one just passed).

like image 43
Peter Rowell Avatar answered Nov 17 '22 20:11

Peter Rowell