最近在学习Django,打算玩玩网页后台方面的东西,因为一直很好奇但却没怎么接触过。Django对我来说是一个全新的内容,思路想来也是全新的,或许并不能写得很明白,所以大家就凑合着看吧~
本篇笔记(其实我的所有笔记都是),并不会过于详细的讲解。因此如果有大家看不明白的地方,欢迎在我正版博客下留言,有时间的时候我很愿意来这里与大家探讨问题。(当然,不能是简简单单就可以百度到的问题-.-)
我所选用的教材是《The Django Book 2.0》,本节是模板部分,对应书中第四章。
------------------------------------------------------------------------------------------------------------------------------------------------
0、代码示例
创建一个网站:django-admin.py startproject four
在网站根目录下创建一个文件夹:templates(网站根目录也就是那个有manage.py的目录,定位网站中任何文件,都默认此目录作为根目录)
在./templates/下,创建home.html,内容如下:
<html>
<head>
</head>
<body>
Hello~I'm {{name}}^.^<br />
</body>
</html>
在./templates/下,创建home.txt,内容如下:
name qiqi
在./four/下,修改urls.py,内容如下:
from django.conf.urls import url
from four.views import home urlpatterns = [
url(r'^$', home),
]
在./four/下,创建views.py,内容如下:
from django.http import HttpResponse
from django.template import Template, Context def home(request):
tin = open('./templates/home.html')
html = tin.read()
tin.close() cin = open('./templates/home.txt')
inf = {}
for line in cin.readlines():
a,b = line.split()
inf[a] = b
cin.close() t = Template(html)
c = Context(inf)
return HttpResponse(t.render(c))
此时,我们运行服务器(python manage.py runserver),便可以访问到以下网页了:
网址 | 内容 |
http://127.0.0.1:8000/ | Hello~I'm qiqi^.^ |
1、模板简介
这一章原文很长,我想,我先向大家大体介绍一下模板,后面再详述细节。
先说咱们熟悉的urls.py和views.py:
urls.py中删去了无用代码,只创建了一个home。
views.py中则利用模板实现了这个home。其中,除了Template、Context和render(即最后三行)属于模板内容之外,其余部分均为python中文件读取的语句。很显然,我把前面创建的home.html读到了html变量里,这是一个字符串;又把home.txt读到了inf变量里,这是一个字典。
好的,知道了这些,下面咱们开始介绍模板:
home.html就是一个模板,大家可以看到,这就是一段html代码,唯一不同的是,其中多了一个"{{name}}"。这是模板里“变量”的写法,变量名是name。这样写出一段可以包含变量的html代码,就算是写出了一个最基础的模板。
而home.txt中的内容,显然就是把name赋值成qiqi。这个东西就是为模板填写的内容了。
最后我们在views.py中,用Template函数把html代码转换成模板,用Context函数把字典转换成内容(书中有时翻译成“上下文”,但我更喜欢翻译成“内容”),而render函数则利用这个模板渲染了这段内容,生成一个网页。这就是传说中的模板了。
这里说明一个细节:render所产生的是Unicode字符串(大家可以自己在python交互界面下试试)!
看到这里,大家应该已经知道什么是模板,并且可以使用Django中的模板做一些有变量的网页了。但我更希望,大家能感到,这过程非常简单,各位同学完全可以自己用任何语言来完成这个任务。因为暂时来看,模板无非就是段变量替换的代码而已,随便一个会编程的人都可以做的。
2、模板基础语法
模板当然不只有替换变量这一个东西,那么我们现在开始介绍模板的各种用法:
引用/语句 | 示例 | 解释 | 备注 |
变量 | {{ name }} | name是变量名, |
如果这个变量不存在,模板中对应位置则会写成一个空字符串 (下面各种变量也都一样,方法还没有实验!!!) |
字典变量 |
{{ person.name }} {{ person.age }} |
person是字典, 有name和age两个key |
|
某个对象 |
{{ d.year }} {{ d.month }} {{ d.day }} |
import datetime d=datetime.date(2015, 5, 21) |
|
自定义类 |
{{ person.name }} {{ person.age }} |
person是自己定义的类, name和age是里面的成员变量 |
|
成员方法 |
{{ str.upper }} {{ str.isdigit }} |
str是string类型, string类型有upper和isdigit函数 |
1. 只能调用不需传参数的方法。 2. 如果方法中出现了错误,按说应该报错。 但如果这个错误中有下面这个属性,则不会报错: silent_variable_failure = True, 此时,模板中对应位置会被写成一个空字符串。 3. 模板系统不会执行任何以下列方式标记的函数, 即使调用了这个函数,也会默默退出,什么都不做: 设置函数属性alters_date = True。 |
列表索引 |
{{ items.0 }} {{ items.1 }} {{ items.2 }} |
items=['go','phone','computer'] |
不能使用负数列表索引: 例如{{ items.-1 }},会触发TemplateSyntexError。 |
if 语句 |
{% if value %} # aaa {% else %} # bbb {% endif %} |
value是变量名, else是可选的 |
1. 变量为真,则会显示aaa处内容;否则,显示bbb处内容。 所谓真,即:变量存在、非空、非布尔值False。只有以下值为假: [], (), {}, '', 0, None, False, 自定义对象中定义布尔值属性为False。 2. 可以使用and,or,not进行逻辑运算,也可以用not对变量取反, 但一句if中不可以同时出现and和or,以免造成逻辑混乱。 注意,不可以使用括号来提示优先级,not优先级高于and和or。 3. 可嵌套(也可与for相互嵌套) 4. 不支持别的if语法(例如elif) |
ifequal 语句 |
{% ifequal x y %} # aaa {% else %} # bbb {% endifequal %} |
如果x与y两变量相等, 那么显示aaa的内容, 否则显示bbb的内容。 |
x与y只能是变量、字符串、整型常数和浮点型常数。 其它类型都不能用,例如:字典、列表、布尔。 因此,判断变量值的真假,应当使用if语句。 |
for 语句 |
{% for x in Y %} # ... {% empty %} # ... {% endfor %} |
Y是要循环(又称迭代)的序列 x是每次循环中使用的变量名称 empty是可选的, 是Y为空时所显示的代码 |
1. 可以使用reversed反向迭代列表 2. 在每个循环中都有一个模板变量,其中含有一些提示循环进度的属性 forloop.counter:当前循环次数,从1开始计数 forloop.counter0:当前循环次数,从0开始计数 forloop.revcounter:当前剩余循环次数,从N开始,最后为1 forloop.revcounter0:当前剩余循环次数,从N-1开始,最后为0 forloop.first:布尔值,仅第一次迭代时为True forloop.last:布尔值,仅最后一次迭代时为True forloop.parentloop:指向上一级循环的forloop对象的引用 如果我们自己定义了一个forloop变量,Django模板也不会报错, 但我并不想介绍其语法,只是希望大家记住变量别叫这个名字就好。 2. 可嵌套(也可与if相互嵌套) 3. 不支持别的for语法(例如break,continue) |
过滤器 (filter) |
{{ name|lower }} {{ mylist|first|upper }} {{ s|truncatewords:"30" }} |
利用Unix的管道符'|'表示过滤器 左面举了4个例子: 1. 把name小写输出 2. 把mylist中第一个字符大写输出 3. 把s前30个字符输出 |
这里仅仅介绍几种常用过滤器,全部的介绍参见本书的附录F。 0. 左面例子中那几种 1. addslashes,添加反斜杠到任何反斜杠、单引号、双引号前面。 这在处理包含JavaScript的文本时非常有用。 2. date,按指定的格式字符串参数格式化date或datetime对象。 例如:{{ now|date:"F j, Y" }},含义详见附录。 3. length,返回变量的长度(针对有__len__()方法的对象)。 这里为新手说一句,列表、字符串就有__len__()方法,用来测长度。 |
单行注释 | {# something #} | something是被注释的内容 | |
多行注释 |
{% comment %} # ... {% endcomment %} |
2.1 '.'的优先级
大家可能已经注意到了,在Django的模板中,各种复杂变量都是使用一个点'.'来引用的。那么,这必然会有优先级问题:
查找顺序 | 类型 | 代码实例 |
1 | 字典 | qiqi["hello"] |
2 | 属性 | qiqi.hello |
3 | 方法 | qiqi.hello() |
4 | 列表索引 | qiqi[0] |
啰嗦一句,查找时采用的是“短路逻辑”,即找到一个匹配的就不继续往下找了。
2.2 '.'可以多级嵌套
2.3 报错信息
当遇到模板语法错误时,那么在调用Template函数时,会抛出TemplateSyntexError异常。所谓错误,有下列几种情况:
情况 | 备注 |
无效的标签(tags) | |
标签的参数无效 | |
无效的过滤器(filter) | |
过滤器的参数无效 | |
无效的模板语法 | |
未封闭的块标签 | 显然是对需要封闭的块标签而言的 |
2.4 Context修改
Context是可以像字典一样添加、删除条目的(但字典别的操作我就不知道Context有没有了!!!)。
3、如何使用模板
开头的代码,的确已经比较简洁地用上了模板。但其中,读取文件的操作显然又是要不断重复书写的代码。因此,Django进一步做了优化。
3.1 模板加载(get_template函数)
打开settings.py,你会看到一个TEMPLATES,其中有一个DIRS列表,这便是Django查找模板的地址了。
因此,在此处加上你的模板路径,例如咱们的templates文件夹,即:'./templates/'。
然后,修改views.py如下(仔细看,代码不只函数内容变了,import处也改了两行):
from django.http import HttpResponse
from django.template import Context
from django.template.loader import get_template def home(request):
cin = open('./templates/home.txt')
inf = {}
for line in cin.readlines():
a,b = line.split()
inf[a] = b
cin.close() t = get_template('home.html')
c = Context(inf)
return HttpResponse(t.render(c))
如此,你会发现网页已经正常运行,而你的代码简洁了不少;如果你的路径写错了,则会出现报错页面,告诉你发生了TemplateDoesNotExist错误。
get_template函数内的路径是可以有子目录的。
在此提醒一句,我的笔记只针对Linux用户,因为我用的就是Linux(在第一篇Django笔记中就已声明过了)。关于Windows用户路径是反斜杠的问题,请去查阅原书,其中有详细介绍。
3.2 模板加载并渲染(render_to_response函数)
我们代码还可以进一步简洁,如下:
from django.shortcuts import render_to_response def home(request):
cin = open('./templates/home.txt')
inf = {}
for line in cin.readlines():
a,b = line.split()
inf[a] = b
cin.close() return render_to_response('home.html', inf)
这个函数很简单,根据给定的字符串找到模板,再把给定的字典转换成内容,然后用模板渲染内容,最后生成Http response。
如果仅仅给出一个参数,字典没给,则默认是导入一个空字典。
render_to_response函数第一个参数的路径依旧可以有子目录,因为这个函数仅仅是对get_template函数的简单封装。
3.3 一个偷懒的小方法(locals函数)
前面用文件home.txt存变量,适合信息比较多的页面。那如果我们的页面中变量很少呢?
前面的做法适合多人协作,写网页的和写网站的分开。那如果我们是一个人做这两件事,而home.txt信息又很少,存个文件岂不是多此一举?
这时候,我们可以考虑使用python内建的函数locals(),这函数返回值中,包括了其所在函数从开头运行到现在所有的局部变量。
from django.shortcuts import render_to_response def home(request):
name = 'qiqi'
return render_to_response('home.html', locals())
代码的确很简单,但需要注意的是,locals()不只有name,还包含了request。代码简洁,实际却多传了变量。如何取舍呢?看你自己了。
4、模板包含(include模板标签)
代码 | 解释 |
{% include 'sub.html' %} | 这是相对路径,路径起点就是上面搜索模板的路径 |
{% include "sub.html" %} | 除了单引号,双引号字符串也是支持的 |
{% include 'includes/sub.html' %} | 可以有子目录 |
{% include template_name %} | 也可以使用字符串变量 |
如果你的路径错误,那么Django会查看你settings.py中的DEBUG参数:
若参数为Ture,那么你会在Django的报错页面中看到TemplateDoesNotExist错误;
若参数为False,那么该标签不会引发错误,其位置上不显示任何东西。
这里提醒一点,假如你的home.html模板,包含了一个sub.html模板。然后你用home.html模板渲染了一段内容,这内容应当同时包含两个模板所需的变量,这样sub.html方能有你所想要的内容。
5、啰嗦两句(没兴趣的直接跳过)
有些同学很喜欢猜谜,喜欢猜出作者所想的共鸣感。那么我现在给出一句话,随着下面的学习,相信在我最终解释之前,你会提前明白这句话的:模板继承与模板包含,思路是相反的。
但就我个人来说,非常反感猜谜。例如,很多书中都会先给出概念,然后隔很久才给出其实例(教材中屡见不鲜)。对这种做法,我简直是深恶痛绝。这会把我本来觉得很有意思的东西变得艰涩难懂,让我失去阅读的兴趣。因此,我向来都喜欢把文章尽我所能写得深入浅出,至少能让自己隔一段时间依旧很容易看懂。
又忍不住啰嗦了几句,没兴趣的读者请见谅,忽略这些就好……咱们回到正题,来说模板的继承。
6、模板继承代码
这段最好还是直接看代码比较好理解,下面让我们来重新建一个网站,名字叫FOUR。
首先按照咱们前面学的,把settings.py里面TEMPLATES参数的DIRS部分,加上一个路径:'./template/'。
然后,在网站根目录建一个文件夹template,里面写出三个模板,内容如下:
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>
current_datetime.html
{% extends "base.html" %} {% block title %}The current time{% endblock %} {% block content %}
<p>It is now {{ current_date }}.</p>
{% endblock %}
hours_ahead.html
{% extends "base.html" %} {% block title %}Future time{% endblock %} {% block content %}
<p>In {{ hour_offset }} hour(s), it will be {{ next_time }}.</p>
{% endblock %}
最后,在'./FOUR/'下,修改urls.py并且创建views.py,内容如下:
urls.py
from django.conf.urls import url
from FOUR.views import * urlpatterns = [
url(r'^time/$', current_datetime),
url(r'^time/(\d{1,2})/$', hours_ahead)
]
views.py
from django.http import Http404
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 }) def hours_ahead(request, offset):
try:
offset = int(offset)
except ValueError:
raise Http404()
dt = datetime.datetime.now() + datetime.timedelta(hours=offset)
return render_to_response('hours_ahead.html', { 'hour_offset': offset, 'next_time': dt })
至此,网站建立完成。运行后,便可进入以下网页:
网址 | 内容 |
http://127.0.0.1/time/ | 显示当前时间(UTC时间) |
http://127.0.0.1/time/num/ | 显示当前时间+num小时(num=8则得到中国标准时间) |
7、模板继承讲解
其实,模板继承部分,大家仔细看上面代码,就应该能够看个八九不离十。就是A模板继承了B模板,把B模板*替换的块选几个替换了,形成一个新的模板。
下面,讲解其各种小细节,请结合代码学习。
标签代码 | 解释 | 备注 |
{% block name %} #... {% endblock %} |
供继承的部分 注意,name不能重名 |
如果继承后重写了,则显示重写内容; 如果没有重写,则显示此处本来内容。 |
{% extends name %} |
继承name模板 name可以是常量,也可以是变量 |
此标签必须是模板的第一个标记!否则,视为没进行模板继承。 此标签的路径也是上文中模板的路径,即TEMPLATES中的DIRS |
{{ block.super name }} |
这是个变量,是父模板中的name标签的内容 |
往往用于并不打算完全重写,而是添加几句话的情况 |
在此说明一点,继承是可以多层嵌套的,即A模板继承B模板,B模板又继承了C模板
8、模板包含与继承的比较
模板包含,是在基础模板中写了大家都有的内容,然后后面都包含它;而模板继承,则是在基础模板中写了大家都有的部分,又标出了大家不同的部分,而后进行填充。
因此,我认为继承更强大和顺手,而包含则更适用于比较简单的网页结构,因为太简单就没必要搞继承了。
9、小吐槽
书中认为,模板继承可以理解为模板包含的逆向思维。因为包含是对相同的代码段进行定义,而继承则是对那些不同的代码段进行定义。
但个人认为,此处理解并不恰当,继承是比包含更加强大的,而非单纯的相反思路。
当然,我这样是断章取义,书中自有其思路。另外,我看的是中文译本,也说不定和英文原版的意思有偏差呢~
故而此处仅仅小小吐槽一下,目的只为理清读者思路。
------------------------------------------------------------------------------------------------------------------------------------------------
至此,“模板”一章笔记完成,下一章是与数据库打交道——“模型”。