Django Rest Framework

Django Rest Framework

1、什麼是RESTful

  • REST與技術無關,表明的是一種軟件架構風格,REST是Representational State Transfer的簡稱,中文翻譯爲「表徵狀態轉移」html

  • REST從資源的角度類審視整個網絡,它將分佈在網絡中某個節點的資源經過URL進行標識,客戶端應用經過URL來獲取資源的表徵,得到這些表徵導致這些應用轉變狀態python

  • 全部的數據,不過是經過網絡獲取的仍是操做(增刪改查)的數據,都是資源,將一切數據視爲資源是REST區別與其餘架構風格的最本質屬性web

  • 對於REST這種面向資源的架構風格,有人提出一種全新的結構理念,即:面向資源架構(ROA:Resource Oriented Architecture)數據庫

2、RESTful API設計

API與用戶的通訊協議,老是使用HTTPs協議。django

域名json

版本數組

路徑,視網絡上任何東西都是資源,均使用名詞表示(可複數)

method

  • GET :從服務器取出資源(一項或多項)
  • POST :在服務器新建一個資源
  • PUT :在服務器更新資源(客戶端提供改變後的完整資源)
  • PATCH :在服務器更新資源(客戶端提供改變的屬性)
  • DELETE :從服務器刪除資源

過濾,經過在url上傳參的形式傳遞搜索條件

狀態碼

200 OK - [GET]:服務器成功返回用戶請求的數據,該操做是冪等的(Idempotent)。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數據成功。
202 Accepted - [*]:表示一個請求已經進入後臺排隊(異步任務)
204 NO CONTENT - [DELETE]:用戶刪除數據成功。
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發出的請求有錯誤,服務器沒有進行新建或修改數據的操做,該操做是冪等的。
401 Unauthorized - [*]:表示用戶沒有權限(令牌、用戶名、密碼錯誤)。
403 Forbidden - [*] 表示用戶獲得受權(與401錯誤相對),可是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發出的請求針對的是不存在的記錄,服務器沒有進行操做,該操做是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(好比用戶請求JSON格式,可是隻有XML格式)。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再獲得的。
422 Unprocesable entity - [POST/PUT/PATCH] 當建立一個對象時,發生一個驗證錯誤。
500 INTERNAL SERVER ERROR - [*]:服務器發生錯誤,用戶將沒法判斷髮出的請求是否成功。

更多看這裏:http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html

錯誤處理,狀態碼是4xx時,應返回錯誤信息,error當作key。

{
    error: "Invalid API key"
}

返回結果,針對不一樣操做,服務器向用戶返回的結果應該符合如下規範。

GET /collection:返回資源對象的列表(數組)
GET /collection/resource:返回單個資源對象
POST /collection:返回新生成的資源對象
PUT /collection/resource:返回完整的資源對象
PATCH /collection/resource:返回完整的資源對象
DELETE /collection/resource:返回一個空文檔

Hypermedia API,RESTful API最好作到Hypermedia,即返回結果中提供連接,連向其餘API方法,使得用戶不查文檔,也知道下一步應該作什麼。

{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

摘自:http://www.ruanyifeng.com/blog/2014/05/restful_api.html

3、基於Django實現

路由系統:

urlpatterns = [
    url(r'^users', Users.as_view()),
]

CBV視圖:

from django.views import View
from django.http import JsonResponse
 
class Users(View):
    def get(self, request, *args, **kwargs):
        result = {
            'status': True,
            'data': 'response data'
        }
        return JsonResponse(result, status=200)
 
    def post(self, request, *args, **kwargs):
        result = {
            'status': True,
            'data': 'response data'
        }
        return JsonResponse(result, status=200)

4、基於Django Rest Framework框架實現

一、基本流程

url.py

from django.conf.urls import url, include
from web.views.s1_api import TestView
 
urlpatterns = [
    url(r'^test/', TestView.as_view()),
]

views.py

from rest_framework.views import APIView
from rest_framework.response import Response
 
 
class TestView(APIView):
    def dispatch(self, request, *args, **kwargs):
        """
        請求到來以後,都要執行dispatch方法,dispatch方法根據請求方式不一樣觸發 get/post/put等方法
         
        注意:APIView中的dispatch方法有好多好多的功能
        """
        return super().dispatch(request, *args, **kwargs)
 
    def get(self, request, *args, **kwargs):
        return Response('GET請求,響應內容')
 
    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容')
 
    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容')

上述是rest framework框架基本流程,重要的功能是在APIView的dispatch中觸發。

2.認證和受權

a. 用戶url傳入的token認證

  • urls.py

    from django.conf.urls import url, include
      from web.viewsimport TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.authentication import BaseAuthentication
      from rest_framework.request import Request
      from rest_framework import exceptions
    
      token_list = [
          'sfsfss123kuf3j123',
          'asijnfowerkkf9812',
      ]
    
    
      class TestAuthentication(BaseAuthentication):
          def authenticate(self, request):
              """
              用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)
              :param request: 
              :return: 
                  None,表示跳過該驗證;
                      若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置
                      self._authenticator = None
                      if api_settings.UNAUTHENTICATED_USER:
                          self.user = api_settings.UNAUTHENTICATED_USER()
                      else:
                          self.user = None
    
                      if api_settings.UNAUTHENTICATED_TOKEN:
                          self.auth = api_settings.UNAUTHENTICATED_TOKEN()
                      else:
                          self.auth = None
                  (user,token)表示驗證經過並設置用戶名和Token;
                  AuthenticationFailed異常
              """
              val = request.query_params.get('token')
              if val not in token_list:
                  raise exceptions.AuthenticationFailed("用戶認證失敗")
    
              return ('登陸用戶', '用戶token')
    
          def authenticate_header(self, request):
              """
              Return a string to be used as the value of the `WWW-Authenticate`
              header in a `401 Unauthenticated` response, or `None` if the
              authentication scheme should return `403 Permission Denied` responses.
              """
              # 驗證失敗時,返回的響應頭WWW-Authenticate對應的值
              pass
    
    
      class TestView(APIView):
          authentication_classes = [TestAuthentication, ]
          permission_classes = []
    
          def get(self, request, *args, **kwargs):
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

b. 請求頭認證

  • urls.py

    from django.conf.urls import url, include
      from web.viewsimport TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.authentication import BaseAuthentication
      from rest_framework.request import Request
      from rest_framework import exceptions
    
      token_list = [
          'sfsfss123kuf3j123',
          'asijnfowerkkf9812',
      ]
    
    
      class TestAuthentication(BaseAuthentication):
          def authenticate(self, request):
              """
              用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)
              :param request: 
              :return: 
                  None,表示跳過該驗證;
                      若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置
                      self._authenticator = None
                      if api_settings.UNAUTHENTICATED_USER:
                          self.user = api_settings.UNAUTHENTICATED_USER()
                      else:
                          self.user = None
    
                      if api_settings.UNAUTHENTICATED_TOKEN:
                          self.auth = api_settings.UNAUTHENTICATED_TOKEN()
                      else:
                          self.auth = None
                  (user,token)表示驗證經過並設置用戶名和Token;
                  AuthenticationFailed異常
              """
              import base64
              auth = request.META.get('HTTP_AUTHORIZATION', b'')
              if auth:
                  auth = auth.encode('utf-8')
              auth = auth.split()
              if not auth or auth[0].lower() != b'basic':
                  raise exceptions.AuthenticationFailed('驗證失敗')
              if len(auth) != 2:
                  raise exceptions.AuthenticationFailed('驗證失敗')
              username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
              if username == 'alex' and password == '123':
                  return ('登陸用戶', '用戶token')
              else:
                  raise exceptions.AuthenticationFailed('用戶名或密碼錯誤')
    
          def authenticate_header(self, request):
              """
              Return a string to be used as the value of the `WWW-Authenticate`
              header in a `401 Unauthenticated` response, or `None` if the
              authentication scheme should return `403 Permission Denied` responses.
              """
              return 'Basic realm=api'
    
    
      class TestView(APIView):
          authentication_classes = [TestAuthentication, ]
          permission_classes = []
    
          def get(self, request, *args, **kwargs):
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

c.多個認證規則

  • urls.py

    from django.conf.urls import url, include
    from web.views.s2_auth import TestView

    urlpatterns = [
    url(r'^test/', TestView.as_view()),
    ]

  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.authentication import BaseAuthentication
      from rest_framework.request import Request
      from rest_framework import exceptions
    
      token_list = [
          'sfsfss123kuf3j123',
          'asijnfowerkkf9812',
      ]
    
    
      class Test1Authentication(BaseAuthentication):
          def authenticate(self, request):
              """
              用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)
              :param request: 
              :return: 
                  None,表示跳過該驗證;
                      若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置
                      self._authenticator = None
                      if api_settings.UNAUTHENTICATED_USER:
                          self.user = api_settings.UNAUTHENTICATED_USER() # 默認值爲:匿名用戶
                      else:
                          self.user = None
    
                      if api_settings.UNAUTHENTICATED_TOKEN:
                          self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默認值爲:None
                      else:
                          self.auth = None
                  (user,token)表示驗證經過並設置用戶名和Token;
                  AuthenticationFailed異常
              """
              import base64
              auth = request.META.get('HTTP_AUTHORIZATION', b'')
              if auth:
                  auth = auth.encode('utf-8')
              else:
                  return None
              print(auth,'xxxx')
              auth = auth.split()
              if not auth or auth[0].lower() != b'basic':
                  raise exceptions.AuthenticationFailed('驗證失敗')
              if len(auth) != 2:
                  raise exceptions.AuthenticationFailed('驗證失敗')
              username, part, password = base64.b64decode(auth[1]).decode('utf-8').partition(':')
              if username == 'alex' and password == '123':
                  return ('登陸用戶', '用戶token')
              else:
                  raise exceptions.AuthenticationFailed('用戶名或密碼錯誤')
    
          def authenticate_header(self, request):
              """
              Return a string to be used as the value of the `WWW-Authenticate`
              header in a `401 Unauthenticated` response, or `None` if the
              authentication scheme should return `403 Permission Denied` responses.
              """
              # return 'Basic realm=api'
              pass
    
      class Test2Authentication(BaseAuthentication):
          def authenticate(self, request):
              """
              用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)
              :param request: 
              :return: 
                  None,表示跳過該驗證;
                      若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置
                      self._authenticator = None
                      if api_settings.UNAUTHENTICATED_USER:
                          self.user = api_settings.UNAUTHENTICATED_USER() # 默認值爲:匿名用戶
                      else:
                          self.user = None
    
                      if api_settings.UNAUTHENTICATED_TOKEN:
                          self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默認值爲:None
                      else:
                          self.auth = None
                  (user,token)表示驗證經過並設置用戶名和Token;
                  AuthenticationFailed異常
              """
              val = request.query_params.get('token')
              if val not in token_list:
                  raise exceptions.AuthenticationFailed("用戶認證失敗")
    
              return ('登陸用戶', '用戶token')
    
          def authenticate_header(self, request):
              """
              Return a string to be used as the value of the `WWW-Authenticate`
              header in a `401 Unauthenticated` response, or `None` if the
              authentication scheme should return `403 Permission Denied` responses.
              """
              pass
    
    
      class TestView(APIView):
          authentication_classes = [Test1Authentication, Test2Authentication]
          permission_classes = []
    
          def get(self, request, *args, **kwargs):
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

d.認證和權限

  • urls.py

    from django.conf.urls import url, include
    from web.views import TestView

    urlpatterns = [
    url(r'^test/', TestView.as_view()),
    ]

  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.authentication import BaseAuthentication
      from rest_framework.permissions import BasePermission
    
      from rest_framework.request import Request
      from rest_framework import exceptions
    
      token_list = [
          'sfsfss123kuf3j123',
          'asijnfowerkkf9812',
      ]
    
    
      class TestAuthentication(BaseAuthentication):
          def authenticate(self, request):
              """
              用戶認證,若是驗證成功後返回元組: (用戶,用戶Token)
              :param request: 
              :return: 
                  None,表示跳過該驗證;
                      若是跳過了全部認證,默認用戶和Token和使用配置文件進行設置
                      self._authenticator = None
                      if api_settings.UNAUTHENTICATED_USER:
                          self.user = api_settings.UNAUTHENTICATED_USER() # 默認值爲:匿名用戶
                      else:
                          self.user = None
    
                      if api_settings.UNAUTHENTICATED_TOKEN:
                          self.auth = api_settings.UNAUTHENTICATED_TOKEN()# 默認值爲:None
                      else:
                          self.auth = None
                  (user,token)表示驗證經過並設置用戶名和Token;
                  AuthenticationFailed異常
              """
              val = request.query_params.get('token')
              if val not in token_list:
                  raise exceptions.AuthenticationFailed("用戶認證失敗")
    
              return ('登陸用戶', '用戶token')
    
          def authenticate_header(self, request):
              """
              Return a string to be used as the value of the `WWW-Authenticate`
              header in a `401 Unauthenticated` response, or `None` if the
              authentication scheme should return `403 Permission Denied` responses.
              """
              pass
    
    
      class TestPermission(BasePermission):
          message = "權限驗證失敗"
    
          def has_permission(self, request, view):
              """
              判斷是否有權限訪問當前請求
              Return `True` if permission is granted, `False` otherwise.
              :param request: 
              :param view: 
              :return: True有權限;False無權限
              """
              if request.user == "管理員":
                  return True
    
          # GenericAPIView中get_object時調用
          def has_object_permission(self, request, view, obj):
              """
              視圖繼承GenericAPIView,並在其中使用get_object時獲取對象時,觸發單獨對象權限驗證
              Return `True` if permission is granted, `False` otherwise.
              :param request: 
              :param view: 
              :param obj: 
              :return: True有權限;False無權限
              """
              if request.user == "管理員":
                  return True
    
    
      class TestView(APIView):
          # 認證的動做是由request.user觸發
          authentication_classes = [TestAuthentication, ]
    
          # 權限
          # 循環執行全部的權限
          permission_classes = [TestPermission, ]
    
          def get(self, request, *args, **kwargs):
              # self.dispatch
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

e. 全局使用

上述操做中均是對單獨視圖進行特殊配置,若是想要對全局進行配置,則須要再配置文件中寫入便可

  • settings.py

    REST_FRAMEWORK = {
          'UNAUTHENTICATED_USER': None,
          'UNAUTHENTICATED_TOKEN': None,
          "DEFAULT_AUTHENTICATION_CLASSES": [
              "web.utils.TestAuthentication",
          ],
          "DEFAULT_PERMISSION_CLASSES": [
              "web.utils.TestPermission",
          ],
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
    
      class TestView(APIView):
    
          def get(self, request, *args, **kwargs):
              # self.dispatch
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

3. 用戶訪問次數/頻率限制

  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      import time
      from rest_framework.views import APIView
      from rest_framework.response import Response
    
      from rest_framework import exceptions
      from rest_framework.throttling import BaseThrottle
      from rest_framework.settings import api_settings
    
      # 保存訪問記錄
      RECORD = {
          '用戶IP': [12312139, 12312135, 12312133, ]
      }
    
    
      class TestThrottle(BaseThrottle):
          ctime = time.time
    
          def get_ident(self, request):
              """
              根據用戶IP和代理IP,當作請求者的惟一IP
              Identify the machine making the request by parsing HTTP_X_FORWARDED_FOR
              if present and number of proxies is > 0. If not use all of
              HTTP_X_FORWARDED_FOR if it is available, if not use REMOTE_ADDR.
              """
              xff = request.META.get('HTTP_X_FORWARDED_FOR')
              remote_addr = request.META.get('REMOTE_ADDR')
              num_proxies = api_settings.NUM_PROXIES
    
              if num_proxies is not None:
                  if num_proxies == 0 or xff is None:
                      return remote_addr
                  addrs = xff.split(',')
                  client_addr = addrs[-min(num_proxies, len(addrs))]
                  return client_addr.strip()
    
              return ''.join(xff.split()) if xff else remote_addr
    
          def allow_request(self, request, view):
              """
              是否仍然在容許範圍內
              Return `True` if the request should be allowed, `False` otherwise.
              :param request: 
              :param view: 
              :return: True,表示能夠經過;False表示已超過限制,不容許訪問
              """
              # 獲取用戶惟一標識(如:IP)
    
              # 容許一分鐘訪問10次
              num_request = 10
              time_request = 60
    
              now = self.ctime()
              ident = self.get_ident(request)
              self.ident = ident
              if ident not in RECORD:
                  RECORD[ident] = [now, ]
                  return True
              history = RECORD[ident]
              while history and history[-1] <= now - time_request:
                  history.pop()
              if len(history) < num_request:
                  history.insert(0, now)
                  return True
    
          def wait(self):
              """
              多少秒後能夠容許繼續訪問
              Optionally, return a recommended number of seconds to wait before
              the next request.
              """
              last_time = RECORD[self.ident][0]
              now = self.ctime()
              return int(60 + last_time - now)
    
    
      class TestView(APIView):
          throttle_classes = [TestThrottle, ]
    
          def get(self, request, *args, **kwargs):
              # self.dispatch
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')
    
          def throttled(self, request, wait):
              """
              訪問次數被限制時,定製錯誤信息
              """
    
              class Throttled(exceptions.Throttled):
                  default_detail = '請求被限制.'
                  extra_detail_singular = '請 {wait} 秒以後再重試.'
                  extra_detail_plural = '請 {wait} 秒以後再重試.'
    
              raise Throttled(wait)

b. 基於用戶IP顯示訪問頻率(利於Django緩存)

  • settings.py

    REST_FRAMEWORK = {
         'DEFAULT_THROTTLE_RATES': {
             'test_scope': '10/m',
         },
     }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
    
      from rest_framework import exceptions
      from rest_framework.throttling import SimpleRateThrottle
    
    
      class TestThrottle(SimpleRateThrottle):
    
          # 配置文件定義的顯示頻率的Key
          scope = "test_scope"
    
          def get_cache_key(self, request, view):
              """
              Should return a unique cache-key which can be used for throttling.
              Must be overridden.
    
              May return `None` if the request should not be throttled.
              """
              if not request.user:
                  ident = self.get_ident(request)
              else:
                  ident = request.user
    
              return self.cache_format % {
                  'scope': self.scope,
                  'ident': ident
              }
    
    
      class TestView(APIView):
          throttle_classes = [TestThrottle, ]
    
          def get(self, request, *args, **kwargs):
              # self.dispatch
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')
    
          def throttled(self, request, wait):
              """
              訪問次數被限制時,定製錯誤信息
              """
    
              class Throttled(exceptions.Throttled):
                  default_detail = '請求被限制.'
                  extra_detail_singular = '請 {wait} 秒以後再重試.'
                  extra_detail_plural = '請 {wait} 秒以後再重試.'
    
              raise Throttled(wait)

c. view中限制請求頻率

  • settings.py

    REST_FRAMEWORK = {
          'DEFAULT_THROTTLE_RATES': {
              'xxxxxx': '10/m',
          },
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
    
      from rest_framework import exceptions
      from rest_framework.throttling import ScopedRateThrottle
    
    
      # 繼承 ScopedRateThrottle
      class TestThrottle(ScopedRateThrottle):
    
          def get_cache_key(self, request, view):
              """
              Should return a unique cache-key which can be used for throttling.
              Must be overridden.
    
              May return `None` if the request should not be throttled.
              """
              if not request.user:
                  ident = self.get_ident(request)
              else:
                  ident = request.user
    
              return self.cache_format % {
                  'scope': self.scope,
                  'ident': ident
              }
    
    
      class TestView(APIView):
          throttle_classes = [TestThrottle, ]
    
          # 在settings中獲取 xxxxxx 對應的頻率限制值
          throttle_scope = "xxxxxx"
    
          def get(self, request, *args, **kwargs):
              # self.dispatch
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')
    
          def throttled(self, request, wait):
              """
              訪問次數被限制時,定製錯誤信息
              """
    
              class Throttled(exceptions.Throttled):
                  default_detail = '請求被限制.'
                  extra_detail_singular = '請 {wait} 秒以後再重試.'
                  extra_detail_plural = '請 {wait} 秒以後再重試.'
    
              raise Throttled(wait)

d. 匿名時用IP限制+登陸時用Token限制

  • settings.py

    REST_FRAMEWORK = {
          'UNAUTHENTICATED_USER': None,
          'UNAUTHENTICATED_TOKEN': None,
          'DEFAULT_THROTTLE_RATES': {
              'luffy_anon': '10/m',
              'luffy_user': '20/m',
          },
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views.s3_throttling import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view()),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
    
      from rest_framework.throttling import SimpleRateThrottle
    
    
      class LuffyAnonRateThrottle(SimpleRateThrottle):
          """
          匿名用戶,根據IP進行限制
          """
          scope = "luffy_anon"
    
          def get_cache_key(self, request, view):
              # 用戶已登陸,則跳過 匿名頻率限制
              if request.user:
                  return None
    
              return self.cache_format % {
                  'scope': self.scope,
                  'ident': self.get_ident(request)
              }
    
    
      class LuffyUserRateThrottle(SimpleRateThrottle):
          """
          登陸用戶,根據用戶token限制
          """
          scope = "luffy_user"
    
          def get_ident(self, request):
              """
              認證成功時:request.user是用戶對象;request.auth是token對象
              :param request: 
              :return: 
              """
              # return request.auth.token
              return "user_token"
    
          def get_cache_key(self, request, view):
              """
              獲取緩存key
              :param request: 
              :param view: 
              :return: 
              """
              # 未登陸用戶,則跳過 Token限制
              if not request.user:
                  return None
    
              return self.cache_format % {
                  'scope': self.scope,
                  'ident': self.get_ident(request)
              }
    
    
      class TestView(APIView):
          throttle_classes = [LuffyUserRateThrottle, LuffyAnonRateThrottle, ]
    
          def get(self, request, *args, **kwargs):
              # self.dispatch
              print(request.user)
              print(request.auth)
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

e. 全局使用

  • settings

    REST_FRAMEWORK = {
          'DEFAULT_THROTTLE_CLASSES': [
              'api.utils.throttles.throttles.LuffyAnonRateThrottle',
              'api.utils.throttles.throttles.LuffyUserRateThrottle',
          ],
          'DEFAULT_THROTTLE_RATES': {
              'anon': '10/day',
              'user': '10/day',
              'luffy_anon': '10/m',
              'luffy_user': '20/m',
          },
      }

4.版本

a. 基於url的get傳參方式

如:/users?version=v1

  • settings

    REST_FRAMEWORK = {
          'DEFAULT_VERSION': 'v1',            # 默認版本
          'ALLOWED_VERSIONS': ['v1', 'v2'],   # 容許的版本
          'VERSION_PARAM': 'version'          # URL中獲取值的key
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view(),name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.versioning import QueryParameterVersioning
    
    
      class TestView(APIView):
          versioning_class = QueryParameterVersioning
    
          def get(self, request, *args, **kwargs):
    
              # 獲取版本
              print(request.version)
              # 獲取版本管理的類
              print(request.versioning_scheme)
    
              # 反向生成URL
              reverse_url = request.versioning_scheme.reverse('test', request=request)
              print(reverse_url)
    
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

b. 基於url的正則方式

如:/v1/users/

  • settings.py

    REST_FRAMEWORK = {
          'DEFAULT_VERSION': 'v1',            # 默認版本
          'ALLOWED_VERSIONS': ['v1', 'v2'],   # 容許的版本
          'VERSION_PARAM': 'version'          # URL中獲取值的key
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^(?P<version>[v1|v2]+)/test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.versioning import URLPathVersioning
    
    
      class TestView(APIView):
          versioning_class = URLPathVersioning
    
          def get(self, request, *args, **kwargs):
              # 獲取版本
              print(request.version)
              # 獲取版本管理的類
              print(request.versioning_scheme)
    
              # 反向生成URL
              reverse_url = request.versioning_scheme.reverse('test', request=request)
              print(reverse_url)
    
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

c. 基於 accept 請求頭方式

如:Accept: application/json; version=1.0

  • settings.py

    REST_FRAMEWORK = {
          'DEFAULT_VERSION': 'v1',            # 默認版本
          'ALLOWED_VERSIONS': ['v1', 'v2'],   # 容許的版本
          'VERSION_PARAM': 'version'          # URL中獲取值的key
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.versioning import AcceptHeaderVersioning
    
    
      class TestView(APIView):
          versioning_class = AcceptHeaderVersioning
    
          def get(self, request, *args, **kwargs):
              # 獲取版本 HTTP_ACCEPT頭
              print(request.version)
              # 獲取版本管理的類
              print(request.versioning_scheme)
              # 反向生成URL
              reverse_url = request.versioning_scheme.reverse('test', request=request)
              print(reverse_url)
    
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

d. 基於主機名方法

如:v1.example.com

  • settings.py

    ALLOWED_HOSTS = ['*']
    REST_FRAMEWORK = {
    'DEFAULT_VERSION': 'v1', # 默認版本
    'ALLOWED_VERSIONS': ['v1', 'v2'], # 容許的版本
    'VERSION_PARAM': 'version' # URL中獲取值的key
    }

  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.versioning import HostNameVersioning
    
    
      class TestView(APIView):
          versioning_class = HostNameVersioning
    
          def get(self, request, *args, **kwargs):
              # 獲取版本
              print(request.version)
              # 獲取版本管理的類
              print(request.versioning_scheme)
              # 反向生成URL
              reverse_url = request.versioning_scheme.reverse('test', request=request)
              print(reverse_url)
    
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

e. 基於django路由系統的namespace

如:example.com/v1/users/

REST_FRAMEWORK = {
        'DEFAULT_VERSION': 'v1',  # 默認版本
        'ALLOWED_VERSIONS': ['v1', 'v2'],  # 容許的版本
        'VERSION_PARAM': 'version'  # URL中獲取值的key
    }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'^v1/', ([
                            url(r'test/', TestView.as_view(), name='test'),
                        ], None, 'v1')),
          url(r'^v2/', ([
                            url(r'test/', TestView.as_view(), name='test'),
                        ], None, 'v2')),
    
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.versioning import NamespaceVersioning
    
    
      class TestView(APIView):
          versioning_class = NamespaceVersioning
    
          def get(self, request, *args, **kwargs):
              # 獲取版本
              print(request.version)
              # 獲取版本管理的類
              print(request.versioning_scheme)
              # 反向生成URL
              reverse_url = request.versioning_scheme.reverse('test', request=request)
              print(reverse_url)
    
              return Response('GET請求,響應內容')
    
          def post(self, request, *args, **kwargs):
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

f. 全局使用

  • settings.py

    REST_FRAMEWORK = {
          'DEFAULT_VERSIONING_CLASS':"rest_framework.versioning.URLPathVersioning",
          'DEFAULT_VERSION': 'v1',
          'ALLOWED_VERSIONS': ['v1', 'v2'],
          'VERSION_PARAM': 'version' 
      }

5.解析器(parser)

根據請求頭 content-type 選擇對應的解析器就請求體內容進行處理。

a. 僅處理請求頭content-type爲application/json的請求體

  • urls.py

    from django.conf.urls import url, include
      from web.views.s5_parser import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.request import Request
      from rest_framework.parsers import JSONParser
    
    
      class TestView(APIView):
          parser_classes = [JSONParser, ]
    
          def post(self, request, *args, **kwargs):
              print(request.content_type)
    
              # 獲取請求的值,並使用對應的JSONParser進行處理
              print(request.data)
    
              # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值
              print(request.POST)
              print(request.FILES)
    
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

b. 僅處理請求頭content-type爲application/x-www-form-urlencoded 的請求體

  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.request import Request
      from rest_framework.parsers import FormParser
    
    
      class TestView(APIView):
          parser_classes = [FormParser, ]
    
          def post(self, request, *args, **kwargs):
              print(request.content_type)
    
              # 獲取請求的值,並使用對應的JSONParser進行處理
              print(request.data)
    
              # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值
              print(request.POST)
              print(request.FILES)
    
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

c. 僅處理請求頭content-type爲multipart/form-data的請求體

  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.request import Request
      from rest_framework.parsers import MultiPartParser
    
    
      class TestView(APIView):
          parser_classes = [MultiPartParser, ]
    
          def post(self, request, *args, **kwargs):
              print(request.content_type)
    
              # 獲取請求的值,並使用對應的JSONParser進行處理
              print(request.data)
              # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值
              print(request.POST)
              print(request.FILES)
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')
  • upload.html

    <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      <form action="http://127.0.0.1:8000/test/" method="post" enctype="multipart/form-data">
          <input type="text" name="user" />
          <input type="file" name="img">
    
          <input type="submit" value="提交">
    
      </form>
      </body>
      </html>

d. 僅上傳文件

  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'test/(?P<filename>[^/]+)', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.request import Request
      from rest_framework.parsers import FileUploadParser
    
    
      class TestView(APIView):
          parser_classes = [FileUploadParser, ]
    
          def post(self, request, filename, *args, **kwargs):
              print(filename)
              print(request.content_type)
    
              # 獲取請求的值,並使用對應的JSONParser進行處理
              print(request.data)
              # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值
              print(request.POST)
              print(request.FILES)
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')
  • upload.html

    <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      <form action="http://127.0.0.1:8000/test/f1.numbers" method="post" enctype="multipart/form-data">
          <input type="text" name="user" />
          <input type="file" name="img">
    
          <input type="submit" value="提交">
    
      </form>
      </body>
      </html>

e. 同時多個Parser

當同時使用多個parser時,rest framework會根據請求頭content-type自動進行比對,並使用對應parser

  • urls.py

    from django.conf.urls import url, include
          from web.views import TestView
    
          urlpatterns = [
              url(r'test/', TestView.as_view(), name='test'),
          ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework.request import Request
      from rest_framework.parsers import JSONParser, FormParser, MultiPartParser
    
    
      class TestView(APIView):
          parser_classes = [JSONParser, FormParser, MultiPartParser, ]
    
          def post(self, request, *args, **kwargs):
              print(request.content_type)
    
              # 獲取請求的值,並使用對應的JSONParser進行處理
              print(request.data)
              # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值
              print(request.POST)
              print(request.FILES)
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

f. 全局使用

  • settings.py

    REST_FRAMEWORK = {
          'DEFAULT_PARSER_CLASSES':[
              'rest_framework.parsers.JSONParser'
              'rest_framework.parsers.FormParser'
              'rest_framework.parsers.MultiPartParser'
          ]
    
      }
  • urls.py

    from django.conf.urls import url, include
      from web.views import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
    
    
      class TestView(APIView):
          def post(self, request, *args, **kwargs):
              print(request.content_type)
    
              # 獲取請求的值,並使用對應的JSONParser進行處理
              print(request.data)
              # application/x-www-form-urlencoded 或 multipart/form-data時,request.POST中才有值
              print(request.POST)
              print(request.FILES)
              return Response('POST請求,響應內容')
    
          def put(self, request, *args, **kwargs):
              return Response('PUT請求,響應內容')

注意:個別特殊的值能夠經過Django的request對象 request._request 來進行獲取

6.序列化

序列化用於對用戶請求數據進行驗證和數據進行序列化。

a. 自定義字段

  • urls.py

    from django.conf.urls import url, include
      from web.views.s6_serializers import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework import serializers
      from .. import models
    
    
      class PasswordValidator(object):
          def __init__(self, base):
              self.base = base
    
          def __call__(self, value):
              if value != self.base:
                  message = 'This field must be %s.' % self.base
                  raise serializers.ValidationError(message)
    
          def set_context(self, serializer_field):
              """
              This hook is called by the serializer instance,
              prior to the validation call being made.
              """
              # 執行驗證以前調用,serializer_fields是當前字段對象
              pass
    
    
      class UserSerializer(serializers.Serializer):
          ut_title = serializers.CharField(source='ut.title')
          user = serializers.CharField(min_length=6)
          pwd = serializers.CharField(error_messages={'required': '密碼不能爲空'}, validators=[PasswordValidator('666')])
    
    
      class TestView(APIView):
          def get(self, request, *args, **kwargs):
    
              # 序列化,將數據庫查詢字段序列化爲字典
              data_list = models.UserInfo.objects.all()
              ser = UserSerializer(instance=data_list, many=True)
              # 或
              # obj = models.UserInfo.objects.all().first()
              # ser = UserSerializer(instance=obj, many=False)
              return Response(ser.data)
    
          def post(self, request, *args, **kwargs):
              # 驗證,對請求發來的數據進行驗證
              ser = UserSerializer(data=request.data)
              if ser.is_valid():
                  print(ser.validated_data)
              else:
                  print(ser.errors)
    
              return Response('POST請求,響應內容')

b. 基於Model自動生成字段

  • urls.py

    from django.conf.urls import url, include
      from web.views.s6_serializers import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework import serializers
      from .. import models
    
    
      class PasswordValidator(object):
          def __init__(self, base):
              self.base = str(base)
    
          def __call__(self, value):
              if value != self.base:
                  message = 'This field must be %s.' % self.base
                  raise serializers.ValidationError(message)
    
          def set_context(self, serializer_field):
              """
              This hook is called by the serializer instance,
              prior to the validation call being made.
              """
              # 執行驗證以前調用,serializer_fields是當前字段對象
              pass
    
      class ModelUserSerializer(serializers.ModelSerializer):
    
          user = serializers.CharField(max_length=32)
    
          class Meta:
              model = models.UserInfo
              fields = "__all__"
              # fields = ['user', 'pwd', 'ut']
              depth = 2
              extra_kwargs = {'user': {'min_length': 6}, 'pwd': {'validators': [PasswordValidator(666), ]}}
              # read_only_fields = ['user']
    
    
      class TestView(APIView):
          def get(self, request, *args, **kwargs):
    
              # 序列化,將數據庫查詢字段序列化爲字典
              data_list = models.UserInfo.objects.all()
              ser = ModelUserSerializer(instance=data_list, many=True)
              # 或
              # obj = models.UserInfo.objects.all().first()
              # ser = UserSerializer(instance=obj, many=False)
              return Response(ser.data)
    
          def post(self, request, *args, **kwargs):
              # 驗證,對請求發來的數據進行驗證
              print(request.data)
              ser = ModelUserSerializer(data=request.data)
              if ser.is_valid():
                  print(ser.validated_data)
              else:
                  print(ser.errors)
    
              return Response('POST請求,響應內容')

c. 生成URL

  • urls.py

    from django.conf.urls import url, include
      from web.views.s6_serializers import TestView
    
      urlpatterns = [
          url(r'test/', TestView.as_view(), name='test'),
          url(r'detail/(?P<pk>\d+)/', TestView.as_view(), name='detail'),
      ]
  • views.py

    #!/usr/bin/env python
      # -*- coding:utf-8 -*-
      from rest_framework.views import APIView
      from rest_framework.response import Response
      from rest_framework import serializers
      from .. import models
    
    
      class PasswordValidator(object):
          def __init__(self, base):
              self.base = str(base)
    
          def __call__(self, value):
              if value != self.base:
                  message = 'This field must be %s.' % self.base
                  raise serializers.ValidationError(message)
    
          def set_context(self, serializer_field):
              """
              This hook is called by the serializer instance,
              prior to the validation call being made.
              """
              # 執行驗證以前調用,serializer_fields是當前字段對象
              pass
    
    
      class ModelUserSerializer(serializers.ModelSerializer):
          ut = serializers.HyperlinkedIdentityField(view_name='detail')
          class Meta:
              model = models.UserInfo
              fields = "__all__"
    
              extra_kwargs = {
                  'user': {'min_length': 6},
                  'pwd': {'validators': [PasswordValidator(666),]},
              }
    
    
    
      class TestView(APIView):
          def get(self, request, *args, **kwargs):
    
              # 序列化,將數據庫查詢字段序列化爲字典
              data_list = models.UserInfo.objects.all()
              ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request})
              # 或
              # obj = models.UserInfo.objects.all().first()
              # ser = UserSerializer(instance=obj, many=False)
              return Response(ser.data)
    
          def post(self, request, *args, **kwargs):
              # 驗證,對請求發來的數據進行驗證
              print(request.data)
              ser = ModelUserSerializer(data=request.data)
              if ser.is_valid():
                  print(ser.validated_data)
              else:
                  print(ser.errors)
    
              return Response('POST請求,響應內容')
相關文章
相關標籤/搜索