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 哦!

相关文章
相关标签/搜索