Django 视图(View)
1. 视图简介
2. URLconf
3. 视图函数&错误视图
4. HttpRequest 对象
5. HttpResponse 对象
6. 状态保持:Session
1. 视图简介
- 视图负责接收 web 请求并返回 web 响应。
- 视图就是一个 python 函数,被定义在 views.py 中。
- 响应可以是一张网页的 HTML 内容、一个重定向、一个 404 错误等等。
响应处理过程如下图:
- 用户在浏览器中输入网址:www.demo.com/1/100
- Django 获取网址信息,去除域名和端口后剩下 URI:1/100
- 按照 urlconf 中正则的配置顺序逐一匹配,一旦匹配成功,则调用对应的视图函数
- 调用对应的视图函数,接收 request 对象(及正则中获取的值),处理并返回 response 对象
2. URLconf
在项目目录下的 settings.py 中通过 ROOT_URLCONF 指定根级 url 的配置:
ROOT_URLCONF = 'ViewDemo.urls'
项目目录下的 urls.py 中的 urlpatterns 是一个包含 url() 实例的列表。一个 url() 对象包括:
- 正则表达式
- 视图函数
- 名称 name
1)关联各应用下的 URLconf
在应用中创建 urls.py,定义本应用中的 urlconf。如 hero_info 应用目录下的 urls.py:
urlpatterns = [ url(r'^$', views.index, name='index'), # 正则匹配成功时访问index函数 url(r'^([0-9]+)/$', views.detail, name='detail'), # 正则匹配成功时访问detail函数 ]
再在项目的 urls.py 中使用 include() 关联对应应用的 urls.py:
urlpatterns = [ url(r'^admin/', include(admin.site.urls)), url(r'^hero_info/', include('hero_book.urls', namespace='hero_info')), # 关联hero_book包下的urls.py ]
使用 include 可以去除 urlconf 的冗余。
注意:django 2.0 起,urls 不支持正则表达式问题。如果需要使用正则,需要导入 re_path:
from django.urls import path, re_path urlpatterns = [ path('admin/', admin.site.urls), re_path(r'^test-(\d+)-(\d+)/', views.test), path('index/', views.index), ]
匹配过程
解析 web 请求地址时,先与主 URLconf 匹配,成功后再用剩余的部分与应用中的 URLconf 匹配。如下示例:
- 请求 http://www.demo.cn/hero_info/1/
- 匹配部分是 /hero_info/1/
- 在 settings.py 中与“hero_info/”匹配成功,再用“1/”与 hero_info 应用的 urls 匹配成功
2)URLconf 的编写
- url 的正则表达式不需要添加一个前导的反斜杠,如应该写作'hero_info/',而不应该写作'/hero_info/'。
- 每个正则表达式前面的 r 表示字符串不转义。
- 请求的 url 被看做是一个普通的 python 字符串,进行匹配时不包括 get 或 post 请求的参数及域名。
http://www.itcast.cn/python/1/?i=1&p=new # 只匹配“/python/1/”部分
- 若要从 url 中捕获一个值,则要使用正则中的分组。
- 优先使用命名参数,如果没有命名参数则使用位置参数。
- 每个捕获的参数都作为一个普通的 python 字符串传递给视图。
url(r'^([0-9]+)/$', views.detail, name='detail') # 通过位置参数传递给视图 url(r'^(?P<id>[0-9]+)/$', views.detail, name='detail') # 通过分组名称传递参数给视图函数,本例的参数名称为id
3)namespace 反向解析
在 include 中通过 namespace 定义命名空间,用于反向解析。
URL 的反向解析
问题:如果在视图、模板中使用硬编码的链接,在 urlconf 发生改变时,维护是一件非常麻烦的事情。
解决:在做链接时,通过指向 urlconf 中的 namespace 名称,动态生成链接地址。
- 视图:使用 django.core.urlresolvers.reverse() 函数。
- 模板:使用 url 模板标签。
3. 视图函数&错误视图
视图本质就是一个函数。在应用目录下默认有 views.py 文件,一般视图都定义在这个文件中。
视图的参数:
- 一个 HttpRequest 实例
- 通过正则表达式分组获取的位置参数或关键字参数
如果处理功能过多,可以将函数定义到不同的 py 文件中(但不建议)。示例:
# 新建views1.py from django.http import HttpResponse
def index(request): return HttpResponse("你好") # 在urls.py中修改配置 from . import views1 url(r'^$', views1.index, name='index'),
错误视图
Django 原生自带了几个默认视图用于处理 HTTP 错误。
404 (page not found) 视图
- 默认的 404 视图将传递一个变量 request_path 给模板,它是导致错误的 url。
- 如果Django在检测URLconf中的每个正则表达式后没有找到匹配的内容时,将调用 404 视图。
- 如果在 settings.py 中 DEBUG 设置为 True,那么将永远不会调用 404 视图,而是显示 URLconf。 并带有一些调试信息。
1)在 templates 中创建 404.html:
2)编写 404.html:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> {{request_path}} <h1>Oops!该网页不存在..</h1> </body> </html>
3)在 settings.py 中修改两个配置:
DEBUG = False # 取消调试状态 ALLOWED_HOSTS = ['*', ] # 允许所有主机访问
4)访问不存在的地址:
500 (server error) 视图
- 默认的 500 视图不会传递变量给 500.html 模板。
- 同理,如果在 settings.py 中 DEBUG 设置为 True,也将不会调用 505 视图,而是显示 URLconf 并带有一些调试信息。
400 (bad request) 视图
- 错误来自客户端的操作。例如当用户进行的操作在安全方面可疑时(篡改会话 cookie 等)。
4. HttpRequest 对象
- 服务器接收到 http 请求后,会根据报文创建 HttpRequest 对象。
- 视图函数的第一个参数是 HttpRequest 对象。
- 在 django.http 模块中定义了 HttpRequest 对象的 API。
属性
下面除非特别说明,属性都是只读的。
- path:一个字符串,表示请求的页面的完整路径,不包含域名和端口。
- method:一个字符串,表示请求使用的 HTTP 方法,常用值包括:'GET'、'POST'。
- encoding:一个字符串,表示提交的数据的编码方式。
- 如果为 None 则表示使用浏览器的默认设置,一般为 utf-8。
- 这个属性是可写的,可以通过修改它来修改访问表单数据时使用的编码,接下来对属性的任何访问将使用新的 encoding 值。
- GET:一个类似于字典的对象,包含 get 请求方式的所有参数。
- POST:一个类似于字典的对象,包含 post 请求方式的所有参数。
- FILES:一个类似于字典的对象,包含所有的上传文件。
- COOKIES:一个标准的 python 字典,包含所有的 cookie,键和值都为字符串。
- session:一个既可读又可写的类似于字典的对象,表示当前的会话,只有当 Django 启用会话的支持时才可用。
方法
- is_ajax():如果请求是通过 XMLHttpRequest 发起的,则返回 True。
1)QueryDict 对象
QueryDict 对象定义在 django.http.QueryDict。
Request 对象的属性 GET、POST 等都是 QueryDict 类型的对象。
与 python 字典不同,QueryDict 类型的对象用来处理同一个键带有多个值的情况。
- 方法 get():根据键获取一个值。如果一个键同时拥有多个值,获取最后一个值。
dict.get('键', default) 或简写为 dict['键']
- 方法 getlist():将键的值以列表返回,即可以获取一个键的多个值。
dict.getlist('键', default)
2)GET 属性
- GET 属性是 QueryDict 类型的对象,包含了 get 请求方式的所有参数,与 url 请求地址中的参数对应,位于 ? 后面。
- 参数的格式是键值对,如 key1=value1。多个参数之间则使用 & 连接,如 key1=value1&key2=value2。
- 键是开发人员定下来的,值是可变的。
范例
views.py:创建 get_test_1() 用于定义链接,get_test_2() 用于接收一键一值,get_test_3() 用于接收一键多值。
1 def get_test_1(request): 2 return render(request, "hero_book/get_test_1.html") 3 4 def get_test_2(request): 5 a = request.GET["a"] 6 b = request.GET["b"] 7 context = {"a": a, "b": b} 8 return render(request, "hero_book/get_test_2.html", context) 9 10 def get_test_3(request): 11 a = request.GET.getlist("a") 12 b = request.GET["b"] 13 context = {"a": a, "b": b} 14 return render(request, "hero_book/get_test_3.html", context)
urls.py:
url(r'^get_test_1/$', views.get_test_1), url(r'^get_test_2/$', views.get_test_2), url(r'^get_test_3/$', views.get_test_3),
创建 get_test_1.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 9 链接1:一个键传递一个值 <a href="/hero_book/get_test_2/?a=1&b=2">get_test_2</a> 10 <br> 11 链接2:一个键传递多个值 <a href="/hero_book/get_test_3/?a=1&a=2&b=3">get_test_3</a> 12 13 </body> 14 </html>
创建 get_test_2.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 a: {{ a }} <br> 9 b: {{ b }} 10 </body> 11 </html>
创建 get_test_3.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 a: {% for value in a %} 9 {{ value }} 10 {% endfor %} 11 <br> 12 b: {{ b }} 13 </body> 14 </html>
执行效果:
3)POST 属性
POST 属性是 QueryDict 类型的对象,包含了 post 请求方式的所有参数,与 form 表单中的控件对应。
问:表单中哪些属性会被提交?
- 控件要有 name 属性,则 name 属性的值为键,value 属性的值为键,构成键值对提交。键是开发人员定下来的,值是可变的。
- 对于 checkbox 控件,name 属性一样为一组,当控件被选中后会被提交,存在一键多值的情况。
范例
views.py:定义视图 post_test_1 用于填写 form 表单;定义视图 post_test_2 获取表单的键值。
1 def post_test_1(request): 2 return render(request, "hero_book/post_test_1.html") 3 4 def post_test_2(request): 5 uname=request.POST['uname'] 6 upwd=request.POST['upwd'] 7 ugender=request.POST['ugender'] 8 uhobby=request.POST.getlist('uhobby') 9 context={'uname': uname, 'upwd': upwd, 'ugender': ugender, 'uhobby': uhobby} 10 return render(request, "hero_book/post_test_2.html", context)
注意:使用表单提交时,先注释掉 settings.py 中的中间件 crsf。
urls.py:
url(r'^post_test_1/$', views.post_test_1), url(r'^post_test_2/$', views.post_test_2),
创建模板 post_test_1.html,用于填写并提交 form 表单:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 <form method="post" action="/hero_book/post_test_2/"> 9 姓名:<input type="text" name="uname"/><br> 10 密码:<input type="password" name="upwd"/><br> 11 性别:<input type="radio" name="ugender" value="1"/>男 12 <input type="radio" name="ugender" value="0"/>女<br> 13 爱好:<input type="checkbox" name="uhobby" value="胸口碎大石"/>胸口碎大石 14 <input type="checkbox" name="uhobby" value="喝酒"/>喝酒 15 <input type="checkbox" name="uhobby" value="爬山"/>爬山<br> 16 <input type="submit" value="提交"/> 17 </form> 18 </body> 19 </html>
创建模板 post_test_2.html,用于输出表单的值:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 用户名:{{ uname }}<br> 密码:{{ upwd }}<br> 性别:{{ ugender }}<br> 爱好:{% for hobby in uhobby %} {{ hobby }} {% endfor %} </body> </html>
执行效果:
5. HttpResponse 对象
- 在 django.http 模块中定义了 HttpResponse 对象的 API。
- HttpRequest 对象由 Django 自动创建,HttpResponse 对象由程序员创建。
示例 1:不调用模板,直接返回数据
from django.http import HttpResponse def index(request): return HttpResponse('你好')
示例 2:调用模板
from django.http import HttpResponse from django.template import RequestContext, loader def index(request): t1 = loader.get_template('polls/index.html') # 加载模板 context = RequestContext(request, {'h1': 'hello'}) # 渲染模板(填充数据) return HttpResponse(t1.render(context)) # 返回一个渲染后的 HttpResponse 对象
示例 3:简写函数 render
render(request, template_name[, context]):结合一个给定的模板和一个给定的上下文字典,并返回一个渲染后的 HttpResponse 对象。
- request:该 request 用于生成 response。
- template_name:要使用的模板的完整名称。
- context:添加到模板上下文的一个字典,视图将在渲染模板之前调用它。
from django.shortcuts import render def index(request): return render(request, 'booktest/index.html', {'h1': 'hello'})
属性
- content:表示返回的内容,字符串类型。
- charset:表示 response 采用的编码字符集,字符串类型。
- status_code:响应的 HTTP 响应状态码。
- content-type:指定输出的 MIME 类型。
方法
- init():实例化 HttpResponse 对象。
- write(content):以文件的方式写。
- flush():以文件的方式输出缓存区。
- set_cookie(key, value='', max_age=None, expires=None):设置 cookie。
- key、value:都是字符串类型。
- max_age:是一个整数,表示在指定秒数后过期。
- expires:是一个 datetime 或 timedelta 对象,会话将在这个指定的日期/时间过期,注意 datetime 和 timedelta 值只有在使用 PickleSerializer 时才可序列化。
- max_age 与 expires 二选一。
- 如果不指定过期时间,则两个星期后过期。
- delete_cookie(key):删除指定 key 的 Cookie,如果 key 不存在则什么也不发生。
1)设置 Cookie
1 def index(request): 2 response = HttpResponse() 3 # 用户已有cookie则读取 4 if "username" in request.COOKIES: 5 response.write("已有 cookie: %s" % request.COOKIES["username"]) 6 return response 7 # 对首次访问的用户设置cookie 8 response.set_cookie("username", "xiaoming", None, datetime(2030, 1, 1)) 9 response.write("初始化 cookie 成功") 10 return response
2)子类 HttpResponseRedirect
- 重定向,服务器端跳转。
- 构造函数的第一个参数用来指定重定向的地址。
- 简写:from django.shortcuts import redirect
1 def redirect_test_1(request): 2 return HttpResponseRedirect("/hero_book/redirect_test_2/") 3 4 def redirect_test_2(request): 5 return render(request, "hero_book/redirect_test_2.html")
访问 http://127.0.0.1:8000/hero_book/redirect_test_1,结果如下:
3)子类 JsonResponse
- 返回 json 数据,一般用于异步请求。
- 帮助用户创建 JSON 编码的响应。
- _init _(data):参数 data 是字典对象。
- JsonResponse 的默认 Content-Type 为 application/json。
from django.http import JsonResponse def index2(requeset): return JsonResponse({'list': 'abc'})
4)异常处理:返回 404
得到对象或返回 404
- get_object_or_404(klass, *args, **kwargs)
- 通过模型管理器或查询集调用 get() 方法,如果没找到对象,不引发模型的 DoesNotExist 异常,而是引发 Http404 异常。
- klass:获取对象的模型类、Manager 对象或 QuerySet 对象。
- **kwargs:查询的参数,格式应该可以被 get() 和 filter() 接受。
- 如果找到多个对象将引发 MultipleObjectsReturned 异常。
from django.shortcuts import * def detail(request, id): try: book = get_object_or_404(BookInfo, pk=id) except BookInfo.MultipleObjectsReturned: book = None return render(request, 'booktest/detail.html', {'book': book}) #将settings.py中的DEBUG改为False #将请求地址输入2和100查看效果
得到列表或返回 404
- get_list_or_404(klass, *args, **kwargs)
- klass:获取列表的一个 Model、Manager 或 QuerySet 实例。
- **kwargs:查寻的参数,格式应该可以被 get() 和 filter() 接受。
from django.shortcuts import * def index(request): # list = get_list_or_404(BookInfo, pk__lt=1) list = get_list_or_404(BookInfo, pk__lt=6) return render(request, 'booktest/index.html', {'list': list}) # 将settings.py中的DEBUG改为False
6. 状态保持:Session
http 协议是无状态的:每次请求都是一次新的请求,不会记得之前通信的状态。而实现状态保持的方式一般是在客户端或服务器端存储与会话有关的数据。
客户端与服务器端的一次通信,就是一次会话。存储方式包括 cookie、session,会话一般指 session 对象。
- 使用 cookie,所有数据存储在客户端,注意不要存储敏感信息。
- 推荐使用 sesison 方式,所有数据存储在服务器端,并自动在客户端 cookie 中存储 session_id。
状态保持的目的是在一段时间内跟踪请求者的状态,可以实现跨页面访问当前请求者的数据。注意:不同的请求者之间不会共享这个数据,会话与请求者一一对应。
启用 session
使用 django-admin startproject 创建项目时已默认启用。在 settings.py 文件中:
# INSTALLED_APPS列表中添加: 'django.contrib.sessions', # MIDDLEWARE_CLASSES列表中添加: 'django.contrib.sessions.middleware.SessionMiddleware',
禁用会话:删除/注释上面指定的两个值,禁用会话将节省一些性能消耗。
使用 session
启用会话后,每个 HttpRequest 对象将具有一个 session 属性,它是一个 QueryDict 对象。其方法如下:
- get(key, default=None):根据键获取会话的值。
- clear():清除所有会话。
- flush():删除当前的会话数据并删除会话的 cookie。
- del request.session['member_id']:删除会话。
范例:用户登录
views.py:
1 # 主页 2 def index(request): 3 user_name = request.session.get("session_name") # 没有则返回默认的None 4 return render(request, "hero_book/index.html", {"user_name": user_name}) 5 6 # 登录页 7 def login(request): 8 return render(request, "hero_book/login.html") 9 10 # 登录页提交表单 11 def login_handler(request): 12 request.session["session_name"] = request.POST["myname"] 13 return redirect("/hero_book/") # 重定向主页 14 15 # 退出登录 16 def logout(request): 17 # del request.session["session_name"] 18 # request.session.clear() 19 request.session.flush() 20 return redirect("/hero_book/") # 重定向主页
urls.py:
url(r'^$', views.index), url(r"^login/", views.login), url(r"^login_handler/", views.login_handler), url(r"^logout/", views.logout),
index.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>Title</title> 6 </head> 7 <body> 8 你好!{{ user_name }} 9 <hr/> 10 <a href="/hero_book/login/">登录</a> 11 <hr/> 12 <a href="/hero_book/logout/">退出</a> 13 </body> 14 </html>
login.html:
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <title>登录</title> 6 </head> 7 <body> 8 <form method="post" action="/hero_book/login_handler/"> 9 请输入用户名:<input type="text" name="myname"/> 10 <input type="submit" value="登录"/> 11 </form> 12 </body> 13 </html>
会话过期时间
set_expiry(value):设置会话的超时时间。
- 如果没有指定,则两个星期后过期。
- 如果 value 是一个整数,会话将在 value 秒没有活动后过期。
- 如果 value 是一个 timedelta 对象,会话将在当前时间加上这个指定的日期/时间过期。
- 如果 value 为 0,那么用户会话的 cookie 将在用户的浏览器关闭时过期。
- 如果 value 为 None,那么会话永不过期。
def login_handle(request): request.session['uname'] = request.POST['uname'] # request.session.set_expiry(10) # request.session.set_expiry(timedelta(days=5)) # request.session.set_expiry(0) # request.session.set_expiry(None)
存储 session
会话的存储方式,可以在 settings.py 的 SESSION_ENGINE 项中指定。
- 基于数据库的会话:这是 Django 默认的会话存储方式,需要添加 django.contrib.sessions 到的 INSTALLED_APPS 设置中,运行 manage.py migrate 后会在数据库中安装会话表,可显示指定为:
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
- 基于缓存的会话:只存在本地内存中,如果丢失则不能找回,比数据库的方式读写更快。
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
- 可以将缓存和数据库同时使用:优先从本地缓存中获取,如果没有则从数据库中获取。
SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
使用 Redis 缓存 session
Session 还支持文件、纯 cookie、Memcached、Redis 等方式存储。
- 安装 Django 与 Redis 的交互包:
pip install django-redis-sessions
- 修改 settings.py 中的配置,增加如下项:
SESSION_ENGINE = 'redis_sessions.session' SESSION_REDIS_HOST = 'localhost' # redis服务器IP SESSION_REDIS_PORT = 6379 # redis 默认端口 SESSION_REDIS_DB = 0 SESSION_REDIS_PASSWORD = '' SESSION_REDIS_PREFIX = 'session'
- 管理 Redis 的命令:
启动:sudo redis-server /etc/redis/redis.conf
停止:sudo redis-server stop
重启:sudo redis-server restart
redis-cli:使用客户端连接服务器
keys *:查看所有的键
get name:获取指定键的值
del name:删除指定名称的键