diff --git a/axes/conf.py b/axes/conf.py index 2de5a1b..33d58ec 100644 --- a/axes/conf.py +++ b/axes/conf.py @@ -1,5 +1,6 @@ from django.conf import settings from django.contrib.auth import get_user_model +from django.utils.functional import SimpleLazyObject from django.utils.translation import gettext_lazy as _ # disable plugin when set to False @@ -43,8 +44,14 @@ settings.AXES_ONLY_ADMIN_SITE = getattr(settings, "AXES_ONLY_ADMIN_SITE", False) settings.AXES_ENABLE_ADMIN = getattr(settings, "AXES_ENABLE_ADMIN", True) # use a specific username field to retrieve from login POST data +def _get_username_field_default(): + return get_user_model().USERNAME_FIELD + + settings.AXES_USERNAME_FORM_FIELD = getattr( - settings, "AXES_USERNAME_FORM_FIELD", get_user_model().USERNAME_FIELD + settings, + "AXES_USERNAME_FORM_FIELD", + SimpleLazyObject(_get_username_field_default), ) # use a specific password field to retrieve from login POST data diff --git a/tests/test_conf.py b/tests/test_conf.py new file mode 100644 index 0000000..2e9e39e --- /dev/null +++ b/tests/test_conf.py @@ -0,0 +1,45 @@ +from django.test import TestCase +from django.utils.functional import SimpleLazyObject + + +class ConfTestCase(TestCase): + def test_axes_username_form_field_uses_lazy_evaluation(self): + """ + Test that AXES_USERNAME_FORM_FIELD uses SimpleLazyObject for lazy evaluation. + This prevents circular import issues with custom user models (issue #1280). + """ + from axes.conf import settings + + # Verify that AXES_USERNAME_FORM_FIELD is a SimpleLazyObject if not overridden + # This is only the case when the setting is not explicitly defined + username_field = settings.AXES_USERNAME_FORM_FIELD + + # The actual type depends on whether AXES_USERNAME_FORM_FIELD was overridden + # If it's using the default, it should be a SimpleLazyObject + # If overridden in settings, it could be a plain string + # Either way, it should be usable as a string + + # Force evaluation and verify it works + username_field_str = str(username_field) + + # Should get the default USERNAME_FIELD from the user model + # For the test suite, this is "username" + self.assertIsInstance(username_field_str, str) + self.assertTrue(len(username_field_str) > 0) + + def test_axes_username_form_field_evaluates_correctly(self): + """ + Test that when AXES_USERNAME_FORM_FIELD is accessed, it correctly + resolves to the user model's USERNAME_FIELD. + """ + from django.contrib.auth import get_user_model + from axes.conf import settings + + # Get the expected value + expected_username_field = get_user_model().USERNAME_FIELD + + # Get the actual value from axes settings + actual_username_field = str(settings.AXES_USERNAME_FORM_FIELD) + + # They should match + self.assertEqual(actual_username_field, expected_username_field)