1. 程式人生 > 實用技巧 >django-rest-framework-原始碼解析002-序列化/請求模組/響應模組/渲染模組/十大介面

django-rest-framework-原始碼解析002-序列化/請求模組/響應模組/渲染模組/十大介面

簡介

當我們使用django-rest-framework框架時, 專案必定是前後端分離的, 那麼前後端進行資料互動時,常見的資料型別就是xml和json(現在主流的是json), 這裡就需要我們django後臺對json和python字典(dict)進行頻繁的轉化, 當然我們可以使用json模組的loads和dumps方法去手動轉換, 但是這樣的操作步驟固定且頻繁, 於是可以將這個轉化=換步驟進行封裝, 讓我們實際開發時無需在資料轉換上花太多的時間.

rest_framework模組就提供了序列化器這個功能, 專門用來處理資料轉換, 將python格式的資料轉化為json被稱為序列化, 一般是用在返回給前端時使用. 將json資料轉化為python格式的資料被稱為反序列化, 一般是用在接收前端提交的資料時使用, 且我們一般都要對前臺提供過來的資料進行校驗, 序列化器中也提供了校驗相關的hook, 可以理解為提供了讓我們編寫自定義的校驗函式的位置

rest_framework提供了多個序列化器類供我們使用, 他們都在rest_framework.serializers中:

  Serializer:是DRF提供的序列化基本類, 需要自己編寫所有的欄位以及create和update方法,比較底層,抽象度較低,接近Django 的form表單類的層次。

  ModelSerializer: 更常用的序列化類, 無需自己編寫欄位以及create和update方法,它會根據指向的model,自動生成預設的 欄位和簡單的create及update方法。

  ListSerializer: 一般直接使用的比較少, 需要使用到的多數情況是要定製ListSerializer行為, 如當需要批量更新時, 就需要額外定義一個ListSerializer類來重寫update方法

  HyperlinkedModelSerializer:類似於ModelSerializer類,不同之處在於它使用超連結來表示關聯關係而不是主鍵。預設情況下序列化器將包含一個url欄位而不是主鍵欄位。

Serializer

定義Serializer類, 需要定義待序列化或反序列化的欄位, 重寫create和update方法

from rest_framework import serializer
from app.models import Comment

class CommentSerializer(serializers.Serializer):
    email = serializers.EmailField()
    content 
= serializers.CharField(max_length=200) created = serializers.DateTimeField() def create(self, validated_data): return Comment(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) return instance

ModelSerializer和ListSerializer

定義ModelSerializer類

from rest_framework.serializers import ModelSerializer, ListSerializer
from rest_framework.exceptions import ValidationError
from books.models import Book, Author, AuthorDetail, Publish

# 群改呼叫update時, 需要通過 ListSerializer 重寫update方法才能做更新操作
# instance為需要修改的物件列表, validated_data為對應的更新後的資料
class V2BookListSerializer(ListSerializer):
    def update(self, instance, validated_data):
        for index, obj in enumerate(instance):
            self.child.update(instance=obj, validated_data=validated_data[index])
        return instance


class V2BookSerializer(ModelSerializer):
    class Meta:
        model = Book
        # 序列化和反序列化的欄位都合併到了fields中
        fields = ['name', 'price', 'img', 'author_list', 'publish_name', 'publish', 'authors']
        # 所有欄位
        # fields = '__all__'
        # 除去這些欄位不展示
        # exclude = ['id', 'is_delete', 'create_time']
        # 自動展示深度
        # depth = 1
        # 通過write_only設定只參與反序列化, read_only只參與序列化
        extra_kwargs = {
            # 校驗規則
            'name': {
                # 哪些校驗規則
                'min_length': 1,
                'required': True,
                # 這些校驗規則對應的錯誤訊息
                'error_messages': {
                    'required': '為必填項'
                }
            },
            # 只參與反序列化
            'publish': {
                'write_only': True
            },
            'authors': {
                'write_only': True
            },
            # 只參與序列化
            'img': {
                'read_only': True
            },
            # 以下自定義欄位可以省略, 預設只參與序列化
            # 'author_list': {
            #     'read_only': True
            # },
            # 'publish_name': {
            #     'read_only': True
            # }
        }
        # 群改時需要使用ListSerializer並重寫update()方法
        list_serializer_class = V2BookListSerializer

    # 反序列化校驗規則
    def validate_name(self, value):
        """校驗書名"""
        # 長度
        if len(value) > 15:
            raise ValidationError('不能超過10位')
        return value

    # 全域性校驗
    def validate(self, attrs):
        """聯合校驗"""
        # context是檢視類傳給序列化類的引數
        print(self.context.get('request').method)
        # 校驗:同一出版社不能的書名不能重複
        book = Book.objects.filter(name=attrs.get('name'), publish=attrs.get('publish'), is_delete=False)
        if book:
            # 已存在同名書籍
            raise ValidationError({'status': 1, 'msg': '該出版社已存在同名書籍'})
        return attrs

定義一個Meta類, 在Meta下面可以定義如下屬性:

1. 設定model = Book, 將序列化類與模型類進行關聯

2. 將需要序列化和反序列化的欄位都放在fields列表中

  2.1 一些自定的序列化欄位如 'author_list' 和'publish_name', 這些欄位不是資料庫欄位, 只是用於序列化給前臺更好的展示, 需要在Book的model類中額外新增:

    @property
    def publish_name(self):
        return self.publish.name

    @property
    def author_list(self):
        # mobile=models.F('detail__mobile')用來將查詢的detail__mobile重新命名為mobile
        authors = self.authors.values('name', 'age', mobile=models.F('detail__mobile'))
        return authors

3. 設定額外關鍵字引數extra_kwargs,每個欄位都有很多屬性可以設定, 在extra_kwargs設定時格式為:

extra_kwargs = {
    '欄位名1': {
        '屬性名1': 屬性值1,
        '屬性名2': 屬性值2,
         ...
     },
     '欄位名2': {
        '屬性名1': 屬性值1,
        '屬性名2': 屬性值2,
        ...
    },
    ....
} 

  3.1read_only(預設為False)只讀欄位, 只能在序列化時轉換為json資料給前臺讀取, 而在反序列化時不能使用該欄位, 因為反序列化需要將前臺傳入的資料寫入資料庫中, 如果想設定只能寫入, 不能讀取, 那麼就要設定write_only(預設為False)為True. 這樣就能將只參與序列化或反序列化的欄位拆開. 同一個欄位不能同時設定read_only為True和write_only為True

  3.2 可以設定一些欄位的簡單驗證規則, 如最大長度(max_length), 最小長度(min_length), 是否必輸(requierd(預設為False))等

  3.3error_messages用於將前面定義的簡單驗證規與自定義的錯誤訊息進行對映

4. 還有一些其他屬性如

# fields列表為model中定義的所有欄位
#
fields = '__all__' # 除去下面這些欄位, 其他model欄位都需要 # exclude = ['id', 'is_delete', 'create_time'] # 自動展示深度, 當不展示深度時, 若圖書中有出版社資訊, 則出版社欄位暫時的是其對應的出版商ID, 如果設定了深度為1, 則出版社欄位預設展示為所有出版社序列化類中展示的欄位, 深度為2以此類推 # depth = 1

5. 在進行批量更新操作時,需要使用ListSerializer並重寫update()方法, 重寫update方法的邏輯其實就是遍歷model類物件, 再單獨呼叫ModelSerializer的update方法

定義反序列化的驗證

反序列化時需要校驗request中的data是否有效, 因此在序列化類中可以重寫 ''validate_欄位名(self, value)'' 和 ''validate(self, attrs)'' 方法, 分別用來做欄位的單獨校驗和欄位的聯合校驗, 如果校驗失敗, 則丟擲ValidationError('錯誤資訊xxx') 異常

在檢視中使用序列化類進行序列化和反序列化

在view中的method中使用序列化類, 這裡主要實現10種介面: 單查, 群查, 單增, 群增, 單刪, 群刪, 單改(整體欄位改), 單改(區域性欄位改), 群改(整體欄位改), 群改(區域性欄位改)

序列化(以get查詢為例)

from rest_framework.views import APIView
from rest_framework.response import Response
from books.serializers import BookSerializer
from books import models

class V2BookView(APIView):
    def get(self, request, *args, **kwargs):
        # 獲取pk
        pk = kwargs.get('pk')
        try:
            book = models.Book.objects.get(pk=pk, is_delete=False)
        except Exception:
            return MyResponse(status=1, msg='該書不存在')
        # 建立序列化物件, instance引數為獲取到的model物件book
        serializer = V2BookSerializer(instance=book)
        # serializer.data返回的是序列化後的json格式資料
        data = {
            'status': 0,
            'msg': 'post OK',
            'result': serializer.data
        }
        return Response(data=data)

主要步驟為:

1. 查詢model物件

2. 使用序列化類通過model物件建立序列化物件

3. 呼叫序列化物件的data屬性獲取到序列化後的結果

4. 將結果建立Response物件並返回

反序列化(以post為例)

def post(self, request, *args, **kwargs):
    # 建立反序列化物件, data引數為request.data, request.data能獲取到前臺提交的常見格式的資料
    serializer = V2BookSerializer(data=request.data)
    # 呼叫is_valid進行資料校驗
    serializer.is_valid(raise_exception=True)
    # 呼叫save建立model物件並儲存
    serializer.save()
    data = {
        'status': 0,
        'msg': 'post OK',
        'result': serializer.data
    }
    # 返回結果
    return Response(result=serializer.data)

主要步驟為:

1. 獲取前臺傳來的資料, 直接丟給序列化類建立序列化物件

2. 呼叫序列化物件的is_valid方法進行序列化類中的校驗, 若校驗失敗則會直接丟擲異常

3. 若校驗成功則呼叫save()將資料儲存至資料庫

4. 最後將結果建立Response物件並返回

分析序列化的原始碼

序列化的繼承關係MRO

檢視序列化的繼承關係MRO可以看到繼承順序, 也是我們看原始碼的優先順序

print(BookSerializer.__mro__)
# 列印結果 (
<class 'books.serializers.BookSerializer'>,
<class 'rest_framework.serializers.ModelSerializer'>,
<class 'rest_framework.serializers.Serializer'>,
<class 'rest_framework.serializers.BaseSerializer'>,
<class 'rest_framework.fields.Field'>,
<class 'object'>)

建立序列化類的物件

序列化與反序列化都建立了序列化類的物件, 不同的是序列化傳入的引數對為instance=book , 反序列化傳入的引數對為data=request.data, 原始碼中根據MRO順序可以找到序列化基類BaseSerializer的__init__方法中將instance引數和data引數分別存到了不同的屬性中

def __init__(self, instance=None, data=empty, **kwargs):
    self.instance = instance
    if data is not empty:
        self.initial_data = data
    self.partial = kwargs.pop('partial', False)
    self._context = kwargs.pop('context', {})
    kwargs.pop('many', None)
    super().__init__(**kwargs)

除了instance和data引數外, 還能接受
  1.many=True, (預設False)用來序列化或反序列化多個物件, 在群增, 群刪, 群查, 群改時使用

  2.partial=True, (預設False)用來序列化或反序列化類中fields中定義的部分欄位, 本質上是失效掉了其他欄位的required=True的屬性, 在單改(區域性欄位), 群改(區域性欄位)時使用

  3. context={}, (預設None)用來給序列化類傳遞額外所需的資訊, 如request物件等, 實現view和serializer的資訊傳遞

反序列化的驗證

序列化類的is_valid用來校驗資料是否正確, 根據MRO順序可以找到原始碼在BaseSerializer.is_valid中

它接收一個引數raise_exception(預設False), 若設定為True, 則在呼叫run_validation()校驗時如果出錯了, 那麼就直接繼續把異常丟擲, 如果設定為False, 則返回bool型別是否校驗通過(是否合法)

可以看到具體的校驗邏輯還在self.run_validation(中)

def is_valid(self, raise_exception=False):
    程式碼省略......
    if not hasattr(self, '_validated_data'):
        try:
            # 執行驗證邏輯
            self._validated_data = self.run_validation(self.initial_data)
        except ValidationError as exc:
            self._validated_data = {}
            self._errors = exc.detail
        else:
            self._errors = {}
    # 判斷是否直接丟擲異常
    if self._errors and raise_exception:
        raise ValidationError(self.errors)
    # 不丟擲異常的話返回Bool型別
    return not bool(self._errors)

繼續根據MRO順序可以找到Serializer.run_validation

def run_validation(self, data=empty):
    程式碼省略......
    # 校驗自定義校驗中的單個欄位
    value = self.to_internal_value(data)
    try:
        # 執行驗證器中的驗證
        self.run_validators(value)
        # 呼叫驗證方法
        value = self.validate(value)
        assert value is not None, '.validate() should return the validated data'
    except (ValidationError, DjangoValidationError) as exc:
        raise ValidationError(detail=as_serializer_error(exc))

    return value

1. 首先執行的是self.to_internal_value(data), 根據MRO找到serializers.to_internal_value, 可以看到通過反射獲取到序列化類中自定的單個欄位的驗證'validate_欄位名'的校驗方法validate_method, 然後執行該方法

def to_internal_value(self, data):
    程式碼省略......
    for field in fields:
        validate_method = getattr(self, 'validate_' + field.field_name, None)
        primitive_value = field.get_value(data)
        try:
            validated_value = field.run_validation(primitive_value)
            if validate_method is not None:
                validated_value = validate_method(validated_value)
        程式碼省略......
        else:
            set_value(ret, field.source_attrs, validated_value)

    if errors:
        raise ValidationError(errors)

    return ret

2. 獲取驗證器self.run_validators(value), 點入程式碼可以看到預設的驗證器default_validators = []是一個空列表, 如果需要自定義驗證器的話需要在序列化類中定義驗證器

3.呼叫驗證方法self.validate(value), 這裡根據MRO優先找到的就是我們序列化類中自定義的validate 方法

save()儲存操作

根據MRO找到BaseSerializer.save()方法, 若例項存在, 則呼叫self.update()更新, 不存在則呼叫self.create()建立, 繼續在MRO中從左往右找update和create方法

def save(self, **kwargs):
    程式碼省略.....
    # 存在則update
    if self.instance is not None:
        self.instance = self.update(self.instance, validated_data)
        assert self.instance is not None, (
            '`update()` did not return an object instance.'
        )
    # 不存在則create
    else:
        self.instance = self.create(validated_data)
        assert self.instance is not None, (
            '`create()` did not return an object instance.'
        )
    return self.instance

發現ModelSerializer中就重寫了create()和update()方法,create()中呼叫了instance = ModelClass._default_manager.create(**validated_data)建立model例項物件, update()中遍歷驗證後的欄位, 呼叫setattr方法設定model物件的屬性setattr(instance, attr, value), 最後呼叫instance.save()儲存修改

請求模組

上一章講過在RDF真正入口為rest_framework.view的dispatch(), dispatch的第一步就是request = self.initialize_request(request, *args, **kwargs)封裝請求, 具體程式碼為

def initialize_request(self, request, *args, **kwargs):
    """
    Returns the initial request object.
    """
    # 獲取需要解析的資料
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        parsers=self.get_parsers(),  # 獲取解析器, 在呼叫request.data時會使用到
        authenticators=self.get_authenticators(),  # 獲取許可權器, 在許可權認證時會使用到
        negotiator=self.get_content_negotiator(),
        parser_context=parser_context
    )

建立了一個DRF的Request物件, 檢視rest_framework.request.Request類的__init__方法, 可以看到將django原生的request物件封裝至了DRF的Request的_request屬性中, 在具有了原生request所有的功能的同時, 又添加了一些其他屬性, 如解析器, 認證器等等

    def __init__(self, request, parsers=None, authenticators=None,
                 negotiator=None, parser_context=None):
        assert isinstance(request, HttpRequest), (
            'The `request` argument must be an instance of '
            '`django.http.HttpRequest`, not `{}.{}`.'
            .format(request.__class__.__module__, request.__class__.__name__)
        )
        # 將原生request封裝至_request屬性中
        self._request = request, 
        self.parsers = parsers or ()
        self.authenticators = authenticators or ()
        self.negotiator = negotiator or self._default_negotiator()
        self.parser_context = parser_context
        self._data = Empty
        self._files = Empty
        self._full_data = Empty
        self._content_type = Empty
        self._stream = Empty

        if self.parser_context is None:
            self.parser_context = {}
        self.parser_context['request'] = self
        self.parser_context['encoding'] = request.encoding or settings.DEFAULT_CHARSET

        force_user = getattr(request, '_force_auth_user', None)
        force_token = getattr(request, '_force_auth_token', None)
        if force_user is not None or force_token is not None:
            forced_auth = ForcedAuthentication(force_user, force_token)
            self.authenticators = (forced_auth,)

之前原生request獲取url引數和form表單提交的引數是分別使用request.GET和request.POST, 現在我們就可分別使用DRF的request.query_params和request.data獲取, 並且request.data能獲取除form表單格式外的其他常見格式的資料, 如json等等

響應模組

DRF提供了一個Response類來支援HTTP內容響應, 該類是Django中 SimpleTemplateResponse 類的一個子類,我們並不一定非要使用DRF的 Response 類進行響應,也可以返回常規的 HttpResponse 或 者 StreamingHttpResponse 物件,但是使用 Response 類可以提供一個多種格式的更漂亮 的介面。響應模組的原始碼在rest_framework.response.py中

class Response(SimpleTemplateResponse):
    """
    An HttpResponse that allows its data to be rendered into
    arbitrary media types.
    """
    def __init__(self, data=None, status=None,
                 template_name=None, headers=None,
                 exception=False, content_type=None):
                 
        super().__init__(None, status=status)

        if isinstance(data, Serializer):
            msg = (
                'You passed a Serializer instance as data, but '
                'probably meant to pass serialized `.data` or '
                '`.error`. representation.'
            )
            raise AssertionError(msg)

        self.data = data
        self.template_name = template_name
        self.exception = exception
        self.content_type = content_type

        if headers:
            for name, value in headers.items():
                self[name] = value

檢視__init__()方法可以看到其接受的引數如下:

data : 一個字典, 包含想要響應的資料

status : 響應的狀態碼。預設是200。

template_name : 當選擇 HTMLRenderer 渲染器時,指定要使用的模板的名稱。

headers : 一個字典,包含響應的HTTP頭部資訊。

content_type : 響應的內容型別。通常由渲染器自行設定,由協商內容確定,但是在某 些情況下,你需要明確指定內容型別。

自定義響應類

直接呼叫Response時returnResponse(data=serializer.data),返回的結果只有查詢出來的資料, 而一般我們都會加上一些額外的與前臺約定的欄位, 如status或者error_msg等,所以我們可以自定義響應類

from rest_framework.response import Response

class MyResponse(Response):
    """繼承Response, 封裝自己的response"""
    def __init__(self, status=0, msg='ok', http_status=None, headers=None, exception=False, **kwargs):
        # 設定data
        data = {
            'status': status,
            'msg': msg,
            **kwargs
        }
        # 繼承父類
        super().__init__(data=data, status=http_status, template_name=None, headers=headers, exception=exception,
                         content_type=None)

1. 繼承rest_framework的Response類

2. 在init方法中新增自定義的引數, 如status, msg等, 其他鍵值對引數通過**kwargs獲取

3. 將自定義的引數和**kwargs(拆包)引數封裝到data字典中

4. 呼叫父類的__init__方法, 將data字典傳入

渲染模組(DRF常用模組的配置方法)

在rest_framework的dispatch方法中, 最後呼叫了self.response = self.finalize_response(request, response, *args, **kwargs)生成最終的response, 在finalize_response中設定了response的渲染器response.accepted_renderer = request.accepted_renderer, 渲染器最終是通過get_renderers返回的, 其實返回的就是一個個渲染類renderer_classes所例項化的物件組成的列表

    def get_renderers(self):
        """
        Instantiates and returns the list of renderers that this view can use.
        """
        return [renderer() for renderer in self.renderer_classes]

DRF的rest_framework.views.py中提供了很多類似get_renderers的方法, 如獲取直譯器get_parsers, 獲取認證器get_authenticators, 獲取許可權器get_permissions等等

且這些get_xxx方法都是返回一個對應類的列表, 且這些類基本都可以手動全域性或區域性配置, 若沒有手動配置則預設取APIView預設的設定

class APIView(View):
    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

這些預設配置都在rest_framework.settings.py中, 預設的配置如下

DEFAULTS = {
    # Base API policies
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
        'rest_framework.renderers.BrowsableAPIRenderer',
    ],
    'DEFAULT_PARSER_CLASSES': [
        'rest_framework.parsers.JSONParser',
        'rest_framework.parsers.FormParser',
        'rest_framework.parsers.MultiPartParser'
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.BasicAuthentication'
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
    'DEFAULT_THROTTLE_CLASSES': [],
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
    'DEFAULT_VERSIONING_CLASS': None,

    # Generic view behavior
    'DEFAULT_PAGINATION_CLASS': None,
    'DEFAULT_FILTER_BACKENDS': [],

    # Schema
    'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',

    # Throttling
    'DEFAULT_THROTTLE_RATES': {
        'user': None,
        'anon': None,
    },
    'NUM_PROXIES': None,

    # Pagination
    'PAGE_SIZE': None,

    # Filtering
    'SEARCH_PARAM': 'search',
    'ORDERING_PARAM': 'ordering',

    # Versioning
    'DEFAULT_VERSION': None,
    'ALLOWED_VERSIONS': None,
    'VERSION_PARAM': 'version',

    # Authentication
    'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
    'UNAUTHENTICATED_TOKEN': None,

    # View configuration
    'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
    'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',

    # Exception handling
    'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
    'NON_FIELD_ERRORS_KEY': 'non_field_errors',

    # Testing
    'TEST_REQUEST_RENDERER_CLASSES': [
        'rest_framework.renderers.MultiPartRenderer',
        'rest_framework.renderers.JSONRenderer'
    ],
    'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',

    # Hyperlink settings
    'URL_FORMAT_OVERRIDE': 'format',
    'FORMAT_SUFFIX_KWARG': 'format',
    'URL_FIELD_NAME': 'url',

    # Input and output formats
    'DATE_FORMAT': ISO_8601,
    'DATE_INPUT_FORMATS': [ISO_8601],

    'DATETIME_FORMAT': ISO_8601,
    'DATETIME_INPUT_FORMATS': [ISO_8601],

    'TIME_FORMAT': ISO_8601,
    'TIME_INPUT_FORMATS': [ISO_8601],

    # Encoding
    'UNICODE_JSON': True,
    'COMPACT_JSON': True,
    'STRICT_JSON': True,
    'COERCE_DECIMAL_TO_STRING': True,
    'UPLOADED_FILES_USE_URL': True,

    # Browseable API
    'HTML_SELECT_CUTOFF': 1000,
    'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",

    # Schemas
    'SCHEMA_COERCE_PATH_PK': True,
    'SCHEMA_COERCE_METHOD_NAMES': {
        'retrieve': 'read',
        'destroy': 'delete'
    },
}

我們可以在django專案的settings.py中全域性配置這些屬性類, 如配置渲染器

REST_FRAMEWORK = {
    # 渲染器類
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',  # 渲染json
        'rest_framework.renderers.BrowsableAPIRenderer',  # 渲染帶有API返回結果的瀏覽器介面
    ],
    # 異常處理類
    'EXCEPTION_HANDLER': 'utils.exceptions.exception_handler'
}

當然我們也可以進行區域性配置, 即在具體的檢視類中配置, 如

class V2BookView(APIView):
    # 渲染器類
    renderer_classes = [
        'rest_framework.renderers.JSONRenderer',  # 渲染json
        'rest_framework.renderers.BrowsableAPIRenderer',  # 渲染帶有API返回結果的瀏覽器介面
    ]

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

10種介面的簡單實現

在一個檢視中可以實現10種介面對圖書的增刪改查介面, 分別為: 單增, 群增, 單刪, 群刪, 單區域性改, 單整體改, 群區域性改, 群整體改, 單查, 群查

路由配置urls.py為:

from django.urls import path
from books import views

urlpatterns = [
    path('v2/', views.V2BookView.as_view()),
    path('v2/<int:pk>/', views.V2BookView.as_view()),
]

檢視類views.py為:

class V2BookView(APIView):

    def get(self, request, *args, **kwargs):
        # 獲取pk
        pk = kwargs.get('pk')

        if pk:
            # 單查
            try:
                book = models.Book.objects.get(pk=pk, is_delete=False)
            except Exception:
                return MyResponse(status=1, msg='該書不存在')

            serializer = V2BookSerializer(book)
        else:
            # 群查
            books = models.Book.objects.filter(is_delete=False).all()
            serializer = V2BookSerializer(books, many=True)
        return MyResponse(result=serializer.data)

    # 單增: 傳的資料是與model對應的字典
    # 群增: 傳的是 包含多個model對應字典的 列表或者元組
    def post(self, request, *args, **kwargs):
        if isinstance(request.data, dict):
            # 單增
            serializer = V2BookSerializer(data=request.data)
        elif isinstance(request.data, list):
            # 群增
            serializer = V2BookSerializer(data=request.data, many=True)
        else:
            return MyResponse(status=1, msg='只能為list或列表')
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return MyResponse(result=serializer.data)

    # 單刪 pk
    # 群刪 pks
    def delete(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        if pk:
            # 單刪
            pks = [pk]
        else:
            # 群刪
            pks = request.data.get('pks')
        # update返回受影響的行
        if not models.Book.objects.filter(pk__in=pks, is_delete=False).update(is_delete=True):
            return MyResponse(status=1, msg='圖書不存在或已被刪除')
        return MyResponse()

    # 單整體改 /pk/ dict
    # 群整體改 list
    # 反序列化的目的是: 將眾多資料的校驗交給序列化類來處理, 讓序列化類扮演反序列化角色
    def put(self, request, *args, **kwargs):
        pk = kwargs.get('pk')
        request_data = request.data
        # 將單整體改和群整體改都轉化為群整體改
        if pk and isinstance(request_data, dict):
            # 單整體改
            pks = [pk, ]
            request_data = [request_data, ]
        elif not pk and isinstance(request_data, list):
            # 群改, 資料格式: [{"pk":1, "name": "python999"}, {"pk":2, "publish": 4}, {"pk":3, "price": 100}]
            pks = []
            for item in request_data:
                pk = item.get('pk', None)
                if pk:
                    pks.append(pk)
                else:
                    request_data.remove(item)
        else:
            return MyResponse(status=1, msg='格式必須為list或dict')
        # 處理pks, 生成序列化引數instance(books)和data(data_list)
        if pks:
            books = []
            data_list = []
            for index, pk in enumerate(pks):
                try:
                    books.append(models.Book.objects.get(pk=pk, is_delete=False))
                    data_list.append(request_data[index])
                except Exception as e:
                    continue
            if books:
                serializer = V2BookSerializer(instance=books, data=data_list, many=True,
                                              partial=kwargs.get('partial', False), context={"request": request})
                serializer.is_valid(raise_exception=True)
                serializer.save()
                return MyResponse(result=serializer.data)
        return MyResponse(status=1, msg='圖書都不存在')

    # 單區域性改 /pk/
    # 群區域性改
    # 區域性改和整體改邏輯一樣, 只是在序列化時多了一個partial引數
    def patch(self, request, *args, **kwargs):
        return self.put(request, partial=True, *args, **kwargs)