第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

全部開發(fā)者教程

Django 入門教程

課程導(dǎo)學(xué)
Django 慕課教程使用指南
Django開發(fā)實戰(zhàn)
35 開發(fā)實戰(zhàn)

Django 的類視圖

前面第9節(jié)中我們簡單介紹了 Django FBV 和 CBV,分別表示以函數(shù)形式定義的視圖和以類形式定義的視圖。函數(shù)視圖便于理解,但是如果一個視圖函數(shù)對應(yīng)的 URL 路徑支持多種不同的 HTTP 請求方式時,如 GET, POST, PUT 等,需要在一個函數(shù)中寫不同的業(yè)務(wù)邏輯,這樣導(dǎo)致寫出的函數(shù)視圖可讀性不好。此外,函數(shù)視圖的復(fù)用性不高,大量使用函數(shù)視圖,導(dǎo)致的一個結(jié)果就是大量重復(fù)邏輯和代碼,嚴(yán)重影響項目質(zhì)量。而 Django 提供的 CBV 正是要解決這個問題而出現(xiàn)的,這也是官方強(qiáng)烈推薦使用的方式。

1. Django 類視圖使用介紹

1.1 CBV 的基本使用

前面我們已經(jīng)介紹了 CBV 的基本使用方法,其基本流程如下:

定義視圖類 (TestView)

該類繼承視圖基類 View,然后實現(xiàn)對應(yīng) HTTP 請求的方法。Django 在 View 類的基礎(chǔ)上又封裝了許多視圖類,如專門返回模板的 TemplateView 視圖類、用于顯示列表數(shù)據(jù)的 ListView 視圖類等等。這些封裝的是圖能夠進(jìn)一步減少大家的重復(fù)代碼,后面我會詳細(xì)介紹這些封裝的視圖類的使用以及其源碼實現(xiàn)。

# 代碼路徑 hello_app/views.py
# ...

class TestView(View):
    def get(self, request, *args, **kwargs):
        return HttpResponse('hello, get\n')

    def post(self, request, *args, **kwargs):
        return HttpResponse('hello, post\n')

    def put(self, request, *args, **kwargs):
        return HttpResponse('hello, put\n')

    def delete(self, request, *args, **kwargs):
        return HttpResponse('hello, delete\n')

    @csrf_exempt
    def dispatch(self, request, *args, **kwargs):
        return super(TestView, self).dispatch(request, *args, **kwargs)

配置 URLConf,如下

# 代碼路徑 hello_app/urls.py
# ...

urlpatterns = [
    path('test-cbv/', views.TestView.as_view(), name="test-cbv")
]

注意:不是直接寫視圖類,而是要調(diào)用視圖類的 as_view() 方法,這個 as_view() 方法返回的也是一個函數(shù)。

啟動 Django 工程,測試

# 啟動django服務(wù)
(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
April 15, 2020 - 07:08:32
Django version 2.2.11, using settings 'first_django_app.settings'
Starting development server at http://0.0.0.0:8888/
Quit the server with CONTROL-C

# 打開另一個xshell窗口,發(fā)送如下請求
[root@server ~]# curl -XGET http://127.0.0.1:8888/hello/test-cbv/
hello, get
[root@server ~]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/
hello, post
[root@server ~]# curl -XPUT http://127.0.0.1:8888/hello/test-cbv/
hello, put
[root@server ~]# curl -XDELETE http://127.0.0.1:8888/hello/test-cbv/
hello, delete

1.2 Django 中使用 Mixin

首先需要了解一下 Mixin 的概念,這里有一篇介紹 Python 中 Mixin 的文章:<<多重繼承>> ,可以認(rèn)真看下,加深對 Mixin 的理解。在我的理解中,Mixin 其實就是單獨的一塊功能類。假設(shè) Django 中提供了 A、B、C 三個視圖類,又有 X、Y、Z三個 Mixin 類。如果我們想要視圖 A,同時需要額外的 X、Y功能,那么使用 Python 中的多重繼承即可達(dá)到目的:

class NewView(A, X, Y):
    """
    定義新的視圖
    """
    pass

我們來看看 Django 的官方文檔是如何引出 Mixin 的:

Django’s built-in class-based views provide a lot of functionality, but some of it you may want to use separately. For instance, you may want to write a view that renders a template to make the HTTP response, but you can’t use TemplateView;perhaps you need to render a template only on POST, with GET doing something else entirely. While you could use TemplateResponse directly, this will likely result in duplicate code.

For this reason, Django also provides a number of mixins that provide more discrete functionality. Template rendering, for instance, is encapsulated in the TemplateResponseMixin.

翻譯過來就是: Django 內(nèi)置的類視圖提供了許多功能,但是我們可能只需要其中的一部分功能。例如我想寫一個視圖,該視圖使用由模板文件渲染后的 HTML 來響應(yīng)客戶端的 HTTP 請求,但是我們又不能使用 TemplateView 來實現(xiàn),因為我只想在 POST 請求上使用這個模板渲染的功能,而在 GET 請求時做其他事情。當(dāng)然,可以直接使用 TemplateResponse 來完成,這樣就會導(dǎo)致代碼重復(fù)。基于這個原因, Django 內(nèi)部提供了許多離散功能的 mixins。

可以看到,這里的 mixins 就是一些單獨功能的類,配合視圖類一起使用,用于組合出各種功能的視圖。接下來,我們結(jié)合前面的 Member 表來使用下 mixin 功能。具體的步驟如下:

改造原來的視圖類-TestView。我們給原來的視圖類多繼承一個 mixin,用于實現(xiàn)單個對象查找查找功能;

from django.shortcuts import render
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin

from .models import Member

# Create your views here.
class TestView(SingleObjectMixin, View):
    model = Member

    def get(self, request, *args, **kwargs):
        return HttpResponse('hello, get\n')

    def post(self, request, *args, **kwargs):
        self.object = self.get_object()
        return HttpResponse('hello, {}\n'.format(self.object.name))

    def put(self, request, *args, **kwargs):
        return HttpResponse('hello, put\n')

    def delete(self, request, *args, **kwargs):
        return HttpResponse('hello, delete\n')

    @csrf_exempt
    def dispatch(self, request, *args, **kwargs):
        return super(TestView, self).dispatch(request, *args, **kwargs)

修改 URLConf 配置,傳遞一個動態(tài)參數(shù),用于查找表中記錄:

urlpatterns = [
    path('test-cbv/<int:pk>/', views.TestView.as_view(), name="test-cbv")
]

啟動服務(wù)器,然后進(jìn)行測試:

[root@server first_django_app]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/2/
hello, 會員2
[root@server first_django_app]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/4/
hello, spyinx-0
[root@server first_django_app]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/9/
hello, spyinx-5
[root@server first_django_app]# curl -XGET http://127.0.0.1:8888/hello/test-cbv/9/
hello, get
[root@server first_django_app]# curl -XPUT http://127.0.0.1:8888/hello/test-cbv/9/
hello, put
[root@server first_django_app]# curl -XDELETE http://127.0.0.1:8888/hello/test-cbv/9/
hello, delete

可以看到在 POST 請求中,我們通過傳遞主鍵值,就能返回 Member 表中對應(yīng)記錄中的 name 字段值,這一功能正是由SingleObjectMixin 中的 get_object() 方法提供的。通過繼承這個查詢功能,我們就不用再使用 ORM 模型進(jìn)行查找了,這簡化了我們的代碼。當(dāng)然,這只能滿足一小部分的場景,對于更多復(fù)雜的場景,我們還是需要實現(xiàn)自己的邏輯,我們也可以把復(fù)雜的功能拆成各種 mixin,然后相關(guān)組合繼承,這樣可以很好的復(fù)用代碼,這是一種良好的編碼方式。

2. 深入理解 Django 類視圖

這里在介紹完類視圖的基本使用后,我們來深入學(xué)習(xí)下 Django 的源代碼,看看 Django 是如何將對應(yīng)的 HTTP 請求映射到對應(yīng)的函數(shù)上。這里我們使用的是 Django 2.2.10 的源代碼進(jìn)行說明。我們使用 VSCode 打開 Django 源碼,定位到 django/views/generic 目錄下,這里是和視圖相關(guān)的源代碼。

圖片描述

首先看 __init__.py 文件,內(nèi)容非常少,主要是將該目錄下的常用視圖類導(dǎo)入到這里,簡化開發(fā)者導(dǎo)入這些常用的類。其中最重要的當(dāng)屬 base.py 文件中定義的 view 類,它是其他所有視圖類的基類。

# base.py中常用的三個view類
from django.views.generic.base import RedirectView, TemplateView, View

# dates.py中定義了許多和時間相關(guān)的視圖類
from django.views.generic.dates import (
    ArchiveIndexView, DateDetailView, DayArchiveView, MonthArchiveView,
    TodayArchiveView, WeekArchiveView, YearArchiveView,
)
# 導(dǎo)入DetailView類
from django.views.generic.detail import DetailView
# 導(dǎo)入增刪改相關(guān)的視圖類
from django.views.generic.edit import (
    CreateView, DeleteView, FormView, UpdateView,
)
# 導(dǎo)入list.py中定義的顯示列表的視圖類
from django.views.generic.list import ListView

__all__ = [
    'View', 'TemplateView', 'RedirectView', 'ArchiveIndexView',
    'YearArchiveView', 'MonthArchiveView', 'WeekArchiveView', 'DayArchiveView',
    'TodayArchiveView', 'DateDetailView', 'DetailView', 'FormView',
    'CreateView', 'UpdateView', 'DeleteView', 'ListView', 'GenericViewError',
]

# 定義一個通用的視圖異常類
class GenericViewError(Exception):
    """A problem in a generic view."""
    pass

接下來,我們查看 base.py 文件,重點分析模塊中定義的 View 類:

# 源碼路徑 django/views/generic/base.py

# 忽略導(dǎo)入
# ...

class View:

    http_method_names = ['get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'trace']
    
    def __init__(self, **kwargs):
        # 忽略
        # ...
            
    @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.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 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())
    
    # 忽略其他函數(shù)
    # ...

# ...

我們來仔細(xì)分析 view 類中的這部分代碼。view 類首先定義了一個屬性 http_method_names,表示其支持的 HTTP 請求方法。接下來最重要的是 as_view() 方法和 dispatch() 方法。在上面使用視圖類的示例中,我們定義的 URLConf 如下:

# first_django_app/hello_app/urls.py

from . import views

urlpatterns = [
    # 類視圖
    url(r'test-cbv/', views.TestView.as_view(), name='test-cbv'),
]

這里結(jié)合源碼可以看到,views.TestView.as_view() 返回的結(jié)果同樣是一個函數(shù):view(),它的定義和前面的視圖函數(shù)一樣。as_view() 函數(shù)可以接收一些參數(shù),函數(shù)調(diào)用會先對接收的參數(shù)進(jìn)行檢查:

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

上面的代碼會對 as_view() 函數(shù)傳遞的參數(shù)做兩方面檢查:

首先確保傳入的參數(shù)不能有 get、post 這樣的 key 值,否則會覆蓋 view 類中的對應(yīng)方法,這樣對應(yīng)的請求就無法正確找到函數(shù)進(jìn)行處理。覆蓋的代碼邏輯如下:

class View:
    # ...
    def __init__(self, **kwargs):
        # 這里會將所有的傳入的參數(shù)通過setattr()方法給屬性類賦值
        for key, value in kwargs.items():
            setattr(self, key, value)
    # ...
    @classonlymethod
    def as_view(cls, **initkwargs):
        # ...

        def view(request, *args, **kwargs):
            # 調(diào)用視圖函數(shù)時,會將這些參數(shù)傳給View類來實例化
            self = cls(**initkwargs)
            # ...
       
        # ...
    # ...

此外,不可以傳遞類中不存在的屬性值。假設(shè)我們將上面的 URLConf 進(jìn)行略微修改,如下:

from . import views

urlpatterns = [
    # 類視圖
    url(r'test-cbv/', views.TestView.as_view(no_key='hello'), name='test-cbv'),
]

啟動后,可以發(fā)現(xiàn) Django 報錯如下,這正是由本處代碼拋出的異常。

圖片描述

接下來看下 update_wrapper() 方法,這個只是 python 內(nèi)置模塊中的一個方法,只是比較少用,所以會讓很多人感到陌生。先看它的作用:

update_wrapper() 這個函數(shù)的主要功能是負(fù)責(zé)復(fù)制原函數(shù)的一些屬性,如 moudle、name、doc 等。如果不加 update_wrapper(), 那么被裝飾器修飾的函數(shù)就會丟失其上面的一些屬性信息。

具體看一個測試代碼示例:

from functools import update_wrapper

def test_wrapper(f):
    def wrapper_function(*args, **kwargs):
        """裝飾函數(shù),不保留原信息"""
        return f(*args, **kwargs)
    return wrapper_function

def test_update_wrapper(f):
    def wrapper_function(*args, **kwargs):
        """裝飾函數(shù),使用update_wrapper()方法保留原信息"""
        return f(*args, **kwargs)
    update_wrapper(wrapper_function, f)  
    return wrapper_function

@test_wrapper
def test_wrapped():
    """被裝飾的函數(shù)"""
    pass

@test_update_wrapper
def test_update_wrapped():
    """被裝飾的函數(shù),使用了update_wrapper()方法"""
    pass

print('不使用update_wrapper()方法:')
print(test_wrapped.__doc__) 
print(test_wrapped.__name__) 
print()
print('使用update_wrapper()方法:')
print(test_update_wrapped.__doc__) 
print(test_update_wrapped.__name__) 

執(zhí)行結(jié)果如下:

不使用update_wrapper()方法:
裝飾函數(shù),不保留原信息
wrapper_function

使用update_wrapper()方法:
被裝飾的函數(shù),使用了update_wrapper()方法
test_update_wrapped

可以看到,不使用 update_wrapper() 方法的話,函數(shù)在使用裝飾器后,它的一些基本屬性比如 __name__ 等都是正真執(zhí)行函數(shù)(比如上面的 wrapper_function() 函數(shù))的屬性。不過這個函數(shù)在分析視圖函數(shù)的處理流程上并不重要。接下來看 as_view 中定義的 view() 方法,它是真正執(zhí)行 HTTP 請求的視圖函數(shù):

def view(request, *args, **kwargs):
    self = cls(**initkwargs)
    # 如果有g(shù)et方法而沒有head方法,對于head請求則直接使用get()方法進(jìn)行處理
    if hasattr(self, 'get') and not hasattr(self, 'head'):
        self.head = self.get
    # 將Django對應(yīng)傳過來的請求實例以及相應(yīng)參數(shù)賦給實例屬性
    self.setup(request, *args, **kwargs)
    # 如果沒有request屬性,表明可能重寫了setup()方法,而且setup()里面忘記了調(diào)用super()
    if not hasattr(self, 'request'):
        raise AttributeError(
            "%s instance has no 'request' attribute. Did you override "
            "setup() and forget to call super()?" % cls.__name__
        )
    # 調(diào)用dispatch()方法
    return self.dispatch(request, *args, **kwargs)

view() 方法里面會調(diào)用 setup() 方法將 Django 給視圖函數(shù)傳遞的參數(shù)賦給實例變量,然后會調(diào)用 dispatch()方法去處理請求。兩個函數(shù)的代碼如下:

def setup(self, request, *args, **kwargs):
    """Initialize attributes shared by all view methods."""
    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)

這里最核心的就是這個 dispatch() 方法了。首先該方法通過 request.method.lower() 這個可以拿到 http 的請求方式,比如 get、post、put 等,然后判斷是不是在預(yù)先定義好的請求方式的列表中。如果滿足,那么最核心的代碼來了:

handler = getattr(self, request.method.lower(), self.http_method_not_allowed)

假設(shè)客戶端發(fā)的是 get 請求,那么 request.method.lower() 就是 “get” ,接下來執(zhí)行上面的代碼,就會得到我們定義的視圖類中定義的 get 函數(shù),最后返回的是這個函數(shù)的處理結(jié)果。這就是為啥 get 請求能對應(yīng)到視圖函數(shù)中g(shù)et() 方法的原因。其他的請求也是類似的,如果是不支持的請求,則會執(zhí)行 http_method_not_allowed() 方法。

return handler(request, *args, **kwargs)

如果對這部分代碼的執(zhí)行流程還有疑問的,我們可以在 Django 的源碼中添加幾個 print() 函數(shù),然后通過實際請求來看看執(zhí)行過程:

[root@server first_django_app]# cat ~/.pyenv/versions/django-manual/lib/python3.8/site-packages/django/views/generic/base.py
    class View:
    ...
    
    @classonlymethod
    def as_view(cls, **initkwargs):
        ...
        
        def view(request, *args, **kwargs):
            print('調(diào)用view函數(shù)處理請求')
            ...
            
    ... 
    
    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.
        print('調(diào)用dispatch()方法處理http請求,請求方式:{}'.format(request.method.lower()))
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            print('得到的handler:{}'.format(handler))
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)

接下來我們還是使用前面定義的視圖類 TestView 來進(jìn)行操作,操作過程以及實驗結(jié)果如下:

# 一個窗口啟動 django 工程
(django-manual) [root@server first_django_app]# python manage.py runserver 0.0.0.0:8888
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
April 15, 2020 - 04:30:04
Django version 2.2.11, using settings 'first_django_app.settings'
Starting development server at http://0.0.0.0:8888/
Quit the server with CONTROL-C.

# 另一個窗口發(fā)送http請求
[root@server django-manual]# curl -XGET http://127.0.0.1:8888/hello/test-cbv/
hello, get
[root@server django-manual]# curl -XPOST http://127.0.0.1:8888/hello/test-cbv/
hello, post
[root@server django-manual]# curl -XPUT http://127.0.0.1:8888/hello/test-cbv/
hello, put
[root@server django-manual]# curl -XDELETE http://127.0.0.1:8888/hello/test-cbv/
hello, delete

圖片描述

3. 小結(jié)

本小節(jié)中,我們簡單介紹了視圖類的使用以及一些高級用法。接下來我們分析了 Django 源碼中的 View 類以及 Django 是如何將請求映射到對應(yīng)的函數(shù)上執(zhí)行,這部分代碼是比較簡單易懂的。只有慢慢深入了解 Django 的源代碼,了解整個 Django 框架背后為我們做的事情,才能從入門到真正掌握 Django。