DRF概述


一. REST

1. 什么是編程?

數據結構和算法的結合

2. 什么是REST?

回顧曾經做過的圖書管理系統, 我們是這樣設計URL的:

   127.0.0.1:9001/books/
   127.0.0.1:9001/get_all_books/   訪問所有的數據
            
    127.0.0.1:9001/books/{id}/     
    127.0.0.1:9001/books/{id}?method=get   訪問單條數據
            
    127.0.0.1:9001/books/add/
    127.0.0.1:9001/books/?type=create   創建數據
            
    127.0.0.1:9001/books/delete/
            
    127.0.0.1:9001/books/update/   

以上定義的URL雖然也可以實現功能, 但是因個人習慣等的不同, 同一個功能會產生五花八門的URL, 而且響應回去的數據(包括錯誤提示等)格式也沒有統一的規范, 這就造成了前后端交互上的困難.

由此產生了REST. REST下的URL唯一代表資源, http請求方式區分用戶行為, 如下是符合REST規范的URL設計示例:

  url的設計規范:
    GET:     127.0.0.1:9001/books/           # 獲取所有數據
    GET:      127.0.0.1:9001/books/{id}      # 獲取單條數據
    POST:    127.0.0.1:9001/books/           # 增加數據
    DELETE:   127.0.0.1:9001/books/{id}      # 刪除數據
    PUT:      127.0.0.1:9001/books/{id}      # 修改數據
  數據響應規范:
    GET:     127.0.0.1:9001/books/          # 返回[{}, {}, {}]
    GET:      127.0.0.1:9001/books/{id}      # 返回單條數據{}
    POST:    127.0.0.1:9001/books/          # 返回添加成功的數據{}
    DELETE:   127.0.0.1:9001/books/{id}      # 返回空"" 
    PUT:      127.0.0.1:9001/books/{id}      # 返回{} ,更新后完整的一條記錄,注意並非一個字段
  錯誤處理:
        { "error": "error_message" }

REST是一種軟件架構設計風格, 不是標准, 也不是技術實現, 它只是提供了一組設計原則和約束條件, 是目前最流行的API設計規范, 用於web數據接口的設計.

參考鏈接:

http://www.ruanyifeng.com/blog/2018/10/restful-api-best-practices.html

http://www.scienjus.com/my-restful-api-best-practices/

那么, 我們接下來要學習的Django REST Framework與REST有什么關系呢?

事實上, DRF(Django REST Framework)是一套基於Django開發的, 幫助我們更好的設計符合REST規范的web應用的一個Django App, 所以, 從本質上來講, DRF是一個Django的App.

二. 知識准備

學習DRF之前, 首先回顧一下一個知識點:

1. CBV(class based view)

from django.views import View
class LoginView(View):
    def get(self, request):
        pass
    def post(self, request):
        pass

2. 類方法 classmethodclassonlymethod

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
    
    # 注意: Person類加載的時候,會執行裝飾器函數classmethod(sleeping),並將結果賦給sleeping
    @classmethod
    def sleeping(cls):	# 相當於 sleeping = classmethod(sleeping)
        print("Tom is sleeping")
        
    @classonlymethod
    def shopping(cls):
        print("Tom is shopping")
        
Person.sleeping()	# 類直接調用 類方法(用@classmethod裝飾)
Person.Angel()		# 類直接調用 方法(用@classonlymethod裝飾)

Tom = Person('Tom', '19')
Tom.sleeping()		# 對象可以直接調用 類方法
Tom.shopping()		# baocuo,對象不能調用由@classonlymethod裝飾的方法

總結:

  • @classmethod(python加的裝飾器)裝飾的方法可以被對象和類調用.
  • @classonlymethod(Django加的裝飾器)只能被調用.

3. 反射、 isinstance()

(1)getattr()

描述: getattr()函數通過name這個字符串去object中找名字叫做name的屬性, 如果找到了, 就返回這個對象的屬性值, 如果找不到, 就返回default

語法:

getattr(object, name[, default])

參數:

  • object -- 對象
  • name -- 字符串, 對象的屬性
  • default -- 默認返回值, 如果不提供參數, 在沒有對應屬性時, 將觸發AttributeError

返回值: 返回對象屬性


(2)hasattr()

描述: hasattr()函數用於判斷對象是否包含對應的屬性.

語法:

hasattr(object, name)

參數:

  • object -- 對象
  • name -- 字符串, 屬性名

返回值: 如果對象object中存在一個名字叫做name的屬性, 那么就返回True, 否則返回False.


(3)setattr()

描述: setsttr()函數對應函數getsttr(), 用於給對象objectname的屬性重新設置值, 注意該屬性name必須存在.

語法:

setattr(object, name, value)

參數:

  • object -- 對象
  • name -- 字符串, 對象屬性
  • value -- 屬性值

返回值: 無

(4)isinstance()

描述: isinstance()函數用來判斷一個對象是否是一個已知的類型.

# isinstence()與type()的區別:
-- type()不會認為子類是一種父類類型,不考慮繼承關系.
-- isinstence()會認為子類是一種父類類型,考慮繼承關系.
如果要判斷兩個類型是否相同推薦使用isinstence().

語法:

isinstence(object, classinfo)

參數:

  • object -- 實例對象
  • classinfo -- 可以是直接或間接類名, 基本類型或者由它們組成的元組

返回值: 如果對象的類型與參數二的類型(classinfo)相同則返回True, 否則返回False.

4. self定位

我們要明確, self指向的始終是調用者.

5. http請求協議

協議就是溝通雙方約定俗成的規范, 也即是解析數據的規則

6. form表單的enctype屬性中有三種請求協議

如果通過form表單提交用戶數據, 可以使用form表單的enctype屬性來定義數據編碼協議, 該屬性有三個值, 代表三種數據編碼協議:

  • application/x-www-form-urlencoded: 該值使用&符號鏈接多個鍵值對, 鍵值對用等號拼接. 該值是enctype屬性的默認值.
  • multipart/form-data: 上傳文件, 圖片時使用該值.
  • text/plain: 空格轉換為+號.

7. JavaScript中的object(如: {name:'alex'} <==> json)的相互轉換方式

JSON.stringify(data)  ==> 相當於python的 json.dumps()
JSON.parse(data)      ==> 相當於python的 json.loads()

三. Django REST Framework ( DRF )

1. 為什么要使用DRF?

DRF從本質上來講, 它就是一個Django的App, 有了這樣一個App, 我們就可以更好的設計出符合RESTful規范的web應用. 實際上, 即便沒有DRF, 我們也能夠自行設計出符合RESTful規范的web應用, 如下示例:

from django.shortcuts import HttpResponse
from django.views import View
from * import models
import json
class CourseView(View):
    def get(self, request):
        course_list = list()
        
        for course in models.Course.objects.all():
            course = {
                'course_name': course.course_name,
                'description': course.description,
            }
            course_list.append(course)
	
    	return HttpResponse(json.dumps(course_list, ensure_ascii=False))

在上面的代碼中, 我們獲取所有的課程數據, 並且根據REST規范, 將所有資源通過列表返回給用戶, 可見, 就算沒有DRF, 我們也能夠設計出符合RESTful規范的接口, 甚至是整個App應用. 但是, 如果所有的接口都自定義, 難免會出現重復代碼, 為了提高工作效率, 我們建議使用優秀的工具, DRF就是這樣一個優秀的工具, 另外, 它不僅能夠幫助我們快速的設計出符合RESTful規范的接口, 還提供了諸如 認證 , 權限 等等其他強大的功能.

2. 什么時候使用DRF?

RESTful 是目前最流行的API設計規范, 如果使用Django開發你的web應用, 那么請盡量使用DRF, 如果使用的是Flask, 則可以使用Flask-RESTful.

3. DRF的使用

DRF官方文檔

DRF安裝

# 安裝django
pip install django

# 安裝djangorestframework
pip install djangorestframework

安裝完成以后, 我們就可以開始使用DRF框架來實現我們的web應用了. 該框架包含以下知識點:

  - APIView
  - 解析器組件
  - 序列化組件
  - 視圖組件
  - 認證組件
  - 權限組件
  - 頻率控制組件
  - 分頁組件
  - 相應器組件
  - url控制

4. APIView

介紹DRF, 必須介紹APIView, 它是重中之重, 是下面所有組件的基礎, 因為所有的請求都是通過APIView來分發的. 那么它究竟是如何來分發請求的呢? 想要弄明白這個問題, 就必須解讀它的源碼, 而想要解讀DRF的APIView源碼, 就必須首先弄清楚django中views.View類的源碼, 為什么使用了視圖函數類調用as_view()之后, 請求就可以被不同的函數處理了呢?

(1)回顧CBV, 解讀View源碼

# urls.py中代碼如下:
from django.urls import path, include, re_path
from classbasedView import views
urlpatterns = [
    re_path('login/$', views.LoginView.as_view())
]

# views.py中代碼如下:
from django.views import View
class LoginView(View):
    def get(self, request):
        pass
    def post(self, request):
        pass

以上代碼執行流程如下(源碼解讀):

  • (1)啟動django項目, 即執行了python manage.py runserver 127.0.0.1:8000之后

  • (2)開始加載settings.py配置文件

    • 讀取models.py
    • 加載views.py
    • 加載urls.py, 執行as_view(): views.LoginView.as_view()
  • (3)由於LoginView中沒有as_view, 因此執行父類View中的as_view方法, 父類View的相關源碼如下:

class View:
    http_method_names = ['get', 'post', 'put', ...]
    def __init__(self, **kwargs):
        for key, value in kwargs.items():
            setattr(self, key, value)
            
    @classonlymethod
    def as_view(cls, **initkwargs):
        for key in initkwargs:
            ...
        def view(request, * args, **kwargs):
            """
            實例化一個對象,對象名稱為self,self是cls的對象,誰調用了as_view,cls就是誰(當前調用as_view的是LoginView),所以,此時的self就是LoginView實例化對象.
            """
            self = cls(**initkwargs)
            if hassttr(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)
    	view.view_class = cls
        view.view_initkwargs = initwargs
        
        update_wrapper(view, cls, updated=())
        update_wrapper(view, cls.dispatch, assigned=())
        return view
    
    def dispatch(self, request, *args, **kwargs):
        if request.method.lower() in self.http_method_name:
            # 通過getattr找到的屬性,已和對象綁定,訪問時不需要再指明對象了
            # 即不需要再: self.handler, 直接handler()執行
            handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed
        return handler(request, *args, **kwargs)            

上面的源碼中可以看出as_view是一個類方法, 並且方法中定義了view函數, 且as_view將view函數返回, 此時url與某一個函數的對應關系建立, 並開始等待用戶請求.

  • (4)當用戶發來請求(如get請求), 開始執行url對應的view函數, 並傳入request對象, View類中view函數的執行結果是返回self.dispatch(request, *args, **kwargs)的執行結果, 這里的self是指LoginView的實例化對象. 由於LoginView中沒有dispatch方法, 所以去執行父類APIView中的dispatch方法, 同樣, APIView類中的dispatch函數中也是通過反射找到self(此時self指LoginView的實例化對象)所屬類(即LoginView)中的get方法, dispatch函數中的handler(request, *args, **kwargs)表示執行LoginView類中的get方法, 其執行結果就是dispatch的執行結果, 也就是請求url對應view函數的執行結果, 最后將結果返回給用戶.

(2)APIView

使用:

# views.py中代碼:
from rest_framework.views import APIView	# 引入APIView
class LoginView(APIView):	# 繼承APIView
    def get(self, request):
        pass
    def post(self, request):
        pass
    
# urls.py中代碼:
from django.urls import path, include, re_path
from classbasedView import views
urlspatterns = [
    re_path('login/$', views.LoginView.as_view())
]

源碼解讀:

  • (1)啟動django項目: python manage.py runserver 127.0.0.1:8000
  • (2)開始加載settings.py文件
  • (3)讀取models.py文件
  • (4)加載views.py文件
  • (5)加載urls.py文件, 執行as_view(): views.LoginView.as_view()
  • (6)由於LoginView中沒有as_view, 因此去執行父類APIView中的as_view方法, 父類APIView的相關源碼如下:
class APIView(View):
    ...
    # api_settings是APISettings類的實例化對象
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    ...
    settings = api_setings
    schema = DefaultSchema()
    
    @classmethod
    def as_view(cls, **initkwargs):		# cls指LoginView
        if isinstence(getattr(cls, 'queryset', None), models.query.Queryset):
            ...
        # 下面一句表示去執行APIView父類(即View類)中的as_view方法
        view = super(APIView, cls).as_view(**initkwargs)
        view.cls = cls
        view.initkwargs = initkwargs
        return csrf_exempt(view)
    
    def dispatch(self, request, *args, **kwargs):
        ...
        request = self.initialize_request(request, *args, **kwargs)
        ...
        try:
            self.initial(request, *args, **kwargs)
            if request.method.lower() in self.http_method_name:
                handler = getattr(self, request.method.lower(), self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
           	response = self.handler(request, *args, **kwargs)
        except Exception as exc:
            response = self.handle_exception(exc)
        
        self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response

參考上面的View源碼解讀, 我們已經知道View中的as_view方法返回view函數, 此時url與view對應關系已經建立, 等待用戶請求.

  • (7)當用戶發來請求(如get請求), 開始執行url對應的view函數, 並傳入request對象, View類中view函數的執行結果是返回self.dispatch(request, *args, **kwargs)的執行結果, 這里的self是指LoginView的實例化對象, LoginView中沒有dispatch方法, 所以去執行父類APIView中的dispatch方法, 同樣, APIview類中的dispatch函數中也是通過反射找到self(此時self指LoginView的實例對象)所屬類(即LoginView)中的get方法, dispatch函數中的handler(request, *args, **kwargs)表示執行LoginView類中的get方法, 其執行結果就是dispatch的執行結果, 也就是請求url對應view函數的執行結果, 最后將結果返回給用戶.

四. 補充知識點

1. 關於裝飾器

若類中有裝飾器函數, 那么當類加載的時候, 裝飾器函數就會執行, 如下代碼:

class Person(object):
    @classmethod	# 相當於 sleeping = classmethod(sleeping)
    def sleeping(cls):
        print('Tom is sleeping')
    
    print(sleeping)	
    # 加載類時執行,結果是:<classmethod object at 0x000001F2C29C8198>

注意: 類中直接print會執行打印並輸出結果, 而函數只有調用時才會執行, 如下所示:

def func():
    print('Hello World!')
    
# 函數func 加載 不會執行打印
# 而只有當我們調用 func() 才會執行打印

2. __dict__方法

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    def sing(self):
        print('I am singing!')

p1 = Person('alex', 18)
print(p1.__dict__)	# {'name':'alex', 'age':18}
print(Person.__dict__)
'''
{'__module__': '__main__', 
'__init__': <function Person.__init__ at 0x0000021E1A46A8C8>, 
'sing': <function Person.sing at 0x0000021E1A46A950>, 
'__dict__': <attribute '__dict__' of 'Person' objects>, 
'__weakref__': <attribute '__weakref__' of 'Person' objects>, 
'__doc__': None}
'''

總結:

  • 對象.__dict__ 返回對象的所有成員字典;
  • 類.__dict__ 返回類的所有成員字典;

我們可以通過 對象.name 取出成員, 字典沒有這種取值方式, 使用 對象.name 的本質是執行類中的 __getitem__ 方法.

3. 對程序進行功能擴展的兩種方式

現在有如下兩種需求:

# 需求一: 計算下面add函數的執行時間(不重寫add函數的前提下)
def add(x, y):
    return x + y

# 解決方式: 裝飾器
def outer(func):
    def inner(*args, **kwargs):
        import time
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print(end_time - start_time)
    return inner

@outer
def add(x, y):
    return x + y
# 需求二: 擴展類中函數的功能(在不重寫Father類的前提下)
class Father(object):
    def show(self):
        print('father show is excuted!')

father = Father()
father.show()

# 解決方式: 重新寫一個類, 繼承Father類, 重寫show()方法, 用super()調用
class Son(Father):
    def show(self):
        print('son show is excuted!')
        super().show()

son = Son()
son.show()

總結:

  • 面向過程的方式對程序功能進行擴展:
    • 裝飾器
  • 面向對象的方式對程序功能進行擴展:
    • 類的繼承
    • 方法重寫
    • super()執行父類的方法

4. super()函數

描述: super()函數是用於調用父類(超類)的一個方法. super是用來解決多重繼承問題, 直接用類名調用父類方法在使用單繼承的時候沒有問題, 但是如果使用多繼承, 會涉及到查找順序(MRO)、重復調用(鑽石繼承)等種種問題.

MRO就是類的方法解析順序表, 其實也就是繼承父類方法時的順序表.

語法:

super(type[, object-or-type])

參數:

  • type -- 類
  • object-or-type -- 類, 一般是self

返回值: 無

注意: Python3.x 和 Python2.x的一個區別是: Pyhton3 可以直接使用 super().xxx 代替 super(Class, self).xxx.

Python3.x與Python2.x中super()的用法對比:

# Python3.x實例:
class A:
    pass
class B(A):
    def add(self, x):
        super().add(x)
        
# Python2.x實例:
class A(object):	# Python2.x記得要繼承object
    pass
class B(A):
    def add(x):
        super(B, self).add(x)

實例說明:

class FooParent(object):
    def __init__(self):
        self.parent = 'I am the parent!'
        print('Parent')
    
    def bar(self, message):
        print('%s from Parent!' % message)
        
class FooChild(FooParent):
    def __init__(self):
        '''
        以super(B, self)的格式作為參照,
        super(FooChild, self)首先找到FooChild的父類(即FooParent),
        然后把類B(即super()的第一個參數)的對象FooChild轉換為FooParent的對象
        '''
        super(FooChild, self).__init__()
        print('Child')
        
    def bar(self, message):
        super(FooChild, self).bar(message)
        print('Child bar function!')
        print(self.parent)
        
if __name__ == '__main__':
    fooChild = FooChild()
    fooChild.bar('HelloWorld')
    
    
##執行結果:
# Parent
# Child
# HelloWorld from Parent!
# Child bar fuction!
# I am the parent!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM