python | DRF 框架知識總覽


DRF 框架

DRF 框架知識總覽

接口(api)

什么是接口

  • 定義

前台與后台進行信息交互的媒介 -- url鏈接

接口組成

  • url鏈接 - http://127.0.0.1:9000/api/users/

  • 請求方式 - get(查), post(增), put(整體改), patch(局部改), delete(刪)

  • 請求參數 - 拼接參數, 數據包參數(urlencoded, json, form-data)

  • 響應結果 - 響應的 json數據

  • 開發階段接口測試工具

    1
    2
    Postman:
    官網下載

接口文檔

為什么要寫接口文檔
  • 作為后台開發者, 要通過url將數據返回給前台
  • 接口文檔是給 后台開發者, 前台開發者, 測試等各個相關項目組同事查看的, 方便團隊開發(規則是后台指定的, 文檔后台來寫)
編寫文檔方式
  • word 編寫(不推薦)
  • drf 框架插件, 可以根據cbv的類快速生產文檔(類必須有注釋等信息, 要規范, 不推薦)
  • 采用寫文檔的平台
書寫過程
  • 先按照開發需要,完成接口的開發.
    • 設置后台url鏈接
    • 設置請求方式
    • 設置請求數據
    • 設置響應結果
  • 選擇接口平台 將以上變成文檔即可

接口規范

為什么要制定接口規范
  • 在前后台分離的情況下, 后台可以采用不同的后台應用, 因為前后台的響應規則是一致的
  • 按照一套規范來編寫, 后台不管是什么語言, 前台都可以采用一樣的交互方式.
  • 與之相反, 后台也不用管前台以何種方式開發
通用的接口規范: Restful 接口規范
  • url 編寫

    • https 協議 - 保證數據的安全性
    • api字眼 - 標識操作的是數據
    • v1/v2 字眼 - 數據的多版本共存
    • 資源復數 - 請求的數據稱之為資源 用數據類別的復數
    • 拼接條件 - 過濾群查接口數據 (https://api.baidu.com/books/?limit=3&ordering=-price)
  • 請求方式

    • /books/ - get - 群查
    • /books/(pk)/ - get - 單查
    • /books/ - post - 單增
    • /books/(pk)/ - put - 單整體改
    • /books/(pk)/ - patch - 單局部改
    • /books/(pk)/ - delete - 單刪
  • 響應結果

    • 網絡狀態碼與狀態信息:2xx | 3xx | 4xx | 5xx
    • 數據狀態碼:前后台約定規則 - 0:成功 1:失敗 2:成功無結果
    • 數據狀態信息:自定義成功失敗的信息解釋(英文)
    • 數據本體:json數據
    • 數據子資源:頭像、視頻等,用資源的url鏈接
  • 基於restful接口規范的接口設計

    1
    2
    3
    4
    5
    urlpatterns = [
    # 資源books接口的設計
    url(r'^books/$', views.BookAPIView.as_view()), # 群查、單增
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view()), # 單查、單刪、單(整體|局部)改
    ]

FBV 與 CBV : Function|Class Base View

  • CBV 比FBV的優勢
    • fbv沒一個接口都會對應一個函數來響應請求
    • cbv可以將一個資源的增刪改查所有操放在一個類中管理,在內部再分方法逐一處理 (高內聚低耦合:六個接口和一個類有關,但都能在類內部處理)

DRF 框架的基礎視圖類

APIView

請求模塊
DRF 框架APIView 的請求生命周期 概覽

重寫as_view方法
  • as_view方法完成路由配置,返回配置函數是 csrf_exempt(view),也就是禁用了csrf認證規則

  • 所有繼承APIView的子類,都不受csrf認證規則的限制

  • 將請求處理的任務交給dispath方法完成

解析模塊
  • 二次封裝了原生Django的uwsgi協議的request對象,並做了向下兼容(原來request對象的內容,用現在的request對象都能訪問)

  • 將所有拼接參數都放在request.query_params中,將所有數據包參數都放在request.data中

  • 路由的有名無名分組的數據還是保存在args和kwargs中

  • 解析模塊在 setting.py自定義解析設置

    1
    2
    3
    4
    5
    6
    7
    8
    REST_FRAMEWORK = {
    # 解析模塊
    'DEFAULT_PARSER_CLASSES': [
    'rest_framework.parsers.JSONParser', # json
    'rest_framework.parsers.FormParser', # urlencoded
    'rest_framework.parsers.MultiPartParser' # form-data
    ],
    }

渲染模塊
  • 當三大認證模塊和自己處理請求的視圖邏輯沒有出現異常時,會執行響應渲染模塊

  • 響應的數據會交給渲染模塊來完成數據的渲染,渲染方式有兩種:Json格式數據渲染、Brower格式數據渲染

  • 自定義渲染模塊解析配置

    1
    2
    3
    4
    5
    6
    7
    8
    REST_FRAMEWORK = {
    # 渲染模塊
    'DEFAULT_RENDERER_CLASSES': [
    'rest_framework.renderers.JSONRenderer',
    # 瀏覽器渲染,上線后會注釋,不然瀏覽器請求接口就暴露了后台語言
    'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    }
響應模塊
響應類: Response
  • 參數

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    """
    def __init__(self, data=None, status=None, template_name=None, headers=None, exception=False, content_type=None):
    pass

    data:響應的數據 - 空、字符串、數字、列表、字段、布爾
    status:網絡狀態碼
    template_name:drf說自己也可以支持前后台不分離返回頁面,但是不能和data共存(不會涉及)
    headers:響應頭(不用刻意去管)
    exception:是否是異常響應(如果是異常響應,可以賦值True,沒什么用)
    content_type:響應的結果類型(如果是響應data,默認就是application/json,所有不用管)
    """
  • 常見使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    Response(
    data={
    'status': 0,
    'msg': 'ok',
    'result': '正常數據'
    }
    )

    Response(
    data={
    'status': 1,
    'msg': '客戶端錯誤提示',
    },
    status=status.HTTP_400_BAD_REQUEST,
    exception=True
    )
異常模塊

DRF 核心組件

序列組件

序列化基類 BaseSerializer
  • 初始化參數

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    def __init__(self, instance=None, data=empty, **kwargs):

    # instance: 對象類型數據賦值給instance形參
    # data:請求來的數據賦值給data 形參
    # kwargs:內部有三個屬性:many、partial、context
    # many:操作的對象或數據,是單個的還是多個的
    # partial:在修改需求時使用,可以將所有校驗字段required校驗規則設置為False
    # context:用於視圖類和序列化類直接傳參使用

    # 常見使用
    # 單查接口
    UserModelSerializer(instance=user_obj)

    # 群查接口
    UserModelSerializer(instance=user_query, many=True)

    # 增接口
    UserModelSerializer(data=request.data)

    # 整體改接口
    UserModelSerializer(instance=user_obj, data=request.data)

    # 局部改接口
    UserModelSerializer(instance=user_obj, data=request.data, partial=True)

    # 刪接口,用不到序列化類
單表序列化
  • models.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from django.db import models
    from django.conf import settings
    class User(models.Model):
    SEX_CHOICES = ((0, '男'), (1, '女'))
    name = models.CharField(max_length=64, verbose_name='姓名')
    password = models.CharField(max_length=64)
    age = models.IntegerField()
    height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
    sex = models.IntegerField(choices=SEX_CHOICES, default=0)
    # sex = models.CharField(choices=[('0', '男'), ('1', '女')])
    icon = models.ImageField(upload_to='icon', default='icon/default.png')

    # 自定義序列化給前台的字段
    # 優點:1)可以格式化數據庫原有字段的數據 2)可以對外隱藏原有數據庫字段名 3)可以直接連表操作
    @property # 制造插頭
    def gender(self):
    return self.get_sex_display()

    @property
    def img(self):
    return settings.BASE_URL + settings.MEDIA_URL + self.icon.name

    def __str__(self):
    return self.name
  • serializers.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    from rest_framework import serializers
    from . import models
    class UserModelSerializer(serializers.ModelSerializer):
    class Meta:
    # 該序列化類是輔助於那個Model類的
    model = models.User
    # 設置參與序列化與反序列化字段
    # 插拔式:可以選擇性返回給前台字段(插頭都是在Model類中制造)
    # fields = ['name', 'age', 'height', 'sex', 'icon]
    fields = ['name', 'age', 'height', 'gender', 'img']
  • views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    from rest_framework.views import APIView
    from rest_framework.response import Response

    from . import models, serializers

    class UserAPIView(APIView):
    def get(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk: # 單查
    # 1)數據庫交互拿到資源obj或資源objs
    # 2)數據序列化成可以返回給前台的json數據
    # 3)將json數據返回給前台
    obj = models.User.objects.get(pk=pk)
    serializer = serializers.UserModelSerializer(obj, many=False)
    return Response(serializer.data)

    else: # 群查
    # 1)數據庫交互拿到資源obj或資源objs
    # 2)數據序列化成可以返回給前台的json數據
    # 3)將json數據返回給前台
    queryset = models.User.objects.all()
    # many操作的數據是否是多個
    serializer = serializers.UserModelSerializer(queryset, many=True)
    return Response(serializer.data)

    def post(self, request, *args, **kwargs):
    # 單增
    # 1)從請求request中獲得前台提交的數據
    # 2)將數據轉換成Model對象,並完成數據庫入庫操作
    # 3)將入庫成功的對象列化成可以返回給前台的json數據(請求與響應數據不對等:請求需要提交密碼,響應一定不展示密碼)
    # 4)將json數據返回給前台

    return Response()
單表反序列化
  • views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class UserAPIView(APIView):
    def post(self, request, *args, **kwargs):
    # 單增
    # 1)將前台請求的數據交給序列化類處理
    # 2)序列化類執行校驗方法,對前台提交的所有數據進行數據校驗:校驗失敗就是異常返回,成功才能繼續
    # 3)序列化組件完成數據入庫操作,得到入庫對象
    # 4)響應結果給前台
    serializer = serializers.UserModelSerializer(data=request.data)
    if serializer.is_valid():
    # 校驗成功 => 入庫 => 正常響應
    obj = serializer.save()
    return Response({
    'status': 0,
    'msg': 'ok',
    'result': '新增的那個對象'
    }, status=status.HTTP_201_CREATED)
    else:
    # 校驗失敗 => 異常響應
    return Response({
    'status': 1,
    'msg': serializer.errors,
    }, status=status.HTTP_400_BAD_REQUEST)
  • 反序列化

    serializers.py

    • 1: 不管是序列化還是反序列化字段,都必須在fields中進行聲明,沒有聲明的不會參與任何過程(數據都會被丟棄)

    • 2: 用 read_only 表示只讀,用 write_only 表示只寫,不標注兩者,代表可讀可寫

    • 3: 自定義只讀字段,在model類中用@property聲明,默認就是read_only

      1
      2
      3
      @property
      def gender(self):
      return self.get_sex_display()
    • 4: 自定義只寫字段,在serializer類中聲明,必須手動明確write_only

      1
      re_password = serializers.CharField(write_only=True)
    • 特殊)在serializer類中聲明,沒有明確write_only,是對model原有字段的覆蓋,且可讀可寫
      password = serializers.CharField()

    • 5: 用 extra_kwargs 來為 寫字段 制定基礎校驗規則(了解)

    • 6: 每一個 寫字段 都可以用局部鈎子 validate_字段(self, value) 方法來自定義校驗規則,成功返回value,失敗拋出 exceptions.ValidationError(‘異常信息’) 異常

    • 7: 需要聯合校驗的 寫字段們,用 validate(self, attrs) 方法來自定義校驗規則,,成功返回attrs,失敗拋出 exceptions.ValidationError({‘異常字段’: ‘異常信息’}) 異常

    • 8: extra_kwargs中一些重要的限制條件

      i)'required':代表是否必須參與寫操作,有默認值或可以為空的字段,該值為False;反之該值為True;也可以手動修改值
序列化類其他配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
# 不常用,將全部字段提供給外界
fields = '__all__'

# ------------------------------------------------------------------

class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
# 不常用,排除指定字段的其他所有字段,不能自動包含 外鍵反向 字段
exclude = ['is_delete', 'updated_time']

# ------------------------------------------------------------------

class AuthorModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Author
# 'detail', 'books' 是 外鍵(正向|反向) 字段
fields = ['name', 'detail', 'books']
# 不常用,自動深度,自動深度會顯示外鍵關聯表的所有字段
depth = 2
# 正向外鍵字段:就是外鍵的屬性名
# 反向外鍵字段:就是外鍵屬性設置的related_name
子序列化
1
2
3
4
5
"""
1)子序列化的字段,必須是 外鍵(正向|反向) 字段
2)子序列化對應的數據是單個many=False,數據對應是多個many=True
3)子序列化其實就是自定義序列化字段,覆蓋了原有 外鍵(正向|反向)字段 的規則,所以不能進行反序列化
"""
  • url.py

    1
    2
    url(r'^authors/$', views.AuthorAPIView.as_view()),
    url(r'^authors/(?P<pk>\d+)/$', views.AuthorAPIView.as_view()),
  • serializers.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    from rest_framework import serializers
    from . import models
    class AuthorDetailModelSerializer(serializers.ModelSerializer):
    class Meta:
    model = models.AuthorDetail
    fields = ['phone']

    class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
    model = models.Book
    fields = ['name', 'price']

    class AuthorModelSerializer(serializers.ModelSerializer):
    # 子序列化:子序列化類必須寫在上方,且只能對 外鍵(正向反向)字段 進行覆蓋
    # 注:運用了子序列化的外鍵字段,就不能進行數據庫的反序列化過程
    detail = AuthorDetailModelSerializer(many=False, read_only=True)
    books = BookModelSerializer(many=True, read_only=True)
    # 問題:
    # 1)不設置read_only時,就相當於允許反序列化,反序列化是就會報錯
    # 2)設置read_only時,可以完成反序列化,但是新增的數據再序列化了,就沒有外鍵關聯的數據,與原來數據格式就不一致了
    class Meta:
    model = models.Author
    fields = ['name', 'detail', 'books']
  • views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 實際開發,資源的大量操作都是查詢操作,只有查需求的資源,可以采用子序列化
    class AuthorAPIView(APIView):
    def get(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk:
    obj = models.Author.objects.filter(is_delete=False, pk=pk).first()
    serializer = serializers.AuthorModelSerializer(instance=obj)
    return APIResponse(result=serializer.data)
    else:
    queryset = models.Author.objects.filter(is_delete=False).all()
    serializer = serializers.AuthorModelSerializer(instance=queryset, many=True)
    return APIResponse(results=serializer.data)

    # 測試子序列化外鍵字段,不能參與反序列化,因為
    def post(self, request, *args, **kwargs):
    serializer = serializers.AuthorModelSerializer(data=request.data)
    if serializer.is_valid():
    obj = serializer.save()
    return APIResponse(result=serializers.AuthorModelSerializer(instance=obj).data, http_status=201)
    else:
    # 校驗失敗 => 異常響應
    return APIResponse(1, serializer.errors, http_status=400)
多表序列化與反序列化
1
2
3
4
"""
1)外鍵字段要參與反序列化,所以外鍵字段設置為write_only
2)外鍵關系需要連表序列化結果給前台,可以用@property來自定義連表序列化
"""
  • urls.py

    1
    2
    url(r'^books/$', views.BookAPIView.as_view()),
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view()),
  • models.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    class Book(BaseModel):
    # ...

    @property # @property字段默認就是read_only,且不允許修改
    def publish_name(self):
    return self.publish.name

    @property # 自定義序列化過程
    def author_list(self):
    temp_author_list = []
    for author in self.authors.all():
    author_dic = {
    "name": author.name
    }
    try:
    author_dic['phone'] = author.detail.phone
    except:
    author_dic['phone'] = ''
    temp_author_list.append(author_dic)
    return temp_author_list

    @property # 借助序列化類完成序列化過程
    def read_author_list(self):
    from .serializers import AuthorModelSerializer
    return AuthorModelSerializer(self.authors.all(), many=True).data
  • serializers

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    # 輔助序列化類
    class AuthorDetailModelSerializer(serializers.ModelSerializer):
    class Meta:
    model = models.AuthorDetail
    fields = ['phone']

    # 輔助序列化類
    class AuthorModelSerializer(serializers.ModelSerializer):
    detail = AuthorDetailModelSerializer(many=False, read_only=True)
    class Meta:
    model = models.Author
    fields = ['name', 'detail']


    # 主序列化類
    class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
    model = models.Book
    fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list', 'read_author_list')
    extra_kwargs = {
    'image': {
    'read_only': True,
    },
    'publish': { # 系統原有的外鍵字段,要留給反序列化過程使用,序列化外鍵內容,用@property自定義
    'write_only': True,
    },
    'authors': {
    'write_only': True,
    }
    }
  • views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    # 六個必備接口:單查、群查、單增、單刪、單整體改(了解)、單局部改
    # 四個額外接口:群增、群刪、群整體改、群局部改
    class BookAPIView(APIView):
    # 單查群查
    def get(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk:
    obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
    serializer = serializers.BookModelSerializer(instance=obj)
    return APIResponse(result=serializer.data)
    else:
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer = serializers.BookModelSerializer(instance=queryset, many=True)
    return APIResponse(results=serializer.data)


    # 單增群增
    def post(self, request, *args, **kwargs):
    # 如何區別單增群增:request.data是{}還是[]
    if not isinstance(request.data, list):
    # 單增
    serializer = serializers.BookModelSerializer(data=request.data)
    serializer.is_valid(raise_exception=True) # 如果校驗失敗,會直接拋異常,返回給前台
    obj = serializer.save()
    # 為什么要將新增的對象重新序列化給前台:序列化與反序列化數據不對等
    return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
    else:
    # 群增
    serializer = serializers.BookModelSerializer(data=request.data, many=True)
    serializer.is_valid(raise_exception=True) # 如果校驗失敗,會直接拋異常,返回給前台
    objs = serializer.save()
    # 為什么要將新增的對象重新序列化給前台:序列化與反序列化數據不對等
    return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)

    # 友情注釋:群增其實是借助了ListSerializer來的create方法完成的
序列化類外鍵字段的覆蓋
1
2
3
4
5
6
7
8
9
10
11
"""
1)在序列化類中自定義字段,名字與model類中屬性名一致,就稱之為覆蓋操作
(覆蓋的是屬性的所有規則:extra_kwargs中指定的簡易規則、model字段提供的默認規則、數據庫唯一約束等哪些規則)

2)外鍵覆蓋字段用PrimaryKeyRelatedField來實現,可以做到只讀、只寫、可讀可寫三種形式
只讀:read_only=True
只寫:queryset=關聯表的queryset, write_only=True
可讀可寫:queryset=關聯表的queryset

3)當外界關聯的數據是多個時,需標識many=True條件
"""
  • serializers.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class BookModelSerializer(serializers.ModelSerializer):
    # 如何覆蓋外鍵字段
    # publish = serializers.PrimaryKeyRelatedField(read_only=True) # 只讀
    # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all(), write_only=True) # 只寫
    # publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all()) # 可讀可寫

    publish = serializers.PrimaryKeyRelatedField(queryset=models.Publish.objects.all())
    authors = serializers.PrimaryKeyRelatedField(queryset=models.Author.objects.all(), many=True)

    class Meta:
    model = models.Book
    fields = ('name', 'price', 'image', 'publish', 'authors')
十大接口 序列化總結
1
2
3
4
5
6
7
"""
1)初始化序列化類,設置partial=True可以將所有反序列化字段的 required 設置為 False(提供就校驗,不提供不校驗),可以運用在局部修改接口

2)初始化序列化類,設置context={...},在序列化類操作self.context,實現視圖類和序列化類數據互通

3)只有要完成資源的群改這種特殊需求時,才需要自定義ListSerializer綁定給自定義的ModelSerializer,重寫update方法,來完成需求
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
"""
def __init__(self, instance=None, data=empty, **kwargs):
pass

instance:是要被賦值對象的 - 對象類型數據賦值給instance
data:是要被賦值數據的 - 請求來的數據賦值給data
kwargs:內部有三個屬性:many、partial、context
many:操作的對象或數據,是單個的還是多個的
partial:在修改需求時使用,可以將所有校驗字段required校驗規則設置為False
context:用於視圖類和序列化類直接傳參使用
"""

# 常見使用
# 單查接口
UserModelSerializer(instance=user_obj)

# 群查接口
UserModelSerializer(instance=user_query, many=True)

# 單增接口,request.data是字典
UserModelSerializer(data=request.data)

# 群增接口,request.data是列表
UserModelSerializer(data=request.data, many=True)

# 單整體改接口,request.data是字典
UserModelSerializer(instance=user_obj, data=request.data)

# 群整體改接口,request.data是列表,且可以分離出pks,轉換成user_queryset
UserModelSerializer(instance=user_queryset, data=request.data, many=True)

# 單局部改接口,request.data是字典
UserModelSerializer(instance=user_obj, data=request.data, partial=True)

# 群局部改接口,request.data是列表,且可以分離出pks,轉換成user_queryset
UserModelSerializer(instance=user_queryset, data=request.data, partial=True, many=True)

# 刪接口,用不到序列化類
  • models.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47

    # 基類:是抽象的(不會完成數據庫遷移),目的是提供共有字段的
    class BaseModel(models.Model):
    is_delete = models.BooleanField(default=False)
    updated_time = models.DateTimeField(auto_now_add=True)

    class Meta:
    abstract = True # 必須完成該配置

    class Book(BaseModel):
    name = models.CharField(max_length=64)
    price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
    image = models.ImageField(upload_to='img', default='img/default.png')

    publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
    authors = models.ManyToManyField(to='Author', related_name='books', db_constraint=False)

    @property # @property字段默認就是read_only,且不允許修改
    def publish_name(self):
    return self.publish.name

    @property # 自定義序列化過程
    def author_list(self):
    temp_author_list = []
    for author in self.authors.all():
    author_dic = {
    "name": author.name
    }
    try:
    author_dic['phone'] = author.detail.phone
    except:
    author_dic['phone'] = ''
    temp_author_list.append(author_dic)
    return temp_author_list


    class Publish(BaseModel):
    name = models.CharField(max_length=64)


    class Author(BaseModel):
    name = models.CharField(max_length=64)


    class AuthorDetail(BaseModel):
    phone = models.CharField(max_length=11)
    author = models.OneToOneField(to=Author, related_name='detail', db_constraint=False, on_delete=models.CASCADE)
  • urls.py

    1
    2
    3

    url(r'^books/$', views.BookAPIView.as_view()),
    url(r'^books/(?P<pk>\d+)/$', views.BookAPIView.as_view()),
  • serializers.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    from rest_framework import serializers
    from . import models

    # 群增群改輔助類(了解)
    class BookListSerializer(serializers.ListSerializer):
    """
    1)create群增方法不需要重新
    2)update群改方法需要重寫,且需要和views中處理request.data的邏輯配套使用
    3)self.child就代表該ListSerializer類綁定的ModelSerializer類
    BookListSerializer的self.child就是BookModelSerializer
    """
    # 重新update方法
    def update(self, queryset, validated_data_list):
    return [
    self.child.update(queryset[index], validated_data) for index, validated_data in enumerate(validated_data_list)
    ]

    # 主序列化類
    class BookModelSerializer(serializers.ModelSerializer):
    class Meta:
    # 配置自定義群增群改序列化類
    list_serializer_class = BookListSerializer

    model = models.Book
    fields = ('name', 'price', 'image', 'publish', 'authors', 'publish_name', 'author_list')
    # fields = ('name', 'price', 'image', 'publish', 'authors', 'abc')
    extra_kwargs = {
    'image': {
    'read_only': True,
    },
    'publish': { # 系統原有的外鍵字段,要留給反序列化過程使用,序列化外鍵內容,用@property自定義
    'write_only': True,
    },
    'authors': {
    'write_only': True,
    },
    }

    # 需求:內外傳參
    # 1)在鈎子函數中,獲得請求請求對象request 視圖類 => 序列化類
    # 2)序列化鈎子校驗過程中,也會產生一些數據,這些數據能不能傳遞給外界使用:序列化類 => 視圖類
    # 序列化類的context屬性,被視圖類與序列化類共享
    def validate(self, attrs):
    print(self.context) # 可以獲得視圖類在初始化序列化對象時傳入的context
    # self.context.update({'a': 10}) # 序列化類內部更新context,傳遞給視圖類
    return attrs
  • views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    from rest_framework.views import APIView
    from . import models, serializers
    from .response import APIResponse

    # 六個必備接口:單查、群查、單增、單刪、單整體改(了解)、單局部改
    # 四個額外接口:群增、群刪、群整體改、群局部改
    class BookAPIView(APIView):
    # 單查群查
    """
    單查:接口:/books/(pk)/
    群查:接口:/books/
    """
    def get(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk:
    obj = models.Book.objects.filter(is_delete=False, pk=pk).first()
    serializer = serializers.BookModelSerializer(instance=obj)
    return APIResponse(result=serializer.data)
    else:
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer = serializers.BookModelSerializer(instance=queryset, many=True)
    return APIResponse(results=serializer.data)

    # 單增群增
    """
    單增:接口:/books/ 數據:dict
    群增:接口:/books/ 數據:list
    """
    def post(self, request, *args, **kwargs):
    # 如何區別單增群增:request.data是{}還是[]
    if not isinstance(request.data, list):
    # 單增
    serializer = serializers.BookModelSerializer(data=request.data)
    serializer.is_valid(raise_exception=True) # 如果校驗失敗,會直接拋異常,返回給前台
    obj = serializer.save()
    # 為什么要將新增的對象重新序列化給前台:序列化與反序列化數據不對等
    return APIResponse(result=serializers.BookModelSerializer(obj).data, http_status=201)
    else:
    # 群增
    serializer = serializers.BookModelSerializer(data=request.data, many=True)
    serializer.is_valid(raise_exception=True) # 如果校驗失敗,會直接拋異常,返回給前台
    objs = serializer.save()
    # 為什么要將新增的對象重新序列化給前台:序列化與反序列化數據不對等
    return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data, http_status=201)

    # 單刪群刪
    """
    單刪:接口:/books/(pk)/
    群刪:接口:/books/ 數據:[pk1, ..., pkn]
    """
    def delete(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk:
    pks = [pk] # 將單刪偽裝成群刪一條
    else:
    pks = request.data # 群刪的數據就是群刪的主鍵們

    try: # request.data可能提交的是亂七八糟的數據,所以orm操作可能會異常
    rows = models.Book.objects.filter(is_delete=False, pk__in=pks).update(is_delete=True)
    except:
    return APIResponse(1, '數據有誤')

    if rows: # 只要有受影響的行,就代表刪除成功
    return APIResponse(0, '刪除成功')

    return APIResponse(2, '刪除失敗')

    # 單整體改群整體改
    """
    單整體改:接口:/books/(pk)/ 數據:dict
    群整體改:接口:/books/ 數據:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
    """
    def put(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk: # 單
    try:
    instance = models.Book.objects.get(is_delete=False, pk=pk)
    except:
    return APIResponse(1, 'pk error', http_status=400)
    # 序列化類同時賦值instance和data,代表用data重新更新instance => 修改
    serializer = serializers.BookModelSerializer(instance=instance, data=request.data)
    serializer.is_valid(raise_exception=True)
    obj = serializer.save()
    return APIResponse(result=serializers.BookModelSerializer(obj).data)
    else: # 群
    """ 分析request.data數據 [{'pk':1, 'name': '', 'publish': 1, 'authors': [1, 2]}, ...]
    1)從 request.data 中分離出 pks 列表
    2)pks中存放的pk在數據庫中沒有對應數據,或者對應的數據已經被刪除了,這些不合理的pk要被剔除
    3)pks最終轉換得到的 objs 列表長度與 request.data 列表長度不一致,就是數據有誤
    """
    pks = []
    try: # 只要不是要求的標准數據,一定會在下方四行代碼某一行拋出異常
    for dic in request.data:
    pks.append(dic.pop('pk'))
    objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
    assert len(objs) == len(request.data) # 兩個列表長度必須一致
    except:
    return APIResponse(1, '數據有誤', http_status=400)

    serializer = serializers.BookModelSerializer(instance=objs, data=request.data, many=True)
    serializer.is_valid(raise_exception=True)
    objs = serializer.save()
    return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)


    # 單局部改群局部改
    """
    單局部改:接口:/books/(pk)/ 數據:dict
    群局部改:接口:/books/ 數據:[{pk1, ...}, ..., {pkn, ...}] | {pks: [pk1, ..., pkn], data: [{}, ..., {}]}
    """
    def patch(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    if pk: # 單
    try:
    instance = models.Book.objects.get(is_delete=False, pk=pk)
    except:
    return APIResponse(1, 'pk error', http_status=400)
    # partial=True就是將所有反序列化字段的 required 設置為 False(提供就校驗,不提供不校驗)
    serializer = serializers.BookModelSerializer(instance=instance, data=request.data, partial=True)
    serializer.is_valid(raise_exception=True)
    obj = serializer.save()
    return APIResponse(result=serializers.BookModelSerializer(obj).data)
    else: # 群
    pks = []
    try: # 只要不是要求的標准數據,一定會在下方三行代碼某一行拋出異常
    for dic in request.data:
    pks.append(dic.get('pk'))
    objs = models.Book.objects.filter(is_delete=False, pk__in=pks)
    assert len(objs) == len(request.data) # 兩個列表長度必須一致
    except:
    return APIResponse(1, '數據有誤', http_status=400)

    serializer = serializers.BookModelSerializer(
    instance=objs,
    data=request.data,
    many=True,
    partial=True,
    context={'request': request} # 初始化時,對context賦值,將視圖類中數據傳遞給序列化類
    )

    serializer.is_valid(raise_exception=True)
    objs = serializer.save()

    print(serializer.context) # 在完成序列化類校驗后,可以重新拿到序列化類內部對context做的值更新
    return APIResponse(result=serializers.BookModelSerializer(objs, many=True).data)
開發流程
1
2
3
4
1)在model類中自定義 讀字段,在serializer類中自定義 寫字段
2)將model自帶字段和所以自定義字段書寫在fields中,用write_only和read_only區別model自帶字段
3)可以寫基礎校驗規則,也可以省略
4)制定局部及全局鈎子
Response
二次封裝Response
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 新建response.py文件
from rest_framework.response import Response

class APIResponse(Response):
def __init__(self, status=0, msg='ok', http_status=None, headers=None, exception=False, **kwargs):
# 將外界傳入的數據狀態碼、狀態信息以及其他所有額外存儲在kwargs中的信息,都格式化成data數據
data = {
'status': status,
'msg': msg
}
# 在外界數據可以用result和results來存儲
if kwargs:
data.update(kwargs)

super().__init__(data=data, status=http_status, headers=headers, exception=exception)


# 使用:
# APIResponse() 代表就返回 {"status": 0, "msg": "ok"}

# APIResponse(result="結果") 代表返回 {"status": 0, "msg": "ok", "result": "結果"}

# APIResponse(status=1, msg='error', http_status=400, exception=True) 異常返回 {"status": 1, "msg": "error"}
視圖家族
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
"""
視圖基類:APIView、GenericAPIView
視圖工具類:mixins包下的五個類(六個方法)
工具視圖類:generics包下的所有GenericAPIView的子類
視圖集:viewsets包下的類
"""

""" GenericAPIView基類(基本不會單獨使用,了解即可,但是是高級視圖類的依賴基礎)
1)GenericAPIView繼承APIV iew,所有APIView子類寫法在繼承GenericAPIView時可以保持一致
2)GenericAPIView給我們提供了三個屬性 queryset、serializer_class、lookup_field
3)GenericAPIView給我們提供了三個方法 get_queryset、get_serializer、get_obj
"""


""" mixins包存放了視圖工具類(不能單獨使用,必須配合GenericAPIView使用)
CreateModelMixin:單增工具類
create方法

ListModelMixin:群查工具類
list方法

RetrieveModelMixin:單查工具類
retrieve方法

UpdateModelMixin:單整體局部改工具類
update方法

DestroyModelMixin:單刪工具類
destory方法
"""

""" generics包下的所有GenericAPIView的子類(就是繼承GenericAPIView和不同mixins下的工具類的組合)
1)定義的視圖類,繼承generics包下已有的特點的GenericAPIView子類,可以在只初始化queryset和serializer_class兩個類屬性后,就獲得特定的功能

2)定義的視圖類,自己手動繼承GenericAPIView基類,再任意組合mixins包下的一個或多個工具類,可以實現自定義的工具視圖類,獲得特定的功能或功能們

注:
i)在這些模式下,不能實現單查群查共存(可以加邏輯區分,也可以用視圖集知識)
ii)DestroyModelMixin工具類提供的destory方法默認是從數據庫中刪除數據,所以一般刪除數據的需求需要自定義邏輯
"""
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# ----------------------------- 過渡寫法:了解 -----------------------------

from rest_framework.generics import GenericAPIView
class BookV1APIView(GenericAPIView):
# 將數據和序列化提示為類屬性,所有的請求方法都可以復用
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
lookup_field = 'pk' # 可以省略,默認是pk,與url有名分組對應的

# 群查
def get(self, request, *args, **kwargs):
# queryset = models.Book.objects.filter(is_delete=False).all() # => 方法+屬性兩行代碼
queryset = self.get_queryset()
# serializer = serializers.BookModelSerializer(instance=queryset, many=True) # => 方法+屬性兩行代碼
serializer = self.get_serializer(instance=queryset, many=True)
return APIResponse(results=serializer.data)

# 單查
# def get(self, request, *args, **kwargs):
# obj = self.get_object()
# serializer = self.get_serializer(obj)
# return APIResponse(results=serializer.data)

# 單增
def post(self, request, *args, **kwargs):
# serializer = serializers.BookModelSerializer(data=request.data)
serializer = self.get_serializer(data=request.data) # 同樣的步驟多了,好處就來了
serializer.is_valid(raise_exception=True)
obj = serializer.save()
return APIResponse(result=self.get_serializer(obj).data, http_status=201)

# ----------------------------- 過渡寫法:了解 -----------------------------

from rest_framework.generics import GenericAPIView
from rest_framework import mixins
class BookV2APIView(GenericAPIView, mixins.RetrieveModelMixin, mixins.CreateModelMixin):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer

# 單查
def get(self, request, *args, **kwargs):
# obj = self.get_object()
# serializer = self.get_serializer(obj)
# return APIResponse(results=serializer.data)

# return self.retrieve(request, *args, **kwargs)

response = self.retrieve(request, *args, **kwargs)
return APIResponse(result=response.data)

# 單增
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)


# ----------------------------- 開發寫法:常用 -----------------------------

from rest_framework.generics import RetrieveAPIView
class BookV3APIView(RetrieveAPIView):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer

# 單查
pass
urls.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from django.conf.urls import url
from . import views

urlpatterns = [
# ...

url(r'^v1/books/$', views.BookV1APIView.as_view()),
url(r'^v1/books/(?P<pk>\d+)/$', views.BookV1APIView.as_view()),

url(r'^v2/books/$', views.BookV2APIView.as_view()),
url(r'^v2/books/(?P<pk>\d+)/$', views.BookV2APIView.as_view()),

url(r'^v3/books/$', views.BookV3APIView.as_view()),
url(r'^v3/books/(?P<pk>\d+)/$', views.BookV3APIView.as_view()),
]
基於generics包下工具視圖類的六大基礎接口
  • views.py

    1
    2
    3
    4
    # 六大基礎接口
    # 1)直接繼承generics包下的工具視圖類,可以完成六大基礎接口
    # 2)單查群查不能共存
    # 3)單刪一般會重寫
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    from . import models, serializers
    from rest_framework.response import Response
    from rest_framework import generics
    class BookV2APIView(generics.ListAPIView,
    generics.RetrieveAPIView,
    generics.CreateAPIView,
    generics.UpdateAPIView,
    generics.DestroyAPIView):
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer_class = serializers.BookModelSerializer

    def get(self, request, *args, **kwargs):
    if 'pk' in kwargs:
    return self.retrieve(request, *args, **kwargs)
    return self.list(request, *args, **kwargs)

    def delete(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
    return Response(status=204)
視圖集
  • 導入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    """ ViewSetMixin類存在理由推到
    1)工具視圖類,可以完全應付六大基礎接口,唯一缺點是單查與群查接口無法共存
    (只配置queryset、serializer_class、lookup_field)
    2)不能共存的原因:RetrieveAPIView和ListAPIView都是get方法,不管帶不帶pk的get請求,只能映射給一個get方法
    3)能不能修改映射關系:
    比如將/books/的get請求映射給list方法,
    將/books/(pk)/的get請求映射給retrieve方法,
    甚至可以隨意自定義映射關系
    """
  • 源碼分析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
     繼承視圖集的視圖類的as_view都是走ViewSetMixin類的,核心源碼分析
    @classonlymethod
    def as_view(cls, actions=None, **initkwargs):
    # ...

    # 沒有actions,也就是調用as_view()沒有傳參,像as_view({'get': 'list'})
    if not actions:
    raise TypeError("The `actions` argument must be provided when "
    "calling `.as_view()` on a ViewSet. For example "
    "`.as_view({'get': 'list'})`")

    # ...

    # 請求來了走view函數
    def view(request, *args, **kwargs):
    # ...
    # 解析actions,修改 請求分發 - 響應函數 映射關系
    self.action_map = actions
    for method, action in actions.items(): # method:get | action:list
    handler = getattr(self, action) # 從我們視圖類用action:list去反射,所以handler是list函數,不是get函數
    setattr(self, method, handler) # 將get請求對應list函數,所以在dispath分發請求時,會將get請求分發給list函數

    # ...
    # 通過視圖類的dispatch完成最后的請求分發
    return self.dispatch(request, *args, **kwargs)

    # ...
    # 保存actions映射關系,以便后期使用
    view.actions = actions
    return csrf_exempt(view)
  • 核心

    1
    2
    3
    4
    5
    6
    7
    8
    """
    視圖集的使用總結
    1)可以直接繼承ModelViewSet,實現六大繼承接口(是否重寫destroy方法,或其他方法,根據需求決定)
    2)可以直接繼承ReadOnlyModelViewSet,實現只讀需求(只有單查群查)
    3)繼承ViewSet類,與Model類關系不是很密切的接口:登錄的post請求,是查詢操作;短信驗證碼發生接口,借助第三方平台
    4)繼承GenericViewSet類,就代表要配合mixins包,自己完成任意組合
    5)繼承以上4個視圖集任何一個,都可以與路由as_view({映射})配合,完成自定義請求響應方法
    """
  • 案例

    urls.py

    1
    2
    3
    4
    5
    6
    7
    url(r'^v3/books/$', views.BookV3APIView.as_view(
    {'get': 'list', 'post': 'create', 'delete': 'multiple_destroy'}
    )),

    url(r'^v3/books/(?P<pk>\d+)/$', views.BookV3APIView.as_view(
    {'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy'}
    )),

    views.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    from rest_framework.viewsets import ModelViewSet
    from rest_framework.response import Response
    class BookV3APIView(ModelViewSet):
    queryset = models.Book.objects.filter(is_delete=False).all()
    serializer_class = serializers.BookModelSerializer

    # 可以在urls.py中as_view({'get': 'my_list'})自定義請求映射
    def my_list(self, request, *args, **kwargs):
    return Response('ok')

    # 需要完成字段刪除,不是重寫delete方法,而是重寫destroy方法
    def destroy(self, request, *args, **kwargs):
    pk = kwargs.get('pk')
    models.Book.objects.filter(is_delete=False, pk=pk).update(is_delete=True)
    return Response(status=204)


    # 群刪接口
    def multiple_destroy(self, request, *args, **kwargs):
    try:
    models.Book.objects.filter(is_delete=False, pk__in=request.data).update(is_delete=True)
    except:
    return Response(status=400)
    return Response(status=204)
路由組件 : 必須配合視圖集使用

urls.py

1
2
3
4
5
6
7
8
9
10
11
12
13
from django.conf.urls import url, include
from . import views
# 路由組件,必須配合視圖集使用
from rest_framework.routers import SimpleRouter
router = SimpleRouter()

# 以后就寫視圖集的注冊即可:BookV3APIView和BookV4APIView都是視圖集
router.register('v3/books', views.BookV3APIView, 'book')
router.register('v4/books', views.BookV4APIView, 'book')

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

views.py

1
2
3
4
from rest_framework.viewsets import ReadOnlyModelViewSet
class BookV4APIView(ReadOnlyModelViewSet):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookModelSerializer
自定義路由組件
  • router.py

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    from rest_framework.routers import SimpleRouter as DrfSimpleRouter
    from rest_framework.routers import Route, DynamicRoute

    class SimpleRouter(DrfSimpleRouter):
    routes = [
    # List route.
    Route(
    url=r'^{prefix}{trailing_slash}$',
    mapping={
    'get': 'list',
    'post': 'create', # 注:群增只能自己在視圖類中重寫create方法,完成區分
    'delete': 'multiple_destroy', # 新增:群刪
    'put': 'multiple_update', # 新增:群整體改
    'patch': 'multiple_partial_update' # 新增:群局部改
    },
    name='{basename}-list',
    detail=False,
    initkwargs={'suffix': 'List'}
    ),
    # Dynamically generated list routes. Generated using
    # @action(detail=False) decorator on methods of the viewset.
    DynamicRoute(
    url=r'^{prefix}/{url_path}{trailing_slash}$',
    name='{basename}-{url_name}',
    detail=False,
    initkwargs={}
    ),
    # Detail route.
    Route(
    url=r'^{prefix}/{lookup}{trailing_slash}$',
    mapping={
    'get': 'retrieve',
    'put': 'update',
    'patch': 'partial_update',
    'delete': 'destroy'
    },
    name='{basename}-detail',
    detail=True,
    initkwargs={'suffix': 'Instance'}
    ),
    # Dynamically generated detail routes. Generated using
    # @action(detail=True) decorator on methods of the viewset.
    DynamicRoute(
    url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$',
    name='{basename}-{url_name}',
    detail=True,
    initkwargs={}
    ),
    ]
上傳文件接口
urls.py
1
2
3
4
5
6
7
8
9
10
11
12
from django.conf.urls import url, include
from . import views
# 路由組件,必須配合視圖集使用
from rest_framework.routers import SimpleRouter
router = SimpleRouter()

# /books/image/(pk) 提交 form-data:用image攜帶圖片
router.register('books/image', views.BookUpdateImageAPIView, 'book')

urlpatterns = [
url('', include(router.urls))
]
serializers.py
1
2
3
4
class BookUpdateImageModelSerializer(serializers.ModelSerializer):
class Meta:
model = models.Book
fields = ['image']
views.py
1
2
3
4
5
6
# 上次文件 - 修改頭像 - 修改海報
from rest_framework.viewsets import GenericViewSet
from rest_framework import mixins
class BookUpdateImageAPIView(GenericViewSet, mixins.UpdateModelMixin):
queryset = models.Book.objects.filter(is_delete=False).all()
serializer_class = serializers.BookUpdateImageModelSerializer

三大認證組件

權限

Django權限六表: 擴展User表
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from django.db import models

# RBAC - Role-Based Access Control
# Django的 Auth組件 采用的認證規則就是RBAC

from django.contrib.auth.models import AbstractUser
class User(AbstractUser):
mobile = models.CharField(max_length=11, unique=True)

def __str__(self):
return self.username


class Book(models.Model):
name = models.CharField(max_length=64)

def __str__(self):
return self.name


class Car(models.Model):
name = models.CharField(max_length=64)

def __str__(self):
return self.name
settings.py
1
2
# 自定義User表,要配置
AUTH_USER_MODEL = 'api.User'
admin.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from django.contrib import admin
from . import models

from django.contrib.auth.admin import UserAdmin as DjangoUserAdmin

# 自定義User表后,admin界面管理User類
class UserAdmin(DjangoUserAdmin):
# 添加用戶課操作字段
add_fieldsets = (
(None, {
'classes': ('wide',),
'fields': ('username', 'password1', 'password2', 'is_staff', 'mobile', 'groups', 'user_permissions'),
}),
)
# 展示用戶呈現的字段
list_display = ('username', 'mobile', 'is_staff', 'is_active', 'is_superuser')


admin.site.register(models.User, UserAdmin)
admin.site.register(models.Book)
admin.site.register(models.Car)
導入
1
2
3
# 1)像專門做人員權限管理的系統(CRM系統)都是公司內部使用,所以數據量都在10w一下,一般效率要求也不是很高
# 2)用戶量極大的常規項目,會分兩種用戶:前台用戶(三大認證) 和 后台用戶(RBAC來管理)
# 結論:沒有特殊要求的Django項目可以直接采用Auth組件的權限六表,不需要自定義六個表,也不需要斷開表關系,但可能需要自定義User表
做項目是否要分表管理前后台用戶
1
2
3
4
5
6
7
8
9
10
11
12
13
"""
1)是否需要分表
答案:不需要
理由:前后台用戶共存的項目,后台用戶量都是很少;做人員管理的項目,基本上都是后台用戶;前后台用戶量都大的會分兩個項目處理

2)用戶權限六表是否需要斷關聯
答案:不需要
理由:前台用戶占主導的項目,幾乎需求只會和User一個表有關;后台用戶占主導的項目,用戶量不會太大

3)Django項目有沒有必須自定義RBAC六表
答案:不需要
理由:auth組件功能十分強大且健全(驗證密碼,創建用戶等各種功能);admin、xadmin、jwt、drf-jwt組件都是依賴auth組件的(自定義RBAC六表,插件都需要自定義,成本極高)
"""

權限六表:RBAC - Role-Based Access Control

三表

六表

三大認證規則

認證組件

重點
1
2
3
4
5
"""
1)認證規則
2)如何自定義認證類
3)我們一般不需要自定義認證類,在settings中全局配置第三方 jwt 認證組件提供的認證類即可
"""
自定義認證類
1
2
3
4
5
6
7
"""
1)自定義認證類,繼承 BaseAuthentication 類
2)必須重寫 authenticate(self, request) 方法
沒有認證信息,返回None:匿名用戶(游客) => 匿名用戶request.user也有值,就是"匿名對象(Anonymous)"
有認證信息,且通過,返回(user, token):合法用戶 => user對象會存到request.user中
有認證信息,不通過,拋異常:非法用戶
"""

權限組件

重點
1
2
3
4
5
"""
1)權限規則
2)如何自定義權限類
3)我們一般在視圖類中局部配置drf提供的權限類,但是也會自定義權限類完成局部配置
"""
自定義權限類
1
2
3
4
5
6
7
8
9
10
11
12
"""
1)自定義權限類,繼承 BasePermission 類
2)必須重寫 has_permission(self, request, view): 方法
設置權限條件,條件通過,返回True:有權限
設置權限條件,條件失敗,返回False:有權限

3)drf提供的權限類:
AllowAny:匿名與合法用戶都可以
IsAuthenticated:必須登錄,只有合法用戶可以
IsAdminUser:必須是admin后台用戶
IsAuthenticatedOrReadOnly:匿名只讀,合法用戶無限制
"""

jwt 認證示意圖

1
2
3
4
5
""" jwt優勢
1)沒有數據庫寫操作,高效
2)服務器不存token,低耗
3)簽發校驗都是算法,集群
"""

jwt 認證算法: 簽發與校驗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
"""
1)jwt分三段式:頭.體.簽名 (head.payload.sgin)
2)頭和體是可逆加密,讓服務器可以反解出user對象;簽名是不可逆加密,保證整個token的安全性的
3)頭體簽名三部分,都是采用json格式的字符串,進行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4)頭中的內容是基本信息:公司信息、項目組信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
5)體中的內容是關鍵信息:用戶主鍵、用戶名、簽發時客戶端信息(設備號、地址)、過期時間
{
"user_id": 1,
...
}
6)簽名中的內容時安全信息:頭的加密結果 + 體的加密結果 + 服務器不對外公開的安全碼 進行md5加密
{
"head": "頭的加密字符串",
"payload": "體的加密字符串",
"secret_key": "安全碼"
}
"""
簽發:根據登錄請求提交來的 賬號 + 密碼 + 設備信息 簽發 token
1
2
3
4
5
6
7
"""
1)用基本信息存儲json字典,采用base64算法加密得到 頭字符串
2)用關鍵信息存儲json字典,采用base64算法加密得到 體字符串
3)用頭、體加密字符串再加安全碼信息存儲json字典,采用hash md5算法加密得到 簽名字符串

賬號密碼就能根據User表得到user對象,形成的三段字符串用 . 拼接成token返回給前台
"""
校驗:根據客戶端帶token的請求 反解出 user 對象
1
2
3
4
5
"""
1)將token按 . 拆分為三段字符串,第一段 頭加密字符串 一般不需要做任何處理
2)第二段 體加密字符串,要反解出用戶主鍵,通過主鍵從User表中就能得到登錄用戶,過期時間和設備信息都是安全信息,確保token沒過期,且時同一設備來的
3)再用 第一段 + 第二段 + 服務器安全碼 不可逆md5加密,與第三段 簽名字符串 進行碰撞校驗,通過后才能代表第二段校驗得到的user對象就是合法的登錄用戶
"""

drf項目的jwt認證開發流程(重點)

1
2
3
4
5
6
7
"""
1)用賬號密碼訪問登錄接口,登錄接口邏輯中調用 簽發token 算法,得到token,返回給客戶端,客戶端自己存到cookies中

2)校驗token的算法應該寫在認證類中(在認證類中調用),全局配置給認證組件,所有視圖類請求,都會進行認證校驗,所以請求帶了token,就會反解出user對象,在視圖類中用request.user就能訪問登錄的用戶

注:登錄接口需要做 認證 + 權限 兩個局部禁用
"""

drf-jwt框架基本使用

安裝
1
>: pip install djangorestframework-jwt
簽發token(登錄接口):視圖類已經寫好了,配置一下路由就行(urls.py)
1
2
3
4
5
6
7
# api/urls.py
urlpatterns = [
# ...
url('^login/$', ObtainJSONWebToken.as_view()),
]

# Postman請求:/api/login/,提供username和password即可
校驗token(認證組件):認證類已經寫好了,全局配置一下認證組件就行了(settings.py)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# drf-jwt的配置
import datetime
JWT_AUTH = {
# 配置過期時間
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=7),
}


# drf配置(把配置放在最下方)
REST_FRAMEWORK = {
# 自定義三大認證配置類們
'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework_jwt.authentication.JSONWebTokenAuthentication'],
# 'DEFAULT_PERMISSION_CLASSES': [],
# 'DEFAULT_THROTTLE_CLASSES': [],
}
設置 需要登錄才能訪問的接口測試
1
2
3
4
5
6
7
from rest_framework.permissions import IsAuthenticated
class UserCenterViewSet(GenericViewSet, mixins.RetrieveModelMixin):
# 設置必須登錄才能訪問的權限類
permission_classes = [IsAuthenticated, ]

queryset = models.User.objects.filter(is_active=True).all()
serializer_class = serializers.UserCenterSerializer
測試封路認證接口
1
2
3
4
5
"""
1)用 {"username": "你的用戶", "password": "你的密碼"} 訪問 /api/login/ 接口等到 token 字符串

2)在請求頭用 Authorization 攜帶 "jwt 登錄得到的token" 訪問 /api/user/center/1/ 接口訪問個人中心
"""
token 的刷新機制
drf-jwt 直接提供了刷新功能
1
2
3
4
5
"""
1)運用在像12306這樣極少數安全性要求高的網站
2)第一個token由登錄簽發
3)之后的所有正常邏輯,都需要發送兩次請求,第一次是刷新token的請求,第二次是正常邏輯的請求
"""
settings.py
1
2
3
4
5
6
7
8
9
10
11
import datetime

JWT_AUTH = {
# 配置過期時間
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=5),

# 是否可刷新
'JWT_ALLOW_REFRESH': True,
# 刷新過期時間
'JWT_REFRESH_EXPIRATION_DELTA': datetime.timedelta(days=7),
}
urls.py
1
2
3
4
5
from rest_framework_jwt.views import ObtainJSONWebToken, RefreshJSONWebToken
urlpatterns = [
url('^login/$', ObtainJSONWebToken.as_view()), # 登錄簽發token接口
url('^refresh/$', RefreshJSONWebToken.as_view()), # 刷新toekn接口
]
Postman
1
2
3
# 接口:/api/refresh/
# 方法:post
# 數據:{"token": "登錄簽發的token"}
認證組件的使用, 多方式登錄
urls.py
1
2
# 自定義登錄(重點):post請求 => 查操作(簽發token返回給前台) - 自定義路由映射
url('^user/login/$', views.LoginViewSet.as_view({'post': 'login'})),
views.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 重點:自定義login,完成多方式登錄
from rest_framework.viewsets import ViewSet
from rest_framework.response import Response
class LoginViewSet(ViewSet):
# 登錄接口,要取消所有的認證與權限規則,也就是要做局部禁用操作(空配置)
authentication_classes = []
permission_classes = []

# 需要和mixins結合使用,繼承GenericViewSet,不需要則繼承ViewSet
# 為什么繼承視圖集,不去繼承工具視圖或視圖基類,因為視圖集可以自定義路由映射:
# 可以做到get映射get,get映射list,還可以做到自定義(靈活)
def login(self, request, *args, **kwargs):
serializer = serializers.LoginSerializer(data=request.data, context={'request': request})
serializer.is_valid(raise_exception=True)
token = serializer.context.get('token')
return Response({"token": token})
serializers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from rest_framework_jwt.serializers import jwt_payload_handler, jwt_encode_handler

# 重點:自定義login,完成多方式登錄
class LoginSerializer(serializers.ModelSerializer):
# 登錄請求,走的是post方法,默認post方法完成的是create入庫校驗,所以唯一約束的字段,會進行數據庫唯一校驗,導致邏輯相悖
# 需要覆蓋系統字段,自定義校驗規則,就可以避免完成多余的不必要校驗,如唯一字段校驗
username = serializers.CharField()
class Meta:
model = models.User
# 結合前台登錄布局:采用賬號密碼登錄,或手機密碼登錄,布局一致,所以不管賬號還是手機號,都用username字段提交的
fields = ('username', 'password')

def validate(self, attrs):
# 在全局鈎子中,才能提供提供的所需數據,整體校驗得到user
# 再就可以調用簽發token算法(drf-jwt框架提供的),將user信息轉換為token
# 將token存放到context屬性中,傳給外鍵視圖類使用
user = self._get_user(attrs)
payload = jwt_payload_handler(user)
token = jwt_encode_handler(payload)
self.context['token'] = token
return attrs

# 多方式登錄
def _get_user(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
import re
if re.match(r'^1[3-9][0-9]{9}$', username):
# 手機登錄
user = models.User.objects.filter(mobile=username, is_active=True).first()
elif re.match(r'^.+@.+$', username):
# 郵箱登錄
user = models.User.objects.filter(email=username, is_active=True).first()
else:
# 賬號登錄
user = models.User.objects.filter(username=username, is_active=True).first()
if user and user.check_password(password):
return user

raise ValidationError({'user': 'user error'})
權限組件的使用: VIP 用戶權限
數據准備
1
2
3
4
5
"""
1)User表創建兩條數據
2)Group表創建一條數據,name叫vip
3)操作User和Group的關系表,讓1號用戶屬於1號vip組
"""
permissions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

from rest_framework.permissions import BasePermission

from django.contrib.auth.models import Group
class IsVipUser(BasePermission):
def has_permission(self, request, view):
if request.user and request.user.is_authenticated: # 必須是合法用戶
try:
vip_group = Group.objects.get(name='vip')
if vip_group in request.user.groups.all(): # 用戶可能不屬於任何分組
return True # 必須是vip分組用戶
except:
pass

return False
views.py
1
2
3
4
5
6
7

from .permissions import IsVipUser
class CarViewSet(ModelViewSet):
permission_classes = [IsVipUser]

queryset = models.Car.objects.all()
serializer_class = serializers.CarSerializer
serializers.py
1
2
3
4
class CarSerializer(serializers.ModelSerializer):
class Meta:
model = models.Car
fields = ('name', )
urls.py
1
router.register('cars', views.CarViewSet, 'car')

頻率組件

重點
1
2
3
4
5
"""
1)如何自定義頻率類
2)頻率校驗的規則
3)自定義頻率類是最常見的:短信接口一分鍾只能發生一條短信
"""
自定義頻率組件
1
2
3
4
5
6
7
8
9
"""
1)自定義類繼承SimpleRateThrottle
2)設置類實現scope,值就是一個字符串,與settings中的DEFAULT_THROTTLE_RATES進行對應
DEFAULT_THROTTLE_RATES就是設置scope綁定的類的頻率規則:1/min 就代表一分鍾只能訪問一次
3)重寫 get_cache_key(self, request, view) 方法,指定限制條件
不滿足限制條件,返回None:代表對這類請求不進行頻率限制
滿足限制條件,返回一個字符串(是動態的):代表對這類請求進行頻率限制
短信頻率限制類,返回 "throttling_%(mobile)s" % {"mobile": 實際請求來的電話}
"""
系統頻率類
1
2
#1)UserRateThrottle: 限制所有用戶訪問頻率
#2)AnonRateThrottle:只限制匿名用戶訪問頻率
頻率組件項目使用:請求方式頻率限制
throteles.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from rest_framework.throttling import SimpleRateThrottle
# 只限制查接口的頻率,不限制增刪改的頻率
class MethodRateThrottle(SimpleRateThrottle):
scope = 'method'
def get_cache_key(self, request, view):
# 只有對get請求進行頻率限制
if request.method.lower() not in ('get', 'head', 'option'):
return None

# 區別不同的訪問用戶,之間的限制是不沖突的
if request.user.is_authenticated:
ident = request.user.pk
else:
# get_ident是BaseThrottle提供的方法,會根據請求頭,區別匿名用戶,
# 保證不同客戶端的請求都是代表一個獨立的匿名用戶
ident = self.get_ident(request)
return self.cache_format % {'scope': self.scope, 'ident': ident}
settings.py
1
2
3
4
5
6
7
8
9
10
REST_FRAMEWORK = {
# ...
# 頻率規則配置
'DEFAULT_THROTTLE_RATES': {
# 只能設置 s,m,h,d,且只需要第一個字母匹配就ok,m = min = maaa 就代表分鍾
'user': '3/min', # 配合drf提供的 UserRateThrottle 使用,限制所有用戶訪問頻率
'anon': '3/min', # 配合drf提供的 AnonRateThrottle 使用,只限制匿名用戶訪問頻率
'method': '3/min',
},
}
views.py
1
2
3
4
5
6
7
8
from .permissions import IsVipUser
from .throttles import MethodRateThrottle
class CarViewSet(ModelViewSet):
permission_classes = [IsVipUser]
throttle_classes = [MethodRateThrottle]

queryset = models.Car.objects.all()
serializer_class = serializers.CarSerializer

異常組件項目使用:記錄異常信息到日志文件

exception.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
def exception_handler(exc, context):
# 只處理客戶端異常,不處理服務器異常,
# 如果是客戶端異常,response就是可以直接返回給前台的Response對象
response = drf_exception_handler(exc, context)

if response is None:
# 沒有處理的服務器異常,處理一下
# 其實給前台返回 服務器異常 幾個字就行了
# 那我們處理異常模塊的目的是 不管任何錯誤,都有必要進行日志記錄(線上項目只能通過記錄的日志查看出現過的錯誤)
response = Response({'detail': '%s' % exc})

# 需要結合日志模塊進行日志記錄的:項目中講
return response

settings.py

1
2
3
4
5
6
REST_FRAMEWORK = {
# ...
# 異常模塊
# 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', # 原來的,只處理客戶端異常
'EXCEPTION_HANDLER': 'api.exception.exception_handler',
}

群查接口相關組件

  • 搜索
  • 篩選
  • 排序
  • 分頁

目的:

  • 必須掌握六大接口:

    • 單查
    • 群查
    • 單增
    • 單局部改
    • 整體改
    • 單刪
  • 十大接口

    • 群增
    • 群局部改
    • 群整體改
    • 群刪


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM