Adding support to enable/disable admin add/change/delete of ImpersonationLog's. Closes #41 #42
4 files changed, 95 insertions(+), 39 deletions(-)

M README.md
M impersonate/admin.py
M impersonate/settings.py
M impersonate/tests.py
M README.md +26 -2
@@ 3,7 3,7 @@ 
 
 Simple application to allow superusers to "impersonate" other non-superuser accounts.
 
-**Version:** 1.5.1
+**Version:** 1.6
 
 **Project Links:** [Issues][issues] - [Mailing List][pinbox] -
 [Contributing](#contributing)

          
@@ 16,7 16,7 @@ Simple application to allow superusers t
 Python / Django Support
 =======================
 
--   Python 3.6+ for Django versions 1.11, 2.2 and 3.0
+-   Python 3.6+ for Django versions 2.2+
 
 **Note:** As of version 1.4 we are only officially supporting Python
 3.6+ and following the Django support schedule. Meaning we can only

          
@@ 31,6 31,9 @@ Dependencies
 
 **NOTE:**
 
+-   **Version 1.6** has officially removed support for the old settings format.
+	Please see the [settings](#settings) section for how settings should be
+	configured.
 -   **Version 1.5 is now only officially supporting Django's 1.11, 2.2,
     and 3.0**
 -   **Version 1.4 is now officially supporting Python 3.6+ and Django

          
@@ 363,6 366,27 @@ all). This is used by the "Filter by imp
 
 It is optional, and defaults to 100.
 
+    ADMIN_DELETE_PERMISSION
+
+A boolean to enable/disable deletion of impersonation logs in the Django admin.
+
+Default is `False`
+
+    ADMIN_ADD_PERMISSION
+
+A boolean to enable/disable ability to add impersonation logs in the Django
+admin.
+
+Default is `False`
+
+    ADMIN_READ_ONLY
+
+A boolean to enable/disable "read only" mode of impersonation logs in the
+Django admin. Generally you want to leave this enabled otherwise admin users
+can alter logs within the Django admin area.
+
+Default is `True`
+
 Admin
 =====
 

          
M impersonate/admin.py +13 -0
@@ 165,5 165,18 @@ class ImpersonationLogAdmin(admin.ModelA
     def _impersonating(self, obj):
         return friendly_name(obj.impersonating)
 
+    def has_add_permission(self, request):
+        return settings.ADMIN_ADD_PERMISSION
+
+    def has_delete_permission(self, request, obj=None):
+        return settings.ADMIN_DELETE_PERMISSION
+
+    # hack to show records but limit updating.
+    # `return False` hides impersonates module in admin page
+    def has_change_permission(self, request, obj=None):
+        if settings.ADMIN_READ_ONLY:
+            return request.method in ['GET', 'HEAD']
+        return True
+
 
 admin.site.register(ImpersonationLog, ImpersonationLogAdmin)

          
M impersonate/settings.py +5 -22
@@ 1,10 1,6 @@ 
 # -*- coding: utf-8 -*-
-from __future__ import unicode_literals
-
-import warnings
+from django.conf import settings as django_settings
 from django.contrib.auth import get_user_model
-from django.conf import settings as django_settings
-
 
 User = get_user_model()
 username_field = getattr(User, 'USERNAME_FIELD', 'username')

          
@@ 23,29 19,16 @@ username_field = getattr(User, 'USERNAME
     'LOOKUP_TYPE': 'icontains',
     'SEARCH_FIELDS': [username_field, 'first_name', 'last_name', 'email'],
     'REDIRECT_URL': getattr(django_settings, 'LOGIN_REDIRECT_URL', u'/'),
+    'ADMIN_DELETE_PERMISSION': False,
+    'ADMIN_ADD_PERMISSION': False,
+    'ADMIN_READ_ONLY': True,
 }
 
 
-def deprecate_settings(name):
-    # Silly reset of specific fields, needed for tests
-    _settings['REDIRECT_URL'] = \
-                getattr(django_settings, 'LOGIN_REDIRECT_URL', u'/')
-
-    old_settings_name = 'IMPERSONATE_{0}'.format(name)
-    if hasattr(django_settings, old_settings_name):
-        warnings.warn(
-            ('The IMPERSONATE_* settings are now deprecated and will be '
-             'removed in an upcoming release. Please use the IMPERSONATE '
-             'dictionary setting.')
-        )
-        return getattr(django_settings, old_settings_name)
-    return _settings.get(name)
-
-
 class Settings(object):
     def __getattribute__(self, name):
         sdict = getattr(django_settings, 'IMPERSONATE', {})
-        return sdict.get(name, deprecate_settings(name))
+        return sdict.get(name, _settings.get(name))
 
 
 settings = Settings()

          
M impersonate/tests.py +51 -15
@@ 203,17 203,17 @@ class TestImpersonation(TestCase):
         self.assertEqual(User.objects.count(), 4)
 
     def test_dont_impersonate_superuser(self):
-        response = self._impersonate_helper('user1', 'foobar', 2)
+        self._impersonate_helper('user1', 'foobar', 2)
         self.assertEqual(self.client.session.get('_impersonate'), None)
         self.client.logout()
 
         # Try again with normal staff user
-        response = self._impersonate_helper('user3', 'foobar', 2)
+        self._impersonate_helper('user3', 'foobar', 2)
         self.assertEqual(self.client.session.get('_impersonate'), None)
         self.client.logout()
 
     def test_successful_impersonation(self):
-        response = self._impersonate_helper('user1', 'foobar', 4)
+        self._impersonate_helper('user1', 'foobar', 4)
         self.assertEqual(self.client.session['_impersonate'], 4)
         self.client.get(reverse('impersonate-stop'))
         self.assertEqual(self.client.session.get('_impersonate'), None)

          
@@ 246,7 246,7 @@ class TestImpersonation(TestCase):
         session_end.connect(on_session_end)
 
         # start the impersonation and check that the _begin signal is sent
-        response = self._impersonate_helper('user1', 'foobar', 4)
+        self._impersonate_helper('user1', 'foobar', 4)
         self.assertEqual(self.client.session['_impersonate'], 4)
         self.assertTrue(self.session_begin_fired)
         self.assertFalse(self.session_end_fired)

          
@@ 263,7 263,7 @@ class TestImpersonation(TestCase):
         self.assertFalse(self.session_end_fired)
 
         # Start impersonation
-        response = self._impersonate_helper('user1', 'foobar', 4)
+        self._impersonate_helper('user1', 'foobar', 4)
         self.assertTrue(self.session_begin_fired)
         self.assertFalse(self.session_end_fired)
 

          
@@ 278,7 278,7 @@ class TestImpersonation(TestCase):
         session_end.disconnect(on_session_end)
 
     def test_successsful_impersonation_by_staff(self):
-        response = self._impersonate_helper('user3', 'foobar', 4)
+        self._impersonate_helper('user3', 'foobar', 4)
         self.assertEqual(self.client.session['_impersonate'], 4)
         self.client.get(reverse('impersonate-stop'))
         self.assertEqual(self.client.session.get('_impersonate'), None)

          
@@ 286,7 286,7 @@ class TestImpersonation(TestCase):
 
     @override_settings(IMPERSONATE={'ALLOW_SUPERUSER': True})
     def test_successful_impersonation_of_superuser(self):
-        response = self._impersonate_helper('user1', 'foobar', 2)
+        self._impersonate_helper('user1', 'foobar', 2)
         self.assertEqual(self.client.session.get('_impersonate'), 2)
         user = User.objects.get(pk=self.client.session.get('_impersonate'))
         self.assertTrue(user.is_superuser)

          
@@ 296,18 296,18 @@ class TestImpersonation(TestCase):
 
     @override_settings(IMPERSONATE={'REQUIRE_SUPERUSER': True})
     def test_unsuccessful_impersonation_by_staff(self):
-        response = self._impersonate_helper('user3', 'foobar', 4)
+        self._impersonate_helper('user3', 'foobar', 4)
         self.assertEqual(self.client.session.get('_impersonate'), None)
         self.client.logout()
 
     @override_settings(IMPERSONATE={'ALLOW_SUPERUSER': False})
     def test_unsuccessful_impersonation_of_superuser(self):
-        response = self._impersonate_helper('user1', 'foobar', 2)
+        self._impersonate_helper('user1', 'foobar', 2)
         self.assertEqual(self.client.session.get('_impersonate'), None)
         self.client.logout()
 
     def test_unsuccessful_impersonation(self):
-        response = self._impersonate_helper('user4', 'foobar', 3)
+        self._impersonate_helper('user4', 'foobar', 3)
         self.assertEqual(self.client.session.get('_impersonate'), None)
         self.client.logout()
 

          
@@ 316,7 316,7 @@ class TestImpersonation(TestCase):
         self.assertEqual(self.client.session['_impersonate'], 4)
 
         # Don't allow impersonated users to use impersonate views
-        with self.settings(LOGIN_REDIRECT_URL='/test-redirect/'):
+        with self.settings(IMPERSONATE={'REDIRECT_URL': '/test-redirect/'}):
             response = self.client.get(reverse('impersonate-list'))
             self._redirect_check(response, '/test-redirect/')
 

          
@@ 359,7 359,7 @@ class TestImpersonation(TestCase):
         self.assertEqual(self.client.session.get('_impersonate'), None)
         self.client.logout()
 
-    @override_settings(LOGIN_REDIRECT_URL='/test-redirect-2/')
+    @override_settings(IMPERSONATE={'REDIRECT_URL': '/test-redirect-2/'})
     def test_successful_impersonation_login_redirect_url(self):
         response = self._impersonate_helper('user1', 'foobar', 4)
         self.assertEqual(self.client.session['_impersonate'], 4)

          
@@ 586,13 586,13 @@ class TestImpersonation(TestCase):
 
     def test_disable_impersonatelog_logging(self):
         self.assertFalse(ImpersonationLog.objects.exists())
-        response = self._impersonate_helper('user1', 'foobar', 4)
+        self._impersonate_helper('user1', 'foobar', 4)
         self.assertFalse(ImpersonationLog.objects.exists())
 
     @override_settings(IMPERSONATE={'DISABLE_LOGGING': False})
     def test_signals_session_begin_impersonatelog(self):
         self.assertFalse(ImpersonationLog.objects.exists())
-        response = self._impersonate_helper('user1', 'foobar', 4)
+        self._impersonate_helper('user1', 'foobar', 4)
         log = ImpersonationLog.objects.get()
         session_key = self.client.session.get('_impersonate_session_id')
         self.assertEqual(log.impersonator.id, 1)

          
@@ 604,7 604,7 @@ class TestImpersonation(TestCase):
     @override_settings(IMPERSONATE={'DISABLE_LOGGING': False})
     def test_signals_session_end_impersonatelog(self):
         self.assertFalse(ImpersonationLog.objects.exists())
-        response = self._impersonate_helper('user1', 'foobar', 4)
+        self._impersonate_helper('user1', 'foobar', 4)
         session_key = self.client.session.get('_impersonate_session_id')
         self.client.get(reverse('impersonate-stop'))
         none_session_key = self.client.session.get('_impersonate_session_id')

          
@@ 721,3 721,39 @@ class TestImpersonation(TestCase):
             (_id, name) for _id, name in _filter.lookups(None, model_admin)
         ]
         self.assertEqual(len(opts), 0)
+
+    def test_impersonatelog_admin_add_delete_permissions(self):
+        model_admin = ImpersonationLogAdmin(ImpersonationLog, AdminSite())
+        self.assertFalse(model_admin.has_add_permission(None))
+        self.assertFalse(model_admin.has_delete_permission(None))
+
+        # Custom perms
+        with self.settings(
+            IMPERSONATE={
+                'ADMIN_DELETE_PERMISSION': True,
+                'ADMIN_ADD_PERMISSION': True,
+            }
+        ):
+            model_admin = ImpersonationLogAdmin(ImpersonationLog, AdminSite())
+            self.assertTrue(model_admin.has_add_permission(None))
+            self.assertTrue(model_admin.has_delete_permission(None))
+
+    def test_impersonatelog_admin_change_permissions(self):
+        class FakeRequest:
+            method = 'GET'
+
+        request = FakeRequest()
+        model_admin = ImpersonationLogAdmin(ImpersonationLog, AdminSite())
+        self.assertTrue(model_admin.has_change_permission(request))
+        request.method = 'HEAD'
+        self.assertTrue(model_admin.has_change_permission(request))
+        request.method = 'POST'
+        self.assertFalse(model_admin.has_change_permission(request))
+
+        # Custom perms
+        with self.settings(IMPERSONATE={'ADMIN_READ_ONLY': False}):
+            model_admin = ImpersonationLogAdmin(ImpersonationLog, AdminSite())
+            request.method = 'GET'
+            self.assertTrue(model_admin.has_change_permission(request))
+            request.method = 'POST'
+            self.assertTrue(model_admin.has_change_permission(request))