惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
WordPress大学
WordPress大学
N
Netflix TechBlog - Medium
T
Troy Hunt's Blog
Help Net Security
Help Net Security
L
LINUX DO - 最新话题
aimingoo的专栏
aimingoo的专栏
Microsoft Azure Blog
Microsoft Azure Blog
Y
Y Combinator Blog
Attack and Defense Labs
Attack and Defense Labs
M
MIT News - Artificial intelligence
Security Archives - TechRepublic
Security Archives - TechRepublic
SecWiki News
SecWiki News
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
AI
AI
PCI Perspectives
PCI Perspectives
L
Lohrmann on Cybersecurity
G
Google Developers Blog
N
News | PayPal Newsroom
Hugging Face - Blog
Hugging Face - Blog
B
Blog RSS Feed
The Hacker News
The Hacker News
Apple Machine Learning Research
Apple Machine Learning Research
B
Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
I
InfoQ
Webroot Blog
Webroot Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
U
Unit 42
A
About on SuperTechFans
Cyberwarzone
Cyberwarzone
Schneier on Security
Schneier on Security
Cisco Talos Blog
Cisco Talos Blog
D
Docker
博客园_首页
The Cloudflare Blog
S
Secure Thoughts
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Last Watchdog
The Last Watchdog
大猫的无限游戏
大猫的无限游戏
S
SegmentFault 最新的问题
Last Week in AI
Last Week in AI
F
Fortinet All Blogs
I
Intezer
Google DeepMind News
Google DeepMind News
Know Your Adversary
Know Your Adversary
Simon Willison's Weblog
Simon Willison's Weblog

追梦人物的博客

0x05:Merkle Tree & Patricia Trie 0x04:ECDSA - 以太坊设计与实现 LeetCode 105 从前序与中序遍历构造二叉树 迭代算法原理 + 完整证明 0x03:Address - 以太坊设计与实现 0x02:Secp256k1 - 以太坊设计与实现 0x00:专栏开篇 - 以太坊设计与实现 0x01:RLP 编码 - 以太坊设计与实现 Bellman-Ford 算法原理及其在 DeFi 套利中的应用 - 追梦人物的博客 迪杰斯特拉(Dijkstra)最短路径算法原理、实现与证明 - 追梦人物的博客 实战:CEX-DEX 稳定币套利监控程序开发 - 追梦人物的博客 CEX-DEX 稳定币套利模型 - 追梦人物的博客 Uniswap 手续费和协议费机制剖析 - 追梦人物的博客 Uniswap 流动性机制及相关数学原理分析 - 追梦人物的博客 uv 替代 pyenv + pipx + poetry 环境管理实践 Rust 项目从创建到发布 - 追梦人物的博客 VS Code 调试 Python - 追梦人物的博客 比特币跨市场套利的数学模型 - 追梦人物的博客 ERC-20 相关知识点总结 - 追梦人物的博客 Django 老项目如何从 SQLite 迁到 PostgreSQL 自动生成接口文档 - HelloDjango - django REST framework 教程 单元测试 - HelloDjango - django REST framework 教程 限制接口访问频率 - HelloDjango - django REST framework 教程 API 版本管理 - HelloDjango - django REST framework 教程 如何在 Windows 下搭建高效的 django 开发环境 拓展Python Markdown - 追梦人物的博客 加缓存为接口提速 - HelloDjango - django REST framework 教程 基于 drf-haystack 实现文章搜索接口 - HelloDjango 评论接口 - HelloDjango - django REST framework 教程 实现分类、标签、归档日期接口 - HelloDjango - django REST framework 教程 在接口返回Markdown解析后的内容 - HelloDjango - django REST framework 教程 文章详情接口 - HelloDjango - django REST framework 教程 分页 - HelloDjango - django REST framework 教程 使用视图集简化代码 - HelloDjango - django REST framework 教程 用类视图实现首页 API - HelloDjango - django REST framework 教程 实现博客首页文章列表 API - HelloDjango - django REST framework 教程 初始化 RESTful API 风格的博客系统 - HelloDjango django-rest-framework 是什么鬼? - HelloDjango - django REST framework 教程 结束 or 开始? - HelloDjango Coverage.py 统计测试覆盖率 - HelloDjango - Django博客教程(第二版) 单元测试:测试评论应用 - HelloDjango - Django博客教程(第二版) 单元测试:测试 blog 应用 - HelloDjango Django Haystack 全文检索与关键词高亮 - HelloDjango Django 博客实现简单的全文搜索 - HelloDjango - Django博客教程(第二版) 开启 Django 博客的 RSS 功能 - HelloDjango 稳定易用的 Django 分页库,完善分页功能 - HelloDjango 通过 Django Pagination 实现简单分页 - HelloDjango 在脚本中使用 ORM:Faker 批量生成测试数据 - HelloDjango Django 官方推荐的姿势:类视图 - HelloDjango - Django博客教程(第二版) Django 使用 union 合并不同模型(Model) 的查询集(QuerySet) 开发博客文章阅读量统计功能 - HelloDjango - Django博客教程(第二版) 使用 Docker 让部署 Django 项目更加轻松 - HelloDjango 使用 Certbot 向 Let's Encrypt 免费申请 HTTPS 证书 - HelloDjango 使用 Fabric 自动化部署 - HelloDjango 博客代码开源啦 - 追梦人物的博客 (赠书)推荐一本django书籍:Django企业开发实战 - 追梦人物的博客 Nginx+Gunicorn+Supervisor 部署 Django 博客应用 - HelloDjango 优化博客功能细节,提升使用体验 - HelloDjango - Django博客教程(第二版) 交流的桥梁:评论功能 - HelloDjango - Django博客教程(第二版) 分类、归档和标签页 - HelloDjango - Django博客教程(第二版) 页面侧边栏:使用自定义模板标签 - HelloDjango - Django博客教程(第二版) 自动生成文章摘要 - HelloDjango - Django博客教程(第二版) Markdown 文章自动生成目录,提升阅读体验 - HelloDjango - Django博客教程(第二版) 让博客支持 Markdown 语法和代码高亮 - HelloDjango 开发博客文章详情页 - HelloDjango - Django博客教程(第二版) 创作后台开启,请开始你的表演 - HelloDjango - Django博客教程(第二版) 博客从“裸奔”到“有皮肤” - HelloDjango - Django博客教程(第二版) Django 的接客之道 - HelloDjango - Django博客教程(第二版) Django 迁移、操作数据库 - HelloDjango - Django博客教程(第二版) 创建 Django 博客的数据库模型 - HelloDjango 开始进入 django 开发之旅 - HelloDjango "空空如也"的博客应用 - HelloDjango - Django博客教程(第二版) 一种自顶而下的Python装饰器设计方法 - 追梦人物的博客 Python提取支付宝和微信支付二维码 - 追梦人物的博客 2018 - 我的学生生涯最后一年回顾 - 追梦人物的博客 批量清除todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 筛选练习参考答案 - Vue 2.x Todo 教程练习参考答案 删除todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 编辑todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 添加todo练习参考答案 - Vue 2.x Todo 教程练习参考答案 标为完成练习参考答案 - Vue 2.x Todo 教程练习参考答案 入门仪式_Hello_Vue练习参考答案 - Vue 2.x Todo 教程练习参考答案 组件化todo应用 - Vue 2.x Todo 教程 批量清除todo - Vue 2.x Todo 教程 本地存储 - Vue 2.x Todo 教程 筛选 - Vue 2.x Todo 教程 还剩多少todo未完成 - Vue 2.x Todo 教程 自定义指令实现自动聚焦 - Vue 2.x Todo 教程 全部标为完成 - Vue 2.x Todo 教程 删除todo - Vue 2.x Todo 教程 编辑todo - Vue 2.x Todo 教程 添加todo - Vue 2.x Todo 教程 标为完成 - Vue 2.x Todo 教程 UI - Vue 2.x Todo 教程 显示todo列表 - Vue 2.x Todo 教程 入门仪式:Hello Vue - Vue 2.x Todo 教程 在学习django-rest-framework时收集的学习资料推荐 - 追梦人物的博客 区块链理论与应用研究小组成员招募书 - 追梦人物的博客 Python界网红,豆瓣工程师董伟明加了我的QQ后 - 追梦人物的博客 招募Django学习小组项目组核心成员 - 追梦人物的博客
统计各个分类和标签下的文章数 - HelloDjango - Django博客教程(第二版)
2020-01-01 · via 追梦人物的博客

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

Model 回顾

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

blog/models.py

class Post(models.Model):
    title = models.CharField('标题', max_length=70)
    body = models.TextField('正文')
    category = models.ForeignKey(Category, verbose_name='分类', on_delete=models.CASCADE)
    # 其它属性...

    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 模型中我们通过 ForeignKeyPostCategory 关联了起来,这时候它们的数据库表结构就像下面这样:

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_extras.py

from django.db.models.aggregates import Count
from blog.models import Category

@register.inclusion_tag('blog/inclusions/_categories.html', takes_context=True)
def show_categories(context):
    category_list = Category.objects.annotate(num_posts=Count('post')).filter(num_posts__gt=0)
    return {
        'category_list': category_list,
    }

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

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

同理,tags 也可以做同样的操作。

@register.inclusion_tag('blog/inclusions/_tags.html', takes_context=True)
def show_tags(context):
    tag_list = Tag.objects.annotate(num_posts=Count('post')).filter(num_posts__gt=0)
    return {
        'tag_list': tag_list,
    }

在模板中引用新增的属性

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

templates/blog/inclusions/_categories.html

<div class="widget widget-category">
  <h3 class="widget-title">分类</h3>
  <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>
</div>

标签也是一样:

templates/blog/inclusions/_tags.html

<div class="widget widget-tag-cloud">
  <h3 class="widget-title">标签云</h3>
  <ul>
    {% for tag in tag_list %}
      <li>
        <a href="{% url 'blog:tag' tag.pk %}">{{ tag.name }} <span class="post-count">({{ tag.num_posts }})</a>
      </li>
    {% empty %}
      暂无标签!
    {% endfor %}
  </ul>
</div>

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

-- EOF --