Plain is headed towards 1.0! Subscribe for development updates →

  1from __future__ import annotations
  2
  3from typing import TYPE_CHECKING, Any
  4
  5from plain.auth.views import AuthView
  6from plain.runtime import settings
  7from plain.urls import reverse
  8from plain.utils import timezone
  9from plain.views import (
 10    TemplateView,
 11)
 12
 13from ..utils import get_gravatar_url
 14from .registry import registry
 15from .types import Img
 16
 17if TYPE_CHECKING:
 18    from plain.http import ResponseBase
 19
 20    from ..cards import Card
 21    from .viewsets import AdminViewset
 22
 23
 24URL_NAMESPACE = "admin"
 25
 26
 27class AdminView(AuthView, TemplateView):
 28    admin_required = True
 29
 30    title: str = ""
 31    path: str = ""
 32    image: Img | None = None
 33
 34    # Leave empty to hide from nav
 35    #
 36    # An explicit disabling of showing this url/page in the nav
 37    # which importantly effects the (future) recent pages list
 38    # so you can also use this for pages that can never be bookmarked
 39    nav_title = ""
 40    nav_section = ""
 41    nav_icon = "dot"
 42
 43    links: dict[str, str] = {}
 44
 45    parent_view_class: AdminView | None = None
 46
 47    # Set dynamically by AdminViewset.get_views()
 48    viewset: type[AdminViewset] | None = None
 49
 50    template_name = "admin/page.html"
 51    cards: list[Card] = []
 52
 53    def get_response(self) -> ResponseBase:
 54        response = super().get_response()
 55        response.headers["Cache-Control"] = (
 56            "no-cache, no-store, must-revalidate, max-age=0"
 57        )
 58        return response
 59
 60    def get_template_context(self) -> dict[str, Any]:
 61        context = super().get_template_context()
 62        context["title"] = self.get_title()
 63        context["image"] = self.get_image()
 64        context["slug"] = self.get_slug()
 65        context["links"] = self.get_links()
 66        context["parent_view_classes"] = self.get_parent_view_classes()
 67        context["admin_registry"] = registry
 68        context["cards"] = self.get_cards()
 69        context["render_card"] = lambda card: card().render(self, self.request)
 70        context["time_zone"] = timezone.get_current_timezone_name()
 71        context["view_class"] = self.__class__
 72        context["app_name"] = settings.NAME
 73        context["get_gravatar_url"] = get_gravatar_url
 74        return context
 75
 76    @classmethod
 77    def view_name(cls) -> str:
 78        return f"view_{cls.get_slug()}"
 79
 80    @classmethod
 81    def get_slug(cls) -> str:
 82        return f"{cls.__module__}.{cls.__qualname__}".lower().replace(".", "_")
 83
 84    # Can actually use @classmethod, @staticmethod or regular method for these?
 85    def get_title(self) -> str:
 86        return self.title
 87
 88    def get_image(self) -> Img | None:
 89        return self.image
 90
 91    @classmethod
 92    def get_path(cls) -> str:
 93        return cls.path
 94
 95    @classmethod
 96    def get_parent_view_classes(cls) -> list[AdminView]:
 97        parents = []
 98        parent = cls.parent_view_class
 99        while parent:
100            parents.append(parent)
101            parent = parent.parent_view_class
102        return parents
103
104    @classmethod
105    def get_nav_title(cls) -> str:
106        if cls.nav_title:
107            return cls.nav_title
108
109        if cls.title:
110            return cls.title
111
112        raise NotImplementedError(
113            f"Please set a title or nav_title on the {cls} class or implement get_nav_title()."
114        )
115
116    @classmethod
117    def get_view_url(cls, obj: Any = None) -> str:
118        # Check if this view's path expects an id parameter
119        if obj and "<int:id>" in cls.get_path():
120            return reverse(f"{URL_NAMESPACE}:" + cls.view_name(), id=obj.id)
121        else:
122            return reverse(f"{URL_NAMESPACE}:" + cls.view_name())
123
124    def get_links(self) -> dict[str, str]:
125        return self.links.copy()
126
127    def get_cards(self) -> list[Card]:
128        return self.cards.copy()