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

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


88 条评论 / 51 人参与
爱不曾释怀

为啥我的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里面的


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


Domon_Lee

各位使用Templatetags没有生效的朋友,可以参考下这个https://stackoverflow.com/questions/40686201/django-1-10-1-my-templatetag-is-not-a-registered-tag-library-must-be-one-of
需要重启Django的服务器。


Pnorest Domon_Lee

stackoverflow的这个链接第二个有效,在settings的.py的templates中做自定义标签的引入


y正在输入 Pnorest

您好 可以写一下自定义标签引入在本教程里的代码吗 我看了那个但是一直都试不对= =新手入门,请您多多指教!


陈振兴

对于@register.simple_tag这里

我在django 1.8.2环境中要用@resister.assignment_tag 赋值标签,才能 as 给其他变量.否则会报参数过多错误,有跟我一样问题的吗


追梦人物 [博主] 陈振兴

版本兼容性问题,你的方法是对的。


Cecilia

为什么侧边栏显示暂无文章啊

<div class="widget widget-recent-posts">
                    <h3 class="widget-title">最新文章</h3>
                    {% get_recent_posts as recent_post_list %}
                    <ul>
                        {% for post in rencent_post_list %}
                        <li>
                            <a href="{{post.get_absolute_url}}">{{post.title}}</a>
                        </li>
                        {% empty %}
                          暂无文章
                        {% endfor %}
                    </ul>
                </div>
# simple_tag是一个模板标签
@register.simple_tag
def get_recent_posts(num=5):
return Post.objects.all().order_by('-create_time')[:num]


小文心剑飞 Cecilia

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


小文心剑飞 Cecilia

我重新复制了一下博主的代码,可以了


Cecilia 小文心剑飞

我也是重新复制,不过我觉得这样好尴尬。


小文心剑飞 Cecilia

恩,不知道为什么,一样的代码


侯梓煜 Cecilia

你们代码不好用的时候是手打的么还是说复制上面去的?


fshgrym Cecilia

没有查到数值吧


小文心剑飞

博主,你好!为什么我的侧边栏都显示“暂无文章!”“暂无归档!”“暂无分类!”?


Cecilia 小文心剑飞

想问一下啊,你解决了嘛,我也遇到了这个问题


小文心剑飞 Cecilia

没有


xueshichaoya
博主请教个问题,这个模板标签,归档部分有没有好的办法同时获得记录数?例如:2017-08(10条) 这样

追梦人物 [博主] xueshichaoya
参考文档 annotate 以及 aggregate 部分,不过我没有细致研究过。

米粥fly怕素色pool
博主大大 按照你的步骤操作了一遍,页面上没出现侧边栏,也没报错。。。。什么情况啊?

米粥fly怕素色pool 米粥fly怕素色pool
我知道了。。

切了特空
重启服务没用,重启电脑就可以了。。。

木犀草号
博主前辈:
InvalidTemplateLibrary at /
Invalid template library specified. ImportError raised when trying to load 'blog.templatetags.blog_tags': cannot import name 'templete'

为什么 cannot import name 'templete' ??

木犀草号 木犀草号
啊········我把template 写成了 templete!!!!

静静地林子中有雨
博主,
from ..models import Post, Category
报错如下:
from ..models import Post, Category
SystemError: Parent module '' not loaded, cannot perform relative import
请问怎么解决呢?目录结构是对的

真真正正正正 静静地林子中有雨
请问下你这个问题解决了么,我也遇到了同样问题

KOI科少 静静地林子中有雨
如果你是右边栏里的分类没有信息的话
那么你应该检查下你get_recent_posts里的方法里是否return了
def get_recent_post():
return Post.objects.all().order_by('-created_time')
单独去运行python blog_tags.py是会报这个错误的 我也没有解决
但是对于项目来说没有影响貌似 只要跟着上面教程做然后记得有返回值就可以返回数据库中的数据列表了

追梦人物 [博主] 静静地林子中有雨
py3么?templatetags 的目录结构是不是和项目中的一样?

LincolnBurrows2017
博主 我也是重启之后没有反应

LincolnBurrows2017 LincolnBurrows2017
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

没错我就是菊花侠 LincolnBurrows2017
需要在settings里面加上STATIC_URL = '/templatetags/'

Cecilia 没错我就是菊花侠

可是我打开setting的时候发现已经有

STATIC_URL = '/static/'

继续加上去的话,不会覆盖嘛


Stallionshell Cecilia

见评论末尾,重启服务即可解决,无需修改settings


JBKylin
TemplateSyntaxError at /

'./blog_tags' is not a valid tag library: Template library ./blog_tags not found, tried django.templatetags../blog_tags,django.contrib.admin.templatetags../blog_tags,django.contrib.staticfiles.templatetags../blog_tags
请问这是怎么回事,反复试了好几次都有问题

追梦人物 [博主] JBKylin
重启一下服务器看看

lucahai
好像markdown不能支持[]()蓝字链接语法?

追梦人物 [博主] lucahai
支持呀,是不是格式不正确?

lucahai 追梦人物 [博主]
复制我自己在简书的文章过来,只有[]()不行...

lucahai 追梦人物 [博主]
您好,实际上应该是结果可以插入点开链接,但是字是黑色的不会变成蓝色,我误以为没有用,和其他文本颜色一样的话确实也很难知道可以点击

追梦人物 [博主] lucahai
额,这个主题貌似就是这样,可以自己改改css链接颜色。

lucahai 追梦人物 [博主]
谢谢~

chenzhixiang1992 lucahai
谢谢

erwwwwwwww
我遇到了个问题:
'created_time' is a DateTimeField, not a DateField.
暂时的处理方法是:
在Django项目的settings.py文件中,可以直接设置为“USE_TZ = False”

追梦人物 [博主] erwwwwwwww
你需要传入完整的 datetime 对象。

Chen yongle erwwwwwwww
return Post.objects.dates('created_time', 'month', order='DESC')
这里dates改成datetimes,接着报错缺少pytz。安装pytz之后页面正常

木二Lin Chen yongle
我的提示安装后我安装了还是报错,重启服务器也不行啊

keyones阳光积极正能量 erwwwwwwww

请问这个最后怎么解决?


逐殇小彬
TemplateSyntaxError at /
Invalid block tag on line 80: 'get_recent_posts'. Did you forget to register or load this tag?
-----------这边的base.xml部分内容为: 是有什么没有定义?

最新文章


{% get_recent_posts as recent_post_list %}

    {% for post in recent_post_list %}

  • {{ post.title }}

  • {% empty %}
    暂无文章!
    {% endfor %}


追梦人物 [博主] 逐殇小彬
你重启一下服务器试试。

rebortyang 逐殇小彬
请问你的问题解决了吗? 我也是相同的问题,找了一天都没有解决方法,希望你能给给回复,谢谢

逐殇小彬 rebortyang
先试一下重启 这个问题我忘记记录了 忘记当时是怎么处理完成的 有可能是模板有问题 你那边可以试一下替换博主的本教程的模板内容看一下

shitianyi
请问一下,这段代码如果放在django1.8版本上该怎样修改?另外,我看django-1.8的手册上有simple_tag,为什么说django1.9以上才支持呢?

追梦人物 [博主] shitianyi
1.9 之后才支持 as 语法,1.9以前你可以把 simple tag 改为 assignment tag

shitianyi 追梦人物 [博主]
原来如此,谢谢博主。

gleesu
add:需要重启服务器,才会生效。否则,报错:'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

楔子_菜菜 gleesu
对,需要重启。

King-Ye gleesu
重启服务器后还是报错,请问可能会是什么情况?
'blog_tags' is not a registered tag library. Must be one of: admin_list admin_modify admin_static admin_urls blog_tag cache i18n l10n log static staticfiles tz

追梦人物 [博主] King-Ye
看看你的项目结构。

追梦人物 [博主] King-Ye
博客不方便粘贴代码的话请发在 http://pythonzh.cn/category/django/

King-Ye 追梦人物 [博主]
最近在期末考试,考完了之后我能直接在QQ上问您吗?

追梦人物 [博主] King-Ye
嗯,可以的

饭团丶3 追梦人物 [博主]
我也是同样的问题,重启不惯用,版本1.11
'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

追梦人物 [博主] 饭团丶3
按照教程中的检查提示做了还是没用么?

没错我就是菊花侠 饭团丶3
在base.html中引用的{% load blog_tags %}需要指明blog_tags的路径,在setting.py里面加上STATIC_URL = '/templatetags/'即可

追梦人物 [博主] 没错我就是菊花侠
blog_tags 是模板标签,这种做法是错误的。只要 blog_tags 所在的 app 已经加入了 INSTALLED_APPS 列表,django 应该就能导入这个模板标签的。

edsszw 追梦人物 [博主]

这种情况主要重启一下本地服务器就好了。不需要更改  STATIC_URL = '/static/'


大圣______ edsszw

重启和修改static都试过了,都不管用


大圣______ 大圣______

已解决,tempatetags放错目录了。


BastinT edsszw

重启服务器也不一定有用,可能突然就好了。也不知道是什么原因


zzfeng2012 BastinT

浏览器缓存...