def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member run_mode = dict( factories.RunMode( certificate_url=None, course_image_url=course_overview.course_image_url, course_key=unicode(self.course.id), # pylint: disable=no-member course_url=reverse('course_root', args=[self.course.id]), # pylint: disable=no-member end_date=strftime_localized(self.course.end, 'SHORT_DATE'), enrollment_open_date=strftime_localized(utils.DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), is_course_ended=self.course.end < timezone.now(), is_enrolled=False, is_enrollment_open=True, marketing_url=MARKETING_URL, start_date=strftime_localized(self.course.start, 'SHORT_DATE'), upgrade_url=None, ), **kwargs ) course_code = factories.CourseCode(display_name=self.course_code['display_name'], run_modes=[run_mode]) expected = copy.deepcopy(self.program) expected['course_codes'] = [course_code] self.assertEqual(actual, expected)
def test_expired_course(self): """ Ensure that a user accessing an expired course sees a redirect to the student dashboard, not a 404. """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime(2010, 1, 1)) course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]: CourseModeFactory.create(course_id=course.id, mode_slug=mode) # assert that an if an expired audit user tries to access the course they are redirected to the dashboard audit_user = UserFactory(password=self.TEST_PASSWORD) self.client.login(username=audit_user.username, password=self.TEST_PASSWORD) audit_enrollment = CourseEnrollment.enroll(audit_user, course.id, mode=CourseMode.AUDIT) ScheduleFactory(start=THREE_YEARS_AGO, enrollment=audit_enrollment) response = self.client.get(url) expiration_date = strftime_localized(course.start + timedelta(weeks=4), 'SHORT_DATE') expected_params = QueryDict(mutable=True) course_name = CourseOverview.get_from_id(course.id).display_name_with_default expected_params['access_response_error'] = 'Access to {run} expired on {expiration_date}'.format( run=course_name, expiration_date=expiration_date ) expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode() ) self.assertRedirects(response, expected_url)
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Args: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ user_orders = [] commerce_configuration = CommerceConfiguration.current() user_query = {'username': user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data( commerce_configuration, 'orders', api=api, querystring=user_query, cache_key=cache_key ) for order in commerce_user_orders: if order['status'].lower() == 'complete': date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") order_data = { 'number': order['number'], 'price': order['total_excl_tax'], 'order_date': strftime_localized(date_placed, 'SHORT_DATE'), 'receipt_url': EcommerceService().get_receipt_page_url(order['number']), 'lines': order['lines'], } user_orders.append(order_data) return user_orders
def _update_certificate_context(context, course, user_certificate, platform_name): """ Build up the certificate web view context using the provided values (Helper method to keep the view clean) """ # Populate dynamic output values using the course/certificate data loaded above certificate_type = context.get('certificate_type') # Override the defaults with any mode-specific static values context['certificate_id_number'] = user_certificate.verify_uuid context['certificate_verify_url'] = "{prefix}{uuid}{suffix}".format( prefix=context.get('certificate_verify_url_prefix'), uuid=user_certificate.verify_uuid, suffix=context.get('certificate_verify_url_suffix') ) # Translators: The format of the date includes the full name of the month date = display_date_for_certificate(course, user_certificate) context['certificate_date_issued'] = _('{month} {day}, {year}').format( month=strftime_localized(date, "%B"), day=date.day, year=date.year ) # Translators: This text represents the verification of the certificate context['document_meta_description'] = _('This is a valid {platform_name} certificate for {user_name}, ' 'who participated in {partner_short_name} {course_number}').format( platform_name=platform_name, user_name=context['accomplishment_copy_name'], partner_short_name=context['organization_short_name'], course_number=context['course_number'] ) # Translators: This text is bound to the HTML 'title' element of the page and appears in the browser title bar context['document_title'] = _("{partner_short_name} {course_number} Certificate | {platform_name}").format( partner_short_name=context['organization_short_name'], course_number=context['course_number'], platform_name=platform_name ) # Translators: This text fragment appears after the student's name (displayed in a large font) on the certificate # screen. The text describes the accomplishment represented by the certificate information displayed to the user context['accomplishment_copy_description_full'] = _("successfully completed, received a passing grade, and was " "awarded this {platform_name} {certificate_type} " "Certificate of Completion in ").format( platform_name=platform_name, certificate_type=context.get("certificate_type")) certificate_type_description = get_certificate_description(user_certificate.mode, certificate_type, platform_name) if certificate_type_description: context['certificate_type_description'] = certificate_type_description # Translators: This text describes the purpose (and therefore, value) of a course certificate context['certificate_info_description'] = _("{platform_name} acknowledges achievements through " "certificates, which are awarded for course activities " "that {platform_name} students complete.").format( platform_name=platform_name, tos_url=context.get('company_tos_url'), verified_cert_url=context.get('company_verified_certificate_url'))
def enforce_compliance_on_login(user, password): """ Verify that the user's password is compliant with password policy rules and determine what should be done if it is not. Raises NonCompliantPasswordException when the password is found to be non-compliant and the compliance deadline for the user has been reached. In this case, login should be prevented. Raises NonCompliantPasswordWarning when the password is found to be non-compliant and the compliance deadline for the user is in the future. Returns None when the password is found to be compliant, or when no deadline for compliance has been set for the user. Important: This method should only be called AFTER the user has been authenticated. """ is_compliant = _check_user_compliance(user, password) if is_compliant: return deadline = _get_compliance_deadline_for_user(user) if deadline is None: return now = datetime.now(pytz.UTC) if now >= deadline: raise NonCompliantPasswordException( HTML(_( u'{strong_tag_open}We recently changed our password requirements{strong_tag_close}{break_line_tag}' 'Your current password does not meet the new security requirements. We just sent a password-reset ' 'message to the email address associated with this account. Thank you for helping us keep your data ' 'safe.' )).format( strong_tag_open=HTML('<strong>'), strong_tag_close=HTML('</strong>'), break_line_tag=HTML('<br/>'), ) ) else: raise NonCompliantPasswordWarning( HTML(_( u'{strong_tag_open}Required Action: Please update your password{strong_tag_close}{break_line_tag}' u'As of {deadline}, {platform_name} will require all learners to have complex passwords. Your current ' u'password does not meet these requirements. To reset your password, go to to ' u'{anchor_tag_open}Account Settings{anchor_tag_close}.' )).format( strong_tag_open=HTML('<strong>'), strong_tag_close=HTML('</strong>'), break_line_tag=HTML('<br/>'), platform_name=settings.PLATFORM_NAME, deadline=strftime_localized(deadline, DEFAULT_SHORT_DATE_FORMAT), anchor_tag_open=HTML(u'<a href="{account_settings_url}">').format( account_settings_url=settings.LMS_ROOT_URL + "/account/settings" ), anchor_tag_close=HTML('</a>') ) )
def test_non_live_course(self): """Ensure that a user accessing a non-live course sees a redirect to the student dashboard, not a 404. """ self.setup_user() self.enroll(self.course) url = reverse('info', args=[unicode(self.course.id)]) response = self.client.get(url) start_date = strftime_localized(self.course.start, 'SHORT_DATE') self.assertRedirects(response, '{0}?{1}'.format(reverse('dashboard'), urlencode({'notlive': start_date})))
def check_course_access(course, user, action, check_if_enrolled=False, check_survey_complete=True): """ Check that the user has the access to perform the specified action on the course (CourseDescriptor|CourseOverview). check_if_enrolled: If true, additionally verifies that the user is enrolled. check_survey_complete: If true, additionally verifies that the user has completed the survey. """ # Allow staff full access to the course even if not enrolled if has_access(user, 'staff', course.id): return request = get_current_request() check_content_start_date_for_masquerade_user(course.id, user, request, course.start) access_response = has_access(user, action, course, course.id) if not access_response: # Redirect if StartDateError if isinstance(access_response, StartDateError): start_date = strftime_localized(course.start, 'SHORT_DATE') params = QueryDict(mutable=True) params['notlive'] = start_date raise CourseAccessRedirect('{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode() ), access_response) # Redirect if AuditExpiredError if isinstance(access_response, AuditExpiredError): params = QueryDict(mutable=True) params['access_response_error'] = access_response.additional_context_user_message raise CourseAccessRedirect('{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode() ), access_response) # Redirect if the user must answer a survey before entering the course. if isinstance(access_response, MilestoneAccessError): raise CourseAccessRedirect('{dashboard_url}'.format( dashboard_url=reverse('dashboard'), ), access_response) # Deliberately return a non-specific error message to avoid # leaking info about access control settings raise CoursewareAccessException(access_response) if check_if_enrolled: # If the user is not enrolled, redirect them to the about page if not CourseEnrollment.is_enrolled(user, course.id): raise CourseAccessRedirect(reverse('about_course', args=[unicode(course.id)])) # Redirect if the user must answer a survey before entering the course. if check_survey_complete and action == 'load': if is_survey_required_and_unanswered(user, course): raise CourseAccessRedirect(reverse('course_survey', args=[unicode(course.id)]))
def get_expiration_banner_text(user, course, language='en'): """ Get text for banner that messages user course expiration date for different tests that depend on it. """ expiration_date = now() + timedelta(weeks=4) upgrade_link = verified_upgrade_deadline_link(user=user, course=course) enrollment = CourseEnrollment.get_enrollment(user, course.id) upgrade_deadline = enrollment.upgrade_deadline if upgrade_deadline is None or now() < upgrade_deadline: upgrade_deadline = enrollment.course_upgrade_deadline date_string = u'<span class="localized-datetime" data-format="shortDate" \ data-datetime="{formatted_date}" data-language="{language}">{formatted_date_localized}</span>' formatted_expiration_date = date_string.format( language=language, formatted_date=expiration_date.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR) ) if upgrade_deadline: formatted_upgrade_deadline = date_string.format( language=language, formatted_date=upgrade_deadline.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized(upgrade_deadline, EXPIRATION_DATE_FORMAT_STR) ) bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ <br>Upgrade by {upgrade_deadline} to get unlimited access to the course as long as it exists\ on the site. <a href="{upgrade_link}">Upgrade now<span class="sr-only"> to retain access past\ {expiration_date}</span></a>'.format( expiration_date=formatted_expiration_date, upgrade_link=upgrade_link, upgrade_deadline=formatted_upgrade_deadline ) else: bannerText = u'<strong>Audit Access Expires {expiration_date}</strong><br>\ You lose all access to this course, including your progress, on {expiration_date}.\ '.format( expiration_date=formatted_expiration_date ) return bannerText
def test_course_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """Verify that course enrollment status is reflected correctly.""" self.course.enrollment_start = timezone.now() - datetime.timedelta(days=start_offset) self.course.enrollment_end = timezone.now() - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member data = utils.ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'), )
def supplement_program_data(program_data, user): """Supplement program course codes with CourseOverview and CourseEnrollment data. Arguments: program_data (dict): Representation of a program. user (User): The user whose enrollments to inspect. """ for organization in program_data['organizations']: # TODO cache the results of the get_organization_by_short_name call # so we don't have to hit database that frequently org_obj = get_organization_by_short_name(organization['key']) if org_obj and org_obj.get('logo'): organization['img'] = org_obj['logo'].url for course_code in program_data['course_codes']: for run_mode in course_code['run_modes']: course_key = CourseKey.from_string(run_mode['course_key']) course_overview = CourseOverview.get_from_id(course_key) run_mode['course_url'] = reverse('course_root', args=[course_key]) run_mode['course_image_url'] = course_overview.course_image_url run_mode['start_date'] = course_overview.start_datetime_text() run_mode['end_date'] = course_overview.end_datetime_text() end_date = course_overview.end or datetime.datetime.max.replace(tzinfo=pytz.UTC) run_mode['is_course_ended'] = end_date < timezone.now() run_mode['is_enrolled'] = CourseEnrollment.is_enrolled(user, course_key) enrollment_start = course_overview.enrollment_start or datetime.datetime.min.replace(tzinfo=pytz.UTC) enrollment_end = course_overview.enrollment_end or datetime.datetime.max.replace(tzinfo=pytz.UTC) is_enrollment_open = enrollment_start <= timezone.now() < enrollment_end run_mode['is_enrollment_open'] = is_enrollment_open if not is_enrollment_open: # Only render this enrollment open date if the enrollment open is in the future run_mode['enrollment_open_date'] = strftime_localized(enrollment_start, 'SHORT_DATE') # TODO: Currently unavailable on LMS. run_mode['marketing_url'] = '' certificate_data = certificate_api.certificate_downloadable_status(user, course_key) certificate_uuid = certificate_data.get('uuid') if certificate_uuid: run_mode['certificate_url'] = certificate_api.get_certificate_url( course_id=course_key, uuid=certificate_uuid, ) return program_data
def test_no_enrollment_start_date(self): """Verify that a closed course with no explicit enrollment start date doesn't cause an error. Regression test for ECOM-4973. """ self.course.enrollment_end = timezone.now() - datetime.timedelta(days=1) self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member data = utils.supplement_program_data(self.program, self.user) self._assert_supplemented( data, is_enrollment_open=False, enrollment_open_date=strftime_localized(utils.DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), )
def to_dict(self): """ Convert entitlement to dictionary representation including relevant policy information. Returns: The entitlement UUID The associated course's UUID The date at which the entitlement expired. None if it is still active. The localized string representing the date at which the entitlement expires. """ expiration_date = None if self.get_days_until_expiration() < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD: expiration_date = strftime_localized( now() + timedelta(days=self.get_days_until_expiration()), 'SHORT_DATE' ) expired_at = strftime_localized(self.expired_at_datetime, 'SHORT_DATE') if self.expired_at_datetime else None return { 'uuid': str(self.uuid), 'course_uuid': str(self.course_uuid), 'expired_at': expired_at, 'expiration_date': expiration_date }
def strftime(self, *args, **kwargs): """ A locale-aware implementation of strftime. """ # This is the wrong place to import this function. I'm putting it here # because the xmodule test suite can't import this module, because # Django is not available in that suite. This function isn't called in # that suite, so this hides the import so the test won't fail. # # As I said, this is wrong. But Cale says this code will soon be # refactored to a place that will be right, and the code can be made # right there. If you are reading this comment after April 1, 2014, # then Cale was a liar. from util.date_utils import strftime_localized return strftime_localized(*args, **kwargs)
def test_non_live_course(self): """ Ensure that a user accessing a non-live course sees a redirect to the student dashboard, not a 404. """ future_course = self.create_future_course() self.create_user_for_course(future_course, CourseUserType.ENROLLED) url = course_home_url(future_course) response = self.client.get(url) start_date = strftime_localized(future_course.start, 'SHORT_DATE') expected_params = QueryDict(mutable=True) expected_params['notlive'] = start_date expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode()) self.assertRedirects(response, expected_url)
def test_non_live_course(self): """Ensure that a user accessing a non-live course sees a redirect to the student dashboard, not a 404. """ self.setup_user() self.enroll(self.course) url = reverse('info', args=[text_type(self.course.id)]) response = self.client.get(url) start_date = strftime_localized(self.course.start, 'SHORT_DATE') expected_params = QueryDict(mutable=True) expected_params['notlive'] = start_date expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode() ) self.assertRedirects(response, expected_url)
def test_non_live_course(self): """Ensure that a user accessing a non-live course sees a redirect to the student dashboard, not a 404. """ self.setup_user() self.enroll(self.course) url = reverse('info', args=[unicode(self.course.id)]) response = self.client.get(url) start_date = strftime_localized(self.course.start, 'SHORT_DATE') expected_params = QueryDict(mutable=True) expected_params['notlive'] = start_date expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode() ) self.assertRedirects(response, expected_url)
def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """ Verify that course run enrollment status is reflected correctly. """ self.course.enrollment_start = datetime.datetime.now(utc) - datetime.timedelta(days=start_offset) self.course.enrollment_end = datetime.datetime.now(utc) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized(self.course.enrollment_start, 'SHORT_DATE'), )
def enforce_compliance_on_login(user, password): """ Verify that the user's password is compliant with password policy rules and determine what should be done if it is not. Raises NonCompliantPasswordException when the password is found to be non-compliant and the compliance deadline for the user has been reached. In this case, login should be prevented. Raises NonCompliantPasswordWarning when the password is found to be non-compliant and the compliance deadline for the user is in the future. Returns None when the password is found to be compliant, or when no deadline for compliance has been set for the user. Important: This method should only be called AFTER the user has been authenticated. """ is_compliant = _check_user_compliance(user, password) if is_compliant: return deadline = _get_compliance_deadline_for_user(user) if deadline is None: return now = datetime.now(pytz.UTC) if now >= deadline: raise NonCompliantPasswordException( _capitalize_first(_( '{platform_name} now requires more complex passwords. Your current password does not meet the new ' 'requirements. Change your password now using the "Forgot password?" link below to continue using the ' 'site. Thank you for helping us keep your data safe.' ).format( platform_name=settings.PLATFORM_NAME )) ) else: raise NonCompliantPasswordWarning( _capitalize_first(_( '{platform_name} now requires more complex passwords. Your current password does not meet the new ' 'requirements. You must change your password by {deadline} to be able to continue using the site. ' 'To change your password, select the dropdown menu icon next to your username, then select "Account". ' 'You can reset your password from this page. Thank you for helping us keep your data safe.' ).format( platform_name=settings.PLATFORM_NAME, deadline=strftime_localized(deadline, DEFAULT_SHORT_DATE_FORMAT) )) )
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Arguments: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ no_data = [] user_orders = [] allowed_course_modes = ["professional", "verified", "credit"] commerce_configuration = CommerceConfiguration.current() user_query = {"username": user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + "." + str(user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data( commerce_configuration, user, "orders", api=api, querystring=user_query, cache_key=cache_key ) for order in commerce_user_orders: if order["status"].lower() == "complete": for line in order["lines"]: product = line.get("product") if product: for attribute in product["attribute_values"]: if attribute["name"] == "certificate_type" and attribute["value"] in allowed_course_modes: try: date_placed = datetime.strptime(order["date_placed"], "%Y-%m-%dT%H:%M:%SZ") order_data = { "number": order["number"], "price": order["total_excl_tax"], "title": order["lines"][0]["title"], "order_date": strftime_localized( date_placed.replace(tzinfo=pytz.UTC), "SHORT_DATE" ), "receipt_url": commerce_configuration.receipt_page + order["number"], } user_orders.append(order_data) except KeyError: log.exception("Invalid order structure: %r", order) return no_data return user_orders
class StrftimeLocalizedTest(unittest.TestCase): """ Tests for strftime_localized. """ @ddt.data( ("%Y", "2013"), ("%m/%d/%y", "02/14/13"), ("hello", "hello"), (u'%Y년 %m월 %d일', u"2013년 02월 14일"), ("%a, %b %d, %Y", "Thu, Feb 14, 2013"), ("%I:%M:%S %p", "04:41:17 PM"), ) def test_usual_strftime_behavior(self, (fmt, expected)): dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt)) # strftime doesn't like Unicode, so do the work in UTF8. self.assertEqual(expected, dtime.strftime(fmt.encode('utf8')).decode('utf8'))
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Arguments: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ no_data = [] user_orders = [] allowed_course_modes = ['professional', 'verified', 'credit'] commerce_configuration = CommerceConfiguration.current() user_query = {'username': user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + '.' + str(user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data( commerce_configuration, user, 'orders', api=api, querystring=user_query, cache_key=cache_key ) for order in commerce_user_orders: if order['status'].lower() == 'complete': for line in order['lines']: product = line.get('product') if product: for attribute in product['attribute_values']: if attribute['name'] == 'certificate_type' and attribute['value'] in allowed_course_modes: try: date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") order_data = { 'number': order['number'], 'price': order['total_excl_tax'], 'title': order['lines'][0]['title'], 'order_date': strftime_localized( date_placed.replace(tzinfo=pytz.UTC), 'SHORT_DATE' ), 'receipt_url': commerce_configuration.receipt_page + order['number'] } user_orders.append(order_data) except KeyError: log.exception('Invalid order structure: %r', order) return no_data return user_orders
def _get_asset_json(display_name, date, location, thumbnail_location, locked): """ Helper method for formatting the asset information to send to client. """ asset_url = StaticContent.get_url_path_from_location(location) external_url = settings.LMS_BASE + asset_url return { 'display_name': display_name, 'date_added': strftime_localized(date, "%m %d, %Y %H:%M"), 'url': asset_url, 'external_url': external_url, 'portable_url': StaticContent.get_static_path_from_location(location), 'thumbnail': StaticContent.get_url_path_from_location(thumbnail_location) if thumbnail_location is not None else None, 'locked': locked, # Needed for Backbone delete/update. 'id': asset_url }
def test_course_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """Verify that course enrollment status is reflected correctly.""" self.course.enrollment_start = timezone.now() - datetime.timedelta( days=start_offset) self.course.enrollment_end = timezone.now() - datetime.timedelta( days=end_offset) self.course = self.update_course(self.course, self.user.id) # pylint: disable=no-member data = utils.ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized( self.course.enrollment_start, 'SHORT_DATE'), )
def get_user_orders(user): """Given a user, get the detail of all the orders from the Ecommerce service. Args: user (User): The user to authenticate as when requesting ecommerce. Returns: list of dict, representing orders returned by the Ecommerce service. """ user_orders = [] commerce_configuration = CommerceConfiguration.current() user_query = {'username': user.username} use_cache = commerce_configuration.is_cache_enabled cache_key = commerce_configuration.CACHE_KEY + '.' + str( user.id) if use_cache else None api = ecommerce_api_client(user) commerce_user_orders = get_edx_api_data(commerce_configuration, 'orders', api=api, querystring=user_query, cache_key=cache_key) for order in commerce_user_orders: if order['status'].lower() == 'complete': date_placed = datetime.strptime(order['date_placed'], "%Y-%m-%dT%H:%M:%SZ") order_data = { 'number': order['number'], 'price': order['total_excl_tax'], 'order_date': strftime_localized(date_placed, 'SHORT_DATE'), 'receipt_url': '{url}&username={username}'.format( url=EcommerceService().get_receipt_page_url( order['number']), username=user.username), 'lines': order['lines'], } user_orders.append(order_data) return user_orders
def __init__(self, user, course, expiration_date): error_code = "audit_expired" developer_message = "User {} had access to {} until {}".format(user, course, expiration_date) expiration_date = strftime_localized(expiration_date, DEFAULT_SHORT_DATE_FORMAT) user_message = _("Access expired on {expiration_date}").format(expiration_date=expiration_date) try: course_name = CourseOverview.get_from_id(course.id).display_name_with_default additional_context_user_message = _("Access to {course_name} expired on {expiration_date}").format( course_name=course_name, expiration_date=expiration_date ) except CourseOverview.DoesNotExist: additional_context_user_message = _("Access to the course you were looking" "for expired on {expiration_date}").format( expiration_date=expiration_date ) super(AuditExpiredError, self).__init__(error_code, developer_message, user_message, additional_context_user_message)
def test_non_live_course(self): """ Ensure that a user accessing a non-live course sees a redirect to the student dashboard, not a 404. """ future_course = self.create_future_course() self.create_user_for_course(future_course, CourseUserType.ENROLLED) url = course_home_url(future_course) response = self.client.get(url) start_date = strftime_localized(future_course.start, 'SHORT_DATE') expected_params = QueryDict(mutable=True) expected_params['notlive'] = start_date expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode() ) self.assertRedirects(response, expected_url)
def __init__(self, user, course, expiration_date): error_code = "audit_expired" developer_message = u"User {} had access to {} until {}".format(user, course, expiration_date) expiration_date = strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR) user_message = _(u"Access expired on {expiration_date}").format(expiration_date=expiration_date) try: course_name = course.display_name_with_default additional_context_user_message = _(u"Access to {course_name} expired on {expiration_date}").format( course_name=course_name, expiration_date=expiration_date ) except CourseOverview.DoesNotExist: additional_context_user_message = _(u"Access to the course you were looking" u" for expired on {expiration_date}").format( expiration_date=expiration_date ) super(AuditExpiredError, self).__init__(error_code, developer_message, user_message, additional_context_user_message)
def __init__(self, user, course, expiration_date): error_code = "audit_expired" developer_message = u"User {} had access to {} until {}".format(user, course, expiration_date) language = get_language() expiration_date = strftime_localized(expiration_date, EXPIRATION_DATE_FORMAT_STR) user_message = _(u"Access expired on {expiration_date}").format(expiration_date=expiration_date) try: course_name = course.display_name_with_default additional_context_user_message = _(u"Access to {course_name} expired on {expiration_date}").format( course_name=course_name, expiration_date=expiration_date ) except CourseOverview.DoesNotExist: additional_context_user_message = _(u"Access to the course you were looking" u" for expired on {expiration_date}").format( expiration_date=expiration_date ) super(AuditExpiredError, self).__init__(error_code, developer_message, user_message, additional_context_user_message)
def check_course_access(course, user, action, check_if_enrolled=False, check_survey_complete=True): """ Check that the user has the access to perform the specified action on the course (CourseDescriptor|CourseOverview). check_if_enrolled: If true, additionally verifies that the user is enrolled. check_survey_complete: If true, additionally verifies that the user has completed the survey. """ # Allow staff full access to the course even if not enrolled if has_access(user, 'staff', course.id): return access_response = has_access(user, action, course, course.id) if not access_response: # Redirect if StartDateError if isinstance(access_response, StartDateError): start_date = strftime_localized(course.start, 'SHORT_DATE') params = QueryDict(mutable=True) params['notlive'] = start_date raise CourseAccessRedirect('{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode() ), access_response) # Redirect if the user must answer a survey before entering the course. if isinstance(access_response, MilestoneAccessError): raise CourseAccessRedirect('{dashboard_url}'.format( dashboard_url=reverse('dashboard'), ), access_response) # Deliberately return a non-specific error message to avoid # leaking info about access control settings raise CoursewareAccessException(access_response) if check_if_enrolled: # If the user is not enrolled, redirect them to the about page if not CourseEnrollment.is_enrolled(user, course.id): raise CourseAccessRedirect(reverse('about_course', args=[unicode(course.id)])) # Redirect if the user must answer a survey before entering the course. if check_survey_complete and action == 'load': if is_survey_required_and_unanswered(user, course): raise CourseAccessRedirect(reverse('course_survey', args=[unicode(course.id)]))
def test_course_run_enrollment_status(self, start_offset, end_offset, is_enrollment_open): """ Verify that course run enrollment status is reflected correctly. """ self.course.enrollment_start = datetime.datetime.now( utc) - datetime.timedelta(days=start_offset) self.course.enrollment_end = datetime.datetime.now( utc) - datetime.timedelta(days=end_offset) self.course = self.update_course(self.course, self.user.id) data = ProgramDataExtender(self.program, self.user).extend() self._assert_supplemented( data, is_enrollment_open=is_enrollment_open, enrollment_open_date=strftime_localized( self.course.enrollment_start, 'SHORT_DATE'), )
def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" course_overview = CourseOverview.get_from_id(self.course.id) # pylint: disable=no-member run_mode = dict( { 'certificate_url': None, 'course_image_url': course_overview.course_image_url, 'course_key': unicode(self.course.id), # pylint: disable=no-member 'course_url': reverse('course_root', args=[self.course.id]), # pylint: disable=no-member 'end_date': self.course.end.replace(tzinfo=utc), 'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), 'is_course_ended': self.course.end < datetime.datetime.now(utc), 'is_enrolled': False, 'is_enrollment_open': True, 'marketing_url': self.run_mode['marketing_url'], 'mode_slug': 'verified', 'start_date': self.course.start.replace(tzinfo=utc), 'upgrade_url': None, 'advertised_start': None, }, **kwargs) self.course_code['run_modes'] = [run_mode] self.program['course_codes'] = [self.course_code] self.assertEqual(actual, self.program)
def _assert_supplemented(self, actual, **kwargs): """DRY helper used to verify that program data is extended correctly.""" self.course_run.update( dict( { 'certificate_url': None, 'course_url': reverse('course_root', args=[self.course.id]), 'enrollment_open_date': strftime_localized(DEFAULT_ENROLLMENT_START_DATE, 'SHORT_DATE'), 'is_course_ended': self.course.end < datetime.datetime.now(utc), 'is_enrolled': False, 'is_enrollment_open': True, 'upgrade_url': None, 'advertised_start': None, }, **kwargs ) ) self.catalog_course['course_runs'] = [self.course_run] self.program['courses'] = [self.catalog_course] self.assertEqual(actual, self.program)
def __init__(self, user, course, expiration_date): error_code = "audit_expired" developer_message = "User {} had access to {} until {}".format( user, course, expiration_date) language = get_language() expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y') user_message = _("Access expired on {expiration_date}").format( expiration_date=expiration_date) try: course_name = CourseOverview.get_from_id( course.id).display_name_with_default additional_context_user_message = _( "Access to {course_name} expired on {expiration_date}").format( course_name=course_name, expiration_date=expiration_date) except CourseOverview.DoesNotExist: additional_context_user_message = _( "Access to the course you were looking" " for expired on {expiration_date}").format( expiration_date=expiration_date) super(AuditExpiredError, self).__init__(error_code, developer_message, user_message, additional_context_user_message)
def test_expired_course(self): """ Ensure that a user accessing an expired course sees a redirect to the student dashboard, not a 404. """ CourseDurationLimitConfig.objects.create(enabled=True, enabled_as_of=datetime( 2010, 1, 1)) course = CourseFactory.create(start=THREE_YEARS_AGO) url = course_home_url(course) for mode in [CourseMode.AUDIT, CourseMode.VERIFIED]: CourseModeFactory.create(course_id=course.id, mode_slug=mode) # assert that an if an expired audit user tries to access the course they are redirected to the dashboard audit_user = UserFactory(password=self.TEST_PASSWORD) self.client.login(username=audit_user.username, password=self.TEST_PASSWORD) audit_enrollment = CourseEnrollment.enroll(audit_user, course.id, mode=CourseMode.AUDIT) audit_enrollment.created = THREE_YEARS_AGO + timedelta(days=1) audit_enrollment.save() ScheduleFactory(enrollment=audit_enrollment) response = self.client.get(url) expiration_date = strftime_localized( course.start + timedelta(weeks=4) + timedelta(days=1), u'%b %-d, %Y') expected_params = QueryDict(mutable=True) course_name = CourseOverview.get_from_id( course.id).display_name_with_default expected_params[ 'access_response_error'] = u'Access to {run} expired on {expiration_date}'.format( run=course_name, expiration_date=expiration_date) expected_url = '{url}?{params}'.format( url=reverse('dashboard'), params=expected_params.urlencode()) self.assertRedirects(response, expected_url)
def get_context_data(self, **kwargs): data = super(UniversityIDView, self).get_context_data(**kwargs) registration_end_date = None terms_and_conditions = None university_settings = get_university_settings(self.get_course_key()) if university_settings: terms_and_conditions = university_settings.terms_and_conditions final_date = university_settings.registration_end_date date = strftime_localized(final_date, format='SHORT_DATE') registration_end_date = date.replace('"', '') if settings.FEATURES.get('ENABLE_MKTG_SITE'): url_to_enroll = marketing_link('COURSES') else: url_to_enroll = reverse('about_course', args=[self.get_course_key()]) data.update({ 'form': self.get_form(), 'has_valid_information': has_valid_university_id(self.request.user, unicode(self.get_course_key())), 'is_form_disabled': is_student_form_disabled(self.request.user, self.get_course_key()), 'show_enroll_banner': self.show_enroll_banner(), 'terms_conditions': terms_and_conditions, 'registration_end': registration_end_date, 'url_to_enroll': url_to_enroll, }) return data
def get_context_data(self, **kwargs): data = super(UniversityIDView, self).get_context_data(**kwargs) mktg_enabled = settings.FEATURES.get('ENABLE_MKTG_SITE') university_settings = self.get_university_settings() final_date = university_settings.registration_end_date if university_settings else None if final_date: date = strftime_localized(final_date, format="SHORT_DATE") registration_end_date = date.replace('"', '') else: registration_end_date = None data.update({ 'form': self.get_form(), 'has_valid_information': has_valid_university_id(self.request.user, self.kwargs['course_id']), 'is_form_disabled': self.is_form_disabled(), 'terms_conditions': university_settings.terms_and_conditions if university_settings else None, 'show_enroll_banner': self.is_not_enrolled(), 'registration_end': registration_end_date, 'url_to_enroll': marketing_link('COURSES') if mktg_enabled else reverse(course_about,args=[self.get_course_key()]), }) return data
def test_usual_strftime_behavior(self, (fmt, expected)): dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt)) # strftime doesn't like Unicode, so do the work in UTF8. self.assertEqual(expected, dtime.strftime(fmt.encode('utf8')).decode('utf8')) @ddt.data( ("SHORT_DATE", "Feb 14, 2013"), ("LONG_DATE", "Thursday, February 14, 2013"), ("TIME", "04:41:17 PM"), ("DAY_AND_TIME", "Thursday at 4pm"), ("%x %X!", "Feb 14, 2013 04:41:17 PM!"), ) def test_shortcuts(self, (fmt, expected)): dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt)) @patch('util.date_utils.pgettext', fake_pgettext(translations={ ("abbreviated month name", "Feb"): "XXfebXX", ("month name", "February"): "XXfebruaryXX", ("abbreviated weekday name", "Thu"): "XXthuXX", ("weekday name", "Thursday"): "XXthursdayXX", ("am/pm indicator", "PM"): "XXpmXX", })) @ddt.data( ("SHORT_DATE", "XXfebXX 14, 2013"), ("LONG_DATE", "XXthursdayXX, XXfebruaryXX 14, 2013"), ("DATE_TIME", "XXfebXX 14, 2013 at 16:41"), ("TIME", "04:41:17 XXpmXX"), ("%x %X!", "XXfebXX 14, 2013 04:41:17 XXpmXX!"), )
def generate_course_expired_message(user, course): """ Generate the message for the user course expiration date if it exists. """ if not CourseDurationLimitConfig.enabled_for_enrollment( user=user, course_key=course.id): return expiration_date = get_user_course_expiration_date(user, course) if not expiration_date: return if is_masquerading_as_specific_student( user, course.id) and timezone.now() > expiration_date: upgrade_message = _( 'This learner does not have access to this course. ' u'Their access expired on {expiration_date}.') return HTML(upgrade_message).format(expiration_date=strftime_localized( expiration_date, EXPIRATION_DATE_FORMAT_STR)) else: enrollment = CourseEnrollment.get_enrollment(user, course.id) if enrollment is None: return upgrade_deadline = enrollment.upgrade_deadline now = timezone.now() course_upgrade_deadline = enrollment.course_upgrade_deadline if (not upgrade_deadline) or (upgrade_deadline < now): upgrade_deadline = course_upgrade_deadline expiration_message = _( u'{strong_open}Audit Access Expires {expiration_date}{strong_close}' u'{line_break}You lose all access to this course, including your progress, on ' u'{expiration_date}.') upgrade_deadline_message = _( u'{line_break}Upgrade by {upgrade_deadline} to get unlimited access to the course ' u'as long as it exists on the site. {a_open}Upgrade now{sronly_span_open} to ' u'retain access past {expiration_date}{span_close}{a_close}') full_message = expiration_message if upgrade_deadline and now < upgrade_deadline: full_message += upgrade_deadline_message using_upgrade_messaging = True else: using_upgrade_messaging = False language = get_language() date_string = get_date_string() formatted_expiration_date = date_string.format( language=language, formatted_date=expiration_date.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized( expiration_date, EXPIRATION_DATE_FORMAT_STR)) if using_upgrade_messaging: formatted_upgrade_deadline = date_string.format( language=language, formatted_date=upgrade_deadline.strftime("%Y-%m-%d"), formatted_date_localized=strftime_localized( upgrade_deadline, EXPIRATION_DATE_FORMAT_STR)) return HTML(full_message).format( a_open=HTML(u'<a href="{upgrade_link}">').format( upgrade_link=verified_upgrade_deadline_link( user=user, course=course)), sronly_span_open=HTML('<span class="sr-only">'), span_close=HTML('</span>'), a_close=HTML('</a>'), expiration_date=HTML(formatted_expiration_date), strong_open=HTML('<strong>'), strong_close=HTML('</strong>'), line_break=HTML('<br>'), upgrade_deadline=HTML(formatted_upgrade_deadline)) else: return HTML(full_message).format( span_close=HTML('</span>'), expiration_date=HTML(formatted_expiration_date), strong_open=HTML('<strong>'), strong_close=HTML('</strong>'), line_break=HTML('<br>'), )
def _attach_course_run_enrollment_open_date(self, run_mode): run_mode['enrollment_open_date'] = strftime_localized( self.enrollment_start, 'SHORT_DATE')
def render_body(context, **pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(pageargs=pageargs) enterprise_customer_name = context.get('enterprise_customer_name', UNDEFINED) enrollment_message = context.get('enrollment_message', UNDEFINED) display_sidebar_account_activation_message = context.get( 'display_sidebar_account_activation_message', UNDEFINED) block_courses = context.get('block_courses', UNDEFINED) course_enrollments = context.get('course_enrollments', UNDEFINED) banner_account_activation_message = context.get( 'banner_account_activation_message', UNDEFINED) static = _mako_get_namespace(context, 'static') unicode = context.get('unicode', UNDEFINED) resume_button_urls = context.get('resume_button_urls', UNDEFINED) isinstance = context.get('isinstance', UNDEFINED) redirect_message = context.get('redirect_message', UNDEFINED) show_email_settings_for = context.get('show_email_settings_for', UNDEFINED) empty_dashboard_message = context.get('empty_dashboard_message', UNDEFINED) unfulfilled_entitlement_pseudo_sessions = context.get( 'unfulfilled_entitlement_pseudo_sessions', UNDEFINED) errored_courses = context.get('errored_courses', UNDEFINED) def js_extra(): return render_js_extra(context._locals(__M_locals)) credit_statuses = context.get('credit_statuses', UNDEFINED) display_course_modes_on_dashboard = context.get( 'display_course_modes_on_dashboard', UNDEFINED) account_activation_messages = context.get( 'account_activation_messages', UNDEFINED) staff_access = context.get('staff_access', UNDEFINED) marketing_link = context.get('marketing_link', UNDEFINED) display_dashboard_courses = context.get('display_dashboard_courses', UNDEFINED) getattr = context.get('getattr', UNDEFINED) course_entitlement_available_sessions = context.get( 'course_entitlement_available_sessions', UNDEFINED) len = context.get('len', UNDEFINED) enumerate = context.get('enumerate', UNDEFINED) consent_required_courses = context.get('consent_required_courses', UNDEFINED) all_course_modes = context.get('all_course_modes', UNDEFINED) user = context.get('user', UNDEFINED) activate_account_message = context.get('activate_account_message', UNDEFINED) courses_requirements_not_met = context.get( 'courses_requirements_not_met', UNDEFINED) order_history_list = context.get('order_history_list', UNDEFINED) def bodyclass(): return render_bodyclass(context._locals(__M_locals)) display_sidebar_on_dashboard = context.get( 'display_sidebar_on_dashboard', UNDEFINED) cert_statuses = context.get('cert_statuses', UNDEFINED) unfulfilled_entitlement = context.get('unfulfilled_entitlement', UNDEFINED) settings = context.get('settings', UNDEFINED) enterprise_message = context.get('enterprise_message', UNDEFINED) show_courseware_links_for = context.get('show_courseware_links_for', UNDEFINED) str = context.get('str', UNDEFINED) verification_status_by_course = context.get( 'verification_status_by_course', UNDEFINED) course_entitlements = context.get('course_entitlements', UNDEFINED) def header_extras(): return render_header_extras(context._locals(__M_locals)) inverted_programs = context.get('inverted_programs', UNDEFINED) def pagetitle(): return render_pagetitle(context._locals(__M_locals)) enrolled_courses_either_paid = context.get( 'enrolled_courses_either_paid', UNDEFINED) __M_writer = context.writer() __M_writer(u'\n') __M_writer(u'\n') __M_writer(u'\n') __M_writer(u'\n') __M_writer(u'\n\n') cert_name_short = settings.CERT_NAME_SHORT cert_name_long = settings.CERT_NAME_LONG __M_locals_builtin_stored = __M_locals_builtin() __M_locals.update( __M_dict_builtin([ (__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['cert_name_long', 'cert_name_short'] if __M_key in __M_locals_builtin_stored ])) __M_writer(u'\n\n\n') if 'parent' not in context._data or not hasattr( context._data['parent'], 'pagetitle'): context['self'].pagetitle(**pageargs) __M_writer(u'\n') if 'parent' not in context._data or not hasattr( context._data['parent'], 'bodyclass'): context['self'].bodyclass(**pageargs) __M_writer(u'\n\n') if 'parent' not in context._data or not hasattr( context._data['parent'], 'header_extras'): context['self'].header_extras(**pageargs) __M_writer(u'\n\n') if 'parent' not in context._data or not hasattr( context._data['parent'], 'js_extra'): context['self'].js_extra(**pageargs) __M_writer( u'\n\n<div class="dashboard-notifications" tabindex="-1">\n\n') if banner_account_activation_message: __M_writer(u' <div class="dashboard-banner">\n ') __M_writer(filters.decode.utf8(banner_account_activation_message)) __M_writer(u'\n </div>\n') __M_writer(u'\n') if enrollment_message: __M_writer(u' <div class="dashboard-banner">\n ') __M_writer(filters.decode.utf8(enrollment_message)) __M_writer(u'\n </div>\n') __M_writer(u'\n') if enterprise_message: __M_writer(u' <div class="dashboard-banner">\n ') __M_writer(filters.decode.utf8(enterprise_message)) __M_writer(u'\n </div>\n') __M_writer(u'\n') if account_activation_messages: __M_writer(u' <div class="activation-message-container">\n') for account_activation_message in account_activation_messages: __M_writer(u' <div class="account-activation ') __M_writer( filters.html_escape( filters.decode.utf8(account_activation_message.tags))) __M_writer( u'" role="alert" aria-label="Account Activation Message" tabindex="-1">\n <div class="message-copy" >\n ' ) __M_writer(filters.decode.utf8(account_activation_message)) __M_writer(u'\n </div>\n </div>\n') __M_writer(u' </div>\n') __M_writer( u'\n</div>\n\n<main id="main" aria-label="Content" tabindex="-1">\n <div class="dashboard holder" id="dashboard-main">\n <div class="main-container">\n <div class="my-courses" id="my-courses">\n' ) if display_dashboard_courses: __M_writer(u' ') runtime._include_file( context, u'learner_dashboard/_dashboard_navigation_courses.html', _template_uri) __M_writer(u'\n') __M_writer(u'\n') if len(course_entitlements + course_enrollments) > 0: __M_writer( u' <ul class="listing-courses">\n ') share_settings = configuration_helpers.get_value( 'SOCIAL_SHARING_SETTINGS', getattr(settings, 'SOCIAL_SHARING_SETTINGS', {})) __M_locals_builtin_stored = __M_locals_builtin() __M_locals.update( __M_dict_builtin([(__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in ['share_settings'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n') for dashboard_index, enrollment in enumerate(course_entitlements + course_enrollments): __M_writer(u' ') # Check if the course run is an entitlement and if it has an associated session entitlement = enrollment if isinstance( enrollment, CourseEntitlement) else None entitlement_session = entitlement.enrollment_course_run if entitlement else None entitlement_days_until_expiration = entitlement.get_days_until_expiration( ) if entitlement else None entitlement_expiration = datetime.now(tz=pytz.UTC) + timedelta( days=entitlement_days_until_expiration) if ( entitlement and entitlement_days_until_expiration < settings.ENTITLEMENT_EXPIRED_ALERT_PERIOD) else None entitlement_expiration_date = strftime_localized( entitlement_expiration, 'SHORT_DATE' ) if entitlement and entitlement_expiration else None entitlement_expired_at = strftime_localized( entitlement.expired_at_datetime, 'SHORT_DATE' ) if entitlement and entitlement.expired_at_datetime else None is_fulfilled_entitlement = True if entitlement and entitlement_session else False is_unfulfilled_entitlement = True if entitlement and not entitlement_session else False entitlement_available_sessions = [] if entitlement: # Grab the available, enrollable sessions for a given entitlement and scrape them for relevant attributes entitlement_available_sessions = [{ 'session_id': course['key'], 'enrollment_end': course['enrollment_end'], 'pacing_type': course['pacing_type'], 'advertised_start': CourseOverview.get_from_id( CourseKey.from_string( course['key'])).advertised_start, 'start': CourseOverview.get_from_id( CourseKey.from_string(course['key'])).start, 'end': CourseOverview.get_from_id( CourseKey.from_string(course['key'])).end, } for course in course_entitlement_available_sessions[str( entitlement.uuid)]] if is_fulfilled_entitlement: # If the user has a fulfilled entitlement, pass through the entitlements CourseEnrollment object enrollment = entitlement_session else: # If the user has an unfulfilled entitlement, pass through a bare CourseEnrollment object to populate card with metadata pseudo_session = unfulfilled_entitlement_pseudo_sessions[ str(entitlement.uuid)] if not pseudo_session: continue enrollment = CourseEnrollment( user=user, course_id=pseudo_session['key'], mode=pseudo_session['type']) # We only show email settings for entitlement cards if the entitlement has an associated enrollment show_email_settings = is_fulfilled_entitlement and ( entitlement_session.course_id in show_email_settings_for) else: show_email_settings = (enrollment.course_id in show_email_settings_for) session_id = enrollment.course_id show_courseware_link = (session_id in show_courseware_links_for) cert_status = cert_statuses.get(session_id) can_refund_entitlement = entitlement and entitlement.is_entitlement_refundable( ) can_unenroll = (not cert_status) or cert_status.get( 'can_unenroll') if not unfulfilled_entitlement else False credit_status = credit_statuses.get(session_id) course_mode_info = all_course_modes.get(session_id) is_paid_course = True if entitlement else ( session_id in enrolled_courses_either_paid) is_course_blocked = (session_id in block_courses) course_verification_status = verification_status_by_course.get( session_id, {}) course_requirements = courses_requirements_not_met.get( session_id) related_programs = inverted_programs.get( unicode(entitlement.course_uuid if is_unfulfilled_entitlement else session_id)) show_consent_link = (session_id in consent_required_courses) course_overview = enrollment.course_overview resume_button_url = resume_button_urls[dashboard_index] __M_locals_builtin_stored = __M_locals_builtin() __M_locals.update( __M_dict_builtin([ (__M_key, __M_locals_builtin_stored[__M_key]) for __M_key in [ 'course_overview', 'is_course_blocked', 'course', 'is_unfulfilled_entitlement', 'entitlement_session', 'can_unenroll', 'pseudo_session', 'entitlement', 'related_programs', 'credit_status', 'is_paid_course', 'course_verification_status', 'can_refund_entitlement', 'resume_button_url', 'show_courseware_link', 'course_requirements', 'entitlement_days_until_expiration', 'entitlement_available_sessions', 'cert_status', 'show_email_settings', 'is_fulfilled_entitlement', 'show_consent_link', 'entitlement_expiration', 'course_mode_info', 'entitlement_expired_at', 'session_id', 'entitlement_expiration_date', 'enrollment' ] if __M_key in __M_locals_builtin_stored ])) __M_writer(u'\n ') runtime._include_file( context, u'dashboard/_dashboard_course_listing.html', _template_uri, course_overview=course_overview, course_card_index=dashboard_index, enrollment=enrollment, is_unfulfilled_entitlement=is_unfulfilled_entitlement, is_fulfilled_entitlement=is_fulfilled_entitlement, entitlement=entitlement, entitlement_session=entitlement_session, entitlement_available_sessions= entitlement_available_sessions, entitlement_expiration_date=entitlement_expiration_date, entitlement_expired_at=entitlement_expired_at, show_courseware_link=show_courseware_link, cert_status=cert_status, can_refund_entitlement=can_refund_entitlement, can_unenroll=can_unenroll, credit_status=credit_status, show_email_settings=show_email_settings, course_mode_info=course_mode_info, 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, related_programs=related_programs, display_course_modes_on_dashboard= display_course_modes_on_dashboard, show_consent_link=show_consent_link, enterprise_customer_name=enterprise_customer_name, resume_button_url=resume_button_url) __M_writer(u'\n') __M_writer(u'\n </ul>\n') else: __M_writer(u' <div class="empty-dashboard-message">\n') if display_dashboard_courses: __M_writer(u' <p>') __M_writer( filters.html_escape( filters.decode.utf8( _("You are not enrolled in any courses yet.")))) __M_writer(u'</p>\n') if empty_dashboard_message: __M_writer(u' <p class="custom-message">') __M_writer(filters.decode.utf8(empty_dashboard_message)) __M_writer(u'</p>\n') if settings.FEATURES.get('COURSES_ARE_BROWSABLE'): __M_writer( u' <a class="btn btn-primary" href="') __M_writer( filters.html_escape( filters.decode.utf8(marketing_link('COURSES')))) __M_writer(u'">\n ') __M_writer( filters.html_escape( filters.decode.utf8(_("Explore courses")))) __M_writer(u'\n </a>\n') else: __M_writer(u' <p>') __M_writer( filters.html_escape( filters.decode.utf8(_("Activate your account!")))) __M_writer(u'</p>\n <p class="custom-message">') __M_writer(filters.decode.utf8(activate_account_message)) __M_writer(u'</p>\n') __M_writer(u' </div>\n') __M_writer(u'\n') if staff_access and len(errored_courses) > 0: __M_writer( u' <div id="course-errors">\n <h2>') __M_writer( filters.html_escape( filters.decode.utf8(_("Course-loading errors")))) __M_writer(u'</h2>\n\n') for course_dir, errors in errored_courses.items(): __M_writer(u' <h3>') __M_writer(filters.html_escape( filters.decode.utf8(course_dir))) __M_writer(u'</h3>\n <ul>\n') for (msg, err) in errors: __M_writer(u' <li>') __M_writer(filters.html_escape(filters.decode.utf8(msg))) __M_writer(u'\n <ul><li><pre>') __M_writer(filters.html_escape(filters.decode.utf8(err))) __M_writer( u'</pre></li></ul>\n </li>\n') __M_writer(u' </ul>\n') __M_writer(u' </div>\n') __M_writer( u' </div>\n </div>\n <div class="side-container">\n' ) if display_sidebar_account_activation_message: __M_writer( u' <div class="sidebar-notification">\n ') runtime._include_file(context, (static.get_template_path( 'registration/account_activation_sidebar_notice.html')), _template_uri) __M_writer(u'\n </div>\n') __M_writer(u'\n') if settings.FEATURES.get('ENABLE_DASHBOARD_SEARCH'): __M_writer( u' <div id="dashboard-search-bar" class="search-bar dashboard-search-bar" role="search" aria-label="Dashboard">\n <form class="search-form">\n <label for="dashboard-search-input">' ) __M_writer( filters.html_escape( filters.decode.utf8(_("Search Your Courses")))) __M_writer( u'</label>\n <div class="search-field-wrapper">\n <input id="dashboard-search-input" type="text" class="search-field"/>\n <button type="submit" class="search-button" title="' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Search')))) __M_writer( u'">\n <span class="icon fa fa-search" aria-hidden="true"></span>\n </button>\n <button type="button" class="cancel-button" title="' ) __M_writer( filters.html_escape(filters.decode.utf8(_('Clear search')))) __M_writer( u'">\n <span class="icon fa fa-remove" aria-hidden="true"></span>\n </button>\n </div>\n </form>\n </div>\n <div id="dashboard-search-results" class="search-results dashboard-search-results"></div>\n' ) __M_writer(u'\n') if display_sidebar_on_dashboard: __M_writer( u' <div class="profile-sidebar" id="profile-sidebar" role="region" aria-label="Account Status Info">\n <header class="profile">\n <h2 class="account-status-title sr">' ) __M_writer( filters.html_escape( filters.decode.utf8(_("Account Status Info")))) __M_writer( u': </h2>\n </header>\n <div class="user-info">\n <ul>\n\n' ) if len(order_history_list): __M_writer( u' <li class="order-history">\n <span class="title">' ) __M_writer( filters.html_escape(filters.decode.utf8( _("Order History")))) __M_writer(u'</span>\n') for order_history_item in order_history_list: __M_writer(u' <span><a href="') __M_writer( filters.html_escape( filters.decode.utf8( order_history_item['receipt_url']))) __M_writer(u'" target="_blank" class="edit-name">') __M_writer( filters.html_escape( filters.decode.utf8( order_history_item['order_date']))) __M_writer(u'</a></span>\n') __M_writer(u' </li>\n') __M_writer(u'\n ') runtime._include_file(context, (static.get_template_path( 'dashboard/_dashboard_status_verification.html')), _template_uri) __M_writer( u'\n\n </ul>\n </div>\n </div>\n' ) __M_writer( u' </div>\n </div>\n</main>\n\n<div id="email-settings-modal" class="modal" aria-hidden="true">\n <div class="inner-wrapper" role="dialog" aria-labelledby="email-settings-title">\n <button class="close-modal">\n <span class="icon fa fa-remove" aria-hidden="true"></span>\n <span class="sr">\n' ) __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Close")))) __M_writer( u'\n </span>\n </button>\n\n <header>\n <h2 id="email-settings-title">\n ' ) __M_writer( filters.html_escape( filters.decode.utf8( Text(_("Email Settings for {course_number}")). format(course_number=HTML( '<span id="email_settings_course_number"></span>'))))) __M_writer(u'\n <span class="sr">,\n') __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("window open")))) __M_writer( u'\n </span>\n </h2>\n <hr/>\n </header>\n\n <form id="email_settings_form" method="post">\n <input name="course_id" id="email_settings_course_id" type="hidden" />\n <label><input type="checkbox" id="receive_emails" name="receive_emails" />' ) __M_writer( filters.html_escape(filters.decode.utf8( _("Receive course emails")))) __M_writer( u' </label>\n <div class="submit">\n <input type="submit" id="submit" value="' ) __M_writer(filters.html_escape(filters.decode.utf8( _('Save Settings')))) __M_writer( u'" />\n </div>\n </form>\n </div>\n</div>\n\n<div id="unenroll-modal" class="modal unenroll-modal" aria-hidden="true">\n <div class="inner-wrapper" role="dialog" aria-labelledby="unenrollment-modal-title" aria-live="polite">\n <button class="close-modal">\n <span class="icon fa fa-remove" aria-hidden="true"></span>\n <span class="sr">\n' ) __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Close")))) __M_writer( u'\n </span>\n </button>\n\n <header class="unenroll-header">\n <h2 id="unenrollment-modal-title">\n <span id=\'track-info\'></span>\n <span id=\'refund-info\'></span>\n <span class="sr">,\n' ) __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("window open")))) __M_writer( u'\n </span>\n </h2>\n <hr/>\n </header>\n <div id="unenroll_error" class="modal-form-error"></div>\n <form id="unenroll_form" method="post" data-remote="true" action="' ) __M_writer( filters.html_escape( filters.decode.utf8(reverse('change_enrollment')))) __M_writer( u'">\n <input name="course_id" id="unenroll_course_id" type="hidden" />\n <input name="enrollment_action" type="hidden" value="unenroll" />\n <div class="submit">\n <input class="submit-button" name="submit" type="submit" value="' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Unenroll')))) __M_writer(u'" />\n </div>\n </form>\n </div>\n</div>\n\n') runtime._include_file( context, u'dashboard/_dashboard_entitlement_unenrollment_modal.html', _template_uri) __M_writer(u'\n') return '' finally: context.caller_stack._pop_frame()
def format_date(date): return strftime_localized(date, u'%b %-d, %Y')
def register_course_expired_message(request, course): """ Add a banner notifying the user of the user course expiration date if it exists. """ if not CourseDurationLimitConfig.enabled_for_enrollment(user=request.user, course_key=course.id): return expiration_date = get_user_course_expiration_date(request.user, course) if not expiration_date: return if is_masquerading_as_student(request.user, course.id) and timezone.now() > expiration_date: upgrade_message = _('This learner does not have access to this course. ' 'Their access expired on {expiration_date}.') PageLevelMessages.register_warning_message( request, HTML(upgrade_message).format( expiration_date=strftime_localized(expiration_date, '%b. %-d, %Y') ) ) else: enrollment = CourseEnrollment.get_enrollment(request.user, course.id) if enrollment is None: return upgrade_deadline = enrollment.upgrade_deadline now = timezone.now() course_upgrade_deadline = enrollment.course_upgrade_deadline if upgrade_deadline and now > upgrade_deadline: upgrade_deadline = course_upgrade_deadline expiration_message = _('{strong_open}Audit Access Expires {expiration_date}{strong_close}' '{line_break}You lose all access to this course, including your progress, on ' '{expiration_date}.') upgrade_deadline_message = _('{line_break}Upgrade by {upgrade_deadline} to get unlimited access to the course ' 'as long as it exists on the site. {a_open}Upgrade now{sronly_span_open} to ' 'retain access past {expiration_date}{span_close}{a_close}') full_message = expiration_message if course_upgrade_deadline and now < course_upgrade_deadline: full_message += upgrade_deadline_message language = get_language() language_is_es = language and language.split('-')[0].lower() == 'es' if language_is_es: formatted_expiration_date = strftime_localized(expiration_date, '%-d de %b. de %Y').lower() else: formatted_expiration_date = strftime_localized(expiration_date, '%b. %-d, %Y') if upgrade_deadline: if language_is_es: formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%-d de %b. de %Y').lower() else: formatted_upgrade_deadline = strftime_localized(upgrade_deadline, '%b. %-d, %Y') if upgrade_deadline: PageLevelMessages.register_info_message( request, Text(full_message).format( a_open=HTML('<a href="{upgrade_link}">').format( upgrade_link=verified_upgrade_deadline_link(user=request.user, course=course) ), sronly_span_open=HTML('<span class="sr-only">'), span_close=HTML('</span>'), a_close=HTML('</a>'), expiration_date=formatted_expiration_date, strong_open=HTML('<strong>'), strong_close=HTML('</strong>'), line_break=HTML('<br>'), upgrade_deadline=formatted_upgrade_deadline ) ) else: PageLevelMessages.register_info_message( request, Text(full_message).format( span_close=HTML('</span>'), expiration_date=formatted_expiration_date, strong_open=HTML('<strong>'), strong_close=HTML('</strong>'), line_break=HTML('<br>'), ) )
def test_translated_formats(self, fmt_expected): (fmt, expected) = fmt_expected dtime = datetime(2013, 2, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt))
def test_invalid_format_strings(self, fmt): dtime = datetime(2013, 02, 14, 16, 41, 17) with self.assertRaises(ValueError): strftime_localized(dtime, fmt)
def test_shortcuts(self, fmt_expected): (fmt, expected) = fmt_expected dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt))
def strftime(self, *args, **kwargs): return strftime_localized(*args, **kwargs)
def test_recursion_protection(self, fmt_expected): (fmt, expected) = fmt_expected dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt))
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')
def _attach_run_mode_enrollment_open_date(self, run_mode): run_mode['enrollment_open_date'] = strftime_localized(self.enrollment_start, 'SHORT_DATE')
dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt)) # strftime doesn't like Unicode, so do the work in UTF8. self.assertEqual(expected, dtime.strftime(fmt.encode('utf8')).decode('utf8')) @ddt.data( ("SHORT_DATE", "Feb 14, 2013"), ("LONG_DATE", "Thursday, February 14, 2013"), ("TIME", "04:41:17 PM"), ("DAY_AND_TIME", "Thursday at 4pm"), ("%x %X!", "Feb 14, 2013 04:41:17 PM!"), ) def test_shortcuts(self, (fmt, expected)): dtime = datetime(2013, 02, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt)) @patch('util.date_utils.pgettext', fake_pgettext( translations={ ("abbreviated month name", "Feb"): "XXfebXX", ("month name", "February"): "XXfebruaryXX", ("abbreviated weekday name", "Thu"): "XXthuXX", ("weekday name", "Thursday"): "XXthursdayXX", ("am/pm indicator", "PM"): "XXpmXX", })) @ddt.data( ("SHORT_DATE", "XXfebXX 14, 2013"), ("LONG_DATE", "XXthursdayXX, XXfebruaryXX 14, 2013"), ("DATE_TIME", "XXfebXX 14, 2013 at 16:41"), ("TIME", "04:41:17 XXpmXX"),
def check_course_access_with_redirect(course, user, action, check_if_enrolled=False, check_survey_complete=True, check_if_authenticated=False): """ Check that the user has the access to perform the specified action on the course (CourseDescriptor|CourseOverview). check_if_enrolled: If true, additionally verifies that the user is enrolled. check_survey_complete: If true, additionally verifies that the user has completed the survey. """ request = get_current_request() check_content_start_date_for_masquerade_user(course.id, user, request, course.start) access_response = check_course_access(course, user, action, check_if_enrolled, check_survey_complete, check_if_authenticated) if not access_response: # Redirect if StartDateError if isinstance(access_response, StartDateError): start_date = strftime_localized(course.start, 'SHORT_DATE') params = QueryDict(mutable=True) params['notlive'] = start_date raise CourseAccessRedirect( '{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode()), access_response) # Redirect if AuditExpiredError if isinstance(access_response, AuditExpiredError): params = QueryDict(mutable=True) params[ 'access_response_error'] = access_response.additional_context_user_message raise CourseAccessRedirect( '{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode()), access_response) # Redirect if the user must answer a survey before entering the course. if isinstance(access_response, MilestoneAccessError): raise CourseAccessRedirect( '{dashboard_url}'.format(dashboard_url=reverse('dashboard'), ), access_response) # Redirect if the user is not enrolled and must be to see content if isinstance(access_response, EnrollmentRequiredAccessError): raise CourseAccessRedirect( reverse('about_course', args=[str(course.id)])) # Redirect if user must be authenticated to view the content if isinstance(access_response, AuthenticationRequiredAccessError): raise CourseAccessRedirect( reverse('about_course', args=[str(course.id)])) # Redirect if the user must answer a survey before entering the course. if isinstance(access_response, SurveyRequiredAccessError): raise CourseAccessRedirect( reverse('course_survey', args=[str(course.id)])) # Deliberately return a non-specific error message to avoid # leaking info about access control settings raise CoursewareAccessException(access_response)
def test_recursion_protection(self, fmt_expected): (fmt, expected) = fmt_expected dtime = datetime(2013, 2, 14, 16, 41, 17) self.assertEqual(expected, strftime_localized(dtime, fmt))