模板
模板的功能
产生html,控制页面上展示的内容。模板文件不仅仅是一个html文件。
模板文件包含两部分内容:
- 静态内容:css,js,html。
- 动态内容:用于动态去产生一些网页内容。通过模板语言来产生。
模板文件的使用
通常是在视图函数中使用模板产生html内容返回给客户端。
- 加载模板文件 loader.get_template
获取模板文件的内容,产生一个模板对象。 - 定义模板上下文 RequeseContext
给模板文件传递数据。 - 模板渲染产生html页面内容 render
用传递的数据替换相应的变量,产生一个替换后的标准的html内容。
view.py
from django.shortcuts import render,redirect
from django.template import loader,RequestContext
def my_render(request, template_path, context={
}):
# 1.加载模板文件,获取一个模板对象
temp = loader.get_template(template_path)
# 2.定义模板上下文,给模板文件传数据
context = RequestContext(request, context)
# 3.模板渲染,产生一个替换后的html内容
res_html = temp.render(context)
# 4.返回应答
return HttpResponse(res_html)
# /index
x`
def index(request):
# return my_render(request, 'booktest/index.html')
return render(request, 'booktest/index.html')
模板文件加载顺序
- 首先去配置的模板目录下面去找模板文件。
- 去INSTALLED_APPS下面的每个应用的templates去找模板文件,前提是应用中必须有templates文件夹。
模板语言
模板语言简称为DTL。(Django Template Language)
模板变量
模板变量名是由数字,字母,下划线和点组成的,不能以下划线开头。
使用模板变量:{
{模板变量名}}
模板变量的解析顺序:
例如:{
{ book.btitle }}
- 首先把book当成一个字典,把btitle当成键名,进行取值book[‘btitle’]
- 把book当成一个对象,把btitle当成属性,进行取值book.btitle
- 把book当成一个对象,把btitle当成对象的方法,进行取值book.btitle
例如:{ {book.0}} - 首先把book当成一个字典,把0当成键名,进行取值book[0]
- 把book当成一个列表,把0当成下标,进行取值book[0]
如果解析失败,则产生内容时用空字符串填充模板变量。
使用模板变量时,.前面的可能是一个字典,可能是一个对象,还可能是一个列表。
# /temp_var
def temp_var(request):
'''模板变量'''
my_dict = {
'title':'字典键值'}
my_list = [1,2,3]
book = BookInfo.objects.get(id=1)
# 定义模板上下文
context = {
'my_dict':my_dict, 'my_list':my_list, 'book':book}
return render(request, 'booktest/temp_var.html', context)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板变量</title>
</head>
<body>
使用字典属性:{
{ my_dict.title }}<br/>
使用列表元素:{
{ my_list.1 }}<br/>
使用对象属性:{
{ book.btitle }}
</body>
</html>
模板标签
{% 代码段 %}
for循环:
{% for x in 列表 %}
# 列表不为空时执行
{% empty %}
# 列表为空时执行
{% endfor %}
可以通过=={
{ forloop.counter }}==得到for循环遍历到了第几次。
{% if 条件 %}
{% elif 条件 %}
{% else %}
{% endif %}
关系比较操作符:> < >= <= == !=
注意:进行比较操作时,比较操作符两边必须有空格。
逻辑运算:not and or
model.py
# /temp_tags
def temp_tags(request):
'''模板标签'''
# 1. 查找所有图书的信息
books = BookInfo.objects.all()
return render(request,'booktest/temp_tags.html', {
'books':books})
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>模板标签</title>
<style>
.red {
background-color: red;
}
.yellow {
background-color: yellow;
}
.green {
background-color: green;
}
</style>
</head>
<body>
<ul>
{% for book in books %}
{% if book.id <= 2 %}
<li class="red">{
{ forloop.counter }}--{
{ book.btitle }}</li>
{% elif book.id <= 5 %}
<li class="yellow">{
{ forloop.counter }}--{
{ book.btitle }}</li>
{% else %}
<li class="green">{
{ forloop.counter }}--{
{ book.btitle }}</li>
{% endif %}
{% endfor %}
</ul>
</body>
</html>
过滤器
过滤器用于对模板变量进行操作。
date:改变日期的显示格式。
length:求长度。字符串,列表.
default:设置模板变量的默认值。
格式:模板变量|过滤器:参数
参考资料:(模板标签和内置过滤器)
http://python.usyiyi.cn/documents/django_182/ref/templates/builtins.html
<!DOCTYPE html>
<html lang="en">
{% load filters %}
<head>
<meta charset="UTF-8">
<title>模板过滤器</title>
<style>
.red {
background-color: red;
}
.yellow {
background-color: yellow;
}
.green {
background-color: green;
}
</style>
</head>
<body>
<ul>
{% comment %}
{% for book in books %}
{# {% if book.id <= 2 %} #}
{# {% if book.id|mod %}#}
{% if book.id|mod_val:3 %}
<li class="red">{
{ book.id }}--{
{ book.btitle|length }}--{
{ book.bpub_date|date:'Y年-m月-d日' }}</li>
{% else %}
<li class="green">{
{ book.btitle }}--{
{ book.bpub_date }}</li>
{% endif %}
{% endfor %}
{% endcomment %}
<!-- <h1>html注释</h1> -->
</ul>
default过滤器:<br/>
{
{ content|default:'hello' }}
</body>
</html>
自定义过滤器
自定义的过滤器函数,至少有一个参数,最多两个
应用中新建templatetags目录,新建filter.py
# 自定义过滤器
# 过滤器其实就是python函数
from django.template import Library
# 创建一个Library类的对象
register = Library()
# 自定义的过滤器函数,至少有一个参数,最多两个
@register.filter
def mod(num):
'''判断num是否为偶数'''
return num%2 == 0
@register.filter
def mod_val(num, val):
'''判断num是否能被val整除'''
return num%val == 0
在模板文件中调用
模板注释
单行注 释:{# 注释内容 #}
多行注释:
{% comment %}
注释内容
{% endcomment %}
网页中查看源代码不能看到模板注释
模板继承
模板继承也是为了重用html页面内容。
在父模板里可以定义块,使用标签:
{% block 块名 %}
块中间可以写内容,也可以不写
{% endblock 块名%}
子模板去继承父模板之后,可以重写父模板中的某一块的内容。
继承格式:{% extends 父模板文件路径%}
{% block 块名 %}
{
{ block.super}} #获取父模板中块的默认内容
重写的内容
{% endblock 块名%}
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}父模板文件{% endblock title %}</title>
</head>
<body>
<h1>导航条</h1>
{% block b1 %}
<h1>这是父模板b1块中的内容</h1>
{% endblock b1 %}
{% block b2 %}
<h1>这是父模板b2块中的内容</h1>
{% endblock b2 %}
<h1>版权信息</h1>
</body>
</html>
child.html
{% extends 'booktest/base.html' %}
{% block title %}子模板文件{% endblock title %}
{% block b1 %}
{
{ block.super }}
<h1>这是子模板b1块中的内容</h1>
{% endblock b1 %}
{% block b2 %}
{
{ block.super }}
<h1>这是子模板b2块中的内容</h1>
{% endblock b2 %}
html转义
编辑商品详情信息,数据表中保存的是html内容。
在模板上下文中的html标记默认是会被转义的。
# /html_escape
def html_escape(request):
'''html转义'''
return render(request, 'booktest/html_escape.html', {
'content':'<h1>hello</h1>'})
小于号< 转换为<
大于号> 转换为>
单引号' 转换为'
双引号" 转换为 "
与符号& 转换为 &
要关闭模板上下文字符串的转义:可以使用 {
{ 模板变量|safe}}
也可以使用:
{% autoescape off %}
模板语言代码
{% endautoescape %}
模板硬编码中的字符串默认不会经过转义,如果需要转义,那需要手动进行转义。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>html转义</title>
</head>
<body>
html转义:<br/>
{
{ content }}<br/>
使用safe过滤器关闭转义:<br/>
{
{ content|safe }}<br/>
使用autoescape关闭转义:<br/>
{% autoescape off %}
{
{ content }}
{
{ content }}
{% endautoescape %}<br/>
模板硬编码中的字符串默认不会经过转义:<br/>
{
{ test|default:'<h1>hello</h1>' }}<br/>
手动进行转义:<br/>
{
{ test|default:'<h1>hello</h1>' }}
</body>
</html>
csrf攻击
首先做一个登录页,让用户输入用户名和密码进行登录,登录成功之后跳转的修改密码页面。在修改密码页面输入新密码,点击确认按钮完成密码修改。
登录页需要一个模板文件login.html.修改密码页面也需要一个模板文件change_pwd.html.
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<form method="post" action="/login_check">
{% csrf_token %}
用户名:<input type="text" name="username" value="{
{ username }}"><br/>
密码:<input type="password" name="password"><br/>
<input type="checkbox" name="remember">记住用户名<br/>
<img src="/verify_code"><input type="text" name="vcode"><br/>
<input type="submit" value="登录">
</form>
</body>
</html>
change_pwd.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>修改密码页面</title>
</head>
<body>
<form method="post" action="/change_pwd_action">
{
% csrf_token %}
新密码:<input type="password" name="pwd">
<input type="submit" value="确认修改">
</form>
</body>
</html>
显示登录页的视图login,验证登录的视图login_check,显示发帖页的视图change_pwd,处理修改密码的视图change_pwd_action.
加功能:
a)只有用户登录之后才可以进行修改密码操作。
# /change_pwd
@login_required
def change_pwd(request):
'''显示修改密码页面'''
# # 进行用户是否登录的判断
# if not request.session.has_key('islogin'):
# # 用户未登录,跳转到登录
# return redirect('/login')
return render(request, 'booktest/change_pwd.html')
# /change_pwd_action
@login_required
def change_pwd_action(request):
'''模拟修改密码处理'''
# # 进行用户是否登录的判断
# if not request.session.has_key('islogin'):
# # 用户未登录,跳转到登录
# return redirect('/login')
# 1.获取新密码
pwd = request.POST.get('pwd')
# 获取用户名
username = request.session.get('username')
# 2.实际开发的时候: 修改对应数据库中的内容...
# 3.返回一个应答
return HttpResponse('%s修改密码为:%s'%(username,pwd))
登录装饰器函数。
def login_required(view_func):
'''登录判断装饰器'''
def wrapper(request, *view_args, **view_kwargs):
# 判断用户是否登录
if request.session.has_key('islogin'):
# 用户已登录,调用对应的视图
return view_func(request, *view_args, **view_kwargs)
else:
# 用户未登录,跳转到登录页
return redirect('/login')
return wrapper
案例流程图:
1.登录正常网站后,浏览器保存的sessionid,没有退出
2.同时访问另一个网站,并点击上边的按钮
而第三方网站会利用sessionid修改最开始登录网站的信息
django防止csrf的方式:
- 默认打开csrf中间件。
2) 表单post提交数据时加上{% csrf_token %}标签。
防御原理:
-
渲染模板文件时在页面生成一个名字叫做csrfmiddlewaretoken的隐藏域。
-
服务器交给浏览器保存一个名字为csrftoken的cookie信息。
-
提交表单时,两个值都会发给服务器,服务器进行比对,如果一样,则csrf验证通过,否则失败。
验证码
在用户注册、登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。
接下来的代码不要求手动写出来,因为这种代码在网上可以搜到很多。
1)安装包Pillow3.4.1。
pip install Pillow==3.4.1
点击查看PIL模块API,以下代码中用到了Image、ImageDraw、ImageFont对象及方法。
2)在booktest/views.py文件中,创建视图verify_code。
提示1:随机生成字符串后存入session中,用于后续判断。
提示2:视图返回mime-type为image/png。
from PIL import Image, ImageDraw, ImageFont
from django.utils.six import BytesIO
# /verify_code
def verify_code(request):
# 引入随机函数模块
import random
# 定义变量,用于画面的背景色、宽、高 RGB
bgcolor = (random.randrange(20, 100), random.randrange(
20, 100), 255)
width = 100
height = 25
# 创建画面对象
im = Image.new('RGB', (width, height), bgcolor)
# 创建画笔对象
draw = ImageDraw.Draw(im)
# 调用画笔的point()函数绘制噪点
for i in range(0, 100):
xy = (random.randrange(0, width), random.randrange(0, height))
fill = (random.randrange(0, 255), 255, random.randrange(0, 255))
draw.point(xy, fill=fill)
# 定义验证码的备选值
str1 = 'ABCD123EFGHIJK456LMNOPQRS789TUVWXYZ0'
# 随机选取4个值作为验证码
rand_str = ''
for i in range(0, 4):
rand_str += str1[random.randrange(0, len(str1))]
# 构造字体对象,ubuntu的字体路径为“/usr/share/fonts/truetype/freefont”
font = ImageFont.truetype('FreeMono.ttf', 23)
# 构造字体颜色
fontcolor = (255, random.randrange(0, 255), random.randrange(0, 255))
# 绘制4个字
draw.text((5, 2), rand_str[0], font=font, fill=fontcolor)
draw.text((25, 2), rand_str[1], font=font, fill=fontcolor)
draw.text((50, 2), rand_str[2], font=font, fill=fontcolor)
draw.text((75, 2), rand_str[3], font=font, fill=fontcolor)
# 释放画笔
del draw
# 存入session,用于做进一步验证
request.session['verifycode'] = rand_str
# 内存文件操作
buf = BytesIO()
# 将图片保存在内存中,文件类型为png
im.save(buf, 'png')
# 将内存中的图片数据返回给客户端,MIME类型为图片png
return HttpResponse(buf.getvalue(), 'image/png')
3)打开booktest/urls.py文件,配置url。
url(r'^verify_code/$', views.verify_code),
4)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/verify_code/
5)浏览效果如下图:
可以多刷新几次看值会不会变。
def login_check(request):
'''登录校验视图'''
# 1.获取提交的用户名和密码
username = request.POST.get('username')
password = request.POST.get('password')
remember = request.POST.get('remember') # None on
# 获取用户输入验证码
vcode1 = request.POST.get('vcode')
# 获取session中保存的验证码
vcode2 = request.session.get('verifycode')
# 进行验证码校验
if vcode1 != vcode2:
# 验证码错误
return redirect('/login')
反向解析
当某一个url配置的地址发生变化时,页面上使用反向解析生成地址的位置不需要发生变化。
根据url 正则表达式的配置动态的生成url。
在项目urls中包含具体应用的urls文件时指定namespace;
在应用的urls中配置是指定name;
在模板文件中使用时,格式如下:
{% url ‘namespace名字:name’ %} 例如{% url ‘booktest:fan2’%}
带位置参数:
{% url ‘namespace名字:name’ 参数 %} 例如{% url ‘booktest:fan2’ 1%}
带关键字参数:
{% url ‘namespace名字:name’ 关键字参数 %} 例如{% url ‘booktest:fan2’ id=1 %}
在重定向的时候使用反向解析:
from django.core.urlresolvers import reverse
无参数:
reverse('namespace名字:name名字')
如果有位置参数
reverse('namespace名字:name名字', args = 位置参数元组)
如果有关键字参数
reverse('namespace名字:name名字', kwargs=字典)
urls_reverse.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>url反向解析</title>
</head>
<body>
index链接:<br/>
<a href="/index">首页</a><br/>
url反向解析生成index链接:<br/>
<a href="{% url 'booktest:index' %}">首页</a>
/show_args/1/2:<br/>
<a href="/show_args/1/2">/show_args/1/2</a><br/>
动态产生/show_args/1/2:<br/>
<a href="{% url 'booktest:show_args' 1 2 %}">/show_args/1/2</a><br/>
/show_kwargs/3/4:<br/>
<a href="/show_kwargs/3/4">/show_kwargs/3/4</a><br/>
动态产生/show_kwargs/3/4:<br/>
<a href="{% url 'booktest:show_kwargs' c=3 d=4 %}">/show_kwargs/3/4</a>
</body>
</html>
view.py
# /url_reverse
def url_reverse(request):
'''url反向解析页面'''
return render(request, 'booktest/url_reverse.html')
def show_args(request, a, b):
return HttpResponse(a+':'+b)
def show_kwargs(request, c, d):
return HttpResponse(c+":"+d)
from django.core.urlresolvers import reverse
# /test_redirect
def test_redirect(request):
# 重定向到/index
# return redirect('/index')
# url = reverse('booktest:index')
# 重定向到/show_args/1/2
# url = reverse('booktest:show_args', args=(1,2))
# 重定向到/show_kwargs/3/4
url = reverse('booktest:show_kwargs', kwargs={
'c':3, 'd':4})
return redirect(url)
urls.py
from django.conf.urls import url
from booktest import views
url(r'^url_reverse$', views.url_reverse), # url反向解析页面
url(r'^show_args/(\d+)/(\d+)$', views.show_args, name='show_args'), # 捕获位置参数
url(r'^show_kwargs/(?P<c>\d+)/(?P<d>\d+)$', views.show_kwargs, name='show_kwargs'), # 捕获关键字参数
url(r'^test_redirect$', views.test_redirect),