天天看點

django rest framework學習

@

目錄

  • 1. 引入DjangoRESTframework
    • 1.1 Web應用模式
      • 前後端不分離
      • 前後端分離
    • 1.2 認識RESTful
    • 1.3 RESTful設計方法
      • 1. 域名
      • 2. 版本(Versioning)
      • 3. 路徑(Endpoint)
      • 4. HTTP動詞
      • 5. 過濾資訊(Filtering)
      • 6. 狀态碼(Status Codes)
      • 7. 錯誤處理(Error handling)
      • 8. 傳回結果
      • 10. 其他
    • 1.4 使用Django開發REST 接口
    • 1.5 明确REST接口開發的核心任務
      • 序列化Serialization
    • 1.6 Django REST framework 簡介
      • 認識Django REST framework
  • 2. DRF工程搭建
    • 環境安裝與配置
    • 見識DRF的魅力
      • 1. 建立序列化器
      • 2. 編寫視圖
      • 3. 定義路由
      • 4. 運作測試
  • 3. Serializer序列化器
    • 序列化器的作用:
    • 3.1 定義Serializer
      • 1. 定義方法
      • 2. 字段與選項
      • 3. 建立Serializer對象
    • 3.2 序列化使用
      • 1 基本使用
      • 2 關聯對象嵌套序列化
        • 1 PrimaryKeyRelatedField
        • 2 StringRelatedField
        • 3 使用關聯對象的序列化器
      • 3 many參數
    • 3.3 反序列化使用
      • 1. 驗證
        • 1)validate_
        • 2)validate
        • 3)validators
        • 2. 儲存
          • 兩點說明:
    • 3.4 模型類序列化器ModelSerializer
      • 1. 定義
      • 2. 指定字段
      • 3. 添加額外參數
  • 4. 視圖
    • 4.1 Request 與 Response
      • 1. Request
        • 常用屬性
          • 1).data
          • 2).query_params
      • 2. Response
        • 構造方式
        • 常用屬性:
      • 3. 狀态碼
    • 4.2 視圖概覽
    • 4.3 視圖說明
      • 1. 兩個基類
        • 1)APIView
        • 2)GenericAPIView
      • 2. 五個擴充類
        • 1)ListModelMixin
        • 2)CreateModelMixin
        • 3) RetrieveModelMixin
        • 4)UpdateModelMixin
        • 5)DestroyModelMixin
        • 3. 幾個可用子類視圖
          • 1) CreateAPIView
          • 2)ListAPIView
          • 3)RetrieveAPIView
          • 4)DestoryAPIView
          • 5)UpdateAPIView
          • 6)RetrieveUpdateAPIView
          • 7)RetrieveUpdateDestoryAPIView
    • 4.4 視圖集ViewSet
      • 1. 常用視圖集父類
        • 1) ViewSet
        • 2)GenericViewSet
        • 3)ModelViewSet
        • 4)ReadOnlyModelViewSet
      • 2. 視圖集中定義附加action動作
      • 3. action屬性
      • 4. 視圖集的繼承關系
    • 4.5 路由Routers
      • 1. 使用方法
      • 2. 視圖集中附加action的聲明
      • 3. 路由router形成URL的方式
        • 1) SimpleRouter
        • 2)DefaultRouter
  • 5. 其他功能
    • 5.1 認證
      • 認證Authentication
    • 5.2 權限
      • 權限Permissions
        • 使用
        • 提供的權限
        • 自定義權限
    • 5.3 限流
      • 限流Throttling
        • 可選限流類
    • 5.4 過濾
      • 過濾Filtering
    • 5.5 排序
      • 排序
        • 使用方法:
    • 5.6 分頁
      • 分頁Pagination
        • 可選分頁器
          • 1) PageNumberPagination
          • 2)LimitOffsetPagination
    • 5.7 異常處理
      • 異常處理 Exceptions
        • REST framework定義的異常
    • 5.8 自動生成接口文檔
      • 自動生成接口文檔
        • 1. 安裝依賴
        • 2. 設定接口文檔通路路徑
        • 3. 文檔描述說明的定義位置
        • 4. 通路接口文檔網頁

django rest framework學習
django rest framework學習

django rest framework學習
django rest framework學習

在前後端分離的應用模式裡,API接口如何定義?

對于接口的請求方式與路徑,每個後端開發人員可能都有自己的定義方式,風格迥異。
是否存在一種統一的定義方式,被廣大開發人員接受認可的方式呢?
這就是被普遍采用的API的RESTful設計風格。
例如對于後端資料庫中儲存了商品的資訊,前端可能需要對商品資料進行增删改查,那相應的每個操作後端都需要提供一個API接口:
django rest framework學習

應該盡量将API部署在專用域名之下。

https://api.example.com
           

如果确定API很簡單,不會有進一步擴充,可以考慮放在主域名下。

https://example.org/api/
           

應該将API的版本号放入URL。

http://www.example.com/app/1.0/foo

http://www.example.com/app/1.1/foo

http://www.example.com/app/2.0/foo
           

另一種做法是,将版本号放在HTTP頭資訊中,但不如放入URL友善和直覺。Github采用這種做法。

因為不同的版本,可以了解成同一種資源的不同表現形式,是以應該采用同一個URL。版本号可以在HTTP請求頭資訊的Accept字段中進行區分(參見Versioning REST Services):

Accept: vnd.example-com.foo+json; version=1.0

Accept: vnd.example-com.foo+json; version=1.1

Accept: vnd.example-com.foo+json; version=2.0
           

路徑又稱"終點"(endpoint),表示API的具體網址,每個網址代表一種資源(resource)

(1) 資源作為網址,隻能有名詞,不能有動詞,而且所用的名詞往往與資料庫的表名對應。

舉例來說,以下是不好的例子:

/getProducts
/listOrders
/retreiveClientByOrder?orderId=1
           

對于一個簡潔結構,你應該始終用名詞。 此外,利用的HTTP方法可以分離網址中的資源名稱的操作。

GET /products :将傳回所有産品清單
POST /products :将産品建立到集合
GET /products/4 :将擷取産品 4
PATCH(或)PUT /products/4 :将更新産品 4
           

(2) API中的名詞應該使用複數。無論子資源或者所有資源。

舉例來說,擷取産品的API可以這樣定義

擷取單個産品:http://127.0.0.1:8080/AppName/rest/products/1 擷取所有産品:

http://127.0.0.1:8080/AppName/rest/products

對于資源的具體操作類型,由HTTP動詞表示。

常用的HTTP動詞有下面四個(括号裡是對應的SQL指令)。

GET(SELECT):從伺服器取出資源(一項或多項)。

POST(CREATE):在伺服器建立一個資源。

PUT(UPDATE):在伺服器更新資源(用戶端提供改變後的完整資源)。

DELETE(DELETE):從伺服器删除資源。

還有三個不常用的HTTP動詞。

PATCH(UPDATE):在伺服器更新(更新)資源(用戶端提供改變的屬性)。

HEAD:擷取資源的中繼資料。

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:删除某個指定動物園的指定動物

如果記錄數量很多,伺服器不可能都将它們傳回給使用者。API應該提供參數,過濾傳回結果。

下面是一些常見的參數。

?limit=10:指定傳回記錄的數量

?offset=10:指定傳回記錄的開始位置。

?page=2&per_page=100:指定第幾頁,以及每頁的記錄數。

?sortby=name&order=asc:指定傳回結果按照哪個屬性排序,以及排序順序。

?animal_type_id=1:指定篩選條件

參數的設計允許存在備援,即允許API路徑和URL參數偶爾有重複。比如,GET /zoos/ID/animals 與 GET /animals?zoo_id=ID 的含義是相同的。

伺服器向使用者傳回的狀态碼和提示資訊,常見的有以下一些(方括号中是該狀态碼對應的HTTP動詞)。

200 OK - [GET]:伺服器成功傳回使用者請求的資料

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 - [*]:伺服器發生錯誤,使用者将無法判斷發出的請求是否成功。 狀态碼的完全清單參見這裡或這裡。

如果狀态碼是4xx,伺服器就應該向使用者傳回出錯資訊。一般來說,傳回的資訊中将error作為鍵名,出錯資訊作為鍵值即可。

{

error: "Invalid API key"

}

針對不同操作,伺服器向使用者傳回的結果應該符合以下規範。

GET /collection:傳回資源對象的清單(數組)

GET /collection/resource:傳回單個資源對象

POST /collection:傳回新生成的資源對象

PUT /collection/resource:傳回完整的資源對象

PATCH /collection/resource:傳回完整的資源對象

DELETE /collection/resource:傳回一個空文檔

django rest framework學習

伺服器傳回的資料格式,應該盡量使用JSON,避免使用XML。

我們以在Django架構中使用的圖書英雄案例來寫一套支援圖書資料增删改查的REST API接口,來了解REST API的開發。

在此案例中,前後端均發送JSON格式資料。

# views.py

from datetime import datetime

class BooksAPIVIew(View):
    """
    查詢所有圖書、增加圖書
    """
    def get(self, request):
        """
        查詢所有圖書
        路由:GET /books/
        """
        queryset = BookInfo.objects.all()
        book_list = []
        for book in queryset:
            book_list.append({
                'id': book.id,
                'btitle': book.btitle,
                'bpub_date': book.bpub_date,
                'bread': book.bread,
                'bcomment': book.bcomment,
                'image': book.image.url if book.image else ''
            })
        return JsonResponse(book_list, safe=False)

    def post(self, request):
        """
        新增圖書
        路由:POST /books/ 
        """
        json_bytes = request.body
        json_str = json_bytes.decode()
        book_dict = json.loads(json_str)

        # 此處詳細的校驗參數省略

        book = BookInfo.objects.create(
            btitle=book_dict.get('btitle'),
            bpub_date=datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        )

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        }, status=201)


class BookAPIView(View):
    def get(self, request, pk):
        """
        擷取單個圖書資訊
        路由: GET  /books/<pk>/
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        })

    def put(self, request, pk):
        """
        修改圖書資訊
        路由: PUT  /books/<pk>
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        json_bytes = request.body
        json_str = json_bytes.decode()
        book_dict = json.loads(json_str)

        # 此處詳細的校驗參數省略

        book.btitle = book_dict.get('btitle')
        book.bpub_date = datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
        book.save()

        return JsonResponse({
            'id': book.id,
            'btitle': book.btitle,
            'bpub_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else ''
        })

    def delete(self, request, pk):
        """
        删除圖書
        路由: DELETE /books/<pk>/
        """
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse(status=404)

        book.delete()

        return HttpResponse(status=204)
           
# urls.py

urlpatterns = [
    url(r'^books/$', views.BooksAPIVIew.as_view()),
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view())
]
           

測試

使用Postman測試上述接口

1) 擷取所有圖書資料

GET 方式通路 http://127.0.0.1:8000/books/, 傳回狀态碼200,資料如下

[
    {
        "id": 1,
        "btitle": "射雕英雄傳",
        "bpub_date": "1980-05-01",
        "bread": 12,
        "bcomment": 34,
        "image": ""
    },
    {
        "id": 2,
        "btitle": "天龍八部",
        "bpub_date": "1986-07-24",
        "bread": 36,
        "bcomment": 40,
        "image": ""
    },
    {
        "id": 3,
        "btitle": "笑傲江湖",
        "bpub_date": "1995-12-24",
        "bread": 20,
        "bcomment": 80,
        "image": ""
    },
    {
        "id": 4,
        "btitle": "雪山飛狐",
        "bpub_date": "1987-11-11",
        "bread": 58,
        "bcomment": 24,
        "image": ""
    },
    {
        "id": 5,
        "btitle": "西遊記",
        "bpub_date": "1988-01-01",
        "bread": 10,
        "bcomment": 10,
        "image": "booktest/xiyouji.png"
    },
    {
        "id": 6,
        "btitle": "水浒傳",
        "bpub_date": "1992-01-01",
        "bread": 10,
        "bcomment": 11,
        "image": ""
    },
    {
        "id": 7,
        "btitle": "紅樓夢",
        "bpub_date": "1990-01-01",
        "bread": 0,
        "bcomment": 0,
        "image": ""
    }
]
           

2)擷取單一圖書資料

GET 通路 http://127.0.0.1:8000/books/5/ ,傳回狀态碼200, 資料如下

{
    "id": 5,
    "btitle": "西遊記",
    "bpub_date": "1988-01-01",
    "bread": 10,
    "bcomment": 10,
    "image": "booktest/xiyouji.png"
}
           

GET 通路http://127.0.0.1:8000/books/100/,傳回狀态碼404

3)新增圖書資料

POST 通路http://127.0.0.1:8000/books/,發送JSON資料:

{
    "btitle": "三國演義",
    "bpub_date": "1990-02-03"
}
           
傳回狀态碼201,資料如下
{
    "id": 8,
    "btitle": "三國演義",
    "bpub_date": "1990-02-03",
    "bread": 0,
    "bcomment": 0,
    "image": ""
}
           
4)修改圖書資料 PUT 通路http://127.0.0.1:8000/books/8/,發送JSON資料:
{
    "btitle": "三國演義(第二版)",
    "bpub_date": "1990-02-03"
}
           
傳回狀态碼200,資料如下
{
    "id": 8,
    "btitle": "三國演義(第二版)",
    "bpub_date": "1990-02-03",
    "bread": 0,
    "bcomment": 0,
    "image": ""
}
           

5)删除圖書資料

DELETE 通路http://127.0.0.1:8000/books/8/,傳回204狀态碼

源碼

import json

from django.http import JsonResponse, HttpResponse
from django.shortcuts import render

"""
GET         /books/
POST        /books/
GET         /books/<pk>/
PUT         /books/<pk>/
DELETE      /books/<pk>/

響應資料  JSON
# 清單視圖: 路由後邊沒有pk/ID
# 詳情視圖: 路由後面   pk/ID
"""
from django.views import View

from .models import BookInfo


class BookListView(View):

    def get(self, request):
        """查詢所有圖書接口"""
        # 1. 查詢出所有圖書模型
        books = BookInfo.objects.all()
        # 2. 周遊查詢集,去除裡邊的每個書籍模型對象,把模型對象轉換成字典
        # 定義一個清單儲存所有字典
        book_list = []
        for book in books:
            book_dict = {
                'id': book.id,
                'btitle': book.btitle,
                'bput_date': book.bpub_date,
                'bread': book.bcomment,
                'image': book.image.url if book.image else '',
            }
            book_list.append(book_dict)  # 将轉換好的字典添加到清單中
        # 3. 響應給前端
        # 如果book_;list 不是一個字典的話就需要将safe設定成False.
        return JsonResponse(book_list, safe=False)

    def post(self, request):
        """新增圖書接口"""
        # 擷取前端傳入的請求體資料(json) request.body
        json_str_bytes = request.body
        # 把bytes類型的json字元串轉換成json_str
        json_str = json_str_bytes.decode()
        # 利用json.loads将json字元串紮UN幹哈UN從json(字典/清單)
        book_dict = json.loads(json_str)
        # 建立模型對象并儲存(把字典轉換成模型并儲存)
        book = BookInfo(
            btitle=book_dict['btitle'],
            bpub_date=book_dict['bpub_date'],

        )
        book.save()

        # 把新增的模型轉換成字典
        json_dict = {
            'id': book.id,
            'btitle': book.btitle,
            'bput_date': book.bpub_date,
            'bread': book.bread,
            'bcomment':book.bcomment,
            'image': book.image.url if book.image else '',
        }
        # 響應(把新增的資料再響應回去,201)
        return JsonResponse(json_dict,status=201)



class BookDetailView(View):
    """詳情視圖"""

    def get(self, request, pk):
        """查詢指定某個圖書館接口"""
        # 1. 擷取出指定pk的那個模型對象
        try:
            book = BookInfo.objects.get(id=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse({'message': '查詢的資料不存在'}, status=404)
        # 2. 模型對象轉字典
        book_dict = {
            'id': book.id,
            'btitle': book.btitle,
            'bput_date': book.bpub_date,
            'bread': book.bread,
            'bcomment':book.bcomment,
            'image': book.image.url if book.image else '',
        }
        # 3. 響應
        return JsonResponse(book_dict)

    def put(self, request, pk):
        """修改指定圖書館接口"""
        # 先查詢要修改的模型對象
        try:
            book = BookInfo.objects.get(pk=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse({'message': '查詢的資料不存在'}, status=404)
        # 擷取前端傳入的新資料(把資料轉換成字典)
        # json_str_bytes = request.body
        # json_str = json_str_bytes.decode()
        # book_dict = json.loads(json_str)

        book_dict = json.loads(request.body.decode())
        # 重新給模型指定的屬性指派
        book.btitle = book_dict['btitle']
        book.bpub_date = book_dict['bpub_date']

        # 調用save方法進行修改操作
        book.save()
        # 把修改後的模型再轉換成字典
        json_dict = {
            'id': book.id,
            'btitle': book.btitle,
            'bput_date': book.bpub_date,
            'bread': book.bread,
            'bcomment': book.bcomment,
            'image': book.image.url if book.image else '',
        }
        # 響應
        return JsonResponse(json_dict)

    def delete(self, request, pk):
        """删除指定圖書接口"""
        # 擷取要删除的模型對象
        try:
            book = BookInfo.objects.get(id=pk)
        except BookInfo.DoesNotExist:
            return HttpResponse({'message':'查詢的資料不存在'},status=404)
        # 删除指定模型對象
        book.delete()  # 實體删除(真正從資料庫删除)
        # book.is_delete = True
        # book.save()  # (邏輯删除)
        # 響應:删除時不需要有響應體但要指定狀态碼為 204
        return HttpResponse(status=204)


           
from django.db import models


# Create your models here.
# 定義圖書模型類BookInfo
class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名稱')
    bpub_date = models.DateField(verbose_name='釋出日期')
    bread = models.IntegerField(default=0, verbose_name='閱讀量')
    bcomment = models.IntegerField(default=0, verbose_name='評論量')
    is_delete = models.BooleanField(default=False, verbose_name='邏輯删除')
    # 注意,如果模型已經遷移建表并且表中如果已經有資料了,那麼後新增的字段,必須給預設值或可以為空,不然遷移就報錯
    # upload_to 指定上傳到media_root配置項的目錄中再建立booktest裡面
    image = models.ImageField(upload_to='booktest', verbose_name='圖檔', null=True)


    class Meta:
        db_table = 'tb_books'  # 指明資料庫表名
        verbose_name = '圖書'  # 在admin站點中顯示的名稱
        verbose_name_plural = verbose_name  # 顯示的複數名稱

    def __str__(self):
        """定義每個資料對象的顯示資訊"""
        return self.btitle

    def pub_date_format(self):
        return self.bpub_date.strftime('%Y-%m-%d')
    # 修改方法名在清單界面的展示
    pub_date_format.short_description = '釋出日期'
    # 指定自定義方法的排序依據
    pub_date_format.admin_order_field = 'bpub_date'


# 定義英雄模型類HeroInfo
class HeroInfo(models.Model):
    GENDER_CHOICES = (
        (0, 'female'),
        (1, 'male')
    )
    hname = models.CharField(max_length=20, verbose_name='名稱')
    hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
    hcomment = models.CharField(max_length=200, null=True, verbose_name='描述資訊')
    hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='圖書')  # 外鍵
    is_delete = models.BooleanField(default=False, verbose_name='邏輯删除')

    class Meta:
        db_table = 'tb_heros'
        verbose_name = '英雄'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.hname

    def read(self):
        return self.hbook.bread
    read.short_description = '閱讀量'
    read.admin_order_field = 'hbook__bread'
    # HeroInfo.objects.filter(hbook__bread=xx)

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

urlpatterns = [
    # 清單視圖的路由
    url(r'^books/$', views.BookListView.as_view()),
    # 詳情視圖的路由
    url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()),
]
           
# 配置項目中靜态檔案存放/讀取目錄
STATICFILES_DIRS = [
    # http://127.0.0.1:8000/static/index.html
    # http://127.0.0.1:8000/static/mm03.jpg
    os.path.join(BASE_DIR, 'static_files'),
    os.path.join(BASE_DIR, 'static_files/good'),
]

# 指定上傳檔案存儲目錄
MEDIA_ROOT=os.path.join(BASE_DIR,"static_files/media")
           

分析一下上節的案例,可以發現,在開發REST API接口時,視圖中做的最主要有三件事:

将請求的資料(如JSON格式)轉換為模型類對象

操作資料庫

将模型類對象轉換為響應的資料(如JSON格式)

維基百科中對于序列化的定義:

序列化(serialization)在計算機科學的資料進行中,是指将資料結構或物件狀态轉換成可取用格式(例如存成檔案,存于緩沖,或經由網絡中傳送),以留待後續在相同或另一台計算機環境中,能恢複原先狀态的過程。依照序列化格式重新擷取位元組的結果時,可以利用它來産生與原始物件相同語義的副本。對于許多物件,像是使用大量參照的複雜物件,這種序列化重建的過程并不容易。面向對象中的物件序列化,并不概括之前原始物件所關聯的函式。這種過程也稱為物件編組(marshalling)。從一系列位元組提取資料結構的反向操作,是反序列化(也稱為解編組,

deserialization, unmarshalling)。

序列化在計算機科學中通常有以下定義:

在資料儲存與傳送的部分是指将一個對象)存儲至一個儲存媒介,例如檔案或是記億體緩沖等,或者透過網絡傳送資料時進行編碼的過程,可以是位元組或是XML等格式。而位元組的或XML編碼格式可以還原完全相等的對象)。這程式被應用在不同應用程式之間傳送對象),以及伺服器将對象)儲存到檔案或資料庫。相反的過程又稱為反序列化。

簡而言之,我們可以将序列化了解為:

将程式中的一個資料結構類型轉換為其他格式(字典、JSON、XML等),例如将Django中的模型類對象裝換為JSON字元串,這個轉換過程我們稱為序列化。

如:

queryset = BookInfo.objects.all()
book_list = []
# 序列化
for book in queryset:
    book_list.append({
        'id': book.id,
        'btitle': book.btitle,
        'bpub_date': book.bpub_date,
        'bread': book.bread,
        'bcomment': book.bcomment,
        'image': book.image.url if book.image else ''
    })
return JsonResponse(book_list, safe=False)
           
反之,将其他格式(字典、JSON、XML等)轉換為程式中的資料,例如将JSON字元串轉換為Django中的模型類對象,這個過程我們稱為反序列化。
json_bytes = request.body
json_str = json_bytes.decode()

# 反序列化
book_dict = json.loads(json_str)
book = BookInfo.objects.create(
    btitle=book_dict.get('btitle'),
    bpub_date=datetime.strptime(book_dict.get('bpub_date'), '%Y-%m-%d').date()
)
           

我們可以看到,在開發REST API時,視圖中要頻繁的進行序列化與反序列化的編寫。

總結 在開發REST API接口時,我們在視圖中需要做的最核心的事是:

  • 将資料庫資料序列化為前端所需要的格式,并傳回;
  • 将前端發送的資料反序列化為模型類對象,并儲存到資料庫中。

  1. 在序列化與反序列化時,雖然操作的資料不盡相同,但是執行的過程卻是相似的,也就是說這部分代碼是可以複用簡化編寫的。
  2. 在開發RESTAPI的視圖中,雖然每個視圖具體操作的資料不同,但增、删、改、查的實作流程基本套路化,是以這部分代碼也是可以複用簡化編寫的:
  • 增:校驗請求資料 -> 執行反序列化過程 -> 儲存資料庫 -> 将儲存的對象序列化并傳回
  • 删:判斷要删除的資料是否存在 ->執行資料庫删除
  • 改:判斷要修改的資料是否存在 -> 校驗請求的資料 -> 執行反序列化過程 -> 儲存資料庫 -> 将儲存的對象序列化并傳回
  • 查:查詢資料庫 -> 将資料序列化并傳回
Django REST framework可以幫助我們簡化上述兩部分的代碼編寫,大大提高REST API的開發速度。

django rest framework學習

Django REST framework 架構是一個用于建構Web API 的強大而又靈活的工具。

通常簡稱為DRF架構 或 REST framework。

DRF架構是建立在Django架構基礎之上,由Tom Christie大牛二次開發的開源項目。

特點

  • 提供了定義序列化器Serializer的方法,可以快速根據 Django ORM 或者其它庫自動序列化/反序列化;
  • 提供了豐富的類視圖、Mixin擴充類,簡化視圖的編寫;
  • 豐富的定制層級:函數視圖、類視圖、視圖集合到自動生成 API, 滿足各種需要;
  • 多種身份認證和權限認證方式的支援;
  • 内置了限流系統;
  • 直覺的 API web 界面;
  • 可擴充性,插件豐富

DRF需要以下依賴:
  • Python (2.7, 3.4, 3.5, 3.6, 3.7)
  • Django (1.11, 2.0, 2.1)
DRF是以Django擴充應用的方式提供的,是以我們可以直接利用已有的Django環境而無需從新建立。(若沒有Django環境,需要先建立環境安裝Django)
  1. 安裝DRF
pip install djangorestframework
           
  1. 添加rest_framework應用
我們利用在Django架構學習中建立的demo工程,在settings.py的INSTALLED_APPS中添加'rest_framework'。
INSTALLED_APPS = [
    ...
    'rest_framework',
]
           
接下來就可以使用DRF進行開發了。

我們仍以在學習Django架構時使用的圖書英雄為案例,使用Django REST framework快速實作圖書的REST API。

在booktest應用中建立serializers.py用于儲存該應用的序列化器。

建立一個BookInfoSerializer用于序列化與反序列化。

class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = '__all__'
           
  • model 指明該序列化器處理的資料字段從模型類BookInfo參考生成
  • fields 指明該序列化器包含模型類中的哪些字段,'all'指明包含所有字段

在booktest應用的views.py中建立視圖BookInfoViewSet,這是一個視圖集合。
from rest_framework.viewsets import ModelViewSet
from .serializers import BookInfoSerializer
from .models import BookInfo

class BookInfoViewSet(ModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
           
  • queryset 指明該視圖集在查詢資料時使用的查詢集
  • serializer_class 指明該視圖在進行序列化或反序列化時使用的序列化器

在booktest應用的urls.py中定義路由資訊。
from . import views
from rest_framework.routers import DefaultRouter

urlpatterns = [
    ...
]

router = DefaultRouter()  # 可以處理視圖的路由器
router.register(r'books', views.BookInfoViewSet)  # 向路由器中注冊視圖集

urlpatterns += router.urls  # 将路由器中的是以路由資訊追到到django的路由清單中
           

運作目前程式(與運作Django一樣)
python manage.py runserver
           
在浏覽器中輸入網址127.0.0.1:8000,可以看到DRF提供的API Web浏覽頁面:
django rest framework學習
1)點選連結127.0.0.1:8000/books/ 可以通路擷取所有資料的接口,呈現如下頁面:
django rest framework學習
django rest framework學習
2)在頁面底下表單部分填寫圖書資訊,可以通路添加新圖書的接口,儲存新書:
django rest framework學習
點選POST後,傳回如下頁面資訊:
django rest framework學習
3)在浏覽器中輸入網址127.0.0.1:8000/books/1/,可以通路擷取單一圖書資訊的接口(id為1的圖書),呈現如下頁面:
django rest framework學習
4)在頁面底部表單中填寫圖書資訊,可以通路修改圖書的接口:
django rest framework學習
點選PUT,傳回如下頁面資訊:
django rest framework學習
5)點選DELETE按鈕,可以通路删除圖書的接口:
django rest framework學習
傳回,如下頁面:
django rest framework學習
至此,是不是發現Django REST framework很好用!

  1. 進行資料的校驗
  2. 對資料對象進行轉換

Django REST framework中的Serializer使用類來定義,須繼承自rest_framework.serializers.Serializer。

例如,我們已有了一個資料庫模型類BookInfo

class BookInfo(models.Model):
    btitle = models.CharField(max_length=20, verbose_name='名稱')
    bpub_date = models.DateField(verbose_name='釋出日期')
    bread = models.IntegerField(default=0, verbose_name='閱讀量')
    bcomment = models.IntegerField(default=0, verbose_name='評論量')
    image = models.ImageField(upload_to='booktest', verbose_name='圖檔', null=True)
           
我們想為這個模型類提供一個序列化器,可以定義如下:
class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20)
    bpub_date = serializers.DateField(label='釋出日期', required=True)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖檔', required=False)
           
注意:serializer不是隻能為資料庫模型類定義,也可以為非資料庫模型類的資料定義。serializer是獨立于資料庫之外的存在。

常用字段類型:

django rest framework學習
django rest framework學習
django rest framework學習
django rest framework學習

定義好Serializer類後,就可以建立Serializer對象了。

Serializer的構造方法為:

Serializer(instance=None, data=empty, **kwarg)
           

說明:

1)用于序列化時,将模型類對象傳入instance參數

2)用于反序列化時,将要被反序列化的資料傳入data參數

3)除了instance和data參數外,在構造Serializer對象時,還可通過context參數額外添加資料,如

serializer = AccountSerializer(account, context={'request': request})
           
通過context參數附加的資料,可以通過Serializer對象的context屬性擷取。

我們在django shell中來學習序列化器的使用。
python manage.py shell
           

1) 先查詢出一個圖書對象
from booktest.models import BookInfo

book = BookInfo.objects.get(id=2)
           
2) 構造序列化器對象
from booktest.serializers import BookInfoSerializer

serializer = BookInfoSerializer(book)
           

3)擷取序列化資料

通過data屬性可以擷取序列化後的資料

serializer.data
# {'id': 2, 'btitle': '天龍八部', 'bpub_date': '1986-07-24', 'bread': 36, 'bcomment': 40, 'image': None}
           
4)如果要被序列化的是包含多條資料的查詢集QuerySet,可以通過添加many=True參數補充說明
book_qs = BookInfo.objects.all()
serializer = BookInfoSerializer(book_qs, many=True)
serializer.data
# [OrderedDict([('id', 2), ('btitle', '天龍八部'), ('bpub_date', '1986-07-24'), ('bread', 36), ('bcomment', 40), ('image', N]), OrderedDict([('id', 3), ('btitle', '笑傲江湖'), ('bpub_date', '1995-12-24'), ('bread', 20), ('bcomment', 80), ('image'ne)]), OrderedDict([('id', 4), ('btitle', '雪山飛狐'), ('bpub_date', '1987-11-11'), ('bread', 58), ('bcomment', 24), ('ima None)]), OrderedDict([('id', 5), ('btitle', '西遊記'), ('bpub_date', '1988-01-01'), ('bread', 10), ('bcomment', 10), ('im', 'booktest/xiyouji.png')])]
           

如果需要序列化的資料中包含有其他關聯對象,則對關聯對象資料的序列化需要指明。

例如,在定義英雄資料的序列化器時,外鍵hbook(即所屬的圖書)字段如何序列化?

我們先定義HeroInfoSerialzier除外鍵字段外的其他部分

class HeroInfoSerializer(serializers.Serializer):
    """英雄資料序列化器"""
    GENDER_CHOICES = (
        (0, 'female'),
        (1, 'male')
    )
    id = serializers.IntegerField(label='ID', read_only=True)
    hname = serializers.CharField(label='名字', max_length=20)
    hgender = serializers.ChoiceField(choices=GENDER_CHOICES, label='性别', required=False)
    hcomment = serializers.CharField(label='描述資訊', max_length=200, required=False, allow_null=True)
           
對于關聯字段,可以采用以下幾種方式:

此字段将被序列化為關聯對象的主鍵。
hbook = serializers.PrimaryKeyRelatedField(label='圖書', read_only=True)
或
hbook = serializers.PrimaryKeyRelatedField(label='圖書', queryset=BookInfo.objects.all())
           

指明字段時需要包含read_only=True或者queryset參數:

  • 包含read_only=True參數時,該字段将不能用作反序列化使用
  • 包含queryset參數時,将被用作反序列化時參數校驗使用

使用效果:

from booktest.serializers import HeroInfoSerializer
from booktest.models import HeroInfo
hero = HeroInfo.objects.get(id=6)
serializer = HeroInfoSerializer(hero)
serializer.data
# {'id': 6, 'hname': '喬峰', 'hgender': 1, 'hcomment': '降龍十八掌', 'hbook': 2}
           

此字段将被序列化為關聯對象的字元串表示方式(即__str__方法的傳回值)

hbook = serializers.StringRelatedField(label='圖書')
           

使用效果

{'id': 6, 'hname': '喬峰', 'hgender': 1, 'hcomment': '降龍十八掌', 'hbook': '天龍八部'}
           

hbook = BookInfoSerializer()
           
{'id': 6, 'hname': '喬峰', 'hgender': 1, 'hcomment': '降龍十八掌', 'hbook': OrderedDict([('id', 2), ('btitle', '天龍八部')te', '1986-07-24'), ('bread', 36), ('bcomment', 40), ('image', None)])}
           

如果關聯的對象資料不是隻有一個,而是包含多個資料,如想序列化圖書BookInfo資料,每個BookInfo對象關聯的英雄HeroInfo對象可能有多個,此時關聯字段類型的指明仍可使用上述幾種方式,隻是在聲明關聯字段時,多補充一個many=True參數即可。

此處僅拿PrimaryKeyRelatedField類型來舉例,其他相同。

在BookInfoSerializer中添加關聯字段:

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20)
    bpub_date = serializers.DateField(label='釋出日期', required=True)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖檔', required=False)
    heroinfo_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)  # 新增
           
from booktest.serializers import BookInfoSerializer
from booktest.models import BookInfo
book = BookInfo.objects.get(id=2)
serializer = BookInfoSerializer(book)
serializer.data
# {'id': 2, 'btitle': '天龍八部', 'bpub_date': '1986-07-24', 'bread': 36, 'bcomment': 40, 'image': None, 'heroinfo_set': [6,8, 9]}
           
django rest framework學習
django rest framework學習

使用序列化器進行反序列化時,需要對資料進行驗證後,才能擷取驗證成功的資料或儲存成模型類對象。
在擷取反序列化的資料前,必須調用is_valid()方法進行驗證,驗證成功傳回True,否則傳回False。
驗證失敗,可以通過序列化器對象的errors屬性擷取錯誤資訊,傳回字典,包含了字段和字段的錯誤。如果是非字段錯誤,可以通過修改REST framework配置中的NON_FIELD_ERRORS_KEY來控制錯誤字典中的鍵名。
驗證成功,可以通過序列化器對象的validated_data屬性擷取資料。
在定義序列化器時,指明每個字段的序列化類型和選項參數,本身就是一種驗證行為。
如我們前面定義過的BookInfoSerializer
class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20)
    bpub_date = serializers.DateField(label='釋出日期', required=True)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖檔', required=False)
           
通過構造序列化器對象,并将要反序列化的資料傳遞給data構造參數,進而進行驗證
from booktest.serializers import BookInfoSerializer
data = {'bpub_date': 123}
serializer = BookInfoSerializer(data=data)
serializer.is_valid()  # 傳回False
serializer.errors
# {'btitle': [ErrorDetail(string='This field is required.', code='required')], 'bpub_date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].', code='invalid')]}
serializer.validated_data  # {}

data = {'btitle': 'python', 'bpub_date': '1998-12-1'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid()  # True
serializer.errors  # {}
serializer.validated_data  #  OrderedDict([('btitle', 'python')])
           
is_valid()方法還可以在驗證失敗時抛出異常serializers.ValidationError,可以通過傳遞raise_exception=True參數開啟,REST framework接收到此異常,會向前端傳回HTTP 400 Bad Request響應。
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
           
如果覺得這些還不夠,需要再補充定義驗證行為,可以使用以下三種方法:

1)validate_<field_name>

對<field_name>字段進行驗證,如
class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def validate_btitle(self, value):
        if 'django' not in value.lower():
            raise serializers.ValidationError("圖書不是關于Django的")
        return value
           
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'python'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid()  # False   
serializer.errors
#  {'btitle': [ErrorDetail(string='圖書不是關于Django的', code='invalid')]}
           

在序列化器中需要同時對多個字段進行比較驗證時,可以定義validate方法來驗證,如
class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def validate(self, attrs):
        bread = attrs['bread']
        bcomment = attrs['bcomment']
        if bread < bcomment:
            raise serializers.ValidationError('閱讀量小于評論量')
        return attrs
           
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'about django', 'bpub_date': '1998-12-1', 'bread': 10, 'bcomment': 20}
s = BookInfoSerializer(data=data)
s.is_valid()  # False
s.errors
#  {'non_field_errors': [ErrorDetail(string='閱讀量小于評論量', code='invalid')]}
           

在字段中添加validators選項參數,也可以補充驗證行為,如
def about_django(value):
    if 'django' not in value.lower():
        raise serializers.ValidationError("圖書不是關于Django的")

class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    btitle = serializers.CharField(label='名稱', max_length=20, validators=[about_django])
    bpub_date = serializers.DateField(label='釋出日期', required=True)
    bread = serializers.IntegerField(label='閱讀量', required=False)
    bcomment = serializers.IntegerField(label='評論量', required=False)
    image = serializers.ImageField(label='圖檔', required=False)
           
測試:
from booktest.serializers import BookInfoSerializer
data = {'btitle': 'python'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid()  # False   
serializer.errors
#  {'btitle': [ErrorDetail(string='圖書不是關于Django的', code='invalid')]}
           

如果在驗證成功後,想要基于validated_data完成資料對象的建立,可以通過實作create()和update()兩個方法來實作。
class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def create(self, validated_data):
        """建立"""
        return BookInfo(**validated_data)

    def update(self, instance, validated_data):
        """更新,instance為要更新的對象執行個體"""
        instance.btitle = validated_data.get('btitle', instance.btitle)
        instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
        instance.bread = validated_data.get('bread', instance.bread)
        instance.bcomment = validated_data.get('bcomment', instance.bcomment)
        return instance
           
如果需要在傳回資料對象的時候,也将資料儲存到資料庫中,則可以進行如下修改
class BookInfoSerializer(serializers.Serializer):
    """圖書資料序列化器"""
    ...

    def create(self, validated_data):
        """建立"""
        return BookInfo.objects.create(**validated_data)

    def update(self, instance, validated_data):
        """更新,instance為要更新的對象執行個體"""
        instance.btitle = validated_data.get('btitle', instance.btitle)
        instance.bpub_date = validated_data.get('bpub_date', instance.bpub_date)
        instance.bread = validated_data.get('bread', instance.bread)
        instance.bcomment = validated_data.get('bcomment', instance.bcomment)
        instance.save()
        return instance
           
實作了上述兩個方法後,在反序列化資料的時候,就可以通過save()方法傳回一個資料對象執行個體了
book = serializer.save()
           
如果建立序列化器對象的時候,沒有傳遞instance執行個體,則調用save()方法的時候,create()被調用,相反,如果傳遞了instance執行個體,則調用save()方法的時候,update()被調用。
from db.serializers import BookInfoSerializer
data = {'btitle': '封神演義', 'bpub_date': '1998-1-1'}
serializer = BookInfoSerializer(data=data)
serializer.is_valid()  # True
serializer.save()  # <BookInfo: 封神演義>

from db.models import BookInfo
book = BookInfo.objects.get(id=2)
data = {'btitle': '倚天劍', 'bpub_date': '1998-12-18'}
serializer = BookInfoSerializer(book, data=data)
serializer.is_valid()  # True
serializer.save()  # <BookInfo: 倚天劍>
book.btitle  # '倚天劍'
           
1)在對序列化器進行save()儲存時,可以額外傳遞資料,這些資料可以在create()和update()中的validated_data參數擷取到
serializer.save(owner=request.user)
           
2)預設序列化器必須傳遞所有required為True的字段,否則會抛出驗證異常。但是我們可以使用partial參數來允許部分字段在更新操作時可以不傳,而使用原有的資料
serializer = BookInfoSerializer(instance=book, data={'btitle': 'hello django', 'bpub_date': book.bpub_date})
# 兩種寫法最終效果等價
serializer = BookInfoSerializer(instance=book, data={'btitle': 'hello django'}, partial=True)

           
django rest framework學習
django rest framework學習
django rest framework學習

django rest framework學習
如果我們想要使用序列化器對應的是Django的模型類,DRF為我們提供了ModelSerializer模型類序列化器來幫助我們快速建立一個Serializer類。
ModelSerializer與正常的Serializer相同,但提供了:
  • 基于模型類自動生成一系列字段
  • 包含預設的create()和update()的實作

比如我們建立一個BookInfoSerializer
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = '__all__'
           
  • model 指明參照哪個模型類
  • fields 指明為模型類的哪些字段生成
我們可以在python manage.py shell中檢視自動生成的BookInfoSerializer的具體實作
>>> from booktest.serializers import BookInfoSerializer
>>> serializer = BookInfoSerializer()
>>> serializer
BookInfoSerializer():
    id = IntegerField(label='ID', read_only=True)
    btitle = CharField(label='名稱', max_length=20)
    bpub_date = DateField(allow_null=True, label='釋出日期', required=False)
    bread = IntegerField(label='閱讀量', max_value=2147483647, min_value=-2147483648, required=False)
    bcomment = IntegerField(label='評論量', max_value=2147483647, min_value=-2147483648, required=False)
    image = ImageField(allow_null=True, label='圖檔', max_length=100, required=False)
           

  1. 使用fields來明确字段,__all__表名包含所有字段,也可以寫明具體哪些字段,如
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = ('id', 'btitle', 'bpub_date')
           
  1. 使用exclude可以明确排除掉哪些字段
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        exclude = ('image',)
           
  1. 顯示指明字段,如:
class HeroInfoSerializer(serializers.ModelSerializer):
    hbook = BookInfoSerializer()

    class Meta:
        model = HeroInfo
        fields = ('id', 'hname', 'hgender', 'hcomment', 'hbook')
           
  1. 指明隻讀字段
可以通過read_only_fields指明隻讀字段,即僅用于序列化輸出的字段
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
        read_only_fields = ('id', 'bread', 'bcomment')
           

我們可以使用extra_kwargs參數為ModelSerializer添加或修改原有的選項參數
class BookInfoSerializer(serializers.ModelSerializer):
    """圖書資料序列化器"""
    class Meta:
        model = BookInfo
        fields = ('id', 'btitle', 'bpub_date', 'bread', 'bcomment')
        extra_kwargs = {
            'bread': {'min_value': 0, 'required': True},
            'bcomment': {'min_value': 0, 'required': True},
        }

# BookInfoSerializer():
#    id = IntegerField(label='ID', read_only=True)
#    btitle = CharField(label='名稱', max_length=20)
#    bpub_date = DateField(allow_null=True, label='釋出日期', required=False)
#    bread = IntegerField(label='閱讀量', max_value=2147483647, min_value=0, required=True)
#    bcomment = IntegerField(label='評論量', max_value=2147483647, min_value=0, required=True)
           
django rest framework學習
django rest framework學習
django rest framework學習
django rest framework學習
django rest framework學習
django rest framework學習
django rest framework學習
django rest framework學習

REST framework 傳入視圖的request對象不再是Django預設的HttpRequest對象,而是REST framework提供的擴充了HttpRequest類的Request類的對象。
REST framework提供了Parser解析器,在接收到請求後會自動根據Content-Type指明的請求資料類型(如JSON、表單等)将請求資料進行parse解析,解析為類字典對象儲存到Request對象中。
Request對象的資料是自動根據前端發送資料的格式進行解析之後的結果。
無論前端發送的哪種格式的資料,我們都可以以統一的方式讀取資料。

request.data 傳回解析之後的請求體資料。類似于Django中标準的request.POST和 request.FILES屬性,但提供如下特性:
  • 包含了解析之後的檔案和非檔案資料
  • 包含了對POST、PUT、PATCH請求方式解析後的資料
  • 利用了REST framework的parsers解析器,不僅支援表單類型資料,也支援JSON資料
request.query_params與Django标準的request.GET相同,隻是更換了更正确的名稱而已。

rest_framework.response.Response
REST framework提供了一個響應類Response,使用該類構造響應對象時,響應的具體資料内容會被轉換(render渲染)成符合前端需求的類型。
REST framework提供了Renderer渲染器,用來根據請求頭中的Accept(接收資料類型聲明)來自動轉換響應資料到對應格式。如果前端請求中未進行Accept聲明,則會采用預設方式處理響應資料,我們可以通過配置來修改預設響應格式。
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': (  # 預設響應渲染類
        'rest_framework.renderers.JSONRenderer',  # json渲染器
        'rest_framework.renderers.BrowsableAPIRenderer',  # 浏覽API渲染器
    )
}
           

Response(data, status=None, template_name=None, headers=None, content_type=None)
           
data資料不要是render處理之後的資料,隻需傳遞python的内建類型資料即可,REST framework會使用renderer渲染器處理data。

data不能是複雜結構的資料,如Django的模型類對象,對于這樣的資料我們可以使用Serializer序列化器序列化處理後(轉為了Python字典類型)再傳遞給data參數。

參數說明:

  • data: 為響應準備的序列化處理後的資料;
  • status: 狀态碼,預設200;
  • template_name: 模闆名稱,如果使用HTMLRenderer 時需指明;
  • headers: 用于存放響應頭資訊的字典;
  • content_type: 響應資料的Content-Type,通常此參數無需傳遞,REST framework會根據前端所需類型資料來設定該參數。

傳給response對象的序列化後,但尚未render處理的資料

2).status_code

狀态碼的數字

3).content

經過render處理後的響應資料

為了友善設定狀态碼,REST framewrok在rest_framework.status子產品中提供了常用狀态碼常量。

1)資訊告知 - 1xx

HTTP_100_CONTINUE

HTTP_101_SWITCHING_PROTOCOLS

2)成功 - 2xx

HTTP_200_OK

HTTP_201_CREATED

HTTP_202_ACCEPTED

HTTP_203_NON_AUTHORITATIVE_INFORMATION

HTTP_204_NO_CONTENT

HTTP_205_RESET_CONTENT

HTTP_206_PARTIAL_CONTENT

HTTP_207_MULTI_STATUS

3)重定向 - 3xx

HTTP_300_MULTIPLE_CHOICES

HTTP_301_MOVED_PERMANENTLY

HTTP_302_FOUND

HTTP_303_SEE_OTHER

HTTP_304_NOT_MODIFIED

HTTP_305_USE_PROXY

HTTP_306_RESERVED

HTTP_307_TEMPORARY_REDIRECT

4)用戶端錯誤 - 4xx

HTTP_400_BAD_REQUEST

HTTP_401_UNAUTHORIZED

HTTP_402_PAYMENT_REQUIRED

HTTP_403_FORBIDDEN

HTTP_404_NOT_FOUND

HTTP_405_METHOD_NOT_ALLOWED

HTTP_406_NOT_ACCEPTABLE

HTTP_407_PROXY_AUTHENTICATION_REQUIRED

HTTP_408_REQUEST_TIMEOUT

HTTP_409_CONFLICT

HTTP_410_GONE

HTTP_411_LENGTH_REQUIRED

HTTP_412_PRECONDITION_FAILED

HTTP_413_REQUEST_ENTITY_TOO_LARGE

HTTP_414_REQUEST_URI_TOO_LONG

HTTP_415_UNSUPPORTED_MEDIA_TYPE

HTTP_416_REQUESTED_RANGE_NOT_SATISFIABLE

HTTP_417_EXPECTATION_FAILED

HTTP_422_UNPROCESSABLE_ENTITY

HTTP_423_LOCKED

HTTP_424_FAILED_DEPENDENCY

HTTP_428_PRECONDITION_REQUIRED

HTTP_429_TOO_MANY_REQUESTS

HTTP_431_REQUEST_HEADER_FIELDS_TOO_LARGE

HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS

5)伺服器錯誤 - 5xx

HTTP_500_INTERNAL_SERVER_ERROR

HTTP_501_NOT_IMPLEMENTED

HTTP_502_BAD_GATEWAY

HTTP_503_SERVICE_UNAVAILABLE

HTTP_504_GATEWAY_TIMEOUT

HTTP_505_HTTP_VERSION_NOT_SUPPORTED

HTTP_507_INSUFFICIENT_STORAGE

HTTP_511_NETWORK_AUTHENTICATION_REQUIRED

REST framework 提供了衆多的通用視圖基類與擴充類,以簡化視圖的編寫。

視圖的繼承關系:

django rest framework學習

視圖的方法與屬性:

django rest framework學習

rest_framework.views.APIView
APIView是REST framework提供的所有視圖的基類,繼承自Django的View父類。
APIView與View的不同之處在于:
  • 傳入到視圖方法中的是REST framework的Request對象,而不是Django的HttpRequeset對象;
  • 視圖方法可以傳回REST framework的Response對象,視圖會為響應資料設定(render)符合前端要求的格式;
  • 任何APIException異常都會被捕獲到,并且處理成合适的響應資訊;
  • 在進行dispatch()分發前,會對請求進行身份認證、權限檢查、流量控制。

支援定義的屬性:

  • authentication_classes 清單或元組,身份認證類
  • permissoin_classes 清單或元組,權限檢查類
  • throttle_classes 清單或元組,流量控制類
在APIView中仍以正常的類視圖定義方法來實作get() 、post() 或者其他請求方式的方法。

加粗樣式舉例:

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

# url(r'^books/$', views.BookListView.as_view()),
class BookListView(APIView):
    def get(self, request):
        books = BookInfo.objects.all()
        serializer = BookInfoSerializer(books, many=True)
        return Response(serializer.data)
           

rest_framework.generics.GenericAPIView
繼承自APIVIew,主要增加了操作序列化器和資料庫查詢的方法,作用是為下面Mixin擴充類的執行提供方法支援。通常在使用時,可搭配一個或多個Mixin擴充類。
提供的關于序列化器使用的屬性與方法
  • 屬性:
    • serializer_class 指明視圖使用的序列化器
  • 方法:
    • get_serializer_class(self) 傳回序列化器類,預設傳回serializer_class,可以重寫,例如:
def get_serializer_class(self):
    if self.request.user.is_staff:
        return FullAccountSerializer
    return BasicAccountSerializer
           
  • get_serializer(self, args, *kwargs)

    傳回序列化器對象,主要用來提供給Mixin擴充類使用,如果我們在視圖中想要擷取序列化器對象,也可以直接調用此方法。

  • 注意,該方法在提供序列化器對象的時候,會向序列化器對象的context屬性補充三個資料:request、format、view,這三個資料對象可以在定義序列化器時使用。
    • request 目前視圖的請求對象
    • view 目前請求的類視圖對象
    • format 目前請求期望傳回的資料格式
提供的關于資料庫查詢的屬性與方法
    • queryset 指明使用的資料查詢集
    • get_queryset(self)
    • 傳回視圖使用的查詢集,主要用來提供給Mixin擴充類使用,是清單視圖與詳情視圖擷取資料的基礎,預設傳回queryset屬性,可以重寫,例如:
def get_queryset(self):
    user = self.request.user
    return user.accounts.all()
           
  • get_object(self)
    • 傳回詳情視圖所需的模型類資料對象,主要用來提供給Mixin擴充類使用。

      在試圖中可以調用該方法擷取詳情資訊的模型類對象。

    • 若詳情通路的模型類對象不存在,會傳回404。
    • 該方法會預設使用APIView提供的check_object_permissions方法檢查目前對象是否有權限被通路。
舉例:
# url(r'^books/(?P<pk>\d+)/$', views.BookDetailView.as_view()),
class BookDetailView(GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request, pk):
        book = self.get_object() # get_object()方法根據pk參數查找queryset中的資料對象
        serializer = self.get_serializer(book)
        return Response(serializer.data)
           
其他可以設定的屬性
  • pagination_class 指明分頁控制類
  • filter_backends 指明過濾控制後端

作用: 提供了幾種後端視圖(對資料資源進行曾删改查)處理流程的實作,如果需要編寫的視圖屬于這五種,則視圖可以通過繼承相應的擴充類來複用代碼,減少自己編寫的代碼量。
這五個擴充類需要搭配GenericAPIView父類,因為五個擴充類的實作需要調用GenericAPIView提供的序列化器與資料庫查詢的方法。
清單視圖擴充類,提供list(request, *args, **kwargs)方法快速實作清單視圖,傳回200狀态碼。
該Mixin的list方法會對資料進行過濾和分頁。

源代碼:

class ListModelMixin(object):
    """
    List a queryset.
    """
    def list(self, request, *args, **kwargs):
        # 過濾
        queryset = self.filter_queryset(self.get_queryset())
        # 分頁
        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)
        # 序列化
        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)
           
from rest_framework.mixins import ListModelMixin

class BookListView(ListModelMixin, GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request):
        return self.list(request)
           
建立視圖擴充類,提供create(request, *args, **kwargs)方法快速實作建立資源的視圖,成功傳回201狀态碼。
如果序列化器對前端發送的資料驗證失敗,傳回400錯誤。
class CreateModelMixin(object):
    """
    Create a model instance.
    """
    def create(self, request, *args, **kwargs):
        # 擷取序列化器
        serializer = self.get_serializer(data=request.data)
        # 驗證
        serializer.is_valid(raise_exception=True)
        # 儲存
        self.perform_create(serializer)
        headers = self.get_success_headers(serializer.data)
        return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)

    def perform_create(self, serializer):
        serializer.save()

    def get_success_headers(self, data):
        try:
            return {'Location': str(data[api_settings.URL_FIELD_NAME])}
        except (TypeError, KeyError):
            return {}
           
詳情視圖擴充類,提供retrieve(request, *args, **kwargs)方法,可以快速實作傳回一個存在的資料對象。
如果存在,傳回200, 否則傳回404。
class RetrieveModelMixin(object):
    """
    Retrieve a model instance.
    """
    def retrieve(self, request, *args, **kwargs):
        # 擷取對象,會檢查對象的權限
        instance = self.get_object()
        # 序列化
        serializer = self.get_serializer(instance)
        return Response(serializer.data)
           
class BookDetailView(RetrieveModelMixin, GenericAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def get(self, request, pk):
        return self.retrieve(request)
           
更新視圖擴充類,提供update(request, *args, **kwargs)方法,可以快速實作更新一個存在的資料對象。
同時也提供partial_update(request, *args, **kwargs)方法,可以實作局部更新。
成功傳回200,序列化器校驗資料失敗時,傳回400錯誤。
class UpdateModelMixin(object):
    """
    Update a model instance.
    """
    def update(self, request, *args, **kwargs):
        partial = kwargs.pop('partial', False)
        instance = self.get_object()
        serializer = self.get_serializer(instance, data=request.data, partial=partial)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)

        if getattr(instance, '_prefetched_objects_cache', None):
            # If 'prefetch_related' has been applied to a queryset, we need to
            # forcibly invalidate the prefetch cache on the instance.
            instance._prefetched_objects_cache = {}

        return Response(serializer.data)

    def perform_update(self, serializer):
        serializer.save()

    def partial_update(self, request, *args, **kwargs):
        kwargs['partial'] = True
        return self.update(request, *args, **kwargs)
           
删除視圖擴充類,提供destroy(request, *args, **kwargs)方法,可以快速實作删除一個存在的資料對象。
成功傳回204,不存在傳回404。
class DestroyModelMixin(object):
    """
    Destroy a model instance.
    """
    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        return Response(status=status.HTTP_204_NO_CONTENT)

    def perform_destroy(self, instance):
        instance.delete()
           

提供 post 方法

繼承自: GenericAPIView、CreateModelMixin

提供 get 方法

繼承自:GenericAPIView、ListModelMixin

繼承自: GenericAPIView、RetrieveModelMixin

提供 delete 方法

繼承自:GenericAPIView、DestoryModelMixin

提供 put 和 patch 方法

繼承自:GenericAPIView、UpdateModelMixin

提供 get、put、patch方法

繼承自: GenericAPIView、RetrieveModelMixin、UpdateModelMixin

提供 get、put、patch、delete方法

繼承自:GenericAPIView、RetrieveModelMixin、UpdateModelMixin、DestoryModelMixin

使用視圖集ViewSet,可以将一系列邏輯相關的動作放到一個類中:
  • list() 提供一組資料
  • retrieve() 提供單個資料
  • create() 建立資料
  • update() 儲存資料
  • destory() 删除資料
ViewSet視圖集類不再實作get()、post()等方法,而是實作動作 action 如 list() 、create() 等。
視圖集隻在使用as_view()方法的時候,才會将action動作與具體請求方式對應上。如:
class BookInfoViewSet(viewsets.ViewSet):

    def list(self, request):
        books = BookInfo.objects.all()
        serializer = BookInfoSerializer(books, many=True)
        return Response(serializer.data)

    def retrieve(self, request, pk=None):
        try:
            books = BookInfo.objects.get(id=pk)
        except BookInfo.DoesNotExist:
            return Response(status=status.HTTP_404_NOT_FOUND)
        serializer = BookInfoSerializer(books)
        return Response(serializer.data)
           
在設定路由時,我們可以如下操作
urlpatterns = [
    url(r'^books/$', BookInfoViewSet.as_view({'get':'list'}),
    url(r'^books/(?P<pk>\d+)/$', BookInfoViewSet.as_view({'get': 'retrieve'})
]
           

繼承自APIView與ViewSetMixin,作用也與APIView基本類似,提供了身份認證、權限校驗、流量管理等。
ViewSet主要通過繼承ViewSetMixin來實作在調用as_view()時傳入字典(如{'get':'list'})的映射處理工作。
在ViewSet中,沒有提供任何動作action方法,需要我們自己實作action方法。

使用ViewSet通常并不友善,因為list、retrieve、create、update、destory等方法都需要自己編寫,而這些方法與前面講過的Mixin擴充類提供的方法同名,是以我們可以通過繼承Mixin擴充類來複用這些方法而無需自己編寫。但是Mixin擴充類依賴與GenericAPIView,是以還需要繼承GenericAPIView。
GenericViewSet就幫助我們完成了這樣的繼承工作,繼承自GenericAPIView與ViewSetMixin,在實作了調用as_view()時傳入字典(如{'get':'list'})的映射處理工作的同時,還提供了GenericAPIView提供的基礎方法,可以直接搭配Mixin擴充類使用。
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action

class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
           

url的定義

urlpatterns = [
    url(r'^books/$', views.BookInfoViewSet.as_view({'get': 'list'})),
    url(r'^books/(?P<pk>\d+)/$', views.BookInfoViewSet.as_view({'get': 'retrieve'})),
]
           

繼承自GenericViewSet,同時包括了ListModelMixin、RetrieveModelMixin、CreateModelMixin、UpdateModelMixin、DestoryModelMixin。

繼承自GenericViewSet,同時包括了ListModelMixin、RetrieveModelMixin。

在視圖集中,除了上述預設的方法動作外,還可以添加自定義動作。
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action

class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    def latest(self, request):
        """
        傳回最新的圖書資訊
        """
        book = BookInfo.objects.latest('id')
        serializer = self.get_serializer(book)
        return Response(serializer.data)

    def read(self, request, pk):
        """
        修改圖書的閱讀量資料
        """
        book = self.get_object()
        book.bread = request.data.get('bread')
        book.save()
        serializer = self.get_serializer(book)
        return Response(serializer.data)
           
urlpatterns = [
    url(r'^books/$', views.BookInfoViewSet.as_view({'get': 'list'})),
    url(r'^books/latest/$', views.BookInfoViewSet.as_view({'get': 'latest'})),
    url(r'^books/(?P<pk>\d+)/$', views.BookInfoViewSet.as_view({'get': 'retrieve'})),
    url(r'^books/(?P<pk>\d+)/read/$', views.BookInfoViewSet.as_view({'put': 'read'})),
]
           

在視圖集中,我們可以通過action對象屬性來擷取目前請求視圖集時的action動作是哪個。

例如:

def get_serializer_class(self):
    if self.action == 'create':
        return OrderCommitSerializer
    else:
        return OrderDataSerializer
           

django rest framework學習

對于視圖集ViewSet,我們除了可以自己手動指明請求方式與動作action之間的對應關系外,還可以使用Routers來幫助我們快速實作路由資訊。
REST framework提供了兩個router
  • SimpleRouter
  • DefaultRouter

1) 建立router對象,并注冊視圖集,例如
from rest_framework import routers

router = routers.SimpleRouter()
router.register(r'books', BookInfoViewSet, base_name='book')
           
register(prefix, viewset, base_name)
  • prefix 該視圖集的路由字首
  • viewset 視圖集
  • base_name 路由名稱的字首
如上述代碼會形成的路由如下:
^books/$    name: book-list
^books/{pk}/$   name: book-detail
           
2)添加路由資料
可以有兩種方式:
urlpatterns = [
    ...
]
urlpatterns += router.urls
           

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

在視圖集中,如果想要讓Router自動幫助我們為自定義的動作生成路由資訊,需要使用rest_framework.decorators.action裝飾器。
以action裝飾器裝飾的方法名會作為action動作名,與list、retrieve等同。
action裝飾器可以接收兩個參數:
  • methods: 聲明該action對應的請求方式,清單傳遞
  • detail: 聲明該action的路徑是否與單一資源對應,及是否是xxx/

    /action方法名/

    • True 表示路徑格式是xxx/
    • False 表示路徑格式是xxx/action方法名/
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet
from rest_framework.decorators import action

class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer

    # detail為False 表示路徑名格式應該為 books/latest/
    @action(methods=['get'], detail=False)
    def latest(self, request):
        """
        傳回最新的圖書資訊
        """
        ...

    # detail為True,表示路徑名格式應該為 books/{pk}/read/
    @action(methods=['put'], detail=True)
    def read(self, request, pk):
        """
        修改圖書的閱讀量資料
        """
        ...
           
由路由器自動為此視圖集自定義action方法形成的路由會是如下内容:
^books/latest/$    name: book-latest
^books/{pk}/read/$  name: book-read
           

django rest framework學習

django rest framework學習
DefaultRouter與SimpleRouter的差別是,DefaultRouter會多附帶一個預設的API根視圖,傳回一個包含所有清單視圖的超連結響應資料。

可以在配置檔案中配置全局預設的認證方案
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',   # 基本認證
        'rest_framework.authentication.SessionAuthentication',  # session認證
    )
}
           
也可以在每個視圖中通過設定authentication_classess屬性來設定
from rest_framework.authentication import SessionAuthentication, BasicAuthentication
from rest_framework.views import APIView

class ExampleView(APIView):
    authentication_classes = (SessionAuthentication, BasicAuthentication)
    ...
           
認證失敗會有兩種可能的傳回值:
  • 401 Unauthorized 未認證
  • 403 Permission Denied 權限被禁止

權限控制可以限制使用者對于視圖的通路和對于具體資料對象的通路。
  • 在執行視圖的dispatch()方法前,會先進行視圖通路權限的判斷
  • 在通過get_object()擷取具體對象時,會進行對象通路權限的判斷

可以在配置檔案中設定預設的權限管理類,如
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}
           
如果未指明,則采用如下預設配置
'DEFAULT_PERMISSION_CLASSES': (
   'rest_framework.permissions.AllowAny',
)
           
也可以在具體的視圖中通過permission_classes屬性來設定,如
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

class ExampleView(APIView):
    permission_classes = (IsAuthenticated,)
    ...
           

  • AllowAny 允許所有使用者
  • IsAuthenticated 僅通過認證的使用者
  • IsAdminUser 僅管理者使用者
  • IsAuthenticatedOrReadOnly 認證的使用者可以完全操作,否則隻能get讀取

舉例

from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView

class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
           

如需自定義權限,需繼承rest_framework.permissions.BasePermission父類,并實作以下兩個任何一個方法或全部
  • .has_permission(self, request, view) 是否可以通路視圖, view表示目前視圖對象
  • .has_object_permission(self, request, view, obj) 是否可以通路資料對象, view表示目前視圖, obj為資料對象
class MyPermission(BasePermission):
    def has_object_permission(self, request, view, obj):
        """控制對obj對象的通路權限,此案例決絕所有對對象的通路"""
        return False

class BookInfoViewSet(ModelViewSet):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    permission_classes = [IsAuthenticated, MyPermission]
           

可以對接口通路的頻次進行限制,以減輕伺服器壓力。

可以在配置檔案中,使用DEFAULT_THROTTLE_CLASSES 和 DEFAULT_THROTTLE_RATES進行全局配置,
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ),
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/day',
        'user': '1000/day'
    }
}
           
DEFAULT_THROTTLE_RATES 可以使用 second, minute, hour 或day來指明周期。
也可以在具體視圖中通過throttle_classess屬性來配置,如
from rest_framework.throttling import UserRateThrottle
from rest_framework.views import APIView

class ExampleView(APIView):
    throttle_classes = (UserRateThrottle,)
    ...
           

1) AnonRateThrottle

限制所有匿名未認證使用者,使用IP區分使用者。

使用DEFAULT_THROTTLE_RATES['anon'] 來設定頻次

2)UserRateThrottle

限制認證使用者,使用User id 來區分。

使用DEFAULT_THROTTLE_RATES['user'] 來設定頻次

3)ScopedRateThrottle

限制使用者對于每個視圖的通路頻次,使用ip或user id。

class ContactListView(APIView):
    throttle_scope = 'contacts'
    ...

class ContactDetailView(APIView):
    throttle_scope = 'contacts'
    ...

class UploadView(APIView):
    throttle_scope = 'uploads'
    ...
           
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': (
        'rest_framework.throttling.ScopedRateThrottle',
    ),
    'DEFAULT_THROTTLE_RATES': {
        'contacts': '1000/day',
        'uploads': '20/day'
    }
}
           

執行個體

from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.generics import RetrieveAPIView
from rest_framework.throttling import UserRateThrottle

class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    authentication_classes = [SessionAuthentication]
    permission_classes = [IsAuthenticated]
    throttle_classes = (UserRateThrottle,)
           

對于清單資料可能需要根據字段進行過濾,我們可以通過添加django-fitlter擴充來增強支援。
pip install django-filter
           
在配置檔案中增加過濾後端的設定:
INSTALLED_APPS = [
    ...
    'django_filters',  # 需要注冊應用,
]

REST_FRAMEWORK = {
    'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',)
}
           
在視圖中添加filter_fields屬性,指定可以過濾的字段
class BookListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_fields = ('btitle', 'bread')

# 127.0.0.1:8000/books/?btitle=西遊記
           

對于清單資料,REST framework提供了OrderingFilter過濾器來幫助我們快速指明資料按照指定字段進行排序。

在類視圖中設定filter_backends,使用rest_framework.filters.OrderingFilter過濾器,REST framework會在請求的查詢字元串參數中檢查是否包含了ordering參數,如果包含了ordering參數,則按照ordering參數指明的排序字段對資料集進行排序。
前端可以傳遞的ordering參數的可選字段值需要在ordering_fields中指明。

示例:

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    filter_backends = [OrderingFilter]
    ordering_fields = ('id', 'bread', 'bpub_date')

# 127.0.0.1:8000/books/?ordering=-bread
           

REST framework提供了分頁的支援。
我們可以在配置檔案中設定全局的分頁方式,如:
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS':  'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 100  # 每頁數目
}
           
也可通過自定義Pagination類,來為視圖添加不同分頁行為。在視圖中通過pagination_clas屬性來指明。
class LargeResultsSetPagination(PageNumberPagination):
    page_size = 1000
    page_size_query_param = 'page_size'
    max_page_size = 10000
           
class BookDetailView(RetrieveAPIView):
    queryset = BookInfo.objects.all()
    serializer_class = BookInfoSerializer
    pagination_class = LargeResultsSetPagination
           
注意:如果在視圖内關閉分頁功能,隻需在視圖内設定
pagination_class = None
           

前端通路網址形式:

GET http://api.example.org/books/?page=4

可以在子類中定義的屬性:
  • page_size 每頁數目
  • page_query_param 前端發送的頁數關鍵字名,預設為"page"
  • page_size_query_param 前端發送的每頁數目關鍵字名,預設為None
  • max_page_size 前端最多能設定的每頁數量
from rest_framework.pagination import PageNumberPagination

class StandardPageNumberPagination(PageNumberPagination):
    page_size_query_param = 'page_size'
    max_page_size = 10

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all().order_by('id')
    serializer_class = BookInfoSerializer
    pagination_class = StandardPageNumberPagination

# 127.0.0.1/books/?page=1&page_size=2
           

GET http://api.example.org/books/?limit=100&offset=400

  • default_limit 預設限制,預設值與PAGE_SIZE設定一直
  • limit_query_param limit參數名,預設'limit'
  • offset_query_param offset參數名,預設'offset'
  • max_limit 最大limit限制,預設None
from rest_framework.pagination import LimitOffsetPaginati`from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # 先調用REST framework預設的異常處理方法獲得标準錯誤響應對象
    response = exception_handler(exc, context)

    # 在此處補充自定義的異常處理
    if response is not None:
        response.data['status_code'] = response.status_code

    return response`on

class BookListView(ListAPIView):
    queryset = BookInfo.objects.all().order_by('id')
    serializer_class = BookInfoSerializer
    pagination_class = LimitOffsetPagination

# 127.0.0.1:8000/books/?offset=3&limit=2
           

REST framework提供了異常處理,我們可以自定義異常處理函數。
from rest_framework.views import exception_handler

def custom_exception_handler(exc, context):
    # 先調用REST framework預設的異常處理方法獲得标準錯誤響應對象
    response = exception_handler(exc, context)

    # 在此處補充自定義的異常處理
    if response is not None:
        response.data['status_code'] = response.status_code

    return response
           
在配置檔案中聲明自定義的異常處理
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'my_project.my_app.utils.custom_exception_handler'
}
           
如果未聲明,會采用預設的方式,如下
REST_FRAMEWORK = {
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler'
}
           
補充上處理關于資料庫的異常
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework import status
from django.db import DatabaseError

def exception_handler(exc, context):
    response = drf_exception_handler(exc, context)

    if response is None:
        view = context['view']
        if isinstance(exc, DatabaseError):
            print('[%s]: %s' % (view, exc))
            response = Response({'detail': '伺服器内部錯誤'}, status=status.HTTP_507_INSUFFICIENT_STORAGE)

    return response
           

  • APIException 所有異常的父類
  • ParseError 解析錯誤
  • AuthenticationFailed 認證失敗
  • NotAuthenticated 尚未認證
  • PermissionDenied 權限決絕
  • NotFound 未找到
  • MethodNotAllowed 請求方式不支援
  • NotAcceptable 要擷取的資料格式不支援
  • Throttled 超過限流次數
  • ValidationError 校驗失敗

REST framework可以自動幫助我們生成接口文檔。
接口文檔以網頁的方式呈現。
自動接口文檔能生成的是繼承自APIView及其子類的視圖。

REST framewrok生成接口文檔需要coreapi庫的支援。
pip install coreapi
           

在總路由中添加接口文檔路徑。

文檔路由對應的視圖配置為

rest_framework.documentation.include_docs_urls,

參數title為接口文檔網站的标題。
from rest_framework.documentation import include_docs_urls

urlpatterns = [
    ...
    url(r'^docs/', include_docs_urls(title='My API title'))
]

# settings.py
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.coreapi.AutoSchema',
           

1) 單一方法的視圖,可直接使用類視圖的文檔字元串,如
class BookListView(generics.ListAPIView):
    """
    傳回所有圖書資訊.
    """
           
2)包含多個方法的視圖,在類視圖的文檔字元串中,分開方法定義,如
class BookListCreateView(generics.ListCreateAPIView):
    """
    get:
    傳回所有圖書資訊.

    post:
    建立圖書.
    """
           
3)對于視圖集ViewSet,仍在類視圖的文檔字元串中封開定義,但是應使用action名稱區分,如
class BookInfoViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, GenericViewSet):
    """
    list:
    傳回圖書清單資料

    retrieve:
    傳回圖書詳情資料

    latest:
    傳回最新的圖書資料

    read:
    修改圖書的閱讀量
    """
           

浏覽器通路 127.0.0.1:8000/docs/,即可看到自動生成的接口文檔。
django rest framework學習

1) 視圖集ViewSet中的retrieve名稱,在接口文檔網站中叫做read

2)參數的Description需要在模型類或序列化器類的字段中以help_text選項定義,如:

class BookInfo(models.Model):
    ...
    bread = models.IntegerField(default=0, verbose_name='閱讀量', help_text='閱讀量')
    ...
           
class BookReadSerializer(serializers.ModelSerializer):
    class Meta:
        model = BookInfo
        fields = ('bread', )
        extra_kwargs = {
            'bread': {
                'required': True,
                'help_text': '閱讀量'
            }
        }