From ed1d33e54771b8a6ec4fc6aa533bf6bd5c31e506 Mon Sep 17 00:00:00 2001 From: Jeff LaJoie Date: Tue, 9 Jan 2018 16:06:22 -0500 Subject: [PATCH] LEARNER-3661: Removes sessions a user has already claimed an entitlement for from list of available sessions --- common/djangoapps/student/tests/test_views.py | 44 +++++++++++++++---- openedx/core/djangoapps/catalog/utils.py | 16 ++++--- 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/common/djangoapps/student/tests/test_views.py b/common/djangoapps/student/tests/test_views.py index 51b029b73b..de7414bfb9 100644 --- a/common/djangoapps/student/tests/test_views.py +++ b/common/djangoapps/student/tests/test_views.py @@ -12,24 +12,28 @@ from django.core.urlresolvers import reverse from django.test import RequestFactory, TestCase from django.test.utils import override_settings from django.utils.timezone import now -from edx_oauth2_provider.constants import AUTHORIZED_CLIENTS_SESSION_KEY -from edx_oauth2_provider.tests.factories import ClientFactory, TrustedClientFactory -from milestones.tests.utils import MilestonesTestCaseMixin from mock import patch from opaque_keys import InvalidKeyError -from pyquery import PyQuery as pq from bulk_email.models import BulkEmailFlag +from course_modes.models import CourseMode +from edx_oauth2_provider.constants import AUTHORIZED_CLIENTS_SESSION_KEY +from edx_oauth2_provider.tests.factories import (ClientFactory, + TrustedClientFactory) from entitlements.tests.factories import CourseEntitlementFactory +from milestones.tests.utils import MilestonesTestCaseMixin from openedx.core.djangoapps.catalog.tests.factories import ProgramFactory from openedx.core.djangoapps.content.course_overviews.models import CourseOverview from openedx.core.djangoapps.content.course_overviews.tests.factories import CourseOverviewFactory +from pyquery import PyQuery as pq from student.cookies import get_user_info_cookie_data from student.helpers import DISABLE_UNENROLL_CERT_STATES from student.models import CourseEnrollment, UserProfile from student.signals import REFUND_ORDER from student.tests.factories import CourseEnrollmentFactory, UserFactory -from util.milestones_helpers import get_course_milestones, remove_prerequisite_course, set_prerequisite_courses +from util.milestones_helpers import (get_course_milestones, + remove_prerequisite_course, + set_prerequisite_courses) from util.testing import UrlResetMixin from xmodule.modulestore import ModuleStoreEnum from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase @@ -350,7 +354,8 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): @patch('student.views.get_visible_sessions_for_entitlement') @patch('student.views.get_pseudo_session_for_entitlement') @patch.object(CourseOverview, 'get_from_id') - def test_unfulfilled_entitlement(self, mock_course_overview, mock_pseudo_session, mock_course_runs, mock_get_programs): + def test_unfulfilled_entitlement(self, mock_course_overview, mock_pseudo_session, + mock_course_runs, mock_get_programs): """ When a learner has an unfulfilled entitlement, their course dashboard should have: - a hidden 'View Course' button @@ -359,9 +364,9 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): - a related programs message """ program = ProgramFactory() - CourseEntitlementFactory(user=self.user, course_uuid=program['courses'][0]['uuid']) + CourseEntitlementFactory.create(user=self.user, course_uuid=program['courses'][0]['uuid']) mock_get_programs.return_value = [program] - mock_course_overview.return_value = CourseOverviewFactory(start=self.TOMORROW) + mock_course_overview.return_value = CourseOverviewFactory.create(start=self.TOMORROW) mock_course_runs.return_value = [ { 'key': 'course-v1:FAKE+FA1-MA1.X+3T2017', @@ -380,6 +385,29 @@ class StudentDashboardTests(SharedModuleStoreTestCase, MilestonesTestCaseMixin): self.assertIn('
', response.content) self.assertIn('Related Programs:', response.content) + # If an entitlement has already been redeemed by the user for a course run, do not let the run be selectable + enrollment = CourseEnrollmentFactory( + user=self.user, course_id=unicode(mock_course_overview.return_value.id), mode=CourseMode.VERIFIED + ) + CourseEntitlementFactory.create( + user=self.user, course_uuid=program['courses'][0]['uuid'], enrollment_course_run=enrollment + ) + + mock_course_runs.return_value = [ + { + 'key': 'course-v1:edX+toy+2012_Fall', + 'enrollment_end': str(self.TOMORROW), + 'pacing_type': 'instructor_paced', + 'type': 'verified' + } + ] + response = self.client.get(self.path) + # There should be two entitlements on the course page, one prompting for a mandatory session, but no + # select option for the courses as there is only the single course run which has already been redeemed + self.assertEqual(response.content.count('
  • '), 2) + self.assertIn('You must select a session to access the course.', response.content) + self.assertNotIn('To access the course, select a session.', response.content) + @patch('student.views.get_visible_sessions_for_entitlement') @patch.object(CourseOverview, 'get_from_id') def test_unfulfilled_expired_entitlement(self, mock_course_overview, mock_course_runs): diff --git a/openedx/core/djangoapps/catalog/utils.py b/openedx/core/djangoapps/catalog/utils.py index b1990c1277..20aaa2a35b 100644 --- a/openedx/core/djangoapps/catalog/utils.py +++ b/openedx/core/djangoapps/catalog/utils.py @@ -8,15 +8,14 @@ from django.conf import settings from django.core.cache import cache from django.core.exceptions import ObjectDoesNotExist from edx_rest_api_client.client import EdxRestApiClient +from pytz import UTC -from openedx.core.djangoapps.catalog.cache import ( - PROGRAM_CACHE_KEY_TPL, - SITE_PROGRAM_UUIDS_CACHE_KEY_TPL -) +from openedx.core.djangoapps.catalog.cache import (PROGRAM_CACHE_KEY_TPL, + SITE_PROGRAM_UUIDS_CACHE_KEY_TPL) from openedx.core.djangoapps.catalog.models import CatalogIntegration from openedx.core.lib.edx_api_utils import get_edx_api_data from openedx.core.lib.token_utils import JwtBuilder -from pytz import UTC +from student.models import CourseEnrollment logger = logging.getLogger(__name__) @@ -276,12 +275,16 @@ def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs): 2) A user can enroll in 3) A user can upgrade in 4) Are published + 5) Are not enrolled in already for an active session These are the only sessions that can be selected for an entitlement. """ enrollable_sessions = [] + enrollments_for_user = CourseEnrollment.enrollments_for_user(entitlement.user).filter(mode=entitlement.mode) + enrolled_sessions = frozenset([str(e.course_id) for e in enrollments_for_user]) + # Only show published course runs that can still be enrolled and upgraded now = datetime.datetime.now(UTC) for course_run in course_runs: @@ -295,7 +298,8 @@ def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs): enrollment_start = course_run.get('enrollment_start') enrollment_end = course_run.get('enrollment_end') can_enroll = ((not enrollment_start or datetime_parse(enrollment_start) < now) - and (not enrollment_end or datetime_parse(enrollment_end) > now)) + and (not enrollment_end or datetime_parse(enrollment_end) > now) + and course_run.get('key') not in enrolled_sessions) # Only upgrade-able courses will be displayed can_upgrade = False