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
612
7- from .utils import monkeypatch_method
13+ from .utils import default_device , monkeypatch_method
814
915try :
1016 from django .utils .http import url_has_allowed_host_and_scheme
@@ -22,25 +28,64 @@ class AdminSiteOTPRequiredMixin:
2228 use :meth:`has_permission` in order to secure those views.
2329 """
2430
31+ def has_admin_permission (self , request ):
32+ return super ().has_permission (request )
33+
2534 def has_permission (self , request ):
2635 """
2736 Returns True if the given HttpRequest has permission to view
2837 *at least one* page in the admin site.
2938 """
30- if not super ().has_permission (request ):
31- return False
32- return request .user .is_verified ()
39+ return self .has_admin_permission (request ) and request .user .is_verified ()
3340
34- def login (self , request , extra_context = None ):
41+ def admin_view (self , view , cacheable = False ):
3542 """
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 ))
43+ Decorator to create an admin view attached to this ``AdminSite``. This
44+ wraps the view and provides permission checking by calling
45+ ``self.has_permission``.
3946
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 )
47+ You'll want to use this from within ``AdminSite.get_urls()``:
4248
43- return redirect_to_login (redirect_to )
49+ class MyAdminSite(AdminSite):
50+
51+ def get_urls(self):
52+ from django.urls import path
53+
54+ urls = super().get_urls()
55+ urls += [
56+ path('my_view/', self.admin_view(some_view))
57+ ]
58+ return urls
59+
60+ By default, admin_views are marked non-cacheable using the
61+ ``never_cache`` decorator. If the view can be safely cached, set
62+ cacheable=True.
63+ """
64+ def inner (request , * args , ** kwargs ):
65+ if not self .has_permission (request ):
66+ if request .path == reverse ('admin:logout' , current_app = self .name ):
67+ index_path = reverse ('admin:index' , current_app = self .name )
68+ return HttpResponseRedirect (index_path )
69+
70+ if (self .has_admin_permission (request ) and not default_device (request .user )):
71+ index_path = reverse ("two_factor:setup" , current_app = self .name )
72+ return HttpResponseRedirect (index_path )
73+
74+ # Inner import to prevent django.contrib.admin (app) from
75+ # importing django.contrib.auth.models.User (unrelated model).
76+ from django .contrib .auth .views import redirect_to_login
77+ return redirect_to_login (
78+ request .get_full_path (),
79+ reverse ('admin:login' , current_app = self .name )
80+ )
81+ return view (request , * args , ** kwargs )
82+ if not cacheable :
83+ inner = never_cache (inner )
84+ # We add csrf_protect here so this function can be used as a utility
85+ # function for any view, without having to repeat 'csrf_protect'.
86+ if not getattr (view , 'csrf_exempt' , False ):
87+ inner = csrf_protect (inner )
88+ return update_wrapper (inner , view )
4489
4590
4691class AdminSiteOTPRequired (AdminSiteOTPRequiredMixin , AdminSite ):
0 commit comments