)
视图函数
视图函数,简称视图,属于Django的视图层,默认定义在views.py文件中,是用来处理web请求信息以及返回响应信息的函数,所以研究视图函数只需熟练掌握两个对象即可:请求对象(HttpRequest)和响应对象(HttpResponse)
官网地址:https://docs.djangoproject.com/en/1.11/ref/request-response/
当一个页面被请求时,django会创建一个包含本次请求原信息(如http协议请求报文中的请求行、首部信息、内容主体)的HttpRequest对象。
之后,django会找到匹配的视图,将该对象传给视图函数的第一个参数,约定俗称该参数名为request(类似于我们自定义框架的environ参数)。
在视图函数中,通过访问该对象的属性便可以提取http协议的请求数据
| 一.HttpRequest.method |
| 获取请求使用的方法(值为纯大写的字符串格式)。例如:"GET"、"POST" |
| 应该通过该属性的值来判断请求方法 |
| |
| 在视图函数中: |
| if request.method. == "GET": |
| ... |
| if request.method == "POST": |
| ... |
| |
| 二.HttpRequest.GET |
| 值为一个类似于字典的QueryDict对象,封装了GET请求的所有参数,可通过HttpRequest.GET.get('键')获取相对应的值 |
| |
| 在视图函数中: |
| request.GET.get('name') |
| |
| 三.HttpRequest.POST |
| 值为一个类似于字典的QueryDict对象,封装了POST请求所包含的表单数据,可通过HttpRequest.POST.get('键')获取相对应的值 |
| |
| 在视图函数中: |
| request.POST.get('name') |
| |
| 针对表单中checkbox类型的input标签、select标签提交的数据,键对应的值为多个,需要用:HttpRequest.POST.getlist("hobbies")获取存有多个值的列表,同理也有HttpRequest.GET.getlist("键") |
| 针对有多个值的的情况,也可以用HttpRequest.GET.get("键"),默认会获取列表中的最后一个值 |
案例:
urls.py
| from django.urls import re_path |
| from app01 import views |
| |
| urlpatterns = [ |
| re_path(r'^login/$',views.login), |
| ] |
Views.py
| from django.shortcuts import render,HttpResponse |
| |
| def login(request): |
| if request.method == 'GET': |
| |
| |
| print(request.GET) |
| |
| |
| |
| a=request.GET.get('a') |
| b=request.GET.get('b') |
| c=request.GET.getlist('c') |
| c1=request.GET.get('c') |
| |
| return render(request,'login.html') |
| elif request.method == 'POST': |
| |
| |
| print(request.POST) |
| |
| |
| |
| name=request.POST.get('name') |
| age=request.POST.get('age') |
| hobbies=request.POST.getlist('hobbies') |
| |
| return HttpResponse('提交成功') |
在templates目录下新建login.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>登录页面</title> |
| </head> |
| <body> |
| |
| <!-- |
| method="post"代表在提交表单时会以POST方法提交表单数据 |
| action="/login/" 代表表单数据的提交地址为http://127.0.0.1:8001/login/,可以简写为action="/login/",或者action="" |
| --> |
| <form action="http://127.0.0.1:8001/login/" method="post"> |
| {% csrf_token %} <!--强调:必须加上这一行,后续我们会详细介绍--> |
| <p>用户名:<input type="text" name="name"></p> |
| <p>年龄:<input type="text" name="age"></p> |
| <p> |
| 爱好: |
| <input type="checkbox" name="hobbies" value="music">音乐 |
| <input type="checkbox" name="hobbies" value="read">阅读 |
| <input type="checkbox" name="hobbies" value="dancing">跳舞 |
| </p> |
| <p><input type="submit" value="提交"></p> |
| |
| </form> |
| </body> |
| </html> |
| 一.HttpRequest.path |
| 获取url地址的路径部分,只包含路径部分 |
| |
| 二.HttpRequest.get_full_path() |
| 获取url地址的完整path,既包含路径又包含参数部分 |
| |
| 如果请求地址是http://127.0.0.1:8001/order/?name=egon&age=10 |
| HttpRequest.path的值为"/order/" |
| HttpRequest.get_full_path()的值为"/order/?name=egon&age=10" |
案例:
urls.py
| from django.urls import path,register_converter,re_path |
| from app01 import views |
| |
| urlpatterns = [ |
| re_path(r'^order',views.order), |
| ] |
views.py
| from django.shortcuts import render,HttpResponse |
| |
| |
| |
| |
| def order(request): |
| print(request.path) |
| print(request.get_full_path()) |
| |
| return HttpResponse('order page') |
| 一.HttpRequest.META.get(...) |
| 值为包含了HTTP协议的请求头数据的Python字典,字典中的key及期对应值的解释如下 |
| CONTENT_LENGTH —— 请求的正文的长度(是一个字符串)。 |
| CONTENT_TYPE —— 请求的正文的MIME类型。 |
| HTTP_ACCEPT —— 响应可接收的Content-Type。 |
| HTTP_ACCEPT_ENCODING —— 响应可接收的编码。 |
| HTTP_ACCEPT_LANGUAGE —— 响应可接收的语言。 |
| HTTP_HOST —— 客服端发送数据的目标主机与端口 |
| HTTP_REFERER —— Referring 页面。 |
| HTTP_USER_AGENT —— 客户端使用的软件版本信息 |
| QUERY_STRING —— 单个字符串形式的查询字符串(未解析过的形式)。 |
| REMOTE_ADDR —— 客户端的IP地址。 |
| REMOTE_HOST —— 客户端的主机名。 |
| REMOTE_USER —— 服务器认证后的用户。 |
| REQUEST_METHOD —— 一个字符串,例如"GET" 或"POST"。 |
| SERVER_NAME —— 服务器的主机名。 |
| SERVER_PORT —— 服务器的端口(是一个字符串)。 |
| 从上面可以看到,除 CONTENT_LENGTH 和 CONTENT_TYPE 之外,HTTP协议的请求头数据转换为 META 的键时, |
| 都会 |
| 1、将所有字母大写 |
| 2、将单词的连接符替换为下划线 |
| 3、加上前缀HTTP_。 |
| 所以,一个叫做 X-Bender 的头部将转换成 META 中的 HTTP_X_BENDER 键。 |
| |
| 二、HttpRequest.encoding |
| 一个字符串,表示提交的数据的编码方式(如果为 None 则表示使用 DEFAULT_CHARSET 的设置,默认为 'utf-8')。 |
| 这个属性是可写的,你可以修改它来修改访问表单数据使用的编码。 |
| 接下来对属性的任何访问(例如从 GET 或 POST 中读取数据)将使用新的 encoding 值。 |
| 如果你知道表单数据的编码不是 DEFAULT_CHARSET ,则使用它。 |
| |
| 三、HttpRequest.scheme |
| 表示请求方案的字符串(通常为http或https) |
| |
| 四、其他 |
| HttpRequest还有很多其他重要属性,比如HttpRequest.body,待我们讲到专门的知识点时再专门详细讲解 |
请求对象HttpRequest是由django为我们创建好的,直接使用即可,而响应对象则需要我们负责创建。我们编写的每个视图都应该返回一个HttpResponse对象,响应可以是一个网页的HTML内容,一个重定向,一个404错误,一个XML文档,或者一张图片。用来返回响应对象的常用类如下
| from django.shortcuts import HttpResponse |
| from django.shortcuts import redirect |
| from django.shortcuts import render |
| |
| from django.http importJsonResponse |
传递字符串
| from django.http import HttpResponse |
| response = HttpResponse("Here's the text of the Web page.") |
| response = HttpResponse("Text only, please.", content_type="text/plain") |
| |
| ''' |
| ps:Content-Type用于指定响应体的MIME类型 |
| |
| MIME类型: |
| mime类型是多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开 |
| |
| MIME 类型有非常多种,一般常见的有: |
| |
| text/html:浏览器在获取到这种文件时会自动调用html的解析器对文件进行相应的处理。 |
| |
| text/plain:意思是将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理。 |
| |
| image/jpeg:JPEG格式的图片 |
| |
| image/gif:GIF格式的图片 |
| |
| video/quicktime:Apple 的 QuickTime 电影 |
| |
| application/vnd.ms-powerpoint:微软的powerpoint文件 |
| ''' |
依次增加字符串(了解)
| >>> response = HttpResponse() |
| >>> response.write("<p>Here's the text of the Web page.</p>") |
| >>> response.write("<p>Here's another paragraph.</p>") |
传递迭代器对象(了解)
| |
| |
| def index(request): |
| f=open(r'a.txt',mode='r',encoding='utf-8') |
| response=HttpResponse(f) |
| print(response.content) |
| |
| |
| return response |
设置或删除响应头信息
| response = HttpResponse() |
| response['Content-Type'] = 'text/html; charset=UTF-8' |
| response['X-Frame-Options'] = 'SAMEORIGIN' |
| del response['Content-Type'] |
属性
| HttpResponse.status_code:响应的状态码 |
| HttpResponse.charset:响应内容的编码encode格式 |
| HttpResponse.content:响应内容 |
字符串编码:
| |
| response = HttpResponse(charset='gbk',content_type='text/html; charset=gbk') |
| |
| |
| |
| |
| |
| |
| |
| |
| def index(request): |
| response = HttpResponse(charset='gbk',content_type='text/html; charset=gbk') |
| response.charset='gbk' |
| |
| response.write("<p>Hello egon美男子</p>") |
| response.write("<p>Hello 林sb</p>") |
| |
| |
| response['Content-Type']='text/html; charset=gbk' |
| |
| |
| print(response.charset) |
| print(response.content) |
| return response |
| def render(request, template_name, context=None, content_type=None, status=None, using=None): |
| """ |
| Return a HttpResponse whose content is filled with the result of calling |
| django.template.loader.render_to_string() with the passed arguments. |
| """ |
| content = loader.render_to_string(template_name, context, request, using=using) |
| return HttpResponse(content, content_type, status) |
| |
| 参数: |
| 1、request:用于生成响应的请求对象,固定必须传入的第一个参数 |
| |
| 2、template_name:要使用的模板的完整名称,必须传入,render默认会去templates目录下查找模板文件 |
| |
| 3、context:可选参数,可以传入一个字典用来替换模块文件中的变量,默认是一个空字典。如果字典中的某个值是可调用的,视图将在渲染模板之前调用它。 |
| |
| 4、content_type:生成的文档要使用的MIME类型。默认为 DEFAULT_CONTENT_TYPE 设置的值。默认为'text/html' |
| |
| 5、status:响应的状态码。默认为200。 |
| |
| 6、using: 用于加载模板的模板引擎的名称。 |
综上,render的功能可以总结为:根据给定字典渲染模板文件,并返回一个渲染后的HttpResponse对象。简单示例如下
urls.py新增路由
| from django.urls import re_path |
| from app01 import views |
| |
| urlpatterns = [ |
| re_path(r'^$',views.index), |
| ] |
views.py内容如下
| from django.shortcuts import render |
| |
| def index(request): |
| return render(request, 'index.html', {'name': 'egon', 'tag': 'dsb'}) |
| |
| |
| from django.shortcuts import render |
| from django.template import loader |
| |
| def index(request): |
| t = loader.get_template('index.html') |
| dic = {'name': 'egon', 'tag': 'dsb'} |
| |
| return HttpResponse(t.render(dic, request)) |
templates目录下新增文件index.html,内容如下
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <p>{{ name }}</p> |
| <p>{{ tag }}</p> |
| </body> |
| </html> |
测试
| 返回重定向对象,返回的状态码为302,第一个参数用来指定浏览器重定向的地址,可以是 |
| |
| |
| |
| |
| |
| def index(request): |
| response=HttpResponse() |
| |
| response.status_code=302 |
| response['Location']='/register/' |
| |
| return response |
示例如下
urls.py新增路由
| from django.urls import re_path |
| from app01 import views |
| |
| urlpatterns = [ |
| re_path(r'^index/$',views.index), |
| re_path(r'^login/$', views.login), |
| |
| ] |
views.py内容如下
| from django.shortcuts import HttpResponse,redirect |
| |
| def index(request): |
| return redirect('/login/') |
| |
| |
| def login(request): |
| return HttpResponse("login page") |
测试:
启动django,在浏览器中访问url地址http://ip地址:端口号/index/,跳转到login页面
重定向转态码301与302的区别(了解)
| 一、301和302的异同。 |
| 1、相同之处: |
| 301和302状态码都表示重定向,具体点说就是浏览器在拿到服务器返回的这个状态码后会自动跳转到一个新的URL地址(浏览器会从响应头Location中获取新地址),用户看到的效果都是输入地址A后瞬间跳转到了另一个地址B |
| |
| 2、不同之处: |
| 301表示永久重定向,旧地址A的资源已经被永久地移除了,即这个资源不可访问了。 |
| 302表示临时重定向,旧地址A的资源还在,即这个资源仍然可以访问。 |
| |
| A页面永久重定向到B页面,那搜索引擎收录的就是B页面。 |
| A页面临时重定向到B页面,那搜索引擎收录的就是A页面。 |
| 从SEO层面考虑,302要好于301 |
| |
| 二、重定向原因: |
| 1、网站调整(如改变网页目录结构); |
| 2、网页被移到一个新地址; |
| 3、网页扩展名改变(如应用需要把.php改成.Html或.shtml)。 |
| 这种情况下,如果不做重定向,则用户收藏夹或搜索引擎数据库中旧地址只能让访问客户得到一个404页面错误信息,访问流量白白丧失;再者某些注册了多个域名的网站,也需要通过重定向让访问这些域名的用户自动跳转到主站点等。 |
向前端返回一个json格式字符串的两种方式
方式一:
| import json |
| |
| def my_view(request): |
| data=['egon','kevin'] |
| return HttpResponse(json.dumps(data) ) |
方式二:
| from django.http import JsonResponse |
| |
| def my_view(request): |
| data=['egon','kevin'] |
| return JsonResponse(data,safe=False) |
| |
Web应用在线上运行的过程中出现错误,是不可避免的,常见错误如
| 1、400:400 Bad Request 是由于明显的客户端错误(例如,格式错误的请求语法,太大的大小,无效的请求消息或欺骗性路由请求),服务器不能或不会处理该请求。 |
| 2、403:用户没有访问某一资源的权限 |
| 3、404:请求的url地址不存在 |
| 4、500:服务端出错 |
django框架为其中的一些常见错误提供了标准的错误页面,但它们仅限于DEBUG=True调试模式下使用,要想符合生产环境的需求,则需要我们进行自定制,配置过程如下
步骤1:在templates目录下的顶层创建错误页面400.html、403.html、404.html、500.html,注意文件内容自定义,但文件名与放置位置不可改变
templates/400.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <h1>自定义400页面</h1> |
| </body> |
| </html> |
templates/403.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <h1>自定义403页面</h1> |
| <p> |
| {{ exception }} |
| </p> |
| </body> |
templates/404.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <h1>自定义404页面</h1> |
| <p> |
| {{ exception }} |
| </p> |
| </body> |
| </html> |
templates/500.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <h1>自定义500页面</h1> |
| </body> |
| </html> |
步骤2.编写处理异常的视图
| |
| from django.views.defaults import bad_request |
| from django.views.defaults import permission_denied |
| from django.views.defaults import page_not_found |
| from django.views.defaults import server_error |
| |
| |
| |
| |
步骤3.请将 settings.py 中的 DEBUG设置为Fasle,这样,如果出现错误,就会出现我们自己定义的页面
| |
| DEBUG = False |
| |
| |
| ALLOWED_HOSTS = ['*'] |
完成上述步骤后,我们来测试一下
urls.py
| from django.urls import path |
| from app01 import views |
| |
| urlpatterns = [ |
| path('test/400',views.error_test_400), |
| path('test/403',views.error_test_403), |
| path('test/404',views.error_test_404), |
| path('test/500',views.error_test_500), |
| |
| ] |
views.py
| from django.core.exceptions import SuspiciousFileOperation |
| from django.core.exceptions import PermissionDenied |
| from django.http import Http404 |
| |
| |
| def error_test_400(request): |
| raise SuspiciousFileOperation('抛出400异常') |
| |
| def error_test_403(request): |
| raise PermissionDenied('抛出403异常') |
| |
| def error_test_404(request): |
| raise Http404('抛出404异常') |
| |
| def error_test_500(request): |
| xxx |
测试
| |
| http://127.0.0.1:8002/test/400 |
| http://127.0.0.1:8002/test/403 |
| http://127.0.0.1:8002/test/404 |
| http://127.0.0.1:8002/test/500 |
django内置的异常处理视图应该可以满足大部分Web应用的需求,但不可否认的是缺乏灵活性,比如我们想在400.html页面中也获取异常信息,此时就需要自定制400异常的处理视图了,为了满足读者日后的需求,我们在此将四个异常处理视图都自定义一下,在上例的基础上,作出如下操作
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <h1>自定义400页面</h1> |
| <p> |
| {{ exception }} |
| </p> |
| </body> |
| </html> |
| |
| from django.core.exceptions import SuspiciousFileOperation |
| from django.core.exceptions import PermissionDenied |
| from django.http import Http404 |
| |
| |
| def error_test_400(request): |
| raise SuspiciousFileOperation('抛出400异常') |
| |
| def error_test_403(request): |
| raise PermissionDenied('抛出403异常') |
| |
| def error_test_404(request): |
| raise Http404('抛出404异常') |
| |
| def error_test_500(request): |
| xxx |
| |
| |
| from django.shortcuts import render |
| |
| def my_custom_bad_request_view(request, exception): |
| ''' |
| 处理400异常的视图,必须接收两个参数 |
| |
| 参数request: |
| 参数exception: 捕获的异常值 |
| |
| 返回值: django规定该函数需要返回一个HttpResponseBadRequest类的对象 |
| 可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 400 |
| from django.views.defaults import HttpResponseBadRequest |
| |
| 明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可 |
| ''' |
| print('====>400') |
| response = render(request, '400.html', {'exception': exception}) |
| response.status_code = 400 |
| return response |
| |
| def my_custom_permission_denied_view(request, exception): |
| ''' |
| 处理403异常的视图,必须接收两个参数 |
| |
| 参数request: |
| 参数exception: 捕获的异常值 |
| |
| 返回值: django规定该函数需要返回一个HttpResponseForbidden类的对象 |
| 可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 403 |
| from django.views.defaults import HttpResponseForbidden |
| |
| 明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可 |
| ''' |
| print('====>403') |
| |
| response = render(request, '403.html', {'exception': exception}) |
| response.status_code = 403 |
| return response |
| |
| def my_custom_page_not_found_view(request, exception): |
| ''' |
| 处理404异常的视图,必须接收两个参数 |
| |
| 参数request: |
| 参数exception: 捕获的异常值 |
| |
| 返回值: django规定该函数需要返回一个HttpResponseNotFound类的对象 |
| 可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 404 |
| from django.views.defaults import HttpResponseNotFound |
| |
| 明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可 |
| ''' |
| print('====>404') |
| |
| response = render(request, '404.html', {'exception': exception}) |
| response.status_code = 404 |
| return response |
| |
| def my_custom_error_view(request): |
| ''' |
| 处理500异常的视图,只接收一个参数 |
| |
| 返回值: django规定该函数需要返回一个HttpResponseServerError类的对象 |
| 可以查看该类的源码,该类是HttpResponse的子类,并设置status_code = 500 |
| from django.views.defaults import HttpResponseServerError |
| |
| 明白了上述原理,为了方便我们渲染自定义页面,此处我们采用render返回,设置好status_code即可 |
| ''' |
| print('====>500') |
| |
| response = render(request, '500.html', ) |
| response.status_code = 500 |
| return response |
| from django.urls import path |
| from app01 import views |
| |
| urlpatterns = [ |
| path('test/400',views.error_test_400), |
| path('test/403',views.error_test_403), |
| path('test/404',views.error_test_404), |
| path('test/500',views.error_test_500), |
| |
| ] |
| |
| |
| handler400 = 'app01.views.my_custom_bad_request_view' |
| handler403 = 'app01.views.my_custom_permission_denied_view' |
| |
| handler404 = 'app01.views.my_custom_page_not_found_view' |
| handler500 = 'app01.views.my_custom_error_view' |
| |
| DEBUG = False |
| |
| ALLOWED_HOSTS = ['*'] |
| |
| http://127.0.0.1:8002/test/400 |
| http://127.0.0.1:8002/test/403 |
| http://127.0.0.1:8002/test/404 |
| http://127.0.0.1:8002/test/500 |
官网地址:https://docs.djangoproject.com/en/3.0/topics/class-based-views/
视图是可调用的,用来处理请求(request)并且返回响应(response),django的视图有两种形式:FBV和CBV
| 1、FBV基于函数的视图(Function base views),我们之前一直介绍的都是FBV |
| |
| 2、CBV基于类的视图(Class base views),我们本节主要介绍它 |
在早期,人们意识到视图开发的过程中存在一些常见的语法和模式,于是引入基于函数的通用视图来抽象这些模式,并简化了常见情况下的视图开发。因此,刚开始的时候只有FBV,django所做的事情就是向你定义的视图函数传递一个HttpRequest,并且希望返回一个HttpResponse。
随着进一步的发展,人们发现,基于函数的通用视图是有问题的,问题在于它很好地覆盖了简单的情况,但针对稍微复杂的场景,它没有办法在某些配置项之外进行扩展或自定义,从而极大地限制了它在许多实际应用程序中的实用性。而考虑到扩展性与自定义,这正是面向对象技术的强大之处,于是诞生了CBV。
基于类的通用视图与基于函数的通用视图目的都一样,都是为了让视图开发更加容易。但是CBV的解决方案是通过使用mixins,并且django内置了一系列通用视图作为工具集提供给我们继承使用,从而使得基于类的通用视图比基于函数的通用视图更具扩展性和灵活性。
如果你之前尝试过FBV,但发现它有缺陷,CBV是一种新的解决方案,而不是单纯地换了一种定义视图的形式,如果CBV只是视图的另外一种定义形式而已,那岂不是很无聊,连详细介绍的必要都没有了。
为了让读者快速掌握CBV的使用,我们只能从简单情况下举例,这看起来会是增加了代码的复杂度,读者可能会对其嗤之以鼻,其实不然,在读者日后遇到复杂的情况时,唯有CBV可以进行更高级的设计
| 综上,CBV并非是FBV的完全替代品,但相对于FBV,CBV确实有一些差异和优势,详解下一小节 1、针对不同的HTTP请求方法(如GET、POST),CBV可以分别对应到专门的处理方法上,无需采用条件分支,代码更加精简 |
| |
| 2、CBV采用面向对象技术,比如mixins(多重继承)可用于将代码转换成可重用的组件。 |
CBV允许我们使用不同的实例方法来响应不同的HTTP请求方法,而不是像FBV那样使用条件分支代码
| from django.http import HttpResponse |
| |
| def my_view(request): |
| if request.method == 'GET': |
| |
| return HttpResponse('GET result') |
| elif request.method == 'POST': |
| |
| return HttpResponse('POST result') |
FBV关于url的配置略
| from django.http import HttpResponse |
| from django.views import View |
| |
| class MyView(View): |
| def get(self, request): |
| |
| return HttpResponse('GET result') |
| |
| def post(self, request): |
| |
| return HttpResponse('POST result') |
所有基于类的视图都必须继承View类,该类用来处理视图关联到URL的操作,具体分析如下:
由于django的URL解析器期望发送request以及相关参数给一个可调用的函数,而不是一个类,所以基于类的视图有一个as_view()类方法(该方法继承自父类View),调用该方法会返回URL解析器所期望的函数,该函数会在请求到达与关联模式匹配的URL时调用,就像调用视图函数一个样子。查看源码会发现调用该函数首先会创建一个MyView类的实例,然后
1、调用self.setup()方法,用于初始化一系列属性。
2、之后调用self.dispatch(),该方法查看request以确定本次请求是GET还是POST等,如果定义了匹配方法,则将请求转发给匹配方法;如果没有,则引发HttpResponseNotAllowed。本例中将调用我们自定义的get与post方法来响应请求*
*
ps:基于面向对象的知识,上述方法,如果子类MyView未重写,则去父类View中寻找
urls.py的配置如下
| |
| from django.urls import path |
| from myapp.views import MyView |
| |
| urlpatterns = [ |
| path('about/', MyView.as_view()), |
| ] |
虽然最精简的CBV不需要设置任何类属性就可以使用,但类属性确实在许多基于CBV的设计中非常有用,我们又两种方法可以配置或设置类属性
方法一:python标准的套路,在子类中重写/覆盖父类的属性和方法
| from django.views import View |
| |
| class GreetingView(View): |
| greeting = "Good Day" |
| |
| def get(self, request): |
| return HttpResponse(self.greeting) |
| |
| |
| class MorningGreetingView(GreetingView): |
| greeting = "Morning to ya" |
| |
| |
| from django.urls import path |
| from myapp.views import MyView |
| |
| urlpatterns = [ |
| path('about/', MorningGreetingView.as_view()), |
| ] |
方法二:在URL配置里为as_view()方法传递关键字参数来设置类属性
| urlpatterns = [ |
| path('about/', GreetingView.as_view(greeting="G'day")), |
| ] |
| |
| |
| from django.views import View |
| |
| class GreetingView(View): |
| greeting = "Good Day" |
| |
| def get(self, request): |
| return HttpResponse(self.greeting) |
| |
| |
| urlpatterns = [ |
| |
| path('about/', GreetingView.as_view(greeting="G'day")), |
| |
| path('about1/', views.GreetingView.as_view()), |
| ] |
Mixins机制指的是子类混合(mixin)不同类的属性和功能,比如在django内置的通用视图里有一个mixin类叫TemplateResponseMixin,它的主要目的就是来定义一个方法render_to_response()。当该类和View类组合到一起时,就得到了TemplateView类,TemplateView类分发请求到相应的方法上(通过执行继承自View类的dispath()方法),并且使用render_to_response()方法来找到template_name属性读取指定的模板从而返回一个TemplateResponse对象(通过执行继承自TemplateResponseMixin类的render_to_response()方法)
from django.views.generic.base import TemplateResponseMixin
这就极大利用了面向对象重之多继承技术来重用代码的优点,但是鱼与熊掌不可兼得,需要付出的代价就是,在多继承背景下,我们定义的mixin类越来越多,代码的可读性仍然会变得越来越差。
关于mixins的用法详见官网https://docs.djangoproject.com/en/3.0/topics/class-based-views/mixins/,篇幅问题此处不再累述。
需要强调的是,CBV可以继承多个mixins类(推荐写在左边),但是只能继承一个父类,该父类只能是View类或者其子/子孙类(推荐写在右面),同时继承两个父类将会导致你的子类无法按照预期那样工作。
django内置了基本的CBV可以满足相当多的应用,所有的视图都继承自View类,View类负责处理将视图链接到url、HTTP方法调度和其他常见功能。内置的CBV如RedirectView提供了一个HTTP重定向,TemplateView扩展了基类,使其同时可以渲染一个模板。
from django.views.generic import View,TemplateView,RedirectView
以TemplateView为例来介绍内置CBV的使用。最直接使用方式就是直接在URL配置中创建,如果只想改变该CBV的几个属性,你可以为as_view传递参数,如下
| from django.urls import path |
| from django.views.generic import TemplateView |
| |
| urlpatterns = [ |
| path('about/', TemplateView.as_view(template_name="about.html")), |
| ] |
一样的道理,如果我们想要重定向一个地址,可以设置RedirectView的url属性
| from django.urls import path |
| from django.views.generic import RedirectView |
| from app01 import views |
| |
| urlpatterns = [ |
| path('index/', views.index), |
| path('about/', RedirectView.as_view(url="/index/")), |
| ] |
第二种更有效的使用内置CBV的方式就是是从现有视图继承并重写子类中的属性(例如template_name)或方法(例如get_context_data),以提供新的值或方法。
例如,我们的需求是要定制一个视图,该视图专门用来显示/渲染模板about.html。Django有一个内置的CBV即TemplateView可以实现,因此我们可以继承它,并重写模板名称template_name以及用来获取渲染模板数据的方法get_context_data
| |
| from django.views.generic.base import TemplateView |
| |
| class HomePageView(TemplateView): |
| template_name = "home.html" |
| |
| def get_context_data(self, **kwargs): |
| context = super().get_context_data(**kwargs) |
| |
| context['name'] = "egon" |
| context['age'] = "18" |
| context['title'] = "dsb" |
| |
| return context |
url.py配置如下
| from django.urls import path |
| from app01 import views |
| |
| urlpatterns = [ |
| path('home/', views.HomePageView.as_view()), |
| ] |
templates/home.html
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| |
| <h1>{{ name }}</h1> |
| <h1>{{ age }}</h1> |
| <h1>{{ title }}</h1> |
| |
| </body> |
| </html> |
更多内置CBV详见:
https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-display/
请看完后续章节中关于表单的使用之后再来读本小节 FBV处理表单代码如下
| from django.http import HttpResponseRedirect |
| from django.shortcuts import render |
| |
| from .forms import MyForm |
| |
| def myview(request): |
| if request.method == "POST": |
| form = MyForm(request.POST) |
| if form.is_valid(): |
| |
| return HttpResponseRedirect('/success/') |
| else: |
| form = MyForm(initial={'key': 'value'}) |
| |
| return render(request, 'form_template.html', {'form': form}) |
CBV处理表单代码如下
| from django.http import HttpResponseRedirect |
| from django.shortcuts import render |
| from django.views import View |
| |
| from .forms import MyForm |
| |
| class MyFormView(View): |
| form_class = MyForm |
| initial = {'key': 'value'} |
| template_name = 'form_template.html' |
| |
| def get(self, request, *args, **kwargs): |
| form = self.form_class(initial=self.initial) |
| return render(request, self.template_name, {'form': form}) |
| |
| def post(self, request, *args, **kwargs): |
| form = self.form_class(request.POST) |
| if form.is_valid(): |
| |
| return HttpResponseRedirect('/success/') |
| |
| return render(request, self.template_name, {'form': form}) |
上述只是一个很小的例子,但是我们可以定制这个视图,比如覆盖任意的类属性(例如form_class),或者为as_view()传参,或者继承MyForView并重写其中方法。
更多表单处理详见:
https://docs.djangoproject.com/en/3.0/topics/class-based-views/generic-editing/
之前我们介绍过,若想要为CBV添加额外的功能,可以采用mixins机制,但除此之外,还可以通过添加装饰器的方法实现。有两个地方可以添加装饰
| |
| from django.contrib.auth.decorators import login_required, permission_required |
| from django.views.generic import TemplateView |
| |
| from .views import VoteView |
| |
| urlpatterns = [ |
| path('about/', login_required(TemplateView.as_view(template_name="secret.html"))), |
| path('vote/', permission_required('polls.can_vote')(VoteView.as_view())), |
| ] |
示例:
views.py如下
| |
| from django.shortcuts import render, HttpResponse |
| from django.views import View |
| import time |
| |
| |
| def timer(func): |
| def wrapper(request, *args, **kwargs): |
| start = time.time() |
| ret = func(request, *args, **kwargs) |
| print("函数执行的时间是{}".format(time.time() - start)) |
| return ret |
| |
| return wrapper |
| |
| |
| class LoginView(View): |
| def dispatch(self, request, *args, **kwargs): |
| obj = super().dispatch(request, *args, **kwargs) |
| return obj |
| |
| def get(self, request): |
| return render(request, "login.html") |
| |
| def post(self, request): |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| if username == 'egon' and password == '123': |
| return HttpResponse('登录成功') |
| else: |
| return HttpResponse('账号或密码错误') |
urls.py如下
| from django.urls import path |
| from app01.views import * |
| |
| urlpatterns = [ |
| path('login/',timer(LoginView.as_view())), |
| ] |
login.html如下
| <!DOCTYPE html> |
| <html lang="en"> |
| <head> |
| <meta charset="UTF-8"> |
| <title>Title</title> |
| </head> |
| <body> |
| <form action="" method="post"> |
| {% csrf_token %} |
| <p> |
| 用户名: |
| <input type="text" name="username"> |
| </p> |
| <p> |
| 密码: |
| <input type="password" name="password"> |
| </p> |
| <p> |
| <input type="submit" value="登录"> |
| </p> |
| </form> |
| </body> |
| </html> |
装饰as_views()相当于给视图类的中的所有方法(例如get、post)都加上了装饰器
类方法与独立函数不完全相同,所以你不能简单地把函数装饰器加到类方法上,这就用到了method_decorator方法,该方法会将函数装饰器转换为方法装饰器,然后才可以用来装饰类方法,例如:
装饰dispatch方法,基于CBV,当请求过来后会先执行dispatch()这个方法进而分发到对用的get、post方法上,所以如果需要批量装饰处理请求的方法(如get,post等)可以为dispatch方法添加装饰,这与为as_view()添加装饰器效果时一样的,如下
| |
| from django.shortcuts import render, HttpResponse |
| from django.views import View |
| from django.utils.decorators import method_decorator |
| import time |
| |
| |
| def timer(func): |
| def wrapper(request, *args, **kwargs): |
| start = time.time() |
| ret = func(request, *args, **kwargs) |
| print("函数执行的时间是{}".format(time.time() - start)) |
| return ret |
| |
| return wrapper |
| |
| |
| class LoginView(View): |
| |
| @method_decorator(timer) |
| def dispatch(self, request, *args, **kwargs): |
| obj = super().dispatch(request, *args, **kwargs) |
| return obj |
| |
| def get(self, request): |
| return render(request, "login.html") |
| |
| def post(self, request): |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| if username == 'egon' and password == '123': |
| return HttpResponse('登录成功') |
| else: |
| return HttpResponse('账号或密码错误') |
ps:url配置中正常调用即可,无需重复装饰,下同
| from django.urls import path |
| from app01.views import * |
| urlpatterns = [ |
| path('login/',LoginView.as_view()), |
| ] |
我们当然可以单独装饰不同的方法,如
| ......省略代码,同上 |
| |
| class LoginView(View): |
| def dispatch(self, request, *args, **kwargs): |
| obj = super().dispatch(request, *args, **kwargs) |
| return obj |
| |
| @method_decorator(timer) |
| def get(self, request): |
| return render(request, "login.html") |
| |
| def post(self, request): |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| if username == 'egon' and password == '123': |
| return HttpResponse('登录成功') |
| else: |
| return HttpResponse('账号或密码错误') |
更简洁一点,我们可以把装饰器加在类上,通过name参数指定要装饰的方法,如下
| |
| @method_decorator(timer,name='get') |
| |
| class LoginView(View): |
| def dispatch(self, request, *args, **kwargs): |
| obj = super().dispatch(request, *args, **kwargs) |
| return obj |
| |
| def get(self, request): |
| return render(request, "login.html") |
| |
| def post(self, request): |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| if username == 'egon' and password == '123': |
| return HttpResponse('登录成功') |
| else: |
| return HttpResponse('账号或密码错误') |
如果有多个装饰器需要添加,可以定义一个列表或者元组将其存放起来,然后按照下述方式指定
| from django.shortcuts import render, HttpResponse |
| from django.views import View |
| from django.utils.decorators import method_decorator |
| import time |
| |
| |
| def deco1(func): |
| def wrapper(request, *args, **kwargs): |
| print('===>deco1') |
| ret = func(request, *args, **kwargs) |
| return ret |
| return wrapper |
| |
| def deco2(func): |
| def wrapper(request, *args, **kwargs): |
| print('===>deco2') |
| ret = func(request, *args, **kwargs) |
| return ret |
| return wrapper |
| |
| |
| decorators = [deco1, deco2] |
| |
| @method_decorator(decorators,name='get') |
| class LoginView(View): |
| def dispatch(self, request, *args, **kwargs): |
| obj = super().dispatch(request, *args, **kwargs) |
| return obj |
| |
| def get(self, request): |
| return render(request, "login.html") |
| |
| def post(self, request): |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| if username == 'egon' and password == '123': |
| return HttpResponse('登录成功') |
| else: |
| return HttpResponse('账号或密码错误') |
上述装饰器会按照列表规定的顺序依次执行装饰器,即先执行deco1,然后deco2。。。与下述添加方式是等同的
| ......省略代码,同上 |
| |
| @method_decorator(deco1,name='get') |
| @method_decorator(deco2,name='get') |
| class LoginView(View): |
| def dispatch(self, request, *args, **kwargs): |
| obj = super().dispatch(request, *args, **kwargs) |
| return obj |
| |
| def get(self, request): |
| return render(request, "login.html") |
| |
| def post(self, request): |
| username = request.POST.get('username') |
| password = request.POST.get('password') |
| if username == 'egon' and password == '123': |
| return HttpResponse('登录成功') |
| else: |
| return HttpResponse('账号或密码错误') |
假设我们写了一个图书管理系统,如果有人想使用视图作为API通过HTTP协议访问我们的图书库。API客户端想要获取最新的图书数据,需要时不时地发起连接并下载自上次访问以来新出版的图书数据。但是如果自上次访问完后,就没有新的图书出版,那么从数据库查询图书信息,渲染一个完整的reponse并发送给客户端将白白耗费CPU时间和带宽,此时,我们最好的方式,就是在得知有新书出版时才发起一次请求完整数据的的API调用,这就用到了head方法来查询是否有数据更新
urls.py如下
| from django.urls import path |
| from app01.views import BookListView |
| |
| urlpatterns = [ |
| path('books/', BookListView.as_view()), |
| ] |
views.py
| from django.shortcuts import HttpResponse |
| from django.views.generic import ListView |
| from app01.models import Book |
| |
| class BookListView(ListView): |
| model = Book |
| |
| def head(self, *args, **kwargs): |
| last_book = self.get_queryset().latest('publication_date') |
| response = HttpResponse() |
| |
| response['Last-Modified'] = last_book.publication_date.strftime('%Y-%m-%d %X') |
| return response |
| |
| def get(self,request): |
| ''' |
| 返回完整的数据,此处我们简单地用返回所有书籍名字代表将要返回的完整数据 |
| ''' |
| books=Book.objects.all() |
| names='|'.join([book.name for book in books]) |
| return HttpResponse(names) |
models.py
| from django.db import models |
| |
| |
| |
| class Book(models.Model): |
| name=models.CharField(max_length=30) |
| publication_date=models.DateTimeField(auto_now_add=True) |
settings.py
| TIME_ZONE = 'Asia/Shanghai' |
| USE_TZ = False |
编写脚本进行测试
| import requests |
| |
| |
| response=requests.head('http://127.0.0.1:8002/books/') |
| response.headers['Last-Modified'] |
| |
| |
| response=requests.get('http://127.0.0.1:8002/books/') |
| print(response.text) |