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