在我们的博客侧边栏有分类列表,显示博客已有的全部文章分类。现在想在分类名后显示该分类下有多少篇文章,该怎么做呢?最优雅的方式就是使用 Django 模型管理器的annotate方法。

模型回顾

回顾一下我们的模型代码,Django博客有一个Article和Category模型,分别表示文章和分类:

blog/models.py

class Article(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 的分类,那么获取该分类的方法是:Category.objects.get(name='text') 。objects 除了 all , get 等方法外,还有很多操作数据库的方法,而其中有一个 annotate 方法,该方法正可以帮我们实现本文所关注的统计分类下的文章数量的功能。

数据库数据聚合

annotate 方法在底层调用了数据库的数据聚合函数,下面使用一个实际的数据库表来帮助我们理解 annotate 方法的工作原理。在 Article 模型中我们通过 ForeignKey 把 Article和 Category 关联了起来,这时候它们的数据库表结构就像下面这样:

Article 表:

id title body category_id
1 article 1 ... 1
2 article 2 ... 1
3 article 3 ... 1
4 article 4 ... 2

Category 表:

name id
category 1 1
category 2 2

这里前 3 篇文章属于 category 1,第 4 篇文章属于 category 2。

当 Django 要查询某篇 Article 对应的分类时,比如 article 1,首先查询到它分类的 id 为 1,然后 Django 再去 Category 表找到 id 为 1 的那一行,这一行就是 article 1 对应的分类。反过来,如果要查询 category 1 对应的全部文章呢?category 1 在 Category 表中对应的 id 是 1,Django 就在 Article 表中搜索哪些行的 category_id 为 1,发现前 3 行都是,把这些行取出来就是 category 1 下的全部文章了。同理,这里 annotate 做的事情就是把全部 Category 取出来,然后去 Article 查询每一个 Category 对应的文章,查询完成后只需算一下每个 category id 对应有多少行记录,这样就可以统计出每个 Category 下有多少篇文章了。把这个统计数字保存到每一条 Category 的记录就可以了(当然并非保存到数据库,在 Django ORM 中是保存到 Category 的实例的属性中,每个实例对应一条记录)。

使用 Annotate

以上是原理方面的分析,具体到 Django 中该如何用呢?在我们的博客中,获取侧边栏的分类列表的方法写在模板标签 get_categories 里,因此我们修改一下这个函数,具体代码如下:

blog/templatetags/blog_tags.py

from django.db.models.aggregates import Count
from blog.models import Category @register.simple_tag
def get_categories():
# 记得在顶部引入 count 函数
# Count 计算分类下的文章数,其接受的参数为需要计数的模型的名称
return Category.objects.annotate(num_articles=Count('article')).filter(num_articles__gt=0)

这个 Category.objects.annotate 方法和Category.objects.all 有点类似,它会返回数据库中全部 Category 的记录,但同时它还会做一些额外的事情,在这里我们希望它做的额外事情就是去统计返回的 Category 记录的集合中每条记录下的文章数。代码中的 Count 方法为我们做了这个事,它接收一个和 Categoty 相关联的模型参数名(这里是 Article,通过 ForeignKey 关联的),然后它便会统计 Category 记录的集合中每条记录下的与之关联的 Post 记录的行数,也就是文章数,最后把这个值保存到 num_articles 属性中。

此外,我们还对结果集做了一个过滤,使用 filter 方法把 num_articles 的值小于 1 的分类过滤掉。因为 num_articles 的值小于 1 表示该分类下没有文章,没有文章的分类我们不希望它在页面中显示。关于 filter 函数以及查询表达式(双下划线)在之前已经讲过,具体请参考分类与归档

在模板中引用新增的属性

现在在 Category 列表中每一项都新增了一个  num_articles 属性记录该 Category 下的文章数量,我们就可以在模板中引用这个属性来显示分类下的文章数量了。

templates/base.html

<ul>
{% for category in category_list %}
<li>
<a href="{% url 'blog:category' category.pk %}">{{ category.name }}
<span class="article-count">({{ category. num_articles }})</span>
</a>
</li>
{% empty %}
暂无分类!
{% endfor %}
</ul>

也就是在模板中通过模板变量 {{ category.num_articles }} 显示  num_articles 的值。开启开发服务器,可以看到分类名后正确地显示了该分类下的文章数了,而没有文章分类则不会在分类列表中出现。

将 Annotate 用于其它关联关系

此外,annotate 方法不局限于用于本文提到的统计分类下的文章数,你也可以举一反三,只要是两个 model 类通过 ForeignKey 或者 ManyToMany 关联起来,那么就可以使用 annotate 方法来统计数量。比如下面这样一个标签系统:

class Article(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 django.db.models.aggregates import Count
from blog.models import Tag # Count 计算分类下的文章数,其接受的参数为需要计数的模型的名称
tag_list = Tag.objects.annotate(num_posts=Count('post'))

关于 annotate 方法官方文档的说明在这里:annotate。同时也建议了解了解 objects 下的其它操作数据库的方法,以便在遇到相关问题时知道去哪里查阅。

Django之数据聚合函数 annotate的更多相关文章

  1. Django 数据聚合函数 annotate

    统计各个分类下的文章数 2 周,3 日前 字数 3818 阅读 546 评论 21 在我们的博客侧边栏有分类列表,显示博客已有的全部文章分类.现在想在分类名后显示该分类下有多少篇文章,该怎么做呢?最优 ...

  2. 七、django学习之聚合函数

    七.django学习之聚合函数 如何使用 from django.db.models import Avg,Max,Min,Sum models.Book.objects.all().aggregat ...

  3. django的聚合函数和aggregate、annotate方法使用

    支持聚合函数的方法: 提到聚合函数,首先我们要知道的就是这些聚合函数是不能在django中单独使用的,要想在django中使用这些聚合函数,就必须把这些聚合函数放到支持他们的方法内去执行.支持聚合函数 ...

  4. django ORM聚合函数

    在Django中,聚合函数是通过aggregate方法实现的,aggregate方法返回的结果是一个字典 在使用时需要先导入模块from django.db.models import Count,A ...

  5. $Django 聚合函数、分组查询、F,Q查询、orm字段以及参数

        一.聚合函数    from django.db.models import Avg,Sum,Max,Min,Count,F,Q   #导入    # .查询图书的总价,平均价,最大价,最小价 ...

  6. 72.Python中ORM聚合函数详解:Avg,aggregate,annotate

    聚合函数: 如果你用原生SQL语句,则可以使用聚合函数提取数据.比如提取某个商品销售的数量,那么就可以使用Count,如果想要知道销售的平均价格,那么就可以使用Avg. 聚合函数是通过aggregat ...

  7. Django(18)聚合函数

    前言 orm模型中的聚合函数跟MySQL中的聚合函数作用是一致的,也有像Sum.Avg.Count.Max.Min,接下来我们逐个介绍 聚合函数 所有的聚合函数都是放在django.db.models ...

  8. Django ORM 多对多操作 使用聚合函数和分组 F查询与Q查询

    创建表 # models.py form django.db import models class Book(models.Model): # 表名book,django会自动使用项目名+我们定义的 ...

  9. ORM中聚合函数、分组查询、Django开启事务、ORM中常用字段及参数、数据库查询优化

    聚合函数 名称 作用 Max() 最大值 Min() 最小值 Sum() 求和 Count() 计数 Avg() 平均值 关键字: aggregate 聚合查询通常都是配合分组一起使用的 关于数据库的 ...

随机推荐

  1. feed流拉取,读扩散,究竟是啥?

    from:https://mp.weixin.qq.com/s?__biz=MjM5ODYxMDA5OQ==&mid=2651961214&idx=1&sn=5e80ad6f2 ...

  2. 表单验证&lt;AngularJs&gt;

    经常使用的表单验证指令 1. 必填项验证 某个表单输入是否已填写,仅仅要在输入字段元素上加入HTML5标记required就可以: <input type="text" re ...

  3. 7 -- Spring的基本用法 -- 11... 基于XML Schema的简化配置方式

    7.11 基于XML Schema的简化配置方式 Spring允许使用基于XML Schema的配置方式来简化Spring配置文件. 7.11.1 使用p:命名空间简化配置 p:命名空间不需要特定的S ...

  4. 【Android】ProgressBar

    http://www.cnblogs.com/wangying222/p/5304990.html http://www.cnblogs.com/plokmju/p/android_ProgressB ...

  5. iOS - UIScrollView 相关属性代理详解

    一.UIScrollView的属性和代理方法详解 属性: - (void)viewDidLoad { [super viewDidLoad]; _scrollView.backgroundColor ...

  6. Python学习(21):Python函数(5):变量作用域与闭包

    转自 http://www.cnblogs.com/BeginMan/p/3179040.html 一.全局变量与局部变量 一个模块中,最高级别的变量有全局作用域. 全局变量一个特征就是:除非被删除, ...

  7. django进阶-1

    前言: 各位久等了,django进阶篇来了. 一.get与post 接口规范: url不能写动词,只能写名词 django默认只支持两种方式: get, post get是获取数据 ?user=zcl ...

  8. 《征服C指针》读书笔记

    本文同时发布在我的个人博客上,欢迎访问~ www.seekingdream.cn 在读完K&R之后,对C的认识就是指针.数组.网上的人们对指针也有些“敬而远之”的感觉.最近从同学处淘得< ...

  9. Delphi 中DataSnap技术网摘

    Delphi2010中DataSnap技术网摘 一.为DataSnap系统服务程序添加描述 这几天一直在研究Delphi 2010的DataSnap,感觉功能真是很强大,现在足有理由证明Delphi7 ...

  10. c++ 用new创建二维数组~创建指针数组【转】

    #include <iostream> using namespace std; void main() { //用new创建一个二维数组,有两种方法,是等价的 //一: ] = ][]; ...