内容协商

内容协商是基于客户端或服务器偏好选择多种可能的表示之一以返回客户端的过程。

确定接受的渲染器

REST framework 根据可用的渲染器,每个渲染器的优先级以及客户端的 Accept: header,使用简单的内容协商风格来确定应将哪些媒体类型返回给客户端。所使用的风格部分由客户端驱动,部分由服务器驱动。

  1. 更具体的媒体类型优先于较不特定的媒体类型。
  2. 如果多种媒体类型具有相同的特性,则优先根据为给定视图配置的渲染器排序。

例如,给出以下 Accept header:

application/json; indent=4, application/json, application/yaml, text/html, */*

每种给定媒体类型的优先级为:

  • application/json; indent=4
  • application/json, application/yamltext/html
  • */*

如果所请求的视图仅用 YAMLHTML 的渲染器配置,则 REST framework 将选择 renderer_classes 列表或 DEFAULT_RENDERER_CLASSES 设置中首先列出的渲染器。


注意: 确定偏好时,REST framework 不会考虑 "q" 值。使用 "q" 值会对缓存产生负面影响,作者认为这是对内容协商的一种不必要和过于复杂的方法。


自定义内容协商

你不太可能希望为 REST framework 提供自定义内容协商方案,但如果需要,你可以这样做。要实现自定义内容协商方案,请覆盖 BaseContentNegotiation

REST framework 的内容协商类处理选择适当的请求解析器和适当的响应渲染器,因此你应该实现 .select_parser(request, parsers).select_renderer(request, renderers, format_suffix) 方法。

select_parser() 方法应从可用解析器列表中返回一个解析器实例,如果没有任何解析器可以处理传入请求,则返回 None

select_renderer() 方法应该返回(渲染器实例,媒体类型)的二元组,或引发 NotAcceptable 异常。

举个栗子

以下是自定义内容协商类,它在选择适当的解析器或渲染器时会忽略客户端请求。

from rest_framework.negotiation import BaseContentNegotiation

class IgnoreClientContentNegotiation(BaseContentNegotiation):
    def select_parser(self, request, parsers):
        """
        Select the first parser in the `.parser_classes` list.
        """
        return parsers[0]

    def select_renderer(self, request, renderers, format_suffix):
        """
        Select the first renderer in the `.renderer_classes` list.
        """
        return (renderers[0], renderers[0].media_type)

设置内容协商

默认内容协商类可以使用 DEFAULT_CONTENT_NEGOTIATION_CLASS setting 全局设置。例如,以下设置将使用我们的示例 IgnoreClientContentNegotiation 类。

REST_FRAMEWORK = {
    'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'myapp.negotiation.IgnoreClientContentNegotiation',
}

你还可以使用 APIView 基于类的视图设置用于单个视图或视图集的内容协商。

from myapp.negotiation import IgnoreClientContentNegotiation
from rest_framework.response import Response
from rest_framework.views import APIView

class NoNegotiationView(APIView):
    """
    An example view that does not perform content negotiation.
    """
    content_negotiation_class = IgnoreClientContentNegotiation

    def get(self, request, format=None):
        return Response({
            'accepted media type': request.accepted_renderer.media_type
        })

元数据

REST framework 包含一个可配置的机制,用于确定 API 如何响应 OPTIONS 请求。这使你可以返回 API schema 或其他资源信息。

对于 HTTP OPTIONS 请求应该返回哪种风格的响应,目前还没有任何被广泛采用的约定,所以我们提供了一种专门的风格来返回一些有用的信息。

下面是一个示例响应,演示默认返回的信息。

HTTP 200 OK
Allow: GET, POST, HEAD, OPTIONS
Content-Type: application/json

{
    "name": "To Do List",
    "description": "List existing 'To Do' items, or create a new item.",
    "renders": [
        "application/json",
        "text/html"
    ],
    "parses": [
        "application/json",
        "application/x-www-form-urlencoded",
        "multipart/form-data"
    ],
    "actions": {
        "POST": {
            "note": {
                "type": "string",
                "required": false,
                "read_only": false,
                "label": "title",
                "max_length": 100
            }
        }
    }
}

设置元数据 scheme

你可以使用 'DEFAULT_METADATA_CLASS' settings key 全局设置元数据类:

REST_FRAMEWORK = {
    'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata'
}

或者你可以单独设置一个视图的元数据类:

class APIRoot(APIView):
    metadata_class = APIRootMetadata

    def get(self, request, format=None):
        return Response({
            ...
        })

REST framework 包只包含一个名为 SimpleMetadata 的元数据类实现。如果你想使用另一种风格,你需要实现一个自定义的元数据类。

创建 schema 端点

如果你对创建通过常规 GET 请求访问的 schema 端点有特定要求,则可以考虑重新使用元数据 API 来实现此目的。

例如,可以在视图集上使用以下附加路由来提供可链接的 schema 端点。

@list_route(methods=['GET'])
def schema(self, request):
    meta = self.metadata_class()
    data = meta.determine_metadata(request, self)
    return Response(data)

有几个原因可以选择采用这种方法,包括 OPTIONS 响应不能缓存。


自定义元数据类

如果你想提供一个自定义的元数据类,你应该继承 BaseMetadata 并且实现 determine_metadata(self, request, view) 方法。

你可能想要做的事情包括返回 schema 信息,使用 JSON schema 等格式,或将调试信息返回给管理员用户。

举个栗子

以下类可用于限定返回到 OPTIONS 请求的信息。

class MinimalMetadata(BaseMetadata):
    """
    Don't include field and other information for `OPTIONS` requests.
    Just return the name and description.
    """
    def determine_metadata(self, request, view):
        return {
            'name': view.get_view_name(),
            'description': view.get_view_description()
        }

然后配置你的设置以使用此自定义类:

REST_FRAMEWORK = {
    'DEFAULT_METADATA_CLASS': 'myproject.apps.core.MinimalMetadata'
}
本文作者:博主:
文章标题:Django DRF(十九)-内容协商和元数据
本文地址:https://wouldmissyou.com/archives/69/     
版权说明:若无注明,本文皆为“多点部落”原创,转载请保留文章出处。
最后修改:2021 年 02 月 04 日 05 : 07 PM
如果觉得我的文章对你有用,请随意赞赏