Python 学习笔记

Django模板

2018-01-28  本文已影响36人  大爷的二舅

在前一章中,您可能已经注意到我们在示例视图中返回文本的一些特殊情况。 也就是说,HTML是直接在我们的Python代码中硬编码的,如下所示:

def current_datetime(request):
    now = datetime.datetime.now()
    html = "<html><body>It is now %s.</body></html>" % now
    return HttpResponse(html)

虽然这个技巧对于解释视图是如何工作的很方便,但是直接在你的视图中直接硬编码HTML并不是一个好主意。 原因如下:

由于这些原因,将页面的设计与Python代码本身分开,使它更简洁,更易于维护。 我们可以用Django的模板系统来做到这一点,我们将在本章中讨论这个模板系统。

模板系统基础

Django模板是一个文本字符串,旨在将文档与其数据分开。 一个模板定义了占位符和各种基本逻辑(模板标签)的各个部分,用来管理文档应该如何显示。 通常,模板用于生成HTML,但Django模板同样能够生成任何基于文本的格式。

Django模板背后的哲学

如果您有编程背景,或者习惯了将编程代码直接混合到HTML中的语言,那么您应该记住,Django模板系统不仅仅是嵌入到HTML中的Python。 这种设计是:模板系统是为了表示图像,而不是程序逻辑。

我们从一个简单的示例模板开始。 这个Django模板描述了一个HTML页面,感谢一个人向公司下订单。 把它想成一个表格信:

<html>
<head>
    <title>Ordering notice</title>
</head>
<body>
    <h1>Ordering notice</h1>
    <p>Dear {{ person_name }},</p>
    <p>Thanks for placing an order from {{ company }}. It's scheduled to ship on {{ s\
hip_date|date:"F j, Y" }}.</p>
    <p>Here are the items you've ordered:</p>
    <ul>
        {% for item in item_list %}
        <li>{{ item }}</li>{% endfor %}
    </ul>
    {% if ordered_warranty %}
    <p>Your warranty information will be included in the packaging.</p>
    {% else %}
    <p>
        You didn't order a warranty, so you're on your own when the products inevitably stop working.
    </p>
    {% endif %}
    <p>Sincerely,<br />{{ company }}</p>
</body>
</html>

这个模板是基于HTML代码编写,其中包含了一些变量和模板标签。让我们一起来看看它:

每个Django模板都可以访问几个内置的标签和过滤器,其中很多将在下面的章节中讨论。 附录E包含标签和过滤器的完整列表,最好熟悉这个列表,这样你就知道什么是可能的。 也可以创建自己的过滤器和标签; 我们将在第8章中介绍。

使用模板系统

Django项目可以配置一个或多个模板引擎(如果不使用模板,甚至可以设置为零)。 Django为其自己的模板系统(Django模板语言(DTL))提供了一个内置的后端。 Django 1.11还包括对流行的Jinja2的选择性支持。

如果您没有迫切的理由选择另一个后端,那么您应该使用DTL--尤其是在您正在编写可插入应用程序并且您打算分发模板时。 Django的contrib应用程序包含模板(如django.contrib.admin),使用DTL。 本章中的所有例子都将使用DTL。 有关更高级的模板主题(包括配置第三方模板引擎),请参阅第8章。

在我们开始在你的视图中实现Django模板之前,先让我们在DTL里面挖掘一下,看看它是如何工作的。 以下是您可以在Python代码中使用Django模板系统的最基本的方法:

  1. 通过将原始模板代码作为字符串提供来创建模板对象。
  2. 使用给定的一组变量(上下文)调用Template对象的render()方法。 这将以字符串形式返回完全呈现的模板,并根据上下文评估所有变量和模板标记。 使用Django自定义Python shell(python manage.py shell),看起来像这样:
>>> from django import template
>>> t = template.Template('My name is {{ name }}.')
>>> c = template.Context({'name': 'Nige'})
>>> print (t.render(c))
My name is Nige.
>>> c = template.Context({'name': 'Barry'})
>>> print (t.render(c))
My name is Barry.

以下各节将更详细地介绍每个步骤。

一个特殊的Python提示符

如果你之前使用过Python,你可能会想知道为什么我们要运行python manage.py而不是python(或python3)。两个命令都会启动交互式解释器,但是manage.py shell命令有一个关键区别:在启动解释器之前,它会告诉Django要使用哪个设置文件。
Django的许多部分,包括模板系统,都依赖于你的设置,除非框架知道使用哪个设置,否则你将无法使用它们。如果你好奇的话,下面是它在后台的工作原理。 Django查找名为DJANGO_SETTINGS_MODULE的环境变量,应该将其设置为settings.py的导入路径。例如,假设mysite在Python路径上,DJANGO_SETTINGS_MODULE可能被设置为“mysite.settings”。
当你运行python manage.py shell时,该命令会为你设置DJANGO_SETTINGS_MODULE。在这些例子中你将需要使用python manage.py shell,否则Django会抛出异常。

创建模板对象

创建一个Template对象最简单的方法是直接实例化它。 Template类位于django.template模块中,构造函数接受一个参数,即原始模板代码。 让我们深入Python交互式解释器,看看它是如何在代码中工作的。 从第1章中创建的mysite项目目录中,输入python manage.py shell启动交互式解释器。

我们来看看一些模板系统的基础知识:

>>> from django.template import Template
>>> t = Template('My name is {{ name }}.')
>>> print (t)

如果你正在交互式命名窗口中,你会看到这样的东西:

<django.template.base.Template object at 0x030396B0>

0x030396B0每次都会有所不同,而且不相关; 这是一个Python的东西(如果你必须知道,这是Python为Template对象定义的一个“身份”,即内存地址)。

创建模板对象时,模板系统将原始模板代码编译为内部优化表单,以供呈现。 但是,如果您的模板代码包含任何语法错误,则对Template()的调用将导致TemplateSyntaxError异常:

>>> from django.template import Template
>>> t = Template('{% notatag %}')
Traceback (most recent call last):
File "", line 1, in ?
...
django.template.exceptions.TemplateSyntaxError: Invalid block tag on line 1: 'notatag'. Did you forget to register or load this tag?

这里的术语“块标记”是指{%notatag%}。 “块标签”和“模板标签”是同义词。 系统为以下任何情况引发TemplateSyntaxError异常:

呈现模板

一旦你有了一个Template对象,你可以通过给它一个上下文来传递它的数据。 上下文只是一组模板变量名称及其相关的值。 一个模板使用它来填充它的变量并评估它的标签。 上下文在Django中由Context类生成,它位于django.template模块中。 它的构造函数有一个可选的参数:将变量名映射到变量值的字典。 使用上下文调用Template对象的render()方法来“填充”模板:

>>> from django.template import Context, Template
>>> t = Template('My name is {{ name }}.')
>>> c = Context({'name': 'Stephane'})
>>> t.render(c)
'My name is Stephane.'
字典和上下文

Python字典是已知键和变量值之间的映射。 上下文类似于字典,但是上下文提供了额外的功能,如第8章所述。

变量名称必须以字母(A-Z或a-z)开头,可能包含更多字母,数字,下划线和点。 (点是我们稍后会遇到的特例。)变量名称区分大小写。 下面是一个模板编译和渲染的例子,使用与本章开头部分相似的模板:

>>> from django.template import Template, Context
>>> raw_template = """<p>Dear {{ person_name }},</p>
...
... <p> Thanks for placing an order from {{ company }}. It's scheduled to
... ship on {{ ship_date|date:"F j, Y" }}.</p>
...
... {% if ordered_warranty %}
... <p>Your warranty information will be included in the packaging.</p>
... {% else %}
... <p> You didn't order a warranty, so you're on your own when
... the products inevitably stop working.</p>
... {% endif %}
...
... <p>Sincerely,<br />{{ company }}</p>"""
>>> t = Template(raw_template)
>>> import datetime
>>> c = Context({'person_name': 'John Smith',
...     'company': 'Outdoor Equipment',
...     'ship_date': datetime.date(2017, 7, 2),
...     'ordered_warranty': False})
>>> t.render(c)
"<p>Dear John Smith,</p>\n\n<p>Thanks for placing an order from Outdoor Equipment. It\
's scheduled to\nship on July 2,2017.</p>\n\n\n<p>You didn't order a warranty, so you\
're on your own when\nthe products inevitably stop working.</p>\n\n\n<p>Sincerely,<br\
 />Outdoor Equipment</p>"

如果您是Python新手,您可能会想知道为什么此输出包含换行符(“\ n”)而不是显示换行符。 这是因为Python交互式解释器中的一个微妙之处:对t.render(c)的调用返回一个字符串,默认情况下交互式解释器显示字符串的表示形式,而不是字符串的打印值。 如果要查看带换行符的字符串而不是“\ n”字符,请使用print函数:print(t.render(c))。

这些是使用Django模板系统的基础:只需编写一个模板字符串,创建一个Template对象,创建一个Context,然后调用render()方法。

多个上下文,相同的模板

一旦你有一个模板对象,你可以通过它来渲染多个上下文。 例如:

>>> from django.template import Template, Context
>>> t = Template('Hello, {{ name }}')
>>> print (t.render(Context({'name': 'John'})))
Hello, John
>>> print (t.render(Context({'name': 'Julie'})))
Hello, Julie
>>> print (t.render(Context({'name': 'Pat'})))
Hello, Pat

无论何时使用相同的模板源渲染多个上下文,只需创建一次Template对象,然后多次调用render()就可以了:

# Bad
for name in ('John', 'Julie', 'Pat'):
    t = Template('Hello, {{ name }}')
    print (t.render(Context({'name': name})))

# Good
t = Template('Hello, {{ name }}')
for name in ('John', 'Julie', 'Pat'):
    print (t.render(Context({'name': name})))

Django的模板解析速度相当快。 在幕后,大部分解析都是通过调用一个正则表达式来实现的。 这与基于XML的模板引擎形成了鲜明的对比,这引起了XML解析器的开销,并且往往比Django的模板渲染引擎要慢几个数量级。

上下文变量查找

在目前为止的例子中,我们已经在上下文中传递了简单的值 - 主要是字符串,还有一个datetime.date的例子。 但是,模板系统优雅地处理更复杂的数据结构,如列表,词典和自定义对象。 遍历Django模板中复杂数据结构的关键是点字符(“.”)。

使用点来访问对象的字典键,属性,方法或索引。 举几个例子来说明这一点。 例如,假设你将一个Python字典传递给一个模板。 要通过字典键访问该字典的值,请使用点:

>>> 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)
'Sally is 43 years old.'

同样,点也允许访问对象属性。 例如,一个Python datetime.date对象具有year,month和day属性,您可以使用一个点来访问Django模板中的这些属性:

>>> from django.template import Template, Context
>>> import datetime
>>> d = datetime.date(2017, 5, 2)
>>> d.year
2017
>>> d.month
5
>>> d.day
2
>>> t = Template('The month is {{ date.month }} and the year is {{ date.year }}.')
>>> c = Context({'date': d})
>>> t.render(c)
'The month is 5 and the year is 2017.'

这个例子使用了一个自定义类,演示了变量点也允许在任意对象上访问属性:

>>> from django.template import Template, Context
>>> class Person(object):
...     def __init__(self, first_name, last_name):
...         self.first_name, self.last_name = first_name, last_name
>>> t = Template('Hello, {{ person.first_name }} {{ person.last_name }}.')
>>> c = Context({'person': Person('John', 'Smith')})
>>> t.render(c)
'Hello, John Smith.'

点也可以指对象的方法。 例如,每个Python字符串都有方法upper()和isdigit(),您可以使用相同的点语法在Django模板中调用这些方法:

>>> from django.template import Template, Context
>>> t = Template('{{ var }} -- {{ var.upper }} -- {{ var.isdigit }}')
>>> t.render(Context({'var': 'hello'}))
'hello -- HELLO -- False'
>>> t.render(Context({'var': '123'}))
'123 -- 123 -- True'

请注意,在方法调用中不包括括号。 另外,不可能将参数传递给方法; 你只能调用没有必要参数的方法。 (我在本章后面解释这个理念。)最后,点也被用来访问列表索引,例如:

>>> from django.template import Template, Context
>>> t = Template('Item 2 is {{ items.2 }}.')
>>> c = Context({'items': ['apples', 'bananas', 'carrots']})
>>> t.render(c)
'Item 2 is carrots.'

负值列表索引是不允许的。 例如,模板变量{{items.-1}}将导致TemplateSyntaxError。

Python列表索引

提醒:Python列表有从0开始的索引。 第一项是索引0,第二项是索引1,依此类推。

点查找可以这样概括:当模板系统在变量名中遇到一个点时,它会按以下顺序尝试以下查找:

系统使用可用的第一个查找类型。 这是短路逻辑。 点查找可以嵌套多层。 例如,下面的例子使用{{person.name.upper}},它翻译成字典查找(person ['name']),然后是方法调用(upper()):

>>> from django.template import Template, Context
>>> person = {'name': 'Sally', 'age': '43'}
>>> t = Template('{{ person.name.upper }} is {{ person.age }} years old.')
>>> c = Context({'person': person})
>>> t.render(c)
'SALLY is 43 years old.'
方法调用行为

方法调用比其他查找类型稍微复杂一些。 这里有一些事情要记住:

>>> 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}))
'My name is .'
 def delete(self):
     # Delete the account
     delete.alters_data = True

模板系统不会执行任何以这种方式标记的方法。 继续上面的例子,如果一个模板包含{{account.delete}}并且delete()方法的alters_data = True,那么当模板被渲染时,delete()方法将不会被执行,引擎将代替 变量与string_if_invalid。

注意:Django模型对象上的动态生成的delete()和save()方法会自动设置alters_data = true。

如何处理无效的变量

通常,如果一个变量不存在,模板系统会插入引擎的string_if_invalid配置选项的值,默认情况下这是一个空字符串。 例如:

>>> from django.template import Template, Context
>>> t = Template('Your name is {{ name }}.')
>>> t.render(Context())
'Your name is .'
>>> t.render(Context({'var': 'hello'}))
'Your name is .'
>>> t.render(Context({'NAME': 'hello'}))
'Your name is .'
>>> t.render(Context({'Name': 'hello'}))
'Your name is .'

这种行为比引发异常更好,因为它的目的是对人为错误有弹性。 在这种情况下,所有的查找失败,因为变量名称有错误的大小写或名称。 在现实世界中,由于小的模板语法错误,网站变得不可访问是不可接受的。

上一篇 下一篇

猜你喜欢

热点阅读