Repository: jazzband/django-axes

Issue:
Django 2.1 LTS support
At the moment Django 2.1 tests don't work. As it is the LTS version it would be very valuable to investigate why the tests fail against the admin views and how they could be fixed.


Discussion:
My opinion is to abandon the admin views altogether in favor of the authentication backend approach
@markddavidoff Yeah, I think that is the correct and neater approach here. Depending on an application that can change (even if it does so rarely) is really not good practice. 

We should be testing against the authentication backend on the unit level and then make integration tests that verify the authentication backend behaves correctly when plugged in with user applications and other 3rd party applications like DRF.

I think we should refactor the test suite a bit by introducing tests that test the current suite against the authentication backend instead of the admin login views.

Resolving patch:
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -38,6 +38,7 @@
         'Framework :: Django',
         'Framework :: Django :: 1.11',
         'Framework :: Django :: 2.0',
+        'Framework :: Django :: 2.1',
         'Intended Audience :: Developers',
         'Intended Audience :: System Administrators',
         'License :: OSI Approved :: MIT License',


Test patch:
diff --git a/axes/tests/test_access_attempt.py b/axes/tests/test_access_attempt.py
--- a/axes/tests/test_access_attempt.py
+++ b/axes/tests/test_access_attempt.py
@@ -89,7 +89,7 @@ def test_failure_limit_once(self):
         for _ in range(1, settings.AXES_FAILURE_LIMIT):
             response = self._login()
             # Check if we are in the same login page
-            self.assertContains(response, self.LOGIN_FORM_KEY)
+            self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
 
         # So, we shouldn't have gotten a lock-out yet.
         # But we should get one now
@@ -103,7 +103,7 @@ def test_failure_limit_many(self):
         for _ in range(1, settings.AXES_FAILURE_LIMIT):
             response = self._login()
             # Check if we are in the same login page
-            self.assertContains(response, self.LOGIN_FORM_KEY)
+            self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
 
         # So, we shouldn't have gotten a lock-out yet.
         # We should get a locked message each time we try again
@@ -115,7 +115,7 @@ def test_valid_login(self):
         """Tests a valid login for a real username
         """
         response = self._login(is_valid_username=True, is_valid_password=True)
-        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
+        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302, html=True)
 
     def test_valid_logout(self):
         """Tests a valid logout and make sure the logout_time is updated
@@ -156,7 +156,7 @@ def test_long_user_agent_valid(self):
             is_valid_password=True,
             user_agent=long_user_agent,
         )
-        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
+        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302, html=True)
 
     def test_long_user_agent_not_valid(self):
         """Tests if can handle a long user agent with failure
@@ -294,7 +294,7 @@ def test_lockout_by_combination_user_and_ip(self):
                 is_valid_password=False,
             )
             # Check if we are in the same login page
-            self.assertContains(response, self.LOGIN_FORM_KEY)
+            self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
 
         # So, we shouldn't have gotten a lock-out yet.
         # But we should get one now
@@ -313,7 +313,7 @@ def test_lockout_by_user_only(self):
                 is_valid_password=False,
             )
             # Check if we are in the same login page
-            self.assertContains(response, self.LOGIN_FORM_KEY)
+            self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
 
         # So, we shouldn't have gotten a lock-out yet.
         # But we should get one now
@@ -328,7 +328,7 @@ def test_lockout_by_user_only(self):
             is_valid_password=True,
         )
         # Check if we are still in the login page
-        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
+        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302, html=True)
 
         # now create failure_limit + 1 failed logins and then we should still
         # be able to login with valid_username
@@ -339,7 +339,7 @@ def test_lockout_by_user_only(self):
             )
         # Check if we can still log in with valid user
         response = self._login(is_valid_username=True, is_valid_password=True)
-        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
+        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302, html=True)
 
     def test_log_data_truncated(self):
         """Tests that query2str properly truncates data to the
@@ -368,7 +368,7 @@ def test_valid_logout_without_success_log(self):
         response = self.client.get(reverse('admin:logout'))
 
         self.assertEqual(AccessLog.objects.all().count(), 0)
-        self.assertContains(response, 'Logged out')
+        self.assertContains(response, 'Logged out', html=True)
 
     @override_settings(AXES_DISABLE_SUCCESS_ACCESS_LOG=True)
     def test_valid_login_without_success_log(self):
@@ -391,7 +391,7 @@ def test_valid_logout_without_log(self):
         response = self.client.get(reverse('admin:logout'))
 
         self.assertEqual(AccessLog.objects.first().logout_time, None)
-        self.assertContains(response, 'Logged out')
+        self.assertContains(response, 'Logged out', html=True)
 
     @override_settings(AXES_DISABLE_ACCESS_LOG=True)
     def test_non_valid_login_without_log(self):
@@ -437,11 +437,11 @@ def _assert_resets_on_success(self):
         for _ in range(settings.AXES_FAILURE_LIMIT - 1):
             response = self._login()
             # Check if we are in the same login page
-            self.assertContains(response, self.LOGIN_FORM_KEY)
+            self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
 
         # Perform a valid login
         response = self._login(is_valid_username=True, is_valid_password=True)
-        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302)
+        self.assertNotContains(response, self.LOGIN_FORM_KEY, status_code=302, html=True)
 
         return self._login()
 
@@ -465,11 +465,11 @@ def test_reset_on_success(self):
 
         # So, we shouldn't have found a lock-out yet.
         # And we shouldn't find one now
-        self.assertContains(response, self.LOGIN_FORM_KEY)
+        self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
         for _ in range(settings.AXES_FAILURE_LIMIT - 2):
             response = self._login()
             # Check if we are on the same login page.
-            self.assertContains(response, self.LOGIN_FORM_KEY)
+            self.assertContains(response, self.LOGIN_FORM_KEY, html=True)
 
         # But we should find one now
         response = self._login()
diff --git a/axes/tests/test_access_attempt_config.py b/axes/tests/test_access_attempt_config.py
--- a/axes/tests/test_access_attempt_config.py
+++ b/axes/tests/test_access_attempt_config.py
@@ -193,7 +193,7 @@ def test_lockout_by_user_with_empty_username_allows_other_users_without_cache(se
 
         # Still possible to access the login page
         response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1)
-        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200)
+        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200, html=True)
 
     # Test for true and false positives when blocking by user and IP together.
     # Cache disabled. When LOCK_OUT_BY_COMBINATION_USER_AND_IP = True
@@ -256,7 +256,7 @@ def test_lockout_by_user_and_ip_with_empty_username_allows_other_users_without_c
 
         # Still possible to access the login page
         response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1)
-        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200)
+        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200, html=True)
 
     # Test for true and false positives when blocking by IP *OR* user (default)
     # With cache enabled. Default criteria.
@@ -315,7 +315,7 @@ def test_lockout_by_user_with_empty_username_allows_other_users_using_cache(self
 
         # Still possible to access the login page
         response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1)
-        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200)
+        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200, html=True)
 
     # Test for true and false positives when blocking by user only.
     # With cache enabled. When AXES_ONLY_USER_FAILURES = True
@@ -432,4 +432,4 @@ def test_lockout_by_user_and_ip_with_empty_username_allows_other_users_using_cac
 
         # Still possible to access the login page
         response = self.client.get(reverse('admin:login'), REMOTE_ADDR=self.IP_1)
-        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200)
+        self.assertContains(response, self.LOGIN_FORM_KEY, status_code=200, html=True)