本教程内容已过时,更新版教程请访问: Django 博客开发入门教程。
这是 Django 博客教程的第 11 篇,在阅读此篇教程以前,请确保你已阅读 Django 博客教程的前 10 篇:
1. Django 博客教程:前言
2. 搭建开发环境
3. 建立我们的 django 博客应用
4. 创建 django 博客的数据库模型
5. 让 django 完成翻译——迁移数据库模型
6. django 博客首页视图
7. 真正的 django 博客首页视图
8. 在 django admin 后台发布我们的文章
9. 博客文章详情页
10. 支持 markdown 语法和代码高亮
我们的博客侧边栏有三项内容:最近发表的博客文章、归档和分类。这些内容相对比较固定,且在各个页面都会显示,如果像文章列表或者文章详情一样,在视图函数中获取然后传递给模板,则每个页面对应的视图函数里都要写一段获取这些内容的代码,然后传递给模板,这会导致很多重复代码。更好的解决方案是直接在模板中获取,为此,我们使用 django 的一个新技术:自定义模板标签来完成我们的任务。
我们接触过一些 django 内置的模板标签,比如比较简单的 {% static %} 模板标签,这个标签帮助我们在模板中引入静态文件。还有比较复杂的如 {% for %} {% endfor%} 标签。这里我们希望自己定义一个模板标签,它可以这样工作:我们只要在模板中写入 {% get_recent_posts as recent_post_list %},那么模板中就会有一个从数据库获取的最近文章列表,并通过 as 语句保存到 recent_post_list 模板变量里,这样我们就可以通过 for 模板标签来循环这个变量,显示文章列表了。这和我们在编写博客首页面视图函数是一样的,它在视图函数中从数据库获取文章列表输入保存到 post_list 变量,并把这个 post_list 变量传给模板,模板使用 for 模板标签循环这个文章列表变量,从而展示一条条文章列表。这里唯一的不同是我们从数据库获取文章列表的操作不是在视图函数中进行,而是在模板中通过我们定义的 get_recent_posts 模板标签进行。
以上就是解决思路,但模板标签不是我们随便乱写的,必须遵循 django 的规范我们才能在模板语言使用我们定义的模板标签,下面我们就依照这些规范来实现我们的需求。
首先在我们的 blog 应用下创建一个 templatetags 文件夹。然后在这个文件夹下创建一个 __init__.py 文件,使这个文件夹变为一个 Python 包,之后在创建一个 blog_tags.py 文件,这个文件存放我们自定义的模板标签的代码。
打开 blog_tags.py 文件,开始写我们的模板标签。模板标签本质上就是一个 Python 函数,比如这里我们的 {% get_recent_posts %},它应该对应着一个函数,这个函数将从数据库获取最近文章列表并返回他们。
templatetags/blog_tags.py
from ..models import Post
def get_recent_posts(num=5):
return Post.objects.all()[:num]
这个函数的功能是获取数据库中前 num 篇文章,这里 num 默认为 5。函数就这么简单,但目前它还只是一个纯 Python 函数,django 模板还不知道该如何使用它。为了能够通过 {% get_recent_posts %} 在模板中调用这个函数,必须按照 django 的规定注册这个函数为模板标签,方法如下:
templatetags/blog_tags.py
from django import template
from ..models import Post
register = template.Library()
@register.simple_tag
def get_recent_posts(num=5):
return Post.objects.all()[:num]
这里我们首先导入 template 这个包,然后实例化了一个 template.Library,并将函数装饰为 register.simple_tag。这样就可以在模板中使用语法 {% get_recent_posts %} 调用这个函数了。
如法炮制,实现归档,分类列表。
归档:
templatetags/blog_tags.py
@register.simple_tag
def archives():
return Post.objects.dates('created_time', 'month', order='DESC')
这个 dates 方法会返回一个列表,列表中的元素为每一篇 Post 创建的时间,精确到月份,降序排列。接受的三个参数值表明了这些含义,一个是 created_time
,即 Post 的创建时间,month 是精度,order='DESC' 表明降序排列。例如如果我们写了 3 篇文章,分别发布于 2017 年 2 月 21 日、2017 年 3 月 25 日、2017 年 3 月 28 日,那么 date 将返回 2017 年 3 月 和 2017 年 2 月这样一个时间列表,从而帮助我们实现按月归档的目的。
分类:
templatetags/blog_tags.py
@register.simple_tag
def get_categories():
return Category.objects.all()
然后在模板中使用它们:
打开 base.html,为了使用模板标签,我们首先需要在模板中导入它。当时我们为了使用 static 模板标签时曾经导入过 {% load staticfiles %},这次先在最顶部导入 blog_tags:
templates/base.html
{% load blog_tags %}
<!DOCTYPE html>
<html>
...
</html>
然后找到最新文章列表处,把里面的列表修改一下:
<div class="widget widget-recent-posts">
<h3 class="widget-title">最新文章</h3>
{% get_recent_posts as recent_post_list %}
<ul>
{% for post in recent_post_list %}
<li>
<a href="{{ post.get_absolute_url }}">{{ post.title }}</a>
</li>
{% endfor %}
</ul>
</div>
这里我们通过使用 get_recent_posts 模板标签获取到最新文章列表,然后我们通过 as 将获取的文章列表保存进了 recent_post_list 模板变量中,之后就可以通过 for 循环来循环显示了,这和我们在写首页视图时是一样的。
然后是归档部分:
<div class="widget widget-archives">
<h3 class="widget-title">归档</h3>
{% archives as date_list %}
<ul>
{% for date in date_list %}
<li>
<a href="#">{{ date.year }} 年 {{ date.month }} 月</a>
</li>
{% endfor %}
</ul>
</div>
同样,这里我们调用 archives 模板标签获取自动获取一个文章发表的日期列表,精确到月份,然后保存在 date_list 模板变量里。由于日期列表中的元素为 Python 的 date 对象,因此可以通过其 year 和 month 属性分别获取其年和月的信息,<a href="#">{{ date.year }} 年 {{ date.month }} 月</a>
反应了这个事实。
分类部分也一样了:
<div class="widget widget-category">
<h3 class="widget-title">分类</h3>
{% get_categories as category_list %}
<ul>
{% for category in category_list %}
<li>
<a href="#">{{ category.name }}</a>
</li>
{% endfor %}
</ul>
</div>