refactor: Extract get_username_from_data helper to remove duplication

This commit is contained in:
Adeleke Omotayo 2026-03-20 13:55:39 +00:00
parent 061f605d59
commit 694dafc092
2 changed files with 69 additions and 25 deletions

View file

@ -132,6 +132,32 @@ def get_credentials(username: Optional[str] = None, **kwargs) -> dict:
return credentials
def get_username_from_data(data: dict) -> Optional[str]:
"""
Extract username from a dict-like object (credentials or request data).
Tries AXES_USERNAME_FORM_FIELD first, then falls back to Django's USERNAME_FIELD
for compatibility with Django's user_login_failed signal.
See: https://github.com/jazzband/django-axes/issues/1159
:param data: dict-like object containing username (credentials or request.POST)
:return: username string or None if not found
"""
username = data.get(settings.AXES_USERNAME_FORM_FIELD, None)
if username is None:
# Fallback to Django's USERNAME_FIELD for compatibility with
# Django's user_login_failed signal which uses USERNAME_FIELD as the key
username_field = get_user_model().USERNAME_FIELD
if username_field != str(settings.AXES_USERNAME_FORM_FIELD):
log.debug(
"Falling back to USERNAME_FIELD '%s' for data lookup",
username_field,
)
username = data.get(username_field, None)
return username
def get_client_username(
request: HttpRequest, credentials: Optional[dict] = None
) -> str:
@ -165,37 +191,14 @@ def get_client_username(
log.debug(
"Using parameter credentials to get username with key settings.AXES_USERNAME_FORM_FIELD"
)
username = credentials.get(settings.AXES_USERNAME_FORM_FIELD, None)
if username is None:
# Fallback to Django's USERNAME_FIELD for compatibility with
# Django's user_login_failed signal which uses USERNAME_FIELD as the key
# See: https://github.com/jazzband/django-axes/issues/1159
username_field = get_user_model().USERNAME_FIELD
if username_field != str(settings.AXES_USERNAME_FORM_FIELD):
log.debug(
"Falling back to USERNAME_FIELD '%s' for credentials lookup",
username_field,
)
username = credentials.get(username_field, None)
return username # type: ignore[return-value]
return get_username_from_data(credentials) # type: ignore[return-value]
log.debug(
"Using parameter request.POST to get username with key settings.AXES_USERNAME_FORM_FIELD"
)
request_data = getattr(request, "data", request.POST)
username = request_data.get(settings.AXES_USERNAME_FORM_FIELD, None)
if username is None:
# Fallback to Django's USERNAME_FIELD for compatibility
# See: https://github.com/jazzband/django-axes/issues/1159
username_field = get_user_model().USERNAME_FIELD
if username_field != str(settings.AXES_USERNAME_FORM_FIELD):
log.debug(
"Falling back to USERNAME_FIELD '%s' for request data lookup",
username_field,
)
username = request_data.get(username_field, None)
return username # type: ignore[return-value]
return get_username_from_data(request_data) # type: ignore[return-value]
def get_client_ip_address(

View file

@ -18,6 +18,7 @@ from axes.helpers import (
get_cool_off,
get_cool_off_iso8601,
get_lockout_response,
get_username_from_data,
is_client_ip_address_blacklisted,
is_client_ip_address_whitelisted,
is_client_method_whitelisted,
@ -848,6 +849,46 @@ class UsernameTestCase(AxesTestCase):
self.assertEqual(expected, actual)
class GetUsernameFromDataTestCase(AxesTestCase):
"""Unit tests for the get_username_from_data helper function."""
@override_settings(AXES_USERNAME_FORM_FIELD="username")
def test_get_username_from_data_with_matching_field(self):
"""Test that username is returned when AXES_USERNAME_FORM_FIELD matches."""
data = {"username": "test-user"}
self.assertEqual(get_username_from_data(data), "test-user")
@override_settings(AXES_USERNAME_FORM_FIELD="custom-field")
def test_get_username_from_data_fallback_to_username_field(self):
"""
Test that when AXES_USERNAME_FORM_FIELD doesn't exist in data,
we fallback to Django's USERNAME_FIELD.
"""
# Django's default USERNAME_FIELD is "username"
data = {"username": "fallback-user"}
self.assertEqual(get_username_from_data(data), "fallback-user")
@override_settings(AXES_USERNAME_FORM_FIELD="custom-field")
def test_get_username_from_data_custom_field_takes_priority(self):
"""Test that AXES_USERNAME_FORM_FIELD takes priority over USERNAME_FIELD."""
data = {
"custom-field": "custom-user",
"username": "fallback-user",
}
self.assertEqual(get_username_from_data(data), "custom-user")
@override_settings(AXES_USERNAME_FORM_FIELD="nonexistent")
def test_get_username_from_data_returns_none_when_not_found(self):
"""Test that None is returned when username is not found in any field."""
data = {"other-field": "some-value"}
self.assertIsNone(get_username_from_data(data))
@override_settings(AXES_USERNAME_FORM_FIELD="username")
def test_get_username_from_data_with_empty_dict(self):
"""Test that None is returned for empty dict."""
self.assertIsNone(get_username_from_data({}))
def get_username(request, credentials: dict) -> str:
return "username"