1+ from functools import update_wrapper
2+
13from django .conf import settings
24from django .contrib .admin import AdminSite
35from django .contrib .auth import REDIRECT_FIELD_NAME
46from django .contrib .auth .views import redirect_to_login
7+ from django .http import HttpResponseRedirect
58from django .shortcuts import resolve_url
9+ from django .urls import reverse
10+ from django .views .decorators .cache import never_cache
11+ from django .views .decorators .csrf import csrf_protect
12+
613
7- from .utils import monkeypatch_method
14+ from .utils import default_device , monkeypatch_method
815
916try :
1017 from django .utils .http import url_has_allowed_host_and_scheme
@@ -22,25 +29,64 @@ class AdminSiteOTPRequiredMixin:
2229 use :meth:`has_permission` in order to secure those views.
2330 """
2431
32+ def has_admin_permission (self , request ):
33+ return super ().has_permission (request )
34+
2535 def has_permission (self , request ):
2636 """
2737 Returns True if the given HttpRequest has permission to view
2838 *at least one* page in the admin site.
2939 """
30- if not super ().has_permission (request ):
31- return False
32- return request .user .is_verified ()
40+ return self .has_admin_permission (request ) and request .user .is_verified ()
3341
34- def login (self , request , extra_context = None ):
42+ def admin_view (self , view , cacheable = False ):
3543 """
36- Redirects to the site login page for the given HttpRequest.
37- """
38- redirect_to = request . POST . get ( REDIRECT_FIELD_NAME , request . GET . get ( REDIRECT_FIELD_NAME ))
44+ Decorator to create an admin view attached to this ``AdminSite``. This
45+ wraps the view and provides permission checking by calling
46+ ``self.has_permission``.
3947
40- if not redirect_to or not url_has_allowed_host_and_scheme (url = redirect_to , allowed_hosts = [request .get_host ()]):
41- redirect_to = resolve_url (settings .LOGIN_REDIRECT_URL )
48+ You'll want to use this from within ``AdminSite.get_urls()``:
4249
43- return redirect_to_login (redirect_to )
50+ class MyAdminSite(AdminSite):
51+
52+ def get_urls(self):
53+ from django.urls import path
54+
55+ urls = super().get_urls()
56+ urls += [
57+ path('my_view/', self.admin_view(some_view))
58+ ]
59+ return urls
60+
61+ By default, admin_views are marked non-cacheable using the
62+ ``never_cache`` decorator. If the view can be safely cached, set
63+ cacheable=True.
64+ """
65+ def inner (request , * args , ** kwargs ):
66+ if not self .has_permission (request ):
67+ if request .path == reverse ('admin:logout' , current_app = self .name ):
68+ index_path = reverse ('admin:index' , current_app = self .name )
69+ return HttpResponseRedirect (index_path )
70+
71+ if (self .has_admin_permission (request ) and not default_device (request .user )):
72+ index_path = reverse ("two_factor:setup" , current_app = self .name )
73+ return HttpResponseRedirect (index_path )
74+
75+ # Inner import to prevent django.contrib.admin (app) from
76+ # importing django.contrib.auth.models.User (unrelated model).
77+ from django .contrib .auth .views import redirect_to_login
78+ return redirect_to_login (
79+ request .get_full_path (),
80+ reverse ('admin:login' , current_app = self .name )
81+ )
82+ return view (request , * args , ** kwargs )
83+ if not cacheable :
84+ inner = never_cache (inner )
85+ # We add csrf_protect here so this function can be used as a utility
86+ # function for any view, without having to repeat 'csrf_protect'.
87+ if not getattr (view , 'csrf_exempt' , False ):
88+ inner = csrf_protect (inner )
89+ return update_wrapper (inner , view )
4490
4591
4692class AdminSiteOTPRequired (AdminSiteOTPRequiredMixin , AdminSite ):
0 commit comments