Coverage for admin.py: 96%
101 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-08-03 17:57 -0700
« prev ^ index » next coverage.py v7.2.7, created at 2023-08-03 17:57 -0700
1import contextlib
2from collections import namedtuple
3from collections.abc import Iterable
4from typing import TYPE_CHECKING, List, Type, Union
6from django.apps import apps
7from django.conf import settings
8from django.contrib import admin
9from django.contrib.admin.apps import AdminConfig
10from django.core.exceptions import ImproperlyConfigured
11from django.urls import include, path, reverse
12from django.views import View
14from django_custom_admin_pages.urls import add_view_to_conf
16if TYPE_CHECKING:
17 from .views.admin_base_view import AdminBaseView
20ViewRegister = namedtuple("ViewRegister", ["app_label", "view"])
23def get_installed_apps():
24 installed_apps = [app_config.name for app_config in apps.get_app_configs()]
25 return installed_apps
28def get_app_label(view: View) -> str:
29 "returns app label or default app for view"
30 return getattr(view, "app_label") or settings.CUSTOM_ADMIN_DEFAULT_APP_LABEL
33class CustomAdminConfig(AdminConfig):
34 default_site = "django_custom_admin_pages.admin.CustomAdminSite"
37class CustomAdminSite(admin.AdminSite):
38 def __init__(self, *args, **kwargs):
39 self._view_registry: List["AdminBaseView"] = []
40 super().__init__(*args, **kwargs)
42 def get_urls(self):
43 """
44 Adds urls.py to perch-admin routes if url patterns in it
45 """
46 urls = super().get_urls()
47 with contextlib.suppress(ImportError):
48 from .urls import urlpatterns
50 if len(urlpatterns) > 0:
51 my_urls = [
52 path(
53 "",
54 include("django_custom_admin_pages.urls"),
55 )
56 ]
57 urls = my_urls + urls
59 return urls
61 def register_view(self, view_or_iterable: Union[Iterable, "AdminBaseView"]):
62 """
63 Register the given view(s) with the admin class.
65 The view(s) should be class-based views inheriting from AdminBaseView.
67 If the view is already registered, raise AlreadyRegistered.
68 """
69 from .views.admin_base_view import AdminBaseView
71 if not isinstance(view_or_iterable, Iterable):
72 view_or_iterable = [view_or_iterable]
74 for view in view_or_iterable:
75 try:
76 if not issubclass(view, AdminBaseView):
77 raise ImproperlyConfigured(
78 "Only class-based views inheriting from AdminBaseView can be registered"
79 )
80 except TypeError as e:
81 raise ImproperlyConfigured(
82 "view_or_iterable must be a class_based view or iterable"
83 ) from e
85 if (
86 not hasattr(view, "view_name")
87 or view.view_name is None
88 or not isinstance(view.view_name, str)
89 ):
90 raise ImproperlyConfigured(
91 "View must have name attribute set as string."
92 )
94 if view in self._view_registry:
95 raise admin.sites.AlreadyRegistered(
96 f"View: {str(view.view_name)} is already registered."
97 )
99 self._view_registry.append(view)
101 add_view_to_conf(view)
103 def unregister_view(self, view_or_iterable: Union[Iterable, Type]):
104 def _raise_not_registered(view, e=None):
105 msg = f"The view {view.__name__} is not registered"
106 if e:
107 raise admin.sites.NotRegistered(msg) from e
108 raise admin.sites.NotRegistered(msg)
110 if not isinstance(view_or_iterable, Iterable):
111 view_or_iterable = [view_or_iterable]
113 original_length = len(self._view_registry)
115 for view in view_or_iterable:
116 try:
117 self._view_registry.remove(view)
118 except ValueError as e:
119 _raise_not_registered(view, e)
121 if len(self._view_registry) == original_length:
122 _raise_not_registered(view)
124 def _build_modelview(self, view) -> dict:
125 """
126 Creates dict for custom admin view for use in app_list[models]
127 """
128 url = reverse(f"{self.name}:{view.route_name}")
129 name = view.view_name
130 return {
131 "name": name,
132 "object_name": name,
133 "admin_url": url,
134 "view_only": True,
135 }
137 def get_app_list(self, request):
138 """
139 Adds registered apps to admin app list used for nav.
140 """
141 app_list = super().get_app_list(request)
142 custom_admin_models = []
144 for view in self._view_registry:
145 found = False
146 view_app_label = get_app_label(view).lower()
147 if view_app_label == settings.CUSTOM_ADMIN_DEFAULT_APP_LABEL:
148 custom_admin_models.append(self._build_modelview(view))
149 continue
151 for app in app_list:
152 if view_app_label == app.get("app_label", "").lower():
153 found = True
154 if view().user_has_permission(request.user):
155 app_models = app["models"]
156 app_models.append(self._build_modelview(view))
157 app_models.sort(key=lambda x: x["name"])
158 break
160 if not found:
161 remaining_apps = set(set(get_installed_apps())).difference(app_list)
162 for app in remaining_apps:
163 if view_app_label == app:
164 found = True
165 app_config = apps.get_app_config(view_app_label)
166 app_name = app_config.verbose_name
167 app_list.append(
168 {
169 "name": app_name,
170 "app_label": view_app_label,
171 "app_url": f"{reverse(f'{self.name}:{view.route_name}')}{view_app_label}/",
172 "models": [self._build_modelview(view)],
173 }
174 )
176 if not found:
177 raise ImproperlyConfigured(
178 f'The following custom admin view has an app_label that couldn\'t be found: "{view.__name__}". Please check that "{view_app_label}" is a valid app_label.'
179 )
181 if custom_admin_models:
182 app_list += [
183 {
184 "name": "Custom Admin Pages",
185 "app_label": settings.CUSTOM_ADMIN_DEFAULT_APP_LABEL,
186 "app_url": f"{reverse(f'{self.name}:index')}{settings.CUSTOM_ADMIN_DEFAULT_APP_LABEL}/",
187 "models": custom_admin_models,
188 }
189 ]
190 app_list = sorted(app_list, key=lambda x: x["name"])
191 return app_list