Plain is headed towards 1.0! Subscribe for development updates →

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