Repository: e-valuation/EvaP

Issue:
Page forwarding
When a publishing notice about a course is sent to a participating student who also is a contributor in another course, the link for this student is always redirecting to `.../contributor`. When the student is a staff member, EvaP always opens at `.../staff`.

Mostly students will be using the `.../student` page, so they should always be forwarded to this page, even if they contribute to any courses. Staff users should still be forwarded to `.../staff`, even if they are students.



Discussion:
To distinguish students and contributors a new method `is_student` should be written. When it returns  `True`, the user is redirected to the `.../student` page, otherwise to the `.../contributor` page (the exception for staff users from above still counts). It uses the following algorithm to find out about a user's state:
- Check in which semester PS the user last was a participant in any course and in which semester CS the user last was a contributor in any course.
- If the PS is a later or the same semester as CS, the user is a student, return `True`.
- Else the user is a contributor, return `False`.


Resolving patch:
diff --git a/evap/evaluation/models.py b/evap/evaluation/models.py
--- a/evap/evaluation/models.py
+++ b/evap/evaluation/models.py
@@ -857,6 +857,23 @@ def can_staff_delete(self):
     def is_participant(self):
         return self.courses_participating_in.exists()
 
+    @property
+    def is_student(self):
+        """
+            A UserProfile is not considered to be a student anymore if the
+            newest contribution is newer than the newest participation.
+        """
+        if not self.is_participant:
+            return False
+
+        if not self.is_contributor:
+            return True
+
+        last_semester_participated = Semester.objects.filter(course__participants=self).order_by("-created_at").first()
+        last_semester_contributed = Semester.objects.filter(course__contributions__contributor=self).order_by("-created_at").first()
+
+        return last_semester_participated.created_at >= last_semester_contributed.created_at
+
     @property
     def is_contributor(self):
         return self.contributions.exists()
diff --git a/evap/evaluation/views.py b/evap/evaluation/views.py
--- a/evap/evaluation/views.py
+++ b/evap/evaluation/views.py
@@ -81,6 +81,8 @@ def index(request):
             return redirect('staff:index')
         elif request.user.is_grade_publisher:
             return redirect('grades:semester_view', Semester.active_semester().id)
+        elif user.is_student:
+            return redirect('student:index')
         elif user.is_contributor_or_delegate:
             return redirect('contributor:index')
         elif user.is_participant:


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
@@ -1,16 +1,15 @@
-import datetime
+from datetime import date, timedelta
 from unittest.mock import patch
 from django.test import TestCase
 from model_mommy import mommy
 
-from evap.evaluation.models import Course
+from evap.evaluation.models import Course, UserProfile, Contribution, Semester
 
 
 class TestCourses(TestCase):
-    today = datetime.date.today()
 
     def test_approved_to_inEvaluation(self):
-        course = mommy.make(Course, state='approved',  vote_start_date=self.today)
+        course = mommy.make(Course, state='approved',  vote_start_date=date.today())
 
         with patch('evap.evaluation.models.EmailTemplate.send_evaluation_started_notifications') as mock:
             Course.update_courses()
@@ -21,7 +20,7 @@ def test_approved_to_inEvaluation(self):
         self.assertEqual(course.state, 'inEvaluation')
 
     def test_inEvaluation_to_evaluated(self):
-        course = mommy.make(Course, state='inEvaluation', vote_end_date=self.today - datetime.timedelta(days=1))
+        course = mommy.make(Course, state='inEvaluation', vote_end_date=date.today() - timedelta(days=1))
 
         with patch('evap.evaluation.models.Course.is_fully_reviewed') as mock:
             mock.return_value = False
@@ -32,7 +31,7 @@ def test_inEvaluation_to_evaluated(self):
 
     def test_inEvaluation_to_reviewed(self):
         # Course is "fully reviewed" as no open text_answers are present by default,
-        course = mommy.make(Course, state='inEvaluation', vote_end_date=self.today - datetime.timedelta(days=1))
+        course = mommy.make(Course, state='inEvaluation', vote_end_date=date.today() - timedelta(days=1))
 
         Course.update_courses()
 
@@ -41,7 +40,7 @@ def test_inEvaluation_to_reviewed(self):
 
     def test_inEvaluation_to_published(self):
         # Course is "fully reviewed" and not graded, thus gets published immediately.
-        course = mommy.make(Course, state='inEvaluation', vote_end_date=self.today - datetime.timedelta(days=1),
+        course = mommy.make(Course, state='inEvaluation', vote_end_date=date.today() - timedelta(days=1),
                             is_graded=False)
 
         with patch('evap.evaluation.tools.send_publish_notifications') as mock:
@@ -52,3 +51,33 @@ def test_inEvaluation_to_published(self):
         course = Course.objects.get(pk=course.pk)
         self.assertEqual(course.state, 'published')
 
+class TestUserProfile(TestCase):
+
+    def test_is_student(self):
+        some_user = mommy.make(UserProfile)
+        self.assertFalse(some_user.is_student)
+
+        student = mommy.make(UserProfile, courses_participating_in=[mommy.make(Course)])
+        self.assertTrue(student.is_student)
+
+        contributor = mommy.make(UserProfile, contributions=[mommy.make(Contribution)])
+        self.assertFalse(contributor.is_student)
+
+        semester_contributed_to = mommy.make(Semester, created_at=date.today())
+        semester_participated_in = mommy.make(Semester, created_at=date.today())
+        course_contributed_to = mommy.make(Course, semester=semester_contributed_to)
+        course_participated_in = mommy.make(Course, semester=semester_participated_in)
+        contribution = mommy.make(Contribution, course=course_contributed_to)
+        user = mommy.make(UserProfile, contributions=[contribution], courses_participating_in=[course_participated_in])
+
+        self.assertTrue(user.is_student)
+
+        semester_contributed_to.created_at = date.today() - timedelta(days=1)
+        semester_contributed_to.save()
+
+        self.assertTrue(user.is_student)
+
+        semester_participated_in.created_at = date.today() - timedelta(days=2)
+        semester_participated_in.save()
+
+        self.assertFalse(user.is_student)