Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/contrib/admin/sites.py: 46%
253 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1import re
2from functools import update_wrapper
3from weakref import WeakSet
5from django.apps import apps
6from django.conf import settings
7from django.contrib.admin import ModelAdmin, actions
8from django.contrib.admin.views.autocomplete import AutocompleteJsonView
9from django.contrib.auth import REDIRECT_FIELD_NAME
10from django.core.exceptions import ImproperlyConfigured
11from django.db.models.base import ModelBase
12from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect
13from django.template.response import TemplateResponse
14from django.urls import NoReverseMatch, Resolver404, resolve, reverse
15from django.utils.decorators import method_decorator
16from django.utils.functional import LazyObject
17from django.utils.module_loading import import_string
18from django.utils.text import capfirst
19from django.utils.translation import gettext as _
20from django.utils.translation import gettext_lazy
21from django.views.decorators.cache import never_cache
22from django.views.decorators.common import no_append_slash
23from django.views.decorators.csrf import csrf_protect
24from django.views.i18n import JavaScriptCatalog
26all_sites = WeakSet()
29class AlreadyRegistered(Exception):
30 pass
33class NotRegistered(Exception):
34 pass
37class AdminSite:
38 """
39 An AdminSite object encapsulates an instance of the Django admin application, ready
40 to be hooked in to your URLconf. Models are registered with the AdminSite using the
41 register() method, and the get_urls() method can then be used to access Django view
42 functions that present a full admin interface for the collection of registered
43 models.
44 """
46 # Text to put at the end of each page's <title>.
47 site_title = gettext_lazy("Django site admin")
49 # Text to put in each page's <h1>.
50 site_header = gettext_lazy("Django administration")
52 # Text to put at the top of the admin index page.
53 index_title = gettext_lazy("Site administration")
55 # URL for the "View site" link at the top of each admin page.
56 site_url = "/"
58 enable_nav_sidebar = True
60 empty_value_display = "-"
62 login_form = None
63 index_template = None
64 app_index_template = None
65 login_template = None
66 logout_template = None
67 password_change_template = None
68 password_change_done_template = None
70 final_catch_all_view = True
72 def __init__(self, name="admin"):
73 self._registry = {} # model_class class -> admin_class instance
74 self.name = name
75 self._actions = {"delete_selected": actions.delete_selected}
76 self._global_actions = self._actions.copy()
77 all_sites.add(self)
79 def __repr__(self):
80 return f"{self.__class__.__name__}(name={self.name!r})"
82 def check(self, app_configs):
83 """
84 Run the system checks on all ModelAdmins, except if they aren't
85 customized at all.
86 """
87 if app_configs is None: 87 ↛ 89line 87 didn't jump to line 89, because the condition on line 87 was never false
88 app_configs = apps.get_app_configs()
89 app_configs = set(app_configs) # Speed up lookups below
91 errors = []
92 modeladmins = (
93 o for o in self._registry.values() if o.__class__ is not ModelAdmin
94 )
95 for modeladmin in modeladmins:
96 if modeladmin.model._meta.app_config in app_configs: 96 ↛ 95line 96 didn't jump to line 95, because the condition on line 96 was never false
97 errors.extend(modeladmin.check())
98 return errors
100 def register(self, model_or_iterable, admin_class=None, **options):
101 """
102 Register the given model(s) with the given admin class.
104 The model(s) should be Model classes, not instances.
106 If an admin class isn't given, use ModelAdmin (the default admin
107 options). If keyword arguments are given -- e.g., list_display --
108 apply them as options to the admin class.
110 If a model is already registered, raise AlreadyRegistered.
112 If a model is abstract, raise ImproperlyConfigured.
113 """
114 admin_class = admin_class or ModelAdmin
115 if isinstance(model_or_iterable, ModelBase): 115 ↛ 116line 115 didn't jump to line 116, because the condition on line 115 was never true
116 model_or_iterable = [model_or_iterable]
117 for model in model_or_iterable:
118 if model._meta.abstract: 118 ↛ 119line 118 didn't jump to line 119, because the condition on line 118 was never true
119 raise ImproperlyConfigured(
120 "The model %s is abstract, so it cannot be registered with admin."
121 % model.__name__
122 )
124 if model in self._registry: 124 ↛ 125line 124 didn't jump to line 125, because the condition on line 124 was never true
125 registered_admin = str(self._registry[model])
126 msg = "The model %s is already registered " % model.__name__
127 if registered_admin.endswith(".ModelAdmin"):
128 # Most likely registered without a ModelAdmin subclass.
129 msg += "in app %r." % re.sub(r"\.ModelAdmin$", "", registered_admin)
130 else:
131 msg += "with %r." % registered_admin
132 raise AlreadyRegistered(msg)
134 # Ignore the registration if the model has been
135 # swapped out.
136 if not model._meta.swapped:
137 # If we got **options then dynamically construct a subclass of
138 # admin_class with those **options.
139 if options: 139 ↛ 143line 139 didn't jump to line 143, because the condition on line 139 was never true
140 # For reasons I don't quite understand, without a __module__
141 # the created class appears to "live" in the wrong place,
142 # which causes issues later on.
143 options["__module__"] = __name__
144 admin_class = type(
145 "%sAdmin" % model.__name__, (admin_class,), options
146 )
148 # Instantiate the admin class to save in the registry
149 self._registry[model] = admin_class(model, self)
151 def unregister(self, model_or_iterable):
152 """
153 Unregister the given model(s).
155 If a model isn't already registered, raise NotRegistered.
156 """
157 if isinstance(model_or_iterable, ModelBase):
158 model_or_iterable = [model_or_iterable]
159 for model in model_or_iterable:
160 if model not in self._registry:
161 raise NotRegistered("The model %s is not registered" % model.__name__)
162 del self._registry[model]
164 def is_registered(self, model):
165 """
166 Check if a model class is registered with this `AdminSite`.
167 """
168 return model in self._registry
170 def add_action(self, action, name=None):
171 """
172 Register an action to be available globally.
173 """
174 name = name or action.__name__
175 self._actions[name] = action
176 self._global_actions[name] = action
178 def disable_action(self, name):
179 """
180 Disable a globally-registered action. Raise KeyError for invalid names.
181 """
182 del self._actions[name]
184 def get_action(self, name):
185 """
186 Explicitly get a registered global action whether it's enabled or
187 not. Raise KeyError for invalid names.
188 """
189 return self._global_actions[name]
191 @property
192 def actions(self):
193 """
194 Get all the enabled actions as an iterable of (name, func).
195 """
196 return self._actions.items()
198 def has_permission(self, request):
199 """
200 Return True if the given HttpRequest has permission to view
201 *at least one* page in the admin site.
202 """
203 return request.user.is_active and request.user.is_staff
205 def admin_view(self, view, cacheable=False):
206 """
207 Decorator to create an admin view attached to this ``AdminSite``. This
208 wraps the view and provides permission checking by calling
209 ``self.has_permission``.
211 You'll want to use this from within ``AdminSite.get_urls()``:
213 class MyAdminSite(AdminSite):
215 def get_urls(self):
216 from django.urls import path
218 urls = super().get_urls()
219 urls += [
220 path('my_view/', self.admin_view(some_view))
221 ]
222 return urls
224 By default, admin_views are marked non-cacheable using the
225 ``never_cache`` decorator. If the view can be safely cached, set
226 cacheable=True.
227 """
229 def inner(request, *args, **kwargs):
230 if not self.has_permission(request):
231 if request.path == reverse("admin:logout", current_app=self.name):
232 index_path = reverse("admin:index", current_app=self.name)
233 return HttpResponseRedirect(index_path)
234 # Inner import to prevent django.contrib.admin (app) from
235 # importing django.contrib.auth.models.User (unrelated model).
236 from django.contrib.auth.views import redirect_to_login
238 return redirect_to_login(
239 request.get_full_path(),
240 reverse("admin:login", current_app=self.name),
241 )
242 return view(request, *args, **kwargs)
244 if not cacheable: 244 ↛ 248line 244 didn't jump to line 248, because the condition on line 244 was never false
245 inner = never_cache(inner)
246 # We add csrf_protect here so this function can be used as a utility
247 # function for any view, without having to repeat 'csrf_protect'.
248 if not getattr(view, "csrf_exempt", False): 248 ↛ 250line 248 didn't jump to line 250, because the condition on line 248 was never false
249 inner = csrf_protect(inner)
250 return update_wrapper(inner, view)
252 def get_urls(self):
253 # Since this module gets imported in the application's root package,
254 # it cannot import models from other applications at the module level,
255 # and django.contrib.contenttypes.views imports ContentType.
256 from django.contrib.contenttypes import views as contenttype_views
257 from django.urls import include, path, re_path
259 def wrap(view, cacheable=False):
260 def wrapper(*args, **kwargs):
261 return self.admin_view(view, cacheable)(*args, **kwargs)
263 wrapper.admin_site = self
264 return update_wrapper(wrapper, view)
266 # Admin-site-wide views.
267 urlpatterns = [
268 path("", wrap(self.index), name="index"),
269 path("login/", self.login, name="login"),
270 path("logout/", wrap(self.logout), name="logout"),
271 path(
272 "password_change/",
273 wrap(self.password_change, cacheable=True),
274 name="password_change",
275 ),
276 path(
277 "password_change/done/",
278 wrap(self.password_change_done, cacheable=True),
279 name="password_change_done",
280 ),
281 path("autocomplete/", wrap(self.autocomplete_view), name="autocomplete"),
282 path("jsi18n/", wrap(self.i18n_javascript, cacheable=True), name="jsi18n"),
283 path(
284 "r/<int:content_type_id>/<path:object_id>/",
285 wrap(contenttype_views.shortcut),
286 name="view_on_site",
287 ),
288 ]
290 # Add in each model's views, and create a list of valid URLS for the
291 # app_index
292 valid_app_labels = []
293 for model, model_admin in self._registry.items():
294 urlpatterns += [
295 path(
296 "%s/%s/" % (model._meta.app_label, model._meta.model_name),
297 include(model_admin.urls),
298 ),
299 ]
300 if model._meta.app_label not in valid_app_labels:
301 valid_app_labels.append(model._meta.app_label)
303 # If there were ModelAdmins registered, we should have a list of app
304 # labels for which we need to allow access to the app_index view,
305 if valid_app_labels: 305 ↛ 311line 305 didn't jump to line 311, because the condition on line 305 was never false
306 regex = r"^(?P<app_label>" + "|".join(valid_app_labels) + ")/$"
307 urlpatterns += [
308 re_path(regex, wrap(self.app_index), name="app_list"),
309 ]
311 if self.final_catch_all_view: 311 ↛ 314line 311 didn't jump to line 314, because the condition on line 311 was never false
312 urlpatterns.append(re_path(r"(?P<url>.*)$", wrap(self.catch_all_view)))
314 return urlpatterns
316 @property
317 def urls(self):
318 return self.get_urls(), "admin", self.name
320 def each_context(self, request):
321 """
322 Return a dictionary of variables to put in the template context for
323 *every* page in the admin site.
325 For sites running on a subpath, use the SCRIPT_NAME value if site_url
326 hasn't been customized.
327 """
328 script_name = request.META["SCRIPT_NAME"]
329 site_url = (
330 script_name if self.site_url == "/" and script_name else self.site_url
331 )
332 return {
333 "site_title": self.site_title,
334 "site_header": self.site_header,
335 "site_url": site_url,
336 "has_permission": self.has_permission(request),
337 "available_apps": self.get_app_list(request),
338 "is_popup": False,
339 "is_nav_sidebar_enabled": self.enable_nav_sidebar,
340 }
342 def password_change(self, request, extra_context=None):
343 """
344 Handle the "change password" task -- both form display and validation.
345 """
346 from django.contrib.admin.forms import AdminPasswordChangeForm
347 from django.contrib.auth.views import PasswordChangeView
349 url = reverse("admin:password_change_done", current_app=self.name)
350 defaults = {
351 "form_class": AdminPasswordChangeForm,
352 "success_url": url,
353 "extra_context": {**self.each_context(request), **(extra_context or {})},
354 }
355 if self.password_change_template is not None:
356 defaults["template_name"] = self.password_change_template
357 request.current_app = self.name
358 return PasswordChangeView.as_view(**defaults)(request)
360 def password_change_done(self, request, extra_context=None):
361 """
362 Display the "success" page after a password change.
363 """
364 from django.contrib.auth.views import PasswordChangeDoneView
366 defaults = {
367 "extra_context": {**self.each_context(request), **(extra_context or {})},
368 }
369 if self.password_change_done_template is not None:
370 defaults["template_name"] = self.password_change_done_template
371 request.current_app = self.name
372 return PasswordChangeDoneView.as_view(**defaults)(request)
374 def i18n_javascript(self, request, extra_context=None):
375 """
376 Display the i18n JavaScript that the Django admin requires.
378 `extra_context` is unused but present for consistency with the other
379 admin views.
380 """
381 return JavaScriptCatalog.as_view(packages=["django.contrib.admin"])(request)
383 def logout(self, request, extra_context=None):
384 """
385 Log out the user for the given HttpRequest.
387 This should *not* assume the user is already logged in.
388 """
389 from django.contrib.auth.views import LogoutView
391 defaults = {
392 "extra_context": {
393 **self.each_context(request),
394 # Since the user isn't logged out at this point, the value of
395 # has_permission must be overridden.
396 "has_permission": False,
397 **(extra_context or {}),
398 },
399 }
400 if self.logout_template is not None:
401 defaults["template_name"] = self.logout_template
402 request.current_app = self.name
403 return LogoutView.as_view(**defaults)(request)
405 @method_decorator(never_cache)
406 def login(self, request, extra_context=None):
407 """
408 Display the login form for the given HttpRequest.
409 """
410 if request.method == "GET" and self.has_permission(request):
411 # Already logged-in, redirect to admin index
412 index_path = reverse("admin:index", current_app=self.name)
413 return HttpResponseRedirect(index_path)
415 # Since this module gets imported in the application's root package,
416 # it cannot import models from other applications at the module level,
417 # and django.contrib.admin.forms eventually imports User.
418 from django.contrib.admin.forms import AdminAuthenticationForm
419 from django.contrib.auth.views import LoginView
421 context = {
422 **self.each_context(request),
423 "title": _("Log in"),
424 "app_path": request.get_full_path(),
425 "username": request.user.get_username(),
426 }
427 if (
428 REDIRECT_FIELD_NAME not in request.GET
429 and REDIRECT_FIELD_NAME not in request.POST
430 ):
431 context[REDIRECT_FIELD_NAME] = reverse("admin:index", current_app=self.name)
432 context.update(extra_context or {})
434 defaults = {
435 "extra_context": context,
436 "authentication_form": self.login_form or AdminAuthenticationForm,
437 "template_name": self.login_template or "admin/login.html",
438 }
439 request.current_app = self.name
440 return LoginView.as_view(**defaults)(request)
442 def autocomplete_view(self, request):
443 return AutocompleteJsonView.as_view(admin_site=self)(request)
445 @no_append_slash
446 def catch_all_view(self, request, url):
447 if settings.APPEND_SLASH and not url.endswith("/"):
448 urlconf = getattr(request, "urlconf", None)
449 try:
450 match = resolve("%s/" % request.path_info, urlconf)
451 except Resolver404:
452 pass
453 else:
454 if getattr(match.func, "should_append_slash", True):
455 return HttpResponsePermanentRedirect("%s/" % request.path)
456 raise Http404
458 def _build_app_dict(self, request, label=None):
459 """
460 Build the app dictionary. The optional `label` parameter filters models
461 of a specific app.
462 """
463 app_dict = {}
465 if label:
466 models = {
467 m: m_a
468 for m, m_a in self._registry.items()
469 if m._meta.app_label == label
470 }
471 else:
472 models = self._registry
474 for model, model_admin in models.items():
475 app_label = model._meta.app_label
477 has_module_perms = model_admin.has_module_permission(request)
478 if not has_module_perms:
479 continue
481 perms = model_admin.get_model_perms(request)
483 # Check whether user has any perm for this module.
484 # If so, add the module to the model_list.
485 if True not in perms.values():
486 continue
488 info = (app_label, model._meta.model_name)
489 model_dict = {
490 "model": model,
491 "name": capfirst(model._meta.verbose_name_plural),
492 "object_name": model._meta.object_name,
493 "perms": perms,
494 "admin_url": None,
495 "add_url": None,
496 }
497 if perms.get("change") or perms.get("view"):
498 model_dict["view_only"] = not perms.get("change")
499 try:
500 model_dict["admin_url"] = reverse(
501 "admin:%s_%s_changelist" % info, current_app=self.name
502 )
503 except NoReverseMatch:
504 pass
505 if perms.get("add"):
506 try:
507 model_dict["add_url"] = reverse(
508 "admin:%s_%s_add" % info, current_app=self.name
509 )
510 except NoReverseMatch:
511 pass
513 if app_label in app_dict:
514 app_dict[app_label]["models"].append(model_dict)
515 else:
516 app_dict[app_label] = {
517 "name": apps.get_app_config(app_label).verbose_name,
518 "app_label": app_label,
519 "app_url": reverse(
520 "admin:app_list",
521 kwargs={"app_label": app_label},
522 current_app=self.name,
523 ),
524 "has_module_perms": has_module_perms,
525 "models": [model_dict],
526 }
528 if label:
529 return app_dict.get(label)
530 return app_dict
532 def get_app_list(self, request):
533 """
534 Return a sorted list of all the installed apps that have been
535 registered in this site.
536 """
537 app_dict = self._build_app_dict(request)
539 # Sort the apps alphabetically.
540 app_list = sorted(app_dict.values(), key=lambda x: x["name"].lower())
542 # Sort the models alphabetically within each app.
543 for app in app_list:
544 app["models"].sort(key=lambda x: x["name"])
546 return app_list
548 def index(self, request, extra_context=None):
549 """
550 Display the main admin index page, which lists all of the installed
551 apps that have been registered in this site.
552 """
553 app_list = self.get_app_list(request)
555 context = {
556 **self.each_context(request),
557 "title": self.index_title,
558 "subtitle": None,
559 "app_list": app_list,
560 **(extra_context or {}),
561 }
563 request.current_app = self.name
565 return TemplateResponse(
566 request, self.index_template or "admin/index.html", context
567 )
569 def app_index(self, request, app_label, extra_context=None):
570 app_dict = self._build_app_dict(request, app_label)
571 if not app_dict:
572 raise Http404("The requested admin page does not exist.")
573 # Sort the models alphabetically within each app.
574 app_dict["models"].sort(key=lambda x: x["name"])
575 context = {
576 **self.each_context(request),
577 "title": _("%(app)s administration") % {"app": app_dict["name"]},
578 "subtitle": None,
579 "app_list": [app_dict],
580 "app_label": app_label,
581 **(extra_context or {}),
582 }
584 request.current_app = self.name
586 return TemplateResponse(
587 request,
588 self.app_index_template
589 or ["admin/%s/app_index.html" % app_label, "admin/app_index.html"],
590 context,
591 )
594class DefaultAdminSite(LazyObject):
595 def _setup(self):
596 AdminSiteClass = import_string(apps.get_app_config("admin").default_site)
597 self._wrapped = AdminSiteClass()
599 def __repr__(self):
600 return repr(self._wrapped)
603# This global object represents the default admin site, for the common case.
604# You can provide your own AdminSite using the (Simple)AdminConfig.default_site
605# attribute. You can also instantiate AdminSite in your own code to create a
606# custom admin site.
607site = DefaultAdminSite()