diff --git a/rest_framework/schemas/openapi.py b/rest_framework/schemas/openapi.py index 9353ac333..a8b61de2f 100644 --- a/rest_framework/schemas/openapi.py +++ b/rest_framework/schemas/openapi.py @@ -14,16 +14,18 @@ from django.db import models from django.utils.encoding import force_str from django.utils.module_loading import import_string -from rest_framework import exceptions, renderers, serializers, permissions +from rest_framework import exceptions, permissions, renderers, serializers from rest_framework.compat import uritemplate from rest_framework.fields import _UnvalidatedField, empty +from rest_framework.schemas.openapi_utils import ( + TYPE_MAPPING, PolymorphicResponse +) from rest_framework.settings import api_settings -from rest_framework.schemas.openapi_utils import TYPE_MAPPING, PolymorphicResponse + from .generators import BaseSchemaGenerator from .inspectors import ViewInspector from .utils import get_pk_description, is_list_view - AUTHENTICATION_SCHEMES = { cls.authentication_class: cls for cls in [import_string(cls) for cls in api_settings.SCHEMA_AUTHENTICATION_CLASSES] @@ -180,7 +182,7 @@ class AutoSchema(ViewInspector): action_or_method = getattr(self.view, getattr(self.view, 'action', method.lower()), None) view_doc = inspect.getdoc(self.view) or '' action_doc = inspect.getdoc(action_or_method) or '' - return view_doc + '\n\n' + action_doc if action_doc else view_doc + return action_doc or view_doc def get_auth(self, path, method): """ override this for custom behaviour """ @@ -262,8 +264,10 @@ class AutoSchema(ViewInspector): elif model_field is not None and model_field.primary_key: description = get_pk_description(model, model_field) - # TODO cover more cases - if isinstance(model_field, models.UUIDField): + # TODO are there more relevant PK base classes? + if isinstance(model_field, models.IntegerField): + schema = TYPE_MAPPING[int] + elif isinstance(model_field, models.UUIDField): schema = TYPE_MAPPING[UUID] parameter = { @@ -498,7 +502,7 @@ class AutoSchema(ViewInspector): result = { 'properties': properties } - if required and method != 'PATCH' and not nested: + if required and method != 'PATCH': result['required'] = required return result @@ -648,7 +652,6 @@ class AutoSchema(ViewInspector): } return {'200': self._get_response_for_code(path, method, schema)} - def _get_response_for_code(self, path, method, serializer_instance): if not serializer_instance: return {'description': 'No response body'} @@ -702,7 +705,7 @@ class AutoSchema(ViewInspector): if name.endswith('Serializer'): name = name[:-10] - if method == 'PATCH' and not nested: + if method == 'PATCH' and not serializer.read_only: # TODO maybe even use serializer.partial name = 'Patched' + name return name diff --git a/rest_framework/schemas/openapi_utils.py b/rest_framework/schemas/openapi_utils.py index 7336268b9..103463c0b 100644 --- a/rest_framework/schemas/openapi_utils.py +++ b/rest_framework/schemas/openapi_utils.py @@ -1,8 +1,8 @@ import inspect import warnings +from datetime import date, datetime from decimal import Decimal from uuid import UUID -from datetime import datetime, date from rest_framework import authentication from rest_framework.settings import api_settings @@ -103,6 +103,7 @@ def extend_schema( TODO some heavy explaining :param operation: + :param operation_id: :param extra_parameters: :param responses: :param request: diff --git a/tests/schemas/test_openapi.py b/tests/schemas/test_openapi.py index faec64c8e..d046d7c90 100644 --- a/tests/schemas/test_openapi.py +++ b/tests/schemas/test_openapi.py @@ -1,14 +1,19 @@ import pytest from django.conf.urls import url +from django.db import models from django.test import RequestFactory, TestCase, override_settings from django.utils.translation import gettext_lazy as _ -from rest_framework import filters, generics, pagination, routers, serializers +from rest_framework import ( + filters, generics, pagination, routers, serializers, viewsets +) from rest_framework.compat import uritemplate from rest_framework.parsers import JSONParser, MultiPartParser from rest_framework.renderers import JSONRenderer from rest_framework.request import Request -from rest_framework.schemas.openapi import AutoSchema, SchemaGenerator, ComponentRegistry +from rest_framework.schemas.openapi import ( + AutoSchema, ComponentRegistry, SchemaGenerator +) from . import views @@ -126,7 +131,7 @@ class TestOperationIntrospection(TestCase): operation = inspector.get_operation(path, method) assert operation == { 'operationId': 'example_retrieve', - 'description': '\n\nA description of my GET operation.', + 'description': 'A description of my GET operation.', 'parameters': [ { 'name': 'id', @@ -294,8 +299,7 @@ class TestOperationIntrospection(TestCase): inspector = AutoSchema() inspector.view = view inspector.init(registry) - - operation = inspector.get_operation(path, method) + inspector.get_operation(path, method) example_schema = registry.schemas['Example'] nested_schema = registry.schemas['Nested'] @@ -681,6 +685,36 @@ class TestOperationIntrospection(TestCase): assert properties['ip']['type'] == 'string' assert 'format' not in properties['ip'] + def test_modelviewset(self): + class ExampleModel(models.Model): + text = models.TextField() + + class ExampleSerializer(serializers.ModelSerializer): + class Meta: + model = ExampleModel + fields = ['id', 'text'] + + class ExampleViewSet(viewsets.ModelViewSet): + serializer_class = ExampleSerializer + queryset = ExampleModel.objects.none() + + from django.urls import path, include + + router = routers.DefaultRouter() + router.register(r'example', ExampleViewSet) + + generator = SchemaGenerator(patterns=[ + path(r'api/', include(router.urls)) + ]) + generator._initialise_endpoints() + + schema = generator.get_schema(request=None, public=True) + + assert list(schema['paths']['/api/example/'].keys()) == ['get', 'post'] + assert list(schema['paths']['/api/example/{id}/'].keys()) == ['get', 'put', 'patch', 'delete'] + assert list(schema['components']['schemas'].keys()) == ['Example', 'PatchedExample'] + # TODO do more checks + @pytest.mark.skipif(uritemplate is None, reason='uritemplate not installed.') @override_settings(REST_FRAMEWORK={'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema'})