diff --git a/README.rst b/README.rst index 636cba9..d4c94c2 100644 --- a/README.rst +++ b/README.rst @@ -94,42 +94,42 @@ In ``settings.py``: .. code:: python - INSTALLED_APPS = [ - ... - 'drf_yasg', - ... - ] + INSTALLED_APPS = [ + ... + 'drf_yasg', + ... + ] In ``urls.py``: .. code:: python - ... - from drf_yasg.views import get_schema_view - from drf_yasg import openapi + ... + from drf_yasg.views import get_schema_view + from drf_yasg import openapi - ... + ... - schema_view = get_schema_view( - openapi.Info( - title="Snippets API", - default_version='v1', - description="Test description", - terms_of_service="https://www.google.com/policies/terms/", - contact=openapi.Contact(email="contact@snippets.local"), - license=openapi.License(name="BSD License"), - ), - validators=['ssv', 'flex'], - public=True, - permission_classes=(permissions.AllowAny,), - ) + schema_view = get_schema_view( + openapi.Info( + title="Snippets API", + default_version='v1', + description="Test description", + terms_of_service="https://www.google.com/policies/terms/", + contact=openapi.Contact(email="contact@snippets.local"), + license=openapi.License(name="BSD License"), + ), + validators=['ssv', 'flex'], + public=True, + permission_classes=(permissions.AllowAny,), + ) - urlpatterns = [ - url(r'^swagger(?P.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'), - url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), - url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'), - ... - ] + urlpatterns = [ + url(r'^swagger(?P.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'), + url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'), + url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'), + ... + ] This exposes 4 cached, validated and publicly available endpoints: diff --git a/docs/changelog.rst b/docs/changelog.rst index 271cb0f..d936c36 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -3,6 +3,14 @@ Changelog ######### +********* +**1.1.3** +********* + +- **FIXED:** schema view cache will now always ``Vary`` on the ``Cookie`` and ``Authentication`` (the + ``Vary`` header was previously only added if ``public`` was set to ``True``) - this fixes issues related to Django + authentication in ``swagger-ui`` and ``CurrentUserDefault`` values in the schema + ********* **1.1.2** ********* diff --git a/docs/rendering.rst b/docs/rendering.rst index 13786f0..f7c2de9 100644 --- a/docs/rendering.rst +++ b/docs/rendering.rst @@ -58,3 +58,30 @@ See the command help for more advanced options: usage: manage.py generate_swagger [-h] [--version] [-v {0,1,2,3}] ... more options ... + +.. Note:: + + The :ref:`DEFAULT_INFO ` setting must be defined when using the ``generate_swagger`` + command. For example, the :ref:`README quickstart ` code could be modified as such: + + In ``settings.py``: + + .. code:: python + + SWAGGER_SETTINGS = { + 'DEFAULT_INFO': 'import.path.to.urls.api_info', + } + + In ``urls.py``: + + .. code:: python + + api_info = openapi.Info( + title="Snippets API", + ... other arguments ... + ) + + schema_view = get_schema_view( + # the info argument is no longer needed here as it will be picked up from DEFAULT_INFO + ... other arguments ... + ) diff --git a/docs/settings.rst b/docs/settings.rst index b156daa..5ba8fbf 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -94,6 +94,8 @@ Paginator inspectors given to :func:`@swagger_auto_schema <.swagger_auto_schema> Swagger document attributes =========================== +.. _default-swagger-settings: + DEFAULT_INFO ------------ diff --git a/src/drf_yasg/utils.py b/src/drf_yasg/utils.py index 4ffb8bd..a1d955e 100644 --- a/src/drf_yasg/utils.py +++ b/src/drf_yasg/utils.py @@ -4,6 +4,7 @@ from collections import OrderedDict from rest_framework import serializers, status from rest_framework.mixins import DestroyModelMixin, RetrieveModelMixin, UpdateModelMixin +from rest_framework.views import APIView logger = logging.getLogger(__name__) @@ -41,7 +42,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod the `manual_parameters` argument. If a ``Serializer`` class or instance is given, it will be automatically converted into a :class:`.Schema` - used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\ s, as appropriate. + used as a ``body`` :class:`.Parameter`, or into a list of ``form`` :class:`.Parameter`\ s, as appropriate. :param .Serializer query_serializer: if you use a ``Serializer`` to parse query parameters, you can pass it here and have :class:`.Parameter` objects be generated automatically from it. @@ -85,6 +86,7 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod """ def decorator(view_method): + assert not any(hm in extra_overrides for hm in APIView.http_method_names), "HTTP method names not allowed here" data = { 'auto_schema': auto_schema, 'request_body': request_body, @@ -97,48 +99,52 @@ def swagger_auto_schema(method=None, methods=None, auto_schema=None, request_bod 'paginator_inspectors': list(paginator_inspectors) if paginator_inspectors else None, 'field_inspectors': list(field_inspectors) if field_inspectors else None, } - data = {k: v for k, v in data.items() if v is not None} + data = filter_none(data) data.update(extra_overrides) + if not data: # pragma: no cover + # no overrides to set, no use in doing more work + return # if the method is a detail_route or list_route, it will have a bind_to_methods attribute bind_to_methods = getattr(view_method, 'bind_to_methods', []) # if the method is actually a function based view (@api_view), it will have a 'cls' attribute view_cls = getattr(view_method, 'cls', None) http_method_names = getattr(view_cls, 'http_method_names', []) - if bind_to_methods or http_method_names: + + available_methods = http_method_names + bind_to_methods + existing_data = getattr(view_method, '_swagger_auto_schema', {}) + + _methods = methods + if methods or method: + assert bool(methods) != bool(method), "specify either method or methods" + assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \ + " use `method` for a single argument" + if method: + _methods = [method.lower()] + else: + _methods = [mth.lower() for mth in methods] + assert all(mth in available_methods for mth in _methods), "http method not bound to view" + assert not any(mth in existing_data for mth in _methods), "http method defined multiple times" + + if available_methods: # detail_route, list_route or api_view assert bool(http_method_names) != bool(bind_to_methods), "this should never happen" - available_methods = http_method_names + bind_to_methods - existing_data = getattr(view_method, '_swagger_auto_schema', {}) - if http_method_names: - _route = "api_view" - else: - _route = "detail_route" if view_method.detail else "list_route" - - _methods = methods if len(available_methods) > 1: - assert methods or method, \ - "on multi-method %s, you must specify swagger_auto_schema on a per-method basis " \ - "using one of the `method` or `methods` arguments" % _route - assert bool(methods) != bool(method), "specify either method or methods" - assert not isinstance(methods, str), "`methods` expects to receive a list of methods;" \ - " use `method` for a single argument" - if method: - _methods = [method.lower()] - else: - _methods = [mth.lower() for mth in methods] - assert not any(mth in existing_data for mth in _methods), "method defined multiple times" - assert all(mth in available_methods for mth in _methods), "method not bound to %s" % _route - - existing_data.update((mth.lower(), data) for mth in _methods) + assert _methods, \ + "on multi-method api_view, detail_route or list_route, you must specify swagger_auto_schema on " \ + "a per-method basis using one of the `method` or `methods` arguments" else: - existing_data[available_methods[0]] = data + # for a single-method view we assume that single method as the decorator target + _methods = _methods or available_methods + + existing_data.update((mth.lower(), data) for mth in _methods) view_method._swagger_auto_schema = existing_data else: - assert method is None and methods is None, \ + assert not _methods, \ "the methods argument should only be specified when decorating a detail_route or list_route; you " \ "should also ensure that you put the swagger_auto_schema decorator AFTER (above) the _route decorator" + assert not existing_data, "a single view method should only be decorated once" view_method._swagger_auto_schema = data return view_method diff --git a/tests/test_schema_views.py b/tests/test_schema_views.py index 136e5aa..f6f40a6 100644 --- a/tests/test_schema_views.py +++ b/tests/test_schema_views.py @@ -52,10 +52,7 @@ def test_caching(client, validate_schema): prev_schema = None for i in range(3): - _validate_ui_schema_view(client, '/cached/redoc/', 'redoc/redoc.min.js') - _validate_text_schema_view(client, validate_schema, '/cached/redoc/?format=openapi', json.loads) - _validate_ui_schema_view(client, '/cached/swagger/', 'swagger-ui-dist/swagger-ui-bundle.js') - _validate_text_schema_view(client, validate_schema, '/cached/swagger/?format=openapi', json.loads) + _validate_text_schema_view(client, validate_schema, '/cached/swagger.yaml', yaml_sane_load) json_schema = client.get('/cached/swagger.json') assert json_schema.status_code == 200