Fork me on GitHub

Django -- 自定义模板标签

这几天一直在折腾Django的博客项目,今天本想着做完收尾工作就传到Gayhub上,没想到遇一大坑,各种填坑ing……

django自定义模板标签

挖坑

先来简单介绍一下本人亲手挖的坑:
本博客项目templates中的所有页面都通过block标签继承自base.html母模板。其中继承了<title><keywords><discription>等内容。开发阶段这些内容都是直接写死在base.html母模板中了,现在准备上线,总不能让用户(假设有用户)去模板中改吧?所以我得将它写到数据库中,让用户可以在后台方便地设置博客的名称关键词描述等内容,各大博客也都是这么做的。燃鹅,事情并非那么简单,随之而来的是各种坑,作为处女座代码狗,我TM必须解决……

填坑

尝试了各种方法之后,终于完美解决了问题。用到了自定义模板标签(simple_tag)这个方法。详细解释在后文(踩坑)中,这里只做演示:
index应用中新建index_tags.py文件:

1
2
3
4
5
6
7
8
from django import template    # 自定义模板是template类下的对象,所以先引入
from index.models import Setting

register = template.Library() # 为对象起个别名

@register.simple_tag # 定义标签的类型
def get_setting(): # 定义标签
return Setting.objects.last() # 标签的作用,返回Setting数据库的一条记录

blog项目下的setting.py中改写以下几行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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': {
'index_tags': 'index.index_tags', # 注册index_tags这个库
}
},
},
]

base.html开头写上以下两行:

1
2
{% load index_tags %}    # 引入index_tags自定义库
{% get_setting as setting %} # 使用get_setting自定义模板函数,并定义别名为setting

然后我们就可以在base.html母模板中使用动态渲染数据库中的内容了。
其他页面只需继承base.html页面即可。例如文章页面中,描述应该是该文章的摘要,我们就用{% block discription %} {% endblock %}重载母模板;而标签页面中,描述无需更改,那就什么都不做呗。

一号坑,重写视图函数

最简单的思路就是在views.py中的各个视图函数中调取数据库,然后再渲染到各个模板中,模板中再通过block标签重写这部分内容。不行,上帝告诉我,不能这么做。一次次地调数据库,渲染模板,重写模板,页面渲染速度拉下一大截。Veto!

二号坑,渲染母模板

稍微拐个弯,很自然的就能想到,我们只需要改写base.html母模板,动态渲染数据库中的内容即可。Yo, 看似很完美,Thinkphp中就可以用此方法完美解决。燃鹅,在Django中此路不通。因为Django的视图函数是接收到request请求后再进行后续的操作,我们的base.html是在模板中直接继承的,根本没有发送额外的请求,使用就不能单独的进行逻辑处理。Veto!!

1
2
3
4
5
def archive(request):
articles = Article.objects.order_by('-post_time').all()
return render(request, 'archive.html', {'articles': articles})

# 例如这段代码是渲染文章内容的,可以看到必须要传入了一个请求(request)

三号坑,重写render方法

终于百度到一条可行的解决方法,思路很清奇。重写Django框架中的render方法,让它能动态渲染base.html。确实很牛,从根本解决了问题,强烈建议Django官方填了这个坑。但是我这儿还是不想用,因为重写render方法之后我依然需要改动许多调用render的地方。Veto!!!
django解决其他页面继承不了base模板的动态数据的问题

四号坑,伪造请求

前面不是提到Django需要请求才能渲染页面嘛,base.html又不能传入请求。突然想到,我们直接伪造一个请求传进去不就好了。奇技淫巧,还是不想这么干。Veto!!!!

五号坑,引入自定义模板标签

终于,谷歌到了一个方法,引入自定义标签。简单解释一下,base.html在是可以使用模板标签的,像{% if %}{% for %}这种。Django又提供了自定义模板标签,就是说我们可以自己造个模板标签,就叫{% get_setting %}吧,这个标签的功能就是从数据库获取博客名称博客关键词(存在Setting这个数据表中)这些内容。
一切突然光明了~查了下官方文档,开始操作。
index这个应用中新建一个index_tags.py文件:

1
2
3
4
5
6
7
8
9
10
# !注意,此为错误示范

from django import template # 自定义模板是template类下的对象,所以先引入
from index.models import Setting

register = template.Library() # 为对象起个别名

@register.template_tags # 定义标签的类型
def get_setting(): # 定义标签
return Setting.objects.last() # 标签的作用,返回Setting数据库的一条记录

然后在base.html开头导入这个库:

1
{% load index_tags %}

然后就报错了:

1
'index_tags' is not a registered tag library.

什么鬼,照着官方文档的方法没错啊?逛了圈谷歌后才发现,原来在模板中引入自定义库前需要先到项目的setting.py中注册这个库。Django官方文档怎么没说啊啊啊。
打开blog项目下的setting.py文件,改写如下几行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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': {
'index_tags': 'index.index_tags', # 注册index_tags这个库
}
},
},
]

六号坑,不存在分配标签

先说明一下,Django中有三种自定义标签,分别是:

  • simple_tag (简单标签) – 单次使用只能返回一层数据。在这里就是说,不能从数据库中返回一条记录作为实例再渲染,只能分别定义N个简单标签分别渲染N条字段。
  • inclusion_tag (内涵标签) – 可以在标签中传入参数,这里没用。
  • assignment_tag (分配标签) – 使用标签时可以定义别名,作为一个实例,然后可以渲染这个实例的各个对象。在这里就是说可以直接定义一个分配标签,然后在模板中定义一个别名“setting”,然后用 分配渲染。

承接上文,我们注册了index_tags.py这个库后终于可以在模板引入了。在模板中使用自定义标签{% get_setting %}。又报错了:

1
AttributeError: 'Library' object has no attribute 'assignment_tag'

什么鬼?找不到assignment_tag对象???还是照着官方的用法没错啊,网上也都是这个干的。走投无路,忍无可忍,翻了一下Django框架的源码。发现……Django2已经启用了assignment_taginclusion_tag标签,所有标签的功能都集中在了simple_tag上。想想也是啊,一个标签可以解决的事情,干嘛撑得用三个。Django官方改进功能值得表扬,但是你TM能不能顺便更新一下文档,您的最新文档不是误导我们嘛!差点气晕!Veto!!!!!!
填完各种天坑之后,终于完美解决了这个问题,头发掉了一大把,还是很开心。呵呵……呵呵呵……真是醉了……承接上文[填坑]

zhaoo wechat
0%