drf框架
- django-rest-framework
- 1、接口:
- restful接口規範
- 基於restful規範的原生Django接口
- DRF框架:
- drf請求生命週期
- 請求模塊:request對象
- 渲染模塊:瀏覽器和Postman請求結果渲染數據的方式不一樣
- 解析模塊:全局局部配置
- 異常模塊
- 響應模塊
- 序列化組件:
- Serializer序列化模塊
- ModelSerializer序列化模塊
- 準備工作
-
django-rest-framework
知識點:
1、接口:什麼是接口、restful接口規範
2、CBV生命週期源碼 - 基於restful規範下的CBV接口
3、請求組件、解析組件、響應組件
4、序列化組件(靈魂)
5、三大認證(重中之重):認證、權限(權限六表)、頻率
6、其他組件:過濾、篩選、排序、分頁、路由
1、接口:
接口:聯繫兩個物質的媒介,完成信息交互
在web程序中,聯繫前台頁面與後台數據庫的媒介
web接口組成:
url:長得像放回數據的URL鏈接
請求參數:請求按照指定的Key提供數據給後台
響應數據:後台與數據庫交互後將數據反饋給前台
restful接口規範
接口規範:就是為了採用不同的後台語言,也能使用同樣的接口獲取同樣的數據
如何學習:接口規範是 規範化書寫接口的,寫接口要寫url、響應數據
注意:如果將請求參數也納入考量範圍,那就是在寫 接口文檔
兩大部分:
- url
1) 用api關鍵字標識接口url
api.baidu.com | www.baidu.com/api
2) 接口數據安全性決定優先選擇https協議
3)如果一個接口有多版本存在,需要在url中標識體現
api.baidu.com/v1/... | api.baidu.com/v2/...
4)接口操作的數據源稱之為資源,在url中一般採用資源複數形式,一個接口可以概括對該資源的多種操作方式
api.baidu.com/books | api.baidu.com/books/(pk)
5) 請求方式有多種,用一個url處理如何保證不混亂 - 通過請求方式表示操作資源的方式
/books get 獲取所有
/books post 增加一個(多個)
/books/(pk) delete 刪除一個
/books/(pk) put 整體更新一個
/books/(pk) patch 局部更新一個
6)資源往往涉及數據的各種操作方式 - 篩選、排序、限制
api.baidu.com/books/?search=西&ordering=-price&limit=3
- 響應數據
1)httpq請求的響應會有響應狀態碼,接口用來返回操作的資源數據,可以擁有操作數據結果的狀態碼
status 0(操作資源成功) 1(操作資源失敗) 2(操作資源成功,但沒匹配結果)
注:資源狀態碼不像http狀態碼,一般都是前後端相約定的。
2)資源的狀態碼文字提示
status ok ‘賬號有誤’ ‘密碼有誤’ ‘用户鎖定’
3)資源本身
results
注:刪除資源成功不做任何數據返回(返回空字符串)
4)不能直接放回的資源(子資源、圖片、視頻等資源),返回該資源的url鏈接
示例準備:
https://api.baidu.com/vi/books?limit=3
get | post | delete | put | patch
{
'ststus':0,
'msg':'ok',
'result':[
{
'title':*,
'img:https://*
}
]
}
基於restful規範的原生Django接口
主路由:url.py
from django.contrib import admin
from django.urls import path,re_path
from django.urls import include
urlpatterns = [
path('admin/', admin.site.urls),
# 路由分發
re_path('api/',include('api.urls'))
]
api組件的子路由:url.py
from django.contrib import admin
from django.urls import path,re_path
from . import views
urlpatterns = [
re_path(r'books/$', views.Book.as_view()),
re_path(r'books/(?P<pk>.*)/$', views.Book.as_view()),
]
模型層:model.py
from django.db import models
class Book(models.Model):
title = models.CharField(max_length=64)
price = models.DecimalField(max_digits=5, decimal_places=2)
class Meta:
db_table = 'my_book'
verbose_name='書籍'
verbose_name_plural = verbose_name #複數形式
def __str__(self):
return '《%s》' % self.title
後台層:admin.py
from django.contrib import admin
from . import models
admin.site.register(models.Book)
接着進行數據庫遷移:
python mange.py makemigrations
python manage.py migrate
python mange.py createsuperuser
視圖層:views.py
from django.http import JsonResponse
from django.views import View
from . import models
# 六大基礎接口: 獲取一個 獲取所有 增加一個 刪除一個 更新一個 更新所有
# 十大接口: 羣增 羣刪 整體改羣改 局部改羣改
class Book(View):
def get(self,request,*args,**kwargs):
pk = kwargs.get('pk')
if not pk: #羣查
# 操作數據庫
book_obj_list = models.Book.objects.all()
# 序列化過程
book_list = []
for obj in book_obj_list:
dic = {}
dic['title'] = obj.title
dic['price'] = obj.price
book_list.append(dic)
# 響應數據
return JsonResponse({
'status': 0,
'msg': 'ok',
'results': book_list
},json_dumps_params={'ensure_ascii': False})
else: #單查
book_dic = models.Book.objects.filter(pk=pk).values('title','price').first()
if book_dic:
return JsonResponse({
'status': 0,
'msg': 'ok',
'results': book_dic
},json_dumps_params={'ensure_ascii': False})
return JsonResponse({
'status': 2,
'msg': 'no result',
},json_dumps_params={'ensure_ascii': False})
return JsonResponse('get ok', safe=False)
# postman 可以完成不同方式的請求: get | post | put ...
# postman 發送數據包有三種方式: form-data | urlencoding |json
# 原生django對urlencoding方式數據兼容最好
def post(self,request,*args,**kwargs):
# 前台通過urlencoding方式提交數據
try:
book_obj = models.Book.objects.create(**request.POST.dict())
if book_obj:
return JsonResponse({
'status': 0,
'msg': 'ok',
'result': {'title': book_obj.title, 'price': book_obj.price},
},json_dumps_params={'ensure_ascii': False})
except:
return JsonResponse({
'status': 1,
'msg': '參數有誤!',
},json_dumps_params={'ensure_ascii': False})
return JsonResponse({
'status': 2,
'msg': '新增失敗!',
},json_dumps_params={'ensure_ascii': False})
#drf框架的封裝風格
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from rest_framework.serializers import Serializer
from rest_framework.settings import APISettings
from rest_framework.filters import SearchFilter
from rest_framework.pagination import PageNumberPagination
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.throttling import SimpleRateThrottle
# 總結:
# 1)drf 對原生request做了二次封裝,request._request就是原生request
# 2)原生request對象的屬性和方法都可以被drf的request對象直接訪問(兼容)
# 2)drf請求的所有url拼接參數均被解析到query_params中,所有數據包數據都被解析到data中
class Test(APIView):
def get(self, request, *args, **kwargs):
# url拼接的參數
print(request._request.GET) # 二次封裝方式
print(request.GET) # 兼容
print(request.query_params) # 拓展
return Response('drf get ok')
def post(self, request, *args, **kwargs):
# 所有請求方式攜帶的數據包
print(request._request.POST) # 二次封裝方式
print(request.POST) # 兼容
print(request.data) # 拓展,兼容性最強,三種數據都可以
print(request.query_params)
return Response('drf post ok')
# 在setting.py中配置REST_FRAMEWORK,完成的是局部配置,所有接口統一處理
# 如果只有部分接口特殊化,可以完成 —— 局部配置
from rest_framework.renderers import JSONRenderer
class Test2(APIView):
renderer_classes = [JSONRenderer]
def get(self, request, *args, **kwargs):
return Response('drf get ok')
def post(self, request, *args, **kwargs):
return Response('drf post ok')
下載:
Postman接口工具
DRF框架:
安裝:
pip install djangorestframework
接着,我們來分析下源碼。
drf請求生命週期
1) 先請求執行APIView的as_view函數
2)在APIView的as_view調用父類(django原生)的as_view,還禁用了csrf認證
3)在父類的as_view中dispatch方法請求執行APIView的dispatch
4)完成任務方法交給視圖類的請求函數處理,得到請求的響應結果,返回給前台
請求模塊:request對象
源碼入口:
APIView類的dispatch方法中:request = self.initialize_request(request, *args, **kwargs)
注:request完全兼容_request
渲染模塊:瀏覽器和Postman請求結果渲染數據的方式不一樣
源碼入口:
#APIView類的dispatch方法
self.response = self.finalize_response(request, response, *args, **kwargs)
neg = self.perform_content_negotiation(request, force=True)
renderers = self.get_renderers()
REST_FRAMEWORK = {
# drf提供的渲染類
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
}
主要掌握:
response數據json與brower兩種渲染方式
重點:全局配置/局部配置
self.render_classes
自定義視圖類(局部)==>APIView視圖類 =>自定義drf配置(全局)=>drf默認配置
解析模塊:全局局部配置
源碼入口:
#APIView類的dispatch方法
self.response = self.finalize_response(request, response, *args, **kwargs)
setting.py
REST_FRAMEWORK = {
# 全局解析類配置
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.MultiPartParser'
],
}
views.py
class User(APIView):
parser_classes = [JSONParser,JSONParser]
def get():
pass
異常模塊
為什麼要自定義異常模塊
1)所有經過drf的APIView視圖類產生的異常,都可以提供異常處理方案
2)drf默認提供了異常處理方案(rest_framework.views.exception_handler),但是處理範圍有限
3)drf提供的處理方案兩種,處理的返回異常,沒處理返回None(後續就是服務器拋異常給前台)
4)自定義異常的目的就是解決drf沒有處理的異常,讓前台得到合理的信息返回,後台記錄異常具體信息
源碼分析
# 異常模塊
response = self.handle_exception(exc)
# 獲取處理異常的方法
# 一層層看源碼,走的是配置文件,拿到的是rest_framework.views的exception_handler
# 自定義:直接寫exception_handler函數,在自己的配置文件配置EXCEPTION_HANDLER指向自己
exception_handler = self.get_exception_handler()
# 異常處理的結果
# 自定義異常就是提供exception_handler異常處理函數,處理的目的就是讓response一直有值
response = exception_handler(exc, context)
處理方案:自定義exception_handler函數如何書寫實現體
# 修改自己的配置文件setting.py
# 自定義drf配置:
REST_FRAMEWORK = {
# 全局配置異常模塊
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}
# 1) 先將異常處理交給rest_framework.views的exception_handler去處理
# 2)判斷處理的結果(返回值)response,有值代表drf已經處理了,None代表需要自己處理
# 自定義異常處理文件exception.py,在文件中書寫exception_handler函數
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status
def exception_handler(exc, context):
# drf 的exception做基礎處理
response = drf_exception_handler(exc, context)
# 為空,自定義二次處理
if response is None:
# print(exc)
# print(context)
print('%s - %s - %s' % (context['view'], context['request'].method, exc))
return Response({
'detail': '服務器錯誤',
},status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
return response
響應模塊
響應類構造器
def __init__(self, data=None, status=None,
template_name=None, headers=None,
exception=False, content_type=None):
"""
:param data: 響應數據
:param status: http響應狀態碼
:param template_name: drf也可以渲染頁面,渲染的頁面模板地址(不用)
:param headers: 響應頭
:param exception: 是否異常了
:param content_type: 響應的數據格式(一般不用處理,響應頭中帶了,且默認是json)
"""
pass
常規實例化響應對象
from rest_framework import status
return Response(data={數據}, status=status.HTTP_200_OK, header={設置的響應頭})
序列化組件:
Serializer(偏底層)、ModelSeralizer(重點)、ListModelSerializer(輔助羣改)
Serializer序列化模塊
序列化準備:
模型層:models.py
class User(models.Model):
SEX_CHOICES = [
[0,'男'],
[1,'女'],
]
name = models.CharField(max_length=64)
pwd = models.CharField(max_length=32)
phone = models.CharField(max_length=11, null=True, default=None)
sex = models.IntegerField(choices=SEX_CHOICES,default=0)
icon = models.ImageField(upload_to='icon',default='icon/default.jpg')
class Meta:
db_table = 'user'
verbose_name='用户'
verbose_name_plural = verbose_name #複數形式
def __str__(self):
return '%s' % self.name
後台管理層:admin.py
from django.contrib import admin
from . import models
admin.site.register(models.User)
配置:settings.py
INSTALLED_APPS = [
#...
'rest_framework',
#...
]
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'api_test',
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1',
'PORT':'3306'
}
}
子路由:urls.py
from django.urls import path,re_path
from . import views
urlpatterns = [
re_path(r'users/$', views.User.as_view()),
re_path(r'users/(?P<pk>.*)/$', views.User.as_view()),
]
序列化使用:
- 序列化:
步驟如下:
ser:
1)設置需要返回給前台,那些model類對應的字段,不需要返回的就不用設置了
2)設置方法字段,字段名可以隨意,字段值有 get_字段名 提供,來完成一些需要處理再返回的數據
view:
1)從數據庫中將要序列化給前台的model對象,或是多個model對象查詢出來
2)將對象交給序列化處理,產生序列化對象,如果序列化的是多個數據,要設置many=True
3)序列化對象.data就是可以返回給前台的序列化數據
在app下創建api/serializer.py
# 序列化組件 - 為每一個model類通過一套序列化工具類
# 序列化組件的工作方式與django froms組件非常相似
from rest_framework import serializers,exceptions
from django.conf import settings
from . import models
class UserSerializer(serializers.Serializer):
name = serializers.CharField()
phone = serializers.CharField()
# 序列化提供給前台的字段個數由後台決定,可以少提供,
# 但是提供的數據庫字段對應的字段,名字一定要與數據庫字段相同
#sex = serializers.IntegerField()
#icon = serializers.ImageField()
#自定義序列化屬性
#屬性名隨意,值由固定的命名規範方法提供,
# get_屬性名(self,參與序列化的model對象)
# 返回值就是自定義序列化屬性的值
gender = serializers.SerializerMethodField()
def get_gender(self, obj):
# choice類型的值解釋性值 get_字段_display來訪問
return obj.get_sex_display()
icon = serializers.SerializerMethodField()
def get_icon(self, obj):
# 自己配置的 /media/,給後面高級序列化與視圖類準備的
# obj.icon不能直接作為數據返回,因為內容雖然是字符串,但是類型是ImageFieldFile類型
return '%s%s%s' % ('http://127.0.0.1:8000', settings.MEDIA_URL, str(obj.icon))
視圖層:views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import JSONParser
from . import models
from . import serializers
class User(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
print(pk)
if pk:
try:
# 用户對象不能直接作為數據返回給前台
user_obj = models.User.objects.get(pk=pk)
# 序列化一下用户對象
user_ser = serializers.UserSerializer(user_obj)
print(user_ser,type(user_ser))
return Response({
'status': 0,
'msg': 'ok',
'results': user_ser.data
})
except:
return Response({
'status': 2,
'msg': '用户不存在',
})
else:
# 用户對象列表(queryset)不能直接作為數據返回給前台
user_obj_list = models.User.objects.all()
# 序列化一下用户對象
user_ser_data =serializers.UserSerializer(user_obj_list, many=True).data
return Response({
'status': 'ok',
'msg': 0,
'results':user_ser_data
})
- 反序列化:
api/serializers.py
1) 設置必填與選填序列化字段,設置校驗規則
2) 為需要額外校驗的字段提供局部鈎子參數,如果該字段不入庫,且不參與全局鈎子校驗,可以將值取出校驗
3) 為有聯合關係的字段們提供全局鈎子函數,如果某些字段不入庫,可以將值取出校驗
4) 重寫create方法,完成校驗通過的數據庫入庫工作,得到新增的對象
class UserDeserializer(serializers.Serializer):
# 1、哪些字段必須反序列化
# 2、字段都有哪些安全校驗
# 3、哪些字段需要額外提供校驗
# 4、哪些字段間存在聯合校驗
# 注:反序列化字段都是用來入庫,不會出現自定義方法屬性,會出現可以設置校驗規則的自定義屬性(re_pwd)
name = serializers.CharField(
max_length=64,
min_length=3,
error_messages={
'max_length':'太長',
'min_length':'太短'
}
)
pwd = serializers.CharField()
phone = serializers.CharField(required=False)
sex = serializers.IntegerField(required=False)
# 自定義有校驗規則的反序列化字段
re_pwd = serializers.CharField(required=True)
# 局部鈎子:validate_要校驗的字段名(self,當前要校驗的字段值)
# 校驗規則:校驗通過返回原值,校驗失敗,拋出異常
def validate_name(self,value):
if 'g' in value.lower(): #名字中不能出現g
raise exceptions.ValidationError('名字非法!')
return value
# 全局鈎子:(固定寫法)validate(self,系統與就不鈎子校驗通過的所有數據)
def validate(self, attrs):
pwd = attrs.get('pwd')
re_pwd = attrs.pop('re_pwd')
if pwd != re_pwd:
raise exceptions.ValidationError({'pwd&re_pwd':'兩次密碼不一致'})
return attrs
# 要完成新增,需要自己重寫create()方法
def create(self, validated_data):
# 儘量在所有校驗完畢之後,數據可以直接入庫
return models.User.objects.create(**validated_data)
視圖層
1) book_ser = serializers.UserDeserializer(data = request_data) # 數據必須賦值給data
2) book_ser.is_valid() # 結果為通過或不通過
3) 不通過返回 book_ser.errors 給前台,通過 book_ser.save() 得到新增對象,再正常返回
class User(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
print(pk)
if pk:
try:
# 用户對象不能直接作為數據返回給前台
user_obj = models.User.objects.get(pk=pk)
# 序列化一下用户對象
user_ser = serializers.UserSerializer(user_obj)
print(user_ser,type(user_ser))
return Response({
'status': 0,
'msg': 'ok',
'results': user_ser.data
})
except:
return Response({
'status': 2,
'msg': '用户不存在',
})
else:
# 用户對象列表(queryset)不能直接作為數據返回給前台
user_obj_list = models.User.objects.all()
# 序列化一下用户對象
user_ser_data =serializers.UserSerializer(user_obj_list, many=True).data
return Response({
'status': 'ok',
'msg': 0,
'results':user_ser_data
})
#只考慮單增
def post(self, request, *args, **kwargs):
# 請求數據
request_data = request.data
# 數據是否是合法(增加對象需要一個字典數據)
if not isinstance(request_data, dict) or request_data == {}:
return Response({
'status': 1,
'msg': '數據有誤',
})
# 數據類型合法,但數據內容不一定合法,需要校驗數據,校驗(參與反序列化)的數據需要賦值給data
book_ser = serializers.UserDeserializer(data = request_data)
# 序列化對象調用is_valid()完成校驗,校驗失敗的失敗信息都會被存儲在序列化對象.errors
if book_ser.is_valid():
# 校驗通過,完成新增
book_obj = book_ser.save()
return Response({
'status': 0,
'msg': 'ok',
'results': serializers.UserSerializer(book_obj).data
})
else:
# 校驗失敗
return Response({
'status': 1,
'msg': book_ser.errors,
})
ModelSerializer序列化模塊
準備工作
首先,先做開發前的配置準備。
- 1、在根目錄新建
media文件夾 - 2、在主項目的
settings.py中註冊App,修改數據庫,國際化等配置,並在末尾添加以下代碼,:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
REST_FRAMEWORK = {
}
- 3、修改路由分發:
在註冊的App中添加urls.py:
from django.urls import path,re_path
from . import views
urlpatterns = [
]
修改主項目WeStore的urls.py:
from django.contrib import admin
from django.urls import path,re_path
from django.urls import include
from django.conf import settings
from django.views.static import serve
urlpatterns = [
path('admin/', admin.site.urls),
# 路由分發
re_path('api/',include('API.urls')),
re_path(r'media/(?P<path>.*)', serve, {'document_root': settings.MEDIA_ROOT}),
]
- 4、在主項目
WeStore的__init__.py中初始化MySQL數據庫:
import pymysql
pymysql.install_as_MySQLdb()
- 5、在App中添加自定義全局異常模塊
exception.py:
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.views import Response
from rest_framework import status
def exception_handler(exc, context):
# drf 的exception做基礎處理
response = drf_exception_handler(exc, context)
# 為空,自定義二次處理
if response is None:
print('%s - %s - %s' % (context['view'], context['request'].method, exc))
return Response({
'detail': '服務器錯誤',
},status=status.HTTP_500_INTERNAL_SERVER_ERROR, exception=True)
return response
- 6、在
settings.py中修改異常模塊REST_FRAMEWORK的配置:
REST_FRAMEWORK = {
# 全局配置異常模塊
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}