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 = [

]

修改主項目WeStoreurls.py

django restframework 教程_#django

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',
    }