The Open API Specification
(OAS 3) allows us to model and document our APIs
in a machine- and human-readable format.
OAS 3 has become the standard machine-readable representation of API schemas.
Having a standardized schema document enables API consumer and producer developers to formally agree on the
API details in an automated way, providing tools for developers to perform basic data input validation
and to provide developer documentation and the familiar swagger "Try it out" functionality.
The Swagger UI shows the API documentation and includes a "Try it out" feature which can be used to
provide access to a live backend API. This includes the ability to login and obtain authorization to
access the API.
redoc provides a nice view of the API as well, although it doesn't
include "Try it out".
Warning
The Swagger UI does not display OAS Security Requirements objects
even though they may exist in the OAS schema document. Redoc does display that information.
I'm still having some problems getting the OAuth2 authorization to properly integrate with Swagger UI.
Watch this space for updates (or submit a PR that fixes it!).
You can try out your static schema document with swagger-ui-watcher:
Install it with
Our demo app has views defined for the openapi schema document and the
swagger and
redoc user interfaces.
1 2 3 4 5 6 7 8 91011121314
fromdrf_spectacular.viewsimportSpectacularAPIView,SpectacularRedocView,SpectacularSwaggerView,SpectacularSwaggerOauthRedirectView# ...urlpatterns=[# ...# OpenAPI schema using drf-spectacular and drf-spectacular-json-api:path('api/schema/',SpectacularAPIView.as_view(),name='schema'),# Optional UI:path('api/schema/swagger-ui/',SpectacularSwaggerView.as_view(url_name='schema'),name='swagger-ui'),path('api/schema/redoc/',SpectacularRedocView.as_view(url_name='schema'),name='redoc'),# not sure if this is needed or how to serve this when DEBUG is falsepath('api/schema/swagger-ui/oauth2-redirect.html',SpectacularSwaggerOauthRedirectView.as_view()),# ...]
diff --git a/training/settings.py b/training/settings.pyindex 4d121bc..1c3a82e 100644--- a/training/settings.py+++ b/training/settings.py@@ -11,6 +11,7 @@ https://docs.djangoproject.com/en/2.1/ref/settings/"""
+from myapp import __title__, __version__, __author__, __email__, __license__, __license_url__, __copyright__# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
@@ -55,6 +56,8 @@ INSTALLED_APPS = [+ 'drf_spectacular',+ 'drf_spectacular_sidecar', # required for Django collectstatic discovery]
REST_FRAMEWORK = {
+ "DEFAULT_PAGINATION_CLASS": "drf_spectacular_jsonapi.schemas.pagination.JsonApiPageNumberPagination",+ "DEFAULT_SCHEMA_CLASS": "drf_spectacular_jsonapi.schemas.openapi.JsonApiAutoSchema",+SPECTACULAR_SETTINGS = {+ 'TITLE': __title__,+ 'DESCRIPTION':+ ''+ '\n'+ '\n'+ '\n'+ 'A sample API that uses courses as an example to demonstrate representing\n'+ '[JSON:API 1.0](http://jsonapi.org/format) in the OpenAPI 3.0 specification.\n'+ '\n'+ '\n'+ '\n' + __copyright__ + '\n',+ 'VERSION': __version__,+ 'CONTACT': {+ 'name': __author__,+ 'email': __email__,+ },+ 'LICENSE': {+ 'name': __license__,+ 'url': __license_url__,+ },+ 'EXTERNAL_DOCS': {+ 'description': 'Read all about this JSONAPI demonstration app.',+ 'url': 'https://columbia-it-django-jsonapi-training.readthedocs.io'+ },+ 'SERVE_INCLUDE_SCHEMA': False,+ # OTHER SETTINGS+ 'SWAGGER_UI_DIST': 'SIDECAR', # shorthand to use the sidecar instead+ 'SWAGGER_UI_FAVICON_HREF': 'SIDECAR',+ 'REDOC_DIST': 'SIDECAR',+ # To provide different schema components for patch and post+ "COMPONENT_SPLIT_REQUEST": True,+ # to fix path parameter names for nested routes https://chibisov.github.io/drf-extensions/docs/#nested-routes+ "PREPROCESSING_HOOKS": [+ "drf_spectacular_jsonapi.hooks.fix_nested_path_parameters"+ ],+ "POSTPROCESSING_HOOKS": [+ "drf_spectacular.hooks.postprocess_schema_enums",+ ],+ # this stuff gets added to SpectacularSwaggerView:+ "SWAGGER_UI_OAUTH2_CONFIG": {+ "clientId": "demo_djt_web_client",+ "clientSecret": "demo_djt_web_secret",+ "usePkceWithAuthorizationCodeGrant": True,+ },+ "OAUTH2_FLOWS": [ "authorizationCode", "clientCredentials", ],+ "OAUTH2_AUTHORIZATION_URL": "http://localhost:8000/o/authorize/" if OAUTH2_SERVER == 'self' else OAUTH2_SERVER + '/as/introspect.oauth2',+ "OAUTH2_TOKEN_URL": "http://localhost:8000/o/token/" if OAUTH2_SERVER == 'self' else OAUTH2_SERVER + '/as/token.oauth2',+ "OAUTH2_REFRESH_URL": None, # same as token url?+ "OAUTH2_SCOPES": OAUTH2_PROVIDER["SCOPES"],+ "SERVERS": [+ {'url': 'http://localhost:8000', 'description': 'local dev'},+ {'url': 'https://localhost', 'description': 'local docker'},+ {'url': 'https://ac45devapp01.cc.columbia.edu', 'description': 'demo'},+ {'url': '{serverURL}', 'description': 'provide your server URL',+ 'variables': {'serverURL': {'default': 'http://localhost:8000'}}}+ ]+}
1 2 3 4 5 6 7 8 91011121314151617
diff --git a/training/urls.py b/training/urls.pyindex e66a72f..65c12b8 100644--- a/training/urls.py+++ b/training/urls.py@@ -22,9 +22,9 @@ from django.contrib.staticfiles.views import serve+from drf_spectacular.views import SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView, SpectacularSwaggerOauthRedirectViewurlpatterns = [
+ path('', RedirectView.as_view(url='api/schema/swagger-ui/', permanent=False)),+ # OpenAPI schema using drf-spectacular and drf-spectacular-json-api:+ path('api/schema/', SpectacularAPIView.as_view(), name='schema'),+ # Optional UI:+ path('api/schema/swagger-ui/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),+ path('api/schema/redoc/', SpectacularRedocView.as_view(url_name='schema'), name='redoc'),+ # not sure if this is needed or how to serve this when DEBUG is false+ path('api/schema/swagger-ui/oauth2-redirect.html', SpectacularSwaggerOauthRedirectView.as_view()),]
Extending the schema for OAuth2 security requirements¶
myapp/django_oauth_toolkit.py is where our customizations get added:
fromdrf_spectacular.extensionsimportOpenApiAuthenticationExtensionclassDjangoOAuthToolkitScheme(OpenApiAuthenticationExtension):target_class='oauth2_provider.contrib.rest_framework.OAuth2Authentication'name='oauth2'defget_security_requirement(self,auto_schema):""" Generate the OAS [Oauth2 security requirement object](https://spec.openapis.org/oas/latest#oauth2-security-requirement). Looks through the view permissions for relevent OAuth2 permission classes such as TokenMatchesOASRequirements in order to generate a list of alternative required scopes. TODO: Better deal with hierarchical (AND, OR) permissions. """fromoauth2_provider.contrib.rest_frameworkimport(IsAuthenticatedOrTokenHasScope,TokenHasScope,TokenMatchesOASRequirements,)view=auto_schema.viewrequest=view.requestforpermissioninauto_schema.view.get_permissions():ifisinstance(permission,TokenMatchesOASRequirements):alt_scopes=permission.get_required_alternate_scopes(request,view)alt_scopes=alt_scopes.get(auto_schema.method,[])return[{self.name:group}forgroupinalt_scopes]ifisinstance(permission,IsAuthenticatedOrTokenHasScope):return{self.name:TokenHasScope().get_scopes(request,view)}ifisinstance(permission,TokenHasScope):# catch-all for subclasses of TokenHasScope like TokenHasReadWriteScopereturn{self.name:permission.get_scopes(request,view)}# deal with hierarchical boolean permissions.scopes=getattr(view,"required_alternate_scopes")ifscopesisNone:scopes=getattr(view,"required_scopes")# try for required_scopesreturn{self.name:scopesifscopeselse[]}return[{self.name:a}forainscopes[auto_schema.method]]defget_security_definition(self,auto_schema):""" Render the securitySchemes for our oauth2 service. """fromoauth2_provider.scopesimportget_scopes_backendfromdrf_spectacular.settingsimportspectacular_settingsflows={}forflow_typeinspectacular_settings.OAUTH2_FLOWS:flows[flow_type]={}ifflow_typein('implicit','authorizationCode'):flows[flow_type]['authorizationUrl']=spectacular_settings.OAUTH2_AUTHORIZATION_URLifflow_typein('password','clientCredentials','authorizationCode'):flows[flow_type]['tokenUrl']=spectacular_settings.OAUTH2_TOKEN_URLifspectacular_settings.OAUTH2_REFRESH_URL:flows[flow_type]['refreshUrl']=spectacular_settings.OAUTH2_REFRESH_URLscope_backend=get_scopes_backend()flows[flow_type]['scopes']=scope_backend.get_all_scopes()return{'type':'oauth2','flows':flows}