如何在Django中创建带ManyToMany关系的过滤注解?

1 投票
2 回答
2581 浏览
提问于 2025-04-16 14:02

我有一个模型,里面有文章、事件和人物:

class Person(models.Model):
  id = models.IntegerField(primary_key=True)
  name = models.CharField(max_length=30)

class Event(models.Model):
  id = models.IntegerField(primary_key=True)
  title = models.CharField(max_length=200)

class Article(models.Model):
  id = models.IntegerField(primary_key=True)
  title = models.CharField(max_length=200)
  publishDate = models.DateTimeField(blank=True, null=True)
  event = models.ForeignKey(Event, blank=False, null=False)
  persons = models.ManyToManyField(Person)

一篇文章属于一个事件,而一个事件可以包含很多篇文章。人物出现在很多篇文章中,而文章也包含很多人物。我的想法是查看在特定时间段内,哪些事件被写得最多。我通过一次查询就实现了这一点:

topEvents = Article.objects.filter(publishDate__gt=dateStart)
                           .filter(publishDate__lt=dateEnd)
                           .values('event').annotate(count=Count('id'))
                           .order_by('-count')[:topN]

我发现这样做比在服务器端计算前N名要快很多。

现在,我的问题是,如何处理我和人物之间的多对多关系?另外,这样做是否是最好的方式?

2 个回答

0

你试过这个吗:

topUsers = Article.objects.filter(publishDate__gt=dateStart)
                          .filter(publishDate__lt=dateEnd)
                          .values('persons').annotate(count=Count('id'))
                          .order_by('-count')[:topN]
6

为了让你的例子更简单明了:

class Event(models.Model):
    title = models.CharField(max_length=200)

class Person(models.Model):
    name = models.CharField(max_length=30)

class Article(models.Model):
    title = models.CharField(max_length=200)
    publishDate = models.DateTimeField(blank=True, null=True)
    event = models.ForeignKey(Event, related_name='articles')
    persons = models.ManyToManyField(Person, related_name='articles')

注意,这里没有 id 字段,因为 Django 会自动为我们生成这些字段。此外,在 Article 类中,eventpersonsrelated_name 都设置为 'articles',这意味着我们可以通过 event.articlesperson.articles 来简单地引用文章。

现在,如果你想找出被提到最多的事件和人物,可以直接在它们的模型管理器上进行查询:

Event.objects.filter(articles__publishDate__gt=dateStart)\
             .filter(articles__publishDate__lt=dateEnd)\
             .annotate(count=Count('articles')).order_by('-count')

Person.objects.filter(articles__publishDate__gt=dateStart)\
              .filter(articles__publishDate__lt=dateEnd)\
              .annotate(count=Count('articles')).order_by('-count')

撰写回答