Repository: e-valuation/EvaP

Issue:
Multiple empty general contributions
When saving a course form it makes sure that a general contribution exists. However, this can result in adding multiple empty contributions without questionnaires. When looking at a course in the Django Admin one can see these contributions.
This results in unwanted side effects (see https://github.com/janno42/EvaP/commit/8d5f9a8e3ac436757ca046881e7a74b55eafeb98#commitcomment-11211525).

These contributions should be removed or at least it has to be made sure that there will be at most one and that it doesn't interfere with other logic.



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
@@ -196,7 +196,7 @@ class Course(models.Model, metaclass=LocalizeModelBase):
     course_evaluated = django.dispatch.Signal(providing_args=['request', 'semester'])
 
     class Meta:
-        ordering = ('semester', 'name_de')
+        ordering = ('name_de',)
         unique_together = (
             ('semester', 'name_de'),
             ('semester', 'name_en'),
@@ -225,7 +225,7 @@ def is_in_evaluation_period(self):
         return today >= self.vote_start_date and today <= self.vote_end_date
 
     def has_enough_questionnaires(self):
-        return self.general_contribution and all(self.contributions.aggregate(Count('questionnaires')).values())
+        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):
         """Returns whether the user is allowed to vote on this course."""
@@ -388,7 +388,7 @@ def is_user_editor(self, user):
 
     def warnings(self):
         result = []
-        if self.state in ['new', 'prepared', 'editorApproved'] and not self.has_enough_questionnaires() and not self.is_single_result():
+        if self.state in ['new', 'prepared', 'editorApproved'] and not self.has_enough_questionnaires():
             result.append(_("Not enough questionnaires assigned"))
         if self.state in ['inEvaluation', 'evaluated', 'reviewed', 'published'] and not self.can_publish_grades:
             result.append(_("Not enough participants to publish results"))


Test patch:
diff --git a/evap/staff/tests.py b/evap/staff/tests.py
--- a/evap/staff/tests.py
+++ b/evap/staff/tests.py
@@ -277,6 +277,28 @@ def test_deleting_last_modified_user_does_not_delete_course(self):
         user.delete()
         self.assertTrue(Course.objects.filter(pk=course.pk).exists())
 
+    def test_has_enough_questionnaires(self):
+        # manually circumvent Course's save() method to have a Course without a general contribution
+        courses = Course.objects.bulk_create([mommy.prepare(Course)])
+        course = Course.objects.get()
+        self.assertEqual(course.contributions.count(), 0)
+        self.assertFalse(course.has_enough_questionnaires())
+
+        responsible_contribution = mommy.make(Contribution, course=course, contributor=mommy.make(UserProfile), responsible=True)
+        course = Course.objects.get()
+        self.assertFalse(course.has_enough_questionnaires())
+
+        general_contribution = mommy.make(Contribution, course=course, contributor=None)
+        course = Course.objects.get() # refresh because of cached properties
+        self.assertFalse(course.has_enough_questionnaires())
+
+        q = mommy.make(Questionnaire)
+        general_contribution.questionnaires.add(q)
+        self.assertFalse(course.has_enough_questionnaires())
+
+        responsible_contribution.questionnaires.add(q)
+        self.assertTrue(course.has_enough_questionnaires())
+
 
 @override_settings(INSTITUTION_EMAIL_DOMAINS=["example.com"])
 class URLTests(WebTest):