一、简介视图
主要内容:URLconf、HttpRequest对象、HttpResponse
1)视图接受Web请求并且返回Web响应
2)视图就是一个python函数,被定义在views.py中
3)响应可以是一张网页的HTML内容,一个重定向,一个404错误等等
4)在http请求中产生两个核心对象,所在位置是,django.http:
http请求:HttpRequest对象
http响应:HttpResponse对象
这两个对象是由django帮我构造的
5)响应处理过程如下图:
二、URLconf
1. 一个url()对象包括
1.1 URLconf相关概述
- 在settings.py文件中通过ROOT_URLCONF指定根级url的配置
- urlpatterns是一个url()实例的列表,我们把它称为路由,它里面的每一个url()我们称为路由配置
- 一个url()对象包括:
- 正则表达式
- 视图函数
- 可选参数
- 名称name
- 编写URLconf的注意:
-
- 若要从url中捕获一个值,需要在它周围设置一对圆括号
- 不需要添加一个前导的反斜杠,如应该写作'test/',而不应该写作'/test/'
- 每个正则表达式前面的r表示字符串不转义
- 请求的url被看做是一个普通的python字符串,进行匹配时不包括get或post请求的参数及域名
1.2 url路由配置方式
前一天,我们在最后实现了一次会话的全过程,我们也清楚的知道,一个网站当中有千千万万个会话,也就是说这些会话,我们都要去配置相应的路由。假设,我们现在这个网站有好多个会话,我们该如何配置路由呢?路由的配置,一般常用的有以下两种。
1.2.1 主路由配置
所谓主路由,就是我们项目设置文件夹当中和settings.py文件同级的那个urls.py文件里面的urlpatterns,无论我们的网站当中有多少个路由,我们全部把这些路由写在我们的主路由当中,我们称为主路由配置。
我们按照昨天我们的想法,当我们输入127.0.0.1:8000的时候,返回字符串首页,当我们输入127.0.0.1:8000/news/的时候,返回新闻,当我们输入127.0.0.1:8000/girls/返回美女。我们可以去实现一下,全部配置在主路由当中,代码如下:
这样的话,我们可以在网址当中,分别输入上述三个网址,可以分别得到我们想要返回的信息。当然,我们配置完路由,是肯定要在应用的views视图文件当中添加相应的view视图函数去处理。代码如下:
这样,我们就实现了url的主路由配置。我们配置了三个路由,那么这些路由是怎么找到它相对应的视图函数的呢?假设我们输入的127.0.0.1:8000/news/,如下图所示
1.2.2 子路由配置
在上面我们实现了主路由的配置,它的配置非常快捷无脑,也就是说,只要我们有会话,那么我们就在主路由当中给它配一个路由,然后就可以接着写视图处理函数。但是,我们大家想一想,假设我们的网站当中有100个会话,代表着我们的主路由当中会配100个路由,如果我们的网站是一个非常大的网站,那么大家可以猜到什么?
这样的话,我们的网站当然可以运行,但是会给我们的维护带来非常大的麻烦,因此,我们就有另外一种路由配置,就是我们的子路由配置。
前一天的课程当中,我们提到过,一个网站有多少个模块,也就代表了我们有多少个app(应用),那么,我们是否可以这么认为,所有app里面的会话总和就是我们网站的所有会话。这样的话,我们是否可以把对应app里面会话的路由,由这个app去管理,这样的话,我们主路由就没有那么复杂了。
还是上面的例子,首页、新闻和美女。我们把新闻和美女当作我们网站里面两个独立的模块,因为新闻和美女网页当中还会有很多的与它相关的请求。那么我们就可以把他们作为独立的app去创建,目录结构如下:
创建项目和应用,我们已经熟悉了,但是记住后续创建了新的app我们就要把这些app添加进入我们的settings文件当中。
接下来,我们就可以配置我们的路由了,我们以一个news app为例。配置流程如下:
1)、在相应的app下,手动创建一个urls.py文件作为子路由配置文件使用,也可以复制主路由配置文件urls.py,如下图所示:
2)、把主路由当作调度分发路由的工具,去分发子路由,在主路由当中,如下添加
fromdjango.conf.urls import include, url
fromdjango.contrib import admin
urlpatterns= [
url(r'^admin/', include(admin.site.urls)),
url(r'^news/',include('news.urls',namespace='news'))
]
include这个方法,会把经过主路由匹配成功后剩下的路径,分发给我们app里面的子路由去处理,它并不是严格匹配,正则是没有结束符$的,第一个参数代表分发给哪个路由配置文件去处理,第二个参数namespace代表反向解析,和url()当中name参数作用类似。
例如:127.0.0.1:8000/news/
这个url会先经过我们的主路由去匹配,匹配上news/之后,剩下的是空,那么会把空分发到子路由当中,因此,我们还要在子路由当中去做配置。
from django.conf.urls import include, url
from.views import show_news
urlpatterns =[
url(r'^$',show_news,name='news')
]
测试效果和主路由配置一样。
也就是说,如果我们不想主路由配置这个路由,可以将它分发给子路由去配置。一样可以得到我们想要的结果。从上面我们可以有这样一个启发,以后,主路由当中基本上不做url路由配置,仅仅是用来分发子路由用的。而真正的路由是在子路由当中去配置的,它们二者相互配合,就会把我们整个网站的会话路由,模块化进行处理。不但结构清晰,而且维护方便。
以后,我们的路由配置全部都是使用子路由配置,不建议直接主路由配置!
1.3 路由传参
1.3.1 未命名正则表达式组传参(位置传参)
在某些时候,视图函数当中需要从我们的路径当中去获取一些数据,这时候我们可以用到一个新的知识,那就是路由传参
举个例子,我们现在可以得到news这个网页的内容,网页上有很多的新闻链接,根据时间分类。假设我们需要得到一个时间点的新闻,比如说我们要获取到2018年4月20日的新闻我们该如何配置呢?因为这个请求也是属于新闻这个模块的,所以,我们也是要以news/开头,那么我们可以像这样去配置路径/news/2018/4/20/,最前面的/配置的时候要省略。
主路由当中,我们已经为news/分过子路由,所以,主路由当中不变,只要是属于这个news模块中的请求,也就是说只要是以new/开头的路径,我们以后都只需要在子路由当中配置剩余的路径即可,剩余的就是2018/4/20/配置如下:
但是,我们要知道一件事,所有的新闻信息都是存在数据库当中的,我们在视图函数中是要通过数据库操作去查询这个时间的新闻的,那么,这个日期对于我们来说就需要传到视图函数当中,我们可以通过正则分组的方式去捕获结果,传递给视图函数。把需要的数据加上小括号即可。视图函数当中要添加相应的形参去接受。子路由里配置如下:
视图函数如下:
效果如下:
这样的话,我们就通过路由传参的方式让视图函数拿到需要的信息。
但是问题又来了,如果我要获取到2017年3月21日的数据怎么做呢?
/news/2018/4/20/
/news/2017/3/21/
我们发现他们的路径,格式都很类似,就是参数值发生了变化,那么我们就想是否可以把路由里面的数字,变成通用的呢?答案是肯定的,那么我们可以像这样去改变我们的路由配置。
其余,都不变,测试,发现成功了。这样的话,我们就实现通用化的路由路径传参。
这样的方式,我们为什么要叫做未命名正则表达式组传参或者位置传参呢,我做一下改变,大家来看,我把视图内的形参顺序改变如下:day和year的位置调换。
结果如下:
这样的话,就不是我们想要的结果了,也就是说,这样传参会有意外发生。视图函数形参位置决定了结果的准确与否。但是一般情况下,没人会这么干。既然有意外,就会有补救,因此我们又有了另外一种方式传参。
1.3.2 命名正则表达式组传参(关键字传参)
命名正则表达式组传参也叫关键字传参,我们在上面发现,未命名正则表达式组传参存在风险,所以,我们可以使用这种方式做优化。方式如下:
视图函数当中的形参还是错误的顺序
结果我们还是可以保证准确:
这样的话,我们就通过两种传参方式,使得视图函数获取到请求路径当中所传递的必要信息
2. URL的反向解析
- 如果在视图、模板中使用硬编码的链接,在urlconf发生改变时,维护是一件非常麻烦的事情
- 解决:在做链接时,通过指向urlconf的名称,也就是我们配置的namespace和name,动态生成链接地址
- 视图:使用django.core.urlresolvers.reverse()函数
- 模板:使用url模板标签
三、视图函数
1. 定义视图
1.1 视图定义
1)本质就是一个函数
2)视图的参数
A、一个HttpRequest实例
B、通过正则表达式组获取的位置参数
C、通过正则表达式组获得的关键字参数
3)在应用目录下默认有views.py文件,一般视图都定义在这个文件中,如果处理功能过多,可以将函数定义到不同的py文件中;但是建议就写在views.py里面,如果逻辑可以区分,可以写在一个新的应用里面。
2. 错误视图
- Django原生自带几个默认视图用于处理HTTP错误
2.1 404(page not found) 视图
- defaults.page_not_found(request, template_name='404.html')
- 默认的404视图将传递一个变量给模板:request_path,它是导致错误的URL
- 如果Django在检测URLconf中的每个正则表达式后没有找到匹配的内容也将调用404视图
- 如果在settings中DEBUG设置为True,那么将永远不会调用404视图,而是显示URLconf 并带有一些调试信息
2.1.1 在settings.py中修改调试
DEBUG = False
ALLOWED_HOSTS = ['*', ]
2.1.2 请求一个不存在的地址,默认模板
http://127.0.0.1:8000/test/
2.1.3 自定义404模板
目标:
在templates中创建404.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
p{
color: red;
}
</style>
</head>
<body>
<p>我的妹呢,跑哪拉?妹的!</p>
<br>
{{ request_path }}
</body>
</html>
2.2 500(server error) 视图
2.2.1 认识500
- defaults.server_error(request, template_name='500.html')
- 在视图代码中出现运行时错误
- 默认的500视图不会传递变量给500.html模板
- 如果在settings中DEBUG设置为True,那么将永远不会调用505视图,而是显示URLconf 并带有一些调试信息
2.2.2 默认500视图
根据我们上面的例子:如果我要返回一个时间的新闻信息,那么访问127.0.0.1:8000/news/2016/12/3/就会返回这个时间的新闻信息。如果我们人为的在这个视图函数当中构造如下的错误,那么这个500视图就会默认调用。
接下来我们访问127.0.0.1:8000/news/2018/4/20/就会出现上面的默认500错误
2.2.3 自定义500 视图
在templates创建500.html内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
p{
color: red;
}
</style>
</head>
<body>
<p>挂了挂了,挂了阿</p>
<br>
</body>
</html>
访问连接:127.0.0.1:8000/news/2018/4/20/ 得到自定义500错误页面
2.3 400(bad request) 视图
- defaults.bad_request(request, template_name='400.html')
- 错误来自客户端的操作
- 当用户进行的操作在安全方面可疑的时候,例如篡改会话cookie
三、HttpReqeust对象
1. HttpReqeust对象说明
1.1 说明
1)服务器接收到http协议的请求后,会根据报文创建HttpRequest对象
2)视图函数的第一个参数是HttpRequest对象
3)在django.http模块中定义了HttpRequest对象的API
1.2 属性
下面除非特别说明,属性都是只读的,都是字符串
1)path:一个字符串,表示请求的页面的完整路径,不包含域名和请求参数
2)method:一个字符串,表示请求使用的HTTP方法,常用值包括:'GET'、'POST'
3)encoding:一个字符串,表示提交的数据的编码方式
A)如果为None则表示使用浏览器的默认设置,一般为utf-8
B)这个属性是可写的,可以通过修改它来修改访问表单数据使用的编码,接下来对属性的任何访问将使用新的encoding值
4)GET:一个类似于字典QueryDict的对象,包含get请求方式的所有参数
5)POST:一个类似于字典QueryDict的对象,包含post请求方式的所有参数
6)FILES:一个类似于字典的对象,包含所有的上传文件
7)COOKIES:一个标准的Python字典,包含所有的cookie,键和值都为字符串
8)session:一个既可读又可写的类似于字典的对象,表示当前的会话,只有当Django 启用会话的支持时才可用,详细内容见“状态保持”
1.3 方法
is_ajax():如果请求是通过XMLHttpRequest发起的,则返回True
2. QueryDict对象
2.1 QueryDict对象说明
1)定义在django.http.QueryDict
2)request对象的属性GET、POST都是QueryDict类型的对象
3)与python字典不同,QueryDict类型的对象用来处理同一个键带有多个值的情况。
例如:http://127.0.0.1:8000/booktest/?a=1&a=2&a=3
2.2 QueryDict对象方法
2.1.1 方法get()
根据键获取值
1)只能获取键的一个值
2)如果一个键同时拥有多个值,获取最后一个值
dict.get('
键
',
default)
或简写为
dict['
键
']
2.1.2 方法getlist()
根据键获取值
将键的值以列表返回,可以获取一个键的多个值
dict.getlist('
键
',
default)
3. GET属性
1)QueryDict类型的对象
2)包含get请求方式的所有参数
3)与url请求地址中的参数对应,位于?后面
4)参数的格式是键值对,如key1=value1
5)多个参数之间,使用&连接,如key1=value1&key2=value2
键是开发人员定下来的,值是可变的。
4. POST属性
1)QueryDict类型的对象
2)包含post请求方式的所有参数
3)与form表单中的控件对应
问:表单中哪些控件会被提交?
答:控件要有name属性,则name属性的值为键,value属性的值为键,构成键值对提交
-
- 对于checkbox控件,name属性一样为一组,当控件被选中后会被提交,存在一键多值的情况
- 键是开发人员定下来的,值是可变的
5. 请求方式传参
浏览器向后台发送请求的时候,有很多种方式,我们常见的两种方式就是get和post,例如在浏览器网址栏当中输入url以及我们点击网页上的超级链接,都会向后台发送请求,并且基本都是get请求,再比如我们登陆注册时候在form表单里面填写数据点击提交也会发送请求,并且基本都是post请求。
get和post这两种方式的请求在进入django后会被做处理,把这两种请求方式封装为HttpRequest对象request的两个属性GET和POST,这两个属性当中存了请求方式对应传递到视图函数的所有参数。在视图函数当中,我们可以通过GET对象,拿到get请求方式传递过来的参数,可以通过POST对象拿到post请求方式传递过来的参数。
浏览器中get请求方式的参数是在url的最后通过键值对明文传递过来的,不安全。例如:
http://127.0.0.1:8000/路径/ ?a=1&b=2
http://127.0.0.1:8000/路径/ ?a=1&a=2
以?作为分割,?之后的就是我们get请求方式传递的参数。它们以键值对传递,但是和字典不同的是,它们可以是一个键多个值
浏览器中form表单里面对应的数据,是通过post方式传参的方式提交到后台的如下图所示:
当我们点击提交按钮的时候,两个文本输入框当中的数据会被当作值,而这两个文本输入框的name属性值被当作键。构造成键值对传递到后台的。它门传递参数,不会把参数明文显示在url上。
get请求参数的获取:可以在视图函数当中通过
data = request.GET.get(‘键’,默认)获取
post请求参数的获取:可以在视图函数当中通过
data = request.POST.get(‘键’,默认)获取
五、HttpResponse对象
1)在django.http模块中定义了HttpResponse对象的API
2)HttpRequest对象由Django自动创建,HttpResponse对象由程序员创建
不调用模板,直接返回数据
1. HttpResponse对象简单案例
1.1 视图response
直接返回数据
url配置:
url(r'^$',index,name='index'),
url(r'^news/$', show_news, name='show_news'),
url(r'^girls/$', show_girls, name='show_girls'),
视图函数
defindex(request):
return HttpResponse('首页')
defshow_news(request):
return HttpResponse('新闻')
defshow_girls(request):
return HttpResponse('美女')
调用模板
url配置:
url(r'^get_page1/$', get_page1, name='get_page1'),
视图函数
defget_page1(request):
#第一步:使用加载器加载我们的页面
html_loader = loader.get_template('get_page1.html')
#第二步:构造我们要动态加载的数据
a =100
b =[1,2,3,4,5]
c ={'name':'yuange','age':30}
data ={'a':a,'b':b,'c':c}
#第三步:构造我们的请求上下文
context = RequestContext(request,data)
#第四步:使用加载器将上下文渲染成标准的html代码
content = html_loader.render(context)
return HttpResponse(content)
# returnrender(request,'get_page1.html',context)
模板
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>我是一个动态的网页</p>
{{ a }}<br>
{{ b }}<br>
{{ c }}<br>
{# 循环遍历列表#}
{%for num in b %}
{{ num }}
{% endfor %}
<br>
{#遍历字典#}
{%for key,value in c.items %}
{{ key }}---{{ value }}
{% endfor %}
</body>
</html>
1.2 简写函数render
- render(request, template_name[, context])
- 结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的HttpResponse对象
- request:该request用于生成response
- template_name:要使用的模板的完整名称
- context:添加到模板上下文的一个字典,视图将在渲染模板之前调用它
defget_page1(request):
a =100
b =[1,2,3,4,5]
c ={'name':'yuange','age':30}
context={'a':a,'b':b,'c':c}
return render(request,'get_page1.html',context)
2. 属性和方法
2.1 属性
- content:表示返回的内容,字符串类型
- charset:表示response采用的编码字符集,字符串类型,例如utf-8
- status_code:响应的HTTP响应状态码,例如404,500,302
- content-type:指定输出的MIME类型
例如:text/html,application/xml;q=0.9,image/webp,image/apng
2.2 方法
1)init :使用页内容实例化HttpResponse对象
2)write(content):以文件的方式写
3)flush():以文件的方式输出缓存区
4)cookie是服务器设置存储在浏览器的一段文本信息,例如用户登录的时候不用输入密码了。
set_cookie(key,value='', max_age=None, expires=None):设置Cookie
-
- key、value都是字符串类型
- max_age是一个整数,表示在指定秒数后过期
- expires是一个datetime或timedelta对象,会话将在这个指定的日期/时间过期,注意datetime和timedelta值只有在使用PickleSerializer时才可序列化
- max_age与expires二选一
- 如果不指定过期时间,则关闭浏览器过期
3. 关于cookie的操作
1)Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份、进行session 跟踪而储存在用户本地终端上的数据(通常经过加密)。定义于 RFC2109 和 2965 中的都已废弃,最新取代的规范是 RFC6265 [1] 。(可以叫做浏览器缓存)
2)cookie是服务器设置存储在浏览器的一段文本信息,例如用户登录的时候不用输入密码了。
4. 子类HttpResponseRedirect(重定向)
- 重定向,服务器端跳转
- 构造函数的第一个参数用来指定重定向的地址
5. 子类JsonResponse
1)返回json数据,一般用于异步请求
2)_init _(data)
3)帮助用户创建JSON编码的响应
4)参数data是字典对象
5)JsonResponse的默认Content-Type为application/json
六、状态保持
1)http协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态,也就是说上次请求的得到的相关信息于新的请求无关。
2)客户端与服务器端的一次通信,就是一次会话
3)实现状态保持的方式:在客户端或服务器端存储与会话有关的数据
4)存储方式包括cookie、session,会话一般指session对象
5)使用cookie,所有数据存储在客户端,注意不要存储敏感信息,容易泄露。
6)推荐使用sesison方式,所有数据存储在服务器端,在客户端cookie中存储session_id
7)状态保持的目的:是在一段时间内跟踪请求者的状态,可以实现跨页面访问当前请求者的数据。
8)同一个浏览器共享session,换浏览器就不共享了
9)注意:不同的请求者之间不会共享这个数据,与请求者一一对应
1. 启用session
1)使用django-adminstartproject创建的项目默认启用
2)在settings.py文件中
项INSTALLED_APPS列表中添加: 'django.contrib.sessions', 项MIDDLEWARE_CLASSES列表中添加: 'django.contrib.sessions.middleware.SessionMiddleware',
3)禁用会话:删除上面指定的两个值,禁用会话将节省一些性能消耗
2. 使用session
1)启用会话后,每个HttpRequest对象将具有一个session属性,它是一个类字典对象
2)get(key,default=None):根据键获取会话的值
3)clear():清除所有会话,不会删除数据库中的session记录
3)flush():删除当前的会话数据并删除会话的Cookie,会删除数据库中的session记录
4)delrequest.session['member_id']:删除会话,不删除mysql的记录
5)request.session[member_id]= None:删除会话,不删除mysql的记录
3. session的基本操作
4. 会话过期时间
- set_expiry(value):设置会话的超时时间
- 如果没有指定,则两个星期后过期,和cookie时间一样
- 如果value是一个整数,会话将在values秒没有活动后过期
- 若果value是一个timedelta对象,会话将在当前时间加上这个指定的日期/时间过期
- 如果value为0,那么用户会话的Cookie将在用户的浏览器关闭时过期
- 如果value为None,那么会话永不过期
- 修改视图中login_handle函数,查看效果
5. 存储session的五种方式(了解)
使用存储会话的方式,可以使用settings.py的SESSION_ENGINE项指定
5.1 基于数据库的会话
1)基于数据库的会话:这是django默认的会话存储方式
需要添加django.contrib.sessions到的INSTALLED_APPS设置中,
运行manage.py migrate在数据库中安装会话表,可显示指定为
SESSION_ENGINE='django.contrib.sessions.backends.db'
5.2 基于缓存的会话
基于缓存的会话:只存在本地内在中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE='django.contrib.sessions.backends.cache'
5.3 可以将缓存和数据库同时使用
可以将缓存和数据库同时使用:优先从本地缓存中获取,如果没有则从数据库中获取,这种性能更好
SESSION_ENGINE='django.contrib.sessions.backends.cached_db'
5.4 将Session存储在文件系统中
将Session存储在文件系统中:最后一种方式是将Session存储在文件系统中。
需要设置SESSION_ENGINE='django.contrib.sessions.backends.file'
这时你还需要同时设置SESSION_FILE_PATH 变量,它代表Session文件保存的位置,缺省的设置一般是tempfile.gettempdir(),表示系统的临时目录。这里要确保应用程序对那个目录有读写的权限。
5.5 使用Redis缓存session
会话还支持文件、纯cookie、Memcached、Redis等方式存储,下面演示使用redis存储
5.5.1 安装django-redis-sessions包
注意要在虚拟环境中安装,不在虚拟环境中会报错
命令:
pip3 install django-redis-sessions
注意要在虚拟环境中安装
如果用下面的安装不行,会报错
报错信息
5.5.2 修改settings中的配置,增加如下项
SESSION_ENGINE = 'redis_sessions.session' SESSION_REDIS_HOST = 'localhost' SESSION_REDIS_PORT = 6379 SESSION_REDIS_DB = 0 SESSION_REDIS_PASSWORD = '' SESSION_REDIS_PREFIX = 'session'
5.5.3管理redis的命令
启动:
sudo redis-server /etc/redis/redis.conf
停止:
sudo redis-server stop
重启:
sudo redis-server restart
redis-cli
:使用客户端连接服务器
keys *
:查看所有的键
get name
:获取指定键的值
del name
:删除指定名称的键
session存储的是base64编码过的
http://www1.tc711.com/tool/BASE64.htm,在该网站解析,得到我们存储的session
5.5.4 COOKIE和SESSION有什么区别
1,session 在服务器端,cookie 在客户端(浏览器)
2,session 默认被存在在服务器的一个文件里(不是内存)
3,session 的运行依赖 session id,而 session id 是存在 cookie 中的,也就是说,如果浏览器禁用了 cookie ,同时 session 也会失效(但是可以通过其它方式实现,比如在 url 中传递 session_id)
4,session 可以放在文件、数据库、或内存中都可以。
5,用户验证这种场合一般会用 session
因此,维持一个会话的核心就是客户端的唯一标识,即 session id
5.5.5 注意
注意如果同时设置了保存到mysql和redis,那么只能保存到redis中