Django REST framework 2

認證

  • 身份驗證是將傳入請求與一組標識憑據(例如請求來自的用戶或其簽名的令牌)相關聯的機制。而後 權限 和 限制 組件決定是否拒絕這個請求。
  • 認證肯定了你是誰
  • 權限肯定你能不能訪問某個接口
  • 限制肯定你訪問某個接口的頻率

1、自定義Token認證

REST framework 提供了一些身份驗證方案,而且還容許實現自定義方案。前端

一、表:

class UserInfo(models.Model):
    username = models.CharField(max_length=16)
    password = models.CharField(max_length=32)
    type = models.SmallIntegerField(
        choices=((1, '普通用戶'), (2, 'VIP用戶')),
        default=1
    )


class Token(models.Model):
    user = models.OneToOneField(to='UserInfo')
    token_code = models.CharField(max_length=128)
 

二、定義一個登陸視圖:

def get_random_token(username):
    """
    根據用戶名和時間戳生成隨機token
    :param username:
    :return:
    """
    import hashlib, time
    timestamp = str(time.time())
    m = hashlib.md5(bytes(username, encoding="utf8"))
    m.update(bytes(timestamp, encoding="utf8"))
    return m.hexdigest()


class LoginView(APIView):
    """
    校驗用戶名密碼是否正確從而生成token的視圖
    """
    def post(self, request):
        res = {"code": 0}
        print(request.data)
        username = request.data.get("username")
        password = request.data.get("password")

        user = models.UserInfo.objects.filter(username=username, password=password).first()
        if user:
            # 若是用戶名密碼正確
            token = get_random_token(username)
            models.Token.objects.update_or_create(defaults={"token_code": token}, user=user)
            res["token"] = token
        else:
            res["code"] = 1
            res["error"] = "用戶名或密碼錯誤"
        return Response(res)

三、定義一個認證類

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


class MyAuth(BaseAuthentication):
    def authenticate(self, request):
        if request.method in ["POST", "PUT", "DELETE"]:
            request_token = request.data.get("token", None)
            if not request_token:
                raise AuthenticationFailed('缺乏token')
            token_obj = models.Token.objects.filter(token_code=request_token).first()
            if not token_obj:
                raise AuthenticationFailed('無效的token')
            return token_obj.user.username, None
        else:
            return None, None

四、視圖級別認證

class CommentViewSet(ModelViewSet):

    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]

五、全局級別認證

# 在settings.py中配置
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ]
}

權限

只有VIP用戶才能看的內容。web

1、自定義

一、自定義一個權限類

# 自定義權限
class MyPermission(BasePermission):
    message = 'VIP用戶才能訪問'

    def has_permission(self, request, view):
        """
        自定義權限只有VIP用戶才能訪問
        """
        # 由於在進行權限判斷以前已經作了認證判斷,因此這裏能夠直接拿到request.user
        if request.user and request.user.type == 2:  # 若是是VIP用戶
            return True
        else:
            return False

二、視圖級別配置

class CommentViewSet(ModelViewSet):

    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    authentication_classes = [MyAuth, ]
    permission_classes = [MyPermission, ]

三、全局級別設置

# 在settings.py中設置rest framework相關配置項
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
    "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
}

限制

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

1、自定義

一、自定義限制類

VISIT_RECORD = {}
# 自定義限制
class MyThrottle(object):

    def __init__(self):
        self.history = None

    def allow_request(self, request, view):
        """
        自定義頻率限制60秒內只能訪問三次
        """
        # 獲取用戶IP
        ip = request.META.get("REMOTE_ADDR")
        timestamp = time.time()
        if ip not in VISIT_RECORD:
            VISIT_RECORD[ip] = [timestamp, ]
            return True
        history = VISIT_RECORD[ip]
        self.history = history
        history.insert(0, timestamp)
        while history and history[-1] < timestamp - 60:
            history.pop()
        if len(history) > 3:
            return False
        else:
            return True

    def wait(self):
        """
        限制時間還剩多少
        """
        timestamp = time.time()
        return 60 - (timestamp - self.history[-1])

二、視圖使用

class CommentViewSet(ModelViewSet):

    queryset = models.Comment.objects.all()
    serializer_class = app01_serializers.CommentSerializer
    throttle_classes = [MyThrottle, ]

三、全局使用

# 在settings.py中設置rest framework相關配置項
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
    "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.MyThrottle", ]
}

1、內置

一、使用內置限制類

from rest_framework.throttling import SimpleRateThrottle


class VisitThrottle(SimpleRateThrottle):

    scope = "xxx"

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

二、全局配置

# 在settings.py中設置rest framework相關配置項
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": ["app01.utils.MyAuth", ],
    # "DEFAULT_PERMISSION_CLASSES": ["app01.utils.MyPermission", ]
    "DEFAULT_THROTTLE_CLASSES": ["app01.utils.VisitThrottle", ],
    "DEFAULT_THROTTLE_RATES": {
        "xxx": "5/m",
    }
}

DRF內置分頁器

  • rest framework中提供了三種分頁模式:
  • from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination

1、PageNumberPagination

  • 按頁碼數分頁,第n頁,每頁顯示m條數據
  • 例如:http://127.0.0.1:8000/api/article/?page=2&size=1

一、分頁器

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

二、視圖

class ArticleList(APIView):
    def get(self, request, *args, **kwargs):
        res = {"code": 0}
        article_list = models.Article.objects.all().order_by("id")
        # 分頁
        page_obj = MyPageNumber()
        page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
        ser_obj = ArticleSerializer(page_article, many=True)
        res["data"] = ser_obj.data
        return Response(res)

三、返回帶頁碼連接的響應

class ArticleList(APIView):
    def get(self, request, *args, **kwargs):
        res = {"code": 0}
        article_list = models.Article.objects.all().order_by("id")
        # 分頁
        page_obj = MyPageNumber()
        page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
        ser_obj = ArticleSerializer(page_article, many=True)
        res["data"] = ser_obj.data
        return page_obj.get_paginated_response(res)

2、LimitOffsetPagination 

  • 分頁,在n位置,向後查看m條數據
  • 例如:http://127.0.0.1:8000/api/article/?offset=2&limit=2

一、分頁器

# offset分頁
class MyLimitOffset(LimitOffsetPagination):
    default_limit = 1
    limit_query_param = 'limit'
    offset_query_param = 'offset'
    max_limit = 999

二、視圖

class ArticleList(APIView):
    def get(self, request, *args, **kwargs):
        res = {"code": 0}
        article_list = models.Article.objects.all().order_by("id")
        # 分頁
        page_obj = MyLimitOffset()
        page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
        ser_obj = ArticleSerializer(page_article, many=True)
        res["data"] = ser_obj.data
        return page_obj.get_paginated_response(res)

3、CursorPagination

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

一、分頁器

# 加密分頁
class MyCursorPagination(CursorPagination):
    cursor_query_param = 'cursor'
    page_size = 1
    ordering = '-id'  # 重寫要排序的字段

二、視圖

class ArticleList(APIView):
    def get(self, request, *args, **kwargs):
        res = {"code": 0}
        article_list = models.Article.objects.all().order_by("id")
        # 分頁
        page_obj = MyCursorPagination()
        page_article = page_obj.paginate_queryset(queryset=article_list, request=request, view=self)
        ser_obj = ArticleSerializer(page_article, many=True)
        res["data"] = ser_obj.data
        # return Response(res)
        return page_obj.get_paginated_response(res)

4、全局配置

REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
    'PAGE_SIZE': 100
}

5、局部配置

能夠在視圖類中進行局部設置後端

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

解析器

  • 解析器的做用就是服務端接收客戶端傳過來的數據,把數據解析成本身能夠處理的數據。本質就是對請求體中的數據進行解析。
  • 在瞭解解析器以前,要先知道Accept以及ContentType請求頭。
  • Accept是告訴對方能解析什麼樣的數據,一般也能夠表示想要什麼樣的數據。
  • 解析器工做原理的就是拿到請求的ContentType來判斷前端給後端的數據類型是什麼,而後在後端使用相應的解析器去解析數據。

1、Django中的數據解析

  • 在視圖中能夠經過request.POST來獲取前端發來的請求數據,那麼Django框架是如何拿到請求體中的數據的呢?
  • 首先,request對象是 WSGIRequest 類的實例化對象
  • 在Django的視圖中經過request.POST和request.FILES可以取到數據都是由於在這裏把請求的數據解析,並賦值給request對象了。
  • Django的解析器是不支持 ContenType爲 application/json 的,也就是說沒法解析json格式的數據。

2、DRF中的解析器

  • DRF中獲取請求提交的數據是經過訪問request.data
  • 若是沒有配置解析器,DRF會使用默認的解析器:
  • 能夠在單個視圖或者全局的settings.py中配置要使用的解析器。
  • 注意:當項目中只配置了 JSONParser 解析器時,就只能解析JSON格式的數據了,客戶端若是使用瀏覽器提交,將沒法解析。
  • 注意,在視圖類中定義的配置項的優先級要高於全局配置中的配置項。

一、單個視圖配置

class BookViewSet(ModelViewSet):
    queryset = models.Book.objects.all()
    serializer_class = BookModelSerializer
    parser_classes = [JSONParser, ]

二、全局配置

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES': (
        'rest_framework.parsers.JSONParser',
    )
}

渲染器

  • 渲染器同解析器相反,它定義了框架按照content_type來返回不一樣的響應。
  • 能夠在視圖中局部設置也能夠在全局的settings.py中進行設置
  • 注意,在視圖類中定義的配置項的優先級要高於全局配置中的配置項。
  • DRF提供的渲染器有不少,默認是:
 'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ),

一、局部設置

這樣設置後就只能返回JSON格式的數據了,並不會像以前同樣提供一個閱讀友好的web頁面。api

class PublisherViewSet(ModelViewSet):
    queryset = models.Publisher.objects.all()
    serializer_class = PublisherModelSerializer
    renderer_classes = [JSONRenderer, ]

二、全局設置

REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (
        'rest_framework.renderers.JSONRenderer',
    ),
}

DRF的版本控制

  • API 版本控制容許在不一樣的客戶端之間更改行爲(同一個接口的不一樣版本會返回不一樣的數據)。 DRF提供了許多不一樣的版本控制方案。
  • 可能會有一些客戶端由於某些緣由再也不維護了,可是後端的接口還要不斷的更新迭代,這個時候經過版本控制返回不一樣的內容就是一種不錯的解決方案。

1、DRF提供的版本控制方案

DRF提供了五種版本控制方案,以下圖:瀏覽器

2、版本控制系統的使用

一、全局配置

這裏以URLPathVersioning 爲例,仍是在項目的settings.py中REST_FRAMEWORK配置項下配置:app

REST_FRAMEWORK = {
    ...
    'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning',
    'DEFAULT_VERSION': 'v1',  # 默認的版本
    'ALLOWED_VERSIONS': ['v1', 'v2'],  # 有效的版本
    'VERSION_PARAM': 'version',  # 版本的參數名與URL conf中一致
}

二、urls.py中

urlpatterns = [
    ...
    url(r'^(?P<version>[v1|v2]+)/publishers/$', views.PublisherViewSet.as_view({'get': 'list', 'post': 'create'})),
    url(r'^(?P<version>[v1|v2]+)/publishers/(?P<pk>\d+)/$', views.PublisherViewSet.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),

]

三、視圖中

  • 視圖中能夠經過訪問 request.version 來獲取當前請求的具體版本,而後根據不一樣的版原本返回不一樣的內容。
  • 能夠在視圖中自定義具體的行爲,下面以不一樣的版本返回不一樣的序列化類爲例。
class PublisherViewSet(ModelViewSet):

    def get_serializer_class(self):
        """不一樣的版本使用不一樣的序列化類"""
        if self.request.version == 'v1':
            return PublisherModelSerializerVersion1
        else:
            return PublisherModelSerializer
    queryset = models.Publisher.objects.all()

四、局部配置

注意,一般是不會單獨給某個視圖設置版本控制的,若是你確實須要給單獨的視圖設置版本控制,能夠在視圖中設置versioning_class屬性,以下:框架

class PublisherViewSet(ModelViewSet):

    ...
    versioning_class = URLPathVersioning
相關文章
相關標籤/搜索