本节内容
一 Class Based View 基于类的视图 |
function view 存在问题,无法继承复用,尤其时框架封装好的类用不了,function组装复用更擅长
class based view 代码更简洁
简单样例
version 1
# urls.py from django.conf.urls import url from mysite import views as my_view urlpatterns = [ url(r'^about/', my_view.about), ] # mysite/views.py from django.shortcuts import render def about(request): return render(request, 'about.html')
基本的function based view
version 2
# urls.py from django.views.generic import TemplateView urlpatterns = [ url(r'^about/', TemplateView.as_view(template_name='about.html')), ] # mysite/views.py from django.shortcuts import render def about(request): return render(request, 'about.html')
TemplateView.as_view(template_name='about.html')) 方法 as_view(),参数template_name=‘网页模板’
version 3
# mysite/views.py from django.views.generic import TemplateView class AboutView(TemplateView): template_name = 'about.html' #mysite/urls.py from mysite import views as my_view urlpatterns = [ url(r'^about/', my_view.AboutView.as_view()), ]
说明:
-
as_view()返回的是一个function object
-
模板名字作为as_view参数传进去,也可以作为类变量设置template_name = '网页模板'
1. 类的视图 View
# mysite/views.py
def my_view(request): if request.method == 'GET': return render(request, 'about.html') elif request.method == 'POST': return HttpResponse('post it') elif request.method == 'HEAD': return HttpResponse('head it')
# mysite/urls.py
from mysite import views
urlpatterns = [
url(r'^about/', views.my_view),
]
等价于 类图公共基类View
# mysite/views.py from django.shortcuts import render,HttpResponse from django.views.generic import View class MyView(View): def get(self, request): return render(request, 'about.html') def post(self, request): return HttpResponse('post it') def head(self, request): return HttpResponse('head it') # mysite/urls.py from mysite import views as my_view urlpatterns = [ url(r'^about/', my_view.MyView.as_view()), ]
类视图好处就是可以直接继承和覆盖
# mysite/views.py from django.views.generic import View class GreetingView(View): greeting = 'Good Day' def get(self, request): return HttpResponse(self.greeting) class MorningGreeting(GreetingView): greeting = 'Morning to ya' # mysite/urls.py urlpatterns = [ my_view.GreetingView.as_view(greeting="G'day")), ]
源码分析
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class View(object): # 所有views的公共类,仅仅实现 dispatch方法和简单安全性验证 http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): for key, value in six.iteritems(kwargs): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): # request-response的主入口点 for key in initkwargs: # 不能有get,post等方法名的参数,后续有setattr()操作,会覆盖原始请求方法 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__)) # 如果传入了一个没有定义的类属性,就报错;如有greeting属性,就不报错 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) # 调用__init__()进行实例化 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) # 调用 self.dispatch()进行处理 # 对view object设置了一些属性 view.view_class = cls view.view_initkwargs = initkwargs # 更新文档说明 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): # 将request.method反射到类相应的方法上,并执行 if request.method.lower() in self.http_method_names: # 如果有如get,就handler=get;如果没有handler = self.http_method_not_allowed 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)]
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#!/usr/bin/env python3 # -*-coding:utf-8 -*- # __author__:Jonathan # email:nining1314@gmail.com from django.shortcuts import HttpResponse from django import http from django.utils import six from django.utils.decorators import classonlymethod from django.conf.urls import url class View(object): http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace'] def __init__(self, **kwargs): for key, value in six.iteritems(kwargs): setattr(self, key, value) @classonlymethod def as_view(cls, **initkwargs): for key in initkwargs: if key in cls.http_method_names: raise TypeError("禁止 %s 作为 %s()关键字参数" % (key, cls.__name__)) if not hasattr(cls, key): raise TypeError("类 %s() 接收到非法参数 %s,只接受类存在类属性" % (cls.__name__, key)) def view(request, *args, **kwargs): # 把 as_view()传入的关键字参数,赋值为相应的类变量 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) # 一行代码,代表的是self.dispatch()的执行结果 # 函数也是一种对象,可以有自己的属性**** view.view_class = cls view.view_initkwargs = initkwargs # 返回闭合的函数对象 return view def dispatch(self, request, *args, **kwargs): 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) # 一行代码,代表的是handler()的执行结果 """最终dispatch结果:根据request.method.lower()得到相应的http请求方法,反射到同名类方法,执行""" def http_method_not_allowed(self, request, *args, **kwargs): return http.HttpResponseNotAllowed(self._allowed_methods()) def _allowed_methods(self): return [m.upper() for m in self.http_method_names if hasattr(self, m)] class MyView(View): def get(self, request): return HttpResponse('get it') def post(self, request): return HttpResponse('post it') def head(self, request): return HttpResponse('head it') urlpatterns = [ url(r'^my_view/', MyView.as_view()), ]
说明:
-
as_view返回一个 view function
-
as_view接收参数是可以覆盖类定义的变量
-
__init__检查as_view传入的参数是否在类中定义
-
View.as_view()返回函数视图的view 对象,说明类视图只是对原有的函数视图进行了封装,而没有否定函数view的作用
-
view function运行时会调用dispatch,根据用户的request.method路由到get, post等方法
最终得出结论:
View类 在没有更改原Django逻辑的情况下,可以用类来编写view,每个http请求会使用对应类的同名方法进行处理
2. 类的视图 TemplateView
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class ContextMixin(object): # 渲染模板时,处理需要的上下文参数 def get_context_data(self, **kwargs): if 'view' not in kwargs: kwargs['view'] = self return kwargs class TemplateResponseMixin(object): # 渲染模板,Mixin一般是小功能类封装,被其他类继承 template_name = None template_engine = None response_class = TemplateResponse content_type = None def render_to_response(self, context, **response_kwargs): # 进行模板渲染,render()是个函数,response_class()是个类,实现本质目标没多大区别 response_kwargs.setdefault('content_type', self.content_type) return self.response_class( request=self.request, template=self.get_template_names(), context=context, using=self.template_engine, **response_kwargs ) def get_template_names(self): if self.template_name is None: raise ImproperlyConfigured( "TemplateResponseMixin requires either a definition of " "'template_name' or an implementation of 'get_template_names()'") else: return [self.template_name] class TemplateView(TemplateResponseMixin, ContextMixin, View): # 实现get()方法,有渲染上文参数,有渲染操作 def get(self, request, *args, **kwargs): context = self.get_context_data(**kwargs) return self.render_to_response(context)
说明:
-
每个Mixin只提供部分功能,最终需要类整合
-
TemplateResponseMixin提供render_to_response() 方法,渲染模板
-
ContentMixin提供get_context_data()方法,提供渲染数据
-
View提供get,post用户访问接口
- 面向对象编程:如何拆分功能模块,如何组装功能模块
3. 类的视图 login_required解决方法
3.1 封装Mixin(推荐)
from django.contrib.auth.decorators import login_required class LoginRequiredMixin(object): @classmethod def as_view(cls, **initkwargs): view = super(LoginRequiredMixin, cls).as_view(**initkwargs) return login_required(view) class MyView(LoginRequiredMixin, ...): pass
3.2 装饰类
from django.contrib.auth.decorators import login_required from django.utils.decorators import method_decorator from django.views.generic import TemplateView class ProtectedView(TemplateView): template_name = 'secret.html' @method_decorator(login_required) def dispatch(self, request, *args, **kwargs): return super(ProtectedView, self).dispatch(*args, **kwargs)
推荐学习网站:http://devdocs.io/
二 通用视图 |
通用视图(generic class base view) 和 class base view 概念上不是一回事
Class Base View 是指用类的方式去写视图
通用视图 是用Class Base View的方式将我们常用的增、删、改、查封装成可扩展的类,使用时直接继承、快速实现
1. 通用视图 - ListView
配置编程,获取数据列表
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
from django.db import models class Publisher(models.Model): name = models.CharField(max_length=30) address = models.CharField(max_length=50) city = models.CharField(max_length=60) state_province = models.CharField(max_length=30) country = models.CharField(max_length=50) website = models.URLField() def __str__(self): return self.name class Author(models.Model): first_name = models.CharField(max_length=30) last_name = models.CharField(max_length=40) email = models.EmailField() def __str__(self): return '%s %s' % (self.first_name, self.last_name) class Book(models.Model): title = models.CharField(max_length=100) authors = models.ManyToManyField('Author') publisher = models.ForeignKey('Publisher') publication_date = models.DateField(auto_now_add=True) def __str__(self): return self.title
from django.views.generic import ListView from .models import Publisher class PublisherList(ListView): model = Publisher # 类属性:指定解析的model # queryset = Publisher.objects.all()[0:1] # 和model二选一,获取指定数据 context_object_name = 'publishers' # 默认object_list,前端渲染时的上下文参数 # template_name = 'books/publisher_list.html' # 指定渲染的模板文件,默认值model名小写_list.html # 联系类的queryset,而该方法为实例的定制 def get_queryset(self): print(self.request) # 根据 self.request进行判断,返回符合条件的queryset return Publisher.objects.all()[0:1]
urlpatterns = [
url(r'^publishers/$', views.PublisherList.as_view(), name='publishers'),
]
说明:
-
multi object list view
-
model、queryset和get_queryset三者之间关系
-
content_object_name
-
template_name
源码UML图分析:
2. 通用视图 - DetailView
显示一个object的详情页面
from django.views.generic import DetailView from .models import Publisher from .models import Book class PublisherDetail(DetailView): model = Publisher context_object_name = 'publisher' # 模板渲染对象,默认值object def get_context_data(self, **kwargs): context = super(PublisherDetail, self).get_context_data(**kwargs) context['book_list'] = Book.objects.all() # 附加额外选软数据 return context ''' # 默认的object,是从url里的pk获取的 def get_object(self, queryset=None): object = super(PublisherDetail, self).get_object() # 进行更新操作 # object.last_accessed = timezone.now() # object.save() return object '''
urlpatterns = [
url(r'^publisher/(?P<pk>[0-9]+)/$', views.PublisherDetail.as_view(), name='publisher-detail'),
]
说明:
-
Single object detail view
-
get_context_data
-
content_object_name
-
get_object
源码UML图分析:
3. 通用视图 - FormView
-
FormView
-
CreateView,UpdateView,DeleteView