Ricardo

Django as_view 方法实现分析

从个人笔记仓库整理迁移

django的as_view方法实现分析

框架概念 MVC/MTV

django 是一个MVC(MTV)框架,官方定义是 MTV,但本质上其实是一样的,官方解释

了解下 MTV/MVC 不同的含义 MVC 是 Model-View-Controller 的缩写,其中每个单词都有其不同的含义:

  • Modle 代表数据存储层,是对数据表的定义和数据的增删改查;
  • View 代表视图层,是系统前端显示部分,它负责显示什么和如何进行显示;
  • Controller 代表控制层,负责根据从 View 层输入的指令来检索 Model 层的数据,并在该层编写代码产生结果并输出。

Django 借鉴了经典的 MVC 模式,它也将交互的过程分为了 3 个层次,也就是 MTV 设计模式;

  • Model:数据存储层,处理所有数据相关的业务,和数据库进行交互,并提供数据的增删改查;
  • Template:模板层(也叫表现层)具体来处理页面的显示;
  • View:业务逻辑层,处理具体的业务逻辑,它的作用是连通Model 层和 Template 。

框架使用方式 CBV/FBV

Django有两种视图方式CBV以及FBV

  • FBV 基于函数的视图 Function Based Views
  • CBV 基于类的视图 Class Based Views

class-based-views
相关名词解释链接
class-based-views/base

FBV视图样例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse

from .models import Choice, Question


# ...
def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST["choice"])
    except (KeyError, Choice.DoesNotExist):
        # Redisplay the question voting form.
        return render(
            request,
            "polls/detail.html",
            {
                "question": question,
                "error_message": "You didn't select a choice.",
            },
        )
    else:
        selected_choice.votes += 1
        selected_choice.save()
        # Always return an HttpResponseRedirect after successfully dealing
        # with POST data. This prevents data from being posted twice if a
        # user hits the Back button.
        return HttpResponseRedirect(reverse("polls:results", args=(question.id,)))

这种方式非常简单,感觉就像初学者在那里学习python,糊弄下搞个函数处理,所以感觉没有成就感,CBV 虽然本质上跟FBV一样,但看起来高大上

CBV视图样例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.views import generic

from .models import Choice, Question


class IndexView(generic.ListView):
    template_name = "polls/index.html"
    context_object_name = "latest_question_list"

    def get_queryset(self):
        """Return the last five published questions."""
        return Question.objects.order_by("-pub_date")[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = "polls/detail.html"


class ResultsView(generic.DetailView):
    model = Question
    template_name = "polls/results.html"


def vote(request, question_id):
    ...  # same as above, no changes needed.

下面就是讲解django CBV的相关原理

django的类视图拥有自动查找指定方法的功能, 通过调用是通过as_view()方法实现

urls.py

1
2
3
4
5
from meduo_mall.demo import views

urlpatterns = [
    url(r'register/$', views.Demo.as_view())
]

views.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from django.views.generic import View


class Demo(View):

    def get(self, request):
        return HttpResponse('get page')

    def post(self, request):
        return HttpResponse('post page')

为什么as_view能自动匹配指定的方法,

先看源码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    @classonlymethod
    def as_view(cls, **initkwargs):  # 实际上是一个闭包  返回 view函数
        """
        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):
              # path('test_class_view/', TestClassView.as_view(test_key='hh'), name='cbv')
              # 当as_view() 视图中有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):  # 作用:增加属性, 调用dispatch方法 
            self = cls(**initkwargs)  # 创建一个 cls 的实例对象, cls 是调用这个方法的 类(Demo)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request  # 为对象增加 request, args, kwargs 三个属性
            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,
        if request.method.lower() in self.http_method_names:  # 判断请求的方法类视图是否拥有, http_method_names=['get', 'post']
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)  # 如果存在 取出该方法
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)  # 执行该方法

简化版

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    @classonlymethod
    def as_view(cls, **initkwargs):  # 实际上是一个闭包  返回 view函数
        """
        Main entry point for a request-response process.
        """
        def view(request, *args, **kwargs):  # 作用:增加属性, 调用dispatch方法 
            self = cls(**initkwargs)  # 创建一个 cls 的实例对象, cls 是调用这个方法的 类(Demo)
            if hasattr(self, 'get') and not hasattr(self, 'head'):
                self.head = self.get
            self.request = request  # 为对象增加 request, args, kwargs 三个属性
            self.args = args
            self.kwargs = kwargs
            return self.dispatch(request, *args, **kwargs)  # 找到指定的请求方法, 并调用它

        return view

    def dispatch(self, request, *args, **kwargs):
        # Try to dispatch to the right method; if a method doesn't exist,
        if request.method.lower() in self.http_method_names:  # 判断请求的方法类视图是否拥有, http_method_names=['get', 'post']
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)  # 如果存在 取出该方法
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)  # 返回该请求方法执行的结果

再简化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def as_view(): # 校验 + 返回view方法
    # 一些校验
    ...
    def view(): # 执行视图
        # 增加 为对象request, args, kwargs 属性
        ...
        return dispatch() # 调用指定的请求方法
    return view

def dispatch(): # 真正的查找指定的方法, 并调用该方法
    ...
    return handler()

调用顺序: as_view –> view –> dispatch

  • 可以看出as_view实际上是一个闭包, 它的作用做一些校验工作, 再返回view方法.
  • 而view方法的作用是给请求对象补充三个参数, 并调用 dispatch方法处理
  • dispatch方法查找到指定的请求方法, 并执行

可以得出结论: 实际上真正实现查找的方法是 dispatch方法

参考链接

django的as_view方法实现分析

functools

我们django中写CBV的时候继承的是View,rest_framework继承的是APIView 我们django中写CBV的时候继承的是View,rest_framework继承的是APIView,那么他们两个有什么不同呢~~~

urlpatterns = [ url(r’^book$’, BookView.as_view()), url(r’^book/(?P\d+)$’, BookEditView.as_view()), ] 我们可以看到,不管是View还是APIView最开始调用的都是as_view()方法 那我们走进源码看看

我们能看到,APIView继承了View, 并且执行了View中的as_view()方法,最后把view返回了,用csrf_exempt()方法包裹后去掉了csrf的认证。

那我们看看View中的as_view()方法做了什么

我们看到了~在View中的as_view方法返回了view函数,而view函数执行了self.dispatch()方法~~但是这里的dispatch方法应该是我们APIView中的

我们去initialize_request中看下把什么赋值给了request,并且赋值给了self.request, 也就是我们在视图中用的request.xxx到底是什么

我们看到,这个方法返回的是Request这个类的实例对象~~我们注意我们看下这个Request类中的第一个参数request,是我们走我们django的时候的原来的request~

我们看到了,这个Request类把原来的request赋值给了self._request, 也就是说以后_request是我们老的request,新的request是我们这个Request类~~

那我们继承APIView之后请求来的数据都在哪呢~~

comments powered by Disqus
记录自己
Built with Hugo
主题 Stack 由 Jimmy 设计