Repository: e-valuation/EvaP

Issue:
Collapsibles in questionnaires
On the evaluation page of a course, all questionnaires related to contributors should be placed in collapsibles.

Should solve #233 and #84.



Discussion:

Resolving patch:
diff --git a/evap/evaluation/tools.py b/evap/evaluation/tools.py
--- a/evap/evaluation/tools.py
+++ b/evap/evaluation/tools.py
@@ -275,6 +275,19 @@ def calculate_average_and_medium_grades(course):
     return final_avg, final_med
 
 
+def questionnaires_and_contributions_by_contributor(course):
+    """Yields a dictionary of contributors with a list of tuples (questionnaire, contribution) for the given course."""
+    result = {}
+
+    for contribution in course.contributions.annotate(Min("questionnaires__index")).order_by("questionnaires__is_for_contributors", "questionnaires__index__min"):
+        if not contribution.contributor in result:
+            result[contribution.contributor] = []
+        for questionnaire in contribution.questionnaires.all():
+            result[contribution.contributor].append((questionnaire, contribution))
+
+    return result
+
+
 def questionnaires_and_contributions(course):
     """Yields tuples of (questionnaire, contribution) for the given course."""
     result = []
diff --git a/evap/student/forms.py b/evap/student/forms.py
--- a/evap/student/forms.py
+++ b/evap/student/forms.py
@@ -56,14 +56,7 @@ def __init__(self, *args, **kwargs):
             self.fields[identifier] = field
 
     def caption(self):
-        if self.contribution.contributor:
-            try:
-                full_name = UserProfile.objects.get(user=self.contribution.contributor).full_name
-            except UserProfile.DoesNotExist:
-                full_name = self.contribution.contributor.get_full_name() or self.contribution.contributor.username
-            return u"%s: %s" % (full_name, self.questionnaire.public_name)
-        else:
-            return self.questionnaire.public_name
+        return self.questionnaire.public_name
 
     def teaser(self):
         return self.questionnaire.teaser
diff --git a/evap/student/views.py b/evap/student/views.py
--- a/evap/student/views.py
+++ b/evap/student/views.py
@@ -7,8 +7,8 @@
 from django.utils.translation import ugettext as _
 
 from evap.evaluation.auth import login_required
-from evap.evaluation.models import Course, Semester
-from evap.evaluation.tools import questionnaires_and_contributions
+from evap.evaluation.models import Course, Semester, UserProfile
+from evap.evaluation.tools import questionnaires_and_contributions_by_contributor
 from evap.student.forms import QuestionsForm
 from evap.student.tools import make_form_identifier
 
@@ -42,17 +42,55 @@ def vote(request, course_id):
         raise PermissionDenied
 
     # build forms
-    forms = SortedDict()
-    for questionnaire, contribution in questionnaires_and_contributions(course):
-        form = QuestionsForm(request.POST or None, contribution=contribution, questionnaire=questionnaire)
-        if form.contribution.contributor == request.user:
-            continue # users shall not vote about themselves
-        forms[(contribution, questionnaire)] = form
-
-    if all(form.is_valid() for form in forms.values()):
-        # begin vote operation
-        with transaction.commit_on_success():
-            for (contribution, questionnaire), form in forms.items():
+    form_group = {}
+    contributors_with_questionnaires = questionnaires_and_contributions_by_contributor(course)
+    for contributor in contributors_with_questionnaires:
+        for questionnaire, contribution in contributors_with_questionnaires[contributor]:
+            form = QuestionsForm(request.POST or None, contribution=contribution, questionnaire=questionnaire)
+            if form.contribution.contributor == request.user:
+                continue # users shall not vote about themselves
+            if not contributor in form_group:
+                form_group[contributor] = SortedDict()
+            form_group[contributor][(contribution, questionnaire)] = form
+
+    for contributor in form_group:
+        for (contribution, questionnaire), form in form_group[contributor].items():
+            for question in questionnaire.question_set.all():
+                identifier = make_form_identifier(contribution, questionnaire, question)
+
+    for contributor in form_group:
+        for form in form_group[contributor].values():
+            if form.is_valid():
+                continue
+            course_forms = []
+            contributor_questionnaires = {}
+            errors = []
+
+            for form in form_group[None].values():
+                course_forms.append(form)
+
+            for contributor in form_group:
+                if contributor == None:
+                    continue
+                user_profile = UserProfile.get_for_user(contributor)
+                contributor_questionnaires[user_profile] = form_group[contributor].values()
+                for form in form_group[contributor].values():
+                    if form.errors:
+                        errors.append(contributor.id)
+
+            return render_to_response(
+                "student_vote.html",
+                dict(course_forms=course_forms,
+                     contributor_questionnaires=contributor_questionnaires,
+                     errors=errors,
+                     course=course),
+                context_instance=RequestContext(request))
+
+    # all forms are valid
+    # begin vote operation
+    with transaction.commit_on_success():
+        for contributor in form_group:
+            for (contribution, questionnaire), form in form_group[contributor].items():
                 for question in questionnaire.question_set.all():
                     identifier = make_form_identifier(contribution, questionnaire, question)
                     value = form.cleaned_data.get(identifier)
@@ -70,14 +108,8 @@ def vote(request, course_id):
                             question=question,
                             answer=value)
 
-            # remember that the user voted already
-            course.voters.add(request.user)
-
-        messages.add_message(request, messages.INFO, _("Your vote was recorded."))
-        return redirect('evap.student.views.index')
-    else:
-        return render_to_response(
-            "student_vote.html",
-            dict(forms=forms.values(),
-                 course=course),
-            context_instance=RequestContext(request))
+        # remember that the user voted already
+        course.voters.add(request.user)
+
+    messages.add_message(request, messages.INFO, _("Your vote was recorded."))
+    return redirect('evap.student.views.index')
\ No newline at end of file


Test patch:
diff --git a/evap/student/tests.py b/evap/student/tests.py
--- a/evap/student/tests.py
+++ b/evap/student/tests.py
@@ -14,12 +14,12 @@ def get_vote_page():
             return self.client.get(reverse('evap.student.views.vote', kwargs={'course_id': course.id}))
         response = get_vote_page() 
         tutorUser = User.objects.get(username='tutor')
-        for form in response.context['forms']:
-            self.assertNotEquals(form.contribution.contributor, tutorUser,
+        for contributor in response.context['contributor_questionnaires']:
+            self.assertNotEquals(contributor.user, tutorUser,
                     "Contributor should not see the questionnaire about themselves")
         self.client.logout()
         if not self.client.login(username='student', password='student'):
             self.fail('Fixture error: student user could not log in')
         response = get_vote_page()
-        self.assertTrue(any(form.contribution.contributor == tutorUser for form in response.context['forms']),
+        self.assertTrue(any(contributor.user == tutorUser for contributor in response.context['contributor_questionnaires']),
                 "Regular students should see the questionnaire about a contributor")