112 lines
3.6 KiB
Python
112 lines
3.6 KiB
Python
import json
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import TYPE_CHECKING, Any, Optional
|
|
|
|
from django.conf import settings
|
|
from django.http import HttpRequest, HttpResponse
|
|
from django.shortcuts import render
|
|
from django.urls import reverse
|
|
|
|
from ninja.constants import NOT_SET
|
|
from ninja.types import DictStrAny
|
|
|
|
if TYPE_CHECKING:
|
|
# if anyone knows a cleaner way to make mypy happy - welcome
|
|
from ninja import NinjaAPI # pragma: no cover
|
|
|
|
ABS_TPL_PATH = Path(__file__).parent.parent / "templates/ninja/"
|
|
|
|
|
|
class DocsBase(ABC):
|
|
@abstractmethod
|
|
def render_page(
|
|
self, request: HttpRequest, api: "NinjaAPI", **kwargs: Any
|
|
) -> HttpResponse:
|
|
pass # pragma: no cover
|
|
|
|
def get_openapi_url(self, api: "NinjaAPI", path_params: DictStrAny) -> str:
|
|
return reverse(f"{api.urls_namespace}:openapi-json", kwargs=path_params)
|
|
|
|
|
|
class Swagger(DocsBase):
|
|
template = "ninja/swagger.html"
|
|
template_cdn = str(ABS_TPL_PATH / "swagger_cdn.html")
|
|
default_settings = {
|
|
"layout": "BaseLayout",
|
|
"deepLinking": True,
|
|
}
|
|
|
|
def __init__(self, settings: Optional[DictStrAny] = None):
|
|
self.settings = {}
|
|
self.settings.update(self.default_settings)
|
|
if settings:
|
|
self.settings.update(settings)
|
|
|
|
def render_page(
|
|
self, request: HttpRequest, api: "NinjaAPI", **kwargs: Any
|
|
) -> HttpResponse:
|
|
self.settings["url"] = self.get_openapi_url(api, kwargs)
|
|
context = {
|
|
"swagger_settings": json.dumps(self.settings, indent=1),
|
|
"api": api,
|
|
"add_csrf": _csrf_needed(api),
|
|
}
|
|
return render_template(request, self.template, self.template_cdn, context)
|
|
|
|
|
|
class Redoc(DocsBase):
|
|
template = "ninja/redoc.html"
|
|
template_cdn = str(ABS_TPL_PATH / "redoc_cdn.html")
|
|
default_settings: DictStrAny = {}
|
|
|
|
def __init__(self, settings: Optional[DictStrAny] = None):
|
|
self.settings = {}
|
|
self.settings.update(self.default_settings)
|
|
if settings:
|
|
self.settings.update(settings)
|
|
|
|
def render_page(
|
|
self, request: HttpRequest, api: "NinjaAPI", **kwargs: Any
|
|
) -> HttpResponse:
|
|
context = {
|
|
"redoc_settings": json.dumps(self.settings, indent=1),
|
|
"openapi_json_url": self.get_openapi_url(api, kwargs),
|
|
"api": api,
|
|
}
|
|
return render_template(request, self.template, self.template_cdn, context)
|
|
|
|
|
|
def render_template(
|
|
request: HttpRequest, template: str, template_cdn: str, context: DictStrAny
|
|
) -> HttpResponse:
|
|
"""
|
|
I do not really want ninja to be required in INSTALLED_APPS to ease installation
|
|
so it automatically detects - if ninja is in INSTALLED_APPS - then we render with django.shortcuts.render
|
|
otherwise - rendering custom html with swagger js from cdn
|
|
"""
|
|
if "ninja" in settings.INSTALLED_APPS:
|
|
return render(request, template, context)
|
|
else:
|
|
return _render_cdn_template(request, template_cdn, context)
|
|
|
|
|
|
def _render_cdn_template(
|
|
request: HttpRequest, template_path: str, context: Optional[DictStrAny] = None
|
|
) -> HttpResponse:
|
|
"this is helper to find and render html template when ninja is not in INSTALLED_APPS"
|
|
from django.template import RequestContext, Template
|
|
|
|
tpl = Template(Path(template_path).read_text())
|
|
html = tpl.render(RequestContext(request, context))
|
|
return HttpResponse(html)
|
|
|
|
|
|
def _csrf_needed(api: "NinjaAPI") -> bool:
|
|
if api.csrf:
|
|
return True
|
|
if not api.auth or api.auth == NOT_SET:
|
|
return False
|
|
|
|
return any(getattr(a, "csrf", False) for a in api.auth) # type: ignore
|