<学习笔记>从零开始自学Python-之-web应用框架Django( 九)自定义标签和过滤器

        Django 的模板语言自带了丰富的标签和过滤器,能满足常见的表现逻辑需求。

        但有时候这些内建的标签和过滤 器可能缺少我们需要的功能,这时候我们可以扩展模板引擎,使用 Python 自定义标签和过滤器,然后使用 {% load %} 标签加载,让自定义的标签和过滤器可在模板中使用。

1、创建模板库

自定义的模板标签和过滤器必须放在一个 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 的 实例。

2、自定义模板过滤器

自定义的过滤器其实就是普通的 Python 函数,接受一个或多个参数:

2.1 自定义过滤器流程

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, '')

2.2 案例:做一个判断成绩是否及格的过滤器

我们下面做一个自定义过滤器,用来过滤学生的分数,低于某个值就显示不及格。

先在标签里面把逻辑写好,其实就是写一个函数jige

from django import template

register = template.Library()


@register.filter(name='jige')        #可以装饰器形式调用,也可以register.filter('jige',jige)
def jige(value,arg):
    if value<arg:
        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<arg:
        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 效果:

猜你喜欢

转载自blog.csdn.net/qq_41597915/article/details/127754455