統計各個分類和標籤下的文章數

做者:HelloGitHub-追夢人物html

文中所涉及的示例代碼,已同步更新到 HelloGitHub-Team 倉庫python

在咱們的博客側邊欄有分類列表和標籤列表,顯示博客已有的所有文章分類。如今想在分類名和標籤名後顯示該分類或者標籤下有多少篇文章,該怎麼作呢?最優雅的方式就是使用 django 的 annotate 方法。git

Model 回顧

回顧一下咱們的 model 代碼,django 博客有一個 PostCategory 模型,分別表示文章和分類:github

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 關聯了起來,這時候它們的數據庫表結構就像下面這樣:django

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 的實例的屬性中,每一個實例對應一條記錄)。post

使用 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 的值。開啓開發服務器,能夠看到分類名後正確地顯示了該分類下的文章數了,而沒有文章分類則不會在分類列表中出現。


『講解開源項目系列』——讓對開源項目感興趣的人再也不畏懼、讓開源項目的發起者再也不孤單。跟着咱們的文章,你會發現編程的樂趣、使用和發現參與開源項目如此簡單。歡迎留言聯繫咱們、加入咱們,讓更多人愛上開源、貢獻開源~

相關文章
相關標籤/搜索