How to make a filtering annotation with ManyToMany relation in Django?
I have a model with articles, events and people:
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)
An article belongs to an event, and events consist of many articles. Persons appear in many articles and articles contain many persons. The idea is to see which events were most written about inside a given time interval. I did this in a single query:
topEvents = Article.objects.filter(publishDate__gt=dateStart)
.filter(publishDate__lt=dateEnd)
.values('event').annotate(count=Count('id'))
.order_by('-count')[:topN]
I found that 开发者_开发知识库this takes significantly less time than calculating the top N server-side.
Now, the question is, how do I do the same with the ManyToMany relation I have with the Persons? Also, is this the best way to do this?
To simplify your example:
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')
Note that there are no id
fields as Django creates them automatically. Also, both event
and persons
on the Article
class have related_name
set to 'articles' which allows us to refer to articles simply by using event.articles
and person.articles
.
Now, to get the events and people that are written about most often, you can query directly on their model managers:
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')
Have you tried:
topUsers = Article.objects.filter(publishDate__gt=dateStart)
.filter(publishDate__lt=dateEnd)
.values('persons').annotate(count=Count('id'))
.order_by('-count')[:topN]
精彩评论