def test_content_availability_date(self, mock_get_course_run_details): """ Content availability date is course start date or enrollment date, whichever is later. """ access_duration = timedelta(weeks=7) mock_get_course_run_details.return_value = {'weeks_to_complete': 7} # Content availability date is enrollment date start_date = now() - timedelta(weeks=10) past_course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, past_course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, past_course) self.assertEqual(result, None) add_course_mode(past_course, upgrade_deadline_expired=False) result = get_user_course_expiration_date(self.user, past_course) content_availability_date = enrollment.created self.assertEqual(result, content_availability_date + access_duration) # Content availability date is course start date start_date = now() + timedelta(weeks=10) future_course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, future_course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, future_course) self.assertEqual(result, None) add_course_mode(future_course, upgrade_deadline_expired=False) result = get_user_course_expiration_date(self.user, future_course) content_availability_date = start_date self.assertEqual(result, content_availability_date + access_duration)
def test_content_availability_date(self, mock_get_course_run_details): """ Content availability date is course start date or enrollment date, whichever is later. """ access_duration = timedelta(weeks=7) mock_get_course_run_details.return_value = {'weeks_to_complete': 7} # Content availability date is enrollment date start_date = now() - timedelta(weeks=10) past_course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, past_course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, past_course) self.assertEqual(result, None) add_course_mode(past_course, upgrade_deadline_expired=False) result = get_user_course_expiration_date(self.user, past_course) content_availability_date = enrollment.created self.assertEqual(result, content_availability_date + access_duration) # Content availability date is course start date start_date = now() + timedelta(weeks=10) future_course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, future_course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, future_course) self.assertEqual(result, None) add_course_mode(future_course, upgrade_deadline_expired=False) result = get_user_course_expiration_date(self.user, future_course) content_availability_date = start_date self.assertEqual(result, content_availability_date + access_duration)
def test_content_availability_date(self, mock_get_course_run_details): """ Content availability date is course start date or enrollment date, whichever is later. """ access_duration = timedelta(weeks=7) mock_get_course_run_details.return_value = {'weeks_to_complete': 7} # Content availability date is enrollment date start_date = now() - timedelta(weeks=10) past_course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, past_course.id, CourseMode.AUDIT) CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(past_course.id), enabled_as_of=past_course.start, ) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(past_course.id), ) assert result is None add_course_mode(past_course, mode_slug=CourseMode.AUDIT) add_course_mode(past_course, upgrade_deadline_expired=False) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(past_course.id), ) content_availability_date = enrollment.created assert result == (content_availability_date + access_duration) # Content availability date is course start date start_date = now() + timedelta(weeks=10) future_course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, future_course.id, CourseMode.AUDIT) CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(future_course.id), enabled_as_of=past_course.start, ) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(future_course.id), ) assert result is None add_course_mode(future_course, mode_slug=CourseMode.AUDIT) add_course_mode(future_course, upgrade_deadline_expired=False) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(future_course.id), ).replace(microsecond=0) content_availability_date = start_date.replace(microsecond=0) assert result == (content_availability_date + access_duration)
def test_get_access_expiration_data(self): enrollment = CourseEnrollmentFactory() overview = enrollment.course user = enrollment.user now = timezone.now() upgrade_deadline = now + timedelta(days=2) CourseModeFactory( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=upgrade_deadline, ) CourseModeFactory( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) expiration_date = get_user_course_expiration_date(user, overview) assert expiration_date is not None data = get_access_expiration_data(user, overview) assert data == \ { 'expiration_date': expiration_date, 'masquerading_expired_course': False, 'upgrade_deadline': upgrade_deadline, 'upgrade_url': '/dashboard' }
def test_all_courses_with_weeks_to_complete( self, weeks_to_complete, access_duration, self_paced, mock_get_course_run_details, ): """ Test that access_duration for a course is equal to the value of the weeks_to_complete field in discovery. If weeks_to_complete is None, access_duration will be the MIN_DURATION constant. """ if self_paced: self.course.self_paced = True mock_get_course_run_details.return_value = { 'weeks_to_complete': weeks_to_complete } enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(self.course.id), enabled_as_of=self.course.start, ) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(self.course.id), ) assert result == (enrollment.created + access_duration)
def test_schedule_start_date_in_past(self): """ Test that when schedule start date is before course start or enrollment date, content_availability_date is set to max of course start or enrollment date """ enrollment = CourseEnrollmentFactory.create(course=self.course) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) Schedule.objects.update(start_date=datetime(2017, 1, 1, tzinfo=UTC)) content_availability_date = max(enrollment.created, enrollment.course.start) access_duration = get_user_course_duration(enrollment.user, enrollment.course) expected_course_expiration_date = content_availability_date + access_duration duration_limit_upgrade_deadline = get_user_course_expiration_date( enrollment.user, enrollment.course) assert duration_limit_upgrade_deadline is not None assert duration_limit_upgrade_deadline == expected_course_expiration_date
def test_schedule_start_date_in_past(self): """ Test that when schedule start date is before course start or enrollment date, content_availability_date is set to max of course start or enrollment date """ enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, start_date=datetime(2017, 1, 1, tzinfo=UTC), ) content_availability_date = max(enrollment.created, enrollment.course.start) access_duration = get_user_course_duration(enrollment.user, enrollment.course) expected_course_expiration_date = content_availability_date + access_duration duration_limit_upgrade_deadline = get_user_course_expiration_date( enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) self.assertEqual(duration_limit_upgrade_deadline, expected_course_expiration_date)
def test_instructor_paced_no_end_date(self): """Tests that instructor paced with no end dates returns default (minimum)""" self.course.self_paced = False enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, self.course) self.assertEqual(result, enrollment.created + MIN_DURATION)
def test_generate_course_expired_message(self, offsets): now = timezone.now() schedule_offset, course_offset = offsets # Set a timezone and request, to test that the message looks at the user's setting request = RequestFactory().get('/') request.user = UserFactory() set_current_request(request) self.addCleanup(set_current_request, None) set_user_preference(request.user, 'time_zone', 'Asia/Tokyo') if schedule_offset is not None: schedule_upgrade_deadline = now + timedelta(days=schedule_offset) else: schedule_upgrade_deadline = None if course_offset is not None: course_upgrade_deadline = now + timedelta(days=course_offset) else: course_upgrade_deadline = None enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=course_upgrade_deadline, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=schedule_upgrade_deadline, ) duration_limit_upgrade_deadline = get_user_course_expiration_date( enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) message = generate_course_expired_message(enrollment.user, enrollment.course) self.assertDateInMessage(duration_limit_upgrade_deadline, message) self.assertIn('data-timezone="Asia/Tokyo"', message) soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline has_upgrade_deadline = course_upgrade_deadline is not None if upgradeable and soft_upgradeable: self.assertDateInMessage(schedule_upgrade_deadline, message) elif upgradeable and has_upgrade_deadline: self.assertDateInMessage(course_upgrade_deadline, message) else: self.assertNotIn("Upgrade by", message)
def get_audit_access_expires(self, model): """ Returns expiration date for a course audit expiration, if any or null """ if not CourseDurationLimitConfig.enabled_for_enrollment(user=model.user, course_key=model.course.id): return None return get_user_course_expiration_date(model.user, model.course)
def get_audit_access_expiration(user, course): """ Return the expiration date for the user's audit access to this course. """ if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id): return None return get_user_course_expiration_date(user, course)
def get_audit_access_expires(self, model): """ Returns expiration date for a course audit expiration, if any or null """ if not CourseDurationLimitConfig.enabled_for_enrollment(model.user, model.course): return None return get_user_course_expiration_date(model.user, model.course)
def test_instructor_paced(self): """Tests that instructor paced courses give the learner start_date - end_date time in the course""" expected_difference = timedelta(weeks=6) self.course.self_paced = False self.course.end = self.course.start + expected_difference enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, self.course) self.assertEqual(result, enrollment.created + expected_difference)
def get_audit_access_expiration(user, course): """ Return the expiration date for the user's audit access to this course. """ if AUDIT_DEADLINE_FLAG.is_enabled(): if not CourseDurationLimitConfig.enabled_for_enrollment(user=user, course_key=course.id): return None return get_user_course_expiration_date(user, course) return None
def test_generate_course_expired_message(self, offsets): now = timezone.now() schedule_offset, course_offset = offsets if schedule_offset is not None: schedule_upgrade_deadline = now + timedelta(days=schedule_offset) else: schedule_upgrade_deadline = None if course_offset is not None: course_upgrade_deadline = now + timedelta(days=course_offset) else: course_upgrade_deadline = None def format_date(date): return strftime_localized(date, u'%b %-d, %Y') enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=course_upgrade_deadline, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=schedule_upgrade_deadline, ) duration_limit_upgrade_deadline = get_user_course_expiration_date( enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) message = generate_course_expired_message(enrollment.user, enrollment.course) self.assertIn(format_date(duration_limit_upgrade_deadline), message) soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline has_upgrade_deadline = course_upgrade_deadline is not None if upgradeable and soft_upgradeable: self.assertIn(format_date(schedule_upgrade_deadline), message) elif upgradeable and has_upgrade_deadline: self.assertIn(format_date(course_upgrade_deadline), message) else: self.assertNotIn("Upgrade by", message)
def test_generate_course_expired_message(self, offsets): now = timezone.now() schedule_offset, course_offset = offsets if schedule_offset is not None: schedule_upgrade_deadline = now + timedelta(days=schedule_offset) else: schedule_upgrade_deadline = None if course_offset is not None: course_upgrade_deadline = now + timedelta(days=course_offset) else: course_upgrade_deadline = None def format_date(date): return strftime_localized(date, u'%b %-d, %Y') enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=course_upgrade_deadline, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=schedule_upgrade_deadline, ) duration_limit_upgrade_deadline = get_user_course_expiration_date(enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) message = generate_course_expired_message(enrollment.user, enrollment.course) self.assertIn(format_date(duration_limit_upgrade_deadline), message) soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline has_upgrade_deadline = course_upgrade_deadline is not None if upgradeable and soft_upgradeable: self.assertIn(format_date(schedule_upgrade_deadline), message) elif upgradeable and has_upgrade_deadline: self.assertIn(format_date(course_upgrade_deadline), message) else: self.assertNotIn("Upgrade by", message)
def handle_goal(goal, today, sunday_date, monday_date): """Sends an email reminder for a single CourseGoal, if it passes all our checks""" if not COURSE_GOALS_NUMBER_OF_DAYS_GOALS.is_enabled(goal.course_key): return False enrollment = CourseEnrollment.get_enrollment(goal.user, goal.course_key, select_related=['course']) # If you're not actively enrolled in the course or your enrollment was this week if not enrollment or not enrollment.is_active or enrollment.created.date( ) >= monday_date: return False audit_access_expiration_date = get_user_course_expiration_date( goal.user, enrollment.course_overview) # If an audit user's access expires this week, exclude them from the email since they may not # be able to hit their goal anyway if audit_access_expiration_date and audit_access_expiration_date.date( ) <= sunday_date: return False cert = get_certificate_for_user_id(goal.user, goal.course_key) # If a user has a downloadable certificate, we will consider them as having completed # the course and opt them out of receiving emails if cert and cert.status == CertificateStatuses.downloadable: return False # Check the number of days left to successfully hit their goal week_activity_count = UserActivity.objects.filter( user=goal.user, course_key=goal.course_key, date__gte=monday_date, ).count() required_days_left = goal.days_per_week - week_activity_count # The weekdays are 0 indexed, but we want this to be 1 to match required_days_left. # Essentially, if today is Sunday, days_left_in_week should be 1 since they have Sunday to hit their goal. days_left_in_week = SUNDAY_WEEKDAY - today.weekday() + 1 # We want to email users in the morning of their timezone user_timezone = get_user_timezone_or_last_seen_timezone_or_utc( goal.user) now_in_users_timezone = datetime.now(user_timezone) if not 9 <= now_in_users_timezone.hour < 12: return False if required_days_left == days_left_in_week: send_ace_message(goal) CourseGoalReminderStatus.objects.update_or_create( goal=goal, defaults={'email_reminder_sent': True}) return True return False
def get_base_experiment_metadata_context(course, user, enrollment, user_enrollments): """ Return a context dictionary with the keys used by dashboard_metadata.html and user_metadata.html """ enrollment_mode = None enrollment_time = None # TODO: clean up as part of REVEM-199 (START) program_key = get_program_context(course, user_enrollments) # TODO: clean up as part of REVEM-199 (END) schedule_start = None if enrollment and enrollment.is_active: enrollment_mode = enrollment.mode enrollment_time = enrollment.created try: schedule_start = enrollment.schedule.start_date except Schedule.DoesNotExist: pass # upgrade_link, dynamic_upgrade_deadline and course_upgrade_deadline should be None # if user has passed their dynamic pacing deadline. upgrade_link, dynamic_upgrade_deadline, course_upgrade_deadline = check_and_get_upgrade_link_and_date( user, enrollment, course) duration = get_user_course_duration(user, course) deadline = duration and get_user_course_expiration_date(user, course) return { 'upgrade_link': upgrade_link, 'upgrade_price': six.text_type(get_cosmetic_verified_display_price(course)), 'enrollment_mode': enrollment_mode, 'enrollment_time': enrollment_time, 'schedule_start': schedule_start, 'pacing_type': 'self_paced' if course.self_paced else 'instructor_paced', 'dynamic_upgrade_deadline': dynamic_upgrade_deadline, 'course_upgrade_deadline': course_upgrade_deadline, 'audit_access_deadline': deadline, 'course_duration': duration, 'course_key': course.id, 'course_display_name': course.display_name_with_default, 'course_start': course.start, 'course_end': course.end, # TODO: clean up as part of REVEM-199 (START) 'program_key_fields': program_key, # TODO: clean up as part of REVEM-199 (END) }
def test_expired_upgrade_deadline(self, mock_get_course_run_details): """ The expiration date still exists if the upgrade deadline has passed """ access_duration = timedelta(weeks=7) mock_get_course_run_details.return_value = {'weeks_to_complete': 7} start_date = now() - timedelta(weeks=10) course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, course.id, CourseMode.AUDIT) add_course_mode(course, upgrade_deadline_expired=True) result = get_user_course_expiration_date(self.user, course) content_availability_date = enrollment.created self.assertEqual(result, content_availability_date + access_duration)
def test_expired_upgrade_deadline(self, mock_get_course_run_details): """ The expiration date still exists if the upgrade deadline has passed """ access_duration = timedelta(weeks=7) mock_get_course_run_details.return_value = {'weeks_to_complete': 7} start_date = now() - timedelta(weeks=10) course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, course.id, CourseMode.AUDIT) add_course_mode(course, upgrade_deadline_expired=True) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(course.id), ) content_availability_date = enrollment.created self.assertEqual(result, content_availability_date + access_duration)
def serialize_upgrade_info(user, course_overview, enrollment): """ Return verified mode upgrade information, or None. This is used in a few API views to provide consistent upgrade info to frontends. """ if not can_show_verified_upgrade(user, enrollment): return None mode = CourseMode.verified_mode_for_course(course=course_overview) return { 'access_expiration_date': get_user_course_expiration_date(user, course_overview), 'currency': mode.currency.upper(), 'currency_symbol': get_currency_symbol(mode.currency.upper()), 'price': mode.min_price, 'sku': mode.sku, 'upgrade_url': verified_upgrade_deadline_link(user, course_overview), }
def test_all_courses_with_weeks_to_complete( self, weeks_to_complete, access_duration, self_paced, mock_get_course_run_details, ): """ Test that access_duration for a course is equal to the value of the weeks_to_complete field in discovery. If weeks_to_complete is None, access_duration will be the MIN_DURATION constant. """ if self_paced: self.course.self_paced = True mock_get_course_run_details.return_value = {'weeks_to_complete': weeks_to_complete} enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, self.course) self.assertEqual(result, enrollment.created + access_duration)
def test_all_courses_with_weeks_to_complete( self, weeks_to_complete, access_duration, self_paced, mock_get_course_run_details, ): """ Test that access_duration for a course is equal to the value of the weeks_to_complete field in discovery. If weeks_to_complete is None, access_duration will be the MIN_DURATION constant. """ if self_paced: self.course.self_paced = True mock_get_course_run_details.return_value = {'weeks_to_complete': weeks_to_complete} enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, self.course) self.assertEqual(result, enrollment.created + access_duration)
def test_self_paced_with_weeks_to_complete( self, weeks_to_complete, expected_difference, mock_get_course_run_details, ): """ Tests that self paced courses allow for a (bounded) # of weeks in courses determined via weeks_to_complete field in discovery. If the field doesn't exist, it should return default (minimum) """ self.course.self_paced = True mock_get_course_run_details.return_value = { 'weeks_to_complete': weeks_to_complete } enrollment = CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) result = get_user_course_expiration_date(self.user, self.course) self.assertEqual(result, enrollment.created + expected_difference)
def test_expired_upgrade_deadline(self, mock_get_course_run_details): """ The expiration date still exists if the upgrade deadline has passed """ access_duration = timedelta(weeks=7) mock_get_course_run_details.return_value = {'weeks_to_complete': 7} start_date = now() - timedelta(weeks=10) course = CourseFactory(start=start_date) enrollment = CourseEnrollment.enroll(self.user, course.id, CourseMode.AUDIT) CourseDurationLimitConfig.objects.create( enabled=True, course=CourseOverview.get_from_id(course.id), enabled_as_of=course.start, ) add_course_mode(course, mode_slug=CourseMode.AUDIT) add_course_mode(course, upgrade_deadline_expired=True) result = get_user_course_expiration_date( self.user, CourseOverview.get_from_id(course.id), ) content_availability_date = enrollment.created assert result == (content_availability_date + access_duration)
def verified_mode(self): """ Return verified mode information, or None. """ if not can_show_verified_upgrade(self.effective_user, self.enrollment_object): return None mode = CourseMode.verified_mode_for_course(self.course_key) return { 'access_expiration_date': get_user_course_expiration_date(self.effective_user, self.overview), 'price': mode.min_price, 'currency': mode.currency.upper(), 'currency_symbol': get_currency_symbol(mode.currency.upper()), 'sku': mode.sku, 'upgrade_url': verified_upgrade_deadline_link(self.effective_user, self.overview), }
def test_register_course_expired_message(self, language, offsets, mock_messages): now = timezone.now() schedule_offset, course_offset = offsets if schedule_offset is not None: schedule_upgrade_deadline = now + timedelta(days=schedule_offset) else: schedule_upgrade_deadline = None if course_offset is not None: course_upgrade_deadline = now + timedelta(days=course_offset) else: course_upgrade_deadline = None def format_date(date): if language.startswith('es-'): return strftime_localized(date, '%-d de %b. de %Y').lower() else: return strftime_localized(date, '%b. %-d, %Y') patch_lang = patch( 'openedx.features.course_duration_limits.access.get_language', return_value=language) with patch_lang: enrollment = CourseEnrollmentFactory.create( course__start=datetime(2018, 1, 1, tzinfo=UTC), course__self_paced=True, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.VERIFIED, expiration_datetime=course_upgrade_deadline, ) CourseModeFactory.create( course_id=enrollment.course.id, mode_slug=CourseMode.AUDIT, ) ScheduleFactory.create( enrollment=enrollment, upgrade_deadline=schedule_upgrade_deadline, ) request = RequestFactory().get('/courseware') request.user = enrollment.user duration_limit_upgrade_deadline = get_user_course_expiration_date( enrollment.user, enrollment.course) self.assertIsNotNone(duration_limit_upgrade_deadline) register_course_expired_message(request, enrollment.course) self.assertEqual(mock_messages.register_info_message.call_count, 1) message = str(mock_messages.register_info_message.call_args[0][1]) self.assertIn(format_date(duration_limit_upgrade_deadline), message) soft_upgradeable = schedule_upgrade_deadline is not None and now < schedule_upgrade_deadline upgradeable = course_upgrade_deadline is None or now < course_upgrade_deadline has_upgrade_deadline = course_upgrade_deadline is not None if upgradeable and soft_upgradeable: self.assertIn(format_date(schedule_upgrade_deadline), message) elif upgradeable and has_upgrade_deadline: self.assertIn(format_date(course_upgrade_deadline), message) else: self.assertNotIn("Upgrade by", message)
def date(self): return get_user_course_expiration_date(self.user, self.course)
def test_enrollment_mode(self): """Tests that verified enrollments do not have an expiration""" CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) result = get_user_course_expiration_date(self.user, CourseOverview.get_from_id(self.course.id)) self.assertEqual(result, None)
def get_audit_access_expires(self, model): """ Returns expiration date for a course audit expiration, if any or null """ return get_user_course_expiration_date(model.user, model.course)
def get(self, request, course_id, error=None): # lint-amnesty, pylint: disable=too-many-statements """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_client_ip(request)[0], url=request.path) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( request.user, course_key) increment('track-selection.{}.{}'.format( enrollment_mode, 'active' if is_active else 'inactive')) increment('track-selection.views') if enrollment_mode is None: LOG.info( 'Rendering track selection for unenrolled user, referred by %s', request.META.get('HTTP_REFERER')) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to 'no-id-professional'. has_enrolled_professional = ( CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode( modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") redirect_url = IDVerificationService.get_verify_location( course_id=course_key) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get( CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get( CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.get_checkout_page_url( professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url( professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. Send the user to the dashboard. if not CourseMode.has_verified_mode(modes): return self._redirect_to_course_or_dashboard( course, course_key, request.user) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): return self._redirect_to_course_or_dashboard( course, course_key, request.user) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(str(course_key), None) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale) params = six.moves.urllib.parse.urlencode( {'course_closed': enrollment_end_date}) return redirect('{}?{}'.format(reverse('dashboard'), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for university credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False)) course_id = str(course_key) gated_content = ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key) context = { "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_id}), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, "content_gating_enabled": gated_content, "course_duration_limit_enabled": CourseDurationLimitConfig.enabled_for_enrollment( request.user, course), } context.update( get_experiment_user_metadata_context( course, request.user, )) title_content = '' if enrollment_mode: title_content = _( "Congratulations! You are now enrolled in {course_name}" ).format(course_name=course.display_name_with_default) context["title_content"] = title_content if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] price_before_discount = verified_mode.min_price course_price = price_before_discount enterprise_customer = enterprise_customer_for_request(request) LOG.info( '[e-commerce calculate API] Going to hit the API for user [%s] linked to [%s] enterprise', request.user.username, enterprise_customer.get('name') if isinstance( enterprise_customer, dict) else None # Test Purpose ) if enterprise_customer and verified_mode.sku: course_price = get_course_final_price(request.user, verified_mode.sku, price_before_discount) context["currency"] = verified_mode.currency.upper() context["currency_symbol"] = get_currency_symbol( verified_mode.currency.upper()) context["min_price"] = course_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description # if course_price is equal to price_before_discount then user doesn't entitle to any discount. if course_price != price_before_discount: context["price_before_discount"] = price_before_discount if verified_mode.sku: context[ "use_ecommerce_payment_flow"] = ecommerce_service.is_enabled( request.user) context[ "ecommerce_payment_page"] = ecommerce_service.payment_page_url( ) context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass language = get_language() context['track_links'] = get_verified_track_links(language) duration = get_user_course_duration(request.user, course) deadline = duration and get_user_course_expiration_date( request.user, course) if deadline: formatted_audit_access_date = strftime_localized_html( deadline, 'SHORT_DATE') context['audit_access_deadline'] = formatted_audit_access_date fbe_is_on = deadline and gated_content # Route to correct Track Selection page. # REV-2133 TODO Value Prop: remove waffle flag after testing is completed # and happy path version is ready to be rolled out to all users. if VALUE_PROP_TRACK_SELECTION_FLAG.is_enabled(): if not error: # TODO: Remove by executing REV-2355 if not enterprise_customer_for_request( request): # TODO: Remove by executing REV-2342 if fbe_is_on: return render_to_response("course_modes/fbe.html", context) else: return render_to_response("course_modes/unfbe.html", context) # If error or enterprise_customer, failover to old choose.html page return render_to_response("course_modes/choose.html", context)
def test_enrollment_mode(self): """Tests that verified enrollments do not have an expiration""" CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) result = get_user_course_expiration_date(self.user, CourseOverview.get_from_id(self.course.id)) self.assertEqual(result, None)
def date(self): if not CourseDurationLimitConfig.enabled_for_enrollment( self.user, self.course): return return get_user_course_expiration_date(self.user, self.course)