Django 學習小組:博客開發實戰第四周——標籤雲與文章歸檔

本教程內容已過期,更新版教程請訪問: django 博客開發入門教程html

經過前四周的時間咱們開發了一個簡單的我的 Blog,教程地址:python

第一週Django 學習小組:博客開發實戰第一週教程 —— 編寫博客的 Model 和首頁面git

第二週Django 學習小組:博客開發實戰第二週教程 —— 博客詳情頁面和分類頁面github

第三週Django 學習小組:博客開發實戰第三週教程 —— 文章列表分頁和代碼語法高亮數據庫

第四周Django 學習小組:基於類的通用視圖詳解(一)django

本週咱們將實現 blog 的標籤雲和文章按時間自動歸檔功能。編程

提示:在閱讀教程的過程當中,若有任何問題請訪問咱們項目的 GithHub 或評論留言以獲取幫助,本教程的相關代碼已所有上傳在 Github。若是你對咱們的教程或者項目有任何改進建議,請您隨時告知咱們。更多交流請加入咱們的郵件列表 django_study@groups.163.com 和關注咱們在 GithHub 上的項目。bootstrap

本文首發於編程派微信公衆號:編程派(微信號:codingpy)是一個專一Python編程的公衆號,天天更新有關Python的國外教程和優質書籍等精選乾貨,歡迎關注。segmentfault

標籤雲與文章歸檔在 Blog 中也是比較常見的功能,標籤雲顯示每篇文章的標籤,文章歸檔顯示某個時間段內的發表的文章,就像這樣:微信

標籤雲

文章歸檔

下面咱們來爲咱們的 Blog 添加相似的功能,最終會爲咱們的我的 blog 實現相似於下面這樣的效果:

總體效果展現

標籤雲

標籤有點相似於分類,只是分類因爲是多對一的關係(咱們規定一篇文章只有一個分類,而一個分類下能夠有多篇文章),所以在咱們的 model 中使用的是 ForeignKeyField 。咱們規定一篇文章能夠打多個標籤,而且一個標籤下可能會有多篇文章,是多對多的關係,所以須要使用到 ManyToManyField,其它的實現則和 Category(分類)十分類似。首先修改咱們的 model 文件,爲標籤(tag)新建一個數據庫 model,並在文章(Article)中指定它們多對多的關係:

blog/models.py

class Article(models.Model):
    """
    文章model中添加tag關係
    """
    ...
    category = models.ForeignKey('Category', verbose_name='分類', null=True, on_delete=models.SET_NULL)
    tags = models.ManyToManyField('Tag', verbose_name='標籤集合', blank=True)
    ...

class Tag(models.Model):
    """
    tag(標籤)對應的數據庫model
    """
    name = models.CharField('標籤名', max_length=20)
    created_time = models.DateTimeField('建立時間', auto_now_add=True)
    last_modified_time = models.DateTimeField('修改時間', auto_now=True)

    def __str__(self):
        return self.name

相似於 CategoryView,點擊某個標籤能夠獲取該標籤下的所有文章,對應的視圖函數:

blog/views.py

class TagView(ListView):
    template_name = "blog/index.html"
    context_object_name = "article_list"

    def get_queryset(self):
        """
        根據指定的標籤獲取該標籤下的所有文章
        """
        article_list = Article.objects.filter(tags=self.kwargs['tag_id'], status='p')
        for article in article_list:
            article.body = markdown2.markdown(article.body, extras=['fenced-code-blocks'], )
        return article_list

    def get_context_data(self, **kwargs):
        kwargs['tag_list'] = Tag.objects.all().order_by('name')
        return super(TagView, self).get_context_data(**kwargs)

模板文件稍微小變了一下,添加了顯示標籤的區域(因爲模板文件代碼比較多,具體請參見 github 項目中 blog/templates/blog/index.html 下的模板文件)。

同時 IndexView 裏也別忘了把 tag 加到 context 中,以便在模板中渲染顯示:

blog/views.py

class IndexView(ListView):
    ...
    def get_context_data(self, **kwargs):
        kwargs['category_list'] = Category.objects.all().order_by('name')
        kwargs['date_archive'] = Article.objects.archive()
        # tag_list 加入 context 裏:
        kwargs['tag_list'] = Tag.objects.all().order_by('name')
        return super(IndexView, self).get_context_data(**kwargs)

配置好 url :

blog/urls.py

url(r'^tag/(?P<tag_id>\d+)$', views.TagView.as_view(), name='tag'),

文章歸檔

文章歸檔咱們實現下面的需求:

在首頁會顯示已發表文章對應的年份列表,點擊相應年份會展開該年年份下對應的月份列表,像這樣:

blog 文章歸檔演示

實現思路大概以下:Django 的 ORM 爲咱們提供一個 datetimes 函數 ( datetimes 函數用法 ),能夠選出數據庫中某個 model 對應的所有已去重的時間,而且能夠任意指定精度。例如,咱們想選出所有文章對應的發表時間,精確到月份:

date_list = Article.objects.datetimes('created_time', 'month', order='DESC')
# created_time 是 Article model 中文章發表時間,對應的是 DatetimeField( datetimes 函數也只能用於DatetimeField ),month 即精確到月,精確到年指定爲 year,天則指定爲 day 便可。DESC 表示降序排列,默認是升序排列。

# 例若有以下的一系列發表時間:
2009-01-02
2009-01-05
2009-02-02
2010-05-04
2011-06-04
2011-06-07
# 則獲得的結果將是精確到月份去重後的結果:
2009-01
2009-02
2010-05
2011-06
# 這正是咱們指望的結果

以這個函數爲基礎,接下來咱們使用 Django 的一點高級技巧(自定義 Manager)來實現完整的功能。

什麼是 Manager(管理器)?Manager 能夠當作是一個 model 的管理器,不少從數據庫中獲取 model 數據的方法都定義在這個類裏,好比咱們常常用的 Article.objects.all()Article.objects.filter(),這裏的 objects 就是一個 Manager 的實例,django 爲每個 model 都指定了一個默認的 Manager ,名字叫作 objects。但如今 Manager 中一些默認的方法沒法知足咱們的需求了,所以咱們拓展一下 Manager 的功能,爲其添加一個歸檔(archive)方法,拓展一個類的最佳方式就是繼承它:

blog/models.py

class ArticleManage(models.Manager):
    """
    繼承自默認的 Manager ,爲其添加一個自定義的 archive 方法
    """
    def archive(self):
        date_list = Article.objects.datetimes('created_time', 'month', order='DESC')
        # 獲取到降序排列的精確到月份且已去重的文章發表時間列表
        # 並把列表轉爲一個字典,字典的鍵爲年份,值爲該年份下對應的月份列表
        date_dict = defaultdict(list)
        for d in date_list:
            date_dict[d.year].append(d.month)
        # 模板不支持defaultdict,所以咱們把它轉換成一個二級列表,因爲字典轉換後無序,所以從新降序排序
        return sorted(date_dict.items(), reverse=True)

自定義了 Manger 後須要在 model 中顯示地指定它:

blog/models.py

class Article(models.model):
    ...
    # 仍然使用默認的 objects 做爲 manager 的名字
    objects = ArticleManager()
    ...

如今在視圖函數中就能夠調用了:

blog/views.py

class IndexView(ListView):
    template_name = "blog/index.html"
    context_object_name = "article_list"

    def get_queryset(self):
        article_list = Article.objects.filter(status='p')
        for article in article_list:
            article.body = markdown2.markdown(article.body, extras=['fenced-code-blocks'], )
        return article_list

    def get_context_data(self, **kwargs):
        kwargs['category_list'] = Category.objects.all().order_by('name')
        # 調用 archive 方法,把獲取的時間列表插入到 context 上下文中以便在模板中渲染
        kwargs['date_archive'] = Article.objects.archive()
        kwargs['tag_list'] = Tag.objects.all().order_by('name')
        return super(IndexView, self).get_context_data(**kwargs)
    
# 如今咱們的時間歸檔列表格式是這樣的:
[(2012,[09,02,01]),(2011,[12,10,06,01]),...]
# 所以在模板中咱們能夠這樣循環以實現咱們預初的設計:
{% for year,month_list in date_archive %}
    {{year}} 年
    {% for month in month_list %}
        {{month}}月
# 使用一些 bootstrap 的組件便可實現上圖同樣的效果了。

完整的模板請參考 github 的 blog/templates/blog/index.html 模板文件。

最後一件事就是實現點擊相應的時間後顯示該時間下的所有已發表文章列表了,實現思路即經過 url 把對應的年份和月份傳給視圖函數,視圖函數經過年份和月份過濾所需文章,而後再模板渲染便可,實現和 category 與 tag 的方式十分相似:

blog/views.py

class ArchiveView(ListView):
    template_name = "blog/index.html"
    context_object_name = "article_list"

    def get_queryset(self):
        # 接收從url傳遞的year和month參數,轉爲int類型
        year = int(self.kwargs['year'])
        month = int(self.kwargs['month'])
        # 按照year和month過濾文章
        article_list = Article.objects.filter(created_time__year=year, created_time__month=month)
        for article in article_list:
            article.body = markdown2.markdown(article.body, extras=['fenced-code-blocks'], )
        return article_list

    def get_context_data(self, **kwargs):
        kwargs['tag_list'] = Tag.objects.all().order_by('name')
        return super(ArchiveView, self).get_context_data(**kwargs)

url:

blog/urls.py

url(r'^archive/(?P<year>\d+)/(?P<month>\d+)$', views.ArchiveView.as_view(), name='archive'),

templates:

blog/index.html

# 詳細請參閱 github 上的模板文件完整代碼
{% for year,month_list in date_archive %}
    {{year}} 年
    {% for month in month_list %}
        <a href="{% url 'blog:archive' year month %}"><p>{{ month }} 月</p></a>

接下來作什麼?

咱們的我的 blog 基本已經成型了!首頁展現文章列表、標籤雲、文章歸檔、分類,文章 markdown 語法標記,代碼高亮顯示,利用 django 後臺,咱們可使用它來寫 blog 文章了,你能夠先嚐試着找一個部署教程把 blog 部署上線。固然咱們接下來也會出如何部署的教程,敬請期待。下一週咱們將實現評論功能,容許用戶對咱們發表的文章進行評論。爲了學習,咱們將不使用第三方 app,而是從新發明輪子。

Django學習小組簡介

django學習小組是一個促進 django 新手互相學習、互相幫助的組織。

小組在一邊學習 django 的同時將一塊兒完成幾個項目,包括:

  • 一個簡單的 django 博客,用於發佈小組每週的學習和開發文檔;

  • django中國社區,爲國內的 django 開發者們提供一個長期維護的 django 社區;

上面所說的這個社區相似於 segmentfault 和 stackoverflow ,但更加專一(只專一於 django 開發的問題)。

更多的信息請關注咱們的 github 組織,本教程項目的相關源代碼也已上傳到 github 上。

同時,你也能夠加入咱們的郵件列表 django_study@groups.163.com ,隨時關注咱們的動態。咱們會將每週的詳細開發文檔和代碼經過郵件列表發出。

若有任何建議,歡迎提 Issue,歡迎 fork,pr,固然也別忘了 star 哦!

相關文章
相關標籤/搜索