renderers.py 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. from django.core.exceptions import ImproperlyConfigured
  2. from django.shortcuts import resolve_url
  3. from django.template.loader import render_to_string
  4. from django.utils.encoding import force_str
  5. from django.utils.functional import Promise
  6. from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
  7. from rest_framework.utils import encoders, json
  8. from .app_settings import redoc_settings, swagger_settings
  9. from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml
  10. from .openapi import Swagger
  11. from .utils import filter_none
  12. class _SpecRenderer(BaseRenderer):
  13. """Base class for text renderers. Handles encoding and validation."""
  14. charset = 'utf-8'
  15. validators = []
  16. codec_class = None
  17. @classmethod
  18. def with_validators(cls, validators):
  19. assert all(vld in VALIDATORS for vld in validators), "allowed validators are " + ", ".join(VALIDATORS)
  20. return type(cls.__name__, (cls,), {'validators': validators})
  21. def render(self, data, media_type=None, renderer_context=None):
  22. assert self.codec_class, "must override codec_class"
  23. codec = self.codec_class(self.validators)
  24. if not isinstance(data, Swagger): # pragma: no cover
  25. # if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
  26. # in that case, it's probably better to let the default ``JSONRenderer`` render it
  27. # see https://github.com/axnsan12/drf-yasg/issues/58
  28. return JSONRenderer().render(data, media_type, renderer_context)
  29. return codec.encode(data)
  30. class OpenAPIRenderer(_SpecRenderer):
  31. """Renders the schema as a JSON document with the ``application/openapi+json`` specific mime type."""
  32. media_type = 'application/openapi+json'
  33. format = 'openapi'
  34. codec_class = OpenAPICodecJson
  35. class SwaggerJSONRenderer(_SpecRenderer):
  36. """Renders the schema as a JSON document with the generic ``application/json`` mime type."""
  37. media_type = 'application/json'
  38. format = '.json'
  39. codec_class = OpenAPICodecJson
  40. class SwaggerYAMLRenderer(_SpecRenderer):
  41. """Renders the schema as a YAML document."""
  42. media_type = 'application/yaml'
  43. format = '.yaml'
  44. codec_class = OpenAPICodecYaml
  45. class _UIRenderer(BaseRenderer):
  46. """Base class for web UI renderers. Handles loading and passing settings to the appropriate template."""
  47. media_type = 'text/html'
  48. charset = 'utf-8'
  49. template = ''
  50. def render(self, swagger, accepted_media_type=None, renderer_context=None):
  51. if not isinstance(swagger, Swagger): # pragma: no cover
  52. try:
  53. # if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
  54. # in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
  55. # see https://github.com/axnsan12/drf-yasg/issues/58
  56. return TemplateHTMLRenderer().render(swagger, accepted_media_type, renderer_context)
  57. except ImproperlyConfigured:
  58. # Fall back to using eg '404 Not Found'
  59. response = renderer_context['response']
  60. return '%d %s' % (response.status_code, response.status_text.title())
  61. self.set_context(renderer_context, swagger)
  62. return render_to_string(self.template, renderer_context, renderer_context['request'])
  63. def set_context(self, renderer_context, swagger=None):
  64. renderer_context['title'] = swagger.info.title or '' if swagger else ''
  65. renderer_context['version'] = swagger.info.version or '' if swagger else ''
  66. renderer_context['oauth2_config'] = json.dumps(self.get_oauth2_config(), cls=encoders.JSONEncoder)
  67. renderer_context['USE_SESSION_AUTH'] = swagger_settings.USE_SESSION_AUTH
  68. renderer_context.update(self.get_auth_urls())
  69. def resolve_url(self, to):
  70. if isinstance(to, Promise):
  71. to = str(to)
  72. if to is None:
  73. return None
  74. args, kwargs = None, None
  75. if not isinstance(to, str):
  76. if len(to) > 2:
  77. to, args, kwargs = to
  78. elif len(to) == 2:
  79. to, kwargs = to
  80. args = args or ()
  81. kwargs = kwargs or {}
  82. return resolve_url(to, *args, **kwargs)
  83. def get_auth_urls(self):
  84. urls = {
  85. 'LOGIN_URL': self.resolve_url(swagger_settings.LOGIN_URL),
  86. 'LOGOUT_URL': self.resolve_url(swagger_settings.LOGOUT_URL),
  87. }
  88. return filter_none(urls)
  89. def get_oauth2_config(self):
  90. data = swagger_settings.OAUTH2_CONFIG
  91. assert isinstance(data, dict), "OAUTH2_CONFIG must be a dict"
  92. return data
  93. class SwaggerUIRenderer(_UIRenderer):
  94. """Renders a swagger-ui web interface for schema browsing."""
  95. template = 'drf-yasg/swagger-ui.html'
  96. format = 'swagger'
  97. def set_context(self, renderer_context, swagger=None):
  98. super(SwaggerUIRenderer, self).set_context(renderer_context, swagger)
  99. swagger_ui_settings = self.get_swagger_ui_settings()
  100. request = renderer_context.get('request', None)
  101. oauth_redirect_url = force_str(swagger_ui_settings.get('oauth2RedirectUrl', ''))
  102. if request and oauth_redirect_url:
  103. swagger_ui_settings['oauth2RedirectUrl'] = request.build_absolute_uri(oauth_redirect_url)
  104. renderer_context['swagger_settings'] = json.dumps(swagger_ui_settings, cls=encoders.JSONEncoder)
  105. def get_swagger_ui_settings(self):
  106. data = {
  107. 'url': self.resolve_url(swagger_settings.SPEC_URL),
  108. 'operationsSorter': swagger_settings.OPERATIONS_SORTER,
  109. 'tagsSorter': swagger_settings.TAGS_SORTER,
  110. 'docExpansion': swagger_settings.DOC_EXPANSION,
  111. 'deepLinking': swagger_settings.DEEP_LINKING,
  112. 'showExtensions': swagger_settings.SHOW_EXTENSIONS,
  113. 'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING,
  114. 'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
  115. 'defaultModelsExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
  116. 'showCommonExtensions': swagger_settings.SHOW_COMMON_EXTENSIONS,
  117. 'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
  118. 'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
  119. 'displayOperationId': swagger_settings.DISPLAY_OPERATION_ID,
  120. 'persistAuth': swagger_settings.PERSIST_AUTH,
  121. 'refetchWithAuth': swagger_settings.REFETCH_SCHEMA_WITH_AUTH,
  122. 'refetchOnLogout': swagger_settings.REFETCH_SCHEMA_ON_LOGOUT,
  123. 'fetchSchemaWithQuery': swagger_settings.FETCH_SCHEMA_WITH_QUERY,
  124. }
  125. data = filter_none(data)
  126. if swagger_settings.VALIDATOR_URL != '':
  127. data['validatorUrl'] = self.resolve_url(swagger_settings.VALIDATOR_URL)
  128. return data
  129. class ReDocRenderer(_UIRenderer):
  130. """Renders a ReDoc web interface for schema browsing."""
  131. template = 'drf-yasg/redoc.html'
  132. format = 'redoc'
  133. def set_context(self, renderer_context, swagger=None):
  134. super(ReDocRenderer, self).set_context(renderer_context, swagger)
  135. renderer_context['redoc_settings'] = json.dumps(self.get_redoc_settings(), cls=encoders.JSONEncoder)
  136. def get_redoc_settings(self):
  137. data = {
  138. 'url': self.resolve_url(redoc_settings.SPEC_URL),
  139. 'lazyRendering': redoc_settings.LAZY_RENDERING,
  140. 'hideHostname': redoc_settings.HIDE_HOSTNAME,
  141. 'expandResponses': redoc_settings.EXPAND_RESPONSES,
  142. 'pathInMiddlePanel': redoc_settings.PATH_IN_MIDDLE,
  143. 'nativeScrollbars': redoc_settings.NATIVE_SCROLLBARS,
  144. 'requiredPropsFirst': redoc_settings.REQUIRED_PROPS_FIRST,
  145. 'fetchSchemaWithQuery': redoc_settings.FETCH_SCHEMA_WITH_QUERY,
  146. }
  147. return filter_none(data)
  148. class ReDocOldRenderer(ReDocRenderer):
  149. """Renders a ReDoc 1.x.x web interface for schema browsing."""
  150. template = 'drf-yasg/redoc-old.html'