summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSezer BOZKIR <admin@sezerbozkir.com>2026-04-07 11:20:16 +0300
committerJacob Walls <jacobtylerwalls@gmail.com>2026-06-11 10:10:47 -0400
commitdc4e5461faa390991e6a0f6d3ca47690e8f5c4e1 (patch)
treeff68f0dab17b27839da4cbdff0c40e077cf0a6d1
parentc6f81b383251dc9fd0918b4ba040c993444b931e (diff)
Fixed #36837 -- Skipped backends not implementing (a)get_user() in (a)force_login().
Co-authored-by: Mykhailo Havelia <Arfey17.mg@gmail.com>
-rw-r--r--django/test/client.py7
-rw-r--r--docs/releases/6.2.txt4
-rw-r--r--docs/topics/testing/tools.txt12
-rw-r--r--tests/test_client/auth_backends.py8
-rw-r--r--tests/test_client/tests.py19
5 files changed, 44 insertions, 6 deletions
diff --git a/django/test/client.py b/django/test/client.py
index 0f986d5a6c..d499045363 100644
--- a/django/test/client.py
+++ b/django/test/client.py
@@ -865,10 +865,15 @@ class ClientMixin:
def _get_backend(self):
from django.contrib.auth import load_backend
+ from django.contrib.auth.backends import BaseBackend
+
+ def overrides(backend, name):
+ base = getattr(BaseBackend, name)
+ return getattr(type(backend), name, base) is not base
for backend_path in settings.AUTHENTICATION_BACKENDS:
backend = load_backend(backend_path)
- if hasattr(backend, "get_user"):
+ if overrides(backend, "get_user") or overrides(backend, "aget_user"):
return backend_path
def _login(self, user, backend=None):
diff --git a/docs/releases/6.2.txt b/docs/releases/6.2.txt
index 74ba31f171..b949b0aa24 100644
--- a/docs/releases/6.2.txt
+++ b/docs/releases/6.2.txt
@@ -222,7 +222,9 @@ Templates
Tests
~~~~~
-* ...
+* :meth:`~django.test.Client.force_login` now skips members of
+ :setting:`AUTHENTICATION_BACKENDS` not implementing ``(a)get_user()``, e.g.
+ permission-only backends.
URLs
~~~~
diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt
index da9dd38eec..6eeb96817b 100644
--- a/docs/topics/testing/tools.txt
+++ b/docs/topics/testing/tools.txt
@@ -479,15 +479,21 @@ Use the ``django.test.Client`` class to make requests.
The user will have its ``backend`` attribute set to the value of the
``backend`` argument (which should be a dotted Python path string), or
- to ``settings.AUTHENTICATION_BACKENDS[0]`` if a value isn't provided.
- The :func:`~django.contrib.auth.authenticate` function called by
- :meth:`login` normally annotates the user like this.
+ if a value isn't provided, the first backend from
+ :setting:`AUTHENTICATION_BACKENDS` implementing ``get_user()`` or
+ ``aget_user()``. The :func:`~django.contrib.auth.authenticate` function
+ called by :meth:`login` normally annotates the user like this.
This method is faster than ``login()`` since the expensive
password hashing algorithms are bypassed. Also, you can speed up
``login()`` by :ref:`using a weaker hasher while testing
<speeding-up-tests-auth-hashers>`.
+ .. versionchanged:: 6.2
+
+ On older versions, backends not implementing ``(a)get_user()`` were
+ eligible to be selected.
+
.. method:: Client.logout()
.. method:: Client.alogout()
diff --git a/tests/test_client/auth_backends.py b/tests/test_client/auth_backends.py
index 97a2763aaa..a20b0be4de 100644
--- a/tests/test_client/auth_backends.py
+++ b/tests/test_client/auth_backends.py
@@ -1,4 +1,4 @@
-from django.contrib.auth.backends import ModelBackend
+from django.contrib.auth.backends import BaseBackend, ModelBackend
class TestClientBackend(ModelBackend):
@@ -7,3 +7,9 @@ class TestClientBackend(ModelBackend):
class BackendWithoutGetUserMethod:
pass
+
+
+class PermissionOnlyBackend(BaseBackend):
+ """This class inherits from BaseBackend but does not implement get_user."""
+
+ pass
diff --git a/tests/test_client/tests.py b/tests/test_client/tests.py
index bc1472d88b..2ba9455d0c 100644
--- a/tests/test_client/tests.py
+++ b/tests/test_client/tests.py
@@ -794,6 +794,25 @@ class ClientTest(TestCase):
self.client.force_login(self.u1)
self.assertEqual(self.u1.backend, "django.contrib.auth.backends.ModelBackend")
+ @override_settings(
+ AUTHENTICATION_BACKENDS=[
+ "test_client.auth_backends.PermissionOnlyBackend",
+ "django.contrib.auth.backends.ModelBackend",
+ ]
+ )
+ def test_force_login_skips_noop_get_user_backend(self):
+ """force_login() skips auth backends without concrete get_user()."""
+ self.client.force_login(self.u1)
+ self.assertEqual(self.u1.backend, "django.contrib.auth.backends.ModelBackend")
+
+ @override_settings(
+ AUTHENTICATION_BACKENDS=[
+ "test_client.auth_backends.PermissionOnlyBackend",
+ ]
+ )
+ def test_force_login_all_backends_noop(self):
+ self.assertIsNone(self.client._get_backend())
+
@override_settings(SESSION_ENGINE="django.contrib.sessions.backends.signed_cookies")
def test_logout_cookie_sessions(self):
self.test_logout()