Repository: carpentries/amy

Issue:
Trainee dashboard: some functionality inaccessible
Password change, soon all data download.


Discussion:

Resolving patch:
diff --git a/workshops/urls.py b/workshops/urls.py
--- a/workshops/urls.py
+++ b/workshops/urls.py
@@ -7,6 +7,7 @@
     url(r'^$', views.dispatch, name='dispatch'),
     url(r'^admin-dashboard/$', views.admin_dashboard, name='admin-dashboard'),
     url(r'^trainee-dashboard/$', views.trainee_dashboard, name='trainee-dashboard'),
+    url(r'^trainee-dashboard/training_progress/$', views.training_progress, name='training-progress'),
 
     url(r'^log/$', views.changes_log, name='changes_log'),
 
diff --git a/workshops/views.py b/workshops/views.py
--- a/workshops/views.py
+++ b/workshops/views.py
@@ -774,7 +774,7 @@ class PersonPermissions(OnlyForAdminsMixin, PermissionRequiredMixin,
     pk_url_kwarg = 'person_id'
 
 
-@admin_required
+@login_required
 def person_password(request, person_id):
     user = get_object_or_404(Person, pk=person_id)
 
@@ -2949,6 +2949,51 @@ def trainingrequest_details(request, pk):
 
 @login_required
 def trainee_dashboard(request):
+    # Workshops person taught at
+    workshops = request.user.task_set.all()
+
+    context = {
+        'title': 'Your profile',
+        'workshops': workshops,
+    }
+    return render(request, 'workshops/trainee_dashboard.html', context)
+
+
+@login_required
+def autoupdate_profile(request):
+    person = request.user
+    form = AutoUpdateProfileForm(instance=person)
+
+    if request.method == 'POST':
+        form = AutoUpdateProfileForm(request.POST, instance=person)
+
+        if form.is_valid() and form.instance == person:
+            # save lessons
+            person.lessons.clear()
+            for lesson in form.cleaned_data['lessons']:
+                q = Qualification(lesson=lesson, person=person)
+                q.save()
+
+            # don't save related lessons
+            del form.cleaned_data['lessons']
+
+            person = form.save()
+
+            messages.success(request, 'Your profile was updated.')
+
+            return redirect(reverse('trainee-dashboard'))
+        else:
+            messages.error(request, 'Fix errors below.')
+
+    context = {
+        'title': 'Update Your Profile',
+        'form': form,
+    }
+    return render(request, 'workshops/autoupdate_profile.html', context)
+
+
+@login_required
+def training_progress(request):
     swc_form = SendHomeworkForm(submit_name='swc-submit')
     dc_form = SendHomeworkForm(submit_name='dc-submit')
 
@@ -2972,9 +3017,6 @@ def trainee_dashboard(request):
     request.user.is_dc_instructor = request.user.award_set.filter(
         badge__name='dc-instructor').exists()
 
-    # Workshops person taught at
-    workshops = request.user.task_set.all()
-
     if request.method == 'POST' and 'swc-submit' in request.POST:
         requirement = TrainingRequirement.objects.get(name='SWC Homework')
         progress = TrainingProgress(trainee=request.user,
@@ -2988,7 +3030,7 @@ def trainee_dashboard(request):
             swc_form.save()
             messages.success(request, 'Your homework submission will be '
                                       'evaluated soon.')
-            return redirect(reverse('trainee-dashboard'))
+            return redirect(reverse('training-progress'))
 
     elif request.method == 'POST' and 'dc-submit' in request.POST:
         requirement = TrainingRequirement.objects.get(name='DC Homework')
@@ -3003,49 +3045,18 @@ def trainee_dashboard(request):
             dc_form.save()
             messages.success(request, 'Your homework submission will be '
                                       'evaluated soon.')
-            return redirect(reverse('trainee-dashboard'))
+            return redirect(reverse('training-progress'))
 
     else:  # GET request
         pass
 
     context = {
-        'title': 'Your profile',
+        'title': 'Your training progress',
         'swc_form': swc_form,
         'dc_form': dc_form,
-        'workshops': workshops,
     }
-    return render(request, 'workshops/trainee_dashboard.html', context)
-
-
-@login_required
-def autoupdate_profile(request):
-    person = request.user
-    form = AutoUpdateProfileForm(instance=person)
-
-    if request.method == 'POST':
-        form = AutoUpdateProfileForm(request.POST, instance=person)
+    return render(request, 'workshops/training_progress.html', context)
 
-        if form.is_valid() and form.instance == person:
-            # save lessons
-            person.lessons.clear()
-            for lesson in form.cleaned_data['lessons']:
-                q = Qualification(lesson=lesson, person=person)
-                q.save()
-
-            # don't save related lessons
-            del form.cleaned_data['lessons']
-
-            person = form.save()
-
-            return redirect(reverse('trainee-dashboard'))
-        else:
-            messages.error(request, 'Fix errors below.')
-
-    context = {
-        'title': 'Update Your Profile',
-        'form': form,
-    }
-    return render(request, 'workshops/generic_form_nonav.html', context)
 
 # ------------------------------------------------------------
 # Instructor Training related views


Test patch:
diff --git a/workshops/test/test_security.py b/workshops/test/test_security.py
--- a/workshops/test/test_security.py
+++ b/workshops/test/test_security.py
@@ -210,8 +210,8 @@ def test_class_based_view_accessible_to_unauthorized_users(self):
         'password_reset_done',
         'password_reset_confirm',
         'password_reset_complete',
-        'password_change',
-        'password_change_done',
+        # 'password_change',
+        # 'password_change_done',
 
         # python-social-auth
         'begin',
diff --git a/workshops/test/test_trainee_dashboard.py b/workshops/test/test_trainee_dashboard.py
--- a/workshops/test/test_trainee_dashboard.py
+++ b/workshops/test/test_trainee_dashboard.py
@@ -33,6 +33,7 @@ def setUp(self):
         self._setUpUsersAndLogin()
         self.swc_instructor = Badge.objects.get(name='swc-instructor')
         self.dc_instructor = Badge.objects.get(name='dc-instructor')
+        self.progress_url = reverse('training-progress')
 
     def test_swc_instructor_and_dc_instructor(self):
         """When the trainee is awarded both Carpentry Instructor badge,
@@ -42,7 +43,7 @@ def test_swc_instructor_and_dc_instructor(self):
                              awarded=datetime(2016, 6, 1, 15, 00))
         Award.objects.create(person=self.admin, badge=self.dc_instructor,
                              awarded=datetime(2016, 6, 1, 15, 00))
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Congratulations, you\'re certified both '
                                 'Software Carpentry and Data Carpentry '
                                 'Instructor!')
@@ -50,21 +51,21 @@ def test_swc_instructor_and_dc_instructor(self):
     def test_swc_instructor(self):
         Award.objects.create(person=self.admin, badge=self.swc_instructor,
                              awarded=datetime(2016, 6, 1, 15, 00))
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Congratulations, you\'re certified '
                                 'Software Carpentry Instructor!')
 
     def test_dc_instructor(self):
         Award.objects.create(person=self.admin, badge=self.dc_instructor,
                              awarded=datetime(2016, 6, 1, 15, 00))
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Congratulations, you\'re certified '
                                 'Data Carpentry Instructor!')
 
     def test_neither_swc_nor_dc_instructor(self):
         """Check that we don't display that the trainee is an instructor if
         they don't have appropriate badge."""
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertNotContains(rv, 'Congratulations, you\'re certified both '
                                    'Software Carpentry and Data Carpentry '
                                    'Instructor!')
@@ -89,7 +90,7 @@ def test_eligible_but_not_awarded(self):
         assert admin.get_missing_swc_instructor_requirements() == []
         assert admin.get_missing_dc_instructor_requirements() == []
 
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
 
         self.assertNotContains(rv, 'Congratulations, you\'re certified both '
                                    'Software Carpentry and Data Carpentry '
@@ -107,17 +108,18 @@ class TestInstructorTrainingStatus(TestBase):
     def setUp(self):
         self._setUpUsersAndLogin()
         self.training = TrainingRequirement.objects.get(name='Training')
+        self.progress_url = reverse('training-progress')
 
     def test_training_passed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.training)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Training passed')
 
     def test_training_passed_but_discarded(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.training, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Training not passed yet')
 
     def test_last_training_discarded_but_another_is_passed(self):
@@ -125,17 +127,17 @@ def test_last_training_discarded_but_another_is_passed(self):
             trainee=self.admin, requirement=self.training)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.training, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Training passed')
 
     def test_training_failed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.training, state='f')
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Training not passed yet')
 
     def test_training_not_finished(self):
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Training not passed yet')
 
 
@@ -146,27 +148,28 @@ class TestSWCHomeworkStatus(TestBase):
     def setUp(self):
         self._setUpUsersAndLogin()
         self.homework = TrainingRequirement.objects.get(name='SWC Homework')
+        self.progress_url = reverse('training-progress')
 
     def test_homework_not_submitted(self):
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Homework not submitted yet')
 
     def test_homework_waiting_to_be_evaluated(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework, state='n')
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Homework not evaluated yet')
 
     def test_homework_passed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Homework accepted')
 
     def test_homework_not_accepted_when_homework_passed_but_discarded(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Homework not submitted yet')
 
     def test_homework_is_accepted_when_last_homework_is_discarded_but_other_one_is_passed(self):
@@ -174,7 +177,7 @@ def test_homework_is_accepted_when_last_homework_is_discarded_but_other_one_is_p
             trainee=self.admin, requirement=self.homework)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Homework accepted')
 
     def test_submission_form(self):
@@ -182,9 +185,9 @@ def test_submission_form(self):
             'url': 'http://example.com',
             'swc-submit': '',
         }
-        rv = self.client.post(reverse('trainee-dashboard'), data, follow=True)
+        rv = self.client.post(self.progress_url, data, follow=True)
         self.assertEqual(rv.status_code, 200)
-        self.assertEqual(rv.resolver_match.view_name, 'trainee-dashboard')
+        self.assertEqual(rv.resolver_match.view_name, 'training-progress')
         self.assertContains(rv, 'Your homework submission will be evaluated '
                                 'soon.')
         got = list(TrainingProgress.objects.values_list(
@@ -205,27 +208,28 @@ class TestDCHomeworkStatus(TestBase):
     def setUp(self):
         self._setUpUsersAndLogin()
         self.homework = TrainingRequirement.objects.get(name='DC Homework')
+        self.progress_url = reverse('training-progress')
 
     def test_homework_not_submitted(self):
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Homework not submitted yet')
 
     def test_homework_waiting_to_be_evaluated(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework, state='n')
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Homework not evaluated yet')
 
     def test_homework_passed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Homework accepted')
 
     def test_homework_not_accepted_when_homework_passed_but_discarded(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Homework not submitted yet')
 
     def test_homework_is_accepted_when_last_homework_is_discarded_but_other_one_is_passed(self):
@@ -233,7 +237,7 @@ def test_homework_is_accepted_when_last_homework_is_discarded_but_other_one_is_p
             trainee=self.admin, requirement=self.homework)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.homework, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Homework accepted')
 
     def test_submission_form(self):
@@ -241,9 +245,9 @@ def test_submission_form(self):
             'url': 'http://example.com',
             'dc-submit': '',
         }
-        rv = self.client.post(reverse('trainee-dashboard'), data, follow=True)
+        rv = self.client.post(self.progress_url, data, follow=True)
         self.assertEqual(rv.status_code, 200)
-        self.assertEqual(rv.resolver_match.view_name, 'trainee-dashboard')
+        self.assertEqual(rv.resolver_match.view_name, 'training-progress')
         self.assertContains(rv, 'Your homework submission will be evaluated '
                                 'soon.')
         got = list(TrainingProgress.objects.values_list(
@@ -265,17 +269,18 @@ class TestDiscussionSessionStatus(TestBase):
     def setUp(self):
         self._setUpUsersAndLogin()
         self.discussion = TrainingRequirement.objects.get(name='Discussion')
+        self.progress_url = reverse('training-progress')
 
     def test_session_passed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.discussion)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Discussion Session passed')
 
     def test_session_passed_but_discarded(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.discussion, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Discussion Session not passed yet')
 
     def test_last_session_discarded_but_another_is_passed(self):
@@ -283,17 +288,17 @@ def test_last_session_discarded_but_another_is_passed(self):
             trainee=self.admin, requirement=self.discussion)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.discussion, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Discussion Session passed')
 
     def test_session_failed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.discussion, state='f')
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Discussion Session not passed yet')
 
     def test_no_participation_in_a_session_yet(self):
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'Discussion Session not passed yet')
 
 
@@ -306,18 +311,19 @@ def setUp(self):
         self._setUpUsersAndLogin()
         self.swc_demo = TrainingRequirement.objects.get(name='SWC Demo')
         self.dc_demo = TrainingRequirement.objects.get(name='DC Demo')
+        self.progress_url = reverse('training-progress')
 
     def test_swc_session_passed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.swc_demo)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Demo Session passed')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_swc_session_passed_but_discarded(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.swc_demo, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Demo Session not passed yet')
         self.assertContains(rv, 'Register for Demo Session on')
 
@@ -326,33 +332,33 @@ def test_swc_last_session_discarded_but_another_is_passed(self):
             trainee=self.admin, requirement=self.swc_demo)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.swc_demo, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Demo Session passed')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_swc_session_failed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.swc_demo, state='f')
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Demo Session not passed yet')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_no_participation_in_a_swc_session_yet(self):
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Demo Session not passed yet')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_dc_session_passed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.dc_demo)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Demo Session passed')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_dc_session_passed_but_discarded(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.dc_demo, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Demo Session not passed yet')
         self.assertContains(rv, 'Register for Demo Session on')
 
@@ -361,19 +367,19 @@ def test_dc_last_session_discarded_but_another_is_passed(self):
             trainee=self.admin, requirement=self.dc_demo)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.dc_demo, discarded=True)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Demo Session passed')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_dc_session_failed(self):
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.dc_demo, state='f')
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Demo Session not passed yet')
         self.assertContains(rv, 'Register for Demo Session on')
 
     def test_no_participation_in_a_dc_session_yet(self):
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'DC Demo Session not passed yet')
         self.assertContains(rv, 'Register for Demo Session on')
 
@@ -382,7 +388,7 @@ def test_no_registration_instruction_when_trainee_passed_both_swc_and_dc_session
             trainee=self.admin, requirement=self.swc_demo)
         TrainingProgress.objects.create(
             trainee=self.admin, requirement=self.dc_demo)
-        rv = self.client.get(reverse('trainee-dashboard'))
+        rv = self.client.get(self.progress_url)
         self.assertContains(rv, 'SWC Demo Session passed')
         self.assertContains(rv, 'DC Demo Session passed')
         self.assertNotContains(rv, 'Register for Demo Session on')