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