views.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import warnings
  2. from functools import WRAPPER_ASSIGNMENTS, wraps
  3. from django.utils.cache import add_never_cache_headers
  4. from django.views.decorators.cache import cache_page
  5. from django.views.decorators.vary import vary_on_headers
  6. from rest_framework import exceptions
  7. from rest_framework.response import Response
  8. from rest_framework.settings import api_settings
  9. from rest_framework.views import APIView
  10. from .app_settings import swagger_settings
  11. from .renderers import (
  12. OpenAPIRenderer, ReDocOldRenderer, ReDocRenderer, SwaggerJSONRenderer, SwaggerUIRenderer, SwaggerYAMLRenderer,
  13. _SpecRenderer
  14. )
  15. SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
  16. UI_RENDERERS = {
  17. 'swagger': (SwaggerUIRenderer, ReDocRenderer),
  18. 'redoc': (ReDocRenderer, SwaggerUIRenderer),
  19. 'redoc-old': (ReDocOldRenderer, ReDocRenderer, SwaggerUIRenderer),
  20. }
  21. def deferred_never_cache(view_func):
  22. """
  23. Decorator that adds headers to a response so that it will
  24. never be cached.
  25. """
  26. @wraps(view_func, assigned=WRAPPER_ASSIGNMENTS)
  27. def _wrapped_view_func(request, *args, **kwargs):
  28. response = view_func(request, *args, **kwargs)
  29. # It is necessary to defer the add_never_cache_headers call because
  30. # cache_page also defers its cache update operation; if we do not defer
  31. # this, cache_page will give up because it will see and obey the "never
  32. # cache" headers
  33. def callback(response):
  34. add_never_cache_headers(response)
  35. return response
  36. response.add_post_render_callback(callback)
  37. return response
  38. return _wrapped_view_func
  39. def get_schema_view(info=None, url=None, patterns=None, urlconf=None, public=False, validators=None,
  40. generator_class=None, authentication_classes=None, permission_classes=None):
  41. """Create a SchemaView class with default renderers and generators.
  42. :param .Info info: information about the API; if omitted, defaults to :ref:`DEFAULT_INFO <default-swagger-settings>`
  43. :param str url: same as :class:`.OpenAPISchemaGenerator`
  44. :param patterns: same as :class:`.OpenAPISchemaGenerator`
  45. :param urlconf: same as :class:`.OpenAPISchemaGenerator`
  46. :param bool public: if False, includes only the endpoints that are accessible by the user viewing the schema
  47. :param list validators: a list of validator names to apply; the only allowed value is ``ssv``, for now
  48. :param type generator_class: schema generator class to use; should be a subclass of :class:`.OpenAPISchemaGenerator`
  49. :param list authentication_classes: authentication classes for the schema view itself
  50. :param list permission_classes: permission classes for the schema view itself
  51. :return: SchemaView class
  52. :rtype: type[drf_yasg.views.SchemaView]
  53. """
  54. _public = public
  55. _generator_class = generator_class or swagger_settings.DEFAULT_GENERATOR_CLASS
  56. _auth_classes = authentication_classes
  57. if _auth_classes is None:
  58. _auth_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
  59. _perm_classes = permission_classes
  60. if _perm_classes is None:
  61. _perm_classes = api_settings.DEFAULT_PERMISSION_CLASSES
  62. info = info or swagger_settings.DEFAULT_INFO
  63. validators = validators or []
  64. _spec_renderers = tuple(renderer.with_validators(validators) for renderer in SPEC_RENDERERS)
  65. class SchemaView(APIView):
  66. _ignore_model_permissions = True
  67. schema = None # exclude from schema
  68. public = _public
  69. generator_class = _generator_class
  70. authentication_classes = _auth_classes
  71. permission_classes = _perm_classes
  72. renderer_classes = _spec_renderers
  73. def get(self, request, version='', format=None):
  74. version = request.version or version or ''
  75. if isinstance(request.accepted_renderer, _SpecRenderer):
  76. generator = self.generator_class(info, version, url, patterns, urlconf)
  77. else:
  78. generator = self.generator_class(info, version, url, patterns=[])
  79. schema = generator.get_schema(request, self.public)
  80. if schema is None:
  81. raise exceptions.PermissionDenied() # pragma: no cover
  82. return Response(schema)
  83. @classmethod
  84. def apply_cache(cls, view, cache_timeout, cache_kwargs):
  85. """Override this method to customize how caching is applied to the view.
  86. Arguments described in :meth:`.as_cached_view`.
  87. """
  88. view = vary_on_headers('Cookie', 'Authorization')(view)
  89. view = cache_page(cache_timeout, **cache_kwargs)(view)
  90. view = deferred_never_cache(view) # disable in-browser caching
  91. return view
  92. @classmethod
  93. def as_cached_view(cls, cache_timeout=0, cache_kwargs=None, **initkwargs):
  94. """
  95. Calls .as_view() and wraps the result in a cache_page decorator.
  96. See https://docs.djangoproject.com/en/dev/topics/cache/
  97. :param int cache_timeout: same as cache_page; set to 0 for no cache
  98. :param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
  99. :param initkwargs: kwargs for .as_view()
  100. :return: a view instance
  101. """
  102. cache_kwargs = cache_kwargs or {}
  103. view = cls.as_view(**initkwargs)
  104. if cache_timeout != 0:
  105. view = cls.apply_cache(view, cache_timeout, cache_kwargs)
  106. elif cache_kwargs:
  107. warnings.warn("cache_kwargs ignored because cache_timeout is 0 (disabled)")
  108. return view
  109. @classmethod
  110. def without_ui(cls, cache_timeout=0, cache_kwargs=None):
  111. """
  112. Instantiate this view with just JSON and YAML renderers, optionally wrapped with cache_page.
  113. See https://docs.djangoproject.com/en/dev/topics/cache/.
  114. :param int cache_timeout: same as cache_page; set to 0 for no cache
  115. :param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
  116. :return: a view instance
  117. """
  118. return cls.as_cached_view(cache_timeout, cache_kwargs, renderer_classes=_spec_renderers)
  119. @classmethod
  120. def with_ui(cls, renderer='swagger', cache_timeout=0, cache_kwargs=None):
  121. """
  122. Instantiate this view with a Web UI renderer, optionally wrapped with cache_page.
  123. See https://docs.djangoproject.com/en/dev/topics/cache/.
  124. :param str renderer: UI renderer; allowed values are ``swagger``, ``redoc``
  125. :param int cache_timeout: same as cache_page; set to 0 for no cache
  126. :param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
  127. :return: a view instance
  128. """
  129. assert renderer in UI_RENDERERS, "supported default renderers are " + ", ".join(UI_RENDERERS)
  130. renderer_classes = UI_RENDERERS[renderer] + _spec_renderers
  131. return cls.as_cached_view(cache_timeout, cache_kwargs, renderer_classes=renderer_classes)
  132. return SchemaView