在咱們的博客側邊欄有分類列表,顯示博客已有的所有文章分類。如今想在分類名後顯示該分類下有多少篇文章,該怎麼作呢?最優雅的方式就是使用 Django 模型管理器的 annotate
方法。html
模型回顧
回顧一下咱們的模型代碼,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 除了 all
、get
等方法外,還有不少操做數據庫的方法,而其中有一個 annotate
方法,該方法正能夠幫咱們實現本文所關注的統計分類下的文章數量的功能。django
數據庫數據聚合
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 表:post
name | id |
---|---|
category 1 | 1 |
category 2 | 2 |
這裏前 3 篇文章屬於 category 1,第 4 篇文章屬於 category 2。url
當 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 的實例的屬性中,每一個實例對應一條記錄)。spa
使用 Annotate
以上是原理方面的分析,具體到 Django 中該如何用呢?在咱們的博客中,獲取側邊欄的分類列表的方法寫在模板標籤 get_categories
裏,所以咱們修改一下這個函數,具體代碼以下:code
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
屬性中。htm
此外,咱們還對結果集作了一個過濾,使用 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 下的其它操做數據庫的方法,以便在遇到相關問題時知道去哪裏查閱。