什么是RESTful API以及Django RestFramework

一. 什么是RESTful API以及Django RestFramework

1. 協(xié)議

API與用戶的通信協(xié)議忽媒,總是使用HTTPS協(xié)議。

2. 域名

3. 版本

應該將API的版本號放入URL: https://api.example.com/v1/
另一種做法是筐骇,將版本號放在HTTP頭信息中债鸡,但不如放入URL方便和直觀。GitHub Developer Guide采用這種做法,跨域時會引發(fā)多次請求

4. 路徑(Endpoint)

路徑又稱"終點"(endpoint)铛纬,表示API的具體網(wǎng)址厌均。
在RESTful架構中,每個網(wǎng)址代表一種資源(resource)告唆,所以網(wǎng)址中不能有動詞棺弊,只能有名詞,而且所用的名詞往往與數(shù)據(jù)庫的表格名對應擒悬。一般來說模她,數(shù)據(jù)庫中的表都是同種記錄的"集合"(collection),所以API中的名詞也應該使用復數(shù)懂牧。
舉例來說菜秦,有一個API提供動物園(zoo)的信息黍氮,還包括各種動物和雇員的信息靶剑,則它的路徑應該設計成下面這樣剩辟。

https://api.example.com/v1/zoos
https://api.example.com/v1/animals
https://api.example.com/v1/employees

5. HTTP動詞

對于資源的具體操作類型,由HTTP動詞表示躯保。
常用的HTTP動詞有下面五個(括號里是對應的SQL命令)

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

還有兩個不常用的HTTP動詞

HEAD:獲取資源的元數(shù)據(jù)
OPTIONS:獲取信息旋膳,關于資源的哪些屬性是客戶端可以改變的

下面是一些栗子:

GET /zoos:列出所有動物園
POST /zoos:新建一個動物園
GET /zoos/ID:獲取某個指定動物園的信息
PUT /zoos/ID:更新某個指定動物園的信息(提供該動物園的全部信息)
PATCH /zoos/ID:更新某個指定動物園的信息(提供該動物園的部分信息)
DELETE /zoos/ID:刪除某個動物園
GET /zoos/ID/animals:列出某個指定動物園的所有動物
DELETE /zoos/ID/animals/ID:刪除某個指定動物園的指定動物

6. 過濾信息(Filtering)

如果記錄數(shù)量很多,服務器不可能都將它們返回給用戶.API應該提供參數(shù),過濾返回結果
常見的參數(shù)形式如下:

?limit=10:指定返回記錄的數(shù)量
?offset=10:指定返回記錄的開始位置。
?page=2&per_page=100:指定第幾頁途事,以及每頁的記錄數(shù)验懊。
?sortby=name&order=asc:指定返回結果按照哪個屬性排序,以及排序順序尸变。
?animal_type_id=1:指定篩選條件

參數(shù)的設計允許存在冗余鲁森,即允許API路徑和URL參數(shù)偶爾有重復。比如振惰,GET /zoo/ID/animals 與 GET /animals?zoo_id=ID 的含義是相同的。

7. 狀態(tài)碼(Status Codes)

服務器向用戶返回的狀態(tài)碼和提示信息垄懂,常見的有以下一些(方括號中是該狀態(tài)碼對應的HTTP動詞)

200 OK - [GET]:服務器成功返回用戶請求的數(shù)據(jù)骑晶,該操作是冪等的(Idempotent)痛垛。
201 CREATED - [POST/PUT/PATCH]:用戶新建或修改數(shù)據(jù)成功。
202 Accepted - [*]:表示一個請求已經(jīng)進入后臺排隊(異步任務)
204 NO CONTENT - [DELETE]:用戶刪除數(shù)據(jù)成功桶蛔。
400 INVALID REQUEST - [POST/PUT/PATCH]:用戶發(fā)出的請求有錯誤匙头,服務器沒有進行新建或修改數(shù)據(jù)的操作,該操作是冪等的仔雷。
401 Unauthorized - [*]:表示用戶沒有權限(令牌蹂析、用戶名、密碼錯誤)碟婆。
403 Forbidden - [*] 表示用戶得到授權(與401錯誤相對)电抚,但是訪問是被禁止的。
404 NOT FOUND - [*]:用戶發(fā)出的請求針對的是不存在的記錄竖共,服務器沒有進行操作蝙叛,該操作是冪等的。
406 Not Acceptable - [GET]:用戶請求的格式不可得(比如用戶請求JSON格式公给,但是只有XML格式)借帘。
410 Gone -[GET]:用戶請求的資源被永久刪除,且不會再得到的淌铐。
422 Unprocesable entity - [POST/PUT/PATCH] 當創(chuàng)建一個對象時肺然,發(fā)生一個驗證錯誤。
500 INTERNAL SERVER ERROR - [*]:服務器發(fā)生錯誤腿准,用戶將無法判斷發(fā)出的請求是否成功

注意: 狀態(tài)碼的完全列表參見這里

8. 錯誤處理(Error handling)

如果狀態(tài)碼是4xx际起,就應該向用戶返回出錯信息。一般來說释涛,返回的信息中將error作為鍵名加叁,出錯信息作為鍵值即可。

{
    error: "Invalid API key"
}

9. 返回結果

針對不同操作唇撬,服務器向用戶返回的結果應該符合以下規(guī)范

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

10. Hypermedia API

RESTful API最好做到Hypermedia它匕,即返回結果中提供鏈接,連向其他API方法窖认,使得用戶不查文檔豫柬,也知道下一步應該做什么。
比如扑浸,當用戶向api.example.com的根目錄發(fā)出請求烧给,會得到這樣一個文檔:

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

上面代碼表示,文檔中有一個link屬性喝噪,用戶讀取這個屬性就知道下一步該調用什么API了础嫡。rel表示這個API與當前網(wǎng)址的關系(collection關系,并給出該collection的網(wǎng)址),href表示API的路徑榴鼎,title表示API的標題伯诬,type表示返回類型。
Hypermedia API的設計被稱為HATEOAS巫财。Github的API就是這種設計盗似,訪問api.github.com會得到一個所有可用API的網(wǎng)址列表。

{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  // ...
}

從上面可以看到平项,如果想獲取當前用戶的信息赫舒,應該去訪問\api.github.com/user,然后就得到了下面結果:

{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

上面代碼表示闽瓢,服務器給出了提示信息接癌,以及文檔的網(wǎng)址

11. 其他

  • API的身份認證應該使用OAuth 2.0框架。
  • 服務器返回的數(shù)據(jù)格式鸳粉,應該盡量使用JSON扔涧,避免使用XML。

RESTful API 設計指南 - 阮一峰的網(wǎng)絡日志
GitHub - aisuhua/restful-api-design-references: RESTful API 設計參考文獻列表

二. 基于Django的實現(xiàn)

2.1 路由系統(tǒng)

from app01 import views
urlpatterns = [
    url(r'^users', views.UserView.as_view())
]

2.2 CBV試圖

class UserView(View):
    def get(self, request, *args, **kwargs):
        result = {"status": True, "data": "This is a get request", "msg": None, "code": 2000}
        return JsonResponse(result, status=200)

    def post(self, request, *args, **kwargs):
        result = {"status": True, "data": "This is a post request", "msg": None, "code": 2000}
        return JsonResponse(result, status=200)

三. 基于Django Rest Framework 框架實現(xiàn)

3.1 基本流程

路由:URL.py

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

urlpatterns = [
    url(r'^index', views.IndexView.as_view())
]

試圖: views.py

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

class IndexView(APIView):
    """
    請求到來之后届谈,都要執(zhí)行dispatch方法枯夜,dispatch方法根據(jù)請求方式不同觸發(fā)反射 get/post/put 等方法
    注意:APIView中的dispatch方法有好多好多的功能
    """

    def dispatch(self, request, *args, **kwargs):
        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請求湖雹,響應內容')

?? : 以上是Django rest framework框架基本流程,重要的功能是在APIView的dispatch中觸發(fā),也是框架的源碼入口

3.2 認證和授權

3.2.1 基于token的驗證

urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py 
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^auth/$', views.AuthView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
]
$ cat models.py 
from django.db import models

class UserInfo(models.Model):
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)


class UserToken(models.Model):
    user = models.OneToOneField('UserInfo', on_delete=True)
    token = models.CharField(max_length=64)
$ cat views.py
from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01 import models
import uuid

class AuthView(APIView):
    authentication_classes = []    # 登錄認證接口覆蓋默認的toekn認證類

    def post(self, request, *args, **kwargs):
        response = {'code': 1000}
        user = request.data.get('username')
        pwd = request.data.get('password')

        obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
        if not obj:
            response['code'] = 1001
            response['msg'] = '用戶或密碼錯誤'
            return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

        try:
            token = str(uuid.uuid4())
            models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            response['token'] = token
        except Exception as e:
            print("Error: ", e)
        return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
    def get(self, request, *args, **kwargs):
        print(request.user)
        print(request.auth)
        return HttpResponse('user.get: %s' % request.user)

    def post(self, request, *args, **kwargs):
        return HttpResponse('user.post')
$ cat app01/utils/auth.py

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

class TokenAuthtication(BaseAuthentication):
    def authenticate(self, request):
        """
        :param request:
        :return:
        (user,auth) 表示認證成功,并將元組分別賦值給request.user/request.auth
        :raise AuthenticationFailed('認證失敗')  表示認證失敗
        """

        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed("用戶Token未攜帶")

        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise AuthenticationFailed("Token已失效或錯誤")
        return (token_obj.user.username, token_obj)
$ cat settings.py

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.TokenAuthtication', ],
}

驗證:

  1. 首先在數(shù)據(jù)庫中構造請求登錄的用戶名密碼
  2. 構造數(shù)據(jù)獲取token信息
$ curl -X POST \
>   http://127.0.0.1:8001/api/v1/auth/ \
>   -H 'Cache-Control: no-cache' \
>   -H 'Content-Type: application/json' \
>   -H 'Postman-Token: 8439ce0a-94ff-3970-c8c2-4c6bc200bb4f' \
>   -d '{"username": "eric","password":"123"}'
返回:
{"code": 1000, "token": "4c90f696-008b-4e30-86fa-4b00b6fc9237"}
  1. 攜帶token訪問user接口,獲取用戶信息
>   'http://127.0.0.1:8001/api/v1/user/?token=4c90f696-008b-4e30-86fa-4b00b6fc9237' \
>   -H 'Cache-Control: no-cache' \
>   -H 'Postman-Token: 1b4125ad-7ad1-1cca-70fd-6a00cca16d96'
返回:
user.get: eric

3.2.2 基于請求頭認證

$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py 
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^auth/$', views.AuthView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
]
$ cat app01/utils/auth.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/1


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

class HeaderAuthentication(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
        import uuid
        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(':')
        try:
            obj = models.UserInfo.objects.filter(username=username, password=password).first()
            if not obj:
                raise exceptions.AuthenticationFailed('用戶名或密碼錯誤')
            token = str(uuid.uuid4())
            token_obj, status = models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            return (token_obj.user.username, token_obj)
        except Exception as e:
            print("Error: ", e)

    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'
$ cat views.py
from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01.utils.auth import HeaderAuthentication
from app01 import models
import uuid


class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
        response = {'code': 1000}
        user = request.data.get('username')
        pwd = request.data.get('password')

        obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
        if not obj:
            response['code'] = 1001
            response['msg'] = '用戶或密碼錯誤'
            return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

        try:
            token = str(uuid.uuid4())
            models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            response['token'] = token
        except Exception as e:
            print("Error: ", e)
        return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
    authentication_classes = [HeaderAuthentication, ]

    def get(self, request, *args, **kwargs):
        print(request.user)
        print(request.auth)
        return HttpResponse('user.get: %s,token: %s' % (request.user, request.auth))

    def post(self, request, *args, **kwargs):
        return HttpResponse('user.post')

驗證:

  1. 首先在數(shù)據(jù)庫中構造請求登錄的用戶名密碼
  2. 構造數(shù)據(jù)獲取token信息,建議使用postman進行構造測試
$ curl -X GET \
  http://127.0.0.1:8001/api/v1/user/ \
  -H 'Authorization: Basic c2h1a2U6MTIz' \
  -H 'Cache-Control: no-cache' \
  -H 'Postman-Token: ba3f99ce-da6a-7e5f-3e0b-3c79129ecca1'
返回:
user.get: shuke,token: 2d3af34a-1598-4d5a-af56-7f29a706a26e

3.2.3 多個認證規(guī)則

$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py 
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^auth/$', views.AuthView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
]
$ cat app01/utils/auth.py
#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/1


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


class TokenAuthtication(BaseAuthentication):
    def authenticate(self, request):
        """

        :param request:
        :return:
        (user,auth) 表示認證成功,并將元組分別賦值給request.user/request.auth
        :raise AuthenticationFailed('認證失敗')  表示認證失敗
        """

        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed("用戶Token未攜帶")

        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise AuthenticationFailed("Token已失效或錯誤")
        return (token_obj.user.username, token_obj)


class HeaderAuthentication(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
        import uuid
        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(':')
        try:
            obj = models.UserInfo.objects.filter(username=username, password=password).first()
            if not obj:
                raise exceptions.AuthenticationFailed('用戶名或密碼錯誤')
            token = str(uuid.uuid4())
            token_obj, status = models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            return (token_obj.user.username, token_obj)
        except Exception as e:
            print("Error: ", e)

    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'
$ cat views.py
from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01.utils.auth import HeaderAuthentication, TokenAuthtication
from app01 import models
import uuid


class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
        response = {'code': 1000}
        user = request.data.get('username')
        pwd = request.data.get('password')

        obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
        if not obj:
            response['code'] = 1001
            response['msg'] = '用戶或密碼錯誤'
            return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

        try:
            token = str(uuid.uuid4())
            models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            response['token'] = token
        except Exception as e:
            print("Error: ", e)
        return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
    authentication_classes = [HeaderAuthentication, TokenAuthtication]

    def get(self, request, *args, **kwargs):
        print(request.user)
        print(request.auth)
        return HttpResponse('user.get: %s,token: %s' % (request.user, request.auth))

    def post(self, request, *args, **kwargs):
        return HttpResponse('user.post')

驗證:

  1. 首先在數(shù)據(jù)庫中構造請求登錄的用戶名密碼
  2. 構造數(shù)據(jù)獲取token信息,建議使用postman進行構造測試
$ curl -X GET \
  http://127.0.0.1:8001/api/v1/user/ \
  -H 'Authorization: Basic c2h1a2U6MTIz' \
  -H 'Cache-Control: no-cache' \
  -H 'Postman-Token: f91805b7-c742-b713-0010-c75cdbfbb24a'
返回:
user.get: shuke,token: 9c3ca96a-ac03-4207-aaec-20433bae6058

3.2.4 認證和權限

路由

$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py 
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^auth/$', views.AuthView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
]

model

from django.db import models

# Create your models here.
from django.db import models


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用戶'),
        (2, '管理員'),
        (3, '超級管理員'),
    )
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=user_type_choices, default=1)


class UserToken(models.Model):
    user = models.OneToOneField('UserInfo', on_delete=True)
    token = models.CharField(max_length=64)

認證

$ cat app01/utils/auth.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/1


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


class TokenAuthtication(BaseAuthentication):
    def authenticate(self, request):
        """

        :param request:
        :return:
        (user,auth) 表示認證成功,并將元組分別賦值給request.user/request.auth
        :raise AuthenticationFailed('認證失敗')  表示認證失敗
        """

        token = request.query_params.get('token')
        if not token:
            raise AuthenticationFailed("用戶Token未攜帶")

        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise AuthenticationFailed("Token已失效或錯誤")
        return (token_obj.user.username, token_obj)


class HeaderAuthentication(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
        import uuid
        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(':')
        try:
            obj = models.UserInfo.objects.filter(username=username, password=password).first()
            if not obj:
                raise exceptions.AuthenticationFailed('用戶名或密碼錯誤')
            token = str(uuid.uuid4())
            token_obj, status = models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            return (token_obj.user.username, token_obj)
        except Exception as e:
            print("Error: ", e)
            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'

權限

$ cat app01/utils/permission.py

#! /usr/bin/env python
# -*- coding: utf-8 -*-
# __author__ = "shuke"
# Date: 2018/6/3
from rest_framework.permissions import BasePermission


class UserPermission(BasePermission):
    """
    權限驗證
    """

    def has_permission(self, request, view):
        """
        判斷是否有權限訪問當前請求
        Return `True` if permission is granted, `False` otherwise.
        :param request:
        :param view:
        :return: True有權限;False無權限
        """
        user_type_id = request.auth.user.user_type
        if user_type_id > 0:
            return True
        return False

    # GenericAPIView中get_object時調用
    def has_object_permission(self, request, view, obj):
        """
        視圖繼承GenericAPIView瓶籽,并在其中使用get_object時獲取對象時匠童,觸發(fā)單獨對象權限驗證
        Return `True` if permission is granted, `False` otherwise.
        :param request:
        :param view:
        :param obj:
        :return: True有權限;False無權限
        """
        user_type_id = request.auth.user.user_type
        if user_type_id > 0:
            return True
        return False


class ManagerPermission(BasePermission):
    """
    視圖繼承GenericAPIView塑顺,并在其中使用get_object時獲取對象時汤求,觸發(fā)單獨對象權限驗證
    Return `True` if permission is granted, `False` otherwise.
    :param request:
    :param view:
    :param obj:
    :return: True有權限;False無權限
    """

    def has_permission(self, request, view):
        user_type_id = request.auth.user.user_type
        if user_type_id > 1:
            return True
        return False

    # GenericAPIView中get_object時調用
    def has_object_permission(self, request, view, obj):
        """
        視圖繼承GenericAPIView,并在其中使用get_object時獲取對象時首昔,觸發(fā)單獨對象權限驗證
        Return `True` if permission is granted, `False` otherwise.
        :param request:
        :param view:
        :param obj:
        :return: True有權限寡喝;False無權限
        """
        user_type_id = request.auth.user.user_type
        if user_type_id > 1:
            return True
        return False

試圖函數(shù)

from django.shortcuts import render, HttpResponse

# Create your views here.
from django.http import JsonResponse
from rest_framework.views import APIView
from app01.utils.auth import HeaderAuthentication, TokenAuthtication
from app01.utils.permission import UserPermission, ManagerPermission
from app01 import models
import uuid


class AuthView(APIView):
    authentication_classes = []

    def post(self, request, *args, **kwargs):
        response = {'code': 1000}
        user = request.data.get('username')
        pwd = request.data.get('password')

        obj = models.UserInfo.objects.filter(username=user, password=pwd).first()
        if not obj:
            response['code'] = 1001
            response['msg'] = '用戶或密碼錯誤'
            return JsonResponse(response, json_dumps_params={'ensure_ascii': False})

        try:
            token = str(uuid.uuid4())
            models.UserToken.objects.update_or_create(user=obj, defaults={"token": token})
            response['token'] = token
        except Exception as e:
            print("Error: ", e)
        return JsonResponse(response, json_dumps_params={'ensure_ascii': False})


class UserView(APIView):
    # 認證的動作是由request.user觸發(fā)
    authentication_classes = [HeaderAuthentication, TokenAuthtication]

    # 循環(huán)執(zhí)行所有的權限,當前試圖只允許管理員以上權限訪問
    permission_classes = [ManagerPermission, ]

    def get(self, request, *args, **kwargs):
        print(request.user)
        print(request.auth)
        return HttpResponse('user.get: %s,token: %s' % (request.user, request.auth))

    def post(self, request, *args, **kwargs):
        return HttpResponse('user.post')

驗證:
[站外圖片上傳中...(image-8f9f77-1528210315969)]
[站外圖片上傳中...(image-7d1e94-1528210315971)]

全局應用權限
上述操作中均是對單獨視圖進行特殊配置,如果想要對全局進行配置勒奇,則需要再配置文件中寫入即可。

REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': None,
    'UNAUTHENTICATED_TOKEN': None,
    'DEFAULT_AUTHENTICATION_CLASSES': ['app01.utils.auth.HeaderAuthentication', ],
    "DEFAULT_PERMISSION_CLASSES": [
        "app01.utils.permission.ManagerPermission",
    ],
}

路由

$ cat urls.py

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include

urlpatterns = [
    url(r'^admin/', admin.site.urls),
    url(r'^api/(?P<version>\w+)/', include('app01.urls')),
]

$ cat app01/urls.py 
from django.conf.urls import url
from app01 import views

urlpatterns = [
    url(r'^auth/$', views.AuthView.as_view()),
    url(r'^user/$', views.UserView.as_view()),
]

試圖

$ cat views.py

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


class UserView(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.3 用戶訪問次數(shù)/頻率限制

3.3.1 基于用戶IP訪問限制頻率

路由

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

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

試圖

#!/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):
        """
        根據(jù)用戶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):
        """
        多少秒后可以允許繼續(xù)訪問
        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):
        """
        訪問次數(shù)被限制時纲菌,定制錯誤信息
        """

        class Throttled(exceptions.Throttled):
            default_detail = '請求被限制.'
            extra_detail_singular = '請 {wait} 秒之后再重試.'
            extra_detail_plural = '請 {wait} 秒之后再重試.'
            
        raise Throttled(wait)

3.3.2 基于用戶IP控制訪問頻率(利用Django緩存)

全局配置

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'test_scope': '10/m',
    },
}

路由

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

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

試圖

#!/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):
        """
        訪問次數(shù)被限制時,定制錯誤信息
        """

        class Throttled(exceptions.Throttled):
            default_detail = '請求被限制.'
            extra_detail_singular = '請 {wait} 秒之后再重試.'
            extra_detail_plural = '請 {wait} 秒之后再重試.'

        raise Throttled(wait)

3.3.3 在試圖中限制請求頻率

全局配置

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_RATES': {
        'request_scope': '10/m',
    },
}

路由

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

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

試圖

#!/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):
        """
        訪問次數(shù)被限制時只冻,定制錯誤信息
        """

        class Throttled(exceptions.Throttled):
            default_detail = '請求被限制.'
            extra_detail_singular = '請 {wait} 秒之后再重試.'
            extra_detail_plural = '請 {wait} 秒之后再重試.'

        raise Throttled(wait)

3.3.4 匿名時用IP限制+登錄時用Token限制

全局配置

REST_FRAMEWORK = {
    'UNAUTHENTICATED_USER': None,
    'UNAUTHENTICATED_TOKEN': None,
    'DEFAULT_THROTTLE_RATES': {
        'backend_anon': '10/m',
        'backend_user': '20/m',
    },
}

路由

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

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

試圖

# !/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 BackendAnonRateThrottle(SimpleRateThrottle):
    """
    匿名用戶庇麦,根據(jù)IP進行限制
    """
    scope = "backend_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 BackendUserRateThrottle(SimpleRateThrottle):
    """
    登錄用戶喜德,根據(jù)用戶token限制
    """
    scope = "backend_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 = [BackendAnonRateThrottle, BackendUserRateThrottle, ]

    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.3.5 全局使用

REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'api.utils.throttles.throttles.BackendAnonRateThrottle',
        'api.utils.throttles.throttles.BackendUserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '10/day',
        'user': '10/day',
        'backend_anon': '10/m',
        'backend_user': '20/m',
    },
}

3.4 版本

共6個類

  1. BaseVersioning
  2. AcceptHeaderVersioning
  3. URLPathVersioning
  4. NamespaceVersioning
  5. HostNameVersioning
  6. QueryParameterVersioning
    而且還可以看到BaseVersioning類是其余5個類的父類,并且這其余的5個類,每個類中都有一個determine_version方法,在項目的視圖函數(shù)中導入其中任意一個類,打印versioning_class
from django.shortcuts import render,HttpResponse
from rest_framework.views import APIView
from django.views import View
from rest_framework.versioning import QueryParameterVersioning

class UsersView(APIView):
    versioning_class=QueryParameterVersioning

    def get(self,request,*args,**kwargs):
        print(self.versioning_class)        #打印versioning_class

        return HttpResponse("aaaa")

輸出結果:

<class 'rest_framework.versioning.QueryParameterVersioning'>

所以versioning_class是一個類,并且versioning_class類中有一個determine_version方法

3.4.1 基于URL的GET傳參方式

如:/users?version=v1
全局配置

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

路由

from django.contrib import admin
from django.urls import path
from django.conf.urls import url, include
from app01.views import TestView

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

試圖

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請求,響應內容')

3.4.2 基于URL的正則方式

如: /v1/users/

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

路由

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'),
]

試圖

#!/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請求,響應內容')

3.4.3 基于accept請求頭方式

如:Accept: application/json; version=1.0

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

路由

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

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

試圖

# !/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請求,響應內容,基于Accept請求頭方式')

    def post(self, request, *args, **kwargs):
        return Response('POST請求呵晚,響應內容蜘腌,基于Accept請求頭方式')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求,響應內容饵隙,基于Accept請求頭方式')

3.4.4 基于主機名方式

如: v1.example.com

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

路由

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

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

試圖

# !/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請求金矛,響應內容,基于主機名方式')

3.4.5 基于Django路由系統(tǒng)的namespace

如: example.com/v1/users/

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

路由

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

urlpatterns = [
    url(r'^v1/', ([
                      url(r'test/', TestView.as_view(), name='test1')
                  ], None, 'v1')),
    url(r'^v2/', ([
                      url(r'test/', TestView.as_view(), name='test2')
                  ], None, 'v2')),

]

試圖

# !/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):
    authentication_classes = []
    permission_classes = []
    versioning_class = NamespaceVersioning

    def get(self, request, *args, **kwargs):
        # 獲取版本
        print(request.version)
        # 獲取版本管理的類
        print(request.versioning_scheme)
        # 反向生成URL
        reverse_url = request.versioning_scheme.reverse('test1', request=request)
        print(reverse_url)

        return Response('GET請求芯急,響應內容,基于Django路由系統(tǒng)的namespace')

    def post(self, request, *args, **kwargs):
        return Response('POST請求,響應內容,基于Django路由系統(tǒng)的namespace')

    def put(self, request, *args, **kwargs):
        return Response('PUT請求驶俊,響應內容,基于Django路由系統(tǒng)的namespace')

3.4.6 全局應用

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

3.4.7 自定義版本控制方案

要實現(xiàn)自定義版本控制方案娶耍,請繼承 BaseVersioning并覆蓋 .determine_version 方法。
舉個栗子
以下示例使用自定義的 X-API-Version header 來確定所請求的版本

class XAPIVersionScheme(versioning.BaseVersioning):
    def determine_version(self, request, *args, **kwargs):
        return request.META.get('HTTP_X_API_VERSION', None)

如果你的版本控制方案基于請求 URL饼酿,則還需要更改版本化 URL 的確定方式榕酒。為了做到這一點,你應該重寫類的 .reverse()方法故俐。有關示例想鹰,請參閱源代碼。

3.5 解析器(parser)

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

3.5.1 僅處理請求頭content-type為application/json的請求體

路由

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

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

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
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請求,請求內容: %s' % request.data)

    def put(self, request, *args, **kwargs):
        return Response('PUT請求刚陡,響應內容惩妇,解析器')

3.5.2 僅處理請求頭content-type為application/x-www-form-urlencoded的請求體

路由

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

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

試圖

#!/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請求,響應內容')

3.5.3 僅處理請求頭content-type為multipart/form-data的請求體

路由

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

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

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser


class TestView(APIView):
    authentication_classes = []
    permission_classes = []
    parser_classes = [MultiPartParser, ]

    def get(self, request, *args, **kwargs):
        return render(request, 'test.html')

    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請求勃刨,響應內容')

模版文件

<!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>

3.5.4 僅上傳文件

路由

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

urlpatterns = [
    url(r'test/(?P<filename>[^/]+)', TestView.as_view(), name='test'),
]

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.parsers import FileUploadParser


class TestView(APIView):
    authentication_classes = []
    permission_classes = []
    parser_classes = [FileUploadParser, ]

    def get(self, request, *args, **kwargs):
        return render(request, 'test.html')

    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請求廷区,響應內容')

模版文件

<!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>

3.5.5 同時多個Parser

當同時使用多個parser時,rest framework會根據(jù)請求頭content-type自動進行比對贾铝,并使用對應parser
路由

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

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

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework.response import Response
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請求玖绿,響應內容')

3.5.6 全局應用

REST_FRAMEWORK = {
    'DEFAULT_PARSER_CLASSES':[
        'rest_framework.parsers.JSONParser'
        'rest_framework.parsers.FormParser'
        'rest_framework.parsers.MultiPartParser'
    ]

}

路由

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

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

試圖

#!/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 來進行獲取

3.6 序列化

序列化用于對用戶請求數(shù)據(jù)進行驗證和數(shù)據(jù)進行序列化

3.6.1 自定義字段

路由

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

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

models

from django.db import models

# Create your models here.
from django.db import models


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用戶'),
        (2, '管理員'),
        (3, '超級管理員'),
    )
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=user_type_choices, default=1)


class UserToken(models.Model):
    user = models.OneToOneField('UserInfo', on_delete=True)
    token = models.CharField(max_length=64)

試圖

# !/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.
        """
        # 執(zhí)行驗證之前調用,serializer_fields是當前字段對象
        pass


class UserSerializer(serializers.Serializer):
    user_type = serializers.IntegerField()
    username = serializers.CharField(min_length=3)
    password = serializers.CharField(error_messages={'required': '密碼不能為空'}, validators=[PasswordValidator('666')])


class TestView(APIView):
    authentication_classes = []
    permission_classes = []

    def get(self, request, *args, **kwargs):

        # 序列化,將數(shù)據(jù)庫查詢字段序列化為字典
        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):
        # 驗證秤标,對請求發(fā)來的數(shù)據(jù)進行驗證
        ser = UserSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data)
        else:
            print(ser.errors)

        return Response('POST請求绝淡,響應內容')

POST驗證:

curl -X POST \
  http://127.0.0.1:8001/test/ \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -H 'Postman-Token: 4ea5d1d1-e3b1-38a3-19f6-25070ace2342' \
  -d '{
        "user_type": 2,
        "username": "python",
        "password": "666"
    }'

3.6.2 基于Model自動生成字段

路由

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

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

試圖

#!/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.
        """
        # 執(zhí)行驗證之前調用,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):

        # 序列化,將數(shù)據(jù)庫查詢字段序列化為字典
        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):
        # 驗證苍姜,對請求發(fā)來的數(shù)據(jù)進行驗證
        print(request.data)
        ser = ModelUserSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data)
        else:
            print(ser.errors)

        return Response('POST請求,響應內容')

3.6.3 生成URL

路由

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'),
]

models

from django.db import models

# Create your models here.
from django.db import models


class UserInfo(models.Model):
    user_type_choices = (
        (1, '普通用戶'),
        (2, '管理員'),
        (3, '超級管理員'),
    )
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)
    user_type = models.IntegerField(choices=user_type_choices, default=1)


class UserToken(models.Model):
    user = models.OneToOneField('UserInfo', on_delete=True)
    token = models.CharField(max_length=64)

試圖

# !/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.
        """
        # 執(zhí)行驗證之前調用,serializer_fields是當前字段對象
        pass


class ModelUserSerializer(serializers.ModelSerializer):
    user_type = serializers.HyperlinkedIdentityField(view_name='detail')

    class Meta:
        model = models.UserInfo
        fields = "__all__"

        extra_kwargs = {
            'username': {'min_length': 6},
            'pasword': {'validators': [PasswordValidator(666), ]},
        }


class TestView(APIView):
    authentication_classes = []
    permission_classes = []

    def get(self, request, *args, **kwargs):

        # 序列化悬包,將數(shù)據(jù)庫查詢字段序列化為字典
        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):
        # 驗證衙猪,對請求發(fā)來的數(shù)據(jù)進行驗證
        print(request.data)
        ser = ModelUserSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data)
        else:
            print(ser.errors)

        return Response('POST請求,響應內容')

請求如下圖所示:
[站外圖片上傳中...(image-5aaf7-1528210315972)]

3.6.4 自動生成URL

路由

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

urlpatterns = [
    url(r'test/', TestView.as_view(), name='test'),
    url(r'detail/(?P<pk>\d+)/', TestView.as_view(), name='detail-info'),
]

試圖

# !/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.
        """
        # 執(zhí)行驗證之前調用,serializer_fields是當前字段對象
        pass


class ModelUserSerializer(serializers.HyperlinkedModelSerializer):
    ll = serializers.HyperlinkedIdentityField(view_name='detail-info')
    tt = serializers.CharField(required=False)

    class Meta:
        model = models.UserInfo
        fields = "__all__"
        list_serializer_class = serializers.ListSerializer

        extra_kwargs = {
            'username': {'min_length': 6},
            'password': {'validators': [PasswordValidator(666), ]},
            'url': {'view_name': 'detail-info'},
            'ut': {'view_name': 'detail-info'},
        }


class TestView(APIView):
    def get(self, request, *args, **kwargs):
        # # 序列化布近,將數(shù)據(jù)庫查詢字段序列化為字典
        data_list = models.UserInfo.objects.all()
        ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request})
        # # 如果Many=True
        # # 或
        # # obj = models.UserInfo.objects.all().first()
        # # ser = UserSerializer(instance=obj, many=False)
        return Response(ser.data)

    def post(self, request, *args, **kwargs):
        # 驗證垫释,對請求發(fā)來的數(shù)據(jù)進行驗證
        print(request.data)
        ser = ModelUserSerializer(data=request.data)
        if ser.is_valid():
            print(ser.validated_data)
        else:
            print(ser.errors)

        return Response('POST請求,響應內容')

3.7 分頁

3.7.1 根據(jù)頁碼進行分頁

路由

from django.conf.urls import url, include
from app01.views import UserViewSet

urlpatterns = [
    url(r'test/', UserViewSet.as_view(), name='test'),
]

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework import serializers
from . import models

from rest_framework.pagination import PageNumberPagination


class StandardResultsSetPagination(PageNumberPagination):
    # 默認每頁顯示的數(shù)據(jù)條數(shù)
    page_size = 1
    # 獲取URL參數(shù)中設置的每頁顯示數(shù)據(jù)條數(shù)
    page_size_query_param = 'page_size'

    # 獲取URL參數(shù)中傳入的頁碼key
    page_query_param = 'page'

    # 最大支持的每頁顯示的數(shù)據(jù)條數(shù)
    max_page_size = 1


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(APIView):
    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all().order_by('-id')

        # 實例化分頁對象撑瞧,獲取數(shù)據(jù)庫中的分頁數(shù)據(jù)
        paginator = StandardResultsSetPagination()
        page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)

        # 序列化對象
        serializer = UserSerializer(page_user_list, many=True)

        # 生成分頁和數(shù)據(jù)
        response = paginator.get_paginated_response(serializer.data)
        return response

訪問驗證:

http://127.0.0.1:8001/test/?page=1
http://127.0.0.1:8001/test/?page=2

3.7.2 位置和個數(shù)進行分頁

路由

from django.conf.urls import url, include
from app01.views import UserViewSet

urlpatterns = [
    url(r'test/', UserViewSet.as_view(), name='test'),
]

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework import serializers
from . import models

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination


class StandardResultsSetPagination(LimitOffsetPagination):
    # 默認每頁顯示的數(shù)據(jù)條數(shù)
    default_limit = 2
    # URL中傳入的顯示數(shù)據(jù)條數(shù)的參數(shù)
    limit_query_param = 'limit'
    # URL中傳入的數(shù)據(jù)位置的參數(shù)
    offset_query_param = 'offset'
    # 最大每頁顯得條數(shù)
    max_limit = None


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(APIView):
    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all().order_by('-id')

        # 實例化分頁對象棵譬,獲取數(shù)據(jù)庫中的分頁數(shù)據(jù)
        paginator = StandardResultsSetPagination()
        page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)

        # 序列化對象
        serializer = UserSerializer(page_user_list, many=True)

        # 生成分頁和數(shù)據(jù)
        response = paginator.get_paginated_response(serializer.data)
        return response

驗證:


3.7.3 游標分頁

路由

from django.conf.urls import url, include
from app01.views import UserViewSet

urlpatterns = [
    url(r'test/', UserViewSet.as_view(), name='test'),
]

試圖

# !/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.views import APIView
from rest_framework import serializers
from . import models

from rest_framework.pagination import PageNumberPagination, LimitOffsetPagination, CursorPagination


class StandardResultsSetPagination(CursorPagination):
    # URL傳入的游標參數(shù)
    cursor_query_param = 'cursor'
    # 默認每頁顯示的數(shù)據(jù)條數(shù)
    page_size = 10
    # URL傳入的每頁顯示條數(shù)的參數(shù)
    page_size_query_param = 'page_size'
    # 每頁顯示數(shù)據(jù)最大條數(shù)
    max_page_size = 1000

    # 根據(jù)ID從大到小排列
    ordering = "id"


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(APIView):
    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all().order_by('-id')

        # 實例化分頁對象,獲取數(shù)據(jù)庫中的分頁數(shù)據(jù)
        paginator = StandardResultsSetPagination()
        page_user_list = paginator.paginate_queryset(user_list, self.request, view=self)

        # 序列化對象
        serializer = UserSerializer(page_user_list, many=True)

        # 生成分頁和數(shù)據(jù)
        response = paginator.get_paginated_response(serializer.data)
        return response

3.8 路由系統(tǒng)

3.8.1 自定義路由

路由

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

urlpatterns = [
    url(r'^test/$', s11_render.TestView.as_view()),
    url(r'^test\.(?P<format>[a-z0-9]+)$', s11_render.TestView.as_view()),
    url(r'^test/(?P<pk>[^/.]+)/$', s11_render.TestView.as_view()),
    url(r'^test/(?P<pk>[^/.]+)\.(?P<format>[a-z0-9]+)$', s11_render.TestView.as_view())
]

試圖

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


class TestView(APIView):
    def get(self, request, *args, **kwargs):
        print(kwargs)
        print(self.renderer_classes)
        return Response('...')

3.8.2 半自動路由

路由

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

urlpatterns = [
    url(r'^test/$', s10_generic.UserViewSet.as_view({'get': 'list', 'post': 'create'})),
    url(r'^test/(?P<pk>\d+)/$', s10_generic.UserViewSet.as_view(
        {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]

試圖

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(ModelViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserSerializer

3.8.3 全自動路由

路由

from django.conf.urls import url, include
from rest_framework import routers
from web.views import s10_generic


router = routers.DefaultRouter()
router.register(r'users', s10_generic.UserViewSet)

urlpatterns = [
    url(r'^', include(router.urls)),
]

試圖

from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(ModelViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserSerializer

3.9 試圖

3.9.1 GenericViewSet

路由

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

urlpatterns = [
    url(r'test/', TestView.as_view({'get':'list'}), name='test'),
    url(r'detail/(?P<pk>\d+)/', TestView.as_view({'get':'list'}), name='xxxx'),
]

試圖

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework import viewsets
from rest_framework.response import Response


class TestView(viewsets.GenericViewSet):
    def list(self, request, *args, **kwargs):
        return Response('...')

    def add(self, request, *args, **kwargs):
        pass

    def delete(self, request, *args, **kwargs):
        pass

    def edit(self, request, *args, **kwargs):
        pass

3.9.2 ModelViewSet(自定義URL)

路由

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

urlpatterns = [
    url(r'^test/$', s10_generic.UserViewSet.as_view({'get': 'list', 'post': 'create'})),
    url(r'^test/(?P<pk>\d+)/$', s10_generic.UserViewSet.as_view(
        {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'})),
]

試圖

#!/usr/bin/env python
# -*- coding:utf-8 -*-
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializers
from .. import models


class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class UserViewSet(ModelViewSet):
    queryset = models.UserInfo.objects.all()
    serializer_class = UserSerializer

3.9.3 ModelViewSet(rest framework路由)

路由

from django.conf.urls import url, include
from rest_framework import routers
from app01 import views

router = routers.DefaultRouter()
router.register(r'users', views.UserViewSet)
router.register(r'groups', views.GroupViewSet)

# Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API.
urlpatterns = [
    url(r'^', include(router.urls)),
]

試圖

from rest_framework import viewsets
from rest_framework import serializers


class UserSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.User
        fields = ('url', 'username', 'email', 'groups')


class GroupSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = models.Group
        fields = ('url', 'name')
        
class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer


class GroupViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows groups to be viewed or edited.
    """
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

3.10 渲染器

根據(jù)用戶請求URL或用戶可接受的類型,篩選出合適的渲染組件
用戶請求URL:

3.10.1 Json

訪問URL:

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

urlpatterns = [
    url(r'^test/$', s11_render.TestView.as_view()),
    url(r'^test\.(?P<format>[a-z0-9]+)', s11_render.TestView.as_view()),
]

試圖:

#!/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 rest_framework.renderers import JSONRenderer

from .. import models


class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class TestView(APIView):
    renderer_classes = [JSONRenderer, ]

    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all()
        ser = TestSerializer(instance=user_list, many=True)
        return Response(ser.data)

3.10.2 表格

訪問URL:

#!/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 rest_framework.renderers import AdminRenderer
from . import models

class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class TestView(APIView):
    renderer_classes = [AdminRenderer, ]

    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all()
        ser = TestSerializer(instance=user_list, many=True)
        return Response(ser.data)

3.10.3 Form表單

訪問URL:

#!/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 rest_framework.renderers import JSONRenderer
from rest_framework.renderers import AdminRenderer
from rest_framework.renderers import HTMLFormRendere
from . import models

class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"

class TestView(APIView):
    renderer_classes = [HTMLFormRenderer, ]

    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all().first()
        ser = TestSerializer(instance=user_list, many=False)
        return Response(ser.data)

3.10.4 自定義顯示模版

訪問URL:

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

urlpatterns = [
    url(r'^test/$', s11_render.TestView.as_view()),
    url(r'^test\.(?P<format>[a-z0-9]+)', s11_render.TestView.as_view()),
]

試圖

#!/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 rest_framework.renderers import TemplateHTMLRenderer
from . import models

class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"


class TestView(APIView):
    renderer_classes = [TemplateHTMLRenderer, ]

    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all().first()
        ser = TestSerializer(instance=user_list, many=False)
        return Response(ser.data, template_name='user_detail.html')

模版

$ cat user_detail.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    {{ user }}
    {{ pwd }}
    {{ ut }}
</body>
</html>

3.10.5 瀏覽器API+JSON

訪問URL:

#!/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 rest_framework.renderers import JSONRenderer
from rest_framework.renderers import BrowsableAPIRenderer
from . import models

class TestSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.UserInfo
        fields = "__all__"

class CustomBrowsableAPIRenderer(BrowsableAPIRenderer):
    def get_default_renderer(self, view):
        return JSONRenderer()

class TestView(APIView):
    renderer_classes = [CustomBrowsableAPIRenderer, ]

    def get(self, request, *args, **kwargs):
        user_list = models.UserInfo.objects.all().first()
        ser = TestSerializer(instance=user_list, many=False)
        return Response(ser.data, template_name='user_detail.html')

注意: 如果同時多個存在時,自動根據(jù)URL后綴來選擇渲染器

原文地址

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末预伺,一起剝皮案震驚了整個濱河市订咸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌酬诀,老刑警劉巖脏嚷,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瞒御,居然都是意外死亡父叙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門肴裙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趾唱,“玉大人,你說我怎么就攤上這事蜻懦√瘃” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵阻肩,是天一觀的道長带欢。 經(jīng)常有香客問我运授,道長,這世上最難降的妖魔是什么乔煞? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任吁朦,我火速辦了婚禮,結果婚禮上渡贾,老公的妹妹穿的比我還像新娘逗宜。我一直安慰自己,他們只是感情好空骚,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布纺讲。 她就那樣靜靜地躺著,像睡著了一般囤屹。 火紅的嫁衣襯著肌膚如雪熬甚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天肋坚,我揣著相機與錄音乡括,去河邊找鬼。 笑死智厌,一個胖子當著我的面吹牛诲泌,可吹牛的內容都是我干的。 我是一名探鬼主播铣鹏,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼敷扫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了诚卸?” 一聲冷哼從身側響起葵第,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎惨险,沒想到半個月后羹幸,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡辫愉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年栅受,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恭朗。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡屏镊,死狀恐怖,靈堂內的尸體忽然破棺而出痰腮,到底是詐尸還是另有隱情而芥,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布膀值,位于F島的核電站棍丐,受9級特大地震影響误辑,放射性物質發(fā)生泄漏。R本人自食惡果不足惜歌逢,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一巾钉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秘案,春花似錦砰苍、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赤惊,卻和暖如春吼旧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背未舟。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工黍少, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人处面。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像菩掏,于是被迫代替她去往敵國和親魂角。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理智绸,服務發(fā)現(xiàn)野揪,斷路器,智...
    卡卡羅2017閱讀 134,600評論 18 139
  • Spring Web MVC Spring Web MVC 是包含在 Spring 框架中的 Web 框架瞧栗,建立于...
    Hsinwong閱讀 22,313評論 1 92
  • 陽光透過窗戶來到我面前斯稳,于是隨它走進推開窗戶,一陣陣春風輕撫著迹恐,它穿過了我作瞄,巡視了我的房間作谭,它是這般的柔軟舒適,我...
    可人兒_1071閱讀 109評論 0 0
  • 我站在路的這頭 透過淅瀝小雨 望向路的那頭 像是見到了你 或清晰 或模糊 我想 畢竟路還是那么長 可能那并不是你
    阿紅莓閱讀 228評論 0 0
  • 昨,一陣風起擦秽,人行道兩邊的梧桐樹葉忽啦啦落了一地切黔。騎車路過的時候,被驚艷到了是偷。趕緊停了車拳氢,然后一個人在這條路上來回...
    曉曉的窩閱讀 497評論 3 3