restdoctor


Namerestdoctor JSON
Version 0.0.63 PyPI version JSON
download
home_page
SummaryBestDoctor's batteries for REST services.
upload_time2024-03-12 19:20:25
maintainer
docs_urlNone
authorBestDoctor
requires_python
licenseMIT
keywords statistics
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # RestDoctor

BestDoctor's batteries for REST services.

## Для чего нужен RestDoctor

У нас в BestDoctor есть [свой API Guide](https://github.com/best-doctor/guides/blob/master/guides/api_guide.md), в котором написано, как API должно быть
построено. А еще у нас есть Django и довольно логично использовать Django Rest Framework. Он достаточно гибкий,
однако в некоторых местах мы хотим получить больше контроля и соблюдения своих правил.

Поэтому мы написали свою надстройку над DRF, которая имеет
1. Полную изоляцию между версиями API
1. Версионирование через заголовок `Accept`
1. Декларативную настройку сериализаторов и классов разрешений для `View` и `ViewSet`
1. Прокачанную генерацию схемы

## Быстрый старт

Добавляем пакет `restdoctor` в зависимости или ставим через pip, добавляем `restdoctor` в `INSTALLED_APPS`.

После этого можно использовать ViewSet'ы из restdoctor, заменив импорты `rest_framework` на
`restdoctor.rest_framework`.

Пример на основе tutorial DRF. Было:
```python
from django.contrib.auth.models import User
from rest_framework import viewsets
from rest_framework import permissions
from tutorial.quickstart.serializers import UserSerializer, UserListSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class = UserSerializer
    permission_classes = [permissions.IsAuthenticated]

    def get_serializer_class(self):
        if self.action == 'list':
            return UserListSerializer
        return self.serializer_class
```

Стало:
```python
from django.contrib.auth.models import User
from restdoctor.rest_framework import viewsets
from rest_framework import permissions
from tutorial.quickstart.serializers import UserSerializer, UserListSerializer


class UserViewSet(viewsets.ModelViewSet):
    """
    API endpoint that allows users to be viewed or edited.
    """
    queryset = User.objects.all().order_by('-date_joined')
    serializer_class_map = {
        'default': UserSerializer,
        'list': {
            'response': UserListSerializer,
        },
    }
    permission_classes_map = {
        'default': [permissions.IsAuthenticated]
    }
```

### Дальнейшая настройка

Для разбора формата из заголовка Accept необходимо добавить middleware в конфигурацию приложения:

```python

ROOT_URLCONF = ...

MIDDLEWARE = [
    ...,
    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',
]

API_PREFIXES = ('/api',)
API_FORMATS = ('full', 'compact')
```

После этого для префиксов, указанных в `API_PREFIXES`? будет производиться разбор заголовка Accept. Во время обработки
запроса во View или ViewSet в request добавится атрибут `api_params`.


## Установка и конфигурирование

Добавляем настройки в Settings:

```python
ROOT_URLCONF = 'app.urls'

INSTALLED_APPS = [
    ...,
    'rest_framework',
    'restdoctor',
]

MIDDLEWARE = [
    ...,
    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',
]

API_FALLBACK_VERSION = 'fallback'
API_FALLBACK_FOR_APPLICATION_JSON_ONLY = False
API_DEFAULT_VERSION = 'v1'
API_DEFAULT_FORMAT = 'full'
API_PREFIXES = ('/api',)
API_FORMATS = ('full', 'compact')
API_RESOURCE_DISCRIMINATIVE_PARAM = 'view_type'
API_RESOURCE_DEFAULT = 'common'
API_RESOURCE_SET_PARAM = False
API_RESOURCE_SET_PARAM_FOR_DEFAULT = False
API_V1_URLCONF = 'api.v1_urls'
API_VERSIONS = {
    'fallback': ROOT_URLCONF,
    'v1': API_V1_URLCONF,
}
```

## Использование в проекте

Максимально наследуемся от restdoctor там, где есть выбор между `rest_framework`
и `restdoctor.rest_framework`.

```python
from restdoctor.rest_framework.serializers import ModelSerializer
from restdoctor.rest_framework.views import GenericAPIView, RetrieveAPIView, ListAPIView
from restdoctor.rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
```

### Версионирование

RestDoctor маршрутизирует вызовы по заголовку `Accept` на изолированный `UrlConf`.
1. Во-первых, это означает, что без корректного заголовка `Accept` ручки API могут быть недоступны и отдавать 404.
1. А во-вторых, в приложении может быть несколько различных версий API, которые не будут "видеть" друг друга.

Общий формат заголовка следующий:

```
application/vnd.{vendor}.{version}[-{resource}][.{format}][+json]
```

Где vendor задается на уровне приложения параметром `API_VENDOR_STRING`, список версий и сопоставление их UrlConf'ам
определяется параметром `API_VERSIONS`.

Саму маршрутизацию для входящего запроса проводит middleware `ApiSelectorMiddleware`, которую надо включить в
настройках.

```python
ROOT_URLCONF = 'app.urls'

MIDDLEWARE = [
    ...,
    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',
]

API_V1_URLCONF = 'api.v1.urls'

API_VENDOR_STRING = 'RestDoctor'

API_FALLBACK_VERSION = 'fallback'
API_DEFAULT_VERSION = 'v1'
API_VERSIONS = {
    API_FALLBACK_VERSION: ROOT_URLCONF,
    API_DEFAULT_VERSION: API_V1_URLCONF,
}
```

Маршрутизация по `API_VERSIONS` срабатывает, если Accept начинается с `application/vnd.{vendor}`,
если не указана версия, то берется `API_DEFAULT_VERSION`. Если Accept не содержит корректной vendor-строки, то
выбирается `API_FALLBACK_VERSION`.

Версия может быть указана в формате `{version}-{resource}`, тогда `ResourceViewSet` будет использовать эту информацию
для выбора `ViewSet`.

Кроме того, может быть дополнительно указан `{format}` для выбора формата ответа, по факту выбор сериализатора в
`SerializerClassMapApiView`.

Также у формата тоже могут быть версии. Если `{format}` в `API_FORMATS` задан `version:{2,3,5}` в запросе Accept фигурирует только номер версии `version:5`.
Выбор сериализатора происходит от большого к меньшему.

В случае успешного определения версии и параметров API из заголовка Accept, middleware выбирает для дальнейшей обработки
запроса конкретный UrlConf и добавляет к объекту `request` атрибут `api_params`.


### Формат ответа API

Нашим API Guide задан [формат ответа](https://github.com/best-doctor/guides/blob/master/guides/api_guide.md#%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%B8-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B0), за который отвечает RestDoctorRenderer
(`restdoctor.rest_framework.renderers.RestDoctorRenderer`). Включается он только для запросов, содержащих атрибут
`api_params`, и работает этот механизм через `content_negotiation_class` заданный в базовом для View и ViewSet
миксине NegotiatedMixin (`restdoctor.rest_framework.mixins.NegotiatedMixin`).


### SerializerClassMapApiView

DRF позволяет достаточно компактно определять `ModelSeraizlier` + `ModelViewSet`, однако оставляет достаточно много
свободы в одних местах, не предоставляя ее в других.

Например, можно переопределить `serializer_class` в классе ViewSet'а, либо определять его динамически через
`ViewSet.get_serializer_class`, однако нельзя переопределять сериализаторы отдельно для запроса, отдельно для ответа.
Т.е. нельзя задать отдельный сериализатор для `update`, используя сериализатор для `retrieve` для возврата измененной
сущности.

`SerializerClassMapApiView` дает возможность декларативно задавать сериализаторы для различных action, отдельно для
request и response.

Поддержка на уровне базовых миксинов для ViewSet'ов позволяет прозрачно заменить, например,
`ReadOnlyModelViewSet` в импортах с `rest_framework.viewsets` на `restdoctor.rest_framework.viewsets`.


#### serializer_class_map

`SerializerClassMapApiView` позволяет задавать сериализаторы для разных action'ов и форматов ответа отдельно для
request и response фазы обработки запроса.

```python
from restdoctor.rest_framework.viewsets import ModelViewSet

from app.api.serializers import (
    MyDefaultSerializer, MyCompactSerializer, MyAntoherSerializer,
    MyCreateSerializer, MyUpdateSerializer,
)


class MyApiView(SerializerClassMapApiView):
    """Пример работы с serializer_class_map."""

    serializer_class_map = {
        'default': MyDefaultSerializer,
        'default.compact': MyCompactSerializer,
         'create': {
            'request': MyCreateSerializer,
         },
         'update': {
            'request': MyUpdateSerializer,
            'request.version:3': MyVersion3UpdateSerializer,
            'request.version:2': MyVersionUpdateSerializer,
         },
         'list': {
            'response.another_format': MyAnotherSerializer,
            'meta': MyMetaSerializer,
        }
    }

```

В этом примере мы задаем `MyDefaultSerializer` как базовый для ViewSet. Но для `create` и `update` action
переопределяем сериализаторы для обработки request'а.

Кроме того, мы определили сериализатор для `compact` формата и отдельно для action `list` и `update` форматы `another_format`, `version:2`, `version:3`.
Формат с версиями работает по принципу поиска точной или меньшей версии сериализатора.
Отдельно добавлена дополнительное формирование meta информации.


#### permission_classes_map

По аналогии с `serializer_class_map` для декларативного задания разных наборов `permission_classes` на разных action'ах
можно определить `permission_classes_map`:

```python
from restdoctor.rest_framework.viewsets import ModelViewSet

from app.api.permissions import PermissionA, PermissionB


class MyViewSet(ModelViewSet):
    permission_classes_map = {
        'default': [PermissionA],
        'retrieve': [PermissionB],
    }
```

#### Замечание про action

В DRF action появляется во время регистрации `ViewSet` с помощью `Router`. При этом для разделения list/detail ресурсов
используются разные наборы `action_maps`:

```
list_action_map = {'get': 'list', 'post': 'create'}
detail_action_map = {'get': 'retrieve', 'put': 'update'}
```

Django-механизмы роутинга создают функцию-обработчик, которая инстанцирует View/ViewSet с нужными параметрами.
При этом один и тот же класс `ViewSet` будет присутствовать в UrlConf в двух экземплярах с разными `action_map`.
Во время обработки запроса по HTTP методу будет определен action и вызван соответствующий метод экземпляра `ViewSet`.
И во время обработки запроса у `ViewSet` всегда задан `self.action`.

Однако это не так для `View`, поэтому в `SerializerClassMapApiView` добавлен атрибут `action`, на который завязывается
поиск сериализатора в `serializer_class_map`.


### Миксины и ModelViewSet

Миксины задают базовые операции `ModelViewSet` для `'list'`, `'retrieve'`, `'create'`, `'update'`, `'destroy'` action'ов.


От DRF-версий они отличаются в основном тем, что используют `SerializerClassMapApiView.get_request_serializer` и
`SerializerClassMapApiView.get_response_serializer` вместо `View.get_serializer`.


#### RetrieveModelMixin

Определяет обработчик для `retrieve` action. Определяет метод `get_item`:

```python
class RetrieveModelMixin(BaseListModelMixin):
    def retrieve(self, request: Request, *args: typing.Any, **kwargs: typing.Any) -> Response:
        item = self.get_item(request_serializer)
        ...


    def get_item(self, request_serializer: BaseSerializer) -> typing.Union[typing.Dict, QuerySet]:
        return self.get_object()
```

Т.е. можно использовать `RetrieveModelMixin` для работы с любыми словарями, а не только моделями, надо только
переопределить `ViewSet.get_item`.

#### ListModelMixin

Определяет обработчик для `list` action. Определяет метод `get_collection`:

```python
class ListModelMixin(BaseListModelMixin):
    def list(self, request: Request, *args: typing.Any, **kwargs: typing.Any) -> Response:
        queryset = self.get_collection()
        ...

    def get_collection(self, request_serializer: BaseSerializer) -> typing.Union[typing.List, QuerySet]:
        return self.filter_queryset(self.get_queryset())
```

Т.е. можно использовать `ListModelMixin` для работы с любыми коллекциями, а не только моделями, надо только
переопределить `ViewSet.get_collection`. При этом, если задан сериализатор для `list`, то он будет использован
для query-параметров, что позволит получить эти параметры и использовать дополнительно к filterset'у.


Определяет формирование дополнительной `meta` информации. Определяет метод `get_meta_data`:

```python
class ListModelMixin(BaseListModelMixin):
    def get_meta_data(self) -> typing.Dict[str, typing.Any]:
        return {'test': typing.Any}
```
Т.е. можно использовать `ListModelMixin` для формирования дополнительной информации в поле `meta`.
Для корректной работы нужно определить сериализатор для `meta`.

```python
    serializer_class_map = {
         'default': MyDefaultSerializer,
         'list': {
            'meta': MyMetaSerializer,
        }
    }
```

Задан обработчик `perform_list` для выбранных данных в пагинации.
Для работы нужно переопределить метод `perform_list`.

```python
class ListModelMixin(BaseListModelMixin):
    def perform_list(self, data: typing.Union[typing.List, QuerySet]) -> None:
        Sender(data)
```
#### ListModelViewSet

Задан только обработчик для `list` action.


#### ReadOnlyModelViewSet

Заданы обработчики для `list` и `retrieve` action'ов.


#### CreateUpdateReadModelViewSet

Заданы обработчики для `list`, `retrieve`, `create`, `update` action'ов.


#### ModelViewSet

Полный набор action'ов: `list`, `retrieve`, `create`, `update`, `destroy`.


### PydanticSerializer

Для использования сериализатор на основе [pydantic](https://pydantic-docs.helpmanual.io/) необходимо наследовать
сериализатор от `PydanticSerializer`, указать в `Meta` `pydantic_model` и `pydantic_use_aliases` (при необходимости).

Параметр `pydantic_use_aliases` позволяет использовать [алиасы pydantic моделей](https://pydantic-docs.helpmanual.io/usage/model_config/#alias-precedence) для сериализации.
```python

class PydanticSerializer(PydanticSerializer):
    class Meta:
        pydantic_model = PydanticModel
        pydantic_use_aliases = True
```

### Генерация схемы
Поддерживается генерация схемы openapi версий 3.0.2 и 3.1.0.
Схема по умолчанию задается параметром `API_DEFAULT_OPENAPI_VERSION` и равна `3.0.2`.

Пример генерации схемы (версия из settings):
```shell
python3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator > your_app/static/openapi.schema
```

Пример генерации схемы версии openapi 3.0.2:
```shell
python3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator30 > your_app/static/openapi-30.schema
```

Пример генерации смхемы версии openapi 3.1.0:
```shell
python3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator31 > your_app/static/openapi-31.schema
```

#### Опции генерации
##### API_STRICT_SCHEMA_VALIDATION
- делает обязательным использование описаний у полей (`help_text`, `verbose_name` у модели)
- проверяет на совпадение аннотацию поля и атрибут `allow_null`
- проверяет на совпадение аннотацию поля и атрибут `many`

Если какая-то проверка не проходит, генерация схемы завершается ошибкой.

##### API_SCHEMA_PRIORITIZE_SERIALIZER_PARAMETERS
При включении этой опции для схемы будут выбираться поля сериализатора, даже если они дублируют существующие.

##### API_SCHEMA_FILTER_MAP_PATH
Путь до кастомной схемы обработки фильтров для `DjangoFilterBackend`, по умолчанию - `restdoctor.rest_framework.schema.filters.FILTER_MAP`.


### pre-commit

Этот репозиторий использует git-хуки настроенные с помощью [pre-commit](https://pre-commit.com)
поэтому если планируется дальнейшее внесение изменений в репозиторий необходимо инициализировать
pre-commit с помощью следующей команды:

```shell script
make install-hooks
```



            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "restdoctor",
    "maintainer": "",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "",
    "keywords": "statistics",
    "author": "BestDoctor",
    "author_email": "s.butkin@bestdoctor.ru",
    "download_url": "https://files.pythonhosted.org/packages/78/49/0435453c9a4d91c70214404ae1be7b02c0535a03853ca34585417d48619b/restdoctor-0.0.63.tar.gz",
    "platform": null,
    "description": "# RestDoctor\n\nBestDoctor's batteries for REST services.\n\n## \u0414\u043b\u044f \u0447\u0435\u0433\u043e \u043d\u0443\u0436\u0435\u043d RestDoctor\n\n\u0423 \u043d\u0430\u0441 \u0432 BestDoctor \u0435\u0441\u0442\u044c [\u0441\u0432\u043e\u0439 API Guide](https://github.com/best-doctor/guides/blob/master/guides/api_guide.md), \u0432 \u043a\u043e\u0442\u043e\u0440\u043e\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e, \u043a\u0430\u043a API \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c\n\u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d\u043e. \u0410 \u0435\u0449\u0435 \u0443 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c Django \u0438 \u0434\u043e\u0432\u043e\u043b\u044c\u043d\u043e \u043b\u043e\u0433\u0438\u0447\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Django Rest Framework. \u041e\u043d \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0433\u0438\u0431\u043a\u0438\u0439,\n\u043e\u0434\u043d\u0430\u043a\u043e \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043c\u0435\u0441\u0442\u0430\u0445 \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u0438 \u0441\u043e\u0431\u043b\u044e\u0434\u0435\u043d\u0438\u044f \u0441\u0432\u043e\u0438\u0445 \u043f\u0440\u0430\u0432\u0438\u043b.\n\n\u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u043c\u044b \u043d\u0430\u043f\u0438\u0441\u0430\u043b\u0438 \u0441\u0432\u043e\u044e \u043d\u0430\u0434\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u043d\u0430\u0434 DRF, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u043c\u0435\u0435\u0442\n1. \u041f\u043e\u043b\u043d\u0443\u044e \u0438\u0437\u043e\u043b\u044f\u0446\u0438\u044e \u043c\u0435\u0436\u0434\u0443 \u0432\u0435\u0440\u0441\u0438\u044f\u043c\u0438 API\n1. \u0412\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a `Accept`\n1. \u0414\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u0443\u044e \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0443 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043a\u043b\u0430\u0441\u0441\u043e\u0432 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0438\u0439 \u0434\u043b\u044f `View` \u0438 `ViewSet`\n1. \u041f\u0440\u043e\u043a\u0430\u0447\u0430\u043d\u043d\u0443\u044e \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044e \u0441\u0445\u0435\u043c\u044b\n\n## \u0411\u044b\u0441\u0442\u0440\u044b\u0439 \u0441\u0442\u0430\u0440\u0442\n\n\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043f\u0430\u043a\u0435\u0442 `restdoctor` \u0432 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0438 \u0438\u043b\u0438 \u0441\u0442\u0430\u0432\u0438\u043c \u0447\u0435\u0440\u0435\u0437 pip, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c `restdoctor` \u0432 `INSTALLED_APPS`.\n\n\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c ViewSet'\u044b \u0438\u0437 restdoctor, \u0437\u0430\u043c\u0435\u043d\u0438\u0432 \u0438\u043c\u043f\u043e\u0440\u0442\u044b `rest_framework` \u043d\u0430\n`restdoctor.rest_framework`.\n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 tutorial DRF. \u0411\u044b\u043b\u043e:\n```python\nfrom django.contrib.auth.models import User\nfrom rest_framework import viewsets\nfrom rest_framework import permissions\nfrom tutorial.quickstart.serializers import UserSerializer, UserListSerializer\n\n\nclass UserViewSet(viewsets.ModelViewSet):\n    \"\"\"\n    API endpoint that allows users to be viewed or edited.\n    \"\"\"\n    queryset = User.objects.all().order_by('-date_joined')\n    serializer_class = UserSerializer\n    permission_classes = [permissions.IsAuthenticated]\n\n    def get_serializer_class(self):\n        if self.action == 'list':\n            return UserListSerializer\n        return self.serializer_class\n```\n\n\u0421\u0442\u0430\u043b\u043e:\n```python\nfrom django.contrib.auth.models import User\nfrom restdoctor.rest_framework import viewsets\nfrom rest_framework import permissions\nfrom tutorial.quickstart.serializers import UserSerializer, UserListSerializer\n\n\nclass UserViewSet(viewsets.ModelViewSet):\n    \"\"\"\n    API endpoint that allows users to be viewed or edited.\n    \"\"\"\n    queryset = User.objects.all().order_by('-date_joined')\n    serializer_class_map = {\n        'default': UserSerializer,\n        'list': {\n            'response': UserListSerializer,\n        },\n    }\n    permission_classes_map = {\n        'default': [permissions.IsAuthenticated]\n    }\n```\n\n### \u0414\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0430\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\n\n\u0414\u043b\u044f \u0440\u0430\u0437\u0431\u043e\u0440\u0430 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 Accept \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c middleware \u0432 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f:\n\n```python\n\nROOT_URLCONF = ...\n\nMIDDLEWARE = [\n    ...,\n    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',\n]\n\nAPI_PREFIXES = ('/api',)\nAPI_FORMATS = ('full', 'compact')\n```\n\n\u041f\u043e\u0441\u043b\u0435 \u044d\u0442\u043e\u0433\u043e \u0434\u043b\u044f \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432, \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0432 `API_PREFIXES`? \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u044c\u0441\u044f \u0440\u0430\u0437\u0431\u043e\u0440 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 Accept. \u0412\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438\n\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0432\u043e View \u0438\u043b\u0438 ViewSet \u0432 request \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0441\u044f \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `api_params`.\n\n\n## \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\n\n\u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432 Settings:\n\n```python\nROOT_URLCONF = 'app.urls'\n\nINSTALLED_APPS = [\n    ...,\n    'rest_framework',\n    'restdoctor',\n]\n\nMIDDLEWARE = [\n    ...,\n    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',\n]\n\nAPI_FALLBACK_VERSION = 'fallback'\nAPI_FALLBACK_FOR_APPLICATION_JSON_ONLY = False\nAPI_DEFAULT_VERSION = 'v1'\nAPI_DEFAULT_FORMAT = 'full'\nAPI_PREFIXES = ('/api',)\nAPI_FORMATS = ('full', 'compact')\nAPI_RESOURCE_DISCRIMINATIVE_PARAM = 'view_type'\nAPI_RESOURCE_DEFAULT = 'common'\nAPI_RESOURCE_SET_PARAM = False\nAPI_RESOURCE_SET_PARAM_FOR_DEFAULT = False\nAPI_V1_URLCONF = 'api.v1_urls'\nAPI_VERSIONS = {\n    'fallback': ROOT_URLCONF,\n    'v1': API_V1_URLCONF,\n}\n```\n\n## \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0432 \u043f\u0440\u043e\u0435\u043a\u0442\u0435\n\n\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c\u0441\u044f \u043e\u0442 restdoctor \u0442\u0430\u043c, \u0433\u0434\u0435 \u0435\u0441\u0442\u044c \u0432\u044b\u0431\u043e\u0440 \u043c\u0435\u0436\u0434\u0443 `rest_framework`\n\u0438 `restdoctor.rest_framework`.\n\n```python\nfrom restdoctor.rest_framework.serializers import ModelSerializer\nfrom restdoctor.rest_framework.views import GenericAPIView, RetrieveAPIView, ListAPIView\nfrom restdoctor.rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet\n```\n\n### \u0412\u0435\u0440\u0441\u0438\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435\n\nRestDoctor \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0438\u0440\u0443\u0435\u0442 \u0432\u044b\u0437\u043e\u0432\u044b \u043f\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0443 `Accept` \u043d\u0430 \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 `UrlConf`.\n1. \u0412\u043e-\u043f\u0435\u0440\u0432\u044b\u0445, \u044d\u0442\u043e \u043e\u0437\u043d\u0430\u0447\u0430\u0435\u0442, \u0447\u0442\u043e \u0431\u0435\u0437 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 `Accept` \u0440\u0443\u0447\u043a\u0438 API \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u044b \u0438 \u043e\u0442\u0434\u0430\u0432\u0430\u0442\u044c 404.\n1. \u0410 \u0432\u043e-\u0432\u0442\u043e\u0440\u044b\u0445, \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0432\u0435\u0440\u0441\u0438\u0439 API, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0431\u0443\u0434\u0443\u0442 \"\u0432\u0438\u0434\u0435\u0442\u044c\" \u0434\u0440\u0443\u0433 \u0434\u0440\u0443\u0433\u0430.\n\n\u041e\u0431\u0449\u0438\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0439:\n\n```\napplication/vnd.{vendor}.{version}[-{resource}][.{format}][+json]\n```\n\n\u0413\u0434\u0435 vendor \u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c `API_VENDOR_STRING`, \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u0435\u0440\u0441\u0438\u0439 \u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0438\u0445 UrlConf'\u0430\u043c\n\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c `API_VERSIONS`.\n\n\u0421\u0430\u043c\u0443 \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u044e \u0434\u043b\u044f \u0432\u0445\u043e\u0434\u044f\u0449\u0435\u0433\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u0442 middleware `ApiSelectorMiddleware`, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043d\u0430\u0434\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\n\u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0445.\n\n```python\nROOT_URLCONF = 'app.urls'\n\nMIDDLEWARE = [\n    ...,\n    'restdoctor.django.middleware.api_selector.ApiSelectorMiddleware',\n]\n\nAPI_V1_URLCONF = 'api.v1.urls'\n\nAPI_VENDOR_STRING = 'RestDoctor'\n\nAPI_FALLBACK_VERSION = 'fallback'\nAPI_DEFAULT_VERSION = 'v1'\nAPI_VERSIONS = {\n    API_FALLBACK_VERSION: ROOT_URLCONF,\n    API_DEFAULT_VERSION: API_V1_URLCONF,\n}\n```\n\n\u041c\u0430\u0440\u0448\u0440\u0443\u0442\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e `API_VERSIONS` \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442, \u0435\u0441\u043b\u0438 Accept \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 `application/vnd.{vendor}`,\n\u0435\u0441\u043b\u0438 \u043d\u0435 \u0443\u043a\u0430\u0437\u0430\u043d\u0430 \u0432\u0435\u0440\u0441\u0438\u044f, \u0442\u043e \u0431\u0435\u0440\u0435\u0442\u0441\u044f `API_DEFAULT_VERSION`. \u0415\u0441\u043b\u0438 Accept \u043d\u0435 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 vendor-\u0441\u0442\u0440\u043e\u043a\u0438, \u0442\u043e\n\u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442\u0441\u044f `API_FALLBACK_VERSION`.\n\n\u0412\u0435\u0440\u0441\u0438\u044f \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0443\u043a\u0430\u0437\u0430\u043d\u0430 \u0432 \u0444\u043e\u0440\u043c\u0430\u0442\u0435 `{version}-{resource}`, \u0442\u043e\u0433\u0434\u0430 `ResourceViewSet` \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u0443 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e\n\u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 `ViewSet`.\n\n\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d `{format}` \u0434\u043b\u044f \u0432\u044b\u0431\u043e\u0440\u0430 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u043e\u0442\u0432\u0435\u0442\u0430, \u043f\u043e \u0444\u0430\u043a\u0442\u0443 \u0432\u044b\u0431\u043e\u0440 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0432\n`SerializerClassMapApiView`.\n\n\u0422\u0430\u043a\u0436\u0435 \u0443 \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u0442\u043e\u0436\u0435 \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u0435\u0440\u0441\u0438\u0438. \u0415\u0441\u043b\u0438 `{format}` \u0432 `API_FORMATS` \u0437\u0430\u0434\u0430\u043d `version:{2,3,5}` \u0432 \u0437\u0430\u043f\u0440\u043e\u0441\u0435 Accept \u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0443\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u043e\u043c\u0435\u0440 \u0432\u0435\u0440\u0441\u0438\u0438 `version:5`.\n\u0412\u044b\u0431\u043e\u0440 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u043f\u0440\u043e\u0438\u0441\u0445\u043e\u0434\u0438\u0442 \u043e\u0442 \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043a \u043c\u0435\u043d\u044c\u0448\u0435\u043c\u0443.\n\n\u0412 \u0441\u043b\u0443\u0447\u0430\u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e\u0433\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0432\u0435\u0440\u0441\u0438\u0438 \u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432 API \u0438\u0437 \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0430 Accept, middleware \u0432\u044b\u0431\u0438\u0440\u0430\u0435\u0442 \u0434\u043b\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438\n\u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0439 UrlConf \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0443 `request` \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `api_params`.\n\n\n### \u0424\u043e\u0440\u043c\u0430\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 API\n\n\u041d\u0430\u0448\u0438\u043c API Guide \u0437\u0430\u0434\u0430\u043d [\u0444\u043e\u0440\u043c\u0430\u0442 \u043e\u0442\u0432\u0435\u0442\u0430](https://github.com/best-doctor/guides/blob/master/guides/api_guide.md#%D1%84%D0%BE%D1%80%D0%BC%D0%B0%D1%82-%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81%D0%B0-%D0%B8-%D0%BE%D1%82%D0%B2%D0%B5%D1%82%D0%B0), \u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043e\u0442\u0432\u0435\u0447\u0430\u0435\u0442 RestDoctorRenderer\n(`restdoctor.rest_framework.renderers.RestDoctorRenderer`). \u0412\u043a\u043b\u044e\u0447\u0430\u0435\u0442\u0441\u044f \u043e\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0449\u0438\u0445 \u0430\u0442\u0440\u0438\u0431\u0443\u0442\n`api_params`, \u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u044d\u0442\u043e\u0442 \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c \u0447\u0435\u0440\u0435\u0437 `content_negotiation_class` \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0439 \u0432 \u0431\u0430\u0437\u043e\u0432\u043e\u043c \u0434\u043b\u044f View \u0438 ViewSet\n\u043c\u0438\u043a\u0441\u0438\u043d\u0435 NegotiatedMixin (`restdoctor.rest_framework.mixins.NegotiatedMixin`).\n\n\n### SerializerClassMapApiView\n\nDRF \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043a\u043e\u043c\u043f\u0430\u043a\u0442\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c `ModelSeraizlier` + `ModelViewSet`, \u043e\u0434\u043d\u0430\u043a\u043e \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043c\u043d\u043e\u0433\u043e\n\u0441\u0432\u043e\u0431\u043e\u0434\u044b \u0432 \u043e\u0434\u043d\u0438\u0445 \u043c\u0435\u0441\u0442\u0430\u0445, \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u044f \u0435\u0435 \u0432 \u0434\u0440\u0443\u0433\u0438\u0445.\n\n\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u043e\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c `serializer_class` \u0432 \u043a\u043b\u0430\u0441\u0441\u0435 ViewSet'\u0430, \u043b\u0438\u0431\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0435\u0433\u043e \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438 \u0447\u0435\u0440\u0435\u0437\n`ViewSet.get_serializer_class`, \u043e\u0434\u043d\u0430\u043a\u043e \u043d\u0435\u043b\u044c\u0437\u044f \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u044b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u0437\u0430\u043f\u0440\u043e\u0441\u0430, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u043e\u0442\u0432\u0435\u0442\u0430.\n\u0422.\u0435. \u043d\u0435\u043b\u044c\u0437\u044f \u0437\u0430\u0434\u0430\u0442\u044c \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u0434\u043b\u044f `update`, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u0434\u043b\u044f `retrieve` \u0434\u043b\u044f \u0432\u043e\u0437\u0432\u0440\u0430\u0442\u0430 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u043e\u0439\n\u0441\u0443\u0449\u043d\u043e\u0441\u0442\u0438.\n\n`SerializerClassMapApiView` \u0434\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u044b \u0434\u043b\u044f \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 action, \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f\nrequest \u0438 response.\n\n\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u043a\u0430 \u043d\u0430 \u0443\u0440\u043e\u0432\u043d\u0435 \u0431\u0430\u0437\u043e\u0432\u044b\u0445 \u043c\u0438\u043a\u0441\u0438\u043d\u043e\u0432 \u0434\u043b\u044f ViewSet'\u043e\u0432 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043f\u0440\u043e\u0437\u0440\u0430\u0447\u043d\u043e \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440,\n`ReadOnlyModelViewSet` \u0432 \u0438\u043c\u043f\u043e\u0440\u0442\u0430\u0445 \u0441 `rest_framework.viewsets` \u043d\u0430 `restdoctor.rest_framework.viewsets`.\n\n\n#### serializer_class_map\n\n`SerializerClassMapApiView` \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0437\u0430\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u044b \u0434\u043b\u044f \u0440\u0430\u0437\u043d\u044b\u0445 action'\u043e\u0432 \u0438 \u0444\u043e\u0440\u043c\u0430\u0442\u043e\u0432 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f\nrequest \u0438 response \u0444\u0430\u0437\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430.\n\n```python\nfrom restdoctor.rest_framework.viewsets import ModelViewSet\n\nfrom app.api.serializers import (\n    MyDefaultSerializer, MyCompactSerializer, MyAntoherSerializer,\n    MyCreateSerializer, MyUpdateSerializer,\n)\n\n\nclass MyApiView(SerializerClassMapApiView):\n    \"\"\"\u041f\u0440\u0438\u043c\u0435\u0440 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 serializer_class_map.\"\"\"\n\n    serializer_class_map = {\n        'default': MyDefaultSerializer,\n        'default.compact': MyCompactSerializer,\n         'create': {\n            'request': MyCreateSerializer,\n         },\n         'update': {\n            'request': MyUpdateSerializer,\n            'request.version:3': MyVersion3UpdateSerializer,\n            'request.version:2': MyVersionUpdateSerializer,\n         },\n         'list': {\n            'response.another_format': MyAnotherSerializer,\n            'meta': MyMetaSerializer,\n        }\n    }\n\n```\n\n\u0412 \u044d\u0442\u043e\u043c \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u044b \u0437\u0430\u0434\u0430\u0435\u043c `MyDefaultSerializer` \u043a\u0430\u043a \u0431\u0430\u0437\u043e\u0432\u044b\u0439 \u0434\u043b\u044f ViewSet. \u041d\u043e \u0434\u043b\u044f `create` \u0438 `update` action\n\u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u044b \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 request'\u0430.\n\n\u041a\u0440\u043e\u043c\u0435 \u0442\u043e\u0433\u043e, \u043c\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043b\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u0434\u043b\u044f `compact` \u0444\u043e\u0440\u043c\u0430\u0442\u0430 \u0438 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f action `list` \u0438 `update` \u0444\u043e\u0440\u043c\u0430\u0442\u044b `another_format`, `version:2`, `version:3`.\n\u0424\u043e\u0440\u043c\u0430\u0442 \u0441 \u0432\u0435\u0440\u0441\u0438\u044f\u043c\u0438 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043f\u043e \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u0443 \u043f\u043e\u0438\u0441\u043a\u0430 \u0442\u043e\u0447\u043d\u043e\u0439 \u0438\u043b\u0438 \u043c\u0435\u043d\u044c\u0448\u0435\u0439 \u0432\u0435\u0440\u0441\u0438\u0438 \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430.\n\u041e\u0442\u0434\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0430 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 meta \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438.\n\n\n#### permission_classes_map\n\n\u041f\u043e \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0438 \u0441 `serializer_class_map` \u0434\u043b\u044f \u0434\u0435\u043a\u043b\u0430\u0440\u0430\u0442\u0438\u0432\u043d\u043e\u0433\u043e \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u0440\u0430\u0437\u043d\u044b\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 `permission_classes` \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 action'\u0430\u0445\n\u043c\u043e\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c `permission_classes_map`:\n\n```python\nfrom restdoctor.rest_framework.viewsets import ModelViewSet\n\nfrom app.api.permissions import PermissionA, PermissionB\n\n\nclass MyViewSet(ModelViewSet):\n    permission_classes_map = {\n        'default': [PermissionA],\n        'retrieve': [PermissionB],\n    }\n```\n\n#### \u0417\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0435 \u043f\u0440\u043e action\n\n\u0412 DRF action \u043f\u043e\u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u0438 `ViewSet` \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e `Router`. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u0434\u043b\u044f \u0440\u0430\u0437\u0434\u0435\u043b\u0435\u043d\u0438\u044f list/detail \u0440\u0435\u0441\u0443\u0440\u0441\u043e\u0432\n\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0440\u0430\u0437\u043d\u044b\u0435 \u043d\u0430\u0431\u043e\u0440\u044b `action_maps`:\n\n```\nlist_action_map = {'get': 'list', 'post': 'create'}\ndetail_action_map = {'get': 'retrieve', 'put': 'update'}\n```\n\nDjango-\u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u044b \u0440\u043e\u0443\u0442\u0438\u043d\u0433\u0430 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u0444\u0443\u043d\u043a\u0446\u0438\u044e-\u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u043d\u0441\u0442\u0430\u043d\u0446\u0438\u0440\u0443\u0435\u0442 View/ViewSet \u0441 \u043d\u0443\u0436\u043d\u044b\u043c\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430\u043c\u0438.\n\u041f\u0440\u0438 \u044d\u0442\u043e\u043c \u043e\u0434\u0438\u043d \u0438 \u0442\u043e\u0442 \u0436\u0435 \u043a\u043b\u0430\u0441\u0441 `ViewSet` \u0431\u0443\u0434\u0435\u0442 \u043f\u0440\u0438\u0441\u0443\u0442\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0432 UrlConf \u0432 \u0434\u0432\u0443\u0445 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430\u0445 \u0441 \u0440\u0430\u0437\u043d\u044b\u043c\u0438 `action_map`.\n\u0412\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u043f\u043e HTTP \u043c\u0435\u0442\u043e\u0434\u0443 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d action \u0438 \u0432\u044b\u0437\u0432\u0430\u043d \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 \u043c\u0435\u0442\u043e\u0434 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 `ViewSet`.\n\u0418 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 \u0443 `ViewSet` \u0432\u0441\u0435\u0433\u0434\u0430 \u0437\u0430\u0434\u0430\u043d `self.action`.\n\n\u041e\u0434\u043d\u0430\u043a\u043e \u044d\u0442\u043e \u043d\u0435 \u0442\u0430\u043a \u0434\u043b\u044f `View`, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 `SerializerClassMapApiView` \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `action`, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f\n\u043f\u043e\u0438\u0441\u043a \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0432 `serializer_class_map`.\n\n\n### \u041c\u0438\u043a\u0441\u0438\u043d\u044b \u0438 ModelViewSet\n\n\u041c\u0438\u043a\u0441\u0438\u043d\u044b \u0437\u0430\u0434\u0430\u044e\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0438 `ModelViewSet` \u0434\u043b\u044f `'list'`, `'retrieve'`, `'create'`, `'update'`, `'destroy'` action'\u043e\u0432.\n\n\n\u041e\u0442 DRF-\u0432\u0435\u0440\u0441\u0438\u0439 \u043e\u043d\u0438 \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0442\u0441\u044f \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u0442\u0435\u043c, \u0447\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 `SerializerClassMapApiView.get_request_serializer` \u0438\n`SerializerClassMapApiView.get_response_serializer` \u0432\u043c\u0435\u0441\u0442\u043e `View.get_serializer`.\n\n\n#### RetrieveModelMixin\n\n\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0434\u043b\u044f `retrieve` action. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 `get_item`:\n\n```python\nclass RetrieveModelMixin(BaseListModelMixin):\n    def retrieve(self, request: Request, *args: typing.Any, **kwargs: typing.Any) -> Response:\n        item = self.get_item(request_serializer)\n        ...\n\n\n    def get_item(self, request_serializer: BaseSerializer) -> typing.Union[typing.Dict, QuerySet]:\n        return self.get_object()\n```\n\n\u0422.\u0435. \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c `RetrieveModelMixin` \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043b\u044e\u0431\u044b\u043c\u0438 \u0441\u043b\u043e\u0432\u0430\u0440\u044f\u043c\u0438, \u0430 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438, \u043d\u0430\u0434\u043e \u0442\u043e\u043b\u044c\u043a\u043e\n\u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c `ViewSet.get_item`.\n\n#### ListModelMixin\n\n\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0434\u043b\u044f `list` action. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 `get_collection`:\n\n```python\nclass ListModelMixin(BaseListModelMixin):\n    def list(self, request: Request, *args: typing.Any, **kwargs: typing.Any) -> Response:\n        queryset = self.get_collection()\n        ...\n\n    def get_collection(self, request_serializer: BaseSerializer) -> typing.Union[typing.List, QuerySet]:\n        return self.filter_queryset(self.get_queryset())\n```\n\n\u0422.\u0435. \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c `ListModelMixin` \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 \u043b\u044e\u0431\u044b\u043c\u0438 \u043a\u043e\u043b\u043b\u0435\u043a\u0446\u0438\u044f\u043c\u0438, \u0430 \u043d\u0435 \u0442\u043e\u043b\u044c\u043a\u043e \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438, \u043d\u0430\u0434\u043e \u0442\u043e\u043b\u044c\u043a\u043e\n\u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c `ViewSet.get_collection`. \u041f\u0440\u0438 \u044d\u0442\u043e\u043c, \u0435\u0441\u043b\u0438 \u0437\u0430\u0434\u0430\u043d \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u0434\u043b\u044f `list`, \u0442\u043e \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\n\u0434\u043b\u044f query-\u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u0432, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u044d\u0442\u0438 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a filterset'\u0443.\n\n\n\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 `meta` \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438. \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u0442 \u043c\u0435\u0442\u043e\u0434 `get_meta_data`:\n\n```python\nclass ListModelMixin(BaseListModelMixin):\n    def get_meta_data(self) -> typing.Dict[str, typing.Any]:\n        return {'test': typing.Any}\n```\n\u0422.\u0435. \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c `ListModelMixin` \u0434\u043b\u044f \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0439 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u0432 \u043f\u043e\u043b\u0435 `meta`.\n\u0414\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u0434\u043b\u044f `meta`.\n\n```python\n    serializer_class_map = {\n         'default': MyDefaultSerializer,\n         'list': {\n            'meta': MyMetaSerializer,\n        }\n    }\n```\n\n\u0417\u0430\u0434\u0430\u043d \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a `perform_list` \u0434\u043b\u044f \u0432\u044b\u0431\u0440\u0430\u043d\u043d\u044b\u0445 \u0434\u0430\u043d\u043d\u044b\u0445 \u0432 \u043f\u0430\u0433\u0438\u043d\u0430\u0446\u0438\u0438.\n\u0414\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u043d\u0443\u0436\u043d\u043e \u043f\u0435\u0440\u0435\u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u043c\u0435\u0442\u043e\u0434 `perform_list`.\n\n```python\nclass ListModelMixin(BaseListModelMixin):\n    def perform_list(self, data: typing.Union[typing.List, QuerySet]) -> None:\n        Sender(data)\n```\n#### ListModelViewSet\n\n\u0417\u0430\u0434\u0430\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0434\u043b\u044f `list` action.\n\n\n#### ReadOnlyModelViewSet\n\n\u0417\u0430\u0434\u0430\u043d\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0434\u043b\u044f `list` \u0438 `retrieve` action'\u043e\u0432.\n\n\n#### CreateUpdateReadModelViewSet\n\n\u0417\u0430\u0434\u0430\u043d\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438 \u0434\u043b\u044f `list`, `retrieve`, `create`, `update` action'\u043e\u0432.\n\n\n#### ModelViewSet\n\n\u041f\u043e\u043b\u043d\u044b\u0439 \u043d\u0430\u0431\u043e\u0440 action'\u043e\u0432: `list`, `retrieve`, `create`, `update`, `destroy`.\n\n\n### PydanticSerializer\n\n\u0414\u043b\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u0435 [pydantic](https://pydantic-docs.helpmanual.io/) \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c\n\u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440 \u043e\u0442 `PydanticSerializer`, \u0443\u043a\u0430\u0437\u0430\u0442\u044c \u0432 `Meta` `pydantic_model` \u0438 `pydantic_use_aliases` (\u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438).\n\n\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 `pydantic_use_aliases` \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c [\u0430\u043b\u0438\u0430\u0441\u044b pydantic \u043c\u043e\u0434\u0435\u043b\u0435\u0439](https://pydantic-docs.helpmanual.io/usage/model_config/#alias-precedence) \u0434\u043b\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438.\n```python\n\nclass PydanticSerializer(PydanticSerializer):\n    class Meta:\n        pydantic_model = PydanticModel\n        pydantic_use_aliases = True\n```\n\n### \u0413\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0445\u0435\u043c\u044b\n\u041f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0445\u0435\u043c\u044b openapi \u0432\u0435\u0440\u0441\u0438\u0439 3.0.2 \u0438 3.1.0.\n\u0421\u0445\u0435\u043c\u0430 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e \u0437\u0430\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u043e\u043c `API_DEFAULT_OPENAPI_VERSION` \u0438 \u0440\u0430\u0432\u043d\u0430 `3.0.2`.\n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0445\u0435\u043c\u044b (\u0432\u0435\u0440\u0441\u0438\u044f \u0438\u0437 settings):\n```shell\npython3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator > your_app/static/openapi.schema\n```\n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0445\u0435\u043c\u044b \u0432\u0435\u0440\u0441\u0438\u0438 openapi 3.0.2:\n```shell\npython3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator30 > your_app/static/openapi-30.schema\n```\n\n\u041f\u0440\u0438\u043c\u0435\u0440 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u043c\u0445\u0435\u043c\u044b \u0432\u0435\u0440\u0441\u0438\u0438 openapi 3.1.0:\n```shell\npython3 ./manage.py generateschema --urlconf api.v1.urls --generator_class restdoctor.rest_framework.schema.RefsSchemaGenerator31 > your_app/static/openapi-31.schema\n```\n\n#### \u041e\u043f\u0446\u0438\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438\n##### API_STRICT_SCHEMA_VALIDATION\n- \u0434\u0435\u043b\u0430\u0435\u0442 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0439 \u0443 \u043f\u043e\u043b\u0435\u0439 (`help_text`, `verbose_name` \u0443 \u043c\u043e\u0434\u0435\u043b\u0438)\n- \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043d\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044f \u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `allow_null`\n- \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043d\u0430 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u0435 \u0430\u043d\u043d\u043e\u0442\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044f \u0438 \u0430\u0442\u0440\u0438\u0431\u0443\u0442 `many`\n\n\u0415\u0441\u043b\u0438 \u043a\u0430\u043a\u0430\u044f-\u0442\u043e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442, \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u044f \u0441\u0445\u0435\u043c\u044b \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442\u0441\u044f \u043e\u0448\u0438\u0431\u043a\u043e\u0439.\n\n##### API_SCHEMA_PRIORITIZE_SERIALIZER_PARAMETERS\n\u041f\u0440\u0438 \u0432\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u044d\u0442\u043e\u0439 \u043e\u043f\u0446\u0438\u0438 \u0434\u043b\u044f \u0441\u0445\u0435\u043c\u044b \u0431\u0443\u0434\u0443\u0442 \u0432\u044b\u0431\u0438\u0440\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u043b\u044f \u0441\u0435\u0440\u0438\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043e\u043d\u0438 \u0434\u0443\u0431\u043b\u0438\u0440\u0443\u044e\u0442 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0435.\n\n##### API_SCHEMA_FILTER_MAP_PATH\n\u041f\u0443\u0442\u044c \u0434\u043e \u043a\u0430\u0441\u0442\u043e\u043c\u043d\u043e\u0439 \u0441\u0445\u0435\u043c\u044b \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0434\u043b\u044f `DjangoFilterBackend`, \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e - `restdoctor.rest_framework.schema.filters.FILTER_MAP`.\n\n\n### pre-commit\n\n\u042d\u0442\u043e\u0442 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 git-\u0445\u0443\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0435 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e [pre-commit](https://pre-commit.com)\n\u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0435\u0441\u043b\u0438 \u043f\u043b\u0430\u043d\u0438\u0440\u0443\u0435\u0442\u0441\u044f \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u0435 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u0432 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0438\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c\npre-commit \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0439 \u043a\u043e\u043c\u0430\u043d\u0434\u044b:\n\n```shell script\nmake install-hooks\n```\n\n\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "BestDoctor's batteries for REST services.",
    "version": "0.0.63",
    "project_urls": null,
    "split_keywords": [
        "statistics"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "83ffcaebc21726976148288bfea78a3e2ec10926f55ede2f78d3ba33d2f54477",
                "md5": "c07793105faae0292ddf0768817a0af4",
                "sha256": "f944592fbc50f6e8968e91fd37c5055bc05a9f72da1ab262b666a5ddc55da6c2"
            },
            "downloads": -1,
            "filename": "restdoctor-0.0.63-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "c07793105faae0292ddf0768817a0af4",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": null,
            "size": 117495,
            "upload_time": "2024-03-12T19:20:23",
            "upload_time_iso_8601": "2024-03-12T19:20:23.376745Z",
            "url": "https://files.pythonhosted.org/packages/83/ff/caebc21726976148288bfea78a3e2ec10926f55ede2f78d3ba33d2f54477/restdoctor-0.0.63-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "78490435453c9a4d91c70214404ae1be7b02c0535a03853ca34585417d48619b",
                "md5": "7de9f59353db5c45f5a55cf066cf99ec",
                "sha256": "214fcd23b9657208fde5aad292d38a2f78bfeab9ff8071565d961502b3db8792"
            },
            "downloads": -1,
            "filename": "restdoctor-0.0.63.tar.gz",
            "has_sig": false,
            "md5_digest": "7de9f59353db5c45f5a55cf066cf99ec",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 82771,
            "upload_time": "2024-03-12T19:20:25",
            "upload_time_iso_8601": "2024-03-12T19:20:25.836309Z",
            "url": "https://files.pythonhosted.org/packages/78/49/0435453c9a4d91c70214404ae1be7b02c0535a03853ca34585417d48619b/restdoctor-0.0.63.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-03-12 19:20:25",
    "github": false,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "lcname": "restdoctor"
}
        
Elapsed time: 0.23719s