基础部分
模板是一个文档或者说一个普通的python字符串由Django模板语言标记而成。一个模板语言可以包括block标签或者是变量。
一个block标签是一个处于模板中的标记,能过完成一些事情。
Block的定义看起来有点模糊,这是django开发团队有意为之的。比如一个block标签即可以用来输出内容;也可以被当做一个控制结构(比如if声明或者是for循环)从数据库中抓去数据;或者是通往另一个模板的入口。
Block标签由”{%”和”%}”组成。
比如一个模板有如下标签
{% if is_logged_in%}Thanksfor logging in!{% else %}Pleaselog in.{% endif %}
变量用于是在模板里输出一个值的符号。
变量标签由”{{”和”}}”组成。
比如:
My first name is {{ first_name}}. Mylast name is {{ last_name }}.
一个Context是指一个传入模板的由“变量名”到“变量值”的映射。
(原文:A context is a “variable name” -> “variable value” mapping thatis passed to a template.)
一个模板通过变量值来填补context中对应变量的“空缺”(译者注:像填空一样吧)和执行block标签来完成一个context的渲染。
使用模板系统
Template类
在django中使用模板系统是一个两部过程:
首先,你将一个未经处理的模板代码编译成一个template对象。
然后,你调用Template中的render()方法,并赋予其一个context。
编译一个字符串
创建一个Template对象最简单的方法就是直接初始化它。这个类位于django.template.Template中。其构造器需要一个参数——一个未经处理的模板代码。
>>> from django.template import Template
>>> t = Template("Myname is {{ my_name }}.")
>>> print(t)
<django.template.Template instance>
幕后 系统只对传入的未经处理的模板代码处理一次——在你创建Template对象的时候。从此以后,出于性能考虑它在内部被当做一个的“节点”结构(node structure)储存起来。 及时这个处理非常迅速。大多数处理在两个表达式之间的短暂地、常规地的调用时发生。 |
渲染一个context
Render
一旦你有一个编译过的Template对象,你就能用它渲染一个context或者操作多个context。Context类存在于django.template.Context,需要两个参数(可选的)来完成构造:
n 一个带有name和对应value的字典
n 现用应用的名字,这个应用的名字用于帮助解决URL的命名空间(Reversingnamespaced URLs)。如果你不使用命名空间化过(namespaced)的URL,你可以忽略这个参数
用context调用Template对象的render()来“填充”模板:
>>> from django.template import Context,Template
>>> t = Template("Myname is {{ my_name }}.")
>>> c = Context({"my_name": "Adrian"})
>>> t.render(c)
"My name is Adrian."
>>> c = Context({"my_name": "Dolores"})
>>> t.render(c)
"My name is Dolores."
变量及查询 变量名包括为字母、数字以及_或者.
.在模板渲染中有特别的含义。一个.在变量名中表示查询。尤其是当模板系统遇见变量名中的一个‘.’时,它会尝试去进行以下查找,查询顺序如下:
n 字典查询。比如:foo[“bar”]
n 属性查询。比如:foo.bar
n 列表查询。比如foo[bar]
注意:像{{ foo.bar }} 中的“bar”在模板标记中将会被当做一个字符串,而非变量bar的值,当bar在模板的context中存在的话。
模板系统将会采用以上3中查询中首先生效的那个查询方式。这是一个短循环逻辑。比如:
>>> from django.template import Context, Template
>>> t = Template("My name is {{person.first_name }}.")
>>> d = {"person": {"first_name": "Joe", "last_name": "Johnson"}}
>>> t.render(Context(d))
"My name is Joe."
>>> class PersonClass: pass
>>> p = PersonClass()
>>> p.first_name = "Ron"
>>> p.last_name = "Nasty"
>>> t.render(Context({"person": p}))
"My name is Ron."
>>> t = Template("The first stooge inthe list is {{ stooges.0 }}.")
>>> c = Context({"stooges": ["Larry", "Curly", "Moe"]})
>>> t.render(c)
"Thefirst stooge in the list is Larry."
如果一个变量的任何一个部分是可调用的,模板会尽量去调用它,比如:
>>> class PersonClass2:
... def name(self):
... return "Samantha"
>>> t = Template("My name is {{person.name }}.")
>>> t.render(Context({"person": PersonClass2}))
"Myname is Samantha."
可调用变量的查询稍微要比那些能直接查询的变量来得复杂些。请记住下面这些:
如果变量引起了一个异常(exception),这个异常将会被传入,除非这个异常silent_variable_failure属性,并且它的值为true。如果异常的确有silent_variable_failure=True,则变量会渲染成空字符串。
比如:
>>> t = Template("My name is {{person.first_name }}.")
>>> class PersonClass3:
... def first_name(self):
... raise AssertionError("foo")
>>> p = PersonClass3()
>>> t.render(Context({"person": p}))
Traceback (most recent call last):
...
AssertionError: foo
>>> class SilentAssertionError(Exception):
... silent_variable_failure= True
>>> class PersonClass4:
... def first_name(self):
... raise SilentAssertionError
>>> p = PersonClass4()
>>> t.render(Context({"person": p}))
"Myname is ."
要注意django.core.exceptions.ObjectDoesNotExist是django数据库API中所有的DoesNotExist异常的基类,并且django.core.exceptions.ObjectDoesNotExist有silent_variable_failure=True。所以如果你在使用模板过程中是用了django模型对象,所有的DoesNotExist异常都会被“静音”处理。(fail silently)
l 一个变量只有当其没有必要参数(required arguments)的时候才会被调用,否则,系统将会返回一个空字符串
l 显然,当你调用参数的时候会有些副作用存在,并且当允许模板系统去访问这些变量是非常不明智的,因为这么做会带来安全漏洞。
一个很好的例子是在django模型对象中使用delete()。模板系统不该允许去做如下的事情:
I will now delete this valuable data. {{ data.delete }}
为了避免这种错误,我们可以在可调用变量上设置alerts_data属性。当一个变量被设置了alters_data=True后,模板系统将不会调动该变量,而会无条件地改用TEMPLATE_STRING_IF_INVALID来替换该变量。在django模型对象上被动态生成的delete()和save()方法已被自动设置了alters_data=True。比如:
def sensitive_function(self):
self.database_record.delete()
sensitive_function.alters_data = True
l 有时候出于某些原因你可能想关掉这个特性,并且告诉模板系统把一个变量设置成无论如何都不被调用。为了达到这一点,设置do_not_call_in_templates=True属性在可调用变量上。模板系统将当以这个变量不可调用的方式来运作。(allowing you to access attributes ofthe callable, for example)
不合法变量如何被处理的 一般情况下,如果一个变量不存在,模板系统将会插入setting中TEMPLATE_STRING_IF_INVALID设置的值,默认为’’。
只有当TEMPLATE_STRING_IF_INVALID被设置为’’时,被用于处理不合法变量的过滤器才会被采用。如果TEMPLATE_STRING_IF_INVALID被设置成了别的值,变量过滤器将会被忽略。
这个动作相比于模板标签中的if\for\regroup稍有些不同。如果一个非法变量被赋予到这三个标签中的一个,变量将会被解释为None。在这些标签中,过滤器将被用于一直应用在非法变量上。
如果TEMPLATE_STRING_IF_INVALID中包含了‘%s’,该格式标记器将会被非法变量的变量名所替换。
只用于Debug! 虽然TEMPLATE_STRING_IF_INVALID能被用作一个很好的debug工具,但是把它当成默认调试工具却是愚蠢的。 许多模板,包括那些在admin中的,当模板中有不存在的变量时,依靠模板系统的静音(silence)来继续运作。当你对TEMPLATE_STRING_IF_INVALID指定了一个’’之外的变量,你将会遇到模板和站点的渲染问题。 一般来说,TEMPLATE_STRING_IF_INVALID只能在你想调试特定模板问题的时候被启用,并在debug完成后被关闭 |
内建变量 每个context都包括了true,false以及none。正如你所预料的,这些变量分解(resolve to)对应的python对象。
字符串的限制 django的模板语言必须遵从其语法。比如,当你要输出类似{%和%}的字符序列时必须要用templatetag标签。
当你想要将这些序列囊括在模板过滤器或者标签变量中时也存在同样的情况。比如,当处理一个block标签,django的模板处理器在遇到一个{%后会匹配之后遇到的第一个%}。这样我们就不能把“%}”当做字符串使用了。比如,下面的例子将会抛出一个TemplateSyntaxError异常:
{% include "template.html" tvar="Some string literal with%} in it." %}
{% with tvar="Some string literal with %} in it." %}{% endwith%}
同样的问题会当你在过滤器中使用反序列时发生:
{{ some.variable|default:"}}" }}
如果你想使用这些字符串,把它们放在模板变量中或者使用自定义的模板标签或者过滤器来绕过这一限制。
玩转Context对象
Context类
大多数情况下,你将用一个完全填充的字典给Context()来初始化Context对象。但是当Context对象被初始化过后,你就可以用标准的字典语法来从Context对象中增加或者删除条目:
>>> from django.template import Context
>>> c = Context({"foo": "bar"})
>>> c[’foo’]
’bar’
>>> del c[’foo’]
>>> c[’foo’]
’’
>>> c[’newvariable’] = ’hello’
>>> c[’newvariable’]
’hello’
Context.pop()
Context.push()
ContextPopException异常
一个Context对象是一个栈(stack)。这意味着你能用push()和pop()来操作它。当你pop()次数过多(注:也就是当字典为空时用了pop()),它将会抛出一个django.template.ContextPopException:
>>> c = Context()
>>> c[’foo’] = ’firstlevel’
>>> c.push()
{}
>>> c[’foo’] = ’secondlevel’
>>> c[’foo’]
’second level’
>>> c.pop()
{’foo’: ’second level’}
>>> c[’foo’]
’first level’
>>> c[’foo’] = ’overwritten’
>>> c[’foo’]
’overwritten’
>>> c.pop()
Traceback (most recent call last):
...
ContextPopException
你也可以把push() 当做一个内容管理器来保证对应的pop()被调用。
>>> c = Context()
>>> c[’foo’] = ’firstlevel’
>>> with c.push():
>>> c[’foo’] = ’secondlevel’
>>> c[’foo’]
’second level’
>>> c[’foo’]
’first level’
所有被传入push()的参数将会被传到dict构造器中去,以便于建造新的内容层级(All arguments passed to push() will be passed to the dict constructor used to build the newcontext level.)
update(other_dict)
除了pop()和push()外,Context对象还定义了一个update()方法。它能像push()一样工作,但是把一个字典当做参数并将该字典进行进栈操作而非和push()一样压入一个空字典。
>>> c = Context()
>>> c[’foo’] = ’firstlevel’
>>> c.update({’foo’: ’updated’})
{’foo’: ’updated’}
>>> c[’foo’]
’updated’
>>> c.pop()
{’foo’: ’updated’}
>>> c[’foo’]
’first level’
在自定义模板标签时,把一个Context当做栈是十分方便的,例子如下:
Context.flatten()
使用flatten()方法你能得到一整个被当成填充了变量的字典的Context栈:
>>> c = Context()
>>> c[’foo’] = ’firstlevel’
>>> c.update({’bar’: ’secondlevel’})
{’bar’: ’second level’}
>>> c.flatten()
{’True’: True, ’None’:None, ’foo’: ’first level’, ’False’: False, ’bar’: ’second level’}
在单元测试中我们能使用从flatten()得到的结果来比较Context和dict:
class ContextTest(unittest.TestCase):
def test_against_dictionary(self):
c1 = Context()
c1[’update’] = ’value’
self.assertEqual(c1.flatten(), {
’True’: True, ’None’: None, ’False’: False,
’update’: ’value’})
子类化Context:RequestContext
RequestContext类
Django带来了一个特别的Context类,django.template.RequestContext,它表现起来稍微不同于普通的django.template.Context。第一个区别就是它把HttpRequest当作它的第一参数。比如:
C=RequestContext(request,{‘foo’:’bar’,
})
第二个区别就是它自动地将几个变量填入context中,根据你的TEMPLATE_CONTEXT_PROCESSORS设置。
TEMPLATE_CONTEXT_PROCESSORS设置是一个由一堆可调用上下文处理器(context processors)构成的元组,这些上下文处理器把一个请求对象当做它们的参数并且返回一个由将被填充进context的元素们组成的字典。默认的,TEMPLATE_CONTEXT_PROCESSORS被设置为:
("django.contrib.auth.context_processors.auth",
"django.core.context_processors.debug",
"django.core.context_processors.i18n",
"django.core.context_processors.media",
"django.core.context_processors.static",
"django.core.context_processors.tz",
"django.contrib.messages.context_processors.messages")
除此之外,RequestContext总是使用django.core.context_processors.csrf。这是一个安全相关的内容处理器,为admin以及其他配置apps所需要,并且,为了避免以外的错误配置,它特地被设置成硬编码形式,并且不能通过TEMPLATE_CONTEXT_PROCESSORS设置来关闭。
每个处理器都是按照一定顺序来被应用的。这意味着如果一个处理器添加了一个变量到context中并且之后的一个处理器添加了一个相同名字的变量,则后者会覆盖前者。默认处理器的解释如下:
内容处理器在何时被使用? 内容处理器应用在一个内容自身被处理之后。这意味着一个内容处理器可以重写你已写入到你的Context或者RequestContext中的变量,所以要谨慎避免变量被覆盖的情况 |
另外,可以给RequestContext一系列其他的处理器,使用可选的、可放在第三方变量中的处理器(optional, third positional argument,processors)。在下面例子中,RequestContext实例获得了一个ip_address变量:
from django.http import HttpResponse
from django.template import RequestContext
def ip_address_processor(request):
return {’ip_address’:request.META[’REMOTE_ADDR’]}
def some_view(request):
#...
c = RequestContext(request,{
’foo’: ’bar’,
},[ip_address_processor])
return HttpResponse(t.render(c))
注意:如果你正在使用django的render_to_response()捷径来将一个字典中的内容填充到模板,你的目标将会被默认地传给一个Context实例(而不是一个RequestContext)。为了使用一个RequestContext在你的模板渲染中,传一个可选的第三方变量给render_to_response():一个RequestContext的实例。你的代码可能会和这个差不多: from django.shortcuts import render_to_response from django.template import RequestContext def some_view(request): # ... return render_to_response(’my_template.html’,my_data_dictionary, context_instance=RequestContext(request)) 可选择的,通过调用render_to_response()的相同方法,即使用一个context_instance参数来调用render()捷径来强迫使用RequestContext。 注意到由提供的字典(比如这个例子中的my_data_dictionary)传入的内容的优先级将会比其他由内容处理器提供的变量或者是RequestContext提供的变量来的要高 |
以下是每个默认的处理器所做的工作:
django.contrib.auth.context_processors.auth
django.core.context_processors.i18n
django.core.context_processors.media
django.core.context_processors.static
django.core.context_processors.csrf
django.core.context_processors.request
django.contrib.messages.context_processors.messages
编写你自己的内容处理器 一个内容处理器有一个非常简单的接口:他只是一个只有一个参数——一个HttpRequest对象的python函数,并且返回一个字典来写入模板内容。每个内容处理器都得返回一个字典。
自定义的内容处理器可以放在你的代码库中的任何地方。Django所关心的只有你的自定义内容处理器们是否被指向了你的TEMPLATE_CONTEXT_PROCESSORS设置。
载入模板
一般来说,你会将你的模板保存在文件系统的文件里而不是直接使用低层次(low-level)的模板API。直接保存模板在特定的模板目录里。
Django在很多地方都会搜索模板目录,这个取决于你的模板加载(template-loader)设置(看下面的读取器类型(“Loader types”)),但是最基本的制定目标目录的方式是通过设置TEMPLATE_DIRS。
TEMPLATE_DIRS设置 通过设置文件中的TEMPLATE_DIRS设置来告诉django你的模板目录。这是一个由记录着你的模板路径的字符串所组成的列表或者元组。比如:
TEMPLATE_DIRS = (
"/home/html/templates/lawrence.com",
"/home/html/templates/default",
)
你的模板可以放在你想要指定的任何地方,只要目录和模板能够被服务器所获取。你还可以随意指定它们的文件类型,比如.html或者.txt,或者他们根本没有文件类型。
注意这些路径应该用Unix下的”/来分隔”,即使是在windows系统上时。
PythonAPI django.template.loader有两种从文件中读取模板的方式:
get_template(template_name[,dirs])
get_template为一个指定名称的模板返回一个经过编译的模板(一个模板对象)。如果模板不存在则会抛出django.template.TemplateDoesNotExist异常。
为了覆盖TEMPLATE_DIRS设置,使用dirs参数。dirs参数可以是一个元组或者列表。
dirs参数被添加了。
select_template(template_name[,dirs])
select_template和get_template很像,除了它带了一个模板名的列表外。在这个列表里,它返回其中第一个存在的模板。
为了覆盖TEMPLATE_DIRS设置,使用dirs参数。dirs参数可以是一个元组或者列表。
dirs参数被添加了。
比如,如果你调用get_template(‘story_detail.html’)并且有以上的TEMPLATE_DIRS设置,那么django会按照一下的方式进行查找,查找顺序如下:
? /home/html/templates/lawrence.com/story_detail.html
? /home/html/templates/default/story_detail.html
如果你调用select_template([‘story_253_detail.html’,‘story_detail.html’]),那么django则会这么查找:
? /home/html/templates/lawrence.com/story_253_detail.html
? /home/html/templates/default/story_253_detail.html
? /home/html/templates/lawrence.com/story_detail.html
? /home/html/templates/default/story_detail.html
当django找到存在的模板,它就停止搜索。
小贴士 你可以使用select_template()来”软化”(super-flexible)“templatability”。比如,如果你已经写入了一个新的故事并且想要把其他故事写到自定义的多个模板中去时,使用select_template([’story_%s_detail.html’ % story.id, ’story_detail.html’]). 这样你就能为每个故事添加独立的模板了,但是搜索失败的故事就没有自己特有的模板。 |
使用子目录 在模板目录的子目录中组织模板这是可能的,而且是非常棒的。这有利于为每个django app创建一个子目录,必要时在子目录中再添加子目录。
这个由你自己来理性地组织。把所有模板都放在一个根目录级别是很容易混乱的。
为了读取一个放置在子目录中的模板,使用’/’比如:
get_template(’news/story_detail.html’)
使用以上相同的TEMPLATE_DIRS设置,这个例子get_template()调用将会尝试去读取以下的模板:
? /home/html/templates/lawrence.com/news/story_detail.html
? /home/html/templates/default/news/story_detail.html
读取器类型(“Loader types”) 默认地,django使用一个基于文件系统的模板读取器,但是django还带来了一些其他的模板读取器,这是为了从其他来源中获取模板。
其中一些默认是关闭的,的那是你可以通过你的TEMPLATE_LOADERS设置来激活它们。TEMPLATE_LOADERS应该是一个由字符串组成的元组,每个字符串代表一个模板加载器的类。以下是django自带的模板读取器:
django.template.loaders.filesystem.Loader
class filesystem.Loader
django.template.loaders.app_directories.Loader
class app_directories.Loader
django.template.loaders.eggs.Loader
class eggs.Loader
django.template.loaders.cached.Loader
class cached.Loader
django根据TEMPLATE_LOADERS设置的顺序来使用模板读取器。他使用每个读取器知道找到匹配的。
模板来源(Template origin) 当TEMPLATE_DEBUG为True时模板对象将会有一个origin属性,该属性取决于他们读取的来源。
class loader.LoaderOrigin
class StringOrigin
render_to_String捷径
loader.render_to_string(template_name, dictionary=None, context_instance=None)
为了减少读取和渲染模板中重复的的特性(repetitive nature),django提供了一个自动化程度非常高的捷径函数:render_to_string()在django.template.loader中,它读取一个模板,渲染它然后返回一个结果字符串:
from django.template.loader import render_to_string
rendered = render_to_string(’my_template.html’, {’foo’: ’bar’})
render_to_string捷径带了一个必要参数-template_name,其应该是将要读取和渲染的模板的名字(或者说一系列模板名字,同样django会采用第一个存在的模板)和两个可选择参数。
字典 一个字典像被模板的内容的键值对被传入。这同样也被当做第二个位置的参数来传递。
context_instance 一个用来当做模板内容的Context或其是子类的实例。这同样也被当做第三个位置的参数来传递。
可以参考render_to_response()捷径,它调用了render_to_string并且把结果传给一个合适于从视图直接返回的HttpResponse。
在独立模式配置模板系统
提示:这一部分只适合于尝试将使用模板系统作为其他应用的输出组件的程序猿。如果你在django中使用模板体统,可以跳过这一节。 |
P1157
使用可选的模板语言
Django模板和读取器类实现了一个简单的API来读取和渲染模板。通过提供一些简单的实现这些API的wrapper类,我们可以使用第三方模板系统比如Jinja2或者Cheetah。这允许我们在不放弃django特性(比如context对象以及如render_to_response这样一些方便的捷径)的情况下来使用第三方模板库。
Django模板系统的核心组件是Template类。这个类有非常简单的接口:它有一个构造器,它带了一个用来具体化模板字符串(specifying the template string)的位置参数(positionalargument),和一个带了Context对象的render()方法并且返回一个包含了渲染后的响应的字符串。
假设我们正在使用模板语言,它用一个带有一个字典而非Context对象的render()方法来定义了一个Template对象。我们可以写一个简单的wrapper来实现django的Template接口:
import some_template_language
class Template(some_template_language.Template):
def render(self, context):
# flatten the DjangoContext into a single dictionary.
context_dict = {}
for d in context.dicts:
context_dict.update(d)
return super(Template, self).render(context_dict)
只要这么简单就能把我们让虚构的Template类拥有和django的读取和渲染系统一样的性能!
下一步就是写一个Loader类,这个类返回我们自定义模板类的实例而不是默认的Template。自定义Loader类应该继承自django.template.loader.BaseLoader并且覆盖了load_template_source()方法,该方法带了一个template_name变量,从硬盘或是其他地方中读取模板,并且返回一个元组:(template_string, template_origin).
Loader类中的load_template()方法通过调用load_template_source()来获得模板字符串,从模板来源中初始化一个模板,并且返回一个元组(template, template_origin)。因为这个方法实际上初始化了Template,我们将需要去覆盖它来使用我们自定义的模板类。我们可以继承自内建的django.template.loader.app_directories.Loader来利用load_temolate_source()方法的优势
from django.template.loaders import app_directories
class Loader(app_directories.Loader):
is_usable = True
def load_template(self, template_name, template_dirs=None):
source, origin = self.load_template_source(template_name,template_dirs)
template = Template(source)
return template, origin
最后我们需要修改我们项目的设置,告诉django来使用我们自定义的loader。现在我们可以在继续使用django模板系统的前提下用我们可选的模板语言来写我们自己的的模板了。
参考:
关于写自己的标签和过滤器,请参考django1.7官方文档P498自定义模板标签和过滤器