Repository: e-valuation/EvaP

Issue:
Don't show a responsible's comments to other responsibles
If a course has multiple responsibles, each of them should not be able to see the comments about the other responsibles of this course.
In general a user who can see `ALL` comments cannot see the comments about other responsible users. Comments about responsible users can only be seen by themselves and their delegates (and reviewers).


Discussion:

Resolving patch:
diff --git a/evap/results/tools.py b/evap/results/tools.py
--- a/evap/results/tools.py
+++ b/evap/results/tools.py
@@ -19,6 +19,8 @@
     5: (235,  89, 90),
 }
 
+COMMENT_STATES_REQUIRED_FOR_VISIBILITY = [TextAnswer.PRIVATE, TextAnswer.PUBLISHED]
+
 
 # see calculate_results
 ResultSection = namedtuple('ResultSection', ('questionnaire', 'contributor', 'label', 'results', 'warning'))
@@ -151,8 +153,7 @@ def _calculate_results_impl(course):
                     results.append(RatingResult(question, total_count, average, deviation, counts, warning))
 
             elif question.is_text_question:
-                allowed_states = [TextAnswer.PRIVATE, TextAnswer.PUBLISHED]
-                answers = get_textanswers(contribution, question, allowed_states)
+                answers = get_textanswers(contribution, question, COMMENT_STATES_REQUIRED_FOR_VISIBILITY)
                 results.append(TextResult(question=question, answers=answers))
 
         section_warning = questionnaire_max_answers[(questionnaire, contribution)] < questionnaire_warning_thresholds[questionnaire]
diff --git a/evap/results/views.py b/evap/results/views.py
--- a/evap/results/views.py
+++ b/evap/results/views.py
@@ -5,7 +5,7 @@
 from django.contrib.auth.decorators import login_required
 
 from evap.evaluation.models import Semester, Degree, Contribution
-from evap.results.tools import calculate_results, calculate_average_grades_and_deviation, TextResult, RatingResult
+from evap.results.tools import calculate_results, calculate_average_grades_and_deviation, TextResult, RatingResult, COMMENT_STATES_REQUIRED_FOR_VISIBILITY
 
 
 @login_required
@@ -125,12 +125,20 @@ def course_detail(request, semester_id, course_id):
 def user_can_see_text_answer(user, represented_users, text_answer, public_view=False):
     if public_view:
         return False
+    if text_answer.state not in COMMENT_STATES_REQUIRED_FOR_VISIBILITY:
+        return False
     if user.is_reviewer:
         return True
+
     contributor = text_answer.contribution.contributor
+
     if text_answer.is_private:
         return contributor == user
+
     if text_answer.is_published:
+        if text_answer.contribution.responsible:
+            return contributor == user or user in contributor.delegates.all()
+
         if contributor in represented_users:
             return True
         if text_answer.contribution.course.contributions.filter(


Test patch:
diff --git a/evap/results/fixtures/minimal_test_data_results.json b/evap/results/fixtures/minimal_test_data_results.json
--- a/evap/results/fixtures/minimal_test_data_results.json
+++ b/evap/results/fixtures/minimal_test_data_results.json
@@ -214,6 +214,61 @@
     "state": "PR"
   }
 },
+{
+  "pk": 11,
+  "model": "evaluation.textanswer",
+  "fields": {
+    "reviewed_answer": null,
+    "question": 1,
+    "original_answer": ".other_responsible_orig_published.",
+    "contribution": 6,
+    "state": "PU"
+  }
+},
+{
+  "pk": 12,
+  "model": "evaluation.textanswer",
+  "fields": {
+    "reviewed_answer": null,
+    "question": 1,
+    "original_answer": ".other_responsible_orig_hidden.",
+    "contribution": 6,
+    "state": "HI"
+  }
+},
+{
+  "pk": 13,
+  "model": "evaluation.textanswer",
+  "fields": {
+    "reviewed_answer": ".other_responsible_changed_published.",
+    "question": 1,
+    "original_answer": ".other_responsible_orig_published_changed.",
+    "contribution": 6,
+    "state": "PU"
+  }
+},
+{
+  "pk": 14,
+  "model": "evaluation.textanswer",
+  "fields": {
+    "reviewed_answer": null,
+    "question": 1,
+    "original_answer": ".other_responsible_orig_private.",
+    "contribution": 6,
+    "state": "PR"
+  }
+},
+{
+  "pk": 15,
+  "model": "evaluation.textanswer",
+  "fields": {
+    "reviewed_answer": null,
+    "question": 1,
+    "original_answer": ".other_responsible_orig_notreviewed.",
+    "contribution": 6,
+    "state": "NR"
+  }
+},
 {
   "pk": 2,
   "model": "evaluation.userprofile",
@@ -333,6 +388,23 @@
     "login_key": null
   }
 },
+{
+  "pk": 9,
+  "model": "evaluation.userprofile",
+  "fields": {
+    "username": "other_responsible",
+    "first_name": "other_responsible",
+    "last_name": "user",
+    "last_login": "2014-09-16T22:51:39.584",
+    "password": "pbkdf2_sha256$12000$oyxddVrECuLk$ayPgZNkTANvBcWbTvPGaR4NBlqPELpElG7C4M1UN210=",
+    "email": "other_responsible@example.com",
+    "title": "",
+    "cc_users": [],
+    "delegates": [],
+    "login_key_valid_until": null,
+    "login_key": null
+  }
+},
 {
   "pk": 1,
   "model": "evaluation.contribution",
@@ -403,6 +475,20 @@
     "comment_visibility": "ALL"
   }
 },
+{
+  "pk": 6,
+  "model": "evaluation.contribution",
+  "fields": {
+    "contributor": 9,
+    "course": 1,
+    "responsible": true,
+    "questionnaires": [
+      1
+    ],
+    "can_edit": true,
+    "comment_visibility": "ALL"
+  }
+},
 {
   "pk": 1,
   "model": "evaluation.coursetype",
diff --git a/evap/results/tests/test_views.py b/evap/results/tests/test_views.py
--- a/evap/results/tests/test_views.py
+++ b/evap/results/tests/test_views.py
@@ -66,12 +66,14 @@ def test_private_course(self):
         student = UserProfile.objects.get(username="student")
         contributor = UserProfile.objects.get(username="contributor")
         responsible = UserProfile.objects.get(username="responsible")
+        other_responsible = UserProfile.objects.get(username="other_responsible")
         test1 = mommy.make(UserProfile, username="test1")
         test2 = mommy.make(UserProfile, username="test2")
         mommy.make(UserProfile, username="random")
         degree = mommy.make(Degree)
         private_course = mommy.make(Course, state='published', is_private=True, semester=self.semester, participants=[student, test1, test2], voters=[test1, test2], degrees=[degree])
         mommy.make(Contribution, course=private_course, contributor=responsible, can_edit=True, responsible=True, comment_visibility=Contribution.ALL_COMMENTS)
+        mommy.make(Contribution, course=private_course, contributor=other_responsible, can_edit=True, responsible=True, comment_visibility=Contribution.ALL_COMMENTS)
         mommy.make(Contribution, course=private_course, contributor=contributor, can_edit=True)
 
         url = '/results/semester/%s' % (self.semester.id)
@@ -81,6 +83,8 @@ def test_private_course(self):
         self.assertIn(private_course.name, page)
         page = self.app.get(url, user='responsible')
         self.assertIn(private_course.name, page)
+        page = self.app.get(url, user='other_responsible')
+        self.assertIn(private_course.name, page)
         page = self.app.get(url, user='contributor')
         self.assertIn(private_course.name, page)
         page = self.app.get(url, user='evap')
@@ -90,6 +94,7 @@ def test_private_course(self):
         self.get_assert_403(url, "random")
         self.get_assert_200(url, "student")
         self.get_assert_200(url, "responsible")
+        self.get_assert_200(url, "other_responsible")
         self.get_assert_200(url, "contributor")
         self.get_assert_200(url, "evap")
 
@@ -107,6 +112,33 @@ def test_textanswer_visibility_for_responsible(self):
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertIn(".contributor_orig_published.", page)
         self.assertNotIn(".contributor_orig_private.", page)  # private comment not visible
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
+
+    def test_textanswer_visibility_for_other_responsible(self):
+        page = self.app.get("/results/semester/1/course/1", user='other_responsible')
+        self.assertIn(".course_orig_published.", page)
+        self.assertNotIn(".course_orig_hidden.", page)
+        self.assertNotIn(".course_orig_published_changed.", page)
+        self.assertIn(".course_changed_published.", page)
+        self.assertNotIn(".responsible_orig_published.", page)
+        self.assertNotIn(".responsible_orig_hidden.", page)
+        self.assertNotIn(".responsible_orig_published_changed.", page)
+        self.assertNotIn(".responsible_changed_published.", page)
+        self.assertNotIn(".responsible_orig_private.", page)
+        self.assertNotIn(".responsible_orig_notreviewed.", page)
+        self.assertIn(".contributor_orig_published.", page)
+        self.assertNotIn(".contributor_orig_private.", page)  # private comment not visible
+        self.assertIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertIn(".other_responsible_changed_published.", page)
+        self.assertIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
 
     def test_textanswer_visibility_for_delegate_for_responsible(self):
         page = self.app.get("/results/semester/1/course/1", user='delegate_for_responsible')
@@ -122,6 +154,12 @@ def test_textanswer_visibility_for_delegate_for_responsible(self):
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertIn(".contributor_orig_published.", page)
         self.assertNotIn(".contributor_orig_private.", page)  # private comment not visible
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
 
     def test_textanswer_visibility_for_contributor(self):
         page = self.app.get("/results/semester/1/course/1", user='contributor')
@@ -137,6 +175,12 @@ def test_textanswer_visibility_for_contributor(self):
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertIn(".contributor_orig_published.", page)
         self.assertIn(".contributor_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
 
     def test_textanswer_visibility_for_delegate_for_contributor(self):
         page = self.app.get("/results/semester/1/course/1", user='delegate_for_contributor')
@@ -152,6 +196,12 @@ def test_textanswer_visibility_for_delegate_for_contributor(self):
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertIn(".contributor_orig_published.", page)
         self.assertNotIn(".contributor_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
 
     def test_textanswer_visibility_for_contributor_course_comments(self):
         page = self.app.get("/results/semester/1/course/1", user='contributor_course_comments')
@@ -167,6 +217,12 @@ def test_textanswer_visibility_for_contributor_course_comments(self):
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertNotIn(".contributor_orig_published.", page)
         self.assertNotIn(".contributor_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
 
     def test_textanswer_visibility_for_contributor_all_comments(self):
         page = self.app.get("/results/semester/1/course/1", user='contributor_all_comments')
@@ -174,14 +230,20 @@ def test_textanswer_visibility_for_contributor_all_comments(self):
         self.assertNotIn(".course_orig_hidden.", page)
         self.assertNotIn(".course_orig_published_changed.", page)
         self.assertIn(".course_changed_published.", page)
-        self.assertIn(".responsible_orig_published.", page)
+        self.assertNotIn(".responsible_orig_published.", page)
         self.assertNotIn(".responsible_orig_hidden.", page)
         self.assertNotIn(".responsible_orig_published_changed.", page)
-        self.assertIn(".responsible_changed_published.", page)
+        self.assertNotIn(".responsible_changed_published.", page)
         self.assertNotIn(".responsible_orig_private.", page)
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertIn(".contributor_orig_published.", page)
         self.assertNotIn(".contributor_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)
 
     def test_textanswer_visibility_for_student(self):
         page = self.app.get("/results/semester/1/course/1", user='student')
@@ -197,3 +259,9 @@ def test_textanswer_visibility_for_student(self):
         self.assertNotIn(".responsible_orig_notreviewed.", page)
         self.assertNotIn(".contributor_orig_published.", page)
         self.assertNotIn(".contributor_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_published.", page)
+        self.assertNotIn(".other_responsible_orig_hidden.", page)
+        self.assertNotIn(".other_responsible_orig_published_changed.", page)
+        self.assertNotIn(".other_responsible_changed_published.", page)
+        self.assertNotIn(".other_responsible_orig_private.", page)
+        self.assertNotIn(".other_responsible_orig_notreviewed.", page)