Repository: e-valuation/EvaP

Issue:
Deactivation of external users
follow-up to #950

external users should not be marked as inactive when they are participants or contributors in any un-archived semester.
inactive users should not be able to log in or to request a login key.


Discussion:

Resolving patch:
diff --git a/evap/evaluation/auth.py b/evap/evaluation/auth.py
--- a/evap/evaluation/auth.py
+++ b/evap/evaluation/auth.py
@@ -52,6 +52,10 @@ def process_request(self, request):
         # We are seeing this user for the first time in this session, attempt to authenticate the user.
         user = auth.authenticate(request, key=key)
 
+        if user and not user.is_active:
+            messages.error(request, _("Inactive users are not allowed to login."))
+            return
+
         # If we already have an authenticated user don't try to login a new user. Show an error message if another user
         # tries to login with a URL in this situation.
         if request.user.is_authenticated:
diff --git a/evap/evaluation/forms.py b/evap/evaluation/forms.py
--- a/evap/evaluation/forms.py
+++ b/evap/evaluation/forms.py
@@ -72,6 +72,9 @@ def clean_email(self):
         except UserProfile.DoesNotExist:
             raise forms.ValidationError(_("No user with this email address was found. Please make sure to enter the email address already known to the university office."))
 
+        if not user.is_active:
+            raise forms.ValidationError(_("Inactive users cannot request login keys."))
+
         return email
 
     def get_user(self):
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -919,6 +919,8 @@ def can_staff_mark_inactive(self):
             return False
         if any(not course.is_archived for course in self.courses_participating_in.all()):
             return False
+        if any(not contribution.course.is_archived for contribution in self.contributions.all()):
+            return False
         return True
 
     @property


Test patch:
diff --git a/evap/evaluation/tests/test_auth.py b/evap/evaluation/tests/test_auth.py
--- a/evap/evaluation/tests/test_auth.py
+++ b/evap/evaluation/tests/test_auth.py
@@ -13,6 +13,8 @@ class LoginTests(WebTest):
     def setUpTestData(cls):
         cls.external_user = mommy.make(UserProfile, email="extern@extern.com")
         cls.external_user.generate_login_key()
+        cls.inactive_external_user = mommy.make(UserProfile, email="inactive@extern.com", is_active=False)
+        cls.inactive_external_user.generate_login_key()
 
     def test_login_url_works(self):
         self.assertRedirects(self.app.get(reverse("results:index")), "/?next=/results/")
@@ -31,3 +33,8 @@ def test_login_key_valid_only_once(self):
         self.external_user.refresh_from_db()
         page = self.app.get(reverse("results:index") + "?loginkey=%s" % self.external_user.login_key)
         self.assertContains(page, 'Logged in as ' + self.external_user.full_name)
+
+    def test_inactive_external_users_can_not_login(self):
+        page = self.app.get(reverse("results:index") + "?loginkey=%s" % self.inactive_external_user.login_key).follow()
+        self.assertContains(page, "Inactive users are not allowed to login")
+        self.assertNotContains(page, "Logged in")
diff --git a/evap/evaluation/tests/test_forms.py b/evap/evaluation/tests/test_forms.py
new file mode 100644
--- /dev/null
+++ b/evap/evaluation/tests/test_forms.py
@@ -0,0 +1,23 @@
+from django.test import TestCase
+
+from model_mommy import mommy
+
+from evap.evaluation.models import UserProfile
+from evap.evaluation.forms import NewKeyForm
+
+
+class TestNewKeyForm(TestCase):
+    @classmethod
+    def setUpTestData(cls):
+        cls.inactive_external_user = mommy.make(UserProfile, email="inactive@extern.com", is_active=False)
+
+    def test_inactive_external_users_can_not_request_login_key(self):
+        data = {
+            "submit_type": "new_key",
+            "email": "inactive@extern.com"
+        }
+
+        form = NewKeyForm(data)
+        self.assertFalse(form.is_valid())
+        self.assertIn("Inactive users cannot request login keys.", form.errors["email"])
+
diff --git a/evap/staff/tests/test_views.py b/evap/staff/tests/test_views.py
--- a/evap/staff/tests/test_views.py
+++ b/evap/staff/tests/test_views.py
@@ -21,6 +21,7 @@ def helper_delete_all_import_files(user_id):
     for filename in glob.glob(file_filter):
         os.remove(filename)
 
+
 # Staff - Sample Files View
 class TestDownloadSampleXlsView(ViewTest):
     test_users = ['staff']
@@ -188,8 +189,12 @@ def test_testrun_deletes_no_users(self):
     def test_deletes_users(self):
         mommy.make(UserProfile, username='testuser1')
         mommy.make(UserProfile, username='testuser2')
-        contribution = mommy.make(Contribution)
-        mommy.make(UserProfile, username='contributor', contributions=[contribution])
+        contribution1 = mommy.make(Contribution)
+        semester = mommy.make(Semester, is_archived=True)
+        course = mommy.make(Course, semester=semester, _participant_count=0, _voter_count=0)
+        contribution2 = mommy.make(Contribution, course=course)
+        mommy.make(UserProfile, username='contributor1', contributions=[contribution1])
+        mommy.make(UserProfile, username='contributor2', contributions=[contribution2])
 
         page = self.app.get(self.url, user='staff')
         form = page.forms["user-bulk-delete-form"]
@@ -208,8 +213,10 @@ def test_deletes_users(self):
         self.assertFalse(UserProfile.objects.filter(username='testuser2').exists())
         self.assertTrue(UserProfile.objects.filter(username='staff').exists())
 
-        self.assertTrue(UserProfile.objects.filter(username='contributor').exists())
-        self.assertFalse(UserProfile.objects.exclude_inactive_users().filter(username='contributor').exists())
+        self.assertTrue(UserProfile.objects.filter(username='contributor1').exists())
+        self.assertTrue(UserProfile.objects.exclude_inactive_users().filter(username='contributor1').exists())
+        self.assertTrue(UserProfile.objects.filter(username='contributor2').exists())
+        self.assertFalse(UserProfile.objects.exclude_inactive_users().filter(username='contributor2').exists())
 
         self.assertEqual(UserProfile.objects.count(), user_count_before - 1)
         self.assertEqual(UserProfile.objects.exclude_inactive_users().count(), user_count_before - 2)