上一篇文章的示例中返回文本的方式有些特别,即在 Python 代码中硬编码 HTML,如下所 示:
def now(request):
now=datetime.datetime.now()
show="你好,现在的时间是{}".format(now)
return HttpResponse(show)
这样做虽然便于说明视图的工作方式,但是直接在视图中硬编码 HTML 不是个好主意。
原因如下:
• 只要想修改页面的设计就要修改 Python 代码。网站的设计肯定比底层的 Python 代码变化频繁,因此 如果无需修改 Python 代码就能改变设计,那多方便。
• 这只是十分简单的示例。网页模板往往包含几百行 HTML 和脚本。在这样的混乱中排错和诊断程序代码简直是噩梦。
• 编写 Python 代码和设计 HTML 是两件不同的事,多数专业的 Web 设计团队会把这两件事交给不同的 人做(甚至不同的部门)。设计师和 HTML/CSS 程序员完成工作不应该需要编辑 Python 代码。
• 如果编写 Python 代码的程序员和设计模板的设计师能同时工作,而不用等到一个人编辑好包含 Python 和 HTML 的文件之后再交给下一个人,工作效率能得到提升。
鉴于此,把页面的设计与 Python 代码分开,结果更简洁,也更易于维护。为此,我们可以使用 Django 的模板系统。
1、模板基础知识
Django 模板是一些文本字符串,作用是把文档的表现与数据区分开。模板定义一些占位符和基本的逻辑(模 板标签),规定如何显示文档。通常,模板用于生成 HTML,不过 Django 模板可以生成任何基于文本的格式。
下面看一个简单的模板示例 sample.html
<html>
<head>
<title>武侠三班</title>
</head>
<body>
<h1>通知</h1>
<p>本次中期比武,{
{ student1_name }}获得了第一名</p>
<p>获得优秀的同学还有{
{student2_name}}、{
{student3_name}}、{
{student4_name}}等</p>
<p>希望其他同学以他们为榜样,苦练武功,争取下次比武取得好成绩</p>
<ul>
{% for k in student_list %}
{% if k.0 == 'male' %}
<li>{
{ k.1 }}大侠</li>
{% else %}
<li>{
{ k.1 }}女侠</li>
{% endif %}
{% endfor %}
</ul>
<p>落款:你们尊敬的老师<br />{
{ teacher }}</p>
<p>日期: {
{ now|date:"D d M Y" }} </p>
</body>
</html>
这个模板的大部分内容是 HTML,内含一些变量和模板标签。
下面详细说明。
• 两对花括号包围的文本(如 { { student1_name }})是变量,意思是“把指定变量的值插入这里”。
• 一对花括号和百分号包围的文本(如 {% if ordered_warranty %})是模板标签。
标签的定义相当宽泛:只要能让模板系统“做些事”的就是标签。
• 这个示例模板中有一个 for 标签{% for s in student_list %}和一个 if 标签({% if k.0 == 'male' %})。
for 标签的作用与 Python 中的 for 语句很像,用于迭代序列中的各个元素。
if 标签的作用是编写逻辑判断语句。 注意,{% else %} 是可选的。
• 最后的落款部分包含一个过滤器,这是调整变量格式最为便利的方式。对这个示例中的 { { now|date:"D d M Y" }}来说,我们把 now 变量传给 date 过滤器,并且为 date 过滤器指定 "D d M Y" 参数。date 过滤器使用参数指定的格式格式化日期。过滤器使用管道符号(|)依附,类似 于 Unix 管道。
Django 模板能访问多个内置的标签和过滤器,后面我们会展开讨论。
2、模板的使用
首先需要向Django说明模板文件的路径,修改settings.py,修改 TEMPLATES 中的 DIRS 为 [os.path.join(BASE_DIR, 'templates')],如下所示:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
然后我们在views.py里面修改视图函数,这次我们不用HttpResponse了,我们引入render函数
def classnotice(request):
now=datetime.datetime.now()
students={1:'郭靖',2:'杨过',3:'王语嫣',4:'赵敏'}
students2=[['male','郭靖'],['male','杨过'],['female','王语嫣'],['female','赵敏']]
context={}
context['student1_name']=students[1]
context['student2_name']=students[2]
context['student3_name']=students[3]
context['student4_name']=students[4]
context['student_list']=students2
context['teacher']='王重阳'
context['now']=now
return render(request,'sample.html',context=context,status=200)
我们重新写一个视图函数classnotice,定义上面模板文件里要用到的各个变量,然后新建一个空字典 context,把各个变量都装进这个字典去。这里要注意,context字典的键值就是模板要引用的变量名,如模板里面要用到变量 student1_name,那这里就要用context['student1_name]=students[1]来赋值,这样这个值才能传给模板,让模板以这个变量名去调用它。数字、字符串、甚至列表、对象等都可以作为字典的值传送给模板。
注意:如果是字典、列表、对象等作为值传给模板,模板在调用的时候,跟Python里面有所不同,Python里面用中括号 students[1]来取值,模板里面统统用(变量+"."+属性、方法 等等)的形式来取值,如上面的变量表示为 students.1的形式来取值
最后一行返回值,这里用到了render函数,第一个参数是request请求对象,第二个参数是模板名称,第三个参数就是这个context字典,所有的变量值通过这个字典传送,第四个参数是响应值,200表示成功响应。
同样的,我们要去配置urls.py,增加一个路径'notice',映射的函数是上面我们写的 classnotice
from django.contrib import admin
from django.urls import path
from newweb.views import *
urlpatterns = [
path('',now),
path('admin/', admin.site.urls),
path('hello/',hello),
path('student/<int:id>/',student),
path('student/<str:sex>/',student2),
path('notice',classnotice),
]
最后我们还要注意查看一下sample.html文件的位置,根据setting.py的路径,它应该在内层newweb文件夹同级别即manage.py所在的文件夹下新建一个 templates文件夹,然后sample.html文件放在这里,后面所有的模板文件都应该放在这里
一切OK,我们访问地址 http://127.0.0.1:8000/notice,看到了下面的内容
3、模板的继承
我们上面举的例子都是很简单的网页,而真实的网站是很复杂的,大家往往发现,网站有很多网页,但是这些网页有差不多同样的页头、边栏和页脚等等,这些同样的内容如果每次都要重复去写会增加很多工作量,也不利于维护,所以使用Django模板的继承功能是很优雅的解决方案。
模板继承是指创建一 个基底“骨架”模板,即父模板,包含网站的所有通用部分,并且定义一些“块”,让子模板覆盖。
父模板用于放置可重复利用的内容,子模板继承父模板的内容,并放置自己的内容。
父模板
标签 block...endblock: 父模板中的预留区域,该区域留给子模板填充差异性的内容,不同预留区域名字不能相同。
{% block 名称 %}
预留给子模板的区域,可以设置设置默认内容
{% endblock 名称 %}
子模板
子模板使用标签 extends 继承父模板
{% extends "父模板路径"%}
子模板如果没有设置父模板预留区域的内容,则使用在父模板设置的默认内容,当然也可以都不设置,就为空。
子模板设置父模板预留区域的内容:
{ % block 名称 % }
内容
{% endblock 名称 %}
下面我们先创建一个名称为base.html的父模板
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>武侠世界</title>
<style>
* {
box-sizing: border-box;
}
body {
font-family: Arial;
padding: 10px;
background: #f1f1f1;
}
/* 头部标题 */
.header {
padding: 30px;
text-align: center;
background: white;
}
.header h1 {
font-size: 50px;
}
/* 导航条 */
.topnav {
overflow: hidden;
background-color: #333;
}
/* 导航条链接 */
.topnav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
/* 链接颜色修改 */
.topnav a:hover {
background-color: #ddd;
color: black;
}
/* 创建两列 */
/* Left column */
.leftcolumn {
float: left;
width: 75%;
}
/* 右侧栏 */
.rightcolumn {
float: left;
width: 25%;
background-color: #f1f1f1;
padding-left: 20px;
}
/* 图像部分 */
.fakeimg {
background-color: #aaa;
width: 100%;
padding: 20px;
}
/* 文章卡片效果 */
.card {
background-color: white;
padding: 20px;
margin-top: 20px;
}
/* 列后面清除浮动 */
.row:after {
content: "";
display: table;
clear: both;
}
/* 底部 */
.footer {
padding: 20px;
text-align: center;
background: #ddd;
margin-top: 20px;
}
/* 响应式布局 - 屏幕尺寸小于 800px 时,两列布局改为上下布局 */
@media screen and (max-width: 800px) {
.leftcolumn, .rightcolumn {
width: 100%;
padding: 0;
}
}
/* 响应式布局 -屏幕尺寸小于 400px 时,导航等布局改为上下布局 */
@media screen and (max-width: 400px) {
.topnav a {
float: none;
width: 100%;
}
}
</style>
</head>
<body>
<div class="header">
<h1>武侠世界</h1>
<p>武林人士自己的线上江湖</p>
{% block title %}
武侠二班主页
{% endblock title %}
</div>
<div class="topnav">
<a href="#">武侠一班</a>
<a href="#">武侠二班</a>
<a href="#">武侠三班</a>
<a href="#" style="float:right">华山论剑</a>
</div>
<div class="leftcolumn">
{% block main %}
<p>
这是base模板
</p>
{% endblock main %}
</div>
<div class="rightcolumn">
<div class="card">
<h2>武力排名</h2>
<ul>
{% for k in student_list %}
{% if k.0 == 'male' %}
<li>{
{ k.1 }}大侠</li>
{% else %}
<li>{
{ k.1 }}女侠</li>
{% endif %}
{% endfor %}
</ul>
</div>
<div class="card">
<h3>武林资讯</h3>
<div class="fakeimg"><p>杨过找到了失散多年的姑姑,久别重逢让人唏嘘不已</p></div>
<div class="fakeimg"><p>西毒欧阳锋重现江湖,西域多人已经遇害</p></div>
<div class="fakeimg"><p>武侠二班老同学聚会,黄药师回母校参观</p></div>
</div>
<div class="card">
<h3>关注下一届武林大会选举</h3>
<p>传闻乔峰和张无忌公将要争夺武林盟主之位</p>
</div>
</div>
</div>
<div class="footer">
<h2>底部区域</h2>
<span class="link">
<a href="javascript:void(0)">Django</a> |
<a href="javascript:void(0)">网站搭建</a> |
<a href="javascript:void(0)">人生苦短</a> |
<a href="javascript:void(0)">我用Python</a>
</span>
<p class="bottom-content">
<span>地址: 上海市武林大道武侠大厦2805室 </span>
<span>联系方式: 021-88888888</span>
</p>
<p class="copyright-desc">
Copyright © 2005 - 2021 武侠世界. All Rights Reserved
</p>
</div>
</body>
</html>
这里我为了方便,把css的设定写在了head里面,正式开发的话css应该单独存一个文件。
我们直接双击打开看看效果
可以看到,这个base.html搭建了网站的整体框架,包括页头、页尾、边栏等,还有一些固定信息,比如网站标题、尾部的联系地址、公司资料等。
其中标题那里 留下了给子模版继承的部分
{% block title %}
武侠二班主页
{% endblock title %}
这个中间“武侠二班的主页”是可以被子模板替换的部分,等下我们把它换成三班
中间左边的部分,也留下了可以继承的部分
{% block main %}
<p>
这是base模板
</p>
{% endblock main %}
这里面就是要放我们子模板的主体部分了,所以我把它命名为“main”,这个名称是自己取的,但是记得这里取什么名字,到子模板中调用的时候要用到这个名字
现在我们去写子模板,把我们之前的sample.html网页的内容作为子模板内容放进去
{% extends "base.html" %}
{% block title %}武侠三班的小窝{% endblock %}
{% block main %}
<h1>通知</h1>
<p>本次中期比武,{
{ student1_name }}获得了第一名</p>
<p>获得优秀的同学还有{
{student2_name}}、{
{student3_name}}、{
{student4_name}}等</p>
<p>希望其他同学以他们为榜样,苦练武功,争取下次比武取得好成绩</p>
<ul>
{% for k in student_list %}
{% if k.0 == 'male' %}
<li>{
{ k.1 }}大侠</li>
{% else %}
<li>{
{ k.1 }}女侠</li>
{% endif %}
{% endfor %}
</ul>
<p>落款:你们尊敬的老师<br />{
{ teacher }}</p>
<p>日期: {
{ now|date:"D d M Y" }} </p>
{% endblock %}
注意:
开头要用{% extends "base.html" %}表示引用base.html作为父模板
{% block title %}武侠三班的小窝{% endblock %}就是把父模板中title那一部分的内容换成我们自己的
{% block main %} 和 {% endblock main %} 之间的内容替换掉父模板的内容
保存为class3.html ,把views.py中的classnotice函数改一下,render的第二个参数改为class3.html
意思classnotice这个视图函数映射的模板改为了class3.html,就是我们刚刚创建的继承的页面
def classnotice(request):
now=datetime.datetime.now()
students={1:'郭靖',2:'杨过',3:'王语嫣',4:'赵敏'}
students2=[['male','郭靖'],['male','杨过'],['female','王语嫣'],['female','赵敏']]
context={}
context['student1_name']=students[1]
context['student2_name']=students[2]
context['student3_name']=students[3]
context['student4_name']=students[4]
context['student_list']=students2
context['teacher']='王重阳'
context['now']=now
return render(request,'class3.html',context=context,status=200)
然后我们访问http://127.0.0.1:8000/notice (因为我们之前的URL没有改,所以这里还是访问/notice,调用classnotice这个视图函数),看到效果如下:
可以看到页头、页尾和右边的边栏都和父模板一样的,只是中间的内容替换了,用这种方法,可以快速搭建网站,因为很多网页只是部分内容不一样,而且这样让整个网站看上去风格统一。
关于模板继承,有一些注意事项:
• 如果模板中有 {% extends %},必须是模板中的第一个标签。否则,模板继承不起作用。
• 一般来说,基模板中的 {% block %} 标签越多越好。记住,子模板无需定义父模板中的全部块,因此 可以为一些块定义合理的默认内容,只在子模板中覆盖需要的块。钩子多总是好的。
• 如果发现要在多个模板中重复编写相同的代码,或许说明应该把那些代码移到父模板中的一个 {% block %} 标签里。 • 如果需要从父模板中的块里获取内容,使用 { { block.super }},这是一个“魔法”变量,提供父模板中渲染后的文本。向块中添加内容,而不是完全覆盖时就可以这么做。
• 在同一个模板中不能为多个 {% block %} 标签定义相同的名称。之所以有这个限制,是因为 block 标签是双向的。即,block 标签不仅标识供填充的空位,还用于定义填充父模板中空位的内容。如果一 个模板中有两个同名的块,那么父模板就不知道使用哪个块里的内容。
• 传给 {% extends %} 的模板名称使用与 get_template() 相同的方法加载。即,模板在 DIRS 设置定义的 目录中,或者在当前 Django 应用的“templates”目录里。
• 多数情况下,{% extends %} 的参数是字符串,不过如果直到运行时才知道父模板的名称,也可以用变量。通过这一点可以做些动态判断。