Repository: e-valuation/EvaP

Issue:
Fix semester deletion, add additional confirmation
* right now, deleting a semester that has any courses in it fails with a 500. the delete-button should not be enabled in this case in the first place. also, right now archived semesters cannot be deleted, that should be changed. check out `Semester.can_staff_delete`.

* To be precise, a semester should be deletable if it is either archived or has no courses. For archived semesters, their courses should be deleted one by one in `staff.views.semester_delete`

* when deleting archived semesters, there should be some additional confirmation, e.g. `enter "WT 12/13" in the text box below to confirm` since deleting semesters potentially means a rather large amount of data to be deleted at once.


Discussion:

Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -58,7 +58,7 @@ def __str__(self):
 
     @property
     def can_be_deleted_by_manager(self):
-        return all(evaluation.can_be_deleted_by_manager for evaluation in self.evaluations.all())
+        return self.evaluations.count() == 0 or (self.participations_are_archived and self.grade_documents_are_deleted and self.results_are_archived)
 
     @property
     def participations_can_be_archived(self):
diff --git a/evap/staff/importers.py b/evap/staff/importers.py
--- a/evap/staff/importers.py
+++ b/evap/staff/importers.py
@@ -14,7 +14,7 @@
 def create_user_list_string_for_message(users):
     msg = ""
     for user in users:
-        msg += "<br>"
+        msg += "<br />"
         msg += "{} {} ({})".format(user.first_name, user.last_name, user.username)
     return msg
 
@@ -219,8 +219,8 @@ def _create_user_data_mismatch_warning(user, user_data, test_run):
         else:
             msg = _("The existing user was overwritten with the following data:")
         return (mark_safe(msg
-            + "<br> - " + ExcelImporter._create_user_string(user) + _(" (existing)")
-            + "<br> - " + ExcelImporter._create_user_string(user_data) + _(" (new)")))
+            + "<br /> - " + ExcelImporter._create_user_string(user) + _(" (existing)")
+            + "<br /> - " + ExcelImporter._create_user_string(user_data) + _(" (new)")))
 
     @staticmethod
     def _create_user_inactive_warning(user, test_run):
@@ -233,8 +233,8 @@ def _create_user_inactive_warning(user, test_run):
     def _create_user_name_collision_warning(self, user_data, users_with_same_names):
         warningstring = _("An existing user has the same first and last name as a new user:")
         for user in users_with_same_names:
-            warningstring += "<br> - " + self._create_user_string(user) + _(" (existing)")
-        warningstring += "<br> - " + self._create_user_string(user_data) + _(" (new)")
+            warningstring += "<br /> - " + self._create_user_string(user) + _(" (existing)")
+        warningstring += "<br /> - " + self._create_user_string(user_data) + _(" (new)")
         self.warnings[self.W_DUPL].append(mark_safe(warningstring))
 
     def check_user_data_sanity(self, test_run):
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -406,6 +406,11 @@ def semester_delete(request):
 
     if not semester.can_be_deleted_by_manager:
         raise SuspiciousOperation("Deleting semester not allowed")
+    RatingAnswerCounter.objects.filter(contribution__evaluation__course__semester=semester).delete()
+    TextAnswer.objects.filter(contribution__evaluation__course__semester=semester).delete()
+    Contribution.objects.filter(evaluation__course__semester=semester).delete()
+    Evaluation.objects.filter(course__semester=semester).delete()
+    Course.objects.filter(semester=semester).delete()
     semester.delete()
     delete_navbar_cache_for_users([user for user in UserProfile.objects.all() if user.is_reviewer or user.is_grade_publisher])
     return redirect('staff:index')


Test patch:
diff --git a/evap/staff/tests/test_importers.py b/evap/staff/tests/test_importers.py
--- a/evap/staff/tests/test_importers.py
+++ b/evap/staff/tests/test_importers.py
@@ -48,7 +48,7 @@ def test_created_users(self):
         user_list, success_messages, warnings, errors = UserImporter.process(self.valid_excel_content, test_run=False)
 
         self.assertIn("Successfully read sheet 'Users'.", success_messages)
-        self.assertIn('Successfully created 2 users:<br>Lucilia Manilium (lucilia.manilium)<br>Bastius Quid (bastius.quid.ext)', success_messages)
+        self.assertIn('Successfully created 2 users:<br />Lucilia Manilium (lucilia.manilium)<br />Bastius Quid (bastius.quid.ext)', success_messages)
         self.assertIn('Successfully read Excel file.', success_messages)
         self.assertEqual(warnings, {})
         self.assertEqual(errors, [])
@@ -66,8 +66,8 @@ def test_duplicate_warning(self):
         __, __, warnings_no_test, __ = UserImporter.process(self.valid_excel_content, test_run=False)
 
         self.assertEqual(warnings_test, warnings_no_test)
-        self.assertIn("An existing user has the same first and last name as a new user:<br>"
-                " - lucilia.manilium2 ( Lucilia Manilium, ) (existing)<br>"
+        self.assertIn("An existing user has the same first and last name as a new user:<br />"
+                " - lucilia.manilium2 ( Lucilia Manilium, ) (existing)<br />"
                 " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)",
                 warnings_test[ExcelImporter.W_DUPL])
 
@@ -75,14 +75,14 @@ def test_email_mismatch_warning(self):
         mommy.make(UserProfile, email="42@42.de", username="lucilia.manilium")
 
         __, __, warnings_test, __ = UserImporter.process(self.valid_excel_content, test_run=True)
-        self.assertIn("The existing user would be overwritten with the following data:<br>"
-                      " - lucilia.manilium ( None None, 42@42.de) (existing)<br>"
+        self.assertIn("The existing user would be overwritten with the following data:<br />"
+                      " - lucilia.manilium ( None None, 42@42.de) (existing)<br />"
                       " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)",
                       warnings_test[ExcelImporter.W_EMAIL])
 
         __, __, warnings_no_test, __ = UserImporter.process(self.valid_excel_content, test_run=False)
-        self.assertIn("The existing user was overwritten with the following data:<br>"
-                " - lucilia.manilium ( None None, 42@42.de) (existing)<br>"
+        self.assertIn("The existing user was overwritten with the following data:<br />"
+                " - lucilia.manilium ( None None, 42@42.de) (existing)<br />"
                 " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)",
                 warnings_no_test[ExcelImporter.W_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
@@ -263,7 +263,7 @@ def test_success_handling(self):
         form["excel_file"] = (self.filename_valid,)
         page = form.submit(name="operation", value="test")
 
-        self.assertContains(page, 'The import run will create 2 users:<br>Lucilia Manilium (lucilia.manilium)<br>Bastius Quid (bastius.quid.ext)')
+        self.assertContains(page, 'The import run will create 2 users:<br />Lucilia Manilium (lucilia.manilium)<br />Bastius Quid (bastius.quid.ext)')
         self.assertContains(page, 'Import previously uploaded file')
 
         form = page.forms["user-import-form"]
@@ -303,8 +303,8 @@ def test_warning_handling(self):
         form["excel_file"] = (self.filename_valid,)
 
         reply = form.submit(name="operation", value="test")
-        self.assertContains(reply, "The existing user would be overwritten with the following data:<br>"
-                " - lucilia.manilium ( None None, 42@42.de) (existing)<br>"
+        self.assertContains(reply, "The existing user would be overwritten with the following data:<br />"
+                " - lucilia.manilium ( None None, 42@42.de) (existing)<br />"
                 " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)")
 
         helper_delete_all_import_files(self.user.id)
@@ -447,13 +447,38 @@ def test_failure(self):
         self.assertEqual(response.status_code, 400)
         self.assertTrue(Semester.objects.filter(pk=semester.pk).exists())
 
-    def test_success(self):
+    def test_success_if_no_courses(self):
         semester = mommy.make(Semester)
         self.assertTrue(semester.can_be_deleted_by_manager)
         response = self.app.post(self.url, params={'semester_id': semester.pk}, user='manager')
         self.assertEqual(response.status_code, 302)
         self.assertFalse(Semester.objects.filter(pk=semester.pk).exists())
 
+    def test_success_if_archived(self):
+        semester = mommy.make(Semester)
+        course = mommy.make(Course, semester=semester)
+        evaluation = mommy.make(Evaluation, course=course, state='published')
+        general_contribution = evaluation.general_contribution
+        responsible_contribution = mommy.make(Contribution, evaluation=evaluation, contributor=mommy.make(UserProfile))
+        textanswer = mommy.make(TextAnswer, contribution=general_contribution, state='PU')
+        ratinganswercounter = mommy.make(RatingAnswerCounter, contribution=responsible_contribution)
+
+        self.assertFalse(semester.can_be_deleted_by_manager)
+
+        semester.archive_participations()
+        semester.delete_grade_documents()
+        semester.archive_results()
+
+        self.assertTrue(semester.can_be_deleted_by_manager)
+        response = self.app.post(self.url, params={'semester_id': semester.pk}, user='manager')
+        self.assertEqual(response.status_code, 302)
+        self.assertFalse(Semester.objects.filter(pk=semester.pk).exists())
+        self.assertFalse(Course.objects.filter(pk=course.pk).exists())
+        self.assertFalse(Evaluation.objects.filter(pk=evaluation.pk).exists())
+        self.assertFalse(Contribution.objects.filter(pk=general_contribution.pk).exists())
+        self.assertFalse(Contribution.objects.filter(pk=responsible_contribution.pk).exists())
+        self.assertFalse(TextAnswer.objects.filter(pk=textanswer.pk).exists())
+        self.assertFalse(RatingAnswerCounter.objects.filter(pk=ratinganswercounter.pk).exists())
 
 class TestSemesterAssignView(WebTest):
     url = '/staff/semester/1/assign'
@@ -621,8 +646,8 @@ def test_warning_handling(self):
         form["excel_file"] = (self.filename_valid,)
 
         reply = form.submit(name="operation", value="test")
-        self.assertContains(reply, "The existing user would be overwritten with the following data:<br>"
-                " - lucilia.manilium ( None None, 42@42.de) (existing)<br>"
+        self.assertContains(reply, "The existing user would be overwritten with the following data:<br />"
+                " - lucilia.manilium ( None None, 42@42.de) (existing)<br />"
                 " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)")
 
     def test_suspicious_operation(self):
@@ -1415,8 +1440,8 @@ def test_import_participants_warning_handling(self):
         form["excel_file"] = (self.filename_valid,)
 
         reply = form.submit(name="operation", value="test-participants")
-        self.assertContains(reply, "The existing user would be overwritten with the following data:<br>"
-                " - lucilia.manilium ( None None, 42@42.de) (existing)<br>"
+        self.assertContains(reply, "The existing user would be overwritten with the following data:<br />"
+                " - lucilia.manilium ( None None, 42@42.de) (existing)<br />"
                 " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)")
 
     def test_import_contributors_error_handling(self):
@@ -1446,8 +1471,8 @@ def test_import_contributors_warning_handling(self):
         form["excel_file"] = (self.filename_valid,)
 
         reply = form.submit(name="operation", value="test-contributors")
-        self.assertContains(reply, "The existing user would be overwritten with the following data:<br>"
-                " - lucilia.manilium ( None None, 42@42.de) (existing)<br>"
+        self.assertContains(reply, "The existing user would be overwritten with the following data:<br />"
+                " - lucilia.manilium ( None None, 42@42.de) (existing)<br />"
                 " - lucilia.manilium ( Lucilia Manilium, lucilia.manilium@institution.example.com) (new)")
 
     def test_suspicious_operation(self):