Django 學習小組:博客開發(fā)實戰(zhàn)第四周——標簽云與文章歸檔

本教程內(nèi)容已過時词顾,更新版教程請訪問: Django 博客開發(fā)入門教程

通過前四周的時間我們開發(fā)了一個簡單的個人 Blog碱妆,相關(guān)教程:

第一周Django 學習小組:博客開發(fā)實戰(zhàn)第一周教程 —— 編寫博客的 Model 和首頁面
第二周Django 學習小組:博客開發(fā)實戰(zhàn)第二周教程 —— 博客詳情頁面和分類頁面
第三周Django 學習小組:博客開發(fā)實戰(zhàn)第三周教程——文章列表分頁和代碼語法高亮
第五周Django 學習小組:博客開發(fā)實戰(zhàn)第五周——基于類的通用視圖詳解(一)
第六周Django 學習小組:博客開發(fā)實戰(zhàn)第六周教程 —— 實現(xiàn)評論功能

本周我們將實現(xiàn) blog 的標簽云和文章按時間自動歸檔功能计技。

提示:在閱讀教程的過程中,如有任何問題請訪問我們項目的 GithHub 或評論留言以獲取幫助山橄,本教程的相關(guān)代碼已全部上傳在 GitHub 的 blog-tutorial 分支 上,請點擊鏈接獲取舍悯。航棱。如果你對我們的教程或者項目有任何改進建議,請您隨時告知我們萌衬。更多交流請加入我們的郵件列表 django_study@groups.163.com 和關(guān)注我們在 GithHub 上的項目饮醇。

本文首發(fā)于編程派微信公眾號:編程派(微信號:codingpy)是一個專注Python編程的公眾號,每天更新有關(guān)Python的國外教程和優(yōu)質(zhì)書籍等精選干貨秕豫,歡迎關(guān)注朴艰。

標簽云與文章歸檔在 Blog 中也是比較常見的功能观蓄,標簽云顯示每篇文章的標簽,文章歸檔顯示某個時間段內(nèi)的發(fā)表的文章祠墅,就像這樣:

標簽云
文章歸檔

下面我們來為我們的 Blog 添加類似的功能侮穿,最終會為我們的個人 blog 實現(xiàn)類似于下面這樣的效果:

整體效果展示

標簽云

標簽有點類似于分類,只是分類由于是多對一的關(guān)系(我們規(guī)定一篇文章只有一個分類毁嗦,而一個分類下可以有多篇文章),因此在我們的 model 中使用的是 ForeignKeyField 克锣。我們規(guī)定一篇文章可以打多個標簽腔长,并且一個標簽下可能會有多篇文章,是多對多的關(guān)系巾乳,因此需要使用到 ManyToManyField故俐,其它的實現(xiàn)則和 Category(分類)十分相似。首先修改我們的 model 文件药版,為標簽(tag)新建一個數(shù)據(jù)庫 model,并在文章(Article)中指定它們多對多的關(guān)系:

blog/models.py

class Article(models.Model):
    """
    文章model中添加tag關(guān)系
    """
    ...
    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(標簽)對應的數(shù)據(jù)庫model
    """
    name = models.CharField('標簽名', max_length=20)
    created_time = models.DateTimeField('創(chuàng)建時間', auto_now_add=True)
    last_modified_time = models.DateTimeField('修改時間', auto_now=True)

    def __str__(self):
        return self.name

類似于 CategoryView何缓,點擊某個標簽可以獲取該標簽下的全部文章碌廓,對應的視圖函數(shù):

blog/views.py

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

    def get_queryset(self):
        """
        根據(jù)指定的標簽獲取該標簽下的全部文章
        """
        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)

模板文件稍微小變了一下,添加了顯示標簽的區(qū)域(由于模板文件代碼比較多剩盒,具體請參見 GitHub 的 blog-tutorial 分支 上 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'),

文章歸檔

文章歸檔我們實現(xiàn)下面的需求:

在首頁會顯示已發(fā)表文章對應的年份列表辽聊,點擊相應年份會展開該年年份下對應的月份列表纪挎,像這樣:

blog 文章歸檔演示

實現(xiàn)思路大概如下:Django 的 ORM 為我們提供一個 datetimes 函數(shù) ( datetimes 函數(shù)用法 ),可以選出數(shù)據(jù)庫中某個 model 對應的全部已去重的時間跟匆,并且可以任意指定精度异袄。例如,我們想選出全部文章對應的發(fā)表時間玛臂,精確到月份:

date_list = Article.objects.datetimes('created_time', 'month', order='DESC')
# created_time 是 Article model 中文章發(fā)表時間烤蜕,對應的是 DatetimeField( datetimes 函數(shù)也只能用于DatetimeField )封孙,month 即精確到月,精確到年指定為 year讽营,天則指定為 day 即可虎忌。DESC 表示降序排列,默認是升序排列斑匪。

# 例如有如下的一系列發(fā)表時間:
2009-01-02
2009-01-05
2009-02-02
2010-05-04
2011-06-04
2011-06-07
# 則得到的結(jié)果將是精確到月份去重后的結(jié)果:
2009-01
2009-02
2010-05
2011-06
# 這正是我們期望的結(jié)果

以這個函數(shù)為基礎(chǔ)呐籽,接下來我們使用 Django 的一點高級技巧(自定義 Manager)來實現(xiàn)完整的功能。

什么是 Manager(管理器)蚀瘸?Manager 可以看成是一個 model 的管理器狡蝶,很多從數(shù)據(jù)庫中獲取 model 數(shù)據(jù)的方法都定義在這個類里,比如我們經(jīng)常用的 Article.objects.all()贮勃,Article.objects.filter()贪惹,這里的 objects 就是一個 Manager 的實例,django 為每一個 model 都指定了一個默認的 Manager 寂嘉,名字叫做 objects硼端。但現(xiàn)在 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')
        # 獲取到降序排列的精確到月份且已去重的文章發(fā)表時間列表
        # 并把列表轉(zhuǎn)為一個字典,字典的鍵為年份,值為該年份下對應的月份列表
        date_dict = defaultdict(list)
        for d in date_list:
            date_dict[d.year].append(d.month)
        # 模板不支持defaultdict悠咱,因此我們把它轉(zhuǎn)換成一個二級列表析既,由于字典轉(zhuǎn)換后無序拂玻,因此重新降序排序
        return sorted(date_dict.items(), reverse=True)

自定義了 Manger 后需要在 model 中顯示地指定它:

blog/models.py

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

現(xiàn)在在視圖函數(shù)中就可以調(diào)用了:

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')
        # 調(diào)用 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)
    
# 現(xiàn)在我們的時間歸檔列表格式是這樣的:
[(2012,[09,02,01]),(2011,[12,10,06,01]),...]
# 因此在模板中我們可以這樣循環(huán)以實現(xiàn)我們預初的設計:
{% for year,month_list in date_archive %}
    {{year}} 年
    {% for month in month_list %}
        {{month}}月
# 使用一些 bootstrap 的組件即可實現(xiàn)上圖一樣的效果了。

完整的模板請參考GitHub 的 blog-tutorial 分支 的 blog/templates/blog/index.html 模板文件咳短。

最后一件事就是實現(xiàn)點擊相應的時間后顯示該時間下的全部已發(fā)表文章列表了咙好,實現(xiàn)思路即通過 url 把對應的年份和月份傳給視圖函數(shù),視圖函數(shù)通過年份和月份過濾所需文章葵第,然后再模板渲染即可卒密,實現(xiàn)和 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參數(shù),轉(zhuǎn)為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 基本已經(jīng)成型了贸伐!首頁展示文章列表捉邢、標簽云伏伐、文章歸檔藐翎、分類吝镣,文章 markdown 語法標記赤惊,代碼高亮顯示圈暗,利用 django 后臺员串,我們可以使用它來寫 blog 文章了寸齐,你可以先嘗試著找一個部署教程把 blog 部署上線渺鹦。當然我們接下來也會出如何部署的教程毅厚,敬請期待。下一周我們將實現(xiàn)評論功能咽安,允許用戶對我們發(fā)表的文章進行評論妆棒。為了學習蛋铆,我們將不使用第三方 app,而是重新發(fā)明輪子纠脾。

Django學習小組簡介

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

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

  • 一個簡單的 django 博客氮采,用于發(fā)布小組每周的學習和開發(fā)文檔鹊漠;
  • django中國社區(qū),為國內(nèi)的 django 開發(fā)者們提供一個長期維護的 django 社區(qū)娶靡;

上面所說的這個社區(qū)類似于 segmentfault 和 stackoverflow 茉唉,但更加專注(只專注于 django 開發(fā)的問題)艾凯。

更多的信息請關(guān)注我們的 github 組織趾诗,本教程項目的相關(guān)源代碼也已上傳到 GitHub 的 blog-tutorial 分支 上郑兴,請點擊鏈接獲取情连。

同時,你也可以加入我們的郵件列表 django_study@groups.163.com 挽拔,隨時關(guān)注我們的動態(tài)。我們會將每周的詳細開發(fā)文檔和代碼通過郵件列表發(fā)出术裸。

如有任何建議穗椅,歡迎提 Issue,歡迎 fork袍镀,pr苇羡,當然也別忘了 star 哦!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市歼捏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖袖迎,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異念颈,居然都是意外死亡嗡靡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜜自,“玉大人卢佣,你說我怎么就攤上這事重荠。” “怎么了虚茶?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵戈鲁,是天一觀的道長。 經(jīng)常有香客問我嘹叫,道長婆殿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己命浴,他們只是感情好悬蔽,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布盖袭。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缤至。 梳的紋絲不亂的頭發(fā)上月洛,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天爵卒,我揣著相機與錄音,去河邊找鬼偎谁。 笑死冈涧,一個胖子當著我的面吹牛奸攻,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播邀窃,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼爵政,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤篱瞎,失蹤者是張志新(化名)和其女友劉穎俄精,沒想到半個月后踱讨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔓挖,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡拷获,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年纸型,在試婚紗的時候發(fā)現(xiàn)自己被綠了逊谋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悲敷。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡究恤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出后德,到底是詐尸還是另有隱情部宿,我是刑警寧澤,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布瓢湃,位于F島的核電站理张,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏绵患。R本人自食惡果不足惜雾叭,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望落蝙。 院中可真熱鬧织狐,春花似錦、人聲如沸筏勒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽管行。三九已至厨埋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間病瞳,已是汗流浹背揽咕。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工悲酷, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留套菜,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓设易,卻偏偏與公主長得像逗柴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子顿肺,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容