Initial implementation as coreapi wrapper
* schema generation implemented via coreapi Document and openapi_codec * schema interface selectable from the latest versions of swagger-ui and redoc * feature parity with django-rest-swagger 2openapi3
parent
cbd8cb68cf
commit
ed02e3c3a1
|
|
@ -0,0 +1,158 @@
|
|||
node_modules/
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Python template
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
.hypothesis/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
.static_storage/
|
||||
.media/
|
||||
local_settings.py
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# pyenv
|
||||
.python-version
|
||||
|
||||
# celery beat schedule file
|
||||
celerybeat-schedule
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff:
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/dictionaries
|
||||
|
||||
# Sensitive or high-churn files:
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.xml
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
|
||||
# Gradle:
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# CMake
|
||||
cmake-build-debug/
|
||||
|
||||
# Mongo Explorer plugin:
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
## File-based project format:
|
||||
*.iws
|
||||
|
||||
## Plugin-specific files:
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
include README.md
|
||||
include LICENSE
|
||||
recursive-include drf_swagger/static *
|
||||
recursive-include drf_swagger/templates *
|
||||
|
|
@ -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
|
||||
|
||||
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,115 @@
|
|||
import warnings
|
||||
from collections import OrderedDict
|
||||
|
||||
import coreapi
|
||||
|
||||
|
||||
class Contact(object):
|
||||
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):
|
||||
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):
|
||||
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 != info.title:
|
||||
warnings.warn("document title is overriden by Swagger Info")
|
||||
if 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 .codec 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,44 @@
|
|||
{% 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 id="redoc-settings">{{ swagger_settings | safe }}</script>
|
||||
<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 (element) {
|
||||
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,113 @@
|
|||
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,
|
||||
)
|
||||
|
||||
SPEC_RENDERERS = (SwaggerYAMLRenderer, SwaggerJSONRenderer, OpenAPIRenderer)
|
||||
SPEC_RENDERERS = {
|
||||
False: tuple(renderer.with_validators([]) for renderer in SPEC_RENDERERS),
|
||||
True: SPEC_RENDERERS,
|
||||
}
|
||||
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, validate=False,
|
||||
authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES,
|
||||
permission_classes=api_settings.DEFAULT_PERMISSION_CLASSES):
|
||||
_auth_classes = authentication_classes
|
||||
_perm_classes = permission_classes
|
||||
_public = public
|
||||
|
||||
class SchemaView(APIView):
|
||||
_ignore_model_permissions = True
|
||||
schema = None # exclude from schema
|
||||
public = _public
|
||||
authentication_classes = _auth_classes
|
||||
permission_classes = _perm_classes
|
||||
renderer_classes = SPEC_RENDERERS[bool(validate)]
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(SchemaView, self).__init__(**kwargs)
|
||||
if self.renderer_classes is None:
|
||||
if renderers.BrowsableAPIRenderer in api_settings.DEFAULT_RENDERER_CLASSES:
|
||||
self.renderer_classes = [
|
||||
renderers.CoreJSONRenderer,
|
||||
renderers.BrowsableAPIRenderer,
|
||||
]
|
||||
else:
|
||||
self.renderer_classes = [renderers.CoreJSONRenderer]
|
||||
|
||||
def get(self, request, version='', format=None):
|
||||
generator = OpenAPISchemaGenerator(info, version, url, patterns, urlconf)
|
||||
schema = generator.get_schema(request, self.public)
|
||||
if schema is None:
|
||||
raise exceptions.PermissionDenied()
|
||||
return Response(schema)
|
||||
|
||||
@classmethod
|
||||
def _cached(cls, view, cache_timeout, cache_kwargs):
|
||||
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 as_cached_view(cls, cache_timeout=0, **cache_kwargs):
|
||||
return cls._cached(cls.as_view(), cache_timeout, cache_kwargs)
|
||||
|
||||
@classmethod
|
||||
def without_ui(cls, cache_timeout=0, **cache_kwargs):
|
||||
renderer_classes = SPEC_RENDERERS[bool(validate)]
|
||||
return cls._cached(cls.as_view(renderer_classes=renderer_classes), cache_timeout, cache_kwargs)
|
||||
|
||||
@classmethod
|
||||
def with_ui(cls, renderer='swagger', cache_timeout=0, **cache_kwargs):
|
||||
assert renderer in UI_RENDERERS, "supported default renderers are " + ", ".join(UI_RENDERERS)
|
||||
renderer_classes = (*UI_RENDERERS[renderer], *SPEC_RENDERERS[bool(validate)])
|
||||
|
||||
view = cls.as_view(renderer_classes=renderer_classes)
|
||||
return cls._cached(view, cache_timeout, cache_kwargs)
|
||||
|
||||
return SchemaView
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "drf-swagger",
|
||||
"requires": true,
|
||||
"lockfileVersion": 1,
|
||||
"dependencies": {
|
||||
"swagger-ui-dist": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.5.0.tgz",
|
||||
"integrity": "sha1-JuvzMRqaYP6dFwYS7tUoKmW8kiY="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "drf-swagger",
|
||||
"dependencies": {
|
||||
"redoc": "^2.0.0-alpha.4",
|
||||
"swagger-ui-dist": "^3.5.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
djangorestframework>=3.7.3
|
||||
django>=1.11.7
|
||||
coreapi>=2.3.3
|
||||
openapi_codec>=1.3.2
|
||||
ruamel.yaml>=0.15.34
|
||||
|
|
@ -0,0 +1 @@
|
|||
pygments>=2.2.0
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
flex>=6.11.1
|
||||
swagger-spec-validator>=2.1.0
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
from setuptools import setup, find_packages
|
||||
|
||||
|
||||
def read_req(req_file):
|
||||
with open(req_file) as req:
|
||||
return [line for line in req.readlines() if line and not line.isspace()]
|
||||
|
||||
|
||||
requirements = read_req('requirements.txt')
|
||||
requirements_validation = read_req('requirements_validation.txt')
|
||||
|
||||
setup(
|
||||
name='drf-swagger',
|
||||
version='1.0.0rc1',
|
||||
packages=find_packages(include=['drf_swagger']),
|
||||
include_package_data=True,
|
||||
install_requires=requirements,
|
||||
extras_require={
|
||||
'validation': requirements_validation
|
||||
},
|
||||
license='BSD License',
|
||||
description='Automated generation of real Swagger/OpenAPI 2.0 schemas from Django Rest Framework code.',
|
||||
long_description='',
|
||||
url='https://github.com/axnsan12/drf-swagger',
|
||||
author='Cristi V.',
|
||||
author_email='cristi@cvjd.me',
|
||||
keywords='drf-swagger drf django rest-framework schema swagger openapi ',
|
||||
classifiers=[
|
||||
'Framework :: Django',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: BSD License',
|
||||
'Operating System :: OS Independent',
|
||||
'Programming Language :: Python',
|
||||
'Programming Language :: Python :: 3',
|
||||
'Programming Language :: Python :: 3.4',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
],
|
||||
)
|
||||
Binary file not shown.
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env python
|
||||
import os
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError:
|
||||
# The above import may fail for some other reason. Ensure that the
|
||||
# issue is really that Django is missing to avoid masking other
|
||||
# exceptions on Python 2.
|
||||
try:
|
||||
import django
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
)
|
||||
raise
|
||||
execute_from_command_line(sys.argv)
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,19 @@
|
|||
from django.db import models
|
||||
from pygments.lexers import get_all_lexers
|
||||
from pygments.styles import get_all_styles
|
||||
|
||||
LEXERS = [item for item in get_all_lexers() if item[1]]
|
||||
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
|
||||
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
|
||||
|
||||
|
||||
class Snippet(models.Model):
|
||||
created = models.DateTimeField(auto_now_add=True)
|
||||
title = models.CharField(max_length=100, blank=True, default='')
|
||||
code = models.TextField()
|
||||
linenos = models.BooleanField(default=False)
|
||||
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
|
||||
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
|
||||
|
||||
class Meta:
|
||||
ordering = ('created', )
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
from rest_framework.status import HTTP_400_BAD_REQUEST
|
||||
|
||||
from rest_framework import serializers
|
||||
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
|
||||
|
||||
|
||||
class LanguageSerializer(serializers.Serializer):
|
||||
name = serializers.ChoiceField(
|
||||
choices=LANGUAGE_CHOICES, default='python', help_text='The name of the programming language')
|
||||
|
||||
|
||||
class ExampleProjectsSerializer(serializers.Serializer):
|
||||
project_name = serializers.CharField(help_text='Name of the project')
|
||||
github_repo = serializers.CharField(required=True, help_text='Github repository of the project')
|
||||
|
||||
|
||||
class SnippetSerializer(serializers.Serializer):
|
||||
id = serializers.IntegerField(read_only=True)
|
||||
title = serializers.CharField(required=False, allow_blank=True, max_length=100)
|
||||
code = serializers.CharField(style={'base_template': 'textarea.html'})
|
||||
linenos = serializers.BooleanField(required=False)
|
||||
language = LanguageSerializer()
|
||||
style = serializers.ChoiceField(choices=STYLE_CHOICES, default='friendly')
|
||||
lines = serializers.ListField(child=serializers.IntegerField(), allow_empty=True, allow_null=True, required=False)
|
||||
example_projects = serializers.ListSerializer(child=ExampleProjectsSerializer())
|
||||
|
||||
class Meta:
|
||||
error_status_codes = {
|
||||
HTTP_400_BAD_REQUEST: 'Bad Request'
|
||||
}
|
||||
|
||||
def create(self, validated_data):
|
||||
"""
|
||||
Create and return a new `Snippet` instance, given the validated data.
|
||||
"""
|
||||
return Snippet.objects.create(**validated_data)
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
"""
|
||||
Update and return an existing `Snippet` instance, given the validated data.
|
||||
"""
|
||||
instance.title = validated_data.get('title', instance.title)
|
||||
instance.code = validated_data.get('code', instance.code)
|
||||
instance.linenos = validated_data.get('linenos', instance.linenos)
|
||||
instance.language = validated_data.get('language', instance.language)
|
||||
instance.style = validated_data.get('style', instance.style)
|
||||
instance.save()
|
||||
return instance
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
url(r'$', views.SnippetList.as_view()),
|
||||
url(r'^(?P<pk>[0-9]+)/$', views.SnippetDetail.as_view()),
|
||||
]
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
from rest_framework import generics
|
||||
from snippets.models import Snippet
|
||||
from snippets.serializers import SnippetSerializer
|
||||
|
||||
|
||||
class SnippetList(generics.ListCreateAPIView):
|
||||
queryset = Snippet.objects.all()
|
||||
serializer_class = SnippetSerializer
|
||||
|
||||
|
||||
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView):
|
||||
queryset = Snippet.objects.all()
|
||||
serializer_class = SnippetSerializer
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = '!z1yj(9uz)zk0gg@5--j)bc4h^i!8))r^dezco8glf190e0&#p'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = []
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'rest_framework',
|
||||
|
||||
'drf_swagger',
|
||||
'snippets'
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'testproj.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'testproj.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.11/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/1.11/howto/static-files/
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
REST_FRAMEWORK = {
|
||||
'DEFAULT_VERSIONING_CLASS': 'rest_framework.versioning.URLPathVersioning'
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from django.conf.urls import url, include
|
||||
from django.contrib import admin
|
||||
|
||||
from drf_swagger.views import get_schema_view
|
||||
from drf_swagger import openapi
|
||||
from rest_framework import permissions
|
||||
|
||||
schema_view = get_schema_view(
|
||||
openapi.Info(
|
||||
title="Snippets API",
|
||||
default_version='v1',
|
||||
description="Test description",
|
||||
terms_of_service="*Some TOS*",
|
||||
contact=openapi.Contact(email="cristi@cvjd.me"),
|
||||
license=openapi.License("BSD License"),
|
||||
),
|
||||
validate=True,
|
||||
public=False,
|
||||
permission_classes=(permissions.AllowAny,),
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
url(r'^swagger(?P<format>.json|.yaml)$', schema_view.without_ui(cache_timeout=None), name='schema-json'),
|
||||
url(r'^swagger/$', schema_view.with_ui('swagger', cache_timeout=None), name='schema-swagger-ui'),
|
||||
url(r'^redoc/$', schema_view.with_ui('redoc', cache_timeout=None), name='schema-redoc'),
|
||||
|
||||
url(r'^admin/', admin.site.urls),
|
||||
url(f'^snippets/', include('snippets.urls')),
|
||||
]
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "testproj.settings")
|
||||
|
||||
application = get_wsgi_application()
|
||||
Loading…
Reference in New Issue