本篇文章會詳細介紹web開發模式、API介面及其介面測試工具、restful規範、還有經常分不清又很重要的序列化與反序列化的部分,初級交接觸APIView、Request類。每個人對知識點的理解都會不一樣,因此我會用自己的理解撰寫一篇文章,這篇將會是我對學習的一個態度更是對自己的一個交代。那咱們開始吧!!嘿嘿嘿DRF安裝與使用
一、web開發模式
在我們的web開發應用中,有兩種開發模式,一是前後端不分離即全棧開發 咱們的BBS專案是典型例子。二是前後端分離,這種開發模式是我們以後的重點發展方向,因為研究的事件一旦變多了我們就會想辦法解耦合,內容越細越精嘛,這就為什麼會出現了後者,這種開發模式個人覺得整個過程非常舒服,每個人清楚的知道自己要幹什麼,自己的主要任務是什麼。前端和後端分離之後,後端程式設計師只需要寫介面也就是說與通過後端程式碼運算元據庫,說白了就是對資料增刪改查。接下來通過圖片的方式更加直觀的解釋,會幫助到咱們的理解。
前後端不分離:使用者端看到的所有頁面和後端關鍵邏輯都是由同一個伺服器端提供的
前後端分離:前端只負責頁面且由獨立伺服器端提供服務,後端只負責返回資料即可
二、API介面
API介面即應用程式介面(Applicant Programming Interface),應用程式對外提供了一個運算元據的入口,這個入口的實現可以是通過FBV或CBV方式來完成、也可以是一個URL地址或一個網址。當用戶端呼叫該入口時應用程式會執行相應的程式碼操作、給使用者端完成對應的功能。這樣可以減小前後端之間的合作成本,可以簡單的理解為API介面是前後端之間資訊互動的媒介。
三、介面測試工具
我們腦海裡肯定會出現一個疑問,瀏覽器就是天生的測試工具為嘛還要搞一個其他工具呢?兄dei那你就太天真來,瀏覽器只能解決get請求的測試需求,二程式設計師還要測試其他的請求,比如post、put、delete等等。這時候瀏覽器大哥也無可奈何了嘛!介面測試工具有以下6個:Poster、Postman、RESTClient、Fiddler、Jmeter、 WireMock。介面測試工具是用於測試系統元件間介面的工具,主要用於檢測外部系統與系統之間以及內部各個子系統之間的互動點。同一類的軟體使用以及底層原理都大差不差、舉一反三,那我們本次學習那postman為例展開從軟體下載到基本使用的操作過程。由於本姑娘喜歡分類與收納,因此我把postman的安裝與使用單獨拎出來寫了點選該地址既可以看到詳細的過程。快戳我--->postman的安裝與使用
四、restful規範
REST全稱為Representational State Transfer即表述性狀態轉移,RESTful是一種專門為web開發而定義API介面的設計風格、尤其是適用於前後端分離的應用模式中。這種風格的理念是後端開發的任務就是提供資料、對外提供的是資料資源的存取介面、所以咋定義介面時使用者端存取的URL路徑就表示這種要操作的資料資源。而對於資料資源分別使用post、get、delete、update等請求動作來表達對資料的增刪改查。RESTful規範是一種通用規範、不限制語言和開發框架的使用。因此、我們當然可以使用任何一門程式語言、任何框架都可以實現RESTful規範的API介面。
- 為安全起見通常用https(http+ssl/tsl)協定
- 介面中要攜帶API標識如https://www.baidu.com/api/books
- 為滿足多版本共存路徑中帶版本資訊如https://www.baidu.com/api/v1/books
- 因為URL資源、均使用名詞、儘量不要出現動詞
- 而且資源的操作方式由請求方式決定
- 在請求地址中帶過濾條件 在?號後面name=米熱&price=11的形式
- 響應狀態碼有兩種預設的和公司自定義的
- 返回資料中帶錯誤資訊
- 返回的結果一個規定規範但是公司有自己的格式
- 響應資料中帶連線
五、序列化與反序列化
序列化與反序列化的核心:轉換資料格式
API介面開發最核心最常見的一個程式碼編寫過程就是序列化,所謂的序列化就是轉換資料格式,其有兩個階段,一是序列化:把後端的資料程式設計字串或者json資料提供給別人、二是反序列化:前端js提供的資料是json格式資料,對於Python而言json就是字串需要反序列化成字典、將字典轉換成模型物件,這樣才能把資料儲存到資料庫中
- 序列化:查資料(單條多條)、後端資料庫到前端頁面、eg:登入介面
- 反序列化:修改及儲存資料、前端頁面到後端資料庫、eg:註冊介面
六、快速體驗drf框架的厲害
from rest_framework.routers import SimpleRouter
router = SimpleRouter()
router.register('books', views.BookView, 'books')
urlpatterns = [
path('admin/', admin.site.urls),
]
# 兩個列表相加 [1,2,4] + [6,7,8]=
urlpatterns += router.urls
from .serializer import BookSerializer
from rest_framework.viewsets import ModelViewSet
class BookView(ModelViewSet):
queryset = Book.objects.all()
serializer_class = BookSerializer
from rest_framework import serializers
from .models import Book
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = '__all__'
七、CBV原始碼分析
八、APIView類及其原始碼分析
# 基於APIView+JsonResponse編寫介面
class BookView(APIView):
def get(self, request):
books = Book.objects.all()
book_list = []
for book in books:
book_list.append({'name': book.name, 'price': book.price, 'publish': book.publish})
return JsonResponse(book_list, safe=False)
# 基於APIView+Response 寫介面
class BookView(APIView):
def get(self, request):
books = Book.objects.all()
book_list = []
for book in books:
book_list.append({'name': book.name, 'price': book.price, 'publish': book.publish})
return Response(book_list)
- 首先去除了所有的CSRF驗證
- 包裝了新的request類request._request
- 執行檢視類之前執行了三大認證
- 為確保出現異常設定了全域性異常捕獲
九、Request類
- 老的request:django.core.handlers.wsgi.WSGIRequest
- 新的request:from rest_framework.request import Request
- 新的request跟老的request用起來一樣、新的取不到雙下getattr就取老的
- request.data無論是什麼編碼什麼請求方式字典從body裡面取資料取出來是字典
- request.query_params就是原來的request._request.GET,請求攜帶的引數
- request.FILES上傳的檔案從這裡取
十、序列化器及其用法
# 檢視層程式碼
from django.http import JsonResponse
from .models import Book
from django.views import View
import json
class BookView(View):
"""1.查所有資料"""
def get(self, request):
# 查詢出所有圖書,queryset物件,不能直接給前端
books = Book.objects.all()
book_list = []
for book in books:
book_list.append({'name': book.name, 'price': book.price, 'publish': book.publish})
return JsonResponse(book_list, safe=False, json_dumps_params={'ensure_ascii': False})
"""2.新增一條資料"""
def post(self, request):
# 取出前端傳入的資料
name = request.POST.get('name')
price = request.POST.get('price')
publish = request.POST.get('publish')
# 存到新增的物件字典
book = Book.objects.create(name=name, price=price, publish=publish)
# 返回新增的物件字典
return JsonResponse({'name': book.name, 'price': book.price, 'publish': book.publish})
class BookDetailView(View):
"""3.查詢單個資料"""
def get(self, request, pk):
book = Book.objects.filter(pk=pk).first()
return JsonResponse({'id': book.id, 'name': book.name, 'price': book.price, 'publish': book.publish})
"""4.修改資料"""
def put(self, request, pk):
# 查到要改的
book = Book.objects.filter(pk=pk).first()
# 前端使用json格式提交 自己儲存
book_dict = json.loads(request.body)
book.name = book_dict.get('name')
book.price = book_dict.get('price')
book.publish = book_dict.get('publish')
book.save()
return JsonResponse({'id': book.id, 'name': book.name, 'price': book.price, 'publish': book.publish})
def delete(self, request, pk):
"""5.刪除資料"""
Book.objects.filter(pk=pk).delete()
return JsonResponse(data={})
# 路由層程式碼
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api/v1/books/<int:pk>/',views.BookDetailView.as_view()),
path('api/v1/books/', views.BookView.as_view()),
]
# 路由層程式碼
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', views.BookView.as_view()),
path('books/<int:pk>/', views.BookDetailView.as_view()),
]
# 檢視層
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book
from .serializer import BookSerializer
class BookView(APIView):
"""1.查所有資料 序列化過程"""
def get(self, request):
books = Book.objects.all()
# 需要序列化類來完成---得有序列化類即BookSerializer
# instance引數是要序列化的資料books queryset物件
# 要傳的資料是多條那就得加上many=True引數,如果是單挑就不用傳
ser = BookSerializer(instance=books, many=True)
return Response(ser.data)
def post(self, request):
"""2.新增資料 反序列化過程"""
# request.data 前端提交資料---校驗資料---存資料,前端傳入的資料給data引數
ser = BookSerializer(data=request.data)
# 校驗資料
if ser.is_valid():
ser.save()
return Response({'code':100, 'msg':'新增成功', 'result':ser.data})
else:
return Response({'code':101, 'msg':ser.errors})
class BookDetailView(APIView):
"""3.獲取單個資料 序列化過程"""
def get(self, request, *args, **kwargs):
book = Book.objects.filter(pk=kwargs.get('pk')).first()
ser = BookSerializer(instance=book)
return Response(ser.data)
def put(self, requet, pk):
"""4.修改資料 反序列化過程"""
book = Book.objects.filter(pk=pk).first()
ser = BookSerializer(data=requet.data, instance=book)
if ser.is_valid():
ser.save()
return Response({'code':100, 'msg':'修改資料', 'result':ser.data})
else:
return Response({'code':101, 'msg':ser.errors})
def delete(self, request, pk):
Book.objects.filter(pk=pk).delete()
return Response({'code':100,'msg':'刪除成功'})
# 在app01目錄下新建serializer.py檔案 寫如下序列化類
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from .models import Book
class BookSerializer(serializers.Serializer):
# 序列化模型表的三個欄位
name = serializers.CharField()
price = serializers.CharField()
publish = serializers.CharField()
def create(self, validated_data):
"""資料儲存邏輯"""
# 儲存到資料庫
# validated_data是校驗過後的資料
book = Book.objects.create(**validated_data)
return book
def update(self, instance, validated_data):
"""資料更新邏輯"""
# instance是要修改的物件
instance.name = validated_data.get('name')
instance.price = validated_data.get('price')
instance.publish = validated_data.get('publish')
# orm的單個物件 修改了單個物件的屬性 只要呼叫物件.save就能把修改儲存的資料庫
instance.save()
# 返回修改後的物件
return instance
def validate_name(self, name):
# 校驗name是否合法
if name.startswith('sb'):
# 校驗啊不能通過 拋異常
raise ValidationError('名字不能以sb開頭')
else:
return name
def validate(self, attrs):
# 校驗過後的資料 書名跟出版社名字不能一樣
if attrs.get('name') == attrs.get('publish'):
raise ValidationError('書名和出版社名不能一樣')
else:
return attrs
需要了解的欄位類以及其引數
1.布林欄位 BooleanField
BooleanField() # 不用傳引數,自帶布林值
2.無布林值欄位 NullBooleanField
NullBooleanField() # 不用傳引數,自帶無布林值
4.郵箱欄位 EmailField
EmailField(max_length=None, min_length=None, allow_blank=False)
5.正則欄位 RegexField
RegexField(regex, max_length=None, min_length=None, allow_blank=False)
6.正則欄位 SlugField
SlugField(max_length=50, min_length=None, allow_blank=False) # 驗證正則模式 [a-zA-Z0-9-]+
7.路由欄位 URLField
URLField(max_length=200, min_length=None, allow_blank=False)
8.UUIDField # UUID是通用唯一識別碼(Universally Unique Identifier)
UUIDField(format=’hex_verbose’)
9.API位址列位 IPAddressField
IPAddressField(protocol=’both’, unpack_ipv4=False, **options)
10.小數位段 FloatField
FloatField(max_value=None, min_value=None)
11.年月日欄位 DateField
DateField(format=api_settings.DATE_FORMAT, input_formats=None)
12.時分秒 TimeField
TimeField(format=api_settings.TIME_FORMAT, input_formats=None)
13.持續時間欄位 DurationField
DurationField()
14.選擇欄位 ChoiceField
ChoiceField(choices) choices與Django的用法相同
15.多選欄位 MultipleChoiceField
MultipleChoiceField(choices)
16.檔案欄位 FileField
FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
17.影象欄位 ImageField
ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL)
必須掌握的欄位類以及引數
前端傳進來的是什麼在序列化類裡面就用什麼欄位
後端在表模型寫的方法是什麼在序列化類裡面就用什麼欄位
重點學習的兩個欄位 列表欄位ListField 字典欄位DictField
沒有引數 在客製化欄位的時候用到具體使用方法請見@模型表中客製化方法,如下嘿嘿嘿!!
1.CharField
CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True)
2.IntegerField
IntegerField(max_value=None, min_value=None)
3.DecimalField
DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None)
# max_digits: 最多位數 decimal_palces: 小數點位置
4.DateTimeField DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None)
序列化器需要用到的兩個重要欄位和兩個引數
- ListField當需要接收一個 list 的時候使用
- DictField當傳入的資料是 dict 時使用
- read_only 表明該欄位僅用於序列化輸出,預設False
- write_only 表明該欄位僅用於反序列化輸入,預設False
- 欄位引數校驗規則
- 欄位單獨設定校驗
- 區域性勾點校驗規則
- 全域性勾點校驗規則
# 反序列化的校驗 只要在序列化類中寫區域性和全域性勾點
區域性勾點
def validate_欄位名(self,name):
校驗通過,返回name,
如果不通過,丟擲異常
全域性勾點
# attr,前端傳入的資料,走完區域性勾點校驗後的資料
def validate(self,attrs):
校驗通過,返回attrs
如果不通過,丟擲異常
# 建立關聯表、遷移資料庫、錄入偽資料
from django.db import models
class Book(models.Model):
"""1.書籍表"""
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
class Publish(models.Model):
"""2.出版社表"""
name = models.CharField(max_length=32)
address = models.CharField(max_length=32)
class Author(models.Model):
"""3.作者表"""
name = models.CharField(max_length=32)
phone = models.CharField(max_length=11)
建立完模型表之後需要錄入一些偽資料,今天我們建立的是多張關聯表,錄入資料不能亂錄入。原則是先錄入結構簡單的表(即沒有外來鍵的表),本次錄入資料的順序的話author》publish》book
# 寫序列化類
from rest_framework import serializers
class BookSerializer(serializers.Serializer):
real_name = serializers.CharField(max_length=8, source='name')
real_price = serializers.CharField(source='price')
publish = serializers.CharField(source='publish.name')
authors = serializers.CharField(source='authors.all')
# 寫檢視類
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from .models import Book, Author, Publish
from .serializer import BookSerializer
class BookView(APIView):
def get(self, request):
books = Book.objects.all()
ser = BookSerializer(instance=books, many=True)
return Response(ser.data)
# 寫路由
from django.contrib import admin
from django.urls import path
from app01 import views
urlpatterns = [
path('books/', views.BookView.as_view()),
]
# 客製化關聯欄位的顯示形式
一對多>>>顯示字典
多對多>>>顯示列表套字典
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=8)
price = serializers.CharField()
publish_detail = serializers.SerializerMethodField()
def get_publish_detail(self, obj):
return {'name': obj.publish.name, 'address': obj.publish.address}
author_list = serializers.SerializerMethodField()
def get_author_list(self, obj):
l = []
for author in obj.authors.all():
l.append({'name': author.name, 'phone': author.phone})
return l
# 在serializer.py中寫這個程式碼
class BookSerializer(serializers.Serializer):
name = serializers.CharField(max_length=8)
price = serializers.CharField
publish_detail = serializers.DictField()
author_list = serializers.ListField()
# 在models.py中寫這兩個方法
class Book(models.Model):
"""1.書籍表"""
name = models.CharField(max_length=32)
price = models.CharField(max_length=32)
publish = models.ForeignKey(to='Publish', on_delete=models.CASCADE)
authors = models.ManyToManyField(to='Author')
def publish_detail(self):
return {'name':self.publish.name, 'address':self.publish.address}
def author_list(self):
l =[]
for author in self.authors.all():
l.append({'name':author.name, 'phone':author.phone})
return
"""
因為這個方法針對一對多和多對多欄位而設定的
而且是外來鍵 所以外來鍵在哪裡這兩個方法建在哪裡
"""
ModelSerializer 是對 serializers 的進一步封裝
"""ModelSerializer的使用"""
class BookSerializer(serializers.ModelSerializer):
# 跟表有關聯
class Meta:
# 跟book建立了關係 序列化類和表模型類
model = Book
# 序列化所有book中的name和price欄位,如果這樣寫fields = '__all__' # 可以序列化所有欄位
fields = ['name', 'price', 'publish_detail', 'author_list', 'publish', 'authors']
# 客製化name反序列化時最長不能超過8
extra_kwargs = {'name': {'max_length': 8},
'publish_detail': {'read_only': True},
'auth0r_list': {'read_only': True},
'publish': {'write_only': True},
'authors': {'write_only': True}
}
def validate_name(self, name):
if name.startswith('sb'):
raise ValidationError('不能以sb開頭')
else:
return name
我們在分析原始碼的時候會經常看到assert和try,因為就一個關鍵詞節省了很多邏輯程式碼,其作用是宣告其布林值必須為真的判定,如果發生異常就說明表達示為假。可以理解assert斷言語句為raise-if-not,用來測試表示式,其返回值為假,就會觸發異常。
# 如果不用斷言實現需求是如下
name = 'almira'
if name == 'almira':
print('yep')
else:
print('nope')
# 如果使用斷言實現需求是如下
# 斷定是就跑正常邏輯 如果不是就拋異常
assert name == 'almira'
print('keep it up')
十一、DRF之請求request
方式一:區域性設定
在APIView及其子類的檢視類中設定具體操作步驟如下
# 1.需要先匯入
from rest_framework.parsers import JSONParser,FormParser,MultiPartParser
# 2.在檢視cbv裡面寫需要用的格式
class BookView(APIView):
parser_classes = [JSONParser,]
方式二:全域性設定
直接在組態檔裡面改設定既可影響全域性
# 不需要用的就註釋掉既可
REST_FRAMEWORK = {
'DEFAULT_PARSER_CLASSES': [
# 'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
# 'rest_framework.parsers.MultiPartParser',
],
}
方式三:區域性全域性
區域性設定方法和全域性設定方法混著用
有個需求:全域性配了一個某個檢視類卻想要三個怎麼解決?
# 解決方法很簡單
"""
直接在該檢視類設定3個就可以
因為先從自身類找找不到去專案找再找不到就去drf預設設定找
"""
request原始碼分析上面已經分析過哦
十二、DRF之響應response
"""
drf是Django的一個APP因此當然要註冊 切記哦
drf的響應如果使用瀏覽器和postman存取同一個介面返回的格式是不一樣
因為drf做了個判斷 如果是瀏覽器好看一些 如果是postman只要json資料
"""
方式一:區域性設定
from rest_framework.renderers import JSONRenderer,BrowsableAPIRenderer
class BookView(APIView):
renderer_classes=[JSONRenderer,]
方式二:全域性設定
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
方式三:使用順序
# 一般就用內建的即可
優先使用檢視類中的設定,其次使用專案組態檔中的設定,最後使用內建的
# drf 的Response 原始碼分析
from rest_framework.response import Response
檢視類的方法返回時,retrun Response ,走它的__init__,init中可以傳什麼引數
# Response init可以傳的引數
def __init__(self,
data=None,
status=None,
template_name=None,
headers=None,
exception=False,
content_type=None)
data:之前咱們寫的ser.data 可以是字典或列表,字串---》序列化後返回給前端---》前端在響應體中看到的就是這個
status:http響應的狀態碼,預設是200,你可以改
drf在status包下,把所有http響應狀態碼都寫了一遍,常數
from rest_framework.status import HTTP_200_OK
Response('dddd',status=status.HTTP_200_OK)
template_name:瞭解即可,修改響應模板的樣子,BrowsableAPIRenderer定死的樣子,後期公司可以自己客製化
headers:響應頭,http響應的響應頭
content_type :響應編碼格式,一般不動
十三、兩個檢視基礎類別、五個檢視擴充套件類、九個檢視子類
- APIView
- GenericAPIView
'''
# GenericAPIView屬性
1 queryset:要序列化或反序列化的表模型資料
2 serializer_class:使用的序列化類
3 lookup_field :查詢單條的路由分組分出來的欄位名
4 filter_backends:過濾類的設定(瞭解)
5 pagination_class:分頁類的設定(瞭解)
# GenericAPIView方法
1 get_queryset :獲取要序列化的物件
2 get_object :獲取單個物件
3 get_serializer :獲取序列化類 ,跟它差不多的get_serializer_class,一般重寫它,不呼叫它
4 filter_queryset :過濾有關係(瞭解)
'''
from rest_framework.generics import GenericAPIView
class BookView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
objs = self.get_queryset() # 好處,可以重寫該方法,後期可延伸性高
ser = self.get_serializer(instance=objs, many=True)
return Response(ser.data)
def post(self, request):
ser = self.get_serializer(data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '新增成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
class BookDetailView(GenericAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, pk):
obj = self.get_object() # 獲取單條
ser = self.get_serializer(instance=obj)
return Response(ser.data)
def put(self, request, pk):
obj = self.get_object()
ser = self.get_serializer(instance=obj, data=request.data)
if ser.is_valid():
ser.save()
return Response({'code': 100, 'msg': '修改成功', 'result': ser.data})
else:
return Response({'code': 101, 'msg': ser.errors})
def delete(self, request, pk):
self.get_object().delete()
return Response({'code': 100, 'msg': '刪除成功'})
- 新增 create方法 CreateModelMixin
- 刪除 destroy方法 DestroyModelMixin
- 差單條 retrieve方法 RetrieveModelMixin
- 查所有 list方法 ListModelMixin
- 更新 update方法 UpdateModelMixin
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import CreateModelMixin, UpdateModelMixin, DestroyModelMixin, RetrieveModelMixin, ListModelMixin
class BookView(GenericAPIView, ListModelMixin, CreateModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request):
return self.list(request)
def post(self, request):
return self.create(request)
class BookDetailView(GenericAPIView, RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin):
queryset = Book.objects.all()
serializer_class = BookSerializer
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs)
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)
週一過來再認真總結