8.Django(FBV与CBV,源码的深入研、装饰器)
FBV:function base view 就是在视图里使用函数处理请求
CBV:class base view 就是在视图里使用类处理请求
CBV在代码的拓展性以及维护性更胜一筹
-
CBV讲解
需求:用CBV写一个简单的登录业务。显示get请求一个登录页面,在通过post请求提交数据,返回一个字符串。
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
def get(self, request):
return render(request, 'login.html')
def post(self, request):
print(request.POST.get('username'))
print(request.POST.get('password'))
return HttpResponse('登录成功')urls:
url(r'^login/', views.LoginView.as_view()),
-
CBV定义类的属性
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
name = '牧羊小董'
def get(self, request):
return render(request, 'login.html')
def post(self, request):
print(request.POST.get('username'))
print(request.POST.get('password'))
return HttpResponse(f'{self.name}登录成功')
# 我们在类中定义一些累的属性在方法中我是可以使用的。 -
CBV定义类的其他方法
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
name = '牧羊小董'
content = '随便写的一些内容'
def get(self, request):
self.f1()1
return render(request, 'login.html')
def post(self, request):
print(request.POST.get('username'))
print(request.POST.get('password'))
return HttpResponse(f'{self.name}登录成功')
def f1(self):
print(666)
self.content = '这是一个坑'
print(777)
# 我们在类中定义了一些除请求方法之外的一些方法,可以使用,但是默认情况下不能在这些方法中更改对象的属性 -
CBV在类中定义请求方法
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
name = '牧羊小董'
content = '随便写的一些内容'
def get(self, request):
self.age = 18
return render(request, 'login.html')
def post(self, request):
print(request.POST.get('username'))
print(request.POST.get('password'))
return HttpResponse(f'{self.name}登录成功')
# 在类中定的请求方法中,默认也不允许对self对象封装属性。 -
CBV的这种形式也可以使用无名分组或者有分组
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
def get(self, request, year):
print(year)
return render(request, 'login.html')
urls:
url(r'^login/(\d{4})', views.LoginView.as_view()), -
CBV可以给类的属性赋值
url(r'^login/', views.LoginView.as_view(name='牧羊小董')),
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
name = '花花'
def get(self, request):
return render(request, 'login.html')
def post(self, request):
print(request.POST.get('username'))
print(request.POST.get('password'))
return HttpResponse(f'{self.name}登录成功')
我们可以通过url的as_view()方法覆盖CBV对应的类的属性,但是有个前提,CBV中的类必须定义这个属性。
源码的深入研究(重要)
1、 再次看一下views视图中的内容
from django.shortcuts import render, HttpResponse
from django.views import View
class LoginView(View):
# name = '牧羊小董'
def get(self, request):
return render(request, 'login.html')
def post(self, request):
print(request.POST.get('username'))
print(request.POST.get('password'))
return HttpResponse(f'{self.name}登录成功')
2、研究urls
urlpatterns = [ # FBV # url(r'^admin/', admin.site.urls), # url(r'^login/', views.login), # url(r'^home/', views.home), # 上面三个FBV的形式,只要匹配上对应的路径,就会自动执行对应的函数 url(正则表达式, 函数名, {键值对}, 别名) 前提:无论是FBV还是CBV 对于url这里远原理以及各式都是一样的。 # CBV url(r'^login/(\d{4})', views.LoginView.as_view(name='牧羊小董')), ]
前提:无论是FBV还是CBV 对于url这里远原理以及各式都是一样的。
按照我们的前提,views.LoginView.as_view() 必须返回一个函数名,所以, 当你们在进行路由匹配时,
第一步要先执行views.LoginView.as_view()
分析:views.LoginView 类名.as_view() 方法
as_view()⽅法应该是个什么⽅法?要不就是类⽅法,要不就是静态⽅法。
类名.方法 先从本类中寻找,本类中只有get、post方法,所以要从父类View 中寻找执行as_view()
3、研究父类View中的as_view() 方法
class View(object): """ Intentionally simple parent class for all views. Only implements dispatch-by-method and simple sanity checking. """ 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 six.iteritems(kwargs): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs 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=()) return view 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 = getattr(self, request.method.lower(), self.http_method_not_allowed) else: handler = self.http_method_not_allowed 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 http.HttpResponseNotAllowed(self._allowed_methods()) def options(self, request, *args, **kwargs): """ Handles responding to requests for the OPTIONS HTTP verb. """ response = http.HttpResponse() response['Allow'] = ', '.join(self._allowed_methods()) response['Content-Length'] = '0' return response def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)]
先研究as_view方法
执行as_view方法,并且将LoginView类名传递给了此方法的cls。
@classonlymethod def as_view(cls, **initkwargs): """ Main entry point for a request-response process. """ for key in initkwargs: if key in cls.http_method_names: raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs 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=()) return view
我们将as_view方法分成三部分:
研究第一部分:
第一部分通过源码解释:可能是根请求与响应相关
Main entry point for a request-response process. 翻译: 请求-响应过程的主要入口点。
我们之前写过一个url:
url(r'^login/(\d{4})', views.LoginView.as_view(name='牧羊小董')),
所以,name='牧羊小董' 就会传递给initkwargs={'name':'牧羊小董'}
for key in initkwargs: if key in cls.http_method_names: # 如果key在这个列表中,我就主动抛出异常,言外之意,当我通过url给此类属性时,不能出现此列表中的名字,出现就报错 raise TypeError("You tried to pass in the %s method name as a " "keyword argument to %s(). Don't do that." % (key, cls.__name__)) # 上述判断 当我通过url给此类覆盖或者创建属性时,不能出现此列表中的名字,出现就报错。 可以⾃⼰测试⼀# 下 url(r'^login/(\d{4})',views.LoginView.as_view(delete='覆盖')), if not hasattr(cls, key): # cls = LoginView hasattr(LoginView,'name') raise TypeError("%s() received an invalid keyword %r. as_view " "only accepts arguments that are already " "attributes of the class." % (cls.__name__, key)) 测试: url(r'^login/(\d{4})',views.LoginView.as_view(name='牧羊小董')), TypeError: LoginView() received an invalid keyword 'name'. as_view only accepts arguments that are already attributes of the class. #上述判断 就是我们之前讲的坑,我们可以通过url的as_view() 方法覆盖CBV对应的类的属性,但是有个前提,CBV中的类必须定义这个属性。
第二部分:
def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) view.view_class = cls view.view_initkwargs = initkwargs
他给函数名封装了两个属性,python中一切皆对象,函数也是对象,函数这个对象是function这个类实例化出来的。举个例子:
def func(): print(666) # print(type(func)) # <class 'function'> 函数这个对象是从function类实例化出来的 func.hobby = '篮球' print(func.hobby)
源码中函数下面还有两个执行的函数,跟主线没关系,跟装饰器有关,在此不做研究
# 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=()) # 这个大概意思就是从装饰器中获取设置的属性, # 上面这两个函数对我们研究流程拉来说没有什么影响所以我们可以暂不考虑
第三部分:return view
返回一个函数名view,那么从哪里调用的as_view方法?是不是从urls里面调用的as_view?
url(r'^login/', views.LoginView.as_view(name='董伟华')), 等同于 url(r'^login/', views.LoginView.view), # 这是源码中的View中的as_view中的viws
4、研究views.LoginView.view
自动执行源码中的view函数。再次看源码中的view函数
def view(request, *args, **kwargs): self = cls(**initkwargs) if hasattr(self, 'get') and not hasattr(self, 'head'): self.head = self.get self.request = request self.args = args self.kwargs = kwargs return self.dispatch(request, *args, **kwargs)
整个view函数返回一个self.dispatch(request, *args, **kwargs) 由于self是本类实例化的对象,先从本来去找dispatch
所以你的urls文件中:
url((r'^login/', views.LoginView.view), # 这是源码中View中的as_view中的view函数。 等同于 url(r'^login/', views.LoginView.dispatch()),
5、执行dispatch函数。
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: # 判断的是,比如get方法in LoginView的对象..['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] handler = getattr(self, request.method.lower(), self.http_method_not_allowed) # 获取LoginView类里面的get方法 else: handler = self.http_method_not_allowed # http_method_not_allowed 此⽅法是本类中如果没有定义get⽅法,会执⾏此⽅ 法,给你报⼀个405错误 return handler(request, *args, **kwargs)
哪里调用的dispatch?
url(r'^login/', views.LoginView.dispatch()), 等同于 如果你来的是get请求 url(r'^login/', views.LoginView.get(request)), 如果你来的是post请求 url(r'^login/', views.LoginView.post(request)), 等同于 url(r'^login/', views.LoginView.render(request,xx.html)),
这里着重说一下dispatch,源码中的dispatch起到什么作用?你来的是get请求,经过dispach就会执行相应的LoginView的get方法,你来的是post请求就会执行相应的LoginView的post方法。
面试会问到dispach的问题。
1、 源码中的dispatch起到什么作用?
2、你有没有在后端重写过源码?
视图装饰器
dispatch:面试会问到:
1、你有没有重写过源码的哪里(dispatch,单例模式)
2、Django的源码你研究过哪些?
CBV的请求处理,dispatch这个方法是决定根据不同的请求执行不同的方法。
这里,面试有问过,如果你用CBV模式,你的get或者post方法是从哪里执行的?能够在get执行之前或者之后做一些特殊的操作?
在dispatch方法中执行的 def dispatch(self, request, *args, **kwargs): print('请求来了') # 这⾥既要执⾏⼦类的⽅法,⼜要执⾏⽗类 ret = super().dispatch(request,*args,**kwargs) # handler(request,*args, **kwargs) print('请求⾛了') return ret # handler(request, *args, **kwargs)
上面给所有的来的请求加了功能,有些时候,我们是需要给单独某个请求(get请求)加一些额外的功能,你要通过重写父类dispatch就实现不了了,我们需要利用装饰器去实现。
-
FBV的装饰器
def wrapper(func): # func = home def inner(*args, **kwargs): '''函数执行前的操作''' print('请求来了') ret = func(*args, **keargs) '''函数执行后的操作''' print('请求走了') return ret return inner @wrapper # home = wrapper(home) def home(request): return render(request, 'index/home.html')
这个没有什么难度,私下串一下流程
-
CBV的装饰器
-
先测试类中的方法是够可以加装饰器
def wrapper(f): # f = func def inner(*args, **kwargs): '''函数执行前的操作''' print('请求来了') ret= f(*args, **kwargs) '''函数执行后的操作''' print('请求走了') return ret return inner class A: def __init__(self): self.name = '董伟华' @wrapper # func = wrapper(func) inner def func(self): print('in func') obj = A() obj.func() #obj,inner() print(obj)
通过测试可以知道,类中的方法也可以加装饰器,第一个self参数也是隐形传参传递到inner函数中的。
-
第一个方法:直接加装饰器
def wrapper(func): # func = home def inner(*args, **kwargs): '''函数执行前的操作''' print('请求来了') print(args) # (<app01.views.LoginView object at 0x1086b35c0>,<WSGIRequest: GET '/login/'>) ret = func(*args, **keargs) '''函数执行后的操作''' print('请求走了') return ret return inner class LoginView(View): @wrapper def get(self, request): return render(requesr, 'login.html') def post(self,request): print(request.POST,get('username')) print(request.POST,get('password')) return HttpResponse('登录成功')
-
方法二:借助于menthod_decorator模块
from django.utils.decorators import method_decorator def wrapper(func): # func = home def inner(*args, **kwargs): '''函数执行前的操作''' print('请求来了') print(args) # (<WSGIRequest: GET '/login/'>) ret = func(*args, **keargs) '''函数执行后的操作''' print('请求走了') return ret return inner class LoginView(View): @method_decorator(wrapper) def get(self, request): return render(requesr, 'login.html') def post(self,request): print(request.POST,get('username')) print(request.POST,get('password')) return HttpResponse('登录成功')
通过method_decorator模块加到装饰器原理:就是在我们自己写的装饰器wrapper基础上又包了一层装饰器。
面试有可能会问到
方法一与方法二的区别:自己直接写的装饰器装饰一个类中的方法会将self对象以及request对象传递给inner函数中,而借助于模块的method_decorator装饰器只会将request对象传递给inner函数。
-
方法三:给类中所有的方法加装饰器
def wrapper(func): # func = home def inner(*args, **kwargs): '''函数执行前的操作''' print('请求来了') print(args) # (<app01.views.LoginView object at 0x1086b35c0>,WSGIRequest: GET '/login/'>) ret = func(*args, **keargs) '''函数执行后的操作''' print('请求走了') return ret return inner class LoginView(View): @wrapper def diapatch(self, request): ret = super().dispatch(request, *args, **keargs)handler(request, *args, **keargs) return ret def get(self, request): return render(requesr, 'login.html') def post(self,request): print(request.POST,get('username')) print(request.POST,get('password')) return HttpResponse('登录成功')
-
方法四:直接给类的某个方法加装饰器(与方法二相同)了解
from django.utils.decorators import method_decorator def wrapper(func): # func = home def inner(*args, **kwargs): '''函数执行前的操作''' print('请求来了') print(args) # (<WSGIRequest: GET '/login/'>) ret = func(*args, **keargs) '''函数执行后的操作''' print('请求走了') return ret return inner @method_decorator(wrapper, name='get') class LoginView(View): def get(self, request): return render(request, 'login.html') def post(self, request): print(request.POST.get('username')) print(request.POST.get('password')) return HttpResponse('登录成功') def delete(self,request): pass def func(self): pass
-
-