django:类视图是什么

导读:本篇文章讲解 django:类视图是什么,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

一,django中的类视图

视图是能接受用户的请求并返回响应的可调用的 python 对象。

(一)从函数视图说起

视图中最常见的就是函数视图(Function-Based View, FBV)。

这种视图以函数的形式进行定义:

from django.http import HttpResponse
import datetime

def current_datetime(request):
    user = request.user.username
    now = datetime.datetime.now()
    html = "<html><body>Hello %s, it is now %s.</body></html>" % (user, now)
    return HttpResponse(html)

一个函数视图被调用后,一般遵循如下的工作流程:

  1. 接受请求:函数视图的第一个必要参数request是自动包含了请求元数据的 HttpRequest 对象
  2. 处理请求:通常来说是处理HttpRequest 对象中携带的数据,比如实现表单处理、数据库操作、模板渲染等。
  3. 做出响应:既可以手动设置HttpResponset 对象其子类或者其它响应对象来指定相应的内容细节,也能通过捷径函数来渲染响应内容、重定向等。

无论视图函数的内容多复杂,它的内部总是按这三步来走的。

(二)从函数视图到类视图

函数视图有这么一些不足:

  • 有时视图函数的内容就比较复杂,比如,使用多个条件分支判断请求方法将导致代码的可读性较差。
  • 函数视图的大量使用似乎过度发力于面向过程编程,并没有使用到构成框架的基础设施即python语言的面向对象这一优秀的编程思想。

这些缺点却可以通过类视图(Class-Based View,CBV)来弥补。尽管类视图并不是函数视图的平替方案,但相比于函数视图,它有自己的优势:

  • 与特定的 HTTP 方法(GET, POST, 等等)关联的代码组织能通过单独的方法替代条件分支来解决。
  • 面向对象技术(比如 mixins 多重继承)可用于将代码分解为可重用组件。

基于函数视图编写重复的 CURD 代码是相当让人感到厌倦的,django 提供了一系列的类视图让这项编程工作变得简洁快速——django已经在类视图中实现了一些频繁的编码操作,只需要简单配置类视图的各种属性、偶尔重写一下类视图的某些方法,必要时还能通过继承的方式拓展类视图,这都有利于快速完成工作——这就是类视图的最大优势。

举个从函数视图到类视图例子——定义处理表单的视图。

函数视图:
from django.http import HttpResponseRedirect
from django.shortcuts import render

from .forms import MyForm

def function_based_view(request):
    if request.method == "POST":
        form = MyForm(request.POST)
        if form.is_valid():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')
    else:
        form = MyForm(initial={'key': 'value'})

    return render(request, 'form_template.html', {'form': form})
# 类视图
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from MyApp.forms import MyForm

class ClassBasedView(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():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')

        return render(request, self.template_name, {'form': form})

(三)简单使用类视图

类视图创建好之后,依然要像函数视图那样,在路由中被调用,但有些小区别:

from MyApp.views import function_based_view, ClassBasedView

urlpatterns = [
    path('FBV/', function_based_view, name='FBV'),	# 直接调用函数视图
    path('CBV/', ClassBasedView.as_view(), name='CBV'),	# 调用类视图的 as_view 方法
]

尽管函数视图与类视图都是直接可调用的python对象,但使用类视图时需要调用其 as_view 方法。

如果需要配置的项目不多的话,可以将它们作为 as_view 方法:

urlpatterns = [
    path('CBV/', ClassBasedView.as_view(template_name="about.html"), name='CBV'),	# 调用类视图的 as_view 方法
]

注意:传递给类视图的参数会在类视图的每个实例之间共享,因此最好不要使用列表、字典或任何其他可变对象作为类视图的参数。

(四)类视图是如何工作的

View是所有类视图的基类,所以就通过分析它的源码来分析类视图是如何工作的:

django.views:
class View:
    """
    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 kwargs.items():
            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(
                    'The method name %s is not accepted as a keyword argument '
                    'to %s().' % (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)
            self.setup(request, *args, **kwargs)
            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=())
        return view

    def setup(self, request, *args, **kwargs):
        """Initialize attributes shared by all view methods."""
        if hasattr(self, 'get') and not hasattr(self, 'head'):
            self.head = self.get
        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 = 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 HttpResponseNotAllowed(self._allowed_methods())

    def options(self, request, *args, **kwargs):
        """Handle responding to requests for the OPTIONS HTTP verb."""
        response = HttpResponse()
        response.headers['Allow'] = ', '.join(self._allowed_methods())
        response.headers['Content-Length'] = '0'
        return response

    def _allowed_methods(self):
        return [m.upper() for m in self.http_method_names if hasattr(self, m)]

1,http_method_names属性

http_method_names属性指定了类视图能处理的 HTTP 方法。

在子类化时不太会覆盖这个属性。

2,as_view方法

as_view()方法是类视图处理请求-响应过程的主要入口点:

@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(
                'The method name %s is not accepted as a keyword argument '
                'to %s().' % (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)
        self.setup(request, *args, **kwargs)
        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=())
    return view

首先是对参数的处理:

for key in initkwargs:
    if key in cls.http_method_names:
        raise TypeError(
            'The method name %s is not accepted as a keyword argument '
            'to %s().' % (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))
  1. 只接受非http_method_names属性值中同名的参数。
  2. 只接受是类本身属性的参数。

再来看看 view 函数:

def view(request, *args, **kwargs):
    self = cls(**initkwargs)	# 初始化
    self.setup(request, *args, **kwargs)
    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)
  1. 接收请求 request 和其他参数位置参数与关键字参数;
  2. 首先对类本身进行初始化;
  3. 然后调用setup方法,传入接收到的参数;
  4. 判断类本身是否通过setup方法设置了 request 属性;
  5. 返回对 dispatch 方法的调用,传入接收到的参数;
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
  1. 设置装饰器中的两个属性;
  2. clscls.dispatch 更新 view 函数;
  3. 返回 view 函数。

这么一看,as_view方法的本质就是一个装饰器,它将被调用的类视图装饰为一个函数视图!

3,setup方法

setup() 方法将请求对象 HttpRequest 分配给视图的 request 属性,将任何从 URL 中捕获的位置参数和关键字参数分别分配给 args 和 kwargs 属性。实际上就是在 dispatch() 方法之前执行关键视图的初始化。

def setup(self, request, *args, **kwargs):
    """Initialize attributes shared by all view methods."""
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
    self.request = request
    self.args = args
    self.kwargs = kwargs

4,dispatch方法

默认的实现将检查 HTTP 方法,并尝试委托给与 HTTP 方法相匹配的方法;GET 将委托给 get(),POST 将委托给 post(),以此类推。

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)
  1. 判断请求方法是否被允许。是,则返回对和请求方法同名的方法的调用。

5,总结

1,在路由中调用 CBV.as_view() 时,调用 setup() 方法初始化类视图本身,并将它装饰为函数视图;
2,再调用 dispatch() 方法调用相应的方法处理请求;
3,被调用的具体方法需要在子类化时具体定义,这就为后面 django 的各种通用类视图的实现以及自定义类视图的实现提供了基础。
4,使用类视图,说先要确定其继承关系以确定用有哪些属性与方法,然后根据需要进行配置和自定义或重写。

# 类视图
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views import View

from MyApp.forms import MyForm

class ClassBasedView(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():
            # <process form cleaned data>
            return HttpResponseRedirect('/success/')

        return render(request, self.template_name, {'form': form})
  1. 继承 django.views.View 类;
  2. 定义属性,指定通用的配置;
  3. 定义不同的请求处理方法;

在这里插入图片描述

二,混入 Mixin

混入(Mixin)模式是一种非常有用的编程模式,特别是对于 python 之类的面向对象的编程语言来说。

django中的通用类视图大量用到各种 Mixin 来为视图扩展不同的功能,为了更好理解通用类视图以及编写自己的类视图,就非常有必要先来理解 Mixin。

(一)类的多重继承

继承是面向对象编程语言的三大特性之一,

在 OOP 思想中,通常使用继承来赋予不同类的对象相同的功能,所以,如果一组对象具有某种公共能力,则应该将这种能力抽象出来,放到一个基类中,然后让需要这些公共能力的对象的类都去继承该基类:

class Employee:  # 这是一个雇员
    def __init__(self, name, salary):
        self.name = name
        self.salary = 0

    def work(self):
        print(self.name, "does stuff")


class Server(PositionMixin, Employee):  # 服务员是一个雇员
    def __init__(self, name):
        Employee.__init__(self, name, 5000)

    def work(self):
        print(self.name, "interfaces with customer")


class Chef(PositionMixin, Employee):  # 主厨是一个雇员
    def __init__(self, name):
        Employee.__init__(self, name, 50000)

    def work(self):
        print(self.name, "makes food")


class PizzaRobot(Chef):  # 披萨机器人是一个厨师
    def __init__(self, name):
        Chef.__init__(self, name)

    def work(self):
        print(self.name, "makes pizza")

用 OOP 的术语讲,这这种多继承现象产生了名为 is-a 链的关系链。这个关系链的存在表明,继承的主要目的,就是对父类进行概念上的扩展,或者说是由父类提供公共内容供子类使用,这种关系可以变得很长。

(二)从多重继承到 Mixin

但考虑一种情况:对主厨与服务员这类的员工而言,工作做得好就需要得到升职加薪,而特殊员工披萨机器人做的再好都是不需要升职加薪的。

这就会导致一个问题:既然 Server、Chef 和 PizzaRobot 都直接或间接地继承了 Employee ,那么将升职加薪放入 Employee 就不太合适了,这就打破了is-a 链

解决方法:

  1. 在 Server 和 Chef 中分别实现升职加薪。
  2. 用单独的 Position 类来实现升职加薪,然后让 Server 和 Chef 再分别继承 Position。

显然第二种方法能提高代码的可复用性。因为本质还是继承,所以也就能通过多重继承的方式继续扩展继承了 Position 类的 Server 和 Chef。

就 python 而言是没有问题的。但有些语言并不支持多重继承,却依然需要这种公共组件思想。

Mixin 就是这样一种实现模块化和可重用性的一种思想,它避免了类继承中is-a 链的局限性,但实际实现起来还是通过类继承的方式,但这种 Mixin 类有一定的要求:

  • Mixin 类的功能必须足够单一。
  • Mixin 类不包含任何内部状态。
  • Mixin 类是可选的。
  • Mixin 类不应该被单独实例化。

简单来说,Mixin 的想法就是让公共能力变成一种纯粹的热插件!即插即用的那种。

class Employee:  # 这是一个雇员
    def __init__(self, name, salary):
        self.name = name
        self.salary = 0

    def work(self):
        print(self.name, "does stuff")


class PositionMixin:					# Mixin 类
    position_class = None

    def print_position_class(self):
        print(self.position_class)


class Server(PositionMixin, Employee):  # 服务员是一个雇员
    def __init__(self, name):
        Employee.__init__(self, name, 5000)

    def work(self):
        print(self.name, "interfaces with customer")


class Chef(PositionMixin, Employee):  # 主厨是一个雇员
    def __init__(self, name):
        Employee.__init__(self, name, 50000)

    def work(self):
        print(self.name, "makes food")


class PizzaRobot(Chef):  # 披萨机器人是一个厨师
    def __init__(self, name):
        Chef.__init__(self, name)

    def work(self):
        print(self.name, "makes pizza")


if __name__ == '__main__':
    chef = Chef(name="Thomas Johnson")
    chef.position_class = 'Head Chef'	# 从 PositionMixin 继承 position_class 属性
    chef.print_position_class()			# 从 PositionMixin 继承 print_position_class() 方法

Interfaces, Mixins and Building Powerful Custom Data Structures in Python

多重继承的主要问题之一是在多个父类定义相同方法时找出调用哪个方法。Python 通过使用类的MRO解决了这个问题,它是父类的有序列表。每当我们调用一个类的方法时,解释器首先检查该类是否实现了所讨论的方法。如果没有,解释器会沿着类的 MRO 向上走,直到找到实现该方法的第一个父级。

(三)多继承和方法解析顺序MRO

当子类继承多个父类时,从 Python 3.X 开始的新式类和它的实例会以由左至右、自底向上的广度优先方式在父类中遍历搜索继承到的属性与方法, 直到找到名称相符者。

这在使用 Mixin 类时需要特别注意。

Using mixins with class-based views
Class-based views mixins

三,内置的类视图

django提供了许多内置的类视图来帮助开发者减少工作量,在理解了什么是类视图以及 Mixin 之后,就能方便地使用这些通用类视图了。

Built-in class-based views API

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/98062.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!