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:
Cristi Vîjdea
2017-12-02 22:46:07 +01:00
parent 207973ae5a
commit 2b0d80dc0f
44 changed files with 314 additions and 14 deletions
+4
View File
@@ -0,0 +1,4 @@
# coding=utf-8
__author__ = """Cristi V."""
__email__ = 'cristi@cvjd.me'
__version__ = '1.0.0rc1'
+43
View File
@@ -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,
)
+152
View File
@@ -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')
+15
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
from rest_framework.schemas import AutoSchema
class SwaggerAutoSchema(AutoSchema):
pass
+136
View File
@@ -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
+107
View File
@@ -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'
+4
View File
@@ -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
[![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](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": "[![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](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>
+137
View File
@@ -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