2. Django的V和M
2.1 视图(views)
- 视图是Django程序中处理后端业务逻辑的地方。
- Django的视图是定义在子应用的
views.py
中的。 - Django的视图分为 函数视图 和 类视图 两种。
2.1.1 函数视图
1. 定义函数视图
函数视图定义方式:
"""
1. 函数视图它是一个标准的Python函数。
2. 函数视图中,第一个参数必须定义:第一个参数为请求对象,用于接收用户发送的请求报文。
3. 函数视图中,必须返回响应对象:用于构造响应报文,并响应给用户。
4. 说明:
请求对象:HttpRequest() 对应的对象
响应对象:HttpResponse() 对应的对象
"""
from django.shortcuts import render
from django import http
# Create your views here.
def register(request):
"""
用户注册函数视图
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
# 响应数据
return http.HttpResponse('这里假装返回注册页面')
2. 访问函数视图
提示:
- 我们定义好的函数视图,需要用户能够访问到。
- 用户如何访问函数视图?
- 通过网络地址向Django程序发请求,即可访问到函数视图
问题:
- 如何保证用户发送的请求,能够访问到对应的函数视图?
解决:
- 路由:使用路由匹配请求地址,每匹配成功一个就执行对应的函数视图逻辑
- 定义路由的方法:path()、re_path()、url()
需求:
- 用户通过网络地址**
http://127.0.0.1:8000/users/register/
**访问用户注册视图
3. 访问函数视图:需求实现 --> path()
1. 新建子路由文件
- 在**
子应用
中新建一个urls.py
**文件用于定义该应用的所有路由信息
2. 注册子路由
- 在**
子应用/urls.py
**文件中定义路由信息
from django.urls import path
from . import views
# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息
urlpatterns = [
# 函数视图路由语法:
# path('网络地址非正则表达式', 函数视图名),
# 第一个参数写的是资源路径
# 用户注册:http://127.0.0.1:8000/users/register/
path('users/register/', views.register),
# 除了path 还有re_path() 能使用正则表达式的资源路径 要写好^和$
]
3. 注册总路由
- 在工程总路由**
工程同名目录/urls.py
**中包含子应用的路由数据
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
# 自带的后台管理系统的总路由:可以忽略
path('admin/', admin.site.urls),
# 总路由包含子路由语法
# path('网络地址前缀/', include('子应用.urls')),
# 或者
# path('', include('子应用.urls')),
# 用户模块:http://127.0.0.1:8000/users/register/
path('', include('users.urls')),
]
总路由说明:
- 一个子应用对应一个总路由。
- 总路由中,使用**
include()
来将users子应用
**里的所有路由都包含进工程总路由中。
4. 启动运行测试
重新启动django程序
python manage.py runserver
使用postman进行请求测试: http://127.0.0.1:8000/users/register/
拓展
r
转义字符
一般会在路由地址之前添加一个r来防止有\
造成地址出错
path()和re_path()
django里一般情况下使用path已经能解决大部分路由路径需求,虽然不能使用正则表达式,但是可以使用路径转换器来达成和正则差不多的效果且代码可读性提高。
但是依旧有需要使用正则的时候,此时我们就使用re_path()来匹配路由路径
4. 路由匹配函数视图逻辑
需求:
- 用户向地址**
http://127.0.0.1:8000/users/register/
**发送GET请求,用来获取注册页面。 - 用户向地址**
http://127.0.0.1:8000/users/register/
**发送POST请求,用来实现注册逻辑。
需求实现:
def register(request):
"""
用户注册函数视图
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
# 获取请求方法,判断是GET还是POST请求
if request.method == 'GET':
# 处理GET请求,返回注册页面
return HttpResponse('这里假装返回注册页面')
else:
# 处理POST请求,实现注册逻辑
return HttpResponse('这里假装实现注册逻辑')
函数视图问题说明:
- 当遇到视图对应的同一个路径,提供了多种不同HTTP请求方式的支持时,便需要在一个函数中编写不同的业务逻辑,代码可读性与复用性都很差。
解决方案:
- 类视图
2.1.2 类视图
类视图定义方式:
1. 类视图它是一个标准的Python类。
2. 类视图需要继承自Django提供的父类视图View。
3. 在类视图中,
3.1 需要定义跟请求方法同名的函数来对应不同请求方式
3.2 在请求方法同名的函数中,还必须定义一个接收请求的参数(同函数视图)
3.3 在请求方法同名的函数中,还必须返回一个响应对象(同函数视图)
from django.views import View
class RegisterView(View):
"""用户注册类视图
http://127.0.0.1:8000/users/register/
"""
def get(self, request):
"""
处理GET请求,返回注册页面
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
return http.HttpResponse('这里假装返回注册页面')
def post(self, request):
"""
处理POST请求,实现注册逻辑
:param request: 请求对象,包含了请求报文信息
:return: 响应对象,用于构造响应报文,并响应给用户
"""
return http.HttpResponse('这里假装实现注册逻辑')
类视图的好处:
- 代码可读性好
- 类视图相对于函数视图有更高的复用性, 如果其他地方需要用到某个类视图的某个特定逻辑,直接继承该类视图即可。
1. 访问类视图
说明:
- 类视图的访问和函数视图的访问是一模一样的。
- 类视图的访问也是使用路由匹配请求地址,每匹配成功一个就执行对应的类视图逻辑
需求:
- 用户向地址**
http://127.0.0.1:8000/users/register/
**发送GET请求,用来获取注册页面。 - 用户向地址**
http://127.0.0.1:8000/users/register/
**发送POST请求,用来实现注册逻辑。
2. 访问类视图:需求实现 --> path()
1. 注册子路由
- 在**
子应用/urls.py
**文件中定义路由信息- 由于当前代码还是编写在users子应用中的,所以总路由注册过一次之后,不用再注册
from django.urls import path
from . import views
# urlpatterns是被Django自动识别的路由列表变量:定义该应用的所有路由信息
urlpatterns = [
# 类视图路由语法:
# path('网络地址正则表达式', 类视图.as_view()),
# 用户注册:http://127.0.0.1:8000/users/register/
path('users/register/', views.RegisterView.as_view()),
]
2. 启动运行测试
- 2.1 注释CSRF中间件
- Django默认开启了CSRF防护,会对非GET请求(POST, PUT, DELETE)进行CSRF防护验证,在测试时可以关闭CSRF防护机制
- 关闭CSRF防护机制是在
settings.py
文件中注释掉CSRF中间件
# 中间件
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 为保证非GET请求(POST, PUT, DELETE)可以正常接收,该中间件需要注释掉
# 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
- 2.2 重新启动Django程序
python manage.py runserver
- 2.3 使用postman进行请求测试:
http://127.0.0.1:8000/users/register/
3. 路由匹配类视图逻辑
4. as_view()底层原理(仅做了解)
class View:
"""
Intentionally simple parent class for all views. Only implements
dispatch-by-method and simple sanity checking.
# 为所有视图定义简单的父类,只实现了请求方法分派和简单的完整性检查。
"""
# 定义Django允许接收的请求方法
http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
def __init__(self, **kwargs):
"""
Constructor. Called in the URLconf; can contain helpful extra
keyword arguments, and other things.
# 类视图的初始化构造函数,创建类视图对象时会被调用,并可以接收额外的参数
"""
# Go through keyword arguments, and either save their values to our
# instance, or raise an error.
for key, value in kwargs.items():
setattr(self, key, value)
# 这是我们定义的类使用的父类方法 传进去Registerview
@classonlymethod
def as_view(cls, **initkwargs):
"""Main entry point for a request-response process.
# 请求-响应过程的主要入口点.
"""
for key in initkwargs:
# 遍历as_view()接收的参数
# 省略......
def view(request, *args, **kwargs):
"""准备一个函数视图,将来作为as_view()的返回值,并用于路由匹配"""
# 初始化类视图对象
# 这里cls就是我们传参进去Registerview 传进去进行一轮判断里面带的是get还是post
self = cls(**initkwargs)
if hasattr(self, 'get') and not hasattr(self, 'head'):
self.head = self.get
# 将路由中传入的参数,绑定到类视图对象中
self.setup(request, *args, **kwargs)
# 检查类视图是否完整:类视图中必须要有'request' attribute
if not hasattr(self, 'request'):
raise AttributeError(
"%s instance has no 'request' attribute. Did you override "
"setup() and forget to call super()?" % cls.__name__
)
# 调用请求分发的方法(最核心):将请求分发给跟请求方法同名的函数
return self.dispatch(request, *args, **kwargs)
view.view_class = cls
view.view_initkwargs = initkwargs
# take name and docstring from class
update_wrapper(view, cls, updated=())
# and possible attributes set by decorators
# like csrf_exempt from dispatch
update_wrapper(view, cls.dispatch, assigned=())
# 最后返回的是闭包函数view
return view
def setup(self, request, *args, **kwargs):
"""Initialize attributes shared by all view methods.
# 初始化所有视图方法共享的属性:将路由中传入的参数,绑定到类视图对象中
"""
self.request = request
self.args = args
self.kwargs = kwargs
def dispatch(self, request, *args, **kwargs):
# Try to dispatch to the right method; if a method doesn't exist,
# defer to the error handler. Also defer to the error handler if the
# request method isn't on the approved list.
# 尽量采用正确的调度方法;如果方法不存在,请遵从错误处理程序。如果请求方法不在批准的列表中,也遵从错误处理程序。
# 先判断客户端的请求方法是否在允许接收的方法列表中
if request.method.lower() in self.http_method_names:
# 如果客户端的请求方法在允许接收的方法列表中,
# 取出类视图对象中的跟请求方法同名的函数名,赋值给handler
# 比如:当前客户端发送的请求,请求方法是GET,那么,handler=get
handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
else:
# 如果客户端的请求方法不在允许接收的方法列表中,遵从错误处理程序
handler = self.http_method_not_allowed
# 如果请求分发没有问题,那么就去调用该跟请求分发同名的函数
# 如果当前客户端发送的请求,请求方法是GET
# handler(request, *args, **kwargs)等价于get(request, *args, **kwargs)
# 如果handler()调用成功,那么跟请求分发同名的函数就会被调用执行
return handler(request, *args, **kwargs)
def http_method_not_allowed(self, request, *args, **kwargs):
"""错误处理程序:请求方法不匹配时,响应的错误信息"""
logger.warning(
'Method Not Allowed (%s): %s', request.method, request.path,
extra={
'status_code': 405, 'request': request}
)
return HttpResponseNotAllowed(self._allowed_methods())
5. 类视图添加扩展类
提示:
- 使用面向对象多继承的特性,可以给类视图定义扩展类。
- 在扩展类中,可以定义想要向类视图补充的方法。
- 类视图继承这些扩展类作为父类,便可实现代码复用。
- 比如我们以前在数据库定义过的打印查询内容方法,我们可以将其做成一个扩展类,以后视图里就能使用这个扩展类的方法。
示例:
class ListModelMixin(object):
"""list扩展类 """
def list(self, request, *args, **kwargs):
pass
class CreateModelMixin(object):
"""create扩展类 """
def create(self, request, *args, **kwargs):
pass
class TestMixinView(CreateModelMixin, ListModelMixin, View):
"""同时继承两个扩展类,复用list和create方法"""
def get(self, request):
self.list(request)
pass
def post(self, request):
self