页面侧边栏:使用自定义模板标签

37882 91 2017年4月12日

我们的博客侧边栏有四项内容:最新文章、归档、分类和标签云。这些内容相对比较固定,且在各个页面都会显示,如果像文章列表或者文章详情一样,从视图函数中获取然后传递给模板,则每个页面对应的视图函数里都要写一段获取这些内容的代码,这会导致很多重复代码。更好的解决方案是直接在模板中获取,为此,我们使用 Django 的一个新技术:自定义模板标签来完成任务。

使用模板标签的解决思路

我们前面已经接触过一些 Django 内置的模板标签,比如比较简单的 {% static %} 模板标签,这个标签帮助我们在模板中引入静态文件。还有比较复杂的如 {% for %} {% endfor%} 标签。这里 我们希望自己定义一个模板标签,例如名为 get_recent_posts 的模板标签,它可以这样工作:我们只要在模板中写入 {% get_recent_posts as recent_post_list %},那么模板中就会有一个从数据库获取的最新文章列表,并通过 as 语句保存到 recent_post_list 模板变量里。这样我们就可以通过 {% for %} {% endfor%} 模板标签来循环这个变量,显示最新文章列表了,这和我们在编写博客首页面视图函数是类似的。首页视图函数中从数据库获取文章列表并保存到 post_list 变量,然后把这个 post_list 变量传给模板,模板使用 for 模板标签循环这个文章列表变量,从而展示一篇篇文章。这里唯一的不同是我们从数据库获取文章列表的操作不是在视图函数中进行,而是在模板中通过自定义的 {% get_recent_posts %} 模板标签进行。

以上就是解决思路,但模板标签不是我们随意写的,必须遵循 Django 的规范我们才能在 Django 的模板系统中使用自定义的模板标签,下面我们就依照这些规范来实现我们的需求。

模板标签目录结构

首先在我们的 blog 应用下创建一个 templatetags 文件夹。然后在这个文件夹下创建一个 __init__.py 文件,使这个文件夹成为一个 Python 包,之后在 templatetags\ 目录下创建一个 blog_tags.py 文件,这个文件存放自定义的模板标签代码。

此时你的目录结构应该是这样的:

blog\
    __init__.py
    admin.py
    apps.py
    migrations\
        __init__.py
    models.py
    static\
    templatetags\
        __init__.py
        blog_tags.py
    tests.py
    views.py

编写模板标签代码

接下来就是编写各个模板标签的代码了,自定义模板标签代码写在 blog_tags.py 文件中。其实模板标签本质上就是一个 Python 函数,因此按照 Python 函数的思路来编写模板标签的代码就可以了,并没有任何新奇的东西或者需要新学习的知识在里面。

最新文章模板标签

打开 blog_tags.py 文件,开始写我们的最新文章模板标签。

blog/templatetags/blog_tags.py

from ..models import Post

def get_recent_posts(num=5):
    return Post.objects.all().order_by('-created_time')[:num]

这个函数的功能是获取数据库中前 num 篇文章,这里 num 默认为 5。函数就这么简单,但目前它还只是一个纯 Python 函数,Django 在模板中还不知道该如何使用它。为了能够通过 {% get_recent_posts %} 的语法在模板中调用这个函数,必须按照 Django 的规定注册这个函数为模板标签,方法如下:

blog/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().order_by('-created_time')[:num]

这里我们首先导入 template 这个模块,然后实例化了一个 template.Library 类,并将函数 get_recent_posts 装饰为 register.simple_tag。这样就可以在模板中使用语法 {% get_recent_posts %} 调用这个函数了。

注意 Django 1.9 后才支持 simple_tag 模板标签,如果你使用的 Django 版本小于 1.9,你将得到一个错误。Django 1.9 以前的版本如何自定义模板标签这里不再赘述。

归档模板标签

和最新文章模板标签一样,先写好函数,然后将函数注册为模板标签即可。

blog/templatetags/blog_tags.py

@register.simple_tag
def archives():
    return Post.objects.dates('created_time', 'month', order='DESC')

这里 dates 方法会返回一个列表,列表中的元素为每一篇文章(Post)的创建时间,且是 Python 的 date 对象,精确到月份,降序排列。接受的三个参数值表明了这些含义,一个是 created_time ,即 Post 的创建时间,month 是精度,order='DESC' 表明降序排列(即离当前越近的时间越排在前面)。例如我们写了 3 篇文章,分别发布于 2017 年 2 月 21 日、2017 年 3 月 25 日、2017 年 3 月 28 日,那么 dates 函数将返回 2017 年 3 月 和 2017 年 2 月这样一个时间列表,且降序排列,从而帮助我们实现按月归档的目的。

分类模板标签

过程还是一样,先写好函数,然后将函数注册为模板标签。注意分类模板标签函数中使用到了 Category 类,其定义在 blog.models.py 文件中,使用前记得先导入它,否则会报错。

blog/templatetags/blog_tags.py

from ..models import Post, Category

@register.simple_tag
def get_categories():
    # 别忘了在顶部引入 Category 类
    return Category.objects.all()

尽管侧边栏有 4 项内容(还有一个标签云),但是这里我们只实现最新文章、归档和分类数据的显示,还有一个标签云没有实现。因为标签云的实现稍有一点不同,所以将在接下来的教程中专门介绍。这里你也可以尝试着自己解决,如果遇到问题,可以通过官方文档或者搜索引擎求助。独立思考并寻求解决方案以及善用搜索引擎是一个开发者必须培养的能力,只有这样你才能成为一个独立的开发者,独立地解决别人可能从来没有遇到过的问题。

使用自定义的模板标签

打开 base.html,为了使用模板标签,我们首先需要在模板中导入存放这些模板标签的模块,这里是 blog_tags.py 模块。当时我们为了使用 static 模板标签时曾经导入过 {% load staticfiles %},这次在 {% load staticfiles %} 下再导入 blog_tags:

templates/base.html

{% load staticfiles %}
{% load blog_tags %}
<!DOCTYPE html>
<html>
...
</html>

然后找到最新文章列表处,把里面的列表修改一下:

templates/base.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>
    {% empty %}
    暂无文章!
    {% endfor %}
  </ul>
</div>

这里我们通过使用 get_recent_posts 模板标签获取到最新文章列表,然后我们通过 as 语法(Django 模板系统的语法)将获取的文章列表保存进了 recent_post_list 模板变量中,之后就可以通过 for 循环来循环显示文章列表数据了,这和我们在写首页视图时是一样的。

然后是归档部分:

templates/base.html

<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>
    {% empty %}
    暂无归档!
    {% endfor %}
  </ul>
</div>

同样,这里我们调用 archives 模板标签自动获取一个已发表文章的日期列表,精确到月份,降序排列,然后通过 as 语法将其保存在 date_list 模板变量里。由于日期列表中的元素为 Python 的 date 对象,因此可以通过其 yearmonth 属性分别获取年和月的信息,<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 }} <span class="post-count">(13)</span></a>
    </li>
    {% empty %}
    暂无分类!
    {% endfor %}
  </ul>
</div>

<span class="post-count">(13)</span> 显示的是该分类下的文章数目,这个特性会在接下来的教程中讲解如何实现,目前暂时用占位数据代替吧。

现在运行开发服务器,可以看到侧边栏显示的数据已经不再是之前的占位数据,而是我们保存在数据库中的数据了。

注意:如果你按照教程的步骤做完后发现报错,请按以下顺序检查。

  1. 检查目录结构是否正确。确保 templatetags\ 位于 blog\ 目录下,且目录名必须为 templatetags。具体请对照上文给出的目录结构。
  2. 确保 templatetags\ 目录下有 __init__.py 文件。
  3. 确保使用的 Django 版本不小于 1.9。
  4. 确保通过 register = template.Library()@register.simple_tag 装饰器将函数装饰为一个模板标签。
  5. 确保在使用模板标签以前导入了 blog_tags,即 {% load blog_tags %}。注意要在使用任何 blog_tags 下的模板标签以前导入它。
  6. 确保模板标签的语法使用正确,即 {% load blog_tags %},注意 { 和 % 以及 % 和 } 之间没有任何空格。

总结

本章节的代码位于:Step10: side bar

如果遇到问题,请通过下面的方式寻求帮助。

  • 在下方评论区留言。
  • 将问题的详细描述通过邮件发送到 djangostudyteam@163.com,一般会在 24 小时内回复。
  • Pythonzhcn 社区的新手问答版块 发布帖子。

-- EOF --

最后更新:2018-11-11 12:21:12

91 条评论 / 54 人参与
花鼓戏芋头绳626

我的是一直在报错,不是{ % get categories as category_list % }这句话里面get 没有找到,就是{ % empty % }里面的empty找不到,没有定义什么的,稍微改正一下%和{的距离就好了,可能就是编译器一时间反应不过来{}和%之间的关系,识别不出,所以导致一直报错。希望可以给其他人一些帮助。。。


爱不曾释怀

为啥我的models没有objects的属性了,我在blog_tags.py里导入Post和Category的models后,在函数里返回Category.objects.all()的时候提示没有objects的属性,在models.py文件里也一样,但是我在shell里面却可以,有没有大佬知道是什么原因呀。提示如下:

Inspection info: This inspection detects names that should resolve but don't. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Top-level and class-level items are supported better than instance items.


爱不曾释怀 爱不曾释怀

这个提示是pycharm里面的


我打野不去中路 爱不曾释怀

在models.py文件中显示地加入一个管理器就行了即objects = models.ModelManager()就行了


看着地面打篮球 爱不曾释怀

@python_2_unicode_compatible 你的python是3.0+吧,加上这个装饰器就好了


llp182

也可以用自定义中间件的方法实现获取数据吧


阿米-Cc

请问这个在写的时候是有指定在浏览器中的位置吗,为什么这些内容就是在侧边栏,而不是在正中间什么的呢


WangYio

我在遇到django.template.exceptions.TemplateSyntaxError: 'blog_tags' is not a registered tag library.问题时

在settings.py文件中加入了'libraries'就ok了。

TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR,'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
'libraries':{
'blog_tags': 'blog.templatetags.blog_tags',
}
},
},
]

dismalstarshine WangYio

。。我也是这个报错,就是不知道怎么加setting,学习了


dismalstarshine dismalstarshine

又在下面找到说不用修改setting,

ctrl+c,重新python manage.py runserver就好了


WangYio dismalstarshine

感觉还是把模板注册进去更好些吧


张小二_tiny WangYio

这是什么原理?setting里的键值是不区分大小写的吗?


WangYio 张小二_tiny

应该是区分大小写的吧,就是把你自己写的模板标签注册到你的模板库里的意思。我是这么认为的,希望可以帮助到你。


Chen xianmin WangYio

我和你碰到的问题一样,后来发现是我的代码出问题了。我是这么解决的

blog_tags.py中

register = template.Library()  #而我写成register = template.library()


amou1758

确保使用的 Django 版本不小于 1.9。



我用的1.11.8的版本  按照步骤没有出错


wang

我看到有些同学出现了

  • django.template.exceptions.TemplateSyntaxError: 'blog_tags' is not a registered tag library. Must be one of:
  •  admin_list 
  • admin_modify 
  • admin_static 
  • admin_urls 
  • cache
  •  i18n 
  • l10n 
  • log 
  • static 
  • staticfiles
  •  tz

的错误,我出现这个错误的原因是我的应用名是myblog,并没有用老师的blog,然后我改名了之后用{% load myblog_tags %}就可以了


zhazhalaila

Django 2.0版本不需要

{% load blog_tags %}

写上反而会出错


lyg4795 zhazhalaila

2.0.3写上没错


keyones阳光积极正能量

操作报错

ValueError at /Cannot truncate TimeField 'created_time' to DateField. Request Method:GETRequest URL:http://localhost:8000/Django Version:2.0.1Exception Type:ValueErrorException Value:Cannot truncate TimeField 'created_time' to DateField. Exception Location:C:\Users\Keyones\AppData\Local\Programs\Python\Python36\lib\site-packages\django\db\models\functions\datetime.py in resolve_expression, line 193Python Executable:C:\Users\Keyones\AppData\Local\Programs\Python\Python36\python.exePython Version:3.6.3Python Path:['E:\\kesites', 'E:\\kesites', 


追梦人物 [博主] keyones阳光积极正能量

使用 DatetimeField 代替 TimeField 


wanconglei

TemplateSyntaxError at /Invalid block tag on line 86: 'empty'. Did you forget to register or load this tag?

请问为什么会出现empty Tag报错,没有用自定义 Tag之前没有报错啊。


半夏的距离 wanconglei

请问你解决了吗?我也碰到了这个问题


libaoshan55 半夏的距离

我也遇到了这个问题,应该在如何加这个empty