Django数据聚合函数annotate
统计各个分类下的⽂章数
2 周,
3 ⽇前字数 3818 阅读 546 评论 21
在我们的博客侧边栏有分类列表,显⽰博客已有的全部⽂章分类。现在想在分类名后显⽰该分类下有多少篇⽂章,该怎么做呢?最优雅的⽅式就是使⽤ Django 模型管理器的annotate⽅法。
模型回顾
回顾⼀下我们的模型代码,Django 博客有⼀个 Post 和 Category 模型,分别表⽰⽂章和分类:
blog/models.py
class Post(models.Model):
title = models.CharField(max_length=70)
body = models.TextField()
category = models.ForeignKey('Category')
# 其它属性...
def __str__(self):
return self.title
class Category(models.Model):
name = models.CharField(max_length=100)
我们知道从数据库取数据都是使⽤模型管理器 objects 的⽅法实现的。⽐如获取全部分类是:Category.objects.all(),假设有⼀个名为 test 的分类,那么获取该分类的⽅法是:(name='test')。objects 除了all、get等⽅法外,还有很多操作数据库的⽅法,⽽其中有⼀
个annotate⽅法,该⽅法正可以帮我们实现本⽂所关注的统计分类下的⽂章数量的功能。
数据库数据聚合
annotate⽅法在底层调⽤了数据库的数据聚合函数,下⾯使⽤⼀个实际的数据库表来帮助我们理解annotate⽅法的⼯作原理。在 Post 模型中我们通过 ForeignKey 把 Post 和 Category 关联了起来,这时候它们的数据库表结构就像下⾯这样:
Post 表:
id title body category_id
1post 1 (1)
2post 2 (1)
django前端模板3post 3 (1)
4post 4 (2)
Category 表:
name id
category 11
category 22
这⾥前 3 篇⽂章属于 category 1,第 4 篇⽂章属于 category 2。
当 Django 要查询某篇 post 对应的分类时,⽐如 post 1,⾸先查询到它分类的 id 为 1,然后 Django 再去 Category 表到 id 为 1 的那⼀⾏,这⼀⾏就是 post 1 对应的分类。反过来,如果要查询 category 1 对应的全部⽂章呢?category 1 在 Category 表中对应的 id 是
1,Django 就在 Post 表中搜索哪些⾏的 category_id 为 1,发现前 3 ⾏都是,把这些⾏取出来就是 category 1 下的全部⽂章了。同理,这⾥annotate做的事情就是把全部 Category 取出来,然后去 Post 查询每⼀个 Category 对应的⽂章,查询完成后只需算⼀下每个 category id 对应有多少⾏记录,这样就可以统计出每个 Category 下有多少篇⽂章了。把这个统计数字保存到每⼀条 Category 的记录就可以了(当然并⾮保存到数据库,在 Django ORM 中是保存到 Category 的实例的属性中,每个实例对应⼀条记录)。
使⽤ Annotate
以上是原理⽅⾯的分析,具体到 Django 中该如何⽤呢?在我们的博客中,获取侧边栏的分类列表的⽅法写在模板标签get_categories⾥,因此我们修改⼀下这个函数,具体代码如下:
blog/templatetags/blog_tags.py
from dels.aggregates import Count
dels import Category
@register.simple_tag
def get_categories():
# 记得在顶部引⼊ count 函数
# Count 计算分类下的⽂章数,其接受的参数为需要计数的模型的名称
return Category.objects.annotate(num_posts=Count('post')).filter(num_posts__gt=0)
这个Category.objects.annotate⽅法和Category.objects.all有点类似,它会返回数据库中全部 Category 的记录,但同时它还会做⼀些额外的事情,在这⾥我们希望它做的额外事情就是去统计返回的 Category 记录的集合中每条记录下的⽂章数。代码中的Count⽅法为我们做了这个事,它接收⼀个和 Categoty 相关联的模型参数名(这⾥是Post,通过 ForeignKey 关联的),然后它便会统计 Category 记录的集合中每条记录下的与之关联的 Post 记录的⾏数,也就是⽂章数,最后把这个值保存到num_posts属性中。
此外,我们还对结果集做了⼀个过滤,使⽤filter⽅法把num_posts的值⼩于 1 的分类过滤掉。因为num_posts的值⼩于 1 表⽰该分类下没有⽂章,没有⽂章的分类我们不希望它在页⾯中显⽰。关于filter函数以及查询表达式(双下划线)在之前已经讲过,具体请参考。
在模板中引⽤新增的属性
现在在 Category 列表中每⼀项都新增了⼀个num_posts属性记录该 Category 下的⽂章数量,我们就可以在模板中引⽤这个属性来显⽰分类下的⽂章数量了。
templates/base.html
<ul>
{% for category in category_list %}
<li>
<a href="{% url 'blog:category' category.pk %}">{{ category.name }}
<span class="post-count">({{ category.num_posts }})</span>
</a>
</li>
{% empty %}
暂⽆分类!
{% endfor %}
</ul>
也就是在模板中通过模板变量 {{ category.num_posts }} 显⽰num_posts的值。开启开发服务器,可以看到分类名后正确地显⽰了该分类下的⽂章数了,⽽没有⽂章分类则不会在分类列表中出现。
将 Annotate ⽤于其它关联关系
此外,annotate⽅法不局限于⽤于本⽂提到的统计分类下的⽂章数,你也可以举⼀反三,只要是两个 model 类通过 ForeignKey 或者ManyToMany 关联起来,那么就可以使⽤ annotate ⽅法来统计数量。⽐如下⾯这样⼀个标签系统:
class Post(models.Model):
title = models.CharField(max_length=70)
body = models.TextField()
Tags = models.ManyToMany('Tag')
def __str__(self):
return self.title
class Tag(models.Model):
name = models.CharField(max_length=100)
统计标签下的⽂章数:
from dels.aggregates import Count
dels import Tag
# Count 计算分类下的⽂章数,其接受的参数为需要计数的模型的名称
tag_list = Tag.objects.annotate(num_posts=Count('post'))
关于 annotate ⽅法官⽅⽂档的说明在这⾥:。同时也建议了解了解 objects 下的其它操作数据库的⽅法,以便在遇到相关问题时知道去哪⾥查阅。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。