From 7f4b2af1ab73b49a7deb0e40260a2b4620668a16 Mon Sep 17 00:00:00 2001 From: kuldeepkhatke Date: Sun, 22 Jun 2025 12:40:04 +0530 Subject: [PATCH 1/3] AccessAttemptAdmin.list_display datatype change tuple->list for customization --- axes/admin.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/axes/admin.py b/axes/admin.py index 877ab75f..7486c5dc 100644 --- a/axes/admin.py +++ b/axes/admin.py @@ -7,25 +7,17 @@ class AccessAttemptAdmin(admin.ModelAdmin): + list_display = [ + "attempt_time", + "ip_address", + "user_agent", + "username", + "path_info", + "failures_since_start", + ] + if settings.AXES_USE_ATTEMPT_EXPIRATION: - list_display = ( - "attempt_time", - "expiration", - "ip_address", - "user_agent", - "username", - "path_info", - "failures_since_start", - ) - else: - list_display = ( - "attempt_time", - "ip_address", - "user_agent", - "username", - "path_info", - "failures_since_start", - ) + list_display.append('expiration') list_filter = ["attempt_time", "path_info"] From b6b8f9a3be0ac8149923649198b2875ca247d7c7 Mon Sep 17 00:00:00 2001 From: kuldeepkhatke Date: Sun, 22 Jun 2025 13:16:25 +0530 Subject: [PATCH 2/3] Added status column to list display --- axes/admin.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/axes/admin.py b/axes/admin.py index 7486c5dc..0f21a467 100644 --- a/axes/admin.py +++ b/axes/admin.py @@ -21,6 +21,11 @@ class AccessAttemptAdmin(admin.ModelAdmin): list_filter = ["attempt_time", "path_info"] + if isinstance(settings.AXES_FAILURE_LIMIT, int) and settings.AXES_FAILURE_LIMIT > 0: + # This will only add the status field if AXES_FAILURE_LIMIT is set to a positive integer + # Because callable failure limit requires scope of request object + list_display.append("status") + search_fields = ["ip_address", "username", "user_agent", "path_info"] date_hierarchy = "attempt_time" @@ -49,6 +54,10 @@ def has_add_permission(self, request: HttpRequest) -> bool: def expiration(self, obj: AccessAttempt): return obj.expiration.expires_at if hasattr(obj, "expiration") else _("Not set") + + def status(self, obj: AccessAttempt): + return f"{settings.AXES_FAILURE_LIMIT - obj.failures_since_start} "+_("Attempt Remaining") if \ + obj.failures_since_start < settings.AXES_FAILURE_LIMIT else _("Locked Out") class AccessLogAdmin(admin.ModelAdmin): list_display = ( From 9d3f4a72f2f08350bda7990fe672c7e3659f78f9 Mon Sep 17 00:00:00 2001 From: kuldeepkhatke Date: Sun, 22 Jun 2025 13:16:53 +0530 Subject: [PATCH 3/3] Added IsLockedOutFilter to AccessAttemptAdmin --- axes/admin.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/axes/admin.py b/axes/admin.py index 0f21a467..9cae2e60 100644 --- a/axes/admin.py +++ b/axes/admin.py @@ -6,6 +6,24 @@ from axes.models import AccessAttempt, AccessLog, AccessFailureLog +class IsLockedOutFilter(admin.SimpleListFilter): + title = _("Locked Out") + parameter_name = "locked_out" + + def lookups(self, request, model_admin): + return ( + ("yes", _("Yes")), + ("no", _("No")), + ) + + def queryset(self, request, queryset): + if self.value() == "yes": + return queryset.filter(failures_since_start__gte=settings.AXES_FAILURE_LIMIT) + elif self.value() == "no": + return queryset.filter(failures_since_start__lt=settings.AXES_FAILURE_LIMIT) + return queryset + + class AccessAttemptAdmin(admin.ModelAdmin): list_display = [ "attempt_time", @@ -25,6 +43,7 @@ class AccessAttemptAdmin(admin.ModelAdmin): # This will only add the status field if AXES_FAILURE_LIMIT is set to a positive integer # Because callable failure limit requires scope of request object list_display.append("status") + list_filter.append(IsLockedOutFilter) search_fields = ["ip_address", "username", "user_agent", "path_info"]