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()