django-templates

模板内容总结:

1.基础

2.locals
3.include
4.模板继承
5.使用RequestContext对上下文内容进行重用
6.HTML代码自动转义(auto-escaping)
7.扩展模板系统
Template加载机制
扩展你的模板系统
    - 创建模板库
    - 实现自定义过滤器
        - 1. 创建register变量
        - 2. 定义过滤器函数
        - 3. 注册过滤器函数
    - 实现自定义tag
        - 了解模板编译过程
        - 创建tag实战
            - 1. 定义Node节点类,实现render方法
            - 2. 创建Compilation函数
            - 3. 注册tag
            - 4. 运行
        - 复杂的实现自定义tag的其他几种方法
            - 1. 在Node类的render函数中设置context
            - 2. 实现块作用区域的tag
            - 3. 在块作用tag中保留context内容
            - 4. 快速创建简单tag的方法
            - 5. 创建Inclusion Tag
创建自定义模板加载类
 
模板使用基础
 
模板基本由两个部分组成,一是HTML代码,二是逻辑控制代码。
逻辑控制的实现又基本由三个部分组成:
1. 变量的使用
{{ person_name }} #使用双大括号来引用变量
2. tag的使用
{% if ordered_warranty %} #使用大括号和百分号的组成来表示使用Django提供的template tag
{% for item in item_list %}
<li>{{ item }}</li>
{% endfor %}
3. filter的使用
{{ ship_date|date:"F j, Y" }},ship_date变量传给data过滤器,data过滤器通过使用
"F j, Y"这几个参数来格式化日期数据。"|"代表类似Unix命令中的管道操作。
Template system不仅仅可以和view进行合作,也可以自己独立使用。
最基本的使用方法是:
1. 使用模板代码字符串作为参数,创建一个Template类
2. 创建模板代码中所需要的上下文Context对象,包含所需要的各个引用参数的值
3. 调用Template.render()方法,把template渲染成一个完整的字符串。
>>> from django import template
>>> t = template.Template('My name is {{ name }}.')
>>> c = template.Context({'name': 'Adrian'})
>>> print t.render(c)
>>> My name is Adrian.
 
还可以在template代码中使用dict索引,然后在context传入所需要的dict
>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
u'Sally is 43 years old.'
 
还可以使用函数,不过只能使用无参数的函数
>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
u'hello -- HELLO -- False'
 
还可以使用列表索引,但是item.-1是不被允许的
>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas', 'carrots']})
>>> t.render(c)
u'Item 2 is carrots.'
 
以上的使用方法被称为Dot Lookup方法。
 
使用dot lookup的访问函数功能时,需要注意的问题:
1. 当在模板代码中执行的函数抛出异常时,会一直向上层传播,除非这个异常中有一个参数
silent_variable_failure=True; 这样的话,这个出错的函数信息就会被渲染成空字符串。
>>> class SilentAssertionError(AssertionError):
... silent_variable_failure = True
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
u'My name is .'
 
2. 很明显,调用函数会产生一些不好的结果,安全漏洞之类的,如果你有一个BankAccout,
然后在模板中写成{{ account.delete }}, 这样在渲染的时候,你的账号就被删除了。。。。
所以要在修改一下你的delete函数
def delete(self):
# Delete the account
delete.alters_data = True#缩进没有问题,把delete看成一个对象,设置它的alters_data属性。
这样在渲染的时候,就会变成failed silent。
 
当在渲染的时候,简单的key值没有找到时,会failed silent,变成空字符串,而不是大动干戈的
报错。
>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
u'Your name is .'
>>> t.render(Context({'var': 'hello'}))
u'Your name is .'
 
Context对象也可以进行增删改值的操作。
>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c['foo']
'bar'
>>> c['newvariable'] = 'hello'
>>> del c['foo']
>>> c['foo']
 
使用python manage.py shell启动python交互式命令行窗口与一般直接启动python自带的
交互式命令行窗口的区别是前者会通过找一个DJANGO_SETTINGS_MODULE环境变量,
告诉Django导入settings.py的配置信息。
 

基本的tag和filter的用法

tag:
  • {% if %}的使用
可以使用and, or, not来组织你的逻辑。但不允许and和or同时出现的条件语句中。
没有{% elif %}这样的用法,只能用嵌套来实现多重if语句。
{% if athlete_list %}
<p>Here are the athletes: {{ athlete_list }}.</p>
{% else %}
<p>No athletes are available.</p>
{% if not coach_list %}
<p>Here are the coaches: {{ coach_list }}.</p>
{% endif %}
{% endif %}
  • {% for %} 的使用
用来循环一个list,还可以使用resersed关键字来进行倒序遍历,一般可以用if语句来先
判断一下列表是否为空,再进行遍历;还可以使用empty关键字来进行为空时候的跳转。
{% for athlete in athlete_list resersed %}
<li>{{ athlete.name }}</li>
{% empty %}
<p>There are no athletes. Only computer programmers.</p>
{% endfor %}
 
for tag还提供了一些内置参数来提供模板循环的信息。
1. forloop.counter 当前循环计数,从1开始
{% for item in todo_list %}
<p>{{ forloop.counter }}: {{ item }}</p>
{% endfor %}
2. forloop.counter0 当前循环计数,从0开始,标准索引方式
3. forloop.revcounter 当前循环的倒数计数,从列表长度开始
4. forloop.revcounter0 当前循环的倒数计数,从列表长度减1开始,标准
5. forloop.first bool值,判断是不是循环的第一个元素
6. forloop.last 同上,判断是不是循环的最后一个元素
7. forloop.parentloop 用在嵌套循环中,得到parent循环的引用,然后可以使用以上的参数
{% for country in countries %}
<table>
{% for city in country.city_list %}
<tr>
<td>Country #{{ forloop.parentloop.counter }}</td>
<td>City #{{ forloop.counter }}</td>
<td>{{ city }}</td>
</tr>
{% endfor %}
</table>
{% endfor %}
  • ifequal和ifnotequal,一看就是直接比较值的tag,需要两个参数,用法比较有限,
  • 只限于字符串,整数,小数的比较,什么列表,字典,元组不支持。
{% ifequal user currentuser %}
<h1>Welcome!</h1>
{% ifequal section "community" %}
<h1>Community</h1>
{% endifequal %}
{% endifequal %}
  • {# #},模板中注释的用法,只能用在一行
  • 如果要使用多行注释,要使用{% comment %}
{# This is a comment #}
{% comment %}
This is a
multi-line comment.
{% endcomment %}
filter:
filter用于变量在显示之前的一些简单的处理。使用类似管道的操作符"|",也可以进行链式操作
{{ name|lower }}
{{ my_list|first|upper }}
{{ bio|truncatewords:"30" }}
介绍几个重要的filter:
  • addslashes :给任何的反斜线,单引号,双引号,再加一个反斜线。在文本中含有javascript字符串的时候有用。
  • date :用来对data和datatime对象的字符串信息进行格式化。
  • {{ pub_date|date:"F j, Y" }}
  • length :返回变量的长度。

在view中使用template:

首先在settings.py中配置模板文件的路径。
TEMPLATE_DIRS = (
'/home/django/mysite/templates',
)
记住一个路径的时候要使用逗号,这样是来区分是一个tuple还是一个block expression
也可以在设置的时候使用python文件路径操作代码:
import os.path
 
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'),
)
 
然后,可以在view中使用模板
from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponse
import datetime

def current_datetime(request):
now = datetime.datetime.now()
t = get_template('current_datetime.html')
html = t.render(Context({'current_date': now}))
return HttpResponse(html)
 
大多数情况下,你会使用一种shortcut方法,render_to_response()去完成以上的工作。
from django.shortcuts import render_to_response
import datetime

def current_datetime(request):
now = datetime.datetime.now()
return render_to_response('current_datetime.html', {'current_date': now})
 

模板渲染

一旦你创建一个 Template 对象,你可以用 context 来传递数据给它。 一个context是一系列变量和它们值的集合。

context在Django里表现为 Context 类,在 django.template 模块里。 她的构造函数带有一个可选的参数: 一个字典映射变量和它们的值。

locals()的小技巧
如果你有很多变量要传给render,一个一个构造dict元素很麻烦。直接把变量名改成模板中所需要的变量名,
再使用locasl()函数,轻松搞定
def current_datetime(request):
current_date = datetime.datetime.now()
return render_to_response('current_datetime.html', locals())
locals()会返回局部空间中变量信息的dict,直接可以传给render,但有一点需要注意,它会返回把有的局部变量
信息,有一些可能不需要用到,如request变量。
 
{% include %}的使用
{% include 'nav.html' %},用来引入其它模板的内容,减少重复的模板代码
{% include template_name %},还可以使用变量名
如果include的模板文件没有找到,当DEBUG为真时,会报错TemplateDoesNotExist,当为假时,页面那一块为空白。 
诚然,include可以有效减少模板的重复代码。但一种更优雅的方式是:
template inheritance.
 
首先,创建base.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html lang="en">
<head>
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<h1>My helpful timestamp site</h1>
{% block content %}{% endblock %}
{% block footer %}
<hr>
<p>Thanks for visiting my site.</p>
{% endblock %}
</body>
</html>
 
我们使用一个新的tag,{% block %}用来告诉template engine,这个部分会被子模板
来实现。如果子模板没有实现这些部分,就会默认使用父模板的代码。
 
再看看,子模板要怎么写:
{% extends "base.html" %} 
{% block title %}The current time{% endblock %}
{% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
只需要先使用{% extends %}继承父模板,再把相应需要实现的部分写上所需要的内容。
{% extends template_name %}也可以使用变量名来实现动态。
jumpserver对模板的使用堪称典范
 
模板继承的三层继承策略:
1. 创建一个base.html,用来设置外观
2. 为网站的每一个部分,创建base_SECTION.html,比如base_phote.html, base_forum.html
3. 为每一个页面创建自己的模板。
 
1.模板继承
#base.html
<html><meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link type="text/css" rel="stylesheet" href="/static/css/base.css"/>
<title>{%block title %} {%endblock%}</title>
<body>
<h1>后台管理</h1><p class="path"> 当前位置:{%block path%}{%endblock%} <span>欢迎你:{{admin}} <a href="#">注销</a></span></p> {%block content%}{%endblock%} </body>
</html>

#piclist.html

{%extends "base.html"%}
{%block title %}图片列表页面{%endblock%}
{%block path%}图片列表{%endblock%}
{%block content%}
内容
{%endblock%}
模板学习笔记

1.模板的简单使用:

from django import template
t = template.Template('My name is {{ name }}.')
c = template.Context({'name':'nick'})
print t.render(c)

创建一个Template对象,实例化。默认构造参数 直接接受字符串作为模板内容。

其中{{}}表示模板的变量。

一旦你创建了Template模板实例,你可以用Context来传递参数给它。

格式 Context({'name':'nick'}) 第一个参数为模板变量的映射,第二个为要传递的值。并以 “:”分开,如果是多个集合用“,”分隔。

最后调用Template对象的 render()方法,并用context对象来填充模板。 

同一个模板对象,可对应多个上下文对象。 

 

如果遍历一个结构对象,可以使用如下方法。

from django.template import Template,Context
person = {'name':'sally','age':'43'}
t = Template('{{ person.name }} is {{ person.age }} years old.')
c = Context({'person':person})
t.render(c)

 

在模板的方法调用中,有一个操作流程是需要注意的。 

在查找方法的过程中,如果方法抛出了一个异常,除非该异常有一个silent_variable_failure属性,别且值为true,否则它将继续传播。

举例:例如你有一个 BankAccount 对象,有一个Del()方法。

如果某个模板包含了 {{ account.del }} 的标签,account是BankAccount的一个实例。这个模板载入时,account对象被删除。

防止这样的事情发生,必须设置 alters_data函数.

def Del(self)
      Del.alters_data = True

 模板不会执行任何以该方式进行标记的方法。

 

模板中得注释,使用 {#  注释内容  #} 该注释不能跨行。

如果跨行,需要使用模板标记 

{% comment %} 

注释内容

{% endcomment %}
 

===================================================================================== 

2.在视图中使用模板

模板加载机制

打开settings.py文件,找到TEMPLATE_DIRS这项设置。

添加一个用于存放模板的路径,如下:

TEMPLATE_DIRS = ( 'C:/www/django/templates', )

如果考虑到以后部署等问题,可以使用动态的路径方法如:

import os.path

TEMPLATE_DIRS = ( os.path.join(os.path.dirname(__file__), 'templates').replace('\\','/'), )

设置好模板路径后 

在项目路径下,创建文件夹  templates 

1.在文件下创建一个HTML页面文件template1.html,内容如下: 

<html><body>现在的时间是: {{ nowtime }}.</body></html>

2.然后创建一个 视图函数 代码如下 

from django.http import HttpResponse
from django.template import Template,Context
from django.template.loader import get_template
import datetime

def current_datetime(request):
       now = datetime.datetime.now()
       t = get_template('template1.html')
       c = Context({'nowtime':now})
      html  = t.render(c)
      return HttpResponse(html)

运行SERVER,看效果吧。

再记录个 简化模板处理的方法。

from django.shortcuts import render_to_response
import datetime
def current_datetime(request):
       now = datetime.datetime.now()
       return render_to_response('template1.html',{'nowtime':now}) 

========================================================================================

include 模板标签

{% include %} 允许在模板中包含其它模板的内容。

标签的参数可以是:模板名称 、变量、字符串。

{% include 'nav.html' %} 

{% include 'includes/nav.html' %}

{% include template_name %}

 

模板继承

本质上说:模板继承就是先构造一个基础框架模板,而后在其子模板中对它所包含站点共用部分和定义进行重载。 

创建 base.html模板:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
    <head>
        <title>{% block title %}{% endblock %}</title>
    </head>
    <body>
        <h1>我得时间表</h1>
        {% block content %}{% endblock %}
        {% block footer %}
        <hr>
        <p>谢谢对网站的支持</p>
        {% endblock %}
    </body>
</html>

 

创建子模板  test.html

{% extends "base.html" %}
{% block title %}现在时间{% endblock %}
{% block content %}
<p>现在时间是:{{nowtime}}</p>
{% endblock %}

 省略 视图函数。

 因为子模板没有定义 footer 块。所以使用父模板中定义的值。

 

你可以根据需要使用任意多的继承次数,使用继承的一种常见方式如下三层法则。

1.  创建base.html模板,在其中定义站点的主要外观感受。这些都是不常修改甚至从不修改的内容。

2.  为网站的每个区域创建 base_SECTION.html模板(例如,base_photos.html和base_forum.html) 。

     这些模板对base.html进行拓展,并包含区域特定的风格与设计。

3.  为每种类型的页面创建独立的模板,例如论坛页或图片库。这些模板扩展相应的区域模板。

使用模板的一些诀窍

  •  如果在模板中使用{% extends %},必须保持其为模板中的第一个模板标记。否则,模板继承将不起作用。
  •  一般来说,基础模板中得{% block %}标签越多越好。子模板不必定义父模板中所有的代码块,因此你可以 
     合理的缺省值对一些代码块进行填充,然后只对子模板所需的代码块进行重载。
  •  如果发现自己在多个模板之间拷贝代码,你应该考虑将该代码放置到父模板的某个 {% block %}中。
  •  如果你需要访问父模板中块的内容,使用{{ block.super }}这个标签。
  •  主要不要在同一个模板中定义同名的{% block %}.
  • {% extends %}对所传入模板名称使用的加载方法和get_template()相同。
  • 多数情况下,{% extends %}的参数应该是字符串,但如果想实现动态的父模板名,这个参数可以是变量。
 

复习一下模板语言的用法

{# 模板tag的用法 #}
{% if done %}
<strong>Over</strong>
{% else %}
<strong>wait</strong>
{% endif %}
{# 模板变量的用法 #}
Now is {{ nowtime }}
在views.py中使用模板的时候:
1. 通过模板名,获得模板对象
2. 创建context对象,类似字典,用于像模板提供变量实际的值
3. 使用context对象进行模板的渲染,返回的是html网页的内容
 

使用RequestContext对上下文内容进行重用

当渲染一个模板的时候,我们通常使用的是django.template.Context的对象,
这里要介绍另外一个它的子类,django.template.RequestContext,
RequestContext提供了一种把不同的context内容中公共的部分提取出来的方法,让context的内容重用。
 

1. Context版

 
from django.template import loader, Context
from django.http import HttpResponse

def view_1(request):
    # ...
    t = loader.get_template('template1.html')
    c = Context({
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR'],
        'message': 'I am view 1.'
    })
    return HttpResponse(t.render(c))

def view_2(request):
    # ...
    t = loader.get_template('template2.html')
    c = Context({
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR'],
        'message': 'I am the second view.'
    })
    return HttpResponse(t.render(c))
可以看到两个context的内容有些是重复的。比如app, user, ip_address
2. 下面改写成RequestContext版
from django.template import loader, RequestContext
from django.http import HttpResponse

# 使用context processro去提供一些context内容中公共的部分,也就是返回一个字典而已。
def custom_proc(request):
    "A context processor that provides 'app', 'user' and 'ip_address'."
    return {
        'app': 'My app',
        'user': request.user,
        'ip_address': request.META['REMOTE_ADDR']
    }

def view_1(request):
    # ...
    t = loader.get_template('template1.html')
    # 创建方式不同,需要提供的参数有三个,request对象,字典类型,processors可选 
    c = RequestContext(request, {'message': 'I am view 1.'},
            processors=[custom_proc])
    return HttpResponse(t.render(c))

def view_2(request):
    # ...
    t = loader.get_template('template2.html')
    c = RequestContext(request, {'message': 'I am the second view.'},
            processors=[custom_proc])
    return HttpResponse(t.render(c))
可以看到所谓的context processors其实就是一个函数,参数为request,返回一个字典类型。这就是它所做的所有的事。在这里custom_proc返回的是包含共同的那三个参数的字典
 
RequestContext构造函数中的第三个参数processors是可选的,可以是多个custom_proc函数的列表或是元组,在这里我们只传递了一个,可以为多个。
 
return render_to_response('template2.html',
        {'message': 'I am the second view.'},
         context_instance=RequestContext(request, processors=[custom_proc]))
 
结合RequestContext使用render_to_response函数直接返回HttpResponse对象

当使用模板生成HTML代码时,如果变量内容是一些影响HTML结果的字符时,那就挺危险的。

例如,模板内容如下:
Hello {{ name }}
当name的值为:
<script>alert('hello')</script>
渲染后的HTML结果就是:
Hello <script>alert('hello')</script>
以上的代码运行的结果就是会让浏览器弹出一个javascript的警告窗口。
 
同理,如果name的值为<b>hanks,那么结果中Hello以后的所有内容就会被字体加粗,因为没有写</b>结束标记。
 
这种攻击方式被称为跨站脚本攻击(Cross Site Scripting,CSS或者XSS),是一种站点应用程序的安全漏洞攻击,是代码注入的一种。它允许恶意用户将代码注入到网页上,其他用户在观看网页时就会受到影响。这类攻击通常包含了HTML以及用户端脚本语言。
使用Django的模板自动转义功能。其实是默认开启的Django默认转义每一个变量的内容,尤其是下面5个字符:
< 被转义成 &lt;
> 被转义成 &gt;
' 被转义成 &#39;
" 被转义成 &quot;
& 被转义成 &amp;
 
上面中的分号也是转义后的一部分。
 
例如网页内容:
 
页面源代码为:
 
可以看到,转义只对变量的内容进行使用,模板本身的HTML代码不会被转义。
如何关闭这个功能?
为什么呢,有可能你有时就是想让变量的内容渲染成原始的HTML代码,所以不想被转义。比如你想让template系统产生文本内容而不是HTML,就像email信息一样。
 
Django提供了三种方式关闭自动转义:变量级别,模板级别和站点级别。
1. 变量级别
使用safe这个过滤器对每一个变量进行禁用自动转义
This will be escaped: {{ data }}
This will not be escaped: {{ data|safe }}
效果如下:
网页内容
页面源代码
上面之所以没有显示出来,是HTML语法错误,这就是不转义的下场。。。
 
2. 模板级别
在模板中使用autoescape标签来控制,可以嵌套使用
Auto-escaping is on by default. Hello {{ name }}
 
{% autoescape off %}
This will not be auto-escaped: {{ data }}.
 
Nor this: {{ other_data }}
{% autoescape on %}
Auto-escaping applies again: {{ name }}
{% endautoescape %}
{% endautoescape %}
同时,autoescape标签的影响具有继承性,可以从父模板影响到子模板。
# base.html
{% autoescape off %}
<h1>{% block title %}{% endblock %}</h1>
{% block content %}
{% endblock %}
{% endautoescape %}
 
# child.html
{% extends "base.html" %}
{% block title %}This & that{% endblock %}
{% block content %}{{ greeting }}{% endblock %}
继承之后的子模板,也都禁用了自动转义功能。如果greeting变量含有<b>,
将不会被转义。
 
需要注意的是,模板的作者不用去担心自动转义的使用。更多的python端的
开发人员需要考虑哪些数据需要去转义,合理去使用这些数据。
如果你创建了一个模板,而且不太清楚所应用的环境是否开启了自动转义功能。那就在所有
的变量上加上escape过滤器,escape过滤器不会对已经escape的内容产生影响。
对于filter过滤器中参数的自动转义
Django中的自动转义功能不会对filter中的参数已作用,也就是说,最好在filter的参数中,自己
写成转义后的代码,比如这种情况你应该手写成
{{ data|default:"3 &lt; 2" }}
而不是
{{ data|default:"3 < 2" }}
这是为了安全起见。
扩展模板系统
 
一般是扩展模板的tag和filter两个功能。可以用来创建你自己的tag和filter功能库。

创建模板库

分为两步:
1. 首先决定由模板库在哪一个注册的app下放置,你可以放在一个已有的app目录下,也可以新建一个
专门管理模板库的app,比如python manage.py startapp myTemplateLibrary。推荐后者,
因为可以方便将来的重用。
 
2. 在app目录下创建templatetags子目录,并在里面创建两个文件,__init__.py,用来声明这是一个
包,另一个是你的tag/filter定义文件。比如myNewLibrary.py,那么在模板文件中可以这样使用:
{% load myNewLibrary %}
 
{% load %}只允许导入注册app目录下的模板库。这样做是为了保证你的模板库可以不被其它Django
程序使用。

实现自定义过滤器

1. 创建register变量
在你的模块文件中,你必须首先创建一个全局register变量,它是用来注册你自定义标签和过滤器的,
你需要在你的python文件的开始处,插入几下代码:
from django import template
register = template.Library()
 
2. 定义过滤器函数
自定义的过滤器就是一个带1,2个参数的python函数,一个参数放变量值,一个用来放选项值。
比如{{ var|remove:"bar" }}, var是变量值,"bar"是选项值,remove过滤器可以定义为:
def remove(var, arg):
    #移除字符串中var的arg字串
    return var.replace(arg, '')
过滤器函数应该总是返回一些信息,即使出错,也不应该抛出异常,可以返回默认值或者空字符串。
不带参数的过滤器也很常见:
def lower(value):
    "Converts a string into all lowercase"
    return value.lower()
3. 注册过滤器函数
#第一个参数是在模板中使用的过滤器的名字
#第二个就是你的过滤器函数引用名
register.filter('remove', remove)
register.filter('lower', lower)
python2.4以上版本,可以使用装饰符(decorator)功能
@register.filter(name='remove')
def remove(value, arg):
    return value.replace(arg, '')
@register.filter
def lower(value):
    return value.lower()
如果装饰符不加name,则默认使用函数名来当作使用名。
下面是完整代码:
from django import template
register = template.Library()
@register.filter(name='remove')
def remove(value, arg):
    return value.replace(arg, '')
@register.filter
def lower(value):
    return value.lower()

实现自定义tag

过程比实现过滤器要复杂,首先回顾一下模板系统的工作流程:
1. 编译生成模板对象
2. 模板对象使用上下文对象,渲染生成HTML内容
 
如果你要实现自己的tag,就需要告诉Django怎样对你的tag进行上面的两个步骤。
 
了解模板编译过程
当Django编译一个模板时,它把原始的模板文件中的内容变成一个个节点,每一个节点
是django.template.Node的实例,节点都有一个render()函数。因此,一个编译过的
模板对象可以看成是一个结点对象的列表。例如,模板文件内容:
Hello, {{ person.name }}.
{% ifequal name.birthday today %}
Happy birthday!
{% else %}
Be sure to come back on your birthday
for a splendid surprise message.
{% endifequal %}
被编译后的Node列表:
  • Text node: "Hello, "
  • Variable node: person.name
  • Text node: ".\n\n"
  • IfEqual node: name.birthday and today
当你调用模板对象的render()方法时,它会去调用Node列表上的每一个Node的render方法。最
后输出的结果就是所有render方法的输出结果的合并。所以要创建你自己的tag,需要实现你自己的
Node类,实现你自己的render方法。
 
创建tag实战
下面我们来实现一个tag,调用方法为:
{% current_time "%Y-%m-%D %I:%M %p" %}
功能是按照给定格式,显示当前时间,这个格式字符串和time.strftime()中定义的格式一样
 
这里是为了演示一下,格式内容可以参考http://docs.python.org/library/time.html#l2h-1941
这个标签也支持不需要参数的默认显示。
 
1. 定义Node节点类,实现render方法
import datetime
from django import template
#这一句还是要的
register = template.Library()
class CurrentTimeNode(template.Node):
    def __init__(self, format_string):
        self.format_string = str(format_string)
    def render(self, context):
        now = datetime.datetime.now()
        #返回的是格式化后的时间表示字符串
        return now.strftime(self.format_string)
render函数一定返回的是字符串,即使是空字符串
2. 创建Compilation函数
这个函数主要用于获取模板中的参数,并创建相应的Node类对象
def do_current_time(parser, token):
    try:
        tag_name, format_string = token.split_contents()
    except ValueError:
        msg = '%r tag requires a single argument' % token.split_contents()[0]
        raise template.TemplateSyntaxError(msg)
    return CurrentTimeNode(format_string[1:-1])
每一个tag的编译函数都需要两个参数parser和token:
parser是模板分析对象
token是被parser分析后的内容,可以直接使用
 
token.contents 是tag的内容,这里token的值是'current_time "%Y-%m-%d %I:%M %p"'
token.split_contents 按空格分割字符串,返回tuple,但保留引号之单位的内容,这里得到
('current_time', '%Y-%m-%d %I:%M %p')
和实现filter不一样,如果tag运行出错,一定要抛出TemplateSyntaxError,返回一些有用的信息。
不要硬编码你的tag名,使用token.split_contents()[0]可以得到它。
编译函数总是返回一个Node子类实例,返回其它类型会报错。
3. 注册tag
register.tag('current_time', do_current_time)
和注册filter类似,两个参数,一个是使用名,一个是对应的函数引用名
python2.4版本以上,也可以使用装饰符功能
@register.tag(name="current_time")
def do_current_time(parser, token):
# ...
@register.tag
def shout(parser, token):
# ...
不用名字,表示默认使用函数名
完整代码为:
from django import template
import datetime
register = template.Library()
@register.filter(name='remove')
def remove(value, arg):
    return value.replace(arg, '')

@register.filter
def lower(value):
    return value.lower()

class CurrentTimeNode(template.Node):
    def __init__(self, format_string):
        self.format_string = str(format_string)
    def render(self, context):
        now = datetime.datetime.now()
        return now.strftime(self.format_string)
    def do_current_time(parser, token):
        try:
            tag_name, format_string = token.split_contents()
        except ValueError:
            msg = '%r tag requires a single argument' % token.split_contents()[0]
            raise template.TemplateSyntaxError(msg)
     return CurrentTimeNode(format_string[1:-1])

register.tag('current_time', do_current_time)
4. 运行
在模板文件中添加:
访问页面:
复杂的实现自定义tag的其他几种方法
1. 在Node类的render函数中设置context
def render(self, context):
    now = datetime.datetime.now()
    #设置context对象的值
    context['current_time'] = now.strftime(self.format_string)
    # render函数一定要返回字符串,即使是空串
    return ''
这样调用的时候,就是如下用法:
{% current_time "%Y-%M-%d %I:%M %p" %}
<p>The time is {{ current_time }}.</p>
但这样做一个不好的地方就是,current_time变量名是硬编码,可能会覆盖相同名字的值。
 
重新设计一个tag的使用格式,如:
{% get_current_time "%Y-%M-%d %I:%M %p" as my_current_time %}
<p>The current time is {{ my_current_time }}.</p>
这样就需要修改一下编译函数,Node类和注册代码,代码如下:
import re
class CurrentTimeNode3(template.Node):
    def __init__(self, format_string, var_name):
        #增加自定义变量名的参数
        self.format_string = str(format_string)
        self.var_name = var_name
    def render(self, context):
        now = datetime.datetime.now()
        context[self.var_name] = now.strftime(self.format_string)
        return ''

def do_current_time(parser, token):
    #使用正规表达式来处理token
    try:
    # 使用string.split(sep[, maxsplit]),1代表最大分割数,也就是
    # 分割后会产生maxsplit+1个元素
    # 这里分割后的结果为(get_current_time, '"%Y-%M-%d %I:%M %p" as my_current_time')
        tag_name, arg = token.contents.split(None, 1)
    except ValueError:
        msg = '%r tag requires arguments' % token.contents.split_contents()[0]
        raise template.TemplateSyntaxError(msg)
    #使用()代表正则组,匹配as两边的字符串
    m = re.search(r'(.*?) as (\w+)', arg)
    if m:
        fmt, var_name = m.groups()
    else:
        msg = '%r tag had invalid arguments' % tag_name
        raise template.TemplateSyntaxError(msg)
    #如果格式没被引号引用,报错
    if not (fmt[0] == fmt[-1] and fmt[0] in ('"', "'")):
        msg = "%r tag's argument should be in quotes" % tag_name
        raise template.TemplateSyntaxError(msg)
    # [1:-1]去除格式两边的引号
    return CurrentTimeNode3(fmt[1:-1], var_name)

register.tag('get_current_time', do_current_time)
运行结果:
2. 实现块作用区域的tag
如{% if %}...{% endif %},需要在你的编译函数中使用parse.parse()
例如我们想要实现{% comment %}...{% endcomment %},功能是
忽略中tag中间的所有内容。
def do_comment(parser, token):
    nodelist = parser.parse(('endcomment',))
    parser.delete_first_token()
    return CommentNode()

class CommentNode(template.Node):
    def render(self, context):
    return ''
parse.parse()的参数是一个包含多个tag名的元组,返回的是它遇到元组中任何一个
tag名之前的所有Node对象列表,所以这里的nodelist包含{% comment %}和
{% endcomment %}之间的所有node对象,并且不包含它们自身两个node对象。
 
parser.delete_first_token():因为执行完parse.parse()之后,{% endcomment %}
tag还在,所以需要显示调用一次,防止这个tag被处理两次。
3. 在块作用tag中保留context内容
代码如下
{% upper %}
This will appear in uppercase, {{ user_name }}.
{% endupper %}
这里需要context中的user_name参数,怎么才能在处理tag的时候,不丢失context信息呢?
def do_upper(parser, token):
    nodelist = parser.parse(('endupper',))
    parser.delete_first_token()
    return UpperNode(nodelist)

class UpperNode(template.Node):
    def __init__(self, nodelist):
        self.nodelist = nodelist

    def render(self, context):
        output = self.nodelist.render(context)
        return output.upper()
只需要保留下nodelist,然后调用self.nodelist.render(contest),就可以间接调用每一个Node
的render函数。
 
 
有更多的例子,可以查看Django源代码,位置为:
D:\Python27\Lib\site-packages\django\template\defaulttags.py
4. 快速创建简单tag的方法
简单tag的定义,只带一个参数,返回经过处理的字符串,像之前的current_time标签一样。
Django提供了一种simple_tag方法来快速创建类似这样的tag。
def current_time(format_string):
    try:
        return datetime.datetime.now().strftime(str(format_string))
    except UnicodeEncodeError:
        return ''

register.simple_tag(current_time)
simple_tag参数为一个函数引用,会把它包装进render函数中,然后再进行注册。也不用定义
Node子类了。
 
python2.4以上,可以使用装饰符
@register.simple_tag
def current_time(token):
# ...
5. 创建Inclusion Tag
另外一种比较普遍的tag类型是只是渲染其它模块显示下内容,这样的类型叫做Inclusion Tag。
例如,实现以下tag:
{% books_for_author author %}
渲染结果为:
<ul>
<li>The Cat In The Hat</li>
<li>Hop On Pop</li>
<li>Green Eggs And Ham</li>
</ul>
列出某个作者所有的书。
  • 定义函数
def books_for_author(author):
    books = Book.objects.filter(authors__id=author.id)
    return {'books': books}
  • 创建另一个模板文件book_snippet.html
<ul>
{% for book in books %}
<li>{{ book.title }}</li>
{% endfor %}
</ul>
  • 注册tag
register.inclusion_tag('book_snippet.html')(books_for_author)
 
有些你的模板可以使用父模板的context内容,Django提供一个takes_context参数来实现,
使用之后,tag不能再带参数,
@register.inclusion_tag('link.html', takes_context=True)
def jump_link(context):
    return {
        'link': context['home_link'],
        'title': context['home_title'],
}
模板文件link.html为
Jump directly to <a href="{{ link }}">{{ title }}</a>.
使用方法:
{% jump_link %}

创建自定义模板加载类

可以自定其它的加载行为,比如从数据库中加载,从svn中加载,从zip中加载等。
 
需要实现一个接口load_template_source(template_name, template_dirs=None):
template_name就是类似'link.html'这样的模板名称
template_dirs是一个可选的路径列表,为空就使用TEMPLATE_DIRS属性定义的路径。
 
 
如果一个加载器加载模板成功,它将返回一个元组(template_source, template_path)。
template_source:模板文件的内容字符中,会用于被编译
template_path:模板文件的路径
 
如果加载失败,报错django.template.TemplateDoesNotExist
 
 
每一个加载函数都需要有一个is_usable的函数属性,对,是函数的属性,因为在python中,
函数也是个对象。这个属性告诉模板引擎在当前的python环境下这个加载器是否可用。
 
例如,之前的eggs加载器是默认关闭的,is_usable=False,因为需要pkg_resources模块
中的从egg中读取信息的功能。不一定每个用户会安装,如果安装了,就可以设置为True,开启
功能。
 
 
下面实现一个从zip文件中加载模板的自定义加载器,它使用TEMPLATE_ZIP_FILES作为搜索路径,来
代替系统的TEMPLATE_DIRS,路径上都是zip文件名。
from django.conf import settings
from django.template import TemplateDoesNotExist
import zipfile
def load_template_source(template_name, template_dirs=None):
    "Template loader that loads templates from a ZIP file."
    #从settings.py配置文件中读取属性TEMPLATE_ZIP_FILES的值,默认返回空列表
    template_zipfiles = getattr(settings, "TEMPLATE_ZIP_FILES", [])
    # Try each ZIP file in TEMPLATE_ZIP_FILES.
    for fname in template_zipfiles:
        try:
            z = zipfile.ZipFile(fname)
            source = z.read(template_name)
        except (IOErrorKeyError):
            continue
        z.close()
        # 找到一个可用的文件就返回
        template_path = "%s:%s" % (fname, template_name)
        return (source, template_path)
    
    # 如果一个zip文件没找到,报错
    raise TemplateDoesNotExist(template_name)

# 设置为可用
load_template_source.is_usable = True
保存为zip_loader.py,放在app目录下,剩下我们需要做的是,在TEMPLATE_LOADERS属性
中注册你的加载器:
TEMPLATE_LOADERS = (
    'books.zip_loader.load_template_source',
)
 
 
posted @ 2015-12-03 11:18  muzinan110  阅读(270)  评论(0编辑  收藏  举报