編程是數據結構和算法的結合,而在Web類型的App中,咱們對於數據的操做請求是經過url來承載的,本文詳細介紹了REST規範和CBV請求流程。css
編程是數據結構和算法的結合,小程序如簡單的計算器,咱們輸入初始數據,通過計算,獲得最終的數據,這個過程當中,初始數據和結果數據都是數據,而計算過程是咱們所說的廣義上的算法。html
大程序,如一個智能掃地機器人,咱們能夠設置打掃的距離,左右擺動的幅度來打掃房間,這裏面打掃的舉例,擺動幅度,都是數據,而打掃的過程是較爲複雜的算法過程,總之,也是算法,即程序的實現方式。jquery
另外,咱們還能夠設置打掃時間等等初始數據。git
總之一句話,編程即數據結構和算法的結合。簡單的程序可能不須要跟用戶交互數據,可是現代的應用程序幾乎都須要跟用戶進行交互,不分應用程序類型,無論是CS型仍是BS型的程序都是如此,而Python最擅長的Web App即BS型的程序,就是經過url和http來跟用戶進行數據交互的,經過url和http請求,用戶能夠操做服務器端的程序,主要操做分爲:增、刪、改、查幾類。github
引入
在開始以前,咱們回顧一下我們以前寫過的圖書管理系統項目,請仔細回想一下,對於該項目的設計,咱們大概是如下面這種方式來實現的web
傳統url設計風格
理論上來講,這種方式徹底能夠實現咱們的需求,可是一旦項目豐富起來,隨着數據量增長,隨着各個業務系統之間的邏輯關係不斷的複雜,url會愈來愈複雜,理論上來講,無論是什麼類型、什麼名稱的url都能指向具體的業務邏輯(視圖函數),從而實現業務需求,可是若是沒有明確的規範,因每一個人的思惟方式不同、命名方式不同而致使的url很是的亂,不方便項目的後期維護和擴展。ajax
- 對於請求處理成功或者失敗的返回信息沒有明確的響應信息規範,返回給客戶端的信息每每都是很隨意的
以上這些狀況的出現,致使了不少的問題,讓互聯網的世界變得雜亂不堪,日益複雜且臃腫。所以http協議創始人警告咱們這些凡人們正在錯誤的使用http協議,除了警告,他還發表了一篇博客,大概意思就是教你們如何正確使用http協議,如何正肯定義url,這就是REST(Representational State Transfer),不須要管這幾個英文單詞表明什麼意思,只須要記住下面一句話:算法
- 用url惟必定位資源,用Http請求方式(GET, POST, DELETE, PUT)描述用戶行爲
根據這句話,咱們從新定義圖書管理系統中的urldjango
RESTful Api設計風格
能夠看到,url很是簡潔優雅,不包含任何操做,不包含任何動詞,簡簡單單,用來描述服務器中的資源而已,服務器根據用戶的請求方式對資源進行各類操做。而對數據的操做,最多見的就是CRUD(建立,讀取,更新,刪除),經過不一樣的請求方式,就足夠描述這些操做方式了。若是不夠用,Http還有其餘的請求方式呢!好比:PATCH,OPTIONS,HEAD, TRACE, CONNECT。編程
REST定義返回結果
每一種請求方式的返回結果不一樣。
REST定義錯誤信息
1 2 3
|
{ "error": "Invalid API key" }
|
經過一個字典,返回錯誤信息。
這就是REST,上圖中的url就是根據REST規範進行設計的RESTful api。
今日概要
- classbasedview請求處理流程源碼剖析
- djangorestframework APIView請求處理流程源碼剖析
- postman工具介紹
知識點複習回顧
- 上節回顧
- CBV
- classmethod和classonlymethod
- hasattr, getattr
- self定位
- Http請求協議
- form表單enctype
- JavaScript JSON格式轉換
知識點複習回顧一:基於類的視圖(ClassBasedView)
請看以下代碼,基於類的視圖,須要經過視圖類調用as_view()這個類方法,從而實現請求的分發。
urls.py部分:
1 2 3 4 5 6 7
|
from django.urls import path
from classbasedview import views
urlpatterns = [ path('login/', views.LoginView.as_view()), ]
|
view.py部分
咱們須要引入View這個類,from django.views import View
1 2 3 4 5 6 7 8 9 10 11 12
|
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")
|
這樣就實現了一個基於類的視圖,請你們之後都使用基於類的視圖,這是生產環境中經常使用的一種方式,以前使用基於函數的視圖,是爲了方便你們學習理解,基於類的視圖採用了面向對象編程思路,這種CBV的方式更加靈活,方便維護和擴展,之後,若是咱們須要在url和處理請求的邏輯之間綁定關係的過程當中自定製更多的操做,都會簡單方便。後面的課程要介紹的Django RestFramework也正是依賴這種設計思路,來進行的功能擴展。
知識點複習回顧二:classmethod和classonlymethod
在學習面向對象時,咱們學習過classmethod,它是一個裝飾器,表示容許類直接調用該方法而不用傳入實例對象,以下代碼所示:
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
|
# -*- 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
一樣的,在學習面向對象時,咱們學習過屬性判斷、屬性查找、屬性設置,接下來,再回顧一下:
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
|
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到底指向哪裏,咱們再回顧一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
# -*- 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請求報文包含三部分:分別是請求行、請求報文頭、請求報文體
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
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
下面是請求報文體:
1
|
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格式,使用以下方式:
1 2 3 4 5 6 7 8 9 10 11
|
>>> 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的字典類型呢?請看下面的代碼:
1 2 3 4 5
|
# 經過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應用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
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:
1 2
|
>>> pip install django >>> pip install djangorestframework
|
安裝完成以後,咱們就能夠開始使用DRF框架來實現我們的Web應用了,這部份內容包括以下知識點:
- APIView
- 解析器組件
- 序列化組件
- 視圖組件
- 認證組件
- 權限組件
- 頻率控制組件
- 分頁組件
- 響應器組件
- url控制器
介紹DRF,必需要介紹APIView,它是重中之重,是下面全部組件的基礎,由於全部的請求都是經過它來分發的,至於它到底是如何分發請求的呢?想要弄明白這個問題,咱們就必須剖析它的源碼,而想要剖析DRF APIView的源碼,咱們須要首先剖析django中views.View類的源碼,爲何使用視圖類調用as_view()以後,咱們的請求就可以被不一樣的函數處理呢?接下來請同窗們思考十分鐘時間,本身嘗試分析一下django中views.View類的源碼?稍後我請同窗來描述一下整個流程。
Django View請求流程源碼
好了,同窗們,接下來,咱們一塊兒來分析一下django中views.View類的源碼。請看下圖:
請仔細回想咱們前面回顧的知識點,源碼中最後會經過getattr在self中查找request.method.lower(),也就是get、post或者delete這些方法中的一個,那麼,self是誰,就是相當重要的一點,前面講到過,誰調用類中的方法,self就指向誰,此時,一層層往回找,咱們會發現,self = cls(**initkwargs),self就是咱們視圖類的實例化對象,因此,dispatch函數確定會到該視圖類中找對應的方法(get或者post)。
接下來是提問時間,請問若是有以下函數,我想經過不修改函數內容的狀況下,如何給函數新增一個統計執行時間的功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
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
|
這是函數,若是是類呢?面向對象編程,如何擴展你的程序,好比有以下代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
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進行功能擴展的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
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()方法來自定製本身的想法。
那麼,Django RestFramework到底自定製了哪些內容呢?在本文的最開始,咱們已經介紹過了,就是那些組件,好比解析器組件、序列化組件、權限、頻率組件等。
Ajax發送Json數據給服務器
接下來,咱們就開始介紹Django RestFramework中的這些組件,首先,最基本的,就是解析器組件,在介紹解析器組件以前,我提一個問題,請你們思考,如何發送Json格式的數據給後端服務器?
好了,時間到,請看下面的代碼,經過ajax請求,咱們能夠發送json格式的數據到後端:
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
|
<!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源碼,能夠看到下面這一段:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
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 RestFramework的解析器組件。雖然Django的原生解析器不支持application/json編碼協議,可是咱們能夠經過拿到原始的請求數據(request.body)來手動處理application/json請求,雖然這種方式不方便,也並不推薦,請看以下代碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
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請求
請看下面的圖片:
如圖所示,在下拉框中選擇GET方式發送請求,在地址欄輸入請求url,而後點擊send發送便可發送GET請求到後端服務器。
以urlencoded協議發送post請求
請看下圖:
選擇POST,輸入地址,而後在下面的選項框中選擇Body,以後選擇x-www-form-urlencoded協議,輸入key和value,點擊Send,便可以urlencoded方式發送post請求,而且在最下面的提示框中能夠查看到服務器返回的信息。
以json協議發送post請求
最後,請看下圖,以Json協議發送POST請求:
前面都一致,在Body框中,選擇raw選項,而後選擇application/json,最後輸入Json格式的數據,點擊Send便可以Json協議發送POST請求。
今日總結
- classbasedview請求處理流程源碼剖析
- djangorestframework APIView請求處理流程源碼剖析
- postman工具介紹
轉自: pizzali