Repository: jazzband/django-axes (python)

Issue:
"is_admin_site" has the opposite meaning
`AxesHandler.is_admin_site()` actually checks if a `request.path` is *NOT* located under the `reverse("admin:index")`.

https://github.com/jazzband/django-axes/blob/ea4d3c477dd1fda2045ed8c927b6d0bb87200d27/axes/handlers/base.py#L136-L147

Pull request:
Deprecate `is_admin_site`
Resolves #772

Patch:
diff --git a/axes/handlers/base.py b/axes/handlers/base.py
index 8c192ed..62d70f3 100644
--- a/axes/handlers/base.py
+++ b/axes/handlers/base.py
@@ -1,6 +1,7 @@
 import re
 from abc import ABC, abstractmethod
 from typing import Optional
+from warnings import warn
 
 from django.urls import reverse
 from django.urls.exceptions import NoReverseMatch
@@ -81,7 +82,7 @@ class AxesBaseHandler:  # pylint: disable=unused-argument
         and inspiration on some common checks and access restrictions before writing your own implementation.
         """
 
-        if self.is_admin_site(request):
+        if settings.AXES_ONLY_ADMIN_SITE and not self.is_admin_request(request):
             return True
 
         if self.is_blacklisted(request, credentials):
@@ -134,10 +135,41 @@ class AxesBaseHandler:  # pylint: disable=unused-argument
 
         return False
 
-    def is_admin_site(self, request) -> bool:
+    def get_admin_url(self) -> Optional[str]:
+        """
+        Returns admin url if exists, otherwise returns None
+        """
+        try:
+            return reverse("admin:index")
+        except NoReverseMatch:
+            return None
+
+    def is_admin_request(self, request) -> bool:
+        """
+        Checks that request located under admin site
         """
-        Checks if the request is for admin site.
+        if hasattr(request, "path"):
+            admin_url = self.get_admin_url()
+            return (
+                admin_url is not None
+                and re.match(f"^{admin_url}", request.path) is not None
+            )
+
+        return False
+
+    def is_admin_site(self, request) -> bool:
         """
+        Checks if the request is NOT for admin site
+        if `settings.AXES_ONLY_ADMIN_SITE` is True.
+        """
+        warn(
+            (
+                "This method is deprecated and will be removed in future versions. "
+                "If you looking for method that checks if `request.path` located under "
+                "admin site, use `is_admin_request` instead."
+            ),
+            DeprecationWarning,
+        )
         if settings.AXES_ONLY_ADMIN_SITE and hasattr(request, "path"):
             try:
                 admin_url = reverse("admin:index")


Test patch:
diff --git a/tests/test_handlers.py b/tests/test_handlers.py
index 6d216e9..0c40573 100644
--- a/tests/test_handlers.py
+++ b/tests/test_handlers.py
@@ -55,14 +55,36 @@ class AxesHandlerTestCase(AxesTestCase):
         for setting_value, url, expected in tests:
             with override_settings(AXES_ONLY_ADMIN_SITE=setting_value):
                 request.path = url
-                self.assertEqual(AxesProxyHandler().is_admin_site(request), expected)
+                with self.assertWarns(DeprecationWarning):
+                    self.assertEqual(AxesProxyHandler().is_admin_site(request), expected)
+
+    def test_is_admin_request(self):
+        request = MagicMock()
+        tests = (  # (URL, Expected)
+            ("/test/", False),
+            (reverse("admin:index"), True),
+        )
+
+        for url, expected in tests:
+            request.path = url
+            self.assertEqual(AxesProxyHandler().is_admin_request(request), expected)
 
     @override_settings(ROOT_URLCONF="tests.urls_empty")
     @override_settings(AXES_ONLY_ADMIN_SITE=True)
     def test_is_admin_site_no_admin_site(self):
         request = MagicMock()
         request.path = "/admin/"
-        self.assertTrue(AxesProxyHandler().is_admin_site(self.request))
+        with self.assertWarns(DeprecationWarning):
+            self.assertTrue(AxesProxyHandler().is_admin_site(self.request))
+
+    @override_settings(ROOT_URLCONF="tests.urls_empty")
+    def test_is_admin_request_no_admin_site(self):
+        request = MagicMock()
+        request.path = "/admin/"
+        self.assertFalse(AxesProxyHandler().is_admin_request(self.request))
+
+    def test_is_admin_request_no_path(self):
+        self.assertFalse(AxesProxyHandler().is_admin_request(self.request))
 
 
 class AxesProxyHandlerTestCase(AxesTestCase):