summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenedict Etzel <developer@beheh.de>2025-11-10 13:29:34 +0100
committernessita <124304+nessita@users.noreply.github.com>2025-11-12 19:42:24 -0300
commit5401b125abca53200eacb62c8a10e602359b76d4 (patch)
tree72bb904a1fb153fd27411fed0a14f41468d915e3
parent66b5a6de78ac3bcdf586844eac61663fece10ab5 (diff)
Fixed #36717 -- Redirect authenticated users on admin login view to next URL.
Co-authored-by: Natalia <124304+nessita@users.noreply.github.com>
-rw-r--r--AUTHORS1
-rw-r--r--django/contrib/admin/sites.py18
-rw-r--r--django/contrib/auth/views.py24
-rw-r--r--docs/releases/6.1.txt3
-rw-r--r--tests/admin_views/tests.py26
5 files changed, 53 insertions, 19 deletions
diff --git a/AUTHORS b/AUTHORS
index 5acbe27233..5e7bca67f5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -159,6 +159,7 @@ answer newbie questions, and generally made Django that much better:
Ben Slavin <benjamin.slavin@gmail.com>
Ben Sturmfels <ben@sturm.com.au>
Bendegúz Csirmaz <csirmazbendeguz@gmail.com>
+ Benedict Etzel <developer@beheh.de>
Berker Peksag <berker.peksag@gmail.com>
Bernd Schlapsi
Bernhard Essl <me@bernhardessl.com>
diff --git a/django/contrib/admin/sites.py b/django/contrib/admin/sites.py
index 17af19fd1b..410bf20da0 100644
--- a/django/contrib/admin/sites.py
+++ b/django/contrib/admin/sites.py
@@ -416,29 +416,27 @@ class AdminSite:
"""
Display the login form for the given HttpRequest.
"""
- if request.method == "GET" and self.has_permission(request):
- # Already logged-in, redirect to admin index
- index_path = reverse("admin:index", current_app=self.name)
- return HttpResponseRedirect(index_path)
-
# Since this module gets imported in the application's root package,
# it cannot import models from other applications at the module level,
# and django.contrib.admin.forms eventually imports User.
from django.contrib.admin.forms import AdminAuthenticationForm
from django.contrib.auth.views import LoginView
+ redirect_url = LoginView().get_redirect_url(request) or reverse(
+ "admin:index", current_app=self.name
+ )
+ if request.method == "GET" and self.has_permission(request):
+ # Already logged-in, redirect accordingly.
+ return HttpResponseRedirect(redirect_url)
+
context = {
**self.each_context(request),
"title": _("Log in"),
"subtitle": None,
"app_path": request.get_full_path(),
"username": request.user.get_username(),
+ REDIRECT_FIELD_NAME: redirect_url,
}
- if (
- REDIRECT_FIELD_NAME not in request.GET
- and REDIRECT_FIELD_NAME not in request.POST
- ):
- context[REDIRECT_FIELD_NAME] = reverse("admin:index", current_app=self.name)
context.update(extra_context or {})
defaults = {
diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py
index 295f2219cf..dcffb4aca6 100644
--- a/django/contrib/auth/views.py
+++ b/django/contrib/auth/views.py
@@ -40,20 +40,28 @@ class RedirectURLMixin:
def get_success_url(self):
return self.get_redirect_url() or self.get_default_redirect_url()
- def get_redirect_url(self):
- """Return the user-originating redirect URL if it's safe."""
- redirect_to = self.request.POST.get(
- self.redirect_field_name, self.request.GET.get(self.redirect_field_name)
+ def get_redirect_url(self, request=None):
+ """Return the user-originating redirect URL if it's safe.
+
+ Optionally takes a request argument, allowing use outside class-based
+ views.
+ """
+ if request is None:
+ request = self.request
+ redirect_to = request.POST.get(
+ self.redirect_field_name, request.GET.get(self.redirect_field_name)
)
url_is_safe = url_has_allowed_host_and_scheme(
url=redirect_to,
- allowed_hosts=self.get_success_url_allowed_hosts(),
- require_https=self.request.is_secure(),
+ allowed_hosts=self.get_success_url_allowed_hosts(request),
+ require_https=request.is_secure(),
)
return redirect_to if url_is_safe else ""
- def get_success_url_allowed_hosts(self):
- return {self.request.get_host(), *self.success_url_allowed_hosts}
+ def get_success_url_allowed_hosts(self, request=None):
+ if request is None:
+ request = self.request
+ return {request.get_host(), *self.success_url_allowed_hosts}
def get_default_redirect_url(self):
"""Return the default redirect URL."""
diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt
index 036fee53cf..e0edf6876a 100644
--- a/docs/releases/6.1.txt
+++ b/docs/releases/6.1.txt
@@ -92,7 +92,8 @@ Minor features
:mod:`django.contrib.admin`
~~~~~~~~~~~~~~~~~~~~~~~~~~~
-* ...
+* The admin site login view now redirects authenticated users to the next URL,
+ if available, instead of always redirecting to the admin index page.
:mod:`django.contrib.admindocs`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py
index 868b616d76..f7eaad659e 100644
--- a/tests/admin_views/tests.py
+++ b/tests/admin_views/tests.py
@@ -2413,6 +2413,32 @@ class AdminViewPermissionsTest(TestCase):
self.assertEqual(response.status_code, 200)
self.assertEqual(response.context[REDIRECT_FIELD_NAME], reverse("admin:index"))
+ def test_login_redirect_when_logged_in(self):
+ self.client.force_login(self.superuser)
+ response = self.client.get(reverse("admin:login"))
+ self.assertRedirects(response, reverse("admin:index"))
+
+ def test_login_redirect_to_next_url_when_logged_in(self):
+ self.client.force_login(self.superuser)
+ next_url = reverse("admin:admin_views_article_add")
+ response = self.client.get(
+ reverse("admin:login"),
+ query_params={REDIRECT_FIELD_NAME: next_url},
+ )
+ self.assertRedirects(response, next_url)
+
+ def test_login_redirect_unsafe_next_url_when_logged_in(self):
+ self.client.force_login(self.superuser)
+ response = self.client.get(
+ reverse("admin:login"),
+ query_params={
+ REDIRECT_FIELD_NAME: "https://example.com/bad",
+ },
+ )
+ self.assertRedirects(
+ response, reverse("admin:index"), fetch_redirect_response=False
+ )
+
def test_login_has_permission(self):
# Regular User should not be able to login.
response = self.client.get(reverse("has_permission_admin:index"))