本教程內(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ā)表文章對應的年份列表辽聊,點擊相應年份會展開該年年份下對應的月份列表纪挎,像這樣:
實現(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 哦!