Merge pull request #10330 from edx/bderusha/unenroll-certs

Conditionally show unenroll action based on certificate status
This commit is contained in:
Bill DeRusha
2015-10-23 17:51:00 -04:00
4 changed files with 106 additions and 4 deletions

View File

@@ -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):
"""

View File

@@ -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()

View File

@@ -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:

View File

@@ -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 (
</a>
<div class="actions-dropdown" id="actions-dropdown-${dashboard_index}" aria-label="${_('Additional Actions Menu')}">
<ul class="actions-dropdown-list" id="actions-dropdown-list-${dashboard_index}" aria-label="${_('Available Actions')}" role="menu">
<li class="actions-item" id="actions-item-unenroll-${dashboard_index}">
% if cert_status.get('status') not in DISABLE_UNENROLL_CERT_STATES:
<li class="actions-item" id="actions-item-unenroll-${dashboard_index}">
% if is_paid_course and show_refund_option:
## Translators: The course name will be added to the end of this sentence.
% if not is_course_blocked:
@@ -255,6 +257,7 @@ from student.helpers import (
% endif
% endif
</li>
% endif
<li class="actions-item" id="actions-item-email-settings-${dashboard_index}">
% if show_email_settings:
% if not is_course_blocked: