@Django框架与项目-下
作为Web框架,Django提供了模板,用于编写html代码,还可以嵌入模板代码更快更方便的完成页面开发,再通过在视图中渲染模板,将生成最终的html字符串返回给客户端浏览器。模版致力于表达外观,而不是程序逻辑。模板的设计实现了业务逻辑view与显示内容template的分离,一个视图可以使用任意一个模板,一个模板可以供多个视图使用。
模板包含两部分:
静态部分,包含html、css、js。
动态部分,就是模板语言。
Django模板语言,简写DTL,定义在django.template包中。 创建项目后,在"项目名称/settings.py"文件中定义了关于模板的配置。
DIRS定义一个目录列表,模板引擎按列表顺序搜索这些目录以查找模板文件,通常是在项目的根目录下创建templates目录。
Django处理模板分为两个阶段:
1.加载:根据给定的路径找到模板文件,编译后放在内存中。
2.渲染:使用上下文数据对模板插值并返回生成的字符串。
为了减少开发人员重复编写加载、渲染的代码,Django提供了简写函数render,用于调用模板。
** 创建示例项目**
1)创建项目test4。
2)进入项目目录test4,创建应用booktest。
cd test4
python manage.py startapp booktest
4)在test4/settings.py中INSTALLED_APPS项安装应用。
5)在test4/settings.py中DATABASES项配置使用MySQL数据库test2,数据库在第二部分已经创建。
6)在test4/settings.py中TEMPLATES项配置模板查找路径。
7)创建模板目录结构如下。
8)打开test4/urls.py文件,包含booktest的url配置。
9)在booktest/目录下创建urls.py,配置url。
from django.conf.urls import url
from booktest import views
urlpatterns=[
url(r'^$',views.index),
]
10)打开booktest/views.py文件,定义视图index。
from django.shortcuts import render
def index(request):
return render(request,'booktest/index.html')
11)在templates/booktest目录下创建文件index.html,代码如下:
<html>
<head>
<title>首页</title>
</head>
<body>
</body>
</html>
12)打开booktest/models.py文件,定义模型类BookInfo,结构参照第二部分设计。
from django.db import models
class BookInfo(models.Model):
btitle = models.CharField(max_length=20)
bpub_date = models.DateField()
bread = models.IntegerField(default=0)
bcommet = models.IntegerField(default=0)
isDelete = models.BooleanField(default=False)
模板语言
模板变量的作用是计算并输出,变量名必须由字母、数字、下划线(不能以下划线开头)和点组成。
语法如下:
{{变量}}
当模版引擎遇到点如book.title,会按照下列顺序解析:
1.字典book[‘title’]
2.先属性后方法,将book当作对象,查找属性title,如果没有再查找方法title()
3.如果是格式为book.0则解析为列表book[0]
如果变量不存在则插入空字符串’'。
在模板中调用方法时不能传递参数。
示例
1)打开booktest/views.py文件,创建视图temp_var。
def temp_var(request):
dict={'title':'字典键值'}
book=BookInfo()
book.btitle='对象属性'
context={'dict':dict,'book':book}
return render(request,'booktest/temp_var.html',context)
2)打开booktest/urls.py文件,配置url。
url(r’^temp_var/$', views.temp_var),
3)修改在templates/booktest下创建temp_var.html。
<html>
<head>
<title>模板变量</title>
</head>
<body>
模板变量:<br/>
{{dict.title}}<br/>
{{book.btitle}}<br/>
</body>
</html>
4)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/temp_var/
5)浏览效果如下图。
语法如下:
{%代码段%}
for标签语法如下:
{%for item in 列表%}
循环逻辑
{{forloop.counter}}表示当前是第几次循环,从1开始
{%empty%}
列表为空或不存在时执行此逻辑
{%endfor%}
if标签语法如下:
{%if ...%}
逻辑1
{%elif ...%}
逻辑2
{%else%}
逻辑3
{%endif%}
比较运算符如下:
注意:运算符左右两侧不能紧挨变量或常量,必须有空格。
==
!=
<
>
<=
>=
布尔运算符如下:
and
or
not
点击查看内建标签了解更多标签http://python.usyiyi.cn/translate/django_182/ref/templates/builtins.html
示例
1)打开booktest/views.py文件,创建视图temp_tag。
from booktest.models import BookInfo
def temp_tags(request):
context={'list':BookInfo.objects.all()}
return render(request,'booktest/temp_tag.html',context)
2)打开booktest/urls.py文件,配置url。
url(r’^temp_tag/$', views.temp_tags),
3)在templates/booktest下创建temp_tag.html。
<html>
<head>
<title>标签</title>
</head>
<body>
图书列表如下:
<ul>
{%for book in list%}
{%if book.id <= 2%}
<li style="background-color: red;">{{book.btitle}}</li>
{%elif book.id <= 3%}
<li style="background-color: blue;">{{book.btitle}}</li>
{%else%}
<li style="background-color: green;">{{book.btitle}}</li>
{%endif%}
{%empty%}
<li>对不起,没有图书</li>
{%endfor%}
</ul>
</body>
</html>
4)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/temp_tag/
浏览效果如下图:
语法如下:
使用管道符号|来应用过滤器,用于进行计算、转换操作,可以使用在变量、标签中。
如果过滤器需要参数,则使用冒号:传递参数。
变量|过滤器:参数
长度length,返回字符串包含字符的个数,或列表、元组、字典的元素个数。
默认值default,如果变量不存在时则返回默认值。
data|default:'默认值'
日期date,用于对日期类型的值进行字符串格式化,常用的格式化字符如下:
Y表示年,格式为4位,y表示两位的年。
m表示月,格式为01,02,12等。
d表示日, 格式为01,02等。
j表示日,格式为1,2等。
H表示时,24进制,h表示12进制的时。
i表示分,为0-59。
s表示秒,为0-59。
value|date:“Y年m月j日 H时i分s秒”
点击查看内建过滤器了解更多过滤器。
示例
1)打开booktest/views.py文件,创建视图temp_filter。
def temp_filter(request):
context={'list':BookInfo.objects.all()}
return render(request,'booktest/temp_filter.html',context)
2)打开booktest/urls.py文件,配置url。
url(r’^temp_filter/$', views.temp_filter),
3)在templates/booktest下创建temp_filter.html。
<head>
<title>过滤器</title>
</head>
<body>
图书列表如下:
<ul>
{%for book in list%}
{%if book.btitle|length > 4%}
<li style="background-color: red;">
{{book.btitle}}
---默认时间格式为:
{{book.bpub_date}}
</li>
{%else%}
<li style="background-color: green;">
{{book.btitle}}
---格式化时间为:
{{book.bpub_date|date:"Y-m-j"}}
</li>
{%endif%}
{%endfor%}
</ul>
</body>
</html>
4)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/temp_filter/
过滤器就是python中的函数,注册后就可以在模板中当作过滤器使用,下面以求余为例开发一个自定义过滤器mod。
示例
1)在应用中创建templatetags目录,当前示例为"booktest/templatetags",创建_init_文件,内容为空。
2)在"booktest/templatetags"目录下创建filters.py文件,代码如下:
#导入Library类
from django.template import Library
#创建一个Library类对象
register=Library()
#使用装饰器进行注册
@register.filter
#定义求余函数mod,将value对2求余
def mod(value):
return value%2 == 0
3)在templates/booktest/temp_filter.html中,使用自定义过滤器。
首先使用load标签引入模块。
{%load filters%}
4)运行服务器,浏览效果如下:
过滤器可以接收参数,将booktest/templatetags/filters.py中增加mod_num函数。
#使用装饰器进行注册
@register.filter
#定义求余函数mod_num,将value对num求余
def mod_num(value,num):
return value%num
5)在templates/booktest/temp_filter.html中修改遍历时判断代码。
6)运行服务器,浏览效果如下:
在模板中使用如下模板注释,这段代码不会被编译,不会输出到客户端;html注释只能注释html内容,不能注释模板语言。
1)单行注释语法如下:
{#…#}
注释可以包含任何模版代码,有效的或者无效的都可以。
{# { % if foo % }bar{ % else % } #}
2)多行注释使用comment标签,语法如下:
{%comment%}
…
{%endcomment%}
模板继承和类的继承含义是一样的,主要是为了提高代码重用,减轻开发人员的工作量。
典型应用:网站的头部、尾部信息。
父模板
如果发现在多个模板中某些内容相同,那就应该把这段内容定义到父模板中。
标签block:用于在父模板中预留区域,留给子模板填充差异性的内容,名字不能相同。 为了更好的可读性,建议给endblock标签写上名字,这个名字与对应的block名字相同。父模板中也可以使用上下文中传递过来的数据。
{%block 名称%}
预留区域,可以编写默认内容,也可以没有默认内容
{%endblock 名称%}
子模板
标签extends:继承,写在子模板文件的第一行。
{% extends “父模板路径”%}
子模版不用填充父模版中的所有预留区域,如果子模版没有填充,则使用父模版定义的默认值。
填充父模板中指定名称的预留区域。
{%block 名称%}
实际填充内容
{{block.super}}用于获取父模板中block的内容
{%endblock 名称%}
示例
1)打开booktest/views.py文件,创建视图temp_inherit。
def temp_inherit(request):
context={'title':'模板继承','list':BookInfo.objects.all()}
return render(request,'booktest/temp_inherit.html',context)
2)打开booktest/urls.py文件,配置url。
url(r’^temp_inherit/$', views.temp_inherit),
3)在templates下创建inherit_base.html。
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<h2>这是头</h2>
<hr>
{%block qu1%}
这是区域一,有默认值
{%endblock qu1%}
<hr>
{%block qu2%}
{%endblock qu2%}
<hr>
<h2>这是尾</h2>
</body>
</html>
4)在templates/booktest下创建temp_inherit.html。
{%extends 'booktest/inherit_base.html'%}
{%block qu2%}
<ul>
{%for book in list%}
<li>{{book.btitle}}</li>
{%endfor%}
</ul>
{%endblock qu2%}
5)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/temp_inherit/
模板对上下文传递的字符串进行输出时,会对以下字符自动转义。
小于号< 转换为 <
大于号> 转换为 >
单引号' 转换为 '
双引号" 转换为 "
与符号& 转换为 &
示例
1)打开booktest/views.py文件,创建视图html_escape。
def html_escape(request):
context={'content':'hello world
'}
return render(request,'booktest/html_escape.html',context)
2)打开booktest/urls.py文件,配置url。
url(r’^html_escape/$', views.html_escape),
3)在templates/booktest/目录下创建html_escape.html。
<html>
<head>
<title>转义</title>
</head>
<body>
自动转义:{{content}}
</body>
</html>
4)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/html_escape/
转义后标记代码不会被直接解释执行,而是被直接呈现,防止客户端通过嵌入js代码攻击网站.
浏览效果如下图:
关闭转义
过滤器escape可以实现对变量的html转义,默认模板就会转义,一般省略。
{{t1|escape}}
过滤器safe:禁用转义,告诉模板这个变量是安全的,可以解释执行。
{{data|safe}}
1)修改templates/booktest/html_escape.html代码如下。
<html>
<head>
<title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
</body>
</html>
刷新浏览器后效果如下图:
标签autoescape:设置一段代码都禁用转义,接受on、off参数。
{%autoescape off%}
...
{%endautoescape%}
1)修改templates/booktest/html_escape.html代码如下。
<html>
<head>
<title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
<hr>
标签autoescape关闭转义:
{%autoescape off%}
{{content}}
{%endautoescape%}
</body>
</html>
字符串字面值
对于在模板中硬编码的html字符串,不会转义。
1)修改templates/booktest/html_escape.html代码如下:
<html>
<head>
<title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
<hr>
标签autoescape关闭转义:
{%autoescape off%}
{{content}}
{%endautoescape%}
<hr>
模板硬编码不转义:{{data|default:'hello'}}
</body>
</html>
2)刷新浏览器后效果如下图:
如果希望出现转义的效果,则需要手动编码转义。
1)修改templates/booktest/html_escape.html代码如下:
<html>
<head>
<title>转义</title>
</head>
<body>
自动转义:{{content}}
<hr>
过滤器safe关闭转义:{{content|safe}}
<hr>
标签autoescape关闭转义:
{%autoescape off%}
{{content}}
{%endautoescape%}
<hr>
模板硬编码不转义:{{data|default:'hello'}}
<hr>
模板硬编码手动转义:{{data|default:"<b>123</b>"}}
</body>
</html>
CSRF
CSRF全拼为Cross Site Request Forgery,译为跨站请求伪造。CSRF指攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。
CSRF示意图如下:
如果想防止CSRF,首先是重要的信息传递都采用POST方式而不是GET方式,接下来就说POST请求的攻击方式以及在Django中的避免。
示例
攻击过程的操作了解即可,不需要重现。
1)打开booktest/views.py文件,创建视图login,login_check, post和post_action。
def login(reqeust):
return render(reqeust, 'booktest/login.html')
def login_check(request):
username = request.POST.get('username') #获取用户名
password = request.POST.get('password') #获取密码
# 校验
if username == 'smart' and password == '123':
request.session['username']=name #记住登录用户名
request.session['islogin']=True #判断用户是否已登录
return redirect('/post/')
else:
return redirect('/login/')
def post(request):
return render(request, 'booktest/post.html')
def post_action(request):
if request.session['islogin']:
username = request.session['username']
return HttpResponse('用户'+username+'发了一篇帖子')
else:
return HttpResponse('发帖失败')
2)打开booktest/urls.py文件,配置url。
url(r'^login/$', views.login),
url(r'^login_check/$', views.login_check),
url(r'^post/$', views.post),
url(r'^post_action/$',views.post_action),
3)在templates/booktest/目录下创建login.html和post.html。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录案例</title>
</head>
<body>
<form method="post" action="/login_check/">
用户名:<input type="text" name="username"/><br/>
密码:<input type="password" name="password"/><br/>
<input type="submit" value="提交"/>
</form>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>发帖页</title>
</head>
<body>
<form method="post" action="/post_action/">
标题:<input type="text" name="title"/><br/>
内容:<textarea name="content"></textarea>
<input type="submit" value="发帖"/>
</form>
</body>
</html>
4)启动运行服务器,采用IP的方式,因为要演示其它IP的请求。
python manage.py runserver 172.16.179.130:8000
5)回到windows中,在浏览器中输入如下网址,将这个标签称为网站A。
http://172.16.179.130:8000/login/
浏览效果如下图:
输入用户名和密码,点击登录,效果如下图:
6)下面使用windows中的IIS服务器模拟另外一个网站,创建post.html,复制templates/booktest/post.html内容,并修改action路径。
<html>
<head>
<title>发帖页</title>
</head>
<body>
<form method="post" action="http://172.16.179.130:8000/post_action/">
标题:<input type="text" name="title"/><br/>
内容:<textarea name="content"></textarea>
<input type="submit" value="发帖"/>
</form>
</body>
</html>
7)在windows中浏览器查看效果如下图,将这个标签称为网站B。
8)Django项目中默认启用了csrf保护,现在先禁用,打开test4/settings.py文件,注释掉csrf中间件。
9)点击游览器的第一个标签即网站A,点击"发帖"按钮后如下图:
10)点击游览器的第二个标签即IIS网站B,点击“发帖”按钮后如下图:
对比上面两张图,发现无论从网站A还是网站B都可以访问网站A的post_action视图,这就是不安全的。
1)Django提供了csrf中间件用于防止CSRF攻击,只需要在test4/settings.py中启用csrf中间件即可。
2)回到windows浏览器中,分别在网站A、网站B中点击“提交”按钮,效果一样,如下图:
3)这下麻烦了,因为网站A自己也不能访问了,接下来templates/booktest/post.html内容,在form表单中使用标签csrf_token。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>发帖页</title>
</head>
<body>
<form method="post" action="/post_action/">
{% csrf_token %}
标题:<input type="text" name="title"/><br/>
内容:<textarea name="content"></textarea>
<input type="submit" value="发帖"/>
</form>
</body>
</html>
4)回到windows浏览器中,在网站A中点击“提交”按钮,效果如下图:
5)回到windows浏览器中,在网站B中点击“提交”按钮,效果如下图:
好了,Django中成功完成CSRF防护。
总结
以上的演示过程了解即可,不需要重现,以下的内容是重点,必须记住
重要信息如金额、积分等,采用POST方式传递
启用CSRF中间件,默认启用
在form表单中post提交时加入标签csrf_token
保护原理
加入标签后,可以查看post.html的源代码,发现多了一个隐藏域。
了解原理即可。
在浏览器的“开发者工具”中查看cookie信息。
说明:当启用中间件并加入标签csrf_token后,会向客户端浏览器中写入一条Cookie信息,这条信息的值与隐藏域input元素的value属性是一致的,提交到服务器后会先由csrf中间件进行验证,如果对比失败则返回403页面,而不会进行后续的处理。
在用户注册、登录页面,为了防止暴力请求,可以加入验证码功能,如果验证码错误,则不需要继续处理,可以减轻业务服务器、数据库服务器的压力。
手动实现验证码
接下来的代码不要求手动写出来,因为这种代码在网上可以搜到很多。
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
...
def verify_code(request):
#引入随机函数模块
import random
#定义变量,用于画面的背景色、宽、高
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)浏览效果如下图:
可以多刷新几次看值会不会变。
调用验证码
1)在booktest/views.py文件中,创建视图verify_show。
def verify_show(request):
return render(request,'booktest/verify_show.html')
2)打开booktest/urls.py文件,配置url。
url(r’^verify_show/$', views.verify_show),
3)在templates/booktest/目录下创建verify_show.html。
<html>
<head>
<title>验证码</title>
</head>
<body>
<form method="post" action="/verify_yz/">
{%csrf_token%}
<input type="text" name="yzm">
<img id="yzm" src="/verify_code/"/>
<span id="change">看不清,换一个</span>
<br>
<input type="submit" value="提交">
</form>
</body>
</html>
4)运行服务器,在浏览器中输入如下网址。
http://127.0.0.1:8000/verify_show/
5)浏览效果如下图:
验证
1)在booktest/views.py文件中,创建视图verify_yz。
def verify_yz(request):
yzm=request.POST.get('yzm')
verifycode=request.session['verifycode']
response=HttpResponse('no')
if yzm==verifycode:
response=HttpResponse('ok')
return response
2)打开booktest/urls.py文件,配置url。
url(r’^verify_yz/$', views.verify_yz),
3)回到浏览器后刷新,在文本框中填写验证码,点击提交按钮。
4)浏览效果如下图:
先看看原来怎么做
def fan1(request):
return render(request,'booktest/fan1.html')
def fan2(request):
return HttpResponse('fan2')
2)打开booktest/urls.py文件,配置url。
url(r’^fan1/ ′ , v i e w s . f a n 1 ) , u r l ( r ′ f a n 2 / ', views.fan1), url(r'^fan2/ ′,views.fan1),url(r′fan2/', views.fan2),
3)在templates/booktest/目录下创建fan1.html。
<html>
<head>
<title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan2/">fan2</a>
</body>
</html>
4)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/fan1/
浏览效果如下图:
5)点击链接后转向fan2,效果如下图:
反射解析
6)打开booktest/urls.py文件,修改"fan2"的正则表达式为"fan_show"。
url(r’^fan_show/$', views.fan2),
7)打开浏览器,后退一下,刷新后再次点击链接,浏览如下图:
问题就来了:随着功能的增加会出现更多的视图,可能之前配置的正则表达式不够准确,于是就要修改正则表达式,但是正则表达式一旦修改了,之前所有对应的超链接都要修改,真是一件麻烦的事情,而且可能还会漏掉一些超链接忘记修改,有办法让链接根据正则表达式动态生成吗? 答:反向解析。
反向解析应用在两个地方:模板中的超链接,视图中的重定向。
反向解析
要实现反向解析功能,需要如下步骤:
1)在test4/urls.py中为include定义namespace属性。
url(r’^',include(‘booktest.urls’,namespace=‘booktest’)),
2)在booktest/urls.py中为url定义name属性,并修改为fan2。
url(r’^fan2/$', views.fan2,name=‘fan2’),
3)在模板中使用url标签做超链接,此处为templates/booktest/fan1.html文件。
<html>
<head>
<title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan2/">fan2</a>
<hr>
反向解析:<a href="{%url 'booktest:fan2'%}">fan2</a>
</body>
</html>
4)回到浏览器中,后退,刷新,查看源文件如下图,两个链接地址一样。
5)在booktest/urls.py中,将fan2修改为fan_show。
url(r’^fan_show/$', views.fan2,name=‘fan2’),
6)回到浏览器中,刷新,查看源文件如下图,两个链接地址不一样。
7)反向解析也可以应用在视图的重定向中。
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
return redirect(reverse('booktest:fan2'))
总结:在定义url时,需要为include定义namespace属性,为url定义name属性,使用时,在模板中使用url标签,在视图中使用reverse函数,根据正则表达式动态生成地址,减轻后期维护成本。
URL的参数
有些url配置项正则表达式中是有参数的,接下来讲解如何传递参数。
情况一:位置参数
1)在booktest/urls.py中,修改fan2如下:
url(r’^fan(\d+)_(\d+)/$', views.fan3,name=‘fan2’),
2)在booktest/views中,定义视图fan3如下:
def fan3(request, a, b):
return HttpResponse(a+b)
3)修改templates/booktest/fan1.html文件如下:
<html>
<head>
<title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan2_3/">fan2</a>
<hr>
反向解析:<a href="{%url 'booktest:fan2' 2 3%}">fan2</a>
</body>
</html>
4)回到浏览器中,刷新,查看源文件如下图:
使用重定向传递位置参数格式如下:
return redirect(reverse(‘booktest:fan2’, args=(2,3)))
情况二:关键字参数
1)在booktest/urls.py中,修改fan2如下:
url(r’^fan(?P\d+)_(?P\d+)/$', views.fan4,name=‘fan2’),
2)在booktest/views中,定义视图fan4如下:
def fan4(request, id, age):
return HttpResponse(id+age)
2)修改templates/booktest/fan1.html文件如下:
<html>
<head>
<title>反向解析</title>
</head>
<body>
普通链接:<a href="/fan100_18/">fan2</a>
<hr>
反向解析:<a href="{%url 'booktest:fan2' id=100 age=18%}">fan2</a>
</body>
</html>
使用重定向传递关键字参数格式如下:
return redirect(reverse(‘booktest:fan2’, kwargs={‘id’:100,‘age’:18}))
点击“看不清,换一个”,换一个新的验证码
<script type="text/javascript" src="/static/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(function(){
$('#change').css('cursor','pointer').click(function() {
$('#yzm').attr('src',$('#yzm').attr('src')+1)
});
});
</script>
...
<img id="yzm" src="/verify_code/?1"/>
<span id="change">看不清,换一个</span>
项目中的CSS、图片、js都是静态文件。一般会将静态文件放到一个单独的目录中,以方便管理。在html页面中调用时,也需要指定静态文件的路径,Django中提供了一种解析的方式配置静态文件路径。静态文件可以放在项目根目录下,也可以放在应用的目录下,由于有些静态文件在项目中是通用的,所以推荐放在项目的根目录下,方便管理。
示例
1)在test5/settings.py文件中定义静态文件存放的物理目录。
STATIC_URL = '/static/'
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
2)在项目根目录下创建static目录,再创建img、css、js目录。
3)在booktest/views.py中定义视图static_test。
def static_test(request):
return render(request,'booktest/static_test.html')
4)在booktest/urls.py中配置url。
url(r’^static_test/$',views.static_test),
5)在templates/booktest/下创建static_test.html文件。
<html>
<head>
<title>静态文件</title>
</head>
<body>
<img src="/static/img/sg.png"/>
</body>
</html>
6)保存图片到static/img/目录下,名称为sg.png。
7)运行服务器,浏览效果如下图:
配置静态文件
Django提供了一种配置,可以在html页面中可以隐藏真实路径。
1)在test5/settings.py文件中修改STATIC_URL项。
# STATIC_URL = '/static/'
STATIC_URL = '/abc/'
2)刷新浏览器,图片找不到了,效果如下图:
3)修改templates/booktest/static_test.html如下:
<html>
<head>
<title>静态文件</title>
</head>
<body>
修改前:<img src="/static/img/sg.png"/>
<hr>
修改后:<img src="/abc/img/sg.png"/>
</body>
</html>
3)刷新浏览器,效果如下图:
4)查看网页源代码,发现可以网址和真实地址之间没有关系。
为了安全可以通过配置项隐藏真实图片路径,在模板中写成固定路径,后期维护太麻烦,可以使用static标签,根据配置项生成静态文件路径。
1)修改templates/booktest/static_test.html如下:
<html>
<head>
<title>静态文件</title>
</head>
<body>
修改前:<img src="/static/img/sg.png"/>
<hr>
修改后:<img src="/abc/img/sg.png"/>
<hr>
动态配置:
{%load static from staticfiles%}
<img src="{%static "img/sg.png" %}"/>
</body>
</html>
2)刷新浏览器,效果如下图:
查看网页源代码如下图:
说明:这种方案可以隐藏真实的静态文件路径,但是结合Nginx布署时,会将所有的静态文件都交给Nginx处理,而不用转到Django部分,所以这项配置就无效了。
Django中的中间件是一个轻量级、底层的插件系统,可以介入Django的请求和响应处理过程,修改Django的输入或输出。中间件的设计为开发者提供了一种无侵入式的开发方式,增强了Django框架的健壮性,其它的MVC框架也有这个功能,名称为IoC。
Django在中间件中预置了五个方法,这五个方法的区别在于不同的阶段执行,对输入或输出进行干预,方法如下:
1)初始化:无需任何参数,服务器响应第一个请求的时候调用一次,用于确定是否启用当前中间件。
def __init__(self):
pass
2)处理请求前:在每个请求上,request对象产生之后,url匹配之前调用,返回None或HttpResponse对象。
def process_request(self, request):
pass
3)处理视图前:在每个请求上,url匹配之后,视图函数调用之前调用,返回None或HttpResponse对象。
def process_view(self, request, view_func, *view_args, **view_kwargs):
pass
4)处理响应后:视图函数调用之后,所有响应返回浏览器之前被调用,在每个请求上调用,返回HttpResponse对象。
def process_response(self, request, response):
pass
5)异常处理:当视图抛出异常时调用,在每个请求上调用,返回一个HttpResponse对象。
def process_exception(self, request,exception):
pass
示例
中间件是一个独立的python类,,可以定义这五个方法中的一个或多个。
1)在booktest/目录下创建middleware.py文件,代码如下:
class my_mid:
def __init__(self):
print '--------------init'
def process_request(self,request):
print '--------------request'
def process_view(self,request, view_func, *view_args, **view_kwargs):
print '--------------view'
def process_response(self,request, response):
print '--------------response'
return response
2)在test5/settings.py文件中,向MIDDLEWARE_CLASSES项中注册。
3)修改booktest/views.py中视图index。
def index(request):
print '======index======'
return render(request,'booktest/index.html')
4)运行服务器,命令行中效果如下图:
3)刷新页面,命令行中效果如下图:
异常中间件
1)在booktest/middleware.py中定义两个异常类如下:
class exp1:
def process_exception(self,request,exception):
print '--------------exp1'
class exp2:
def process_exception(self,request,exception):
print '--------------exp2'
2)在test5/settings.py文件中,向MIDDLEWARE_CLASSES项中注册。
3)修改booktest/views.py中视图index。
def index(request):
print '======index======'
raise Exception('自定义异常')
return render(request,'booktest/index.html')
总结:如果多个注册的中间件类中都有process_exception的方法,则先注册的后执行。
内容发布的部分由网站的管理员负责查看、添加、修改、删除数据,开发这些重复的功能是一件单调乏味、缺乏创造力的工作,为此,Django能够根据定义的模型类自动地生成管理模块。
在第一部分对管理站点做了简单介绍,现在做详细讲解。在Django项目中默认启用Admin管理站点。
1)准备工作:创建管理员的用户名和密码。
python manage.py createsuperuser
按提示填写用户名、邮箱、密码。
2)使用:在应用的admin.py中注册模型类
例:打开booktest/admin.py文件,注册地区模型。
from django.contrib import admin
from booktest.models import *
admin.site.register(AreaInfo)
3)输入如下网址:
http://127.0.0.1:8000/admin/
按提示填写用户名、密码,点击“Log in”按钮登录。
登录成功后,可以看到AreaInfos,可以进行增加、修改、删除、查询的管理。
列表页显示效果如下图:
控制管理页展示
类ModelAdmin可以控制模型在Admin界面中的展示方式,主要包括在列表页的展示方式、添加修改页的展示方式。
1)在booktest/admin.py中,注册模型类前定义管理类AreaAdmin。
class AreaAdmin(admin.ModelAdmin):
pass
管理类有两种使用方式:
注册参数
装饰器
注册参数:打开booktest/admin.py文件,注册模型类代码如下:
admin.site.register(AreaInfo,AreaAdmin)
装饰器:打开booktest/admin.py文件,在管理类上注册模型类,代码如下:
@admin.register(AreaInfo)
class AreaAdmin(admin.ModelAdmin):
pass
接下来介绍如何控制列表页、增加修改页展示效果。
页大小
每页中显示多少条数据,默认为每页显示100条数据,属性如下:
list_per_page=100
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
list_per_page = 10
2)在浏览器中查看区域信息的列表页面,效果如下图:
"操作选项"的位置
顶部显示的属性,设置为True在顶部显示,设置为False不在顶部显示,默认为True。
actions_on_top=True
底部显示的属性,设置为True在底部显示,设置为False不在底部显示,默认为False。
actions_on_bottom=False
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
actions_on_top = True
actions_on_bottom = True
2)在浏览器中刷新效果如下图:
列表中的列
属性如下:
list_display=[模型字段1,模型字段2,…]
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_display = ['id','atitle']
2)在浏览器中刷新效果如下图:
点击列头可以进行升序或降序排列。
将方法作为列
列可以是模型字段,还可以是模型方法,要求方法有返回值。
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
def title(self):
return self.atitle
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_display = ['id','atitle','title']
3)在浏览器中刷新效果如下图:
方法列是不能排序的,如果需要排序需要为方法指定排序依据。
admin_order_field=模型类字段
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
def title(self):
return self.atitle
title.admin_order_field='atitle'
2)在浏览器中刷新效果如下图:
列标题
列标题默认为属性或方法的名称,可以通过属性设置。需要先将模型字段封装成方法,再对方法使用这个属性,模型字段不能直接使用这个属性。
short_description=‘列标题’
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
title.short_description='区域名称'
2)在浏览器中刷新效果如下图:
关联对象
无法直接访问关联对象的属性或方法,可以在模型类中封装方法,访问关联对象的成员。
1)打开booktest/models.py文件,修改AreaInfo类如下:
class AreaInfo(models.Model):
...
def parent(self):
if self.aParent is None:
return ''
return self.aParent.atitle
parent.short_description='父级区域名称'
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_display = ['id','atitle','title','parent']
3)在浏览器中刷新效果如下图:
列表页选项
右侧栏过滤器
属性如下,只能接收字段,会将对应字段的值列出来,用于快速过滤。一般用于有重复值的字段。
list_filter=[]
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
list_filter=['atitle']
2)在浏览器中刷新效果如下图:
搜索框
属性如下,用于对指定字段的值进行搜索,支持模糊查询。列表类型,表示在这些字段上进行搜索。
search_fields=[]
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
search_fields=['atitle']
2)在浏览器中刷新效果如下图:
中文标题
1)打开booktest/models.py文件,修改模型类,为属性指定verbose_name参数,即第一个参数。
class AreaInfo(models.Model):
atitle=models.CharField('标题',max_length=30)#名称
...
2)在浏览器中刷新效果如下图:
显示字段顺序
属性如下:
fields=[]
1)点击某行ID的链接,可以转到修改页面,默认效果如下图:
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
fields=['aParent','atitle']
3)刷新浏览器效果如下图:
在下拉列表中输出的是对象的名称,可以在模型类中定义str方法用于对象转换字符串。
1)打开booktest/models.py文件,修改AreaInfo类,添加str方法。
class AreaInfo(models.Model):
...
def __str__(self):
return self.atitle
2)刷新浏览器效果如下图:
分组显示
属性如下:
fieldset=(
('组1标题',{'fields':('字段1','字段2')}),
('组2标题',{'fields':('字段3','字段4')}),
)
1)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
# fields=['aParent','atitle']
fieldsets = (
('基本', {'fields': ['atitle']}),
('高级', {'fields': ['aParent']})
)
2)刷新浏览器效果如下图:
说明:fields与fieldsets两者选一使用。
关联对象
在一对多的关系中,可以在一端的编辑页面中编辑多端的对象,嵌入多端对象的方式包括表格、块两种。 类型InlineModelAdmin:表示在模型的编辑页面嵌入关联模型的编辑。子类TabularInline:以表格的形式嵌入。子类StackedInline:以块的形式嵌入。
1)打开booktest/admin.py文件,创建AreaStackedInline类。
class AreaStackedInline(admin.StackedInline):
model = AreaInfo#关联子对象
extra = 2#额外编辑2个子对象
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
…
inlines = [AreaStackedInline]
3)刷新浏览器效果如下图:
可以用表格的形式嵌入。
1)打开booktest/admin.py文件,创建AreaTabularInline类。
class AreaTabularInline(admin.TabularInline):
model = AreaInfo#关联子对象
extra = 2#额外编辑2个子对象
2)打开booktest/admin.py文件,修改AreaAdmin类如下:
class AreaAdmin(admin.ModelAdmin):
...
inlines = [AreaTabularInline]
3)刷新浏览器效果如下图:
1)在templates/目录下创建admin目录,结构如下图:
重写模板
2)打开当前虚拟环境中Django的目录,再向下找到admin的模板,目录如下:
/home/python/.virtualenvs/py_django/lib/python3.5/site-packages/django/contrib/admin/templates/admin
3)将需要更改文件拷贝到第一步建好的目录里,此处以base_site.html为例。
编辑base_site.html文件:
{% extends "admin/base.html" %}
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
{% block branding %}
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
<hr>
<h1>自定义的管理页模板</h1>
<hr>
{% endblock %}
{% block nav-global %}{% endblock %}
4)在浏览器中转到列表页面,刷新后如下图:
其它后台的模板可以按照相同的方式进行修改。
在python中进行图片操作,需要安装包PIL。
pip install Pillow==3.4.1
在Django中上传图片包括两种方式:
在管理页面admin中上传图片
自定义form表单中上传图片
上传图片后,将图片存储在服务器上,然后将图片的路径存储在表中。
创建包含图片的模型类
将模型类的属性定义成models.ImageField类型。
1)打开booktest/models.py文件,定义模型类PicTest。
class PicTest(models.Model):
pic = models.ImageField(upload_to='booktest/')
2)回到命令行中,生成迁移。
python manage.py makemigrations
3)打开booktest/migrations/0001_initial.py文件,删除AreaInfo部分,因为这个表已经存在。
4)回到命令行中,执行迁移。
python manage.py migrate
5)因为当前没有定义图书、英雄模型类,会提示“是否删除”,输入“no”后回车,表示不删除。
6)打开test5/settings.py文件,设置图片保存路径。
因为图片也属于静态文件,所以保存到static目录下。
MEDIA_ROOT=os.path.join(BASE_DIR,"static/media")
7)在static目录下创建media目录,再创建应用名称的目录,此例为booktest。
1)打开booktest/admin.py文件,注册PicTest。
from django.contrib import admin
from booktest.models import *
admin.site.register(PicTest)
2)运行服务器,输入如下网址。
3)点击“Add”添加数据,打开新页面。
4)选择图片,点击“save”按钮完成图片上传。 5)回到数据库命令行,查询表pictest中的数据如下图:
6)图片被保存到目录static/media/booktest/下,如下图:
1)打开booktest/views.py文件,创建视图pic_upload。
def pic_upload(request):
return render(request,'booktest/pic_upload.html')
2)打开booktest/urls.py文件,配置url。
url(r’^pic_upload/$', views.pic_upload),
3)在templates/booktest/目录下创建模板pic_upload.html。
在模板中定义上传表单,要求如下:
form的属性enctype="multipart/form-data"
form的method为post
input的类型为file
<html>
<head>
<title>自定义上传图片</title>
</head>
<body>
<form method="post" action="/pic_handle/" enctype="multipart/form-data">
{%csrf_token%}
<input type="file" name="pic"/><br>
<input type="submit" value="上传">
</form>
</body>
</html>
4)打开booktest/views.py文件,创建视图pic_handle,用于接收表单保存图片。
request对象的FILES属性用于接收请求的文件,包括图片。
from django.conf import settings
from django.http import HttpResponse
...
def pic_handle(request):
f1=request.FILES.get('pic')
fname='%s/booktest/%s'%(settings.MEDIA_ROOT,f1.name)
with open(fname,'wb') as pic:
for c in f1.chunks():
pic.write(c)
return HttpResponse('OK')
5)打开booktest/urls.py文件,配置url。
url(r’^pic_handle/$', views.pic_handle),
6)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/pic_upload/
选择文件后点击按钮上传图片。
7)图片上传目录如下图:
这里只是完成图片上传的代码,如果需要保存数据到表中需要创建PicTest对象完成保存。
1)打开booktest/views.py文件,创建视图pic_show。
from booktest.models import PicTest
...
def pic_show(request):
pic=PicTest.objects.get(pk=1)
context={'pic':pic}
return render(request,'booktest/pic_show.html',context)
2)打开booktest/urls.py文件,配置url。
url(r’^pic_show/$', views.pic_show),
3)在templates/booktest/目录下创建模板pic_show.html。
<html>
<head>
<title>显示上传的图片</title>
</head>
<body>
<img src="/static/media/{{pic.pic}}"/>
</body>
4)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/pic_show/
Django提供了数据分页的类,这些类被定义在django/core/paginator.py中。 类Paginator用于对列进行一页n条数据的分页运算。类Page用于表示第m页的数据。
Paginator类实例对象
方法_init_(列表,int):返回分页对象,第一个参数为列表数据,第二个参数为每页数据的条数。
属性count:返回对象总数。
属性num_pages:返回页面总数。
属性page_range:返回页码列表,从1开始,例如[1, 2, 3, 4]。
方法page(m):返回Page类实例对象,表示第m页的数据,下标以1开始。
Page类实例对象
调用Paginator对象的page()方法返回Page对象,不需要手动构造。
属性object_list:返回当前页对象的列表。
属性number:返回当前是第几页,从1开始。
属性paginator:当前页对应的Paginator对象。
方法has_next():如果有下一页返回True。
方法has_previous():如果有上一页返回True。
方法len():返回当前页面对象的个数。
示例
1)在booktest/views.py文件中创建视图page_test。
from django.core.paginator import Paginator
from booktest.models import AreaInfo
...
#参数pIndex表示:当前要显示的页码
def page_test(request,pIndex):
#查询所有的地区信息
list1 = AreaInfo.objects.filter(aParent__isnull=True)
#将地区信息按一页10条进行分页
p = Paginator(list1, 10)
#如果当前没有传递页码信息,则认为是第一页,这样写是为了请求第一页时可以不写页码
if pIndex == '':
pIndex = '1'
#通过url匹配的参数都是字符串类型,转换成int类型
pIndex = int(pIndex)
#获取第pIndex页的数据
list2 = p.page(pIndex)
#获取所有的页码信息
plist = p.page_range
#将当前页码、当前页的数据、页码信息传递到模板中
return render(request, 'booktest/page_test.html', {'list': list2, 'plist': plist, 'pIndex': pIndex})
2)在booktest/urls.py文件中配置url。
url(r’^page(?P[0-9]*)/$', views.page_test),
3)在templates/booktest/目录下创建page_test.html模板文件。
<html>
<head>
<title>分页</title>
</head>
<body>
显示当前页的地区信息:<br>
<ul>
{%for area in list%}
<li>{{area.id}}--{{area.atitle}}</li>
{%endfor%}
</ul>
<hr>
显示页码信息:当前页码没有链接,其它页码有链接<br>
{%for pindex in plist%}
{%if pIndex == pindex%}
{{pindex}}
{%else%}
<a href="/page{{pindex}}/">{{pindex}}</a>
{%endif%}
{%endfor%}
</body>
</html>
4)运行服务器,在浏览器中输入如下网址:
5)点击页码数字,效果如下图:
本示例讲解在Django中使用jquery的ajax进行数据交互。 jquery框架中提供了 . a j a x 、 .ajax、 .ajax、.get、 . p o s t 方法,用于进行异步交互,由于 D j a n g o 中默认使用 C S R F 约束,推荐使用 .post方法,用于进行异步交互,由于Django中默认使用CSRF约束,推荐使用 .post方法,用于进行异步交互,由于Django中默认使用CSRF约束,推荐使用.get。
示例:实现省市区的选择。
最终实现效果如图:
1)将jquery文件拷贝到static/js/目录下。
2)打开booktest/views.py文件,定义视图area1,用于显示下拉列表。
#提供显示下拉列表的控件,供用户操作
def area1(request):
return render(request,'booktest/area1.html')
3)打开booktest/urls.py文件,配置url。
url(r’^area1/$', views.area1),
4)在templates/booktest/目录下创建area1.html。
<html>
<head>
<title>省市区列表</title>
<script type="text/javascript" src="/static/js/jquery-1.12.4.min.js"></script>
<script type="text/javascript">
$(function(){
//页面加载完成后获取省信息,并添加到省select
$.get('/area2/',function(dic) {
pro=$('#pro')
$.each(dic.data,function(index,item){
pro.append('+item[1]+'');
})
});
//为省select绑定change事件,获取市信息,并添加到市select
$('#pro').change(function(){
$.get('/area3_'+$(this).val()+'/',function(dic){
city=$('#city');
city.empty().append('');
dis=$('#dis');
dis.empty().append('');
$.each(dic.data,function(index,item){
city.append('+item[1]+'');
})
});
});
//为市select绑定change事件,获取区县信息,并添加到区县select
$('#city').change(function(){
$.get('/area3_'+$(this).val()+'/',function(dic){
dis=$('#dis');
dis.empty().append('');
$.each(dic.data,function(index,item){
dis.append('+item[1]+'');
})
})
});
});
</script>
</head>
<body>
<select id="pro">
<option value="">请选择省</option>
</select>
<select id="city">
<option value="">请选择市</option>
</select>
<select id="dis">
<option value="">请选择区县</option>
</select>
</body>
</html>
5)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/area1/
浏览效果如下图:
6)打开booktest/views.py文件,定义视图area2,用于获取省信息。
from django.http import JsonResponse
...
#获取省信息
def area2(request):
list = AreaInfo.objects.filter(aParent__isnull=True)
list2 = []
for item in list:
list2.append([item.id, item.atitle])
return JsonResponse({'data': list2})
7)打开booktest/urls.py文件,配置url。
url(r’^area2/$', views.area2),
8)在浏览器中输入如下网址。
http://127.0.0.1:8000/area2/
浏览效果如下图:
9)打开booktest/views.py文件,定义视图area3,用于根据编号获取对应的子级信息,如果传递的是省编号则获取市信息,如果传递的是市编号则获取区县信息。
#根据pid查询子级区域信息
def area3(request, pid):
list = AreaInfo.objects.filter(aParent_id=pid)
list2 = []
for item in list:
list2.append([item.id, item.atitle])
return JsonResponse({'data': list2})
10)打开booktest/urls.py文件,配置url。
url(r’^area3_(\d+)/$', views.area3),
11)在浏览器中输入如下网址:
http://127.0.0.1:8000/area3_140000/
浏览效果如下图:
12)在浏览器中输入如下网址:
http://127.0.0.1:8000/area1/
选择效果如下图:
布署
当项目开发完成后,需要将代码放到服务器上,这个过程称为布署,服务器上需要有一个运行代码的环境,这个环境一般使用uWSGI+Nginx。
创建示例项目
1)在~/Desktop/pytest目录下,进入工作环境py_django。
cd ~/Desktop/pytest
workon py_django
2)创建项目test6。
django-admin startproject test6
3)进入项目目录test6,创建应用booktest。
cd test6
python manage.py startapp booktest
4)在test6/settings.py中INSTALLED_APPS项安装应用。
5)在test6/settings.py中DATABASES项配置使用MySQL数据库test2,数据库在第二部分已经创建。
6)在test6/settings.py中TEMPLATES项配置模板查找路径。
7)创建模板目录结构如下。
8)打开test6/urls.py文件,包含booktest的url配置。
9)在booktest/目录下创建urls.py,配置url。
from django.conf.urls import url
from booktest import views
urlpatterns=[
url(r'^$',views.index),
]
10)打开booktest/views.py文件,定义视图index。
from django.shortcuts import render
def index(request):
return render(request,'booktest/index.html')
11)在templates/booktest目录下创建文件index.html,代码如下:
<html>
<head>
<title>第三方包</title>
</head>
<body>
<h1>第三方包</h1>
</body>
</html>
12)运行服务器。
python manage.py runserver
13)浏览效果如下图:
借助富文本编辑器,网站的编辑人员能够像使用offfice一样编写出漂亮的、所见即所得的页面。此处以tinymce为例,其它富文本编辑器的使用也是类似的。
在虚拟环境中安装包。
pip install django-tinymce==2.6.0
安装完成后,可以使用在Admin管理中,也可以自定义表单使用。
示例
1)在test6/settings.py中为INSTALLED_APPS添加编辑器应用。
INSTALLED_APPS = (
...
'tinymce',
)
2)在test6/settings.py中添加编辑器配置。
TINYMCE_DEFAULT_CONFIG = {
'theme': 'advanced',
'width': 600,
'height': 400,
}
3)在test6/urls.py中配置编辑器url。
urlpatterns = [
...
url(r'^tinymce/', include('tinymce.urls')),
]
1)在booktest/models.py中,定义模型的属性为HTMLField()类型。
from django.db import models
from tinymce.models import HTMLField
class GoodsInfo(models.Model):
gcontent=HTMLField()
2)生成迁移文件。
3)执行迁移。
python manage.py migrate
4)在本示例中没有定义其它的模型类,但是数据库中有这些表,提示是否删除,输入no后回车,表示不删除,因为其它的示例中需要使用这些表。
5)迁移完成,新开终端,连接mysql,使用test2数据库,查看表如下:
6)发现并没有表GoodsInfo,解决办法是删除迁移表中关于booktest应用的数据。
delete from django_migrations where app=‘booktest’;
7)再次执行迁移。
python manage.py migrate
成功完成迁移,记得不删除no。
8)在booktest/admin.py中注册模型类GoodsInfo
from django.contrib import admin
from booktest.models import *
class GoodsInfoAdmin(admin.ModelAdmin):
list_display = ['id']
admin.site.register(GoodsInfo,GoodsInfoAdmin)
9)运行服务器,进入admin后台管理,点击GoodsInfo的添加,效果如下图
在编辑器中编辑内容后保存。
1)在booktest/views.py中定义视图editor,用于显示编辑器。
def editor(request):
return render(request, 'booktest/editor.html')
2)在booktest/urls.py中配置url。
url(r’^editor/',views.editor),
3)在项目目录下创建静态文件目录如下图:
4)打开py_django虚拟环境的目录,找到tinymce的目录。
/home/python/.virtualenvs/py_django/lib/python3.5/site-packages/tinymce/static/tiny_mce
5)拷贝tiny_mce_src.js文件、langs文件夹以及themes文件夹拷贝到项目目录下的static/js/目录下。
6)在test6/settings.py中配置静态文件查找路径。
STATICFILES_DIRS=[
os.path.join(BASE_DIR,'static'),
]
7)在templates/booktest/目录下创建editor.html模板。
<html>
<head>
<title>自定义使用tinymce</title>
<script type="text/javascript" src='/static/js/tiny_mce.js'></script>
<script type="text/javascript">
tinyMCE.init({
'mode':'textareas',
'theme':'advanced',
'width':400,
'height':100
});
</script>
</head>
<body>
<form method="post" action="#">
<textarea name='gcontent'>哈哈,这是啥呀</textarea>
</form>
</body>
</html>
8)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/editor/
浏览效果如下图:
通过富文本编辑器产生的字符串是包含html的。 在数据库中查询如下图:
在模板中显示字符串时,默认会进行html转义,如果想正常显示需要关闭转义。
问:在模板中怎么关闭转义
方式一:过滤器safe
方式二:标签autoescape off
1)在booktest/views.py中定义视图show,用于显示富文本编辑器的内容。
from booktest.models import *
...
def show(request):
goods=GoodsInfo.objects.get(pk=1)
context={'g':goods}
return render(request,'booktest/show.html',context)
2)在booktest/urls.py中配置url。
url(r’^show/', views.show),
3)在templates/booktest/目录下创建show.html模板。
<html>
<head>
<title>展示富文本编辑器内容</title>
</head>
<body>
id:{{g.id}}
<hr>
{%autoescape off%}
{{g.gcontent}}
{%endautoescape%}
<hr>
{{g.gcontent|safe}}
</body>
</html>
4)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/show/
浏览效果如下图:
全文检索不同于特定字段的模糊查询,使用全文检索的效率更高,并且能够对于中文进行分词处理。
haystack:全文检索的框架,支持whoosh、solr、Xapian、Elasticsearc四种全文检索引擎,点击查看官方网站。
whoosh:纯Python编写的全文搜索引擎,虽然性能比不上sphinx、xapian、Elasticsearc等,但是无二进制包,程序不会莫名其妙的崩溃,对于小型的站点,whoosh已经足够使用,点击查看whoosh文档。
jieba:一款免费的中文分词包,如果觉得不好用可以使用一些收费产品。
1)在虚拟环境中依次安装需要的包。
pip install django-haystack
pip install whoosh
pip install jieba
2)修改test6/settings.py文件,安装应用haystack。
INSTALLED_APPS = (
...
'haystack',
)
3)在test6/settings.py文件中配置搜索引擎。
...
HAYSTACK_CONNECTIONS = {
'default': {
#使用whoosh引擎
'ENGINE': 'haystack.backends.whoosh_cn_backend.WhooshEngine',
#索引文件路径
'PATH': os.path.join(BASE_DIR, 'whoosh_index'),
}
}
#当添加、修改、删除数据时,自动生成索引
HAYSTACK_SIGNAL_PROCESSOR = 'haystack.signals.RealtimeSignalProcessor'
4)在test6/urls.py中添加搜索的配置。
url(r’^search/', include(‘haystack.urls’)),
1)在booktest目录下创建search_indexes.py文件。
from haystack import indexes
from booktest.models import GoodsInfo
#指定对于某个类的某些数据建立索引
class GoodsInfoIndex(indexes.SearchIndex, indexes.Indexable):
text = indexes.CharField(document=True, use_template=True)
def get_model(self):
return GoodsInfo
def index_queryset(self, using=None):
return self.get_model().objects.all()
2)在templates目录下创建"search/indexes/booktest/"目录。
3)在上面的目录中创建"goodsinfo_text.txt"文件。
#指定索引的属性
{{object.gcontent}}
4)找到虚拟环境py_django下的haystack目录。
/home/python/.virtualenvs/py_django/lib/python3.5/site-packages/haystack/backends/
5)在上面的目录中创建ChineseAnalyzer.py文件。
import jieba
from whoosh.analysis import Tokenizer, Token
class ChineseTokenizer(Tokenizer):
def __call__(self, value, positions=False, chars=False,
keeporiginal=False, removestops=True,
start_pos=0, start_char=0, mode='', **kwargs):
t = Token(positions, chars, removestops=removestops, mode=mode,
**kwargs)
seglist = jieba.cut(value, cut_all=True)
for w in seglist:
t.original = t.text = w
t.boost = 1.0
if positions:
t.pos = start_pos + value.find(w)
if chars:
t.startchar = start_char + value.find(w)
t.endchar = start_char + value.find(w) + len(w)
yield t
def ChineseAnalyzer():
return ChineseTokenizer()
6)复制whoosh_backend.py文件,改为如下名称:
注意:复制出来的文件名,末尾会有一个空格,记得要删除这个空格。
whoosh_cn_backend.py
7)打开复制出来的新文件,引入中文分析类,内部采用jieba分词。
from .ChineseAnalyzer import ChineseAnalyzer
8)更改词语分析类。
查找
analyzer=StemmingAnalyzer()
改为
analyzer=ChineseAnalyzer()
9)初始化索引数据。
python manage.py rebuild_index
10)按提示输入y后回车,生成索引。
11)索引生成后目录结构如下图:
按照配置,在admin管理中添加数据后,会自动为数据创建索引,可以直接进行搜索,可以先创建一些测试数据。
1)在booktest/views.py中定义视图query。
def query(request):
return render(request,'booktest/query.html')
2)在booktest/urls.py中配置。
url(r’^query/', views.query),
3)在templates/booktest/目录中创建模板query.html。
参数q表示搜索内容,传递到模板中的数据为query。
<html>
<head>
<title>全文检索</title>
</head>
<body>
<form method='get' action="/search/" target="_blank">
<input type="text" name="q">
<br>
<input type="submit" value="查询">
</form>
</body>
</html>
4)自定义搜索结果模板:在templates/search/目录下创建search.html。
搜索结果进行分页,视图向模板中传递的上下文如下:
query:搜索关键字
page:当前页的page对象
paginator:分页paginator对象
视图接收的参数如下:
参数q表示搜索内容,传递到模板中的数据为query
参数page表示当前页码
<html>
<head>
<title>全文检索--结果页</title>
</head>
<body>
<h1>搜索 <b>{{query}}</b> 结果如下:</h1>
<ul>
{%for item in page%}
<li>{{item.object.id}}--{{item.object.gcontent|safe}}</li>
{%empty%}
<li>啥也没找到</li>
{%endfor%}
</ul>
<hr>
{%for pindex in page.paginator.page_range%}
{%if pindex == page.number%}
{{pindex}}
{%else%}
<a href="?q={{query}}&page={{pindex}}">{{pindex}}</a>
{%endif%}
{%endfor%}
</body>
</html>
5)运行服务器,在浏览器中输入如下地址:
http://127.0.0.1:8000/query/
在文本框中填写要搜索的信息,点击”搜索“按钮。
搜索结果如下:
Django中内置了邮件发送功能,被定义在django.core.mail模块中。发送邮件需要使用SMTP服务器,常用的免费服务器有:163、126、QQ,下面以163邮件为例。
1)注册163邮箱itcast88,登录后设置。
2)在新页面中点击“客户端授权密码”,勾选“开启”,弹出新窗口填写手机验证码。
3)填写授权码。
4)提示开启成功。
5)打开test6/settings.py文件,点击下图配置。
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'smtp.163.com'
EMAIL_PORT = 25
#发送邮件的邮箱
EMAIL_HOST_USER = '[email protected]'
#在邮箱中设置的客户端授权密码
EMAIL_HOST_PASSWORD = 'python808'
#收件人看到的发件人
EMAIL_FROM = 'python'
6)在booktest/views.py文件中新建视图send。
from django.conf import settings
from django.core.mail import send_mail
from django.http import HttpResponse
...
def send(request):
msg='点击激活'
send_mail('注册激活','',settings.EMAIL_FROM,
['[email protected]'],
html_message=msg)
return HttpResponse('ok')
7)在booktest/urls.py文件中配置。
url(r'^send/$',views.send),
8)启动服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/send/
邮件发送成功后,在邮箱中查看邮件如下图:
celery
情景:用户发起request,并等待response返回。在本些views中,可能需要执行一段耗时的程序,那么用户就会等待很长时间,造成不好的用户体验,比如发送邮件、手机验证码等。
使用celery后,情况就不一样了。解决:将耗时的程序放到celery中执行。
celery名词:
任务task:就是一个Python函数。
队列queue:将需要执行的任务加入到队列中。
工人worker:在一个新进程中,负责执行队列中的任务。
代理人broker:负责调度,在布置环境中使用redis。
安装包:
celery3.1.25
django-celery3.1.17
示例
1)在booktest/views.py文件中创建视图sayhello。
import time
...
def sayhello(request):
print('hello ...')
time.sleep(2)
print('world ...')
return HttpResponse("hello world")
2)在booktest/urls.py中配置。
url(r'^sayhello$',views.sayhello),
3)启动服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/sayhello/
4)在终端中效果如下图,两次输出之间等待一段时间才会返回结果。
5)在test6/settings.py中安装。
INSTALLED_APPS = (
...
'djcelery',
}
6)在test6/settings.py文件中配置代理和任务模块。
import djcelery
djcelery.setup_loader()
BROKER_URL = 'redis://127.0.0.1:6379/2'
7)在booktest/目录下创建tasks.py文件。
import time
from celery import task
@task
def sayhello():
print('hello ...')
time.sleep(2)
print('world ...')
8)打开booktest/views.py文件,修改sayhello视图如下:
from booktest import tasks
...
def sayhello(request):
# print('hello ...')
# time.sleep(2)
# print('world ...')
tasks.sayhello.delay()
return HttpResponse("hello world")
9)执行迁移生成celery需要的数据表。
python manage.py migrate
生成表如下:
10)启动Redis,如果已经启动则不需要启动。
sudo service redis start
11)启动worker。
python manage.py celery worker --loglevel=info
启动成功后提示如下图:
11)打开新终端,进入虚拟环境,启动服务器,刷新浏览器。 在旧终端中两个输出间仍有时间间隔。
运行完成后如下图,注意两个终端中的时间,服务器的响应是立即返回的。
12)打开booktest/task.py文件,修改为发送邮件的代码,就可以实现无阻塞发送邮件。
from django.conf import settings
from django.core.mail import send_mail
from celery import task
@task
def sayhello():
msg='点击激活'
send_mail('注册激活','',settings.EMAIL_FROM,
['[email protected]'],
html_message=msg)
当项目开发完成后,需要将项目代码放到服务器上,这个服务器拥有固定的IP,再通过域名绑定,就可以供其它人浏览,对于python web开发,可以使用wsgi、apache服务器,此处以wsgi为例进行布署。
服务器首先是物理上的一台性能高、线路全、运行稳定的机器,分为私有服务器、公有服务器。
私有服务器:公司自己购买、自己维护,只布署自己的应用,可供公司内部或外网访问,成本高,需要专业人员维护,适合大公司使用。
公有服务器:集成好运营环境,销售空间或主机,供其布署自己的应用,适合初创公司使用,成本低。
常用的公有服务器,如阿里云、青云等,可按流量收费或按时间收费。服务器还需要安装服务器软件,此处需要uWSGI、Nginx。
服务器架构如下图:
示例
1)布署前需要关闭调试、允许任何机器访问,打开test6/settings.py文件。
DEBUG = False
ALLOW_HOSTS=[‘*’,]
2)打开templates/booktest/index.html文件,修改如下:
<html>
<head>
<title>第三方包</title>
</head>
<body>
<h1>第三方包</h1>
<hr>
<h1>布署</h1>
<img src="/static/img/sg.png"/>
</body>
</html>
3)将图片sg.png拷贝到static/img/目录下。
4)运行服务器,在浏览器中输入如下网址:
http://127.0.0.1:8000/
浏览效果如下图,图片是看不到的。
1)在本机进入虚拟环境,执行命令导出当前需要的所有包。
pip freeze > plist.txt
2)通过ftp软件将项目代码和plist.txt文件上传到服务器。
3)创建虚拟环境,在虚拟环境上安装包。
mkvirtualenv 虚拟环境名称
pip install -r plist.txt
在生产环境中使用WSGI作为python web的服务器。WSGI:全拼为Python Web Server Gateway Interface,Python Web服务器网关接口,是Python应用程序或框架和Web服务器之间的一种接口,被广泛接受。WSGI没有官方的实现, 因为WSGI更像一个协议,只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行。
项目默认会生成一个wsgi.py文件,确定了settings模块、application对象。
application对象:在Python模块中使用application对象与应用服务器交互。
settings模块:用于进行项目配置。
uWSGI
uWSGI实现了WSGI的所有接口,是一个快速、自我修复、开发人员和系统管理员友好的服务器。uWSGI代码完全用C编写,效率高、性能稳定。
1)安装uWSGI。
pip install uwsgi
2)配置uWSGI,在项目目录下创建uwsgi.ini文件,配置如下:
[uwsgi]
#使用nginx连接时使用
#socket=127.0.0.1:8080
#直接做web服务器使用
http=127.0.0.1:8080
#项目目录
chdir=/home/python/Desktop/pytest/test6
#项目中wsgi.py文件的目录,相对于项目目录
wsgi-file=test6/wsgi.py
processes=4
threads=2
master=True
pidfile=uwsgi.pid
daemonize=uwsgi.log
3)启动。
uwsgi --ini uwsgi.ini
4)查看。
ps ajx|grep uwsgi
效果如下图:
5)停止。
uwsgi --stop uwsgi.pid
6)在浏览器中输入如下网址:
http://127.0.0.1:8080/
浏览效果如下图,图片是看不到的。
7)测试没问题,将配置中启用socket,禁用http。
8)停止uwsgi服务,然后再启动uwsgi。
使用nginx的作用主要包括负载均衡、反向代理。
1)下载nginx后放到桌面上,解压缩。
tar zxvf nginx-1.6.3.tar.gz
2)进入nginx-1.6.3目录,依次执行以下命令进行安装。
./configure
make
sudo make install
3)默认安装到/usr/local/nginx/目录,进入此目录。
cd /usr/local/nginx/
4)启动。
sudo sbin/nginx
5)查看进程。
6)停止。
sudo sbin/nginx -s stop
7)打开浏览器,输入如下网址:
http://127.0.0.1/
浏览效果如下图:
指向uwsgi项目
1)打开conf/nginx.conf文件。
sudo gedit conf/nginx.conf
2)在server节点下添加新的location项,指向uwsgi的ip与端口。
location / {
#将所有的参数转到uwsgi下
include uwsgi_params;
#uwsgi的ip与端口
uwsgi_pass 127.0.0.1:8080;
}
代码效果如下图:
3)关闭nginx后再开启。
4)打开浏览器,刷新后如下图:
静态文件
所有的静态文件都会由nginx处理,不会将请求转到uwsgi。
1)打开conf/nginx.conf文件。
sudo gedit conf/nginx.conf
2)在server节点下添加新的location项,用于处理静态文件。
location /static {
alias /var/www/test6/static/;
}
3)在服务器上创建如下目录。
sudo mkdir -vp /var/www/test6/static/
修改目录权限。
sudo chmod 777 /var/www/test6/static/
最终目录结构如下图:
4)修改test6/settings.py文件。
STATIC_ROOT='/var/www/test6/static/'
STATIC_URL='/static/'
5)收集所有静态文件到static_root指定目录。
python manage.py collectstatic
按提示输入yes,收集文件。
6)停止后再启动nginx服务。
7)在浏览器中刷新,浏览效果如下图:
布署完成。