Django 的模板语言自带了丰富的标签和过滤器,能满足常见的表现逻辑需求。
但有时候这些内建的标签和过滤 器可能缺少我们需要的功能,这时候我们可以扩展模板引擎,使用 Python 自定义标签和过滤器,然后使用 {% load %} 标签加载,让自定义的标签和过滤器可在模板中使用。
自定义的模板标签和过滤器必须放在一个 Django 应用中。如果与现有应用有关,可以放在现有应用中;否则,应该专门创建一个应用存放。应用中应该有个 templatetags 目录,与 models.py、views.py 等文件放在 同一级。
在 templatetags 目录中创建两个空文件:__init__.py(告诉 Python 这是包含 Python 代码的包)和存放自定 义标签(过滤器)的文件。后者的名称是加载标签所用的名称。
例如,自定义的标签(过滤器)保存在 my_tags.py 文件中,那么在模板中要这么加载:
{% load my_tags %}
然后整个项目的目录结构应该如下:

{% load %} 标签在 INSTALLED_APPS 设置中查找包含自定义标签(过滤器)的应用,而且只从应用中加载模板库。这是一个安全特性,以便在同一台电脑中存贮多个模板库的 Python 代码,而不让每个 Django 实例都能访问。
如果模板库不与任何模型或视图绑定,Django 应用中可以只有 templatetags 包。事实上,这也相当常见。 templatetags 包中的模块数量不限。记住,{% load %} 语句加载的是指定的 Python 模块,而不是应用。
创建存放标签(过滤器)的 Python 模块之后,剩下的就是编写 Python 代码了。代码怎么写,取决于定义的是过滤器还是标签。一个有效的标签库必须有一个名为 register 的模块层变量,其值是 template.Library 的 实例。
自定义的过滤器其实就是普通的 Python 函数,接受一个或多个参数:
1). 变量的值(输入),不一定是字符串。
2). 参数的值,可以有默认值,也可以留空。 例如,对 {{ var|foo:"bar" }} 来说,传给 foo 过滤器的变量是 var,参数是 "bar"。
注意:因为模板语言没有提供异常处理功能,所以模板过滤器抛出的异常会以服务器错误体现出来。鉴于此,模板过滤器应该避免抛出异常,而是回落到其他合理的值。如果输入明显有问题,最好还是抛出异常,以免深埋缺陷。
定义一个过滤器的格式如下:
- def cut(value, arg):
- """注释内容"""
- 一些代码
- return value.replace(arg, '')
定义好过滤器之后,要使用 Library 实例注册,让 Django 的模板语言知道它的存在:
register.filter('cut', cut)
Library.filter() 有两个参数: 1. 过滤器的名称,一个字符串。 2. 负责处理过滤器的函数,一个 Python 函数(不是函数名称的字符串形式)。
register.filter() 也可以作为装饰器使用:
- @register.filter(name='cut')
- def cut(value, arg):
- return value.replace(arg, '')
我们下面做一个自定义过滤器,用来过滤学生的分数,低于某个值就显示不及格。
先在标签里面把逻辑写好,其实就是写一个函数jige
- from django import template
-
- register = template.Library()
-
-
- @register.filter(name='jige') #可以装饰器形式调用,也可以register.filter('jige',jige)
- def jige(value,arg):
- if value
- out = '不及格'
- else:
- out = value
- return out
然后在class3.html中引用这个标签
- {% extends "base.html" %}
- {% block title %}武侠三班的小窝{% endblock %}
- {% load my_tags %}
- {% block main %}
- <h1>通知h1>
- <p>本次中期比武,{{ student_list.0.name }}获得了第一名p>
- <p>获得优秀的同学还有{{student_list.1.name}}、{{student_list.2.name}}、{{student_list.3.name}}等p>
- <p>希望其他同学以他们为榜样,苦练武功,争取下次比武取得好成绩p>
- <ul>
- {% for s in student_list %}
- {% if s.sex == 'male' %}
- // 设定80分以下为不及格
- <li>{{ s.name }}大侠的成绩为{{s.score|jige:90}}li>
- {% else %}
- <li>{{ s.name }}女侠的成绩为{{s.score|jige:80}}li>
- {% endif %}
- {% endfor %}
- ul>
- <p>落款:你们尊敬的老师<br />{{ teacher }}p>
- <p>日期: {{ now|date:"D d M Y" }} p>
- {% endblock %}
为了更好地显示效果,我们把之前views里面的 addstudent 函数改造一下,初始化一些学生的数据:
- def addstudent(request):
- Student.objects.all().delete()
- s1=Student(id=1,name='杨过',age=18,sex='male',score=98)
- s1.save()
- s2=Student(id=2,name='小龙女',age=20,sex='female',score=86)
- s2.save()
- s3=Student(id=3,name='黄药师',age=18,sex='male',score=95)
- s3.save()
- s4=Student(id=4,name='洪七公',age=20,sex='male',score=86)
- s4.save()
- s5=Student(id=5,name='郭靖',age=18,sex='male',score=92)
- s5.save()
- s6=Student(id=6,name='黄蓉',age=20,sex='female',score=86)
- s6.save()
- s7=Student(id=7,name='周芷若',age=17,sex='female',score=76)
- s7.save()
- s8=Student(id=8,name='欧阳锋',age=20,sex='male',score=96)
- s8.save()
- s9=Student(id=9,name='丘处机',age=18,sex='male',score=75)
- s9.save()
- s10=Student(id=10,name='郭襄',age=20,sex='female',score=86)
- s10.save()
- s11=Student.objects.create(id=11,name='胡一刀',age=16,sex='male',score=99)
- return HttpResponse('{}、{}、{}、{}、{}、{}、{}、{}、{}、{}、{}等同学已经添加成功'.format(s1.name,s2.name,s3.name,s4.name,s5.name,s6.name,s7.name,s8.name,s9.name,s10.name,s11.name))
再把classnotice函数改造一下:
- def classnotice(request):
- now=datetime.datetime.now()
- context={}
- context['student_list']=Student.objects.all().order_by('-score')
- context['teacher']='王重阳'
- context['now']=now
-
- return render(request,'class3.html',context=context,status=200)
好了,我们访问网站,先访问 http://127.0.0.1:8000/add/,更新学生数据
然后访问 http://127.0.0.1:8000/notice,得到如下结果:

可以看到分数低于80的会显示为“不及格”。
2.3 期待字符串的模板过滤器
如果模板过滤器期望第一个参数是字符串,应该使用 stringfilter 装饰器。这样,对象在传给过滤器之前会 先转换成字符串值。
- from django import template
- from django.template.defaultfilters import stringfilter
- register = template.Library()
- @register.filter
- @stringfilter
- def lower(value):
- return value.lower()
这里,可以把整数传给过滤器,不会抛出 AttributeError(因为整数没有 lower() 方法)。
2.4 过滤器和时区
自定义处理 datetime 对象的过滤器时,注册时通常要把 expects_localtime 旗标设为 True:
- @register.filter(expects_localtime=True)
- def businesshours(value):
- try:
- return 9 <= value.hour < 17
- except AttributeError:
- return ''
这样设定之后,如果过滤器的第一个参数是涉及时区的日期时间,根据模板中的时区转换规则,必要时 Django 会先把它转换成当前时区,然后再传给过滤器。
3、自定义模板
标签要比过滤器复杂一些,Django 提供了一些快捷方式,简化了多数标签类型的编写。
3.1 简单的标签
很多模板标签接受几个参数(字符串或模板变量),对输入参数和一些外部信息做些处理之后返回一个结果。Django 提供了一个辅助函数,simple_tag。它是 django.template.Library 中的一个方法,其参数是一个接受任意个参数的函数,把它包装在 render 函数中之后,再做些前述的必要处理,最后注册到模板系统中。
比如说我们做一个实现除法的标签(Django其实没有默认的除法标签),在my_tags.py中写一个标签函数chufa:
- @register.simple_tag
- def chufa(a,b):
- c=a/b
- return c
然后去class3.html中调用这个标签,实现把成绩从百分制换算成10分制:
- {% extends "base.html" %}
- {% block title %}武侠三班的小窝{% endblock %}
- {% load my_tags %}
- {% block main %}
- <h1>通知h1>
- <p>本次中期比武,{{ student_list.0.name }}获得了第一名p>
- <p>获得优秀的同学还有{{student_list.1.name}}、{{student_list.2.name}}、{{student_list.3.name}}等p>
- <p>希望其他同学以他们为榜样,苦练武功,争取下次比武取得好成绩p>
- <ul>
- {% for s in student_list %}
- {% if s.sex == 'male' %}
- <li>{{ s.name }}大侠的成绩为 {% chufa s.score 10 %}li>
- {% else %}
- <li>{{ s.name }}女侠的成绩为 {% chufa s.score 10 %}li>
- {% endif %}
- {% endfor %}
- ul>
- <p>落款:你们尊敬的老师<br />{{ teacher }}p>
- <p>日期: {{ now|date:"D d M Y" }} p>
- {% endblock %}
然后访问 http://127.0.0.1:8000/notice/ , 看看效果:

3.2 赋值标签
有时候我们希望 simple_tag() 的运算结果储存起来,以供别的地方使用,而不直接输出。
只要在标签中用 as 参数把结果储存为一个变量即可
比如前面所举的 chufa 函数可以这样引用:
- <ul>
- {% for s in student_list %}
- {% if s.sex == 'male' %}
- <li>{{ s.name }}大侠的成绩为 {% chufa s.score 10 %}li>
- {% else %}
- {% chufa s.score 10 as jg %}
- <li>{{ s.name }}女侠的成绩为 {{jg}} li>
- {% endif %}
- {% endfor %}
- ul>
这样 jg 这个变量就储存了chufa 标签计算的结果,可以在网页任何地方用{{jg}}把它引用出来。
3.3 从头构建自定义标签
3.3.1 基本原理
模板系统的运作分为两步:编译和渲染。因此,自定义模板要指定如何编译和渲染。
Django 编译模板时,把原始模板分解为一个个“节点”。节点是 django.template.Node 实例,有一个 render() 方法。编译好的模板其实就是一些 Node 对象。 在编译好的模板对象上调用 render() 方法时,模板在节点列表中的各个 Node 对象上调用 render() 方法,并传入指定的上下文。最后,把各个节点的输出拼接在一起,组成模板的输出。因此,自定义模板标签时,要 指定如何把原始模板转换成 Node 对象(编译),并且指定节点的 render() 方法要做什么(渲染)。
我们这次从头做一个乘法的标签。
3.3.2 编写编译函数
模板解析器遇到模板标签时,调用一个 Python 函数,并且传入标签的内容和解析器对象自身。这个函数负责 根据标签的内容返回一个 Node 实例。
我们先写一个实现乘法的函数,假设我们希望在HTML中这样使用它
<p>{% chengfa numA numB %}p>
我们写一个编译函数,用于编译原始模板文件:
- def chengfa(parser, token):
- try:
- tag_name,numA,numB = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError("%r tag requires 2 argument" % token.contents.split()[0])
- return chengfaNode(numA,numB)
这里最终返回三个值,分别是tag标签名称,参数1 和 参数2
这里有一些注意事项:
• parser 是模板解析器对象。这里用不到。
• token.contents 是标签的原始内容字符串。这里是 'chengfa s.score 10'。
• token.split_contents() 在空格处把标签的名称和参数分开,不过放在引号内的字符串保持不动。token.contents.split() 简单一些,但是不够可靠,它会在所有空格处拆分,包括引号内的空格。最好始终使用 token.split_contents()。
• 遇到句法错误时,这个函数会抛出 django.template.TemplateSyntaxError,并且提供有用的消息。
• TemplateSyntaxError 异常用到了 tag_name 变量。别在错误消息中硬编码标签的名称,避免标签的名称与函数耦合。token.contents.split()[0] 的值始终是标签的名称,即使标签没有参数。
• 这个函数返回一个 chengfaNode 对象,它具有节点需要知道的关于这个标签的一切信息。这里是返回两个乘数 numA 和 numB 作为chengfaNode的参数
3.3.3 编写渲染器
自定义标签的第二步是定义一个具有 render() 方法的 Node 子类。
我们继续编写一个chengfaNode类:
- class chengfaNode(template.Node):
- def __init__(self,a,b):
- self.a=a
- self.b=b
- def render(self, context):
- numa = int(a)
- numb = int(b)
- return numa*numb
注意事项:
• __init__() 从chengfa() 中获取 numA 和 numB。节点的选项或参数都通过 __init__() 传递。
• 具体的工作在 render() 方法中做。
• 一般来说,render() 应该静默问题,尤其是在 DEBUG 和 TEMPLATE_DEBUG 设为 False 的生产环境。然而,有些情况下,尤其是 TEMPLATE_DEBUG 设为 True 时,render() 方法可以抛出异常,以便调试。例如,有几个内置的标签在收到错误的参数数量或类型时抛出 django.template.TemplateSyntaxError。 这种编译和渲染的解耦得到的是高效的模板系统,因为无需多次解析一个模板就可以渲染多个上下文。
3.3.4 把模板变量传给标签
但是上面这个类有个问题,因为token.contents解析得到的是字符串,而实际上‘s.score’是一个变量,如果让 它 直接乘以10 肯定会报错,那么怎么把模板变量传给标签呢?
django.template 包中有一个 Variable() 类可以实现这个功能。
Variable() 类的用法很简单,先使用变量的名称实例化,然后调用 variable.resolve(context)。
我们把上面chengfaNode的类改写一下:
- class chengfaNode(template.Node):
- def __init__(self,a,b):
- self.a=template.Variable(a)
- self.b=template.Variable(b)
- def render(self, context):
- numa = int(self.a.resolve(context))
- numb = int(self.b.resolve(context))
- return numa*numb
这样的话不管输入的 a,b 是变量还是具体的数值(字符串形式)都可以正确获取到值了。
3.3.5 在上下文中设定变量
上述示例只是输出值。一般来说,设定模板变量的标签比输出值的标签更灵活。这样,模板编写人便可以复用编码标签创建的值。若想在上下文中设定变量,在 render() 方法中像字典那样为上下文中的变量赋值。
下 面是 chengfaNode 的更新版本,设定 chengfa_result 模板变量,而不输出:
- class chengfaNode(template.Node):
- def __init__(self,a,b):
- self.a=template.Variable(a)
- self.b=template.Variable(b)
- def render(self, context):
- numa = int(self.a.resolve(context))
- numb = int(self.b.resolve(context))
- context['chengfa_result'] = numa * numb
- return ''
注意,render() 方法返回了一个空字符串。render() 始终应该返回一个字符串。如果模板标签只设定变量, render() 应该返回一个空字符串。这个新版本的用法如下:
- {% chengfa s.score 10 %}
- <p> 成绩换算成千分制为 {{ chengfa_result }}p>
3.3.6 注册标签
最后别忘了像之前那样注册标签,同样可以用两种方法:
A) register.tag('chengfa', chengfa)
tag() 方法有两个参数:
1. 模板标签的名称,一个字符串。如果留空,使用编译函数的名称。
2. 编译函数,一个 Python 函数(不是函数名称的字符串形式)。
B) 作为装饰器使用:
- @register.tag(name="chengfa")
- def chengfa(parser, token):
- ......
- ......
最后贴一下完整my_tags.py 代码 和访问效果
- from django import template
-
- register = template.Library()
-
-
- @register.filter(name='jige')
- def jige(value,arg):
- if value
- out = '不及格'
- else:
- out = value
- return out
-
- @register.simple_tag
- def chufa(a,b):
- c=a/b
- return c
-
-
-
-
-
-
- @register.tag(name="chengfa")
- def chengfa(parser, token):
- try:
- tag_name,numA,numB = token.split_contents()
- except ValueError:
- raise template.TemplateSyntaxError("%r tag requires 2 argument" % token.contents.split()[0])
- return chengfaNode(numA,numB)
-
-
- class chengfaNode(template.Node):
- def __init__(self,a,b):
- self.a=template.Variable(a)
- self.b=template.Variable(b)
- def render(self, context):
- numa = int(self.a.resolve(context))
- numb = int(self.b.resolve(context))
- return numa*numb
class3.html
- {% extends "base.html" %}
- {% block title %}武侠三班的小窝{% endblock %}
- {% load my_tags %}
- {% block main %}
- <h1>通知h1>
- <p>本次中期比武,{{ student_list.0.name }}获得了第一名p>
- <p>获得优秀的同学还有{{student_list.1.name}}、{{student_list.2.name}}、{{student_list.3.name}}等p>
- <p>希望其他同学以他们为榜样,苦练武功,争取下次比武取得好成绩p>
- <ul>
- {% for s in student_list %}
- {% if s.sex == 'male' %}
- <li>{{ s.name }}大侠的成绩为 {% chengfa s.score 10 %}li>
- {% else %}
- {% chufa s.score 10 as jg %}
- <li>{{ s.name }}女侠的成绩为 {{jg}} li>
- {% endif %}
- {% endfor %}
- ul>
- <p>落款:你们尊敬的老师<br />{{ teacher }}p>
- <p>日期: {{ now|date:"D d M Y" }} p>
- {% endblock %}
访问 http://127.0.0.1:8000/notice 效果:

-
相关阅读:
Lua语法之简单变量
什么是GIL锁,有什么作用?python的垃圾回收机制是什么样的?解释为什么计算密集型用多进程,io密集型用多线程。
04-React脚手架
玫瑰通行证发放中!
32岁事业无成,我终于选择放过自己了
Jenkins部署springboot项目至远程服务器
解决ubuntu报错:No such file or directory
I/O Passthru: Upstreaming a flexible and efficient I/O Path in Linux——泛读笔记
飞凌嵌入式受邀亮相2023中国国际数字经济博览会
提高编程效率-Vscode实用指南
-
原文地址:https://blog.csdn.net/qq_41597915/article/details/127754455