Repository: e-valuation/EvaP

Issue:
Questionnaire assignment warning for contributors without questionnaires
The label "Not enough questionnaires assigned" is always shown next to courses on the staff page where one of the contributors has the checkbox "Does not contribute to course" selected.
This should count as an assigned questionnaire in respect of this warning message, which should not be displayed if the course has sufficient questionnaires assigned otherwise.

The property `has_enough_questionnaires` must be adapted to reflect this.
Questionnaire assignment warning for contributors without questionnaires
The label "Not enough questionnaires assigned" is always shown next to courses on the staff page where one of the contributors has the checkbox "Does not contribute to course" selected.
This should count as an assigned questionnaire in respect of this warning message, which should not be displayed if the course has sufficient questionnaires assigned otherwise.

The property `has_enough_questionnaires` must be adapted to reflect this.


Discussion:
well, the problem here is that "Does not contribute to course" is not saved anywhere. right now the code simply checks that checkbox if there are no questionnaires selected. Which is probably not what we want in case of newly imported courses...
This also prevents approving courses in case a contributor has no questionnaires.
as discussed, `has_enough_questionnaires` should simply not care about contributors.

however, when selecting multiple courses in the semester view and approving them, courses that have a contributor with no questionnaires should get a warning in the confirmation page.
well, the problem here is that "Does not contribute to course" is not saved anywhere. right now the code simply checks that checkbox if there are no questionnaires selected. Which is probably not what we want in case of newly imported courses...
This also prevents approving courses in case a contributor has no questionnaires.
as discussed, `has_enough_questionnaires` should simply not care about contributors.

however, when selecting multiple courses in the semester view and approving them, courses that have a contributor with no questionnaires should get a warning in the confirmation page.

Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -271,7 +271,11 @@ def is_in_evaluation_period(self):
         return today >= self.vote_start_date and today <= self.vote_end_date
 
     @property
-    def has_enough_questionnaires(self):
+    def general_contribution_has_questionnaires(self):
+        return self.general_contribution and (self.is_single_result or self.general_contribution.questionnaires.count() > 0)
+
+    @property
+    def all_contributions_have_questionnaires(self):
         return self.general_contribution and (self.is_single_result or all(self.contributions.annotate(Count('questionnaires')).values_list("questionnaires__count", flat=True)))
 
     def can_user_vote(self, user):
@@ -337,7 +341,7 @@ def ready_for_editors(self):
     def editor_approve(self):
         pass
 
-    @transition(field=state, source=['new', 'prepared', 'editor_approved'], target='approved', conditions=[lambda self: self.has_enough_questionnaires])
+    @transition(field=state, source=['new', 'prepared', 'editor_approved'], target='approved', conditions=[lambda self: self.general_contribution_has_questionnaires])
     def staff_approve(self):
         pass
 
@@ -453,8 +457,12 @@ def is_user_editor(self, user):
 
     def warnings(self):
         result = []
-        if self.state in ['new', 'prepared', 'editor_approved'] and not self.has_enough_questionnaires:
-            result.append(_("Not enough questionnaires assigned"))
+        if self.state in ['new', 'prepared', 'editor_approved'] and not self.all_contributions_have_questionnaires:
+            if not self.general_contribution_has_questionnaires:
+                result.append(_("General contribution has no questionnaires"))
+            else:
+                result.append(_("Not all contributions have questionnaires"))
+
         if self.state in ['in_evaluation', 'evaluated', 'reviewed', 'published'] and not self.can_publish_grades:
             result.append(_("Not enough participants to publish results"))
         return result
diff --git a/evap/staff/views.py b/evap/staff/views.py
--- a/evap/staff/views.py
+++ b/evap/staff/views.py
@@ -180,14 +180,23 @@ def semester_course_operation(request, semester_id):
 
         elif operation == 'approve':
             new_state_name = STATES_ORDERED['approved']
-            # remove courses without enough questionnaires
-            courses_with_enough_questionnaires = [course for course in courses if course.has_enough_questionnaires]
+            # remove courses without questionnaires on general contribution, warn about courses with missing questionnaires
+            courses_with_enough_questionnaires = [course for course in courses if course.general_contribution_has_questionnaires]
+            courses_with_missing_questionnaires = [course for course in courses_with_enough_questionnaires if not course.all_contributions_have_questionnaires]
+
             difference = len(courses) - len(courses_with_enough_questionnaires)
             if difference:
                 courses = courses_with_enough_questionnaires
-                messages.warning(request, ungettext("%(courses)d course can not be approved, because it has not enough questionnaires assigned. It was removed from the selection.",
-                    "%(courses)d courses can not be approved, because they have not enough questionnaires assigned. They were removed from the selection.",
-                    difference) % {'courses': difference})
+                messages.warning(request,
+                                 ungettext('%(courses)d course can not be approved, because it has not enough questionnaires assigned. It was removed from the selection.',
+                                           '%(courses)d courses can not be approved, because they have not enough questionnaires assigned. They were removed from the selection.',
+                                           difference) % {'courses': difference})
+
+            if courses_with_missing_questionnaires:
+                messages.warning(request,
+                                 ungettext('%(courses)d course does not have a questionnaire assigned for every contributor. It can be approved anyway.',
+                                           '%(courses)d courses do not have a questionnaire assigned for every contributor. They can be approved anyway.',
+                                           len(courses_with_missing_questionnaires)) % {'courses': len(courses_with_missing_questionnaires)})
 
         elif operation == 'startEvaluation':
             new_state_name = STATES_ORDERED['in_evaluation']


Test patch:
diff --git a/evap/evaluation/tests/test_models.py b/evap/evaluation/tests/test_models.py
--- a/evap/evaluation/tests/test_models.py
+++ b/evap/evaluation/tests/test_models.py
@@ -76,24 +76,29 @@ def test_has_enough_questionnaires(self):
         Course.objects.bulk_create([mommy.prepare(Course, semester=mommy.make(Semester), type=mommy.make(CourseType))])
         course = Course.objects.get()
         self.assertEqual(course.contributions.count(), 0)
-        self.assertFalse(course.has_enough_questionnaires)
+        self.assertFalse(course.general_contribution_has_questionnaires)
+        self.assertFalse(course.all_contributions_have_questionnaires)
 
         responsible_contribution = mommy.make(
                 Contribution, course=course, contributor=mommy.make(UserProfile),
                 responsible=True, can_edit=True, comment_visibility=Contribution.ALL_COMMENTS)
         course = Course.objects.get()
-        self.assertFalse(course.has_enough_questionnaires)
+        self.assertFalse(course.general_contribution_has_questionnaires)
+        self.assertFalse(course.all_contributions_have_questionnaires)
 
         general_contribution = mommy.make(Contribution, course=course, contributor=None)
         course = Course.objects.get()
-        self.assertFalse(course.has_enough_questionnaires)
+        self.assertFalse(course.general_contribution_has_questionnaires)
+        self.assertFalse(course.all_contributions_have_questionnaires)
 
         questionnaire = mommy.make(Questionnaire)
         general_contribution.questionnaires.add(questionnaire)
-        self.assertFalse(course.has_enough_questionnaires)
+        self.assertTrue(course.general_contribution_has_questionnaires)
+        self.assertFalse(course.all_contributions_have_questionnaires)
 
         responsible_contribution.questionnaires.add(questionnaire)
-        self.assertTrue(course.has_enough_questionnaires)
+        self.assertTrue(course.general_contribution_has_questionnaires)
+        self.assertTrue(course.all_contributions_have_questionnaires)
 
     def test_deleting_last_modified_user_does_not_delete_course(self):
         user = mommy.make(UserProfile)