Django 多对多信号?

5 投票
3 回答
4681 浏览
提问于 2025-04-15 17:20

假设我有这样一个模型

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

如果事件添加或删除了一些用户,你会建议怎么更新用户数量的值呢?

3 个回答

2

重写 save() 方法可能帮不了你,因为对多对多关系的更新不是原子操作,它发生在 Event 实例保存之后(我还没研究过 delete() 的具体情况,但应该也差不多)。这个问题在 另一个讨论串中有提到。

大家都在讨论这个问题,并且 在努力解决它。我看到的最好的解决方案是 gregoirecachet 提出的这个 MonkeyPatch。我不知道这个解决方案是否会被纳入 1.2 版本。可能不会,因为发布经理(James Bennett)正在努力让大家遵守冻结日期(一个重要的日期刚刚过去)。

4

我解决了这个问题,使用了内置的信号django.db.models.signals.m2m_changed

在我的情况下,每次多对多关系发生变化时,我都需要更新另一个模型的相关实例。正如你所知道的,重写Model.save()方法并不能解决这个问题。

这里是我的(法语和简化版的)模型:

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)

也许这个解决方案并不是最理想的,但我真的不喜欢对Django进行“猴子补丁”!

8

如果可以的话,你可以引入一个叫做 Participation 的模型,这个模型可以把活动(Event)和用户(User)连接起来:

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

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

然后处理 Participation 发送的预保存信号,这样就可以更新 instance.event 的计数。这会大大简化多对多关系的处理。而且在大多数情况下,后来会发现一些逻辑和数据放在这个中间模型里是最合适的。如果你的情况不适合这样做,那就试试自定义的解决方案(反正你应该不会有太多的代码路径来把用户添加到活动中)。

撰写回答