From 60fc3f57054404cba2f493474f66e347fec41acf Mon Sep 17 00:00:00 2001 From: Bill DeRusha Date: Wed, 21 Oct 2015 16:14:24 -0400 Subject: [PATCH] Conditionally show unenroll action based on certificat status --- common/djangoapps/student/helpers.py | 5 ++ common/djangoapps/student/tests/test_views.py | 87 +++++++++++++++++++ common/djangoapps/student/views.py | 11 ++- .../dashboard/_dashboard_course_listing.html | 7 +- 4 files changed, 106 insertions(+), 4 deletions(-) create mode 100644 common/djangoapps/student/tests/test_views.py diff --git a/common/djangoapps/student/helpers.py b/common/djangoapps/student/helpers.py index 4449ecaa6d..e285e3dcf1 100644 --- a/common/djangoapps/student/helpers.py +++ b/common/djangoapps/student/helpers.py @@ -18,6 +18,11 @@ VERIFY_STATUS_APPROVED = "verify_approved" VERIFY_STATUS_MISSED_DEADLINE = "verify_missed_deadline" VERIFY_STATUS_NEED_TO_REVERIFY = "verify_need_to_reverify" +DISABLE_UNENROLL_CERT_STATES = [ + 'generating', + 'ready', +] + def check_verify_status_by_course(user, course_enrollments): """ diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py new file mode 100644 index 0000000000..2c42339913 --- /dev/null +++ b/common/djangoapps/student/tests/test_views.py @@ -0,0 +1,87 @@ +""" +Test the student dashboard view. +""" +import ddt +import unittest +from mock import patch +from pyquery import PyQuery as pq + +from django.core.urlresolvers import reverse +from django.conf import settings + +from student.tests.factories import UserFactory, CourseEnrollmentFactory +from student.models import CourseEnrollment +from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase +from xmodule.modulestore.tests.factories import CourseFactory + + +@ddt.ddt +@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms') +class TestStudentDashboardUnenrollments(ModuleStoreTestCase): + """ + Test to ensure that the student dashboard does not show the unenroll button for users with certificates. + """ + USERNAME = "Bob" + EMAIL = "bob@example.com" + PASSWORD = "edx" + UNENROLL_ELEMENT_ID = "#actions-item-unenroll-0" + + def setUp(self): + """ Create a course and user, then log in. """ + super(TestStudentDashboardUnenrollments, self).setUp() + self.course = CourseFactory.create() + self.user = UserFactory.create(username=self.USERNAME, email=self.EMAIL, password=self.PASSWORD) + CourseEnrollmentFactory(course_id=self.course.id, user=self.user) + self.cert_status = None + self.client.login(username=self.USERNAME, password=self.PASSWORD) + + def mock_cert(self, _user, _course_overview, _course_mode): # pylint: disable=unused-argument + """ Return a preset certificate status. """ + if self.cert_status is not None: + return {'status': self.cert_status} + else: + return {} + + @ddt.data( + ('notpassing', 1), + ('restricted', 1), + ('processing', 1), + (None, 1), + ('generating', 0), + ('ready', 0), + ) + @ddt.unpack + def test_unenroll_available(self, cert_status, unenroll_action_count): + """ Assert that the unenroll action is shown or not based on the cert status.""" + self.cert_status = cert_status + + with patch('student.views.cert_info', side_effect=self.mock_cert): + response = self.client.get(reverse('dashboard')) + + self.assertEqual(pq(response.content)(self.UNENROLL_ELEMENT_ID).length, unenroll_action_count) + + @ddt.data( + ('notpassing', 200), + ('restricted', 200), + ('processing', 200), + (None, 200), + ('generating', 400), + ('ready', 400), + ) + @ddt.unpack + @patch.object(CourseEnrollment, 'unenroll') + def test_unenroll_request(self, cert_status, status_code, course_enrollment): + """ Assert that the unenroll method is called or not based on the cert status""" + self.cert_status = cert_status + + with patch('student.views.cert_info', side_effect=self.mock_cert): + response = self.client.post( + reverse('change_enrollment'), + {'enrollment_action': 'unenroll', 'course_id': self.course.id} + ) + + self.assertEqual(response.status_code, status_code) + if status_code == 200: + course_enrollment.assert_called_with(self.user, self.course.id) + else: + course_enrollment.assert_not_called() diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 387f311c44..07669ee36a 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -106,7 +106,8 @@ import third_party_auth from third_party_auth import pipeline, provider from student.helpers import ( check_verify_status_by_course, - auth_pipeline_urls, get_next_url_for_login_page + auth_pipeline_urls, get_next_url_for_login_page, + DISABLE_UNENROLL_CERT_STATES, ) from student.cookies import set_logged_in_cookies, delete_logged_in_cookies from student.models import anonymous_id_for_user @@ -1014,8 +1015,14 @@ def change_enrollment(request, check_access=True): # Otherwise, there is only one mode available (the default) return HttpResponse() elif action == "unenroll": - if not CourseEnrollment.is_enrolled(user, course_id): + enrollment = CourseEnrollment.get_enrollment(user, course_id) + if not enrollment: return HttpResponseBadRequest(_("You are not enrolled in this course")) + + certicifate_info = cert_info(user, enrollment.course_overview, enrollment.mode) + if certicifate_info.get('status') in DISABLE_UNENROLL_CERT_STATES: + return HttpResponseBadRequest(_("Your certificate prevents you from unenrolling from this course")) + CourseEnrollment.unenroll(user, course_id) return HttpResponse() else: diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index 10f66f91ad..f884278b00 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -14,7 +14,8 @@ from student.helpers import ( VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED, VERIFY_STATUS_MISSED_DEADLINE, - VERIFY_STATUS_NEED_TO_REVERIFY + VERIFY_STATUS_NEED_TO_REVERIFY, + DISABLE_UNENROLL_CERT_STATES, ) %> @@ -171,7 +172,8 @@ from student.helpers import (