目前,我们为 Rango 应用的几个页面创建了 HTML 模板。你可能发现了,不同模板之间有很多HTML 代码是重复的,这违背了 DRY 原则。此外,你可能也注意到了,链接中使用的是硬编码的 URL 路径。这些问题会导致网站难以维护,倘若想改变网站的整体结构,或者调整 URL 路径,每个模板都要修改。本章使用模板继承解决第一个问题,使用 URL 模板标签解决第二个问题。先看第二个问题。
8.1 使用相对 URL
- 目前,模板中的链接地址使用的是硬编码的 URL,例如 :
< a href="/rango/about/">About</a>
- 这样做的缺点是一旦修改了 urls.py 中的 URL 映射,就要更新对应的所有 URL 引用。正确的方法是使用模板标签 url 查询 urls.py 文件中的 URL 模式,动态插入 URL 路径。在模板中使用相对 URL 十分简单。若想链接到关于页面,可以在模板中插入下面这行代码。Django 模板引擎会检查 urls.py 模块中有没有 name 属性的值为 about 的 URL 模式,然后反向匹配URL。这样一来,如果修改了 urls.py 模块中的 URL 映射,无需一个一个修改模板。
<a href="{% url 'about' %}">About</a>
- URL 模式也可以不通过名称引用,而是直接引用视图,如下所示。此时,必须保证 rango 应用的 views.py 模块中有名为 about 的视图。
<a href="{% url 'rango.views.about' %}">About</a>
Rango 应用的 index.html 模板中有个带参数的 URL,即 /rango/category/{ { category.slug }}。为了避免硬编码,我们可以使用 url 模板标签,并指定 URL(或视图)的名称和分类的别名,如下所示:
在这里插入代码片
URL 和多个 Django 应用
这本书仅关注于单个应用开发。但是你也会发现一个django项目是会利用多个应用的。所以你可能就得面对成百个潜在的URL需要引用的问题。这种会产生问题,我们该如何组织我们的URL?两个app中有可能会有命名重复的url,这就会产生潜在的冲突。
django提供了命名空间(namespace)的方法,为项目里的每一个应用配置一个命名空间。只需要在应用的urls.py文件里增加一个app_name的变量就够了。下面的例子为rango的应用指定了命名空间,并命名为rango。
from django.conf.urls import url
from rango import views
app_name = 'rango'
urlpatterns =[url(r'^$', views.index, name='index'),
]
增加了app_name的变量后,从rango应用中引用URL就需要这样做:
<a href="{% url 'rango:about' %}">About</a>
url命令中的冒号将url名称从名称空间分开。当然这是一个多应用使用的高级属性。但是当项目规模越来越大时,这是一个有用的诀窍。
8.2 去除重复
在模板中使用继承的方法如下:
①检查应用中每个页面重用的部分(即页头、页边、页脚、正文)。有时候,在纸上画出不同页面的基本结构能够帮助你找到哪些组件是重用的。
②在base模板中,提供了一个基本页面的框架结构,以及一些共同的内容(即页脚的版权信息,某些区域出现的logo和标题)。然后根据用户要查看的页面内容来定义数个要修改的块。
③为应用的页面创建具体的模板。所有的模板都继承于基类模板,每个块都指定了内容。
在基模板中定义重复出现的 HTML
目前为止我们已经创建了数个模板,很明显这些模板里有相当一部分的代码是重复的。下面的代码中,我们已经抽象出页面的细节,只显示在每个模板中重复组件的框架结构。
<!DOCTYPE html>
{
% load static %}
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>Rango</title>
</head>
<body>
<!-- 各页面的具体内容 -->
</body>
</html>
我们暂时把这个实例作为我们应用的基类模板。保存上面的代码并命名为base.html,存放到templates/rango/目录中(即templates/rango/base.html)。
记住的声明一定要放在模板的第一行,如果第一行没有关于文档类型的声明,那么冲模板中生成的结果页面就不会执行W3C的html方针。
定义区块
回顾下django模板的标准命令都是通过{%和%}表示的。block命令表示为{% block %},此处的NAME就是你想创建的block的名称。你要确保block要以{% endblock %}结束,要用django的模板标签包围。
你也可以为block指定默认内容,这个默认内容将在继承模板中没有定义此block时使用。指定默认内容就是在{% block %}和{% endblock %}之间添加你想要展现的默认内容就可以了,如下所示:
- 打开rango/base.html模板
<!DOCTYPE html>
{
% load static %}
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>Rango</title>
</head>
<body>
{
% block body_block %}
This is body_block's default content.
{
% endblock %}
</body>
</html>
进一步抽象
- 下面的例子中,我们在模板中引入了两个新的属性。
- 第一个是title_block块,这个块允许我们从基类模板继承的每一个页面指定一个自定义的页面标题。如果继承的页面没有覆盖这个块,那么title_block的默认内容就是 How to Tango with Django!,完整的标题就是Rango - How to Tango with Django!。查看一下title标签里的内容,看看是怎么写的。
- 我们还从之前的index.html模板里导入了一个链接列表,并把它们放在了body_block块下面的HTML标签div里。这就保证了从基类模板中继承的页面都会存在这些链接。这些链接前有一个分割线(< hr />),让用户在body_block块和链接之间能看到一条分割线。
- 打开rango/base.html模板
<!DOCTYPE html>
{
% load static %}
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>
Rango -
{
% block title_block %}
How to Tango with Django!
{
% endblock %}
</title>
</head>
<body>
<div>
{
% block body_block %}
This is body_block's default content.
{
% endblock %}
</div>
<hr />
<div>
<ul>
<li><a href="{% url 'add_category' %}">Add New category</a></li>
<li><a href="{% url 'about' %}">About</a></li>
<li><a href="{% url 'index' %}">index</a></li>
</ul>
</div>
</body>
</html>
8.3 模板继承
让我们首先开始重构rango/category.html模板。首先删除所有重复的HTML代码,只留下页面中具体的HTML和模板标签或命令。然后在模板的最开始添加如下面的一行:
{
% extends 'rango/base.html' %}
extends命令需要有一个参数,这个参数表示所要继承页面模板的名称(即rango/base.html)。在extends命令中提供的继承模板参数应该是相对于项目的templates目录的。比如说,rango应用中使用的所有的模板都应该继承于rango/base.html,而不是base.html。我们继续修改category.html模板让它看起来如下面的完整示例。
- template/rango/category.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
{
{
category.name}}
{
% endblock %}
{
% block body_block %}
{
% if category %}
<h1>{
{
category.name }}</h1>>
{
% if pages %}
<ul>
{
% for page in pages %}
<li><a href="{
{page.url}}">{
{
page.title}}</a>></li>>
{
% endfor %}
</ul>>
{
% else %}
<strong>No pages currently in category.</strong>>
{
% endif %}
<a href="{% url 'add_page' category.slug %}">Add a Page</a>
{
% else %}
The specified category does not exist!
{
% endif %}
{
% endblock %}
你得确保在每个模板文件里的顶部添加了{% load staticfiles %}用来加载静态媒体文件。如果不这样做,就会出错。
8.4 render() 函数和 request 上下文
- 编写视图代码时我们已经尝试了好几个不同的渲染方法,一个更好的方法就是使用django的快捷方法render()。rener()方法需要你传递request作为第一个参数。request上下文存放了许多与会话、登录用户等相关的信息,查看django官方文档获取更多信息。将request传递给模板表示在创建模板时你还可以访问那些信息。下个章节,我们将会访问关于user的信息。现在我们需要检查一下所有的视图函数,确保他们用render()方法实现,要不然你的模板就获取不到会话等相关信息。
- about视图是一个你必须要执行检查的简单样例。如下所示,最开始是通过硬编码返回字符串实现的。这里我们仅发送字符串,我们并没有用到request参数。
def about(request):
return HttpResponse('Rango says: Here is the about page.<a href="/rango/">Index</a>')
- 为了展示模板的使用方法,我们调用了render()函数并传递了request对象。这样就能让你的模板引擎访问到request请求类型的数据(例如GET/POST)和一些与用户状态相关的信息(查看第9章)。
def about(request):
# prints out whether the method is a GET or a POST
print(request.method)
# prints out the user name, if no one is logged in it prints 'AnonymousUser'
print(request.user)
return render(request, 'rango/about.html', {
})
- 记住,render()函数的最后一个参数就是上下文字典,你可以利用它来传递额外的数据给django的模板引擎。这里我们不需要传递额外数据,我们就传递来一个空的字典{}。
8.5 自定义模板标签
让用户通过每个页面的工具栏能够访问到不同的分类是个不错的用户体验。结合目前学到的知识,我们可以做到如下地步:
- 在base.html模板里,我们可以增加代码用来展示分类列表,
在每个视图里,我们可以访问所有的Category对象,获取所有的分类,并通过上下文字典返回。 - 然而,这是一个非常差的方法,因为我们需要在所有的视图里不断地重复着相同的代码。一个DIY式的解决方案就是在模板里导入一个自定义一个标签,这个标签可以自动请求数据。
定义模板标签
创建一个目录rango/templatetags,在里面再创建两个模块。一个模块必须叫__init__.py,这个模块保留空白;另一个模块必须叫rango_template_tag.py,在这个文件里添加如下代码。
from django import template
from rango.models import Category
register = template.Library()
@register.inclusion_tag('rango/cats.htm1')
def get_category_list():
return {
’cats': Category.objects.al1()}
上面的代码片段中,你看到了一个新的叫get_category_list的方法,这个方法返回了分类的列表,但是是同rango/cats.html模板混合在一起了(可以在register.inclusion_tag装饰器中看出)。现在你可以创建一个模板文件,增加如下的HTML代码。
<ul>
{
% if cats %}
{
% for c in cats %}
<li><a href="{% url 'show_category' c.slug %}">{
{
c.name }}</a></li>
{
% endfor %}
{
% else %}
<li> <strong>There are no categories present.</strong></li>
{
% endif %}
</ul>
要在基类模板中使用自定义模板标签,首先要在base.html模板上方导入命令{% load rango_template_tags %} 来加载自定义标签。然后你就可以创建一个新的块用来表示工具栏,这样我们就可以调用我们自定义的新标签了。
<div>
{
% block sidebar_block %}
{
% get_category_list %}
{
% endblock %}
</div>
参数化模板标签
我们也可以让自定义的模板标签带上参数,非常灵活。例如,我们用参数实现高亮显示用户正在访问的那个分类。添加一个参数是很容易的,如下修改get_category_list()方法:
def get_category_list(cat=None):
return {
'cats': Category.objects.al1(),
'act_cat': cat}
注意get_category_list()里的cat参数是可选的,如果你不传递一个分类进去,那默认值就是None。然后我们修改base.html模板,利用自定义的标签传递当前的分类(只有它存在才能传递)。
<div>
{
% block sidebar_block %}
{
% get_category_list category %}
{
% endblock %}
</div>
完整的模板base.html
<!DOCTYPE html>
{
% load static %}
{
% load rango_template_tags %}
<html>
<head lang="en">
<meta charset="UTF-8" />
<title>
Rango -
{
% block title_block %}
How to Tango with Django!
{
% endblock %}
</title>
</head>
<body>
<div>
{
% block sidebar_block %}
{
% get_category_list category %}
{
% endblock %}
</div>
<div>
{
% block body_block %}
This is body_block's default content.
{
% endblock %}
</div>
<hr />
<div>
<ul>
<li><a href="{% url 'add_category' %}">Add New category</a></li>
<li><a href="{% url 'about' %}">About</a></li>
<li><a href="{% url 'index' %}">index</a></li>
</ul>
</div>
</body>
</html>
然后再更新cat.html模板。
在模板中我们通过for循环来检查正在展示的分类是否同传递进来的分类一样(即c == act_cat)。如果是,我们就使用< strong>标签加粗文本以突出分类名称。
{
% for c in cats %}
{
% if c == act_cat %}
<li>
<strong>
<a href="{% url 'show_category' c.slug %}">{
{
c.name }}</a>
</strong>
</li>
{
% else %}
<li>
<a href="{% url 'show_category' c.slug %}">{
{
c.name }}</a>
</li>
{
% endif %}
{
% endfor %}
8.6 小结
- 使用url模板标签避免硬编码URL;
- 使用模板继承减少了样板代码数量;
- 使用自定义标签避免了视图函数里重复代码的出现。
8.7 练习
❏ 修改 Rango 应用中的其他模板,继承 base.html 模板。具体过程参见前文。改完之后,所有模板都继承自 base.html。
❏ 修改的过程中顺便把 index.html 模板中的链接删除。那些链接用不到了!about.html 模板中指向 Rango 首页的链接可以一并删除。
❏ 修改 index.html 模板时,不要删除图像。
- about.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
About
{
% endblock %}
{
% block body_block %}
<h1>Rango says...</h1>
<div>
This tutorial has been put together by <your-name>.
<img src="{
{MEDIA_URL}}cat.jpg" alt="Picture of cat"/>
</div>
<div>
hey there partner! <br />
<strong>{
{
boldmessage }}</strong><br />
</div>
<div>
<!--a href="/rango/about/">About</a><br /-->
<!--a href="{% url 'about' %}">About</a><br /-->
<img src="{% static 'images/rango.jpg'%}"
alt="Picture of Rango"/><!--新增-->>
</div>
{
% endblock %}
- add_category.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
Rango
{
% endblock %}
{
% block body_block %}
<h1>Add a Category</h1>
<div>
<form id="category_form" method="post" action="/rango/add_category/">
{
% csrf_token %}
{
% for hidden in form.hidden_fields %}
{
{
hidden }}
{
% endfor %}
{
% for field in form.visible_fields %}
{
{
field.errors }}
{
{
field.help_text }}
{
{
field }}
{
% endfor %}
<input type="submit" name="submit" value="Create Category"/>
</form>
</div>>
{
% endblock %}
- add_page.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
Rango - Add Page
{
% endblock %}
{
% block body_block %}
{
% if category %}
<h1>Add a Page to {
{
category.name}}</h1>
<form id="page_form" method="post" action="/rango/category/{
{ category.slug }}/add_page/">
{
% csrf_token %}
{
% for hidden in form.hidden_fields %}
{
{
hidden }}
{
% endfor %}
{
% for field in form.visible_fields %}
{
{
field.errors }}
{
{
field.help_text }}
{
{
field }}
{
% endfor %}
<input type="submit" name="submit" value="Add Page" />
</form>
{
% else %}
The specified category does not exist!
{
% endif%}
{
% endblock %}
- category.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
{
{
category.name}}
{
% endblock %}
{
% block body_block %}
{
% if category %}
<h1>{
{
category.name }}</h1>>
{
% if pages %}
<ul>
{
% for page in pages %}
<li><a href="{
{page.url}}">{
{
page.title}}</a>></li>>
{
% endfor %}
</ul>>
{
% else %}
<strong>No pages currently in category.</strong>>
{
% endif %}
<a href="{% url 'add_page' category.slug %}">Add a Page</a>
{
% else %}
The specified category does not exist!
{
% endif %}
{
% endblock %}
- index.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
Rango
{
% endblock %}
{
% block body_block %}
<h1>Rango says...</h1>
<div>
hey there partner! <br /></div>
<div>
<div><h3>Most Liked Categories</h3> </div>
{
% if categories %}
<ul>
{
% for category in categories %}
<li>
<!--a href="/rango/category/{
{category.slug}}">{
{
category.name}}</a-->
<a href="{% url 'show_category' category.slug %}">{
{
category.name}}</a>
</li>
{
% endfor %}
</ul>
{
% else %}
<strong>There are no categories present.</strong>>
{
% endif %}
</div>
<div>
<div><h3>Most Viewed Pages</h3></div>
{
% if pages %}
<ul>
{
% for page in pages %}
<li><a href="{
{ page.url }}">{
{
page.title }}</a></li>
{
% endfor %}
</ul>
{
% else %}
<strong>There are no categories present.</strong>
{
% endif %}
</div>
<div>
<!--a href="/rango/about/">About Rango</a><br /-->
<img src="{% static 'images/rango.jpg'%}" alt="Picture of Rango"/><!--新增-->>
</div>
<div>
<!--a href="/rango/add_category/">Add a New Category</a><br /-->
</div>
{
% endblock %}
- page_list.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
NA
{
% endblock %}
{
% block body_block %}
{
% if pages %}
<ul>
{
% for page in pages %}
<li><a href="{
{page.url}}">{
{
page.title}}{
{
page.views}}</a>></li>>
{
% endfor %}
</ul>
{
% else %}
<strong>No pages currently in category.</strong>
{
% endif %}
{
% endblock %}
❏ 使用 url 模板标签更新所有内部链接。也可以在 views.py 模块中修改,此时要使用reverse() 辅助函数。
- add_category.html
{
% extends 'rango/base.html' %}
{
% load static %}
{
% block title_block %}
Rango
{
% endblock %}
{
% block body_block %}
<h1>Add a Category</h1>
<div>
<!--form id="category_form" method="post" action="/rango/add_category/"-->
<form id="category_form" method="post" action="{% url 'add_category' %}"></form>
{
% csrf_token %}
{
% for hidden in form.hidden_fields %}
{
{
hidden }}
{
% endfor %}
{
% for field in form.visible_fields %}
{
{
field.errors }}
{
{
field.help_text }}
{
{
field }}
{
% endfor %}
<input type="submit" name="submit" value="Create Category"/>
</form>
</div>>
{
% endblock %}
8.8 心得
- 使用相对 URL就像是类Class的引用,加一堆点.
- 模板继承可以统一网页风格,以{% block title_block %}和{% endblock %}来定义,以{% extends ‘rango/base.html’ %}来运用。
- 未解决8.5 中“‘rango_template_tags’ is not a registered tag library”,故先删除8.5章内容