统计各个分类下的文章数

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

数据库数据聚合

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

Post 表:

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

Category 表:

name id
category 1 1
category 2 2

这里前 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 django.db.models.aggregates import Count
from blog.models 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 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

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

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

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

  3. django ORM聚合函数

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

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

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

  5. django中聚合aggregate和annotate GROUP BY的使用方法

    接触django已经很长时间了,但是使用QuerySet查询集的方式一直比较低端,只会使用filter/Q函数/exclude等方式来查询,数据量比较小的时候还可以,但是如果数据量很大,而且查询比较复 ...

  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. Chapter5_初始化与清理_方法重载

    方法重载是java中编译器在方法调用时采用的一个重要机制.起初方法重载的初衷是方便构造器的调用,比如对于一个类,可以实现用不同的方法来初始化它,又由于构造器的名称都是一样的,所以必须使用到方法重载的机 ...

  2. Apache Sling

    最新打算研究下 Apache Sling ,在此记录下过程~ 官网地址 Apache Sling - Bringing Back the Fun! 带回快乐 Apache Sling™ is an i ...

  3. Java实现多线程生产者消费者模型及优化方案

    生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...

  4. CKeditor5 图片上传

    下面是自定义了一个适配器,之前我一直是在 ClassicEditor .create( editorElement, { ckfinder: { uploadUrl: '/ckfinder/core/ ...

  5. 适用于 iOS、Android 和 Windows 设备的移动设备管理

    适用于 iOS.Android 和 Windows 设备的移动设备管理 随着企业环境中移动设备的数量不断增加,详细检查访问您企业资源的移动设备变得至关重要.统一终端管理软件 Desktop Centr ...

  6. "hello,world"———C++入门有感

    刚进入这所学校时,编程对于我来说应该算得上一个既熟悉又陌生的词语.虽然曾经耳边不断有人不断提到编程语言,C语言,程序猿等词语,但是作为一个外行人在来到这所学校之前,对于其中的奥秘还是没什么特别了解,仅 ...

  7. Ubuntu搭建Anki服务器

    Ubuntu搭建Anki服务器 第一步安装Anki 阿里云的服务器,xshell远程登录上 #以root用户进行操作 #安装Akni服务 easy_install AnkiServer #添加名为an ...

  8. C++枚举类型Enum及C++11强枚举类型用法

    C++中的枚举类型常常和switch配合使用,这里用一个简单的switch控制键盘回调的代码片段来说明枚举的用法: //W A S D 前.后.左.右行走 enum Keydown{ Forward= ...

  9. Python之路系列笔记

    备注:本套笔记内容来源于互联网,只做学习使用,如有侵权请联系本笔记作者. 资料内容 Python之路(一)——Python 初识 Python之路(二)——基础语法 Python之路(三)——函数 P ...

  10. 【Spring】SpringMVC配置文件

    SpringMVC中一般会引入三个配置文件applicationContext.xml.dispatcher-servlet.xml(SpringMVC-servlet.xml).web.xml 1. ...