import six
from django.shortcuts import render, resolve_url
from django.utils.functional import Promise
from rest_framework.renderers import BaseRenderer, JSONRenderer, TemplateHTMLRenderer
from rest_framework.utils import json
from .app_settings import redoc_settings, swagger_settings
from .codecs import VALIDATORS, OpenAPICodecJson, OpenAPICodecYaml
from .openapi import Swagger
from .utils import filter_none
class _SpecRenderer(BaseRenderer):
"""Base class for text renderers. Handles encoding and validation."""
charset = 'utf-8'
validators = []
codec_class = None
@classmethod
def with_validators(cls, validators):
assert all(vld in VALIDATORS for vld in validators), "allowed validators are " + ", ".join(VALIDATORS)
return type(cls.__name__, (cls,), {'validators': validators})
def render(self, data, media_type=None, renderer_context=None):
assert self.codec_class, "must override codec_class"
codec = self.codec_class(self.validators)
if not isinstance(data, Swagger): # pragma: no cover
# if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
# in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
# see https://github.com/axnsan12/drf-yasg/issues/58
return JSONRenderer().render(data, media_type, renderer_context)
return codec.encode(data)
[docs]class OpenAPIRenderer(_SpecRenderer):
"""Renders the schema as a JSON document with the ``application/openapi+json`` specific mime type."""
media_type = 'application/openapi+json'
format = 'openapi'
codec_class = OpenAPICodecJson
[docs]class SwaggerJSONRenderer(_SpecRenderer):
"""Renders the schema as a JSON document with the generic ``application/json`` mime type."""
media_type = 'application/json'
format = '.json'
codec_class = OpenAPICodecJson
[docs]class SwaggerYAMLRenderer(_SpecRenderer):
"""Renders the schema as a YAML document."""
media_type = 'application/yaml'
format = '.yaml'
codec_class = OpenAPICodecYaml
class _UIRenderer(BaseRenderer):
"""Base class for web UI renderers. Handles loading and passing settings to the appropriate template."""
media_type = 'text/html'
charset = 'utf-8'
template = ''
def render(self, swagger, accepted_media_type=None, renderer_context=None):
if not isinstance(swagger, Swagger): # pragma: no cover
# if `swagger` is not a ``Swagger`` object, it means we somehow got a non-success ``Response``
# in that case, it's probably better to let the default ``TemplateHTMLRenderer`` render it
# see https://github.com/axnsan12/drf-yasg/issues/58
return TemplateHTMLRenderer().render(swagger, accepted_media_type, renderer_context)
self.set_context(renderer_context, swagger)
return render(
renderer_context['request'],
self.template,
renderer_context
)
def set_context(self, renderer_context, swagger):
renderer_context['title'] = swagger.info.title
renderer_context['version'] = swagger.info.version
renderer_context['oauth2_config'] = json.dumps(self.get_oauth2_config())
renderer_context['USE_SESSION_AUTH'] = swagger_settings.USE_SESSION_AUTH
renderer_context.update(self.get_auth_urls())
def resolve_url(self, to):
if isinstance(to, Promise):
to = str(to)
if to is None:
return None
args, kwargs = None, None
if not isinstance(to, six.string_types):
if len(to) > 2:
to, args, kwargs = to
elif len(to) == 2:
to, kwargs = to
args = args or ()
kwargs = kwargs or {}
return resolve_url(to, *args, **kwargs)
def get_auth_urls(self):
urls = {
'LOGIN_URL': self.resolve_url(swagger_settings.LOGIN_URL),
'LOGOUT_URL': self.resolve_url(swagger_settings.LOGOUT_URL),
}
return filter_none(urls)
def get_oauth2_config(self):
data = swagger_settings.OAUTH2_CONFIG
assert isinstance(data, dict), "OAUTH2_CONFIG must be a dict"
return data
[docs]class SwaggerUIRenderer(_UIRenderer):
"""Renders a swagger-ui web interface for schema browisng."""
template = 'drf-yasg/swagger-ui.html'
format = 'swagger'
[docs] def set_context(self, renderer_context, swagger):
super(SwaggerUIRenderer, self).set_context(renderer_context, swagger)
renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings())
[docs] def get_swagger_ui_settings(self):
data = {
'url': self.resolve_url(swagger_settings.SPEC_URL),
'operationsSorter': swagger_settings.OPERATIONS_SORTER,
'tagsSorter': swagger_settings.TAGS_SORTER,
'docExpansion': swagger_settings.DOC_EXPANSION,
'deepLinking': swagger_settings.DEEP_LINKING,
'showExtensions': swagger_settings.SHOW_EXTENSIONS,
'defaultModelRendering': swagger_settings.DEFAULT_MODEL_RENDERING,
'defaultModelExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
'defaultModelsExpandDepth': swagger_settings.DEFAULT_MODEL_DEPTH,
'showCommonExtensions': swagger_settings.SHOW_COMMON_EXTENSIONS,
'oauth2RedirectUrl': swagger_settings.OAUTH2_REDIRECT_URL,
'supportedSubmitMethods': swagger_settings.SUPPORTED_SUBMIT_METHODS,
'displayOperationId': swagger_settings.DISPLAY_OPERATION_ID,
}
data = filter_none(data)
if swagger_settings.VALIDATOR_URL != '':
data['validatorUrl'] = self.resolve_url(swagger_settings.VALIDATOR_URL)
return data
[docs]class ReDocRenderer(_UIRenderer):
"""Renders a ReDoc web interface for schema browisng."""
template = 'drf-yasg/redoc.html'
format = 'redoc'
[docs] def set_context(self, renderer_context, swagger):
super(ReDocRenderer, self).set_context(renderer_context, swagger)
renderer_context['redoc_settings'] = json.dumps(self.get_redoc_settings())
[docs] def get_redoc_settings(self):
data = {
'url': self.resolve_url(redoc_settings.SPEC_URL),
'lazyRendering': redoc_settings.LAZY_RENDERING,
'hideHostname': redoc_settings.HIDE_HOSTNAME,
'expandResponses': redoc_settings.EXPAND_RESPONSES,
'pathInMiddlePanel': redoc_settings.PATH_IN_MIDDLE,
'nativeScrollbars': redoc_settings.NATIVE_SCROLLBARS,
'requiredPropsFirst': redoc_settings.REQUIRED_PROPS_FIRST,
}
return filter_none(data)
[docs]class ReDocOldRenderer(ReDocRenderer):
"""Renders a ReDoc 1.x.x web interface for schema browisng."""
template = 'drf-yasg/redoc-old.html'