zoukankan      html  css  js  c++  java
  • Django Class Based View

    本节内容

    一   Class Based View 基于类的视图

      1.  类的视图 View

      2.  类的视图 TemplateView

      3.  类的视图 login_required解决方法

    二  通用视图

      1.  通用视图 - ListView

      2.  通用视图 - DetailView

      3.  通用视图 - FormView

    一  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")),
    ]

    源码分析

    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)]
    类View
    #!/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()),
    ]
    吃透类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

    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)
    类TemplateView

    说明:

    • 每个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

    配置编程,获取数据列表

    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
    models.py
    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

  • 相关阅读:
    IDEA使用 磨刀霍霍向代码
    如何设计一个高可用系统?要考虑哪些地方?
    spring boot 集成apollo 快速指南
    实战_Spring_Cloud
    Spring Boot 入门(十二):报表导出,对比poi、jxl和esayExcel的效率
    「newbee-mall新蜂商城开源啦」1000 Star Get !仓库Star数破千!记录一下
    一个C#开发者重温Java的心路历程
    BeanUtils 如何拷贝 List?
    JVM之JVM的体系结构
    python类中的私有方法
  • 原文地址:https://www.cnblogs.com/jonathan1314/p/7545298.html
Copyright © 2011-2022 走看看