From cb4901431d912dc94fd1f71dd3e50ffb6f35074f Mon Sep 17 00:00:00 2001 From: Chris Dodge Date: Fri, 30 Oct 2015 15:46:13 -0400 Subject: [PATCH 1/8] mark test_can_reset_attempts as flakey as it sporadically fails --- .../test/acceptance/tests/lms/test_lms_instructor_dashboard.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py index d4c5285674..ee687c5d2e 100644 --- a/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py +++ b/common/test/acceptance/tests/lms/test_lms_instructor_dashboard.py @@ -244,7 +244,7 @@ class ProctoredExamsTest(BaseInstructorDashboardTest): # Stop the timed exam. self.courseware_page.stop_timed_exam() - @flaky # TODO fix this SOL-1183 + @flaky # TODO fix this SOL-1182 def test_can_add_remove_allowance(self): """ Make sure that allowances can be added and removed. @@ -262,6 +262,7 @@ class ProctoredExamsTest(BaseInstructorDashboardTest): # Then I can add Allowance to that exam for a student self.assertTrue(allowance_section.is_add_allowance_button_visible) + @flaky # TODO fix this SOL-1182 def test_can_reset_attempts(self): """ Make sure that Exam attempts are visible and can be reset. From c68af9d21ef0c3e2c0dabeb9dba4a0ce3b9e8537 Mon Sep 17 00:00:00 2001 From: jsa Date: Sun, 1 Nov 2015 12:39:23 -0500 Subject: [PATCH 2/8] Show xseries upsell even if the course is not yet in session ECOM-2780 --- common/djangoapps/student/tests/tests.py | 26 ++++++++++++++++++++++++ common/djangoapps/student/views.py | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index cc03c370c3..9b03546e05 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -1033,6 +1033,32 @@ class DashboardTestXSeriesPrograms(ModuleStoreTestCase, ProgramsApiConfigMixin): else: self.assertIn('xseries-border-btn', response.content) + @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) + @ddt.data((-2, -1), (-1, 1), (1, 2)) + @ddt.unpack + def test_start_end_offsets(self, start_days_offset, end_days_offset): + """Test that the xseries upsell messaging displays whether the course + has not yet started, is in session, or has already ended. + """ + self.course_1.start = datetime.now(pytz.UTC) + timedelta(days=start_days_offset) + self.course_1.end = datetime.now(pytz.UTC) + timedelta(days=end_days_offset) + self.update_course(self.course_1, self.user.id) + CourseEnrollment.enroll(self.user, self.course_1.id, mode='verified') + + self.client.login(username="jack", password="test") + self.create_config(enabled=True, enable_student_dashboard=True) + + with patch( + 'student.views.get_course_programs_for_dashboard', + return_value=self._create_program_data([(self.course_1.id, 'active')]) + ) as mock_get_programs: + response = self.client.get(reverse('dashboard')) + # ensure that our course id was included in the API call regardless of start/end dates + __, course_ids = mock_get_programs.call_args[0] + self.assertEqual(list(course_ids), [self.course_1.id]) + # count total courses appearing on student dashboard + self._assert_responses(response, 1) + @ddt.data( ('unpublished', 'unpublished', 'unpublished', 0), ('active', 'unpublished', 'unpublished', 1), diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 97df4883e9..1e02fe1514 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -581,7 +581,7 @@ def dashboard(request): # program-related information on the dashboard view. course_programs = {} if is_student_dashboard_programs_enabled(): - course_programs = _get_course_programs(user, show_courseware_links_for) + course_programs = _get_course_programs(user, [enrollment.course_id for enrollment in course_enrollments]) # Construct a dictionary of course mode information # used to render the course list. We re-use the course modes dict From 62f3cf9c46f488470ee68a6dee9fa5f28831208e Mon Sep 17 00:00:00 2001 From: Bill DeRusha Date: Fri, 30 Oct 2015 19:52:53 -0400 Subject: [PATCH 3/8] Fix cert_status None bug --- common/djangoapps/student/tests/test_views.py | 20 ++++++++++++++++++- common/djangoapps/student/tests/tests.py | 19 ++++++++++++------ common/djangoapps/student/views.py | 5 ++++- lms/templates/dashboard.html | 3 ++- .../dashboard/_dashboard_course_listing.html | 4 ++-- 5 files changed, 40 insertions(+), 11 deletions(-) diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index 2c42339913..3438770856 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -11,6 +11,7 @@ from django.conf import settings from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.models import CourseEnrollment +from student.helpers import DISABLE_UNENROLL_CERT_STATES from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.factories import CourseFactory @@ -38,7 +39,10 @@ class TestStudentDashboardUnenrollments(ModuleStoreTestCase): 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} + return { + 'status': self.cert_status, + 'can_unenroll': self.cert_status not in DISABLE_UNENROLL_CERT_STATES + } else: return {} @@ -85,3 +89,17 @@ class TestStudentDashboardUnenrollments(ModuleStoreTestCase): course_enrollment.assert_called_with(self.user, self.course.id) else: course_enrollment.assert_not_called() + + def test_no_cert_status(self): + """ Assert that the dashboard loads when cert_status is None.""" + with patch('student.views.cert_info', return_value=None): + response = self.client.get(reverse('dashboard')) + + self.assertEqual(response.status_code, 200) + + def test_cant_unenroll_status(self): + """ Assert that the dashboard loads when cert_status does not allow for unenrollment""" + with patch('certificates.models.certificate_status_for_student', return_value={'status': 'ready'}): + response = self.client.get(reverse('dashboard')) + + self.assertEqual(response.status_code, 200) diff --git a/common/djangoapps/student/tests/tests.py b/common/djangoapps/student/tests/tests.py index cc03c370c3..0f6de3b4ac 100644 --- a/common/djangoapps/student/tests/tests.py +++ b/common/djangoapps/student/tests/tests.py @@ -79,6 +79,7 @@ class CourseEndingTest(TestCase): 'show_disabled_download_button': False, 'show_download_url': False, 'show_survey_button': False, + 'can_unenroll': True, } ) @@ -91,7 +92,8 @@ class CourseEndingTest(TestCase): 'show_download_url': False, 'show_survey_button': False, 'mode': None, - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': True, } ) @@ -106,7 +108,8 @@ class CourseEndingTest(TestCase): 'survey_url': survey_url, 'grade': '67', 'mode': 'honor', - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': False, } ) @@ -121,7 +124,8 @@ class CourseEndingTest(TestCase): 'survey_url': survey_url, 'grade': '67', 'mode': 'verified', - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': False, } ) @@ -143,7 +147,8 @@ class CourseEndingTest(TestCase): 'survey_url': survey_url, 'grade': '67', 'mode': 'honor', - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': False, } ) @@ -162,7 +167,8 @@ class CourseEndingTest(TestCase): 'survey_url': survey_url, 'grade': '67', 'mode': 'honor', - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': True, } ) @@ -181,7 +187,8 @@ class CourseEndingTest(TestCase): 'show_survey_button': False, 'grade': '67', 'mode': 'honor', - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': True, } ) diff --git a/common/djangoapps/student/views.py b/common/djangoapps/student/views.py index 97df4883e9..6d5bc9b7d5 100644 --- a/common/djangoapps/student/views.py +++ b/common/djangoapps/student/views.py @@ -202,6 +202,7 @@ def cert_info(user, course_overview, course_mode): 'show_survey_button': bool 'survey_url': url, only if show_survey_button is True 'grade': if status is not 'processing' + 'can_unenroll': if status allows for unenrollment """ if not course_overview.may_certify(): return {} @@ -302,6 +303,7 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa 'show_disabled_download_button': False, 'show_download_url': False, 'show_survey_button': False, + 'can_unenroll': True } if cert_status is None: @@ -319,7 +321,8 @@ def _cert_info(user, course_overview, cert_status, course_mode): # pylint: disa 'show_download_url': status == 'ready', 'show_disabled_download_button': status == 'generating', 'mode': cert_status.get('mode', None), - 'linked_in_url': None + 'linked_in_url': None, + 'can_unenroll': status not in DISABLE_UNENROLL_CERT_STATES, } if (status in ('generating', 'ready', 'notpassing', 'restricted') and diff --git a/lms/templates/dashboard.html b/lms/templates/dashboard.html index 8755a7f8d2..a54bb972dd 100644 --- a/lms/templates/dashboard.html +++ b/lms/templates/dashboard.html @@ -87,6 +87,7 @@ import json % for dashboard_index, enrollment in enumerate(course_enrollments): <% show_courseware_link = (enrollment.course_id in show_courseware_links_for) %> <% cert_status = cert_statuses.get(enrollment.course_id) %> + <% can_unenroll = (not cert_status) or cert_status.get('can_unenroll') %> <% credit_status = credit_statuses.get(enrollment.course_id) %> <% show_email_settings = (enrollment.course_id in show_email_settings_for) %> <% course_mode_info = all_course_modes.get(enrollment.course_id) %> @@ -96,7 +97,7 @@ import json <% course_verification_status = verification_status_by_course.get(enrollment.course_id, {}) %> <% course_requirements = courses_requirements_not_met.get(enrollment.course_id) %> <% course_program_info = course_programs.get(unicode(enrollment.course_id)) %> - <%include file = 'dashboard/_dashboard_course_listing.html' args="course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option=show_refund_option, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, course_program_info=course_program_info" /> + <%include file = 'dashboard/_dashboard_course_listing.html' args="course_overview=enrollment.course_overview, enrollment=enrollment, show_courseware_link=show_courseware_link, cert_status=cert_status, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, show_refund_option=show_refund_option, is_paid_course=is_paid_course, is_course_blocked=is_course_blocked, verification_status=course_verification_status, course_requirements=course_requirements, dashboard_index=dashboard_index, share_settings=share_settings, user=user, course_program_info=course_program_info" /> % endfor diff --git a/lms/templates/dashboard/_dashboard_course_listing.html b/lms/templates/dashboard/_dashboard_course_listing.html index e73780761c..6bca726e22 100644 --- a/lms/templates/dashboard/_dashboard_course_listing.html +++ b/lms/templates/dashboard/_dashboard_course_listing.html @@ -1,4 +1,4 @@ -<%page args="course_overview, enrollment, show_courseware_link, cert_status, credit_status, show_email_settings, course_mode_info, show_refund_option, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, course_program_info" /> +<%page args="course_overview, enrollment, show_courseware_link, cert_status, can_unenroll, credit_status, show_email_settings, course_mode_info, show_refund_option, is_paid_course, is_course_blocked, verification_status, course_requirements, dashboard_index, share_settings, course_program_info" /> <%! import urllib @@ -178,7 +178,7 @@ from student.helpers import (