DRF之REST規范介紹及View請求流程分析


  編程是數據結構和算法的結合,而在Web類型的App中,我們對於數據的操作請求是通過url來承載的,本文詳細介紹了REST規范和CBV規范和CBV請求流程。

  編程即數據結構和算法的結合。簡單的程序可能不需要跟用戶交互數據,但是現代的應用程序幾乎都需要跟用戶進行交互,不分應用程序類型,不管是CS型還是BS型的程序都是如此,而Python最擅長的Web App即BS型的程序,就是通過url和http來跟用戶進行數據交互的,通過url和http請求,用戶可以操作服務器端的程序,主要操作分為:增、刪、改、查幾類。

  傳統url設計風格

    url各式各樣,設計混亂

  理論上來說,這種方式完全可以實現我們的需求,但是一旦項目豐富起來,隨着數據量增加,隨着各個業務系統之間的邏輯關系不斷的復雜,url會越來越復雜,理論上來說,不管是什么類型、什么名稱的url都能指向具體的業務邏輯(視圖函數),從而實現業務需求,但是如果沒有明確的規范,因每個人的思維方式不一樣、命名方式不一樣而導致的url非常的亂,不方便項目的后期維護和擴展。

  • 對於請求處理成功或者失敗的返回信息沒有明確的響應信息規范,返回給客戶端的信息往往都是很隨意的

  以上這些情況的出現,導致了很多的問題,讓互聯網的世界變得雜亂不堪,日益復雜且臃腫。因此http協議創始人警告我們這些凡人們正在錯誤的使用http協議,除了警告,他還發表了一篇博客,大概意思就是教大家如何正確使用http協議,如何正確定義url,這就是REST(Representational State Transfer),不需要管這幾個英文單詞代表什么意思,只需要記住下面一句話:

  • 用url唯一定位資源,用Http請求方式(GET, POST, DELETE, PUT)描述用戶行為

  這句話很重要,一定要記住喔!

  根據這句話,我們重新定義圖書管理系統中的url

RESTful Api設計風格

  這種方式簡單易懂,服務器只需要根據用戶的請求方式對資源進行各種操作。

 

  每一種請求方式的返回結果不同

  REST定義錯誤信息

{
    "error": "Invalid API key"
}

  通過一個字典,返回錯誤信息,這就是REST,上圖中的url就是根據REST規范進行設計的RESTful api. 

知識點復習回顧一:基於類的視圖(ClassBasedView)

  請看如下代碼,基於類的視圖,需要通過視圖類調用as_view()這個類方法,從而實現請求的分發。

urls.py部分:

from django.urls import path

from classbasedview import views

urlpatterns = [
    path('login/', views.LoginView.as_view()),
]

 view.py部分

我們需要引入View這個類,from django.views import View

from django.shortcuts import render, HttpResponse
from django.views import View

# Create your views here.


class LoginView(View):
    def get(self, request):
        return render(request, 'classbasedview/login.html')

    def post(self, request):
        return HttpResponse("post")
View.py

  這樣就實現了一個基於類的視圖,請大家以后都使用基於類的視圖,這是生產環境中常用的一種方式,之前使用基於函數的視圖,是為了方便大家學習理解,基於類的視圖采用了面向對象編程思路,這種CBV的方式更加靈活,方便維護和擴展,以后,如果我們需要在url和處理請求的邏輯之間綁定關系的過程中自定制更多的操作,都會簡單方便。后面的課程要介紹的Django RestFramework也正是依賴這種設計思路,來進行的功能擴展。

知識點復習回顧二:classmethod和classonlymethod

在學習面向對象時,我們學習過classmethod,它是一個裝飾器,表示允許類直接調用該方法而不用傳入實例對象,如下代碼所示:

# -*- coding: utf-8 -*-
from django.utils.decorators import classonlymethod

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show_info(self):
        print("show info method been executed")

    @classmethod
    def show_info2(cls):
        print("show info2 method been executed")

    @classonlymethod
    def show_info3(cls):
        print("show info3 method been executed")


p1 = Person("pizza", 18)
# 普通方法可以通過實例對象和類調用
# 通過實例調用時默認傳入self作為第一個參數,不需要手動傳入參數
# 通過類調用時,必須手動指定實例對象傳遞給該方法
p1.show_info()
Person.show_info(p1)

# 被classmethod裝飾器裝飾后的方法可以通過實例對象和類直接調用
# 默認傳入類名作為第一個參數
p1.show_info2()
Person.show_info2()

p1.show_info3()      # 報錯,Django框架實現了classonlymethd,不再允許實例對象調用被該裝飾器裝飾的方法
Person.show_info3()

知識點復習回顧三:hasattr、getattr、setattr

同樣的,在學習面向對象時,我們學習過屬性判斷、屬性查找、屬性設置,接下來,再回顧一下:

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age
        self._hobby = 'girls'

    def show_info(self):
        print("show info method been executed")


p1 = Person("pizza", 18)
# 查看該對象是否有show_info方法
print(hasattr(p1, "show_info"))
# 查看該對象是否有age屬性
print(hasattr(p1, "age"))
print(hasattr(p1, "hahaha"))

greeting = "Hello World"
# 設置屬性
setattr(p1, "hahaha", greeting)
print(hasattr(p1, "hahaha"))

func = getattr(p1, "show_info")
print(func)  # <bound method Person.show_info of <__main__.Person object at 0x102219d68>>
# 注意:直接調用,不需要傳入self,getattr時已經綁定self到func了
func()

print(hasattr(Person, "show_info"))  # True
print(hasattr(Person, "name"))       # False
print(hasattr(Person, "country"))    # False
# 給Person類設置屬性
setattr(Person, "country", "china")
print(hasattr(Person, "country"))    # True
print(hasattr(p1, "country"))        # True

知識點復習回顧四:self定位

關於self到底指向哪里,我們再回顧一下:

# -*- coding: utf-8 -*-
class Request(object):

    def show_request(self):
        print(self)


class NewRequest(Request):

    def show_request(self):
        print(self)


request = NewRequest()
# 調用方法的實例對象是哪個,self就指向哪個對象
request.show_request()

知識點復習回顧五:Http請求協議

Http請求報文包含三部分:分別是請求行、請求報文頭、請求報文體

POST /classbasedview/login/ HTTP/1.1
Host: 127.0.0.1:9001
Connection: keep-alive
Content-Length: 114
Cache-Control: max-age=0
Origin: http://127.0.0.1:9001
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Referer: http://127.0.0.1:9001/classbasedview/login/
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: csrftoken=xPHeedcs8duBuCv031bCvsG1zMX1aGNpKlsPdCkhQHICd4lvMeHIEwkpJUQbLiOl; is_login=True; username=alex; last_time="2018-11-24 13:37:33"; fruit=xiangjiao

注意:post請求類型有formdata,而get沒有,下面詳細介紹幾個常見的請求頭信息:

  • POST /classbasedview/login/ HTTP/1.1:第一行表示請求行,分別是請求方式(POST),請求URL,HTTP協議版本

  • HOST:請求行下面的都是請求報文,HOST表示請求的主機和端口號

  • Connection:請求連接方式

  • Content-Length:內容長度(字節)

  • Content-Type:請求數據的編碼協議,只有POST,DELETE等請求方式有該部分內容(需重點記憶)

  • User-Agent:客戶端信息

  • Accept:可接受的響應內容類型

  • Accept-Encoding:采用什么方式對請求數據進行編碼

  • Cookie:服務器端設置的cookie

下面是請求報文體:

csrfmiddlewaretoken=EqHslTSOeI6TWMXwmFCTuLLeflkjWSJTgUdLeFw1Xtxp5S1ea8vYo3IOX7DEPnO4&username=pizzali&password=aaa

   不同的Content-Type,請求報文體的格式不一樣,application/x-www-form-urlencoded使用&符合來拼接請求的鍵值對,最后,本質上,還是通過socket將以上內容編碼成字節數據,發送到服務器端,服務器則根據Content-Encoding先將數據整體解碼,之后再通過Content-Type指定的編碼協議來讀取請求數據。

通俗一點理解,我們可以使用任何連接符(&)或者(……),只要服務器端還客戶端互相認可即可。因為協議就是為了方便溝通雙方進行信息交互的。

知識點復習回顧七:JavaScript JSON格式轉換

  最后,我們來復習一下JavaScript中的object數據類型和Json類型時間的互相轉換,Json數據是網絡傳輸中非常流行且使用廣泛的數據格式,我們知道在Python中,將一個字典轉換為Json格式,使用如下方式:

>>> dict_data = {
        "name": "Pizza",
        "age": 18
    }

>>> import json
# 通過json模塊的json.dumps(data)方式將Python中的字典類型轉換為json類型
>>> json_data = json.dumps(di)

>>> print(type(json_data))
<class 'str'>

 而如何將Json數據轉換為Python的字典類型呢?請看下面的代碼:

# 通過json.loads(data)可以將json數據轉換為Python中的字典類型
>>> dict_data2 = json.loads(json_data)

>>> print(type(dict_data2))
<class 'dict'>

 那么在JavaScript中,如果實現將數據類型(數組,object)轉換為Json類型呢?請看下圖

 今日詳細:

  概念

REST是一種軟件架構設計風格,不是標准,也不是具體的技術實現,只是提供了一組設計原則和約束條件。是目前最流行的 API 設計規范,用於 Web 數據接口的設計。2000年,由Roy Fielding在他的博士論文中提出,Roy Fielding是HTTP規范的主要編寫者之一。

那么,我們所要講的Django RestFramework與rest有什么關系呢?

其實,DRF(Django RestFramework)是一套基於Django開發的、幫助我們更好的設計符合REST規范的Web應用的一個Django App,所以,本質上,它是一個Django App。

  為什么使用DRF?

  從概念就可以看出,有了這樣的一個App,能夠幫助我們更好的設計符合RESTful規范的Web應用,實際上,沒有它,我們也能自己設計符合規范的Web應用。下面的代碼演示如何手動實現符合RESTful規范的Web應用。

class CoursesView(View):
    def get(self, request):
        courses = list()

        for item in Courses.objects.all():
            course = {
                "title": item.title,
                "price": item.price,
                "publish_date": item.publish_date,
                "publish_id": item.publish_id
            }

            courses.append(course)

        return HttpResponse(json.dumps(courses, ensure_ascii=False))

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

  什么時候使用DRF?前面提到,REST是目前最流行的 API 設計規范,如果使用Django開發你的Web應用,那么請盡量使用DRF,如果使用的是Flask,可以使用Flask-RESTful。

  開始使用DRF

  DRF安裝

DRF官方文檔中有詳細介紹。使用如下命令安裝,首先安裝Django,然后安裝DRF:

>>> pip install django
>>> pip install djangorestframework

   安裝完成之后,我們就可以開始使用DRF框架來實現咱們的Web應用了,這部分內容包括如下知識點:

  • APIView

  • 解析器組件

  • 序列化組件

  • 視圖組件

  • 認證組件

  • 權限組件

  • 頻率控制組件

  • 分頁組件

  • 響應器組件

  • url控制器

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

  Django View請求流程源碼

  我們一起來分析一下django中views.View類的源碼。請看下圖:

    

  請仔細回想我們前面回顧的知識點,源碼中最后會通過getattr在self中查找

  request.method.lower(),也就是get、post或者delete這些方法中的一個,那么,self是誰,就是至關重要的一點,前面講到過,誰調用類中的方法,self就指向誰,此時,一層層往回找,我們會發現,self = cls(**initkwargs),self就是我們視圖類的實例化對象,所以,dispatch函數肯定會到該視圖類中找對應的方法(get或者post)。

  接下來是提問時間,請問如果有如下函數,我想通過不修改函數內容的情況下,如何給函數新增一個統計執行時間的功能:

def outer(func):
    def inner(*args, **kwargs):
        import time
        start_time = time.time()
        ret = func(*args, **kwargs)
        end_time = time.time()
        print("This function elapsed %s" % str(end_time - start_time))
        return ret
    return inner


@outer
def add(x, y):
    return x + y

 這是函數,如果是類呢?面向對象編程,如何擴展你的程序,比如有如下代碼:

class Person(object):
    def show(self):
        print("Person's show method executed!")
        

class MyPerson(Person):
    def show(self):
        print("MyPerson's show method executed")
        super().show()
        

mp = MyPerson()
mp.show()

   這就是面向對象的程序擴展,現在大家是否對面向對象有了更加深刻的認識呢?接下來給大家十分鍾時間,消化一下上面兩個概念,然后請思考,那么假設你是Django RestFramework的開發者,你想自定制一些自己想法,如何實現。

  好了,相信大家都已經有了自己的想法,接下來,我們一起來分析一下,Django RestFramework的APIView是如何對Django框架的View進行功能擴展的。

from django.shortcuts import HttpResponse

import json

from .models import Courses

# 引入APIView
from rest_framework.views import APIView
# Create your views here.


class CoursesView(APIView):  # 繼承APIView而不是原來的View
    def get(self, request):
        courses = list()

        for item in Courses.objects.all():
            course = {
                "title": item.title,
                "description": item.description
            }

            courses.append(course)

        return HttpResponse(json.dumps(courses, ensure_ascii=False))

  DRF APIView請求流程源碼

其他的保持不變,完整的請求流程請看下圖:

 

  以上就是Django RestFramework APIView的請求處理流程,我們可以通過重寫dispatch()方法或者重寫as_view()方法來自定制自己的想法。

   Ajax發送Json數據給服務器

  接下來,我們就開始介紹Django RestFramework中的這些組件,首先,最基本的,就是解析器組件,在介紹解析器組件之前,我提一個問題,請大家思考,如何發送Json格式的數據給后端服務器?

好了,時間到,請看下面的代碼,通過ajax請求,我們可以發送json格式的數據到后端:

 

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
  <script src="/static/jquery-1.10.2.min.js"></script>
</head>
<body>
  <form action="" method="post" enctype="application/x-www-form-urlencoded">
    {% csrf_token %}
    用戶名: <input type="text" name="username"/>
    密碼:  <input type="password" name="password"/>
    提交:  <input type="submit" value="提交"/>
  </form>

  <hr>
  <button class="btn">點擊發送Ajax請求</button>

  <script>
    $(".btn").click(function () {
      $.ajax({
        url: '',
        type: 'post',
        contentType: 'application/json',
        data: JSON.stringify({
          username: "alex",
          password: 123
        }
        ),
        success: function (data) {
          console.log(data);
        }
      })
    })

  </script>

</body>
</html>

   通過上文的知識點復習我們已經知道,Content-Type用來定義發送數據的編碼協議,所以,在上面的代碼中,我們指定Content-Type為application/json,即可將我們的Json數據發送到后端,那么后端如何獲取呢?

  服務器對Json數據的處理方式

  按照之前的方式,我們使用request.POST, 如果打印該值,會發現是一個空對象:request post <QueryDict: {}>,該現象證明Django並不能處理請求協議為application/json編碼協議的數據,我們可以去看看request源碼,可以看到下面這一段:

if self.content_type == 'multipart/form-data':
    if hasattr(self, '_body'):
        # Use already read data
        data = BytesIO(self._body)
    else:
        data = self
    try:
        self._post, self._files = self.parse_file_upload(self.META, data)
    except MultiPartParserError:
        # An error occurred while parsing POST data. Since when
        # formatting the error the request handler might access
        # self.POST, set self._post and self._file to prevent
        # attempts to parse POST data again.
        # Mark that an error occurred. This allows self.__repr__ to
        # be explicit about it instead of simply representing an
        # empty POST
        self._mark_post_parse_error()
        raise
elif self.content_type == 'application/x-www-form-urlencoded':
    self._post, self._files = QueryDict(self.body, encoding=self._encoding), MultiValueDict()
else:
    self._post, self._files = QueryDict(encoding=self._encoding), MultiValueDict() 

  可見Django原生解析器並不處理application/json編碼協議的數據請求,好了,有了這樣的認識之后,咱們就可以開始正式介紹DRF的解析器了,解析器,顧名思義,就是用來解析數據的請求的。

  雖然Django的原生解析器不支持application/json編碼協議,但是我們可以通過拿到原始的請求數據(request.body)來手動處理application/json請求,雖然這種方式不方便,也並不推薦,請看如下代碼:

class LoginView(View):
    def get(self, request):
        return render(request, 'classbasedview/login.html')

    def post(self, request):
        print(request.POST)  # <QueryDict: {}>
        print(request.body)  # b'{"username":"alex","password":123}'
        data = request.body.decode('utf-8')
        dict_data = json.loads(data)

        username = dict_data['username']
        password = dict_data['password']

        return HttpResponse(json.dumps(dict_data))

   通過上面的代碼,我們可以通過request.body手動處理application/json請求,不過,如上文所說,並不推薦。

postman工具介紹

  今天最后一項內容,就是postman這個工具的介紹,我們通過postman來模擬用戶請求,簡單方便,不再需要使用瀏覽器來發請求了。

安裝postman

  從POSTMAN官網下載之后安裝即可。

  發送get請求

  請看下面的圖片:

DRF如圖所示,在下拉框中選擇GET方式發送請求,在地址欄輸入請求url,然后點擊send發送即可發送GET請求到后端服務器。

以urlencoded協議發送post請求

請看下圖:

DRF       選擇POST,輸入地址,然后在下面的選項框中選擇Body,之后選擇x-www-form-urlencoded協議,輸入key和value,點擊Send,即可以urlencoded方式發送post請求,並且在最下面的提示框中可以查看到服務器返回的信息。

  以json協議發送post請求

  最后,請看下圖,以Json協議發送POST請求:

DRF

  前面都一致,在Body框中,選擇raw選項,然后選擇application/json,最后輸入Json格式的數據,點擊Send即可以Json協議發送POST請求。

   

  注:本篇博客參考自:https://pizzali.github.io/

 


免責聲明!

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



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