Restructure project to add test support
* separated drf_swagger and testproj modules, moved both out of project root * added testing support via pytest and tox * enabled Travis CI * integrated coverage & Coveralls
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
# coding=utf-8
|
||||
__author__ = """Cristi V."""
|
||||
__email__ = 'cristi@cvjd.me'
|
||||
__version__ = '1.0.0rc1'
|
||||
@@ -0,0 +1,43 @@
|
||||
from django.conf import settings
|
||||
from rest_framework.settings import APISettings
|
||||
|
||||
SWAGGER_DEFAULTS = {
|
||||
'USE_SESSION_AUTH': True,
|
||||
'SECURITY_DEFINITIONS': {
|
||||
'basic': {
|
||||
'type': 'basic'
|
||||
}
|
||||
},
|
||||
'LOGIN_URL': getattr(settings, 'LOGIN_URL', None),
|
||||
'LOGOUT_URL': getattr(settings, 'LOGOUT_URL', None),
|
||||
'VALIDATOR_URL': '',
|
||||
|
||||
'OPERATIONS_SORTER': None,
|
||||
'TAGS_SORTER': None,
|
||||
'DOC_EXPANSION': 'list',
|
||||
'DEEP_LINKING': False,
|
||||
'SHOW_EXTENSIONS': True,
|
||||
'DEFAULT_MODEL_RENDERING': 'model',
|
||||
'DEFAULT_MODEL_DEPTH': 2,
|
||||
}
|
||||
|
||||
REDOC_DEFAULTS = {
|
||||
'LAZY_RENDERING': True,
|
||||
'HIDE_HOSTNAME': False,
|
||||
'EXPAND_RESPONSES': 'all',
|
||||
'PATH_IN_MIDDLE': False,
|
||||
}
|
||||
|
||||
IMPORT_STRINGS = []
|
||||
|
||||
swagger_settings = APISettings(
|
||||
user_settings=getattr(settings, 'SWAGGER_SETTINGS', {}),
|
||||
defaults=SWAGGER_DEFAULTS,
|
||||
import_strings=IMPORT_STRINGS,
|
||||
)
|
||||
|
||||
redoc_settings = APISettings(
|
||||
user_settings=getattr(settings, 'REDOC_SETTINGS', {}),
|
||||
defaults=REDOC_DEFAULTS,
|
||||
import_strings=IMPORT_STRINGS,
|
||||
)
|
||||
@@ -0,0 +1,152 @@
|
||||
import json
|
||||
from collections import OrderedDict
|
||||
|
||||
from coreapi.codecs import BaseCodec
|
||||
from coreapi.compat import force_bytes, urlparse
|
||||
from drf_swagger.app_settings import swagger_settings
|
||||
from openapi_codec import encode
|
||||
from ruamel import yaml
|
||||
|
||||
from . import openapi
|
||||
|
||||
|
||||
class SwaggerValidationError(Exception):
|
||||
def __init__(self, msg, validator_name, spec, *args) -> None:
|
||||
super(SwaggerValidationError, self).__init__(msg, *args)
|
||||
self.validator_name = validator_name
|
||||
self.spec = spec
|
||||
|
||||
def __str__(self):
|
||||
return str(self.validator_name) + ": " + super(SwaggerValidationError, self).__str__()
|
||||
|
||||
|
||||
def _validate_flex(spec):
|
||||
from flex.core import parse as validate_flex
|
||||
from flex.exceptions import ValidationError
|
||||
try:
|
||||
validate_flex(spec)
|
||||
except ValidationError as ex:
|
||||
raise SwaggerValidationError(str(ex), 'flex', spec) from ex
|
||||
|
||||
|
||||
def _validate_swagger_spec_validator(spec):
|
||||
from swagger_spec_validator.validator20 import validate_spec as validate_ssv
|
||||
from swagger_spec_validator.common import SwaggerValidationError as SSVErr
|
||||
try:
|
||||
validate_ssv(spec)
|
||||
except SSVErr as ex:
|
||||
raise SwaggerValidationError(str(ex), 'swagger_spec_validator', spec) from ex
|
||||
|
||||
|
||||
VALIDATORS = {
|
||||
'flex': _validate_flex,
|
||||
'swagger_spec_validator': _validate_swagger_spec_validator,
|
||||
'ssv': _validate_swagger_spec_validator,
|
||||
}
|
||||
|
||||
|
||||
class _OpenAPICodec(BaseCodec):
|
||||
format = 'openapi'
|
||||
|
||||
def __init__(self, validators):
|
||||
self._validators = validators
|
||||
|
||||
@property
|
||||
def validators(self):
|
||||
return self._validators
|
||||
|
||||
def encode(self, document, **options):
|
||||
if not isinstance(document, openapi.Swagger):
|
||||
raise TypeError('Expected a `openapi.Swagger` instance')
|
||||
|
||||
spec = self.generate_swagger_object(document)
|
||||
for validator in self.validators:
|
||||
VALIDATORS[validator](spec)
|
||||
return force_bytes(self._dump_spec(spec))
|
||||
|
||||
def _dump_spec(self, spec):
|
||||
return NotImplementedError("override this method")
|
||||
|
||||
def generate_swagger_object(self, swagger):
|
||||
"""
|
||||
Generates root of the Swagger spec.
|
||||
|
||||
:param openapi.Swagger swagger:
|
||||
:return OrderedDict: swagger spec as dict
|
||||
"""
|
||||
parsed_url = urlparse.urlparse(swagger.url)
|
||||
|
||||
spec = OrderedDict()
|
||||
|
||||
spec['swagger'] = '2.0'
|
||||
spec['info'] = swagger.info.to_swagger(swagger.version)
|
||||
|
||||
if parsed_url.netloc:
|
||||
spec['host'] = parsed_url.netloc
|
||||
if parsed_url.scheme:
|
||||
spec['schemes'] = [parsed_url.scheme]
|
||||
spec['basePath'] = '/'
|
||||
|
||||
spec['paths'] = encode._get_paths_object(swagger)
|
||||
|
||||
spec['securityDefinitions'] = swagger_settings.SECURITY_DEFINITIONS
|
||||
|
||||
return spec
|
||||
|
||||
|
||||
class OpenAPICodecJson(_OpenAPICodec):
|
||||
media_type = 'application/openapi+json'
|
||||
|
||||
def _dump_spec(self, spec):
|
||||
return json.dumps(spec)
|
||||
|
||||
|
||||
class SaneYamlDumper(yaml.SafeDumper):
|
||||
def increase_indent(self, flow=False, indentless=False, **kwargs):
|
||||
"""https://stackoverflow.com/a/39681672
|
||||
Indent list elements.
|
||||
"""
|
||||
return super(SaneYamlDumper, self).increase_indent(flow=flow, indentless=False, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def represent_odict(dump, mapping, flow_style=None):
|
||||
"""https://gist.github.com/miracle2k/3184458
|
||||
Make PyYAML output an OrderedDict.
|
||||
|
||||
It will do so fine if you use yaml.dump(), but that generates ugly,
|
||||
non-standard YAML code.
|
||||
|
||||
To use yaml.safe_dump(), you need the following.
|
||||
"""
|
||||
tag = u'tag:yaml.org,2002:map'
|
||||
value = []
|
||||
node = yaml.MappingNode(tag, value, flow_style=flow_style)
|
||||
if dump.alias_key is not None:
|
||||
dump.represented_objects[dump.alias_key] = node
|
||||
best_style = True
|
||||
if hasattr(mapping, 'items'):
|
||||
mapping = mapping.items()
|
||||
for item_key, item_value in mapping:
|
||||
node_key = dump.represent_data(item_key)
|
||||
node_value = dump.represent_data(item_value)
|
||||
if not (isinstance(node_key, yaml.ScalarNode) and not node_key.style):
|
||||
best_style = False
|
||||
if not (isinstance(node_value, yaml.ScalarNode) and not node_value.style):
|
||||
best_style = False
|
||||
value.append((node_key, node_value))
|
||||
if flow_style is None:
|
||||
if dump.default_flow_style is not None:
|
||||
node.flow_style = dump.default_flow_style
|
||||
else:
|
||||
node.flow_style = best_style
|
||||
return node
|
||||
|
||||
|
||||
SaneYamlDumper.add_representer(OrderedDict, SaneYamlDumper.represent_odict)
|
||||
|
||||
|
||||
class OpenAPICodecYaml(_OpenAPICodec):
|
||||
media_type = 'application/openapi+yaml'
|
||||
|
||||
def _dump_spec(self, spec):
|
||||
return yaml.dump(spec, Dumper=SaneYamlDumper, default_flow_style=False, encoding='utf-8')
|
||||
@@ -0,0 +1,15 @@
|
||||
from rest_framework.schemas import SchemaGenerator as _SchemaGenerator
|
||||
|
||||
from . import openapi
|
||||
|
||||
|
||||
class OpenAPISchemaGenerator(_SchemaGenerator):
|
||||
def __init__(self, info, version, url=None, patterns=None, urlconf=None):
|
||||
super(OpenAPISchemaGenerator, self).__init__(info.title, url, info.description, patterns, urlconf)
|
||||
self.info = info
|
||||
self.version = version
|
||||
|
||||
def get_schema(self, request=None, public=False):
|
||||
document = super(OpenAPISchemaGenerator, self).get_schema(request, public)
|
||||
swagger = openapi.Swagger.from_coreapi(document, self.info, self.version)
|
||||
return swagger
|
||||
@@ -0,0 +1,5 @@
|
||||
from rest_framework.schemas import AutoSchema
|
||||
|
||||
|
||||
class SwaggerAutoSchema(AutoSchema):
|
||||
pass
|
||||
@@ -0,0 +1,136 @@
|
||||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import coreapi
|
||||
|
||||
|
||||
class Contact(object):
|
||||
"""Swagger Contact object
|
||||
At least one of the following fields is required:
|
||||
|
||||
:param str name: contact name
|
||||
:param str url: contact url
|
||||
:param str email: contact e-mail
|
||||
"""
|
||||
def __init__(self, name=None, url=None, email=None):
|
||||
self.name = name
|
||||
self.url = url
|
||||
self.email = email
|
||||
if name is None and url is None and email is None:
|
||||
raise ValueError("one of name, url or email is requires for Swagger Contact object")
|
||||
|
||||
def to_swagger(self):
|
||||
contact = OrderedDict()
|
||||
if self.name is not None:
|
||||
contact['name'] = self.name
|
||||
if self.url is not None:
|
||||
contact['url'] = self.url
|
||||
if self.email is not None:
|
||||
contact['email'] = self.email
|
||||
|
||||
return contact
|
||||
|
||||
|
||||
class License(object):
|
||||
"""Swagger License object
|
||||
|
||||
:param str name: Requird. License name
|
||||
:param str url: link to detailed license information
|
||||
"""
|
||||
def __init__(self, name, url=None):
|
||||
self.name = name
|
||||
self.url = url
|
||||
if name is None:
|
||||
raise ValueError("name is required for Swagger License object")
|
||||
|
||||
def to_swagger(self):
|
||||
license = OrderedDict()
|
||||
license['name'] = self.name
|
||||
if self.url is not None:
|
||||
license['url'] = self.url
|
||||
|
||||
return license
|
||||
|
||||
|
||||
class Info(object):
|
||||
"""Swagger Info object
|
||||
|
||||
:param str title: Required. API title.
|
||||
:param str default_version: Required. API version string (not to be confused with Swagger spec version)
|
||||
:param str description: API description; markdown supported
|
||||
:param str terms_of_service: API terms of service; should be a URL
|
||||
:param Contact contact: contact object
|
||||
:param License license: license object
|
||||
"""
|
||||
def __init__(self, title, default_version, description=None, terms_of_service=None, contact=None, license=None):
|
||||
if title is None or default_version is None:
|
||||
raise ValueError("title and version are required for Swagger info object")
|
||||
if contact is not None and not isinstance(contact, Contact):
|
||||
raise ValueError("contact must be a Contact object")
|
||||
if license is not None and not isinstance(license, License):
|
||||
raise ValueError("license must be a License object")
|
||||
self.title = title
|
||||
self.default_version = default_version
|
||||
self.description = description
|
||||
self.terms_of_service = terms_of_service
|
||||
self.contact = contact
|
||||
self.license = license
|
||||
|
||||
def to_swagger(self, version):
|
||||
info = OrderedDict()
|
||||
info['title'] = self.title
|
||||
if self.description is not None:
|
||||
info['description'] = self.description
|
||||
if self.terms_of_service is not None:
|
||||
info['termsOfService'] = self.terms_of_service
|
||||
if self.contact is not None:
|
||||
info['contact'] = self.contact.to_swagger()
|
||||
if self.license is not None:
|
||||
info['license'] = self.license.to_swagger()
|
||||
info['version'] = version or self.default_version
|
||||
return info
|
||||
|
||||
|
||||
class Swagger(coreapi.Document):
|
||||
@classmethod
|
||||
def from_coreapi(cls, document, info, version):
|
||||
"""
|
||||
Create an openapi.Swagger from the fields of a coreapi.Document.
|
||||
|
||||
:param coreapi.Document document: source coreapi.Document
|
||||
:param openapi.Info info: Swagger info object
|
||||
:param string version: API version string
|
||||
:return: an openapi.Swagger
|
||||
"""
|
||||
if document.title and document.title != info.title:
|
||||
warnings.warn("document title is overriden by Swagger Info")
|
||||
if document.description and document.description != info.description:
|
||||
warnings.warn("document description is overriden by Swagger Info")
|
||||
return Swagger(
|
||||
info=info,
|
||||
version=version,
|
||||
url=document.url,
|
||||
media_type=document.media_type,
|
||||
content=document.data
|
||||
)
|
||||
|
||||
def __init__(self, info=None, version=None, url=None, media_type=None, content=None):
|
||||
super(Swagger, self).__init__(url, info.title, info.description, media_type, content)
|
||||
self._info = info
|
||||
self._version = version
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return self._info
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
return self._version
|
||||
|
||||
|
||||
class Field(coreapi.Field):
|
||||
pass
|
||||
|
||||
|
||||
class Link(coreapi.Link):
|
||||
pass
|
||||
@@ -0,0 +1,107 @@
|
||||
from django.shortcuts import render, resolve_url
|
||||
from rest_framework.renderers import BaseRenderer
|
||||
from rest_framework.utils import json
|
||||
|
||||
from .app_settings import swagger_settings, redoc_settings
|
||||
from .codecs import OpenAPICodecJson, VALIDATORS, OpenAPICodecYaml
|
||||
|
||||
|
||||
class _SpecRenderer(BaseRenderer):
|
||||
charset = None
|
||||
validators = ['flex', 'ssv']
|
||||
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)
|
||||
return codec.encode(data)
|
||||
|
||||
|
||||
class OpenAPIRenderer(_SpecRenderer):
|
||||
media_type = 'application/openapi+json'
|
||||
format = 'openapi'
|
||||
codec_class = OpenAPICodecJson
|
||||
|
||||
|
||||
class SwaggerJSONRenderer(_SpecRenderer):
|
||||
media_type = 'application/json'
|
||||
format = '.json'
|
||||
codec_class = OpenAPICodecJson
|
||||
|
||||
|
||||
class SwaggerYAMLRenderer(_SpecRenderer):
|
||||
media_type = 'application/yaml'
|
||||
format = '.yaml'
|
||||
codec_class = OpenAPICodecYaml
|
||||
|
||||
|
||||
class _UIRenderer(BaseRenderer):
|
||||
media_type = 'text/html'
|
||||
charset = 'utf-8'
|
||||
template = ''
|
||||
|
||||
def render(self, data, accepted_media_type=None, renderer_context=None):
|
||||
self.set_context(renderer_context, data)
|
||||
return render(
|
||||
renderer_context['request'],
|
||||
self.template,
|
||||
renderer_context
|
||||
)
|
||||
|
||||
def set_context(self, renderer_context, data):
|
||||
renderer_context['title'] = data.title
|
||||
renderer_context['version'] = data.version
|
||||
renderer_context['swagger_settings'] = json.dumps(self.get_swagger_ui_settings())
|
||||
renderer_context['redoc_settings'] = json.dumps(self.get_redoc_settings())
|
||||
renderer_context['USE_SESSION_AUTH'] = swagger_settings.USE_SESSION_AUTH
|
||||
renderer_context.update(self.get_auth_urls())
|
||||
|
||||
def get_auth_urls(self):
|
||||
urls = {}
|
||||
if swagger_settings.LOGIN_URL is not None:
|
||||
urls['LOGIN_URL'] = resolve_url(swagger_settings.LOGIN_URL)
|
||||
if swagger_settings.LOGOUT_URL is not None:
|
||||
urls['LOGOUT_URL'] = resolve_url(swagger_settings.LOGOUT_URL)
|
||||
|
||||
return urls
|
||||
|
||||
def get_swagger_ui_settings(self):
|
||||
data = {
|
||||
'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,
|
||||
}
|
||||
data = {k: v for k, v in data.items() if v is not None}
|
||||
if swagger_settings.VALIDATOR_URL != '':
|
||||
data['validatorUrl'] = swagger_settings.VALIDATOR_URL
|
||||
|
||||
return data
|
||||
|
||||
def get_redoc_settings(self):
|
||||
data = {
|
||||
'lazyRendering': redoc_settings.LAZY_RENDERING,
|
||||
'hideHostname': redoc_settings.HIDE_HOSTNAME,
|
||||
'expandResponses': redoc_settings.EXPAND_RESPONSES,
|
||||
'pathInMiddle': redoc_settings.PATH_IN_MIDDLE,
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class SwaggerUIRenderer(_UIRenderer):
|
||||
template = 'drf-swagger/swagger-ui.html'
|
||||
format = 'swagger'
|
||||
|
||||
|
||||
class ReDocRenderer(_UIRenderer):
|
||||
template = 'drf-swagger/redoc.html'
|
||||
format = 'redoc'
|
||||
@@ -0,0 +1,4 @@
|
||||
// insertion-query v1.0.3 (2016-01-20)
|
||||
// license:MIT
|
||||
// Zbyszek Tenerowicz <naugtur@gmail.com> (http://naugtur.pl/)
|
||||
var insertionQ=function(){"use strict";function a(a,b){var d,e="insQ_"+g++,f=function(a){(a.animationName===e||a[i]===e)&&(c(a.target)||b(a.target))};d=document.createElement("style"),d.innerHTML="@"+j+"keyframes "+e+" { from { outline: 1px solid transparent } to { outline: 0px solid transparent } }\n"+a+" { animation-duration: 0.001s; animation-name: "+e+"; "+j+"animation-duration: 0.001s; "+j+"animation-name: "+e+"; } ",document.head.appendChild(d);var h=setTimeout(function(){document.addEventListener("animationstart",f,!1),document.addEventListener("MSAnimationStart",f,!1),document.addEventListener("webkitAnimationStart",f,!1)},n.timeout);return{destroy:function(){clearTimeout(h),d&&(document.head.removeChild(d),d=null),document.removeEventListener("animationstart",f),document.removeEventListener("MSAnimationStart",f),document.removeEventListener("webkitAnimationStart",f)}}}function b(a){a.QinsQ=!0}function c(a){return n.strictlyNew&&a.QinsQ===!0}function d(a){return c(a.parentNode)?a:d(a.parentNode)}function e(a){for(b(a),a=a.firstChild;a;a=a.nextSibling)void 0!==a&&1===a.nodeType&&e(a)}function f(f,g){var h=[],i=function(){var a;return function(){clearTimeout(a),a=setTimeout(function(){h.forEach(e),g(h),h=[]},10)}}();return a(f,function(a){if(!c(a)){b(a);var e=d(a);h.indexOf(e)<0&&h.push(e),i()}})}var g=100,h=!1,i="animationName",j="",k="Webkit Moz O ms Khtml".split(" "),l="",m=document.createElement("div"),n={strictlyNew:!0,timeout:20};if(m.style.animationName&&(h=!0),h===!1)for(var o=0;o<k.length;o++)if(void 0!==m.style[k[o]+"AnimationName"]){l=k[o],i=l+"AnimationName",j="-"+l.toLowerCase()+"-",h=!0;break}var p=function(b){return h&&b.match(/[^{}]/)?(n.strictlyNew&&e(document.body),{every:function(c){return a(b,c)},summary:function(a){return f(b,a)}}):!1};return p.config=function(a){for(var b in a)a.hasOwnProperty(b)&&(n[b]=a[b])},p}();"undefined"!=typeof module&&"undefined"!=typeof module.exports&&(module.exports=insertionQ);
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,2 @@
|
||||
README.md
|
||||
deploy.sh
|
||||
@@ -0,0 +1,22 @@
|
||||
# Swagger UI Dist
|
||||
[](http://badge.fury.io/js/swagger-ui-dist)
|
||||
|
||||
# API
|
||||
|
||||
This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module.
|
||||
Use `swagger-ui` instead, if you'd like to have npm install dependencies for you.
|
||||
|
||||
`SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported:
|
||||
```javascript
|
||||
import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist"
|
||||
```
|
||||
|
||||
To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method:
|
||||
|
||||
```javascript
|
||||
const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath()
|
||||
|
||||
// then instantiate server that serves files from the swaggerUiAssetPath
|
||||
```
|
||||
|
||||
For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository.
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* getAbsoluteFSPath
|
||||
* @return {string} When run in NodeJS env, returns the absolute path to the current directory
|
||||
* When run outside of NodeJS, will return an error message
|
||||
*/
|
||||
const getAbsoluteFSPath = function () {
|
||||
// detect whether we are running in a browser or nodejs
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
return require("path").resolve(__dirname)
|
||||
}
|
||||
throw new Error('getAbsoluteFSPath can only be called within a Nodejs environment');
|
||||
}
|
||||
|
||||
module.exports = getAbsoluteFSPath
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,17 @@
|
||||
try {
|
||||
module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js")
|
||||
module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js")
|
||||
} catch(e) {
|
||||
// swallow the error if there's a problem loading the assets.
|
||||
// allows this module to support providing the assets for browserish contexts,
|
||||
// without exploding in a Node context.
|
||||
//
|
||||
// see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388
|
||||
// for more information.
|
||||
}
|
||||
|
||||
// `absolutePath` and `getAbsoluteFSPath` are both here because at one point,
|
||||
// we documented having one and actually implemented the other.
|
||||
// They were both retained so we don't break anyone's code.
|
||||
module.exports.absolutePath = require("./absolute-path.js")
|
||||
module.exports.getAbsoluteFSPath = require("./absolute-path.js")
|
||||
@@ -0,0 +1,60 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: "Authorization failed: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"_from": "swagger-ui-dist",
|
||||
"_id": "swagger-ui-dist@3.5.0",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY=",
|
||||
"_location": "/swagger-ui-dist",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "tag",
|
||||
"registry": true,
|
||||
"raw": "swagger-ui-dist",
|
||||
"name": "swagger-ui-dist",
|
||||
"escapedName": "swagger-ui-dist",
|
||||
"rawSpec": "",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "latest"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"#USER",
|
||||
"/"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
|
||||
"_shasum": "26ebf3311a9a60fe9d170612eed5282a65bc9226",
|
||||
"_spec": "swagger-ui-dist",
|
||||
"_where": "C:\\Projects\\drf_openapi",
|
||||
"bugs": {
|
||||
"url": "https://github.com/swagger-api/swagger-ui/issues"
|
||||
},
|
||||
"bundleDependencies": false,
|
||||
"contributors": [
|
||||
{
|
||||
"url": "in alphabetical order"
|
||||
},
|
||||
{
|
||||
"name": "Anna Bodnia",
|
||||
"email": "anna.bodnia@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Buu Nguyen",
|
||||
"email": "buunguyen@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Josh Ponelat",
|
||||
"email": "jponelat@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Kyle Shockey",
|
||||
"email": "kyleshockey1@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Robert Barnwell",
|
||||
"email": "robert@robertismy.name"
|
||||
},
|
||||
{
|
||||
"name": "Sahar Jafari",
|
||||
"email": "shr.jafari@gmail.com"
|
||||
}
|
||||
],
|
||||
"dependencies": {},
|
||||
"deprecated": false,
|
||||
"description": "[](http://badge.fury.io/js/swagger-ui-dist)",
|
||||
"devDependencies": {},
|
||||
"homepage": "https://github.com/swagger-api/swagger-ui#readme",
|
||||
"license": "Apache-2.0",
|
||||
"main": "index.js",
|
||||
"name": "swagger-ui-dist",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@github.com/swagger-api/swagger-ui.git"
|
||||
},
|
||||
"version": "3.5.0"
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":[],"names":[],"mappings":"","file":"swagger-ui.css","sourceRoot":""}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,43 @@
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{ title }}</title>
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
{% if not request.version %}
|
||||
span.api-info-version {
|
||||
display: none;
|
||||
}
|
||||
{% endif %}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
||||
var specURL = currentPath + '?format=openapi';
|
||||
var redoc = document.createElement("redoc");
|
||||
redoc.setAttribute("spec-url", specURL);
|
||||
|
||||
var redocSettings = {};
|
||||
redocSettings = {{ redoc_settings | safe }};
|
||||
if (redocSettings.lazyRendering) {
|
||||
redoc.setAttribute("lazy-rendering", '');
|
||||
}
|
||||
if (redocSettings.pathInMiddle) {
|
||||
redoc.setAttribute("path-in-middle-panel", '');
|
||||
}
|
||||
if (redocSettings.hideHostname) {
|
||||
redoc.setAttribute("hide-hostname", '');
|
||||
}
|
||||
redoc.setAttribute("expand-responses", redocSettings.expandResponses);
|
||||
document.body.appendChild(redoc);
|
||||
</script>
|
||||
<script src="{% static 'drf-swagger/redoc/redoc.min.js' %}"> </script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,203 @@
|
||||
<!-- HTML for static distribution bundle build -->
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{ title }}</title>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Open+Sans:400,700|Source+Code+Pro:300,600|Titillium+Web:400,600,700"
|
||||
rel="stylesheet">
|
||||
<link rel="stylesheet" type="text/css" href="{% static 'drf-swagger/swagger-ui-dist/swagger-ui.css' %}">
|
||||
<link rel="icon" type="image/png" href="{% static 'drf-swagger/swagger-ui-dist/favicon-32x32.png' %}"
|
||||
sizes="32x32"/>
|
||||
<link rel="icon" type="image/png" href="{% static 'drf-swagger/swagger-ui-dist/favicon-16x16.png' %}"
|
||||
sizes="16x16"/>
|
||||
<style>
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
#django-session-auth {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
#django-session-auth.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#django-session-auth > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#django-session-auth .btn.authorize {
|
||||
padding: 10px 23px;
|
||||
}
|
||||
|
||||
#django-session-auth .btn.authorize a {
|
||||
color: #49cc90;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#django-session-auth .hello {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
#django-session-auth .hello .django-session {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: inline;
|
||||
padding: .2em .6em .3em;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
vertical-align: baseline;
|
||||
border-radius: .25em;
|
||||
}
|
||||
|
||||
.label-primary {
|
||||
background-color: #337ab7;
|
||||
}
|
||||
|
||||
.divider {
|
||||
margin-right: 8px;
|
||||
background: #16222c44;
|
||||
width: 2px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0">
|
||||
<defs>
|
||||
<symbol viewBox="0 0 20 20" id="unlocked">
|
||||
<path
|
||||
d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V6h2v-.801C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="locked">
|
||||
<path
|
||||
d="M15.8 8H14V5.6C14 2.703 12.665 1 10 1 7.334 1 6 2.703 6 5.6V8H4c-.553 0-1 .646-1 1.199V17c0 .549.428 1.139.951 1.307l1.197.387C5.672 18.861 6.55 19 7.1 19h5.8c.549 0 1.428-.139 1.951-.307l1.196-.387c.524-.167.953-.757.953-1.306V9.199C17 8.646 16.352 8 15.8 8zM12 8H8V5.199C8 3.754 8.797 3 10 3c1.203 0 2 .754 2 2.199V8z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="close">
|
||||
<path
|
||||
d="M14.348 14.849c-.469.469-1.229.469-1.697 0L10 11.819l-2.651 3.029c-.469.469-1.229.469-1.697 0-.469-.469-.469-1.229 0-1.697l2.758-3.15-2.759-3.152c-.469-.469-.469-1.228 0-1.697.469-.469 1.228-.469 1.697 0L10 8.183l2.651-3.031c.469-.469 1.228-.469 1.697 0 .469.469.469 1.229 0 1.697l-2.758 3.152 2.758 3.15c.469.469.469 1.229 0 1.698z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow">
|
||||
<path
|
||||
d="M13.25 10L6.109 2.58c-.268-.27-.268-.707 0-.979.268-.27.701-.27.969 0l7.83 7.908c.268.271.268.709 0 .979l-7.83 7.908c-.268.271-.701.27-.969 0-.268-.269-.268-.707 0-.979L13.25 10z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 20 20" id="large-arrow-down">
|
||||
<path
|
||||
d="M17.418 6.109c.272-.268.709-.268.979 0s.271.701 0 .969l-7.908 7.83c-.27.268-.707.268-.979 0l-7.908-7.83c-.27-.268-.27-.701 0-.969.271-.268.709-.268.979 0L10 13.25l7.418-7.141z"></path>
|
||||
</symbol>
|
||||
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="jump-to">
|
||||
<path d="M19 7v4H5.83l3.58-3.59L8 6l-6 6 6 6 1.41-1.41L5.83 13H21V7z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol viewBox="0 0 24 24" id="expand">
|
||||
<path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z"></path>
|
||||
</symbol>
|
||||
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="{% static 'drf-swagger/swagger-ui-dist/swagger-ui-bundle.js' %}"></script>
|
||||
<script src="{% static 'drf-swagger/swagger-ui-dist/swagger-ui-standalone-preset.js' %}"></script>
|
||||
<script src="{% static 'drf-swagger/insQ.min.js' %}"></script>
|
||||
<script>
|
||||
window.onload = function () {
|
||||
var currentPath = window.location.protocol + "//" + window.location.host + window.location.pathname;
|
||||
var specURL = currentPath + '?format=openapi';
|
||||
var swaggerConfig = {
|
||||
url: specURL,
|
||||
dom_id: '#swagger-ui',
|
||||
displayOperationId: true,
|
||||
displayRequestDuration: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
};
|
||||
|
||||
var swaggerSettings = {};
|
||||
swaggerSettings = {{ swagger_settings | safe }};
|
||||
console.log(swaggerSettings);
|
||||
for (var p in swaggerSettings) {
|
||||
if (swaggerSettings.hasOwnProperty(p)) {
|
||||
swaggerConfig[p] = swaggerSettings[p];
|
||||
}
|
||||
}
|
||||
window.ui = SwaggerUIBundle(swaggerConfig);
|
||||
|
||||
insertionQ('.auth-wrapper .authorize').every(function () {
|
||||
var authWrapper = document.querySelector('.auth-wrapper');
|
||||
var authorizeButton = document.querySelector('.auth-wrapper .authorize');
|
||||
var djangoSessionAuth = document.querySelector('#django-session-auth');
|
||||
|
||||
authWrapper.insertBefore(djangoSessionAuth, authorizeButton);
|
||||
djangoSessionAuth.classList.remove("hidden");
|
||||
|
||||
var divider = document.createElement("div");
|
||||
divider.classList.add("divider");
|
||||
authWrapper.insertBefore(divider, authorizeButton);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<div id="django-session-auth" class="hidden">
|
||||
{% if USE_SESSION_AUTH %}
|
||||
{% csrf_token %}
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class="hello">
|
||||
<span class="django-session">Django</span> <span class="label label-primary">{{ request.user }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.is_authenticated %}
|
||||
<div class='btn authorize'>
|
||||
<a id="auth" class="header__btn" href="{{ LOGOUT_URL }}?next={{ request.path }}" data-sw-translate>
|
||||
Django Logout
|
||||
</a>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class='btn authorize'>
|
||||
<a id="auth" class="header__btn" href="{{ LOGIN_URL }}?next={{ request.path }}" data-sw-translate>
|
||||
Django Login
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,137 @@
|
||||
import warnings
|
||||
from functools import wraps
|
||||
|
||||
from django.utils.cache import add_never_cache_headers
|
||||
from django.utils.decorators import available_attrs
|
||||
from django.views.decorators.cache import cache_page
|
||||
from django.views.decorators.vary import vary_on_headers
|
||||
from rest_framework import exceptions, renderers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.settings import api_settings
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from .generators import OpenAPISchemaGenerator
|
||||
from .renderers import (
|
||||
SwaggerJSONRenderer, SwaggerYAMLRenderer, SwaggerUIRenderer, ReDocRenderer, OpenAPIRenderer,
|
||||
)
|
||||
from .openapi import Info
|
||||
|
||||
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
|
||||
UI_RENDERERS = {
|
||||
'swagger': (SwaggerUIRenderer, ReDocRenderer),
|
||||
'redoc': (ReDocRenderer, SwaggerUIRenderer),
|
||||
}
|
||||
|
||||
|
||||
def deferred_never_cache(view_func):
|
||||
"""
|
||||
Decorator that adds headers to a response so that it will
|
||||
never be cached.
|
||||
"""
|
||||
|
||||
@wraps(view_func, assigned=available_attrs(view_func))
|
||||
def _wrapped_view_func(request, *args, **kwargs):
|
||||
response = view_func(request, *args, **kwargs)
|
||||
|
||||
# It is necessary to defer the add_never_cache_headers call because
|
||||
# cache_page also defers its cache update operation; if we do not defer
|
||||
# this, cache_page will give up because it will see and obey the "never
|
||||
# cache" headers
|
||||
def callback(response):
|
||||
add_never_cache_headers(response)
|
||||
return response
|
||||
|
||||
response.add_post_render_callback(callback)
|
||||
return response
|
||||
|
||||
return _wrapped_view_func
|
||||
|
||||
|
||||
def get_schema_view(info, url=None, patterns=None, urlconf=None, *, public=False, validators=None,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||
"""
|
||||
Create a SchemaView class with default renderers and generators.
|
||||
|
||||
:param Info info: Required. Swagger API Info object
|
||||
:param str url: API base url; if left blank will be deduced from the location the view is served at
|
||||
:param str patterns: passed to SchemaGenerator
|
||||
:param str urlconf: passed to SchemaGenerator
|
||||
:param bool public: if False, includes only endpoints the current user has access to
|
||||
:param list validators: a list of validator names to apply on the generated schema; allowed values are `flex`, `ssv`
|
||||
:param tuple authentication_classes: authentication classes for the schema view itself
|
||||
:param tuple permission_classes: permission classes for the schema view itself
|
||||
:return: SchemaView class
|
||||
"""
|
||||
_auth_classes = authentication_classes
|
||||
_perm_classes = permission_classes
|
||||
_public = public
|
||||
validators = validators or []
|
||||
_spec_renderers = tuple(renderer.with_validators(validators) for renderer in SPEC_RENDERERS)
|
||||
|
||||
class SchemaView(APIView):
|
||||
_ignore_model_permissions = True
|
||||
schema = None # exclude from schema
|
||||
public = _public
|
||||
generator_class = OpenAPISchemaGenerator
|
||||
authentication_classes = _auth_classes
|
||||
permission_classes = _perm_classes
|
||||
renderer_classes = _spec_renderers
|
||||
|
||||
def get(self, request, version='', format=None):
|
||||
generator = self.generator_class(info, version, url, patterns, urlconf)
|
||||
schema = generator.get_schema(request, self.public)
|
||||
if schema is None:
|
||||
raise exceptions.PermissionDenied()
|
||||
return Response(schema)
|
||||
|
||||
@classmethod
|
||||
def as_cached_view(cls, cache_timeout=0, cache_kwargs=None, **initkwargs):
|
||||
"""
|
||||
Calls .as_view() and wraps the result in a cache_page decorator.
|
||||
See https://docs.djangoproject.com/en/1.11/topics/cache/
|
||||
|
||||
:param int cache_timeout: same as cache_page; set to 0 for no cache
|
||||
:param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
|
||||
:param initkwargs: kwargs for .as_view()
|
||||
:return: a view instance
|
||||
"""
|
||||
cache_kwargs = cache_kwargs or {}
|
||||
view = cls.as_view(**initkwargs)
|
||||
if cache_timeout != 0:
|
||||
view = vary_on_headers('Cookie', 'Authorization', 'Accept')(view)
|
||||
view = cache_page(cache_timeout, **cache_kwargs)(view)
|
||||
view = deferred_never_cache(view) # disable in-browser caching
|
||||
elif cache_kwargs:
|
||||
warnings.warn("cache_kwargs ignored because cache_timeout is 0 (disabled)")
|
||||
return view
|
||||
|
||||
@classmethod
|
||||
def without_ui(cls, cache_timeout=0, cache_kwargs=None):
|
||||
"""
|
||||
Instantiate this view with just JSON and YAML renderers, optionally wrapped with cache_page.
|
||||
See https://docs.djangoproject.com/en/1.11/topics/cache/.
|
||||
|
||||
:param int cache_timeout: same as cache_page; set to 0 for no cache
|
||||
:param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
|
||||
:return: a view instance
|
||||
"""
|
||||
return cls.as_cached_view(cache_timeout, cache_kwargs, renderer_classes=_spec_renderers)
|
||||
|
||||
@classmethod
|
||||
def with_ui(cls, renderer='swagger', cache_timeout=0, cache_kwargs=None):
|
||||
"""
|
||||
Instantiate this view with a Web UI renderer, optionally wrapped with cache_page.
|
||||
See https://docs.djangoproject.com/en/1.11/topics/cache/.
|
||||
|
||||
:param str renderer: UI renderer; allowed values are `swagger`, `redoc`
|
||||
:param int cache_timeout: same as cache_page; set to 0 for no cache
|
||||
:param dict cache_kwargs: dictionary of kwargs to be passed to cache_page
|
||||
:return: a view instance
|
||||
"""
|
||||
assert renderer in UI_RENDERERS, "supported default renderers are " + ", ".join(UI_RENDERERS)
|
||||
renderer_classes = (*UI_RENDERERS[renderer], *_spec_renderers)
|
||||
|
||||
return cls.as_cached_view(cache_timeout, cache_kwargs, renderer_classes=renderer_classes)
|
||||
|
||||
return SchemaView
|
||||
Reference in New Issue
Block a user