1. 程式人生 > >DRF 版本、認證、權限、限制、解析器和渲染器

DRF 版本、認證、權限、限制、解析器和渲染器

斷言 lin .html remote ati efault rfi site initial

目錄

  • 一.DRF之版本控制
    • 為什麽要有版本控制?
    • DRF提供的版本控制方案
    • 版本的使用
      • 全局配置
      • 局部配置(使用較少)
  • 二.DRF之認證
    • 內置的認證
    • 步驟
  • 三.DRF之權限
    • 1.自定義一個權限類
    • 2.權限 局部配置
    • 3.權限 全局配置
  • 四.DRF之限制
    • 1.使用自定義限制類
      • 1.1自定義一個限制類
      • 1.2限制 局部配置
      • 1.3限制 全局配置
    • 2.使用內置限制類
      • 2.1定義內置限制類
      • 2.2全局配置
  • 五.DRF之分頁
    • 1.為什麽要使用分頁
    • 2.DRF使用分頁器
      • 2.1分頁模式
      • 2.2全局配置
      • 2.3局部配置
    • 3.DRF內置分頁器
      • 3.1PageNumberPagination
      • 3.2LimitOffsetPagination
      • 3.3CursorPagination
  • 六.解析器和渲染器
  • 七.對DRF中的request對象的相關總結
    • 1.查看源碼
    • 2.總結
  • 八.版本,認證,權限,限制,分頁 -- 源碼查看方法
  • 九.補充知識
    • 1.issubset()
    • 2.語法糖setter,getter,deleter
    • 3.ORM之update_or_create()
    • 4.assert斷言
    • 5.while循環測試
    • 6.ORM的QuerySet操作

一.DRF之版本控制

為什麽要有版本控制?

API版本控制允許我們在不同的客戶端之間更改行為(同一個接口的不同版本會返回不同的數據). DRF提供了許多不同的版本控制方案.

可能會有一些客戶端因為某些原因不再維護了, 但是我們後端的接口還要不斷的更新叠代, 這個時候通過版本控制返回不同的內容就是一種不錯的解決方案.

DRF提供的版本控制方案

DRF提供了五種版本控制方案, 如下:

技術分享圖片

版本的使用

全局配置

  1. settings.py文件中進行全局配置

除非明確設置, 否則DEFAULT_VERSIONING_CLASS值為None, 此例中的request.version將會始終返回None.

REST_FRAMEWORK = {
    # 配置默認使用的版本控制方案: URLPathVersioning
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',  # 默認的版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 有效的版本
    'VERSION_PARAM': 'version',  # 版本的參數名與URL conf中一致
}
  1. urls.py文件中:
from django.conf.urls import url
from django.contrib import admin
from bms import views

urlpatterns = [
    url(r'^admin/', admin.site.urls),

    url(r'^(?P<version>[v1|v2]+)/book/$',   # 版本的參數名與URL conf中一致
        views.BookViewSet.as_view(actions={'get': 'list', 'post': 'create'})),
    url(r'^(?P<version>[v1|v2]+)/book/(?P<pk>\d+)$',
        views.BookViewSet.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),

]
  1. bms/views.py文件中:

我們在中可以通過訪問request.version來獲取當前請求的具體版本, 然後根據不同的版本來返回不同的內容.

  • 只要在settings.py中配置了版本信息, 在視圖(bms/views.py)中就能通過request.version獲取當前版本
  • get_serilaizer_class方法可以根據不同版本返回不同的序列化類
  • get_queryset方法可以根據不同的版本返回不同的數據控制

思考: 為什麽可以直接用request.version拿到版本號? --> 這就是我們看源碼的目的

from bms import models
from bms.modelserializers import BookModelSerializer
from rest_framework.viewsets import ModelViewSet


class BookViewSet(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer

    def get_serializer_class(self):
        """不同版本 使用不同的序列化類"""
        if self.request.version == 'v1':
            return BookModelSerializer1
        return self.serializer_class

    def get_queryset(self):
        """不同的版本可以 返回不同的數據控制"""
        if self.request.version == 'v1':
            return models.Book.objects.all()[:3]
        return self.queryset.all()

局部配置(使用較少)

我們可以在一個單獨的視圖上設置版本控制方案. 通常, 我們==不需要這樣做==, 因為在全局範圍內使用一個版本控制方案更有意義. 如果我們確實需要這樣做, 請使用versioning_class屬性.

  1. 導入版本控制方案:from rest_framework.versioning import 版本控制方案
  2. versioning_class=版本控制方案
  3. 定義 get_queryset方法 或 get_serializer_class方法

==註意==: 版本控制方案有五種

# AcceptHeaderVersioning
# -- 將版本信息放在請求頭中

URLPathVersioning
# -- 將版本信息放在URL中,如: 127.0.0.1:8000/v1/book

NamespaceVersioning
# -- 通過namespace來區分版本

HostNameVersioning
# -- 通過主機名來區分版本

QueryParameterVersioning
# -- 通過URL查詢參數來區分版本 如: 127.0.0.1:8000/authors/?version=1

my_app/views.py文件中:

# 第一步
from rest_framework.versioning import URLPathVersioning
class AuthorViewSet(ModelViewSet):
    queryset = models.Author.objects.all()
    serializer_class = AuthorModelSerializer
    
    # 第二步
    versioning_class = URLPathVersioning

    # 第三步
    def get_queryset(self):
        """不同的版本可以 返回不同的數據控制量"""
        pass
    
    # 第三步
    def get_serializer_class(self):
        """不同版本 使用不同的序列化類"""
        pass

urls.py文件中:

from django.conf.urls import url
from bms import views
urlpatterns = [
    url(r'^(?P<version>[v1|v2]+)/authors/$',
        views.AuthorViewSet.as_view(actions={'get': 'list', 'post': 'create'})),
    url(r'^(?P<version>[v1|v2]+)/authors/(?P<pk>\d+)$',
        views.AuthorViewSet.as_view(actions={'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]

二.DRF之認證

身份驗證是將==傳入請求==與==一組標識憑據(如請求來自的用戶或其簽名的令牌)==相關聯的機制. 然後 權限 和 限制 組件決定是否拒絕這個請求.

簡單來說:

  • 認證 -- 確定了你是誰
  • 權限 -- 確定你能不能訪問某個接口
  • 限制 -- 確定你訪問某個接口的頻率

認證的目的: 告訴服務端你是誰

思考: 有個問題, 我們的Django和Vue項目是分離的, 它們很可能是建立在兩個不同的服務器上的, 這種前後端分離的情況我們該怎樣存cookie和session呢? 對於這種情況, 我們一般是通過Vue發ajax請求來把數據保存到cookie/session中的. 還有一種解決辦法, 當前端請求到來時, 前端發送過來一個==token==值給後端, 後端通過查詢這個token值(數據庫中匹配)就可以確定你是誰了.

總結: 我們通過token值來確定前端來訪問的用戶是誰.

內置的認證

技術分享圖片

技術分享圖片

步驟

1.新創建一個app: auth_demo

2.在settings.py中註冊auth_demo這個app

3.auth_demo/models.py中的表結構設計:

from django.db import models

# 用戶表
class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    pwd = models.CharField(max_length=32)
    vip = models.BooleanField(default=False)
    token = models.CharField(max_length=128, null=True, blank=True)

4.二級路由

根目錄下的urls.py:

from django.conf.urls import url, include

urlpatterns = [
    url(r'^users/', include('auth_demo.urls')),
]

auth_demo/urls.py:

from django.conf.urls import url
from auth_demo import views

urlpatterns = [
    url(r'reg/$', views.RegView.as_view()),  # 註冊
    url(r'login/$', views.LoginView.as_view()),  # 登錄
    url(r'test_auth/$', views.TestAuthView.as_view()),  # 測試登錄認證
]

5.視圖函數 auth_demo/views.py:

註冊:

from rest_framework.views import APIView
from rest_framework.response import Response
from auth_demo import models


class RegView(APIView):
    """只支持註冊用戶"""

    def post(self, request):
        # 1.獲取用戶註冊的數據
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        re_pwd = request.data.get('re_pwd')
        if name and pwd:
            # 2.判斷密碼和確認密碼是否一致
            if pwd == re_pwd:
                # 3.創建用戶
                models.UserInfo.objects.create(name=name, pwd=pwd)
                # 4.返回響應
                return Response('註冊成功')
            else:
                return Response('兩次密碼不一致')
        else:
            return Response('無效的參數')

登錄:

class LoginView(APIView):
    """只支持用戶登錄"""

    def post(self, request):
        # 1.通過request.data獲取前端提交的數據
        name = request.data.get('name')
        pwd = request.data.get('pwd')
        if name and pwd:
            # 2.從數據庫中進行篩選匹配
            user_obj = models.UserInfo.objects.filter(name=name, pwd=pwd).first()
            if user_obj:
                # 3.登錄成功,生成token(時間戳 + Mac地址)
                import uuid
                token = uuid.uuid1().hex
                # 4.把token保存到用戶表中
                user_obj.token = token
                user_obj.save()
                # 5.返回響應(包括狀態碼和token值)
                return Response({'error_no': 0, 'token': token})
            else:
                # 3.用戶名或密碼錯誤,登錄失敗
                return Response({'error_no': 1, 'error': '用戶名或密碼錯誤'})
        else:
            return Response('無效的參數')

6.自定義認證類 auth_demo/auth.py:

from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from auth_demo import models


class MyAuth(BaseAuthentication):

    def authenticate(self, request):
        # 1.通過request.query_params獲取前端的url參數
        token = request.query_params.get('token')
        if token:
            # 2.如果請求的url中攜帶了token參數
            user_obj = models.UserInfo.objects.filter(token=token).filter()
            if user_obj:
                # 3.token是有效的
                return user_obj, token  # 必須返回一個元組: (user_obj, token) --> (request.user, request.auth)
            else:
                raise AuthenticationFailed('無效的token')
        else:
            raise AuthenticationFailed('請求的URL中必須攜帶token參數')

7.局部認證 配置: auth_demo/views.py

註意: ==局部配置的優先級高於全局配置==

from auth_demo.auth import MyAuth

# 登錄之後才能看到的數據接口
class TestAuthView(APIView):
    authentication_classes = [MyAuth, ]  # 配置局部認證, 全局認證在settings.py文件中配置

    def get(self, request):
        print(request.user.name)    # request.user 用戶對象
        print(request.auth)         # reequest.auth 設置的token值
        return Response('這個視圖裏面的數據只有登錄以後才能看到!')

8.全局配置: settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ],  # 認證 全局配置
}

三.DRF之權限

1.自定義一個權限類

auth_demo/permissions.py:

"""
自定義一個權限組件
"""
from rest_framework.permissions import BasePermission
class MyPermission(BasePermission): # 繼承BasePermission
    message = '只有VIP才能訪問'

    def has_permission(self, request, view):    # 必須實現has_permission方法
        # 只有通過權限驗證的用戶才能訪問has_permission方法
        if not request.auth:    # request.auth --> token值
            return False
        # request.user --> 當前通過token認證的用戶(UserInfo表中的用戶對象)
        if request.user.vip:
            # 是VIP就通過
            return True
        else:
            # 不是VIP就拒絕
            return False

2.權限 局部配置

auth_demo/views.py:

from auth_demo.auth import MyAuth
from auth_demo.permissions import MyPermission


# 登錄之後才能看到的數據接口
class TestAuthView(APIView):
    authentication_classes = [MyAuth, ]  # 配置局部認證, 全局認證在settings.py文件中配置
    permission_classes = [MyPermission, ]   # 配置局部權限, 全局權限在settings.py文件中配置

    def get(self, request):
        print(request.user.name)
        print(request.auth)
        return Response('這個視圖裏面的數據只有登錄以後才能看到!')

3.權限 全局配置

settings.py:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ],
    'DEFAULT_PERMISSION_CLASSES': ['auth_demo.permissions.MyPermission', ]
}

四.DRF之限制

1.使用自定義限制類

DRF內置了基本的限制類,首先我們自己動手寫一個限制類,熟悉下限制組件的執行過程。

1.1自定義一個限制類

auth_demo/throttle.py:

import time

# 訪問記錄
VISIT_RECORD = {}
class MyThrottle(object):

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        # 1.拿到當前請求的ip作為VISIT_RECORD的key
        ip = request.META.get('REMOTE_ADDR')
        # 2.拿到當前請求的時間戳
        now = time.time()
        # 3.如果是請求是第一次來訪問
        if ip not in VISIT_RECORD:
            # {ip:[]}
            VISIT_RECORD[ip] = []
            return True
        # 4.把當前請求的訪問記錄拿出來保存到一個變量(訪問歷史)中
        history = VISIT_RECORD[ip]
        self.history = history
        # 5.循環訪問歷史,把超過10秒鐘的請求時間去掉
        while history and now - history[-1] > 10:
            history.pop()
        # 6.此時,history中只保存了最近10秒鐘的訪問記錄
        if len(history) >= 3:
            # (1)history中存放了3條及以上的歷史記錄,拒絕訪問
            return False
        else:
            # (2)history中的歷史記錄不到3條,存儲當前歷史記錄
            self.history.insert(0, now)
            return True

    def wait(self):
        """告訴客戶端還需要等待多久"""
        now = time.time()
        return self.history[-1] + 10 - now

1.2限制 局部配置

auth_demo/views.py:

from auth_demo.throttle import MyThrottle


# 登錄之後才能看到的數據接口
class TestView(APIView):
    throttle_classes = [MyThrottle, ]  # 配置局部限制, 全局限制在settings.py文件中配置

    def get(self, request):
        return Response('你成功了!這個視圖裏面的數據只有登錄以後才能看到!')

1.3限制 全局配置

settings.py:

# 在settings.py中設置rest framework相關配置項
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['auth_demo.auth.MyAuth', ],  # 認證 全局配置
    'DEFAULT_PERMISSION_CLASSES': ['auth_demo.permissions.MyPermission', ],  # 權限 全局配置
    'DEFAULT_THROTTLE_CLASSES': ['auth_demo.throttle.MyThrottle'],  # 限制 全局配置
}

2.使用內置限制類

2.1定義內置限制類

auth_demo/throttle.py:

#使用內置限制類

from rest_framework.throttling import SimpleRateThrottle
class VisitThrottle(SimpleRateThrottle):
    scope = "throttle"

    def get_cache_key(self, request, view):
        return self.get_ident(request)

2.2全局配置

REST_FRAMEWORK = {
    # 內置限制類的全局配置
    "DEFAULT_THROTTLE_RATES": {
        "throttle": "5/m",  # 這裏的key要與throttle.py文件中的scope="throttle"相對應
    },
}

五.DRF之分頁

1.為什麽要使用分頁

我們的數據表中可能會有成千上萬條數據, 當我們訪問某張表的所有數據時,我們不大可能需要一次把所有數據都展示出來, 因為數據量很大, 對服務端的內存壓力比較大並且網絡傳輸過程中耗時也會比較大.

通常我們會希望一部分一部分去請求數據, 也就是我們常說的一頁一頁獲取數據並展示出來.

2.DRF使用分頁器

2.1分頁模式

REST framework中提供了三種分頁模式:

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

2.2全局配置

REST_FRAMEWORK = {
    # 默認使用的分頁類
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    # 默認每頁的數據個數
    'PAGE_SIZE': 100
}

2.3局部配置

我們可以在視圖類中進行局部配置

class PublisherViewSet(ModelViewSet):
    queryset = models.Publisher.objects.all()
    serializer_class = PublisherModelSerializer
    pagination_class = PageNumberPagination  # 註意不是列表(只能有一個分頁模式)

3.DRF內置分頁器

3.1PageNumberPagination

按頁碼數分頁, 第n頁, 每頁顯示m條數據.

例如: http:127.0.0.1:8000/v1/book/?page=2&size=1

分頁器

# bms/pagination.py

from rest_framework.pagination import PageNumberPagination
class MyPageNumber(PageNumberPagination):
    page_size = 2  # 每頁顯示多少條
    page_size_query_param = 'size'  # URL中每頁顯示條數的參數
    page_query_param = 'page'  # URL中頁碼的參數
    max_page_size = None  # 最大頁碼數限制

視圖

# bms/views.py

from bms.pagination import MyPageNumber
class BookViewSet(ModelViewSet):
    queryset = models.Book.objects.all().order_by('id')
    serializer_class = BookModelSerializer
    """
    普通分頁器
    """
    pagination_class = MyPageNumber

3.2LimitOffsetPagination

分頁, 在n位置, 向後查看m條數據.

例如: 127.0.0.1:8000/v1/book/?offset=2&limit=2

分頁器

# bms/pagination.py

from rest_framework.pagination import LimitOffsetPagination
class MyLimitOffset(LimitOffsetPagination):
    default_limit = 1
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 999

視圖

# bms/views.py

from bms.pagination import MyLimitOffset
class BookViewSet(ModelViewSet):
    queryset = models.Book.objects.all().order_by('id')
    serializer_class = BookModelSerializer
    """
    offset分頁器
    """
    pagination_class = MyLimitOffset

3.3CursorPagination

加密分頁, 把上一頁和下一頁的id值記住.

分頁器

# bms/pagination.py

from rest_framework.pagination import CursorPagination
class MyCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 1
    ordering = '-id'  # 重寫要排序的字段

視圖

# bms/views.py

from bms.pagination import MyCursorPagination
class BookViewSet(ModelViewSet):
    queryset = models.Book.objects.all().order_by('id')
    serializer_class = BookModelSerializer
    """
    加密分頁器
    """
    pagination_class = MyCursorPagination

六.解析器和渲染器

參考資料

略.

七.對DRF中的request對象的相關總結

1.查看源碼

1.APIView

技術分享圖片

2.1.1initialize_request方法

技術分享圖片

2.1.2Request

技術分享圖片

2.2.1initial方法

技術分享圖片

2.總結

  • request.data -- 前端post提交的數據
  • request.query_params -- 前端頁面的url參數
  • request.user -- 通過認證的用戶對象
  • request.auth -- 前端發過來的token值
  • request.version -- 版本號(如: v1, v2)
  • requst.versioning_scheme -- 版本控制方案(5個)

八.版本,認證,權限,限制,分頁 -- 源碼查看方法

from rest_framework.versioning import *      # 查看 版本 源碼
from rest_framework.authentication import *  # 查看 認證 源碼
from rest_framework.permissions import *     # 查看 權限 源碼
from rest_framework.throttling import *      # 查看 限制 源碼
from rest_framework.pagination import *      # 查看 分頁 源碼

from django.core.handlers.wsgi import WSGIRequest   # 查看Django自己的request 源碼

from rest_framework import settings # 查看settings.py文件中的配置項(版本,認證,權限,等等)

九.補充知識

1.issubset()

描述: issubset()方法用於判斷集合的所有元素是否都包含在指定集合中, 如果是則返回True, 否則返回False.

語法:

set.issubset(set)

參數:

  • set -- 必需, 要比較查找的集合

返回值: 返回布爾值, 如果都包含返回True, 否則返回False.

實例說明: 判斷集合x的所有元素是否都包含在集合u中.

x = {"a", "b", "c"}
y = {"f", "e", "d", "c", "b", "a"}
z = x.issubset(y)
print(z)

# 執行結果:
# True
x = {"a", "b", "c"}
y = {"f", "e", "d", "c", "b"}
z = x.issubset(y)
print(z)

# 執行結果:
# False

2.語法糖setter,getter,deleter

實例說明:

  • 例1:
class Person:
    def __init__(self, name):
        self.name = name

p1 = Person('王乃卉')
print(p1.name)

# 執行結果:
# 王乃卉
  • 例2:
class Person:

    def __init__(self, name):
        self.name = name

    @property   # getter -- 獲取屬性
    def age(self):
        print('get age called')
        return self._age

    @age.setter # setter -- 設置屬性
    def age(self, value):
        print('set age called')
        if not isinstance(value, int):
            raise TypeError('Excepted an int')
        self._age = value


p2 = Person('王力宏')  # 實例化
p2.age = 19     # 設置屬性
print(p2.age)   # 獲取屬性

##執行結果:
# set age called
# get age called
# 19
  • 例3:
class Person:

    def __init__(self, age):
        self.age = age

    @property   # getter -- 獲取屬性
    def age(self):
        print('get age called')
        return self._age

    @age.setter # setter -- 設置屬性
    def age(self, value):
        print('set age called')
        if not isinstance(value, int):
            raise TypeError('Excepted an int')
        self._age = value


p3 = Person(19)     # 實例化,設置屬性
print(p3.age)       # 獲取屬性

p3.age = 22         # 設置屬性
print(p3.age)       # 獲取屬性

##執行結果:
# set age called
# get age called
# 19
# set age called
# get age called
# 22
  • 例4:
class Person:
    def __init__(self):
        self.__name = None

    @property       # 訪問屬性
    def name(self):
        return self.__name

    @name.setter    # 設置屬性
    def name(self, value):
        self.__name = value

    @name.deleter   # 刪除屬性
    def name(self):
        del self.__name

p = Person()
print(p.name)     # 訪問屬性 --> None
p.name = '王乃卉'  # 設置屬性
print(p.name)     # 訪問屬性 --> 王乃卉
del p.name        # 刪除屬性
#print(p.name)    # 訪問屬性 --> 報錯: 對象沒有該屬性

3.ORM之update_or_create()

# 檢查是否有這條記錄,有則更新(需要defaults參數,字典類型),無則新增
models.UserToken.objects.update_or_create(user=user_obj, defaults={
    "token": access_token
})

4.assert斷言

根據Python 官方文檔解釋 : "Assert statements are a convenient way to insert debugging assertions into a program".

語法:

assert condition

用來讓程序測試這個condition, 如果condition為False, 則raise一個AssertionError出來. 邏輯上等同於:

if not condition:
    raise AssertionError('error_message')

實例說明:

>>> assert 1==1
>>> assert 1==0
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AssertionError

>>> assert True
>>> assert False
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AssertionError

>>> assert 1<2
>>> assert 1>2
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AssertionError

5.while循環測試

對比以下兩個例子並思考為什麽執行結果會不同.

  • 例1:
lst1 = []
while lst1[-1] and lst1:
    print('這裏是lst1')

# 執行結果:
# IndexError: list index out of range
  • 例2:
lst2 = []
while lst2 and lst2[-1]:
    print('這裏是lst2')

# 由於lst2為空,所以不執行while循環

6.ORM的QuerySet操作

記住一點: QuerySet切片之後不能再order_by了.



DRF 版本、認證、權限、限制、解析器和渲染器