def render_body(context, **pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(pageargs=pageargs) submit_button_submitting = context.get('submit_button_submitting', UNDEFINED) save_message = context.get('save_message', UNDEFINED) short_id = context.get('short_id', UNDEFINED) answer_notification_message = context.get( 'answer_notification_message', UNDEFINED) should_enable_next_hint = context.get('should_enable_next_hint', UNDEFINED) id = context.get('id', UNDEFINED) reset_button = context.get('reset_button', UNDEFINED) has_saved_answers = context.get('has_saved_answers', UNDEFINED) demand_hint_possible = context.get('demand_hint_possible', UNDEFINED) should_enable_submit_button = context.get( 'should_enable_submit_button', UNDEFINED) submit_button = context.get('submit_button', UNDEFINED) attempts_allowed = context.get('attempts_allowed', UNDEFINED) save_button = context.get('save_button', UNDEFINED) problem = context.get('problem', UNDEFINED) attempts_used = context.get('attempts_used', UNDEFINED) answer_available = context.get('answer_available', UNDEFINED) answer_notification_type = context.get('answer_notification_type', UNDEFINED) __M_writer = context.writer() __M_writer(u'\n') __M_writer(u'\n\n') __M_writer(u'\n<h3 class="hd hd-3 problem-header" id="') __M_writer(filters.html_escape(filters.decode.utf8(short_id))) __M_writer(u'-problem-title" aria-describedby="') __M_writer(filters.html_escape(filters.decode.utf8(id))) __M_writer(u'-problem-progress" tabindex="-1">\n ') __M_writer(filters.html_escape(filters.decode.utf8(problem['name']))) __M_writer(u'\n</h3>\n\n<div class="problem-progress" id="') __M_writer(filters.html_escape(filters.decode.utf8(id))) __M_writer(u'-problem-progress"></div>\n\n<div class="problem">\n ') __M_writer( filters.html_escape(filters.decode.utf8(HTML(problem['html'])))) __M_writer( u'\n <div class="action">\n <input type="hidden" name="problem_id" value="' ) __M_writer(filters.html_escape(filters.decode.utf8(problem['name']))) __M_writer(u'" />\n') if demand_hint_possible: __M_writer(u' <div class="problem-hint">\n ') runtime._include_file(context, u'problem_notifications.html', _template_uri, notification_name='hint', notification_type='problem-hint', notification_icon='fa-question', notification_message='') __M_writer(u'\n </div>\n') __M_writer( u'\n <div class="submit-attempt-container">\n <button type="button" class="submit btn-brand" data-submitting="' ) __M_writer( filters.html_escape(filters.decode.utf8(submit_button_submitting))) __M_writer(u'" data-value="') __M_writer(filters.html_escape(filters.decode.utf8(submit_button))) __M_writer(u'" data-should-enable-submit-button="') __M_writer( filters.html_escape( filters.decode.utf8(should_enable_submit_button))) __M_writer(u'" aria-describedby="submission_feedback_') __M_writer(filters.html_escape(filters.decode.utf8(short_id))) __M_writer(u'" ') __M_writer( filters.html_escape( filters.decode.utf8( '' if should_enable_submit_button else 'disabled'))) __M_writer(u'>\n <span class="submit-label">') __M_writer(filters.html_escape(filters.decode.utf8(submit_button))) __M_writer( u'</span>\n </button>\n <div class="submission-feedback" id="submission_feedback_' ) __M_writer(filters.html_escape(filters.decode.utf8(short_id))) __M_writer(u'">\n') if attempts_allowed: __M_writer(u' ') __M_writer( filters.html_escape( filters.decode.utf8( ungettext( "You have used {num_used} of {num_total} attempt", "You have used {num_used} of {num_total} attempts", attempts_allowed).format( num_used=attempts_used, num_total=attempts_allowed)))) __M_writer(u'\n') __M_writer(u' <span class="sr">') __M_writer( filters.html_escape( filters.decode.utf8( _("Some problems have options such as save, reset, hints, or show answer. These options follow the Submit button." )))) __M_writer( u'</span>\n </div>\n </div>\n <div class="problem-action-buttons-wrapper">\n' ) if demand_hint_possible: __M_writer( u' <span class="problem-action-button-wrapper">\n <button type="button" class="hint-button problem-action-btn btn-default btn-small" data-value="' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Hint')))) __M_writer(u'" ') __M_writer( filters.html_escape( filters.decode.utf8( '' if should_enable_next_hint else 'disabled'))) __M_writer( u'><span class="icon fa fa-question" aria-hidden="true"></span>' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Hint')))) __M_writer(u'</button>\n </span>\n') if save_button: __M_writer( u' <span class="problem-action-button-wrapper">\n <button type="button" class="save problem-action-btn btn-default btn-small" data-value="' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Save')))) __M_writer( u'">\n <span class="icon fa fa-floppy-o" aria-hidden="true"></span>\n <span aria-hidden="true">' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Save')))) __M_writer(u'</span>\n <span class="sr">') __M_writer( filters.html_escape(filters.decode.utf8( _("Save your answer")))) __M_writer(u'</span>\n </button>\n </span>\n') if reset_button: __M_writer( u' <span class="problem-action-button-wrapper">\n <button type="button" class="reset problem-action-btn btn-default btn-small" data-value="' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Reset')))) __M_writer( u'"><span class="icon fa fa-refresh" aria-hidden="true"></span><span aria-hidden="true">' ) __M_writer(filters.html_escape(filters.decode.utf8(_('Reset')))) __M_writer(u'</span><span class="sr">') __M_writer( filters.html_escape(filters.decode.utf8( _("Reset your answer")))) __M_writer(u'</span></button>\n </span>\n') if answer_available: __M_writer( u' <span class="problem-action-button-wrapper">\n <button type="button" class="show problem-action-btn btn-default btn-small" aria-describedby="' ) __M_writer(filters.html_escape(filters.decode.utf8(short_id))) __M_writer( u'-problem-title"><span class="icon fa fa-info-circle" aria-hidden="true"></span><span class="show-label">' ) __M_writer( filters.html_escape(filters.decode.utf8(_('Show Answer')))) __M_writer(u'</span></button>\n </span>\n') __M_writer(u' </div>\n </div>\n ') runtime._include_file(context, u'problem_notifications.html', _template_uri, notification_type='warning', notification_icon='fa-exclamation-circle', notification_name='gentle-alert', notification_message='') __M_writer(u'\n') if answer_notification_type: if 'correct' == answer_notification_type: __M_writer(u' ') runtime._include_file( context, u'problem_notifications.html', _template_uri, notification_type='success', notification_icon='fa-check', notification_name='submit', is_hidden=False, notification_message=answer_notification_message) __M_writer(u'\n') if 'incorrect' == answer_notification_type: __M_writer(u' ') runtime._include_file( context, u'problem_notifications.html', _template_uri, notification_type='error', notification_icon='fa-close', notification_name='submit', is_hidden=False, notification_message=answer_notification_message) __M_writer(u'\n') if 'partially-correct' == answer_notification_type: __M_writer(u' ') runtime._include_file( context, u'problem_notifications.html', _template_uri, notification_type='success', notification_icon='fa-asterisk', notification_name='submit', is_hidden=False, notification_message=answer_notification_message) __M_writer(u'\n') if 'submitted' == answer_notification_type: __M_writer(u' ') runtime._include_file( context, u'problem_notifications.html', _template_uri, notification_type='general', notification_icon='fa-info-circle', notification_name='submit', is_hidden=False, notification_message=answer_notification_message) __M_writer(u'\n') __M_writer(u' ') runtime._include_file(context, u'problem_notifications.html', _template_uri, notification_type='warning', notification_icon='fa-save', notification_name='save', notification_message=save_message, is_hidden=not has_saved_answers) __M_writer(u'\n ') notification_message = _('Answers are displayed within the problem') __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 ['notification_message'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n ') runtime._include_file(context, u'problem_notifications.html', _template_uri, notification_type='general', notification_icon='fa-info-circle', notification_name='show-answer', notification_message=notification_message, is_hidden=True) __M_writer(u'\n</div>\n') return '' finally: context.caller_stack._pop_frame()
def activate_account(request, key): """ When link in activation e-mail is clicked """ # If request is in Studio call the appropriate view if theming_helpers.get_project_root_name().lower() == u'cms': monitoring_utils.set_custom_attribute('student_activate_account', 'cms') return activate_account_studio(request, key) # TODO: Use custom attribute to determine if there are any `activate_account` calls for cms in Production. # If not, the templates wouldn't be needed for cms, but we still need a way to activate for cms tests. monitoring_utils.set_custom_attribute('student_activate_account', 'lms') activation_message_type = None try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): activation_message_type = 'error' messages.error( request, HTML(_( '{html_start}Your account could not be activated{html_end}' 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.' )).format( support_url=configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK ) or settings.SUPPORT_SITE_LINK, html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon' ) else: if registration.user.is_active: activation_message_type = 'info' messages.info( request, HTML(_('{html_start}This account has already been activated.{html_end}')).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) else: registration.activate() # Success message for logged in users. message = _('{html_start}Success{html_end} You have activated your account.') tracker.emit( USER_ACCOUNT_ACTIVATED, { "user_id": registration.user.id, } ) if not request.user.is_authenticated: # Success message for logged out users message = _( '{html_start}Success! You have activated your account.{html_end}' 'You will now receive email updates and alerts from us related to' ' the courses you are enrolled in. Sign In to continue.' ) # Add message for later use. activation_message_type = 'success' messages.success( request, HTML(message).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) if should_redirect_to_authn_microfrontend() and not request.user.is_authenticated: url_path = '/login?account_activation_status={}'.format(activation_message_type) return redirect(settings.AUTHN_MICROFRONTEND_URL + url_path) return redirect('dashboard')
def _register_course_goal_message(request, course): """ Register a message to let a learner specify a course goal. """ course_goal_options = get_course_goal_options() goal_choices_html = Text(_( 'To start, set a course goal by selecting the option below that best describes ' u'your learning plan. {goal_options_container}' )).format( goal_options_container=HTML('<div class="row goal-options-container">') ) # Add the dismissible option for users that are unsure of their goal goal_choices_html += Text( '{initial_tag}{choice}{closing_tag}' ).format( initial_tag=HTML( u'<div tabindex="0" aria-label="{aria_label_choice}" class="goal-option dismissible" ' 'data-choice="{goal_key}">' ).format( goal_key=GOAL_KEY_CHOICES.unsure, aria_label_choice=Text(_(u"Set goal to: {choice}")).format( choice=course_goal_options[GOAL_KEY_CHOICES.unsure], ), ), choice=Text(_('{choice}')).format( choice=course_goal_options[GOAL_KEY_CHOICES.unsure], ), closing_tag=HTML('</div>'), ) # Add the option to set a goal to earn a certificate, # complete the course or explore the course course_goals_by_commitment_level = valid_course_goals_ordered() for goal in course_goals_by_commitment_level: goal_key, goal_text = goal goal_choices_html += HTML( '{initial_tag}{goal_text}{closing_tag}' ).format( initial_tag=HTML( u'<button tabindex="0" aria-label="{aria_label_choice}" class="goal-option btn-outline-primary" ' 'data-choice="{goal_key}">' ).format( goal_key=goal_key, aria_label_choice=Text(_(u"Set goal to: {goal_text}")).format( goal_text=Text(_(goal_text)) ) ), goal_text=goal_text, closing_tag=HTML('</button>') ) CourseHomeMessages.register_info_message( request, HTML('{goal_choices_html}{closing_tag}').format( goal_choices_html=goal_choices_html, closing_tag=HTML('</div>') ), title=Text(_(u'Welcome to {course_display_name}')).format( course_display_name=course.display_name ) )
def activate_account(request, key): """ When link in activation e-mail is clicked """ # If request is in Studio call the appropriate view if theming_helpers.get_project_root_name().lower() == u'cms': return activate_account_studio(request, key) try: registration = Registration.objects.get(activation_key=key) except (Registration.DoesNotExist, Registration.MultipleObjectsReturned): messages.error( request, HTML( _('{html_start}Your account could not be activated{html_end}' 'Something went wrong, please <a href="{support_url}">contact support</a> to resolve this issue.' )).format( support_url=configuration_helpers.get_value( 'SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon') else: if registration.user.is_active: messages.info( request, HTML( _('{html_start}This account has already been activated.{html_end}' )).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) elif waffle().is_enabled(PREVENT_AUTH_USER_WRITES): messages.error( request, HTML(u'{html_start}{message}{html_end}').format( message=Text(SYSTEM_MAINTENANCE_MSG), html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) else: registration.activate() # Success message for logged in users. message = _( '{html_start}Success{html_end} You have activated your account.' ) if not request.user.is_authenticated: # Success message for logged out users message = _( '{html_start}Success! You have activated your account.{html_end}' 'You will now receive email updates and alerts from us related to' ' the courses you are enrolled in. Sign In to continue.') # Add message for later use. messages.success( request, HTML(message).format( html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), ), extra_tags='account-activation aa-icon', ) return redirect('dashboard')
def render_body(context,**pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(pageargs=pageargs) loop = __M_loop = runtime.LoopStack() def certificate_block(): return render_certificate_block(context._locals(__M_locals)) int = context.get('int', UNDEFINED) float = context.get('float', UNDEFINED) def pagetitle(): return render_pagetitle(context._locals(__M_locals)) course = context.get('course', UNDEFINED) static = _mako_get_namespace(context, 'static') unicode = context.get('unicode', UNDEFINED) def bodyclass(): return render_bodyclass(context._locals(__M_locals)) credit_course_requirements = context.get('credit_course_requirements', UNDEFINED) def js_extra(): return render_js_extra(context._locals(__M_locals)) studio_url = context.get('studio_url', UNDEFINED) getattr = context.get('getattr', UNDEFINED) dict = context.get('dict', UNDEFINED) course_expiration_fragment = context.get('course_expiration_fragment', UNDEFINED) user_timezone = context.get('user_timezone', UNDEFINED) len = context.get('len', UNDEFINED) student = context.get('student', UNDEFINED) user_language = context.get('user_language', UNDEFINED) staff_access = context.get('staff_access', UNDEFINED) certificate_data = context.get('certificate_data', UNDEFINED) progress_graph = _mako_get_namespace(context, 'progress_graph') request = context.get('request', UNDEFINED) grade_summary = context.get('grade_summary', UNDEFINED) def headextra(): return render_headextra(context._locals(__M_locals)) courseware_summary = context.get('courseware_summary', 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') username = get_enterprise_learner_generic_name(request) or student.username __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 ['username'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n\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'], 'headextra'): context['self'].headextra(**pageargs) __M_writer(u'\n\n\n') __M_writer(u'\n\n') if 'parent' not in context._data or not hasattr(context._data['parent'], 'pagetitle'): context['self'].pagetitle(**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') runtime._include_file(context, u'/courseware/course_navigation.html', _template_uri, active_page='progress') __M_writer(u'\n\n<main id="main" aria-label="Content" tabindex="-1">\n <div class="container">\n <div class="profile-wrapper">\n <section class="course-info" id="course-info-progress"\n') if getattr(course, 'language'): __M_writer(u' lang="') __M_writer(filters.html_escape(filters.decode.utf8(course.language))) __M_writer(u'"\n') __M_writer(u' >\n') if staff_access and studio_url is not None: __M_writer(u' <div class="wrap-instructor-info">\n <a class="instructor-info-action studio-view" href="') __M_writer(filters.html_escape(filters.decode.utf8(studio_url))) __M_writer(u'">') __M_writer(filters.html_escape(filters.decode.utf8(_("View Grading in studio")))) __M_writer(u'</a>\n </div>\n') __M_writer(u' <h2 class="hd hd-2 progress-certificates-title">\n ') __M_writer(filters.html_escape(filters.decode.utf8(_("Course Progress for Student '{username}' ({email})").format(username=username, email=student.email)))) __M_writer(u'\n </h2>\n') if course_expiration_fragment: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(HTML(course_expiration_fragment.content)))) __M_writer(u'\n') __M_writer(u' <div class="wrapper-msg wrapper-auto-cert">\n <div id="errors-info" class="errors-info"></div>\n ') if 'parent' not in context._data or not hasattr(context._data['parent'], 'certificate_block'): context['self'].certificate_block(**pageargs) __M_writer(u'\n </div>\n\n') if not course.disable_progress_graph: __M_writer(u' <div class="grade-detail-graph" id="grade-detail-graph"></div>\n') __M_writer(u'\n') if credit_course_requirements: __M_writer(u' <section class="credit-eligibility">\n <h3 class="hd hd-4 eligibility-heading">') __M_writer(filters.html_escape(filters.decode.utf8(_("Requirements for Course Credit")))) __M_writer(u'</h3>\n <div class="credit-eligibility-container">\n') if credit_course_requirements['eligibility_status'] == 'not_eligible': __M_writer(u' <span class="eligibility_msg">') __M_writer(filters.html_escape(filters.decode.utf8(_("{student_name}, you are no longer eligible for credit in this course.").format(student_name=student.profile.name)))) __M_writer(u'</span>\n') elif credit_course_requirements['eligibility_status'] == 'eligible': __M_writer(u' <span class="eligibility_msg">\n ') __M_writer(filters.html_escape(filters.decode.utf8(Text(_("{student_name}, you have met the requirements for credit in this course. {a_start}Go to your dashboard{a_end} to purchase course credit.")).format( student_name=student.profile.name, a_start=HTML("<a href={url}>").format(url=reverse('dashboard')), a_end=HTML("</a>") )))) __M_writer(u'\n </span>\n') elif credit_course_requirements['eligibility_status'] == 'partial_eligible': __M_writer(u' <span>') __M_writer(filters.html_escape(filters.decode.utf8(_("{student_name}, you have not yet met the requirements for credit.").format(student_name=student.profile.name)))) __M_writer(u'</span>\n') __M_writer(u'\n <a href="') __M_writer(filters.html_escape(filters.decode.utf8(settings.CREDIT_HELP_LINK_URL))) __M_writer(u'" class="credit-help">\n <span class="fa fa-question" aria-hidden="true"></span>\n <span class="sr">') __M_writer(filters.html_escape(filters.decode.utf8(_("Information about course credit requirements")))) __M_writer(u'</span>\n </a><br />\n\n <div class="requirement-container" data-eligible="') __M_writer(filters.html_escape(filters.decode.utf8(credit_course_requirements['eligibility_status']))) __M_writer(u'">\n') for requirement in credit_course_requirements['requirements']: __M_writer(u' <div class="requirement">\n <div class="requirement-name">\n ') __M_writer(filters.html_escape(filters.decode.utf8(_(requirement['display_name'])))) __M_writer(u'\n') if requirement['namespace'] == 'grade': __M_writer(u' <span>') __M_writer(filters.html_escape(filters.decode.utf8(int(requirement['criteria']['min_grade'] * 100)))) __M_writer(u'%</span>\n') __M_writer(u' </div>\n <div class="requirement-status">\n') if requirement['status']: if requirement['status'] == 'submitted': __M_writer(u' <span class="requirement-submitted">') __M_writer(filters.html_escape(filters.decode.utf8(_("Verification Submitted")))) __M_writer(u'</span>\n') elif requirement['status'] == 'failed': __M_writer(u' <span class="fa fa-times" aria-hidden="true"></span>\n <span>') __M_writer(filters.html_escape(filters.decode.utf8(_("Verification Failed" )))) __M_writer(u'</span>\n') elif requirement['status'] == 'declined': __M_writer(u' <span class="fa fa-times" aria-hidden="true"></span>\n <span>') __M_writer(filters.html_escape(filters.decode.utf8(_("Verification Declined" )))) __M_writer(u'</span>\n') elif requirement['status'] == 'satisfied': __M_writer(u' <span class="fa fa-check" aria-hidden="true"></span>\n <span class="localized-datetime" data-datetime="') __M_writer(filters.html_escape(filters.decode.utf8(requirement['status_date']))) __M_writer(u'" data-string="') __M_writer(filters.html_escape(filters.decode.utf8(_('Completed by {date}')))) __M_writer(u'" data-timezone="') __M_writer(filters.html_escape(filters.decode.utf8(user_timezone))) __M_writer(u'" data-language="') __M_writer(filters.html_escape(filters.decode.utf8(user_language))) __M_writer(u'"></span>\n') else: __M_writer(u' <span class="not-achieve">') __M_writer(filters.html_escape(filters.decode.utf8(_("Upcoming")))) __M_writer(u'</span>\n') __M_writer(u' </div>\n </div>\n') __M_writer(u' </div>\n <button class="detail-collapse">\n <span class="fa fa-caret-up" aria-hidden="true"></span>\n <span class="requirement-detail">') __M_writer(filters.html_escape(filters.decode.utf8(_("Less")))) __M_writer(u'</span>\n </button>\n </div>\n </section>\n') __M_writer(u'\n') if courseware_summary: __M_writer(u' <section class="chapters">\n <h2 class="sr">') __M_writer(filters.html_escape(filters.decode.utf8(_('Details for each chapter')))) __M_writer(u'</h2>\n') loop = __M_loop._enter(courseware_summary) try: for chapter in loop: if not chapter['display_name'] == "hidden": __M_writer(u' <section aria-labelledby="chapter_') __M_writer(filters.html_escape(filters.decode.utf8(loop.index))) __M_writer(u'">\n <h3 class="hd hd-3" id="chapter_') __M_writer(filters.html_escape(filters.decode.utf8(loop.index))) __M_writer(u'">') __M_writer(filters.html_escape(filters.decode.utf8( chapter['display_name']))) __M_writer(u'</h3>\n <div class="sections">\n') for section in chapter['sections']: __M_writer(u' <div>\n ') earned = section.all_total.earned total = section.all_total.possible percentageString = "{0:.0%}".format(section.percent_graded) if earned > 0 and total > 0 else "" __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 ['percentageString','total','earned'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n <h4 class="hd hd-4">\n <a href="') __M_writer(filters.html_escape(filters.decode.utf8(reverse('courseware_section', kwargs=dict(course_id=text_type(course.id), chapter=chapter['url_name'], section=section.url_name))))) __M_writer(u'">\n ') __M_writer(filters.html_escape(filters.decode.utf8( section.display_name))) __M_writer(u'\n') if total > 0 or earned > 0: __M_writer(u' <span class="sr">\n ') __M_writer(filters.html_escape(filters.decode.utf8(_("{earned} of {total} possible points").format(earned='{:.3n}'.format(float(earned)), total='{:.3n}'.format(float(total)))))) __M_writer(u'\n </span>\n') __M_writer(u' </a>\n') if total > 0 or earned > 0: __M_writer(u' <span> ') __M_writer(filters.html_escape(filters.decode.utf8("({0:.3n}/{1:.3n}) {2}".format( float(earned), float(total), percentageString )))) __M_writer(u'</span>\n') __M_writer(u' </h4>\n <p>\n') if section.format is not None: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(section.format))) __M_writer(u'\n') if section.due is not None: __M_writer(u' <em class="localized-datetime" data-datetime="') __M_writer(filters.html_escape(filters.decode.utf8(section.due))) __M_writer(u'" data-string="') __M_writer(filters.html_escape(filters.decode.utf8(_('due {date}')))) __M_writer(u'" data-timezone="') __M_writer(filters.html_escape(filters.decode.utf8(user_timezone))) __M_writer(u'" data-language="') __M_writer(filters.html_escape(filters.decode.utf8(user_language))) __M_writer(u'"></em>\n') __M_writer(u' </p>\n <p class="override-notice">\n') if section.override is not None: if section.format is not None and section.format == "Exam": __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Suspicious activity detected during proctored exam review. Exam score 0.")))) __M_writer(u'\n') else: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Section grade has been overridden.")))) __M_writer(u'\n') __M_writer(u' </p>\n') if len(section.problem_scores.values()) > 0: if section.show_grades(staff_access): __M_writer(u' <dl class="scores">\n <dt class="hd hd-6">') __M_writer(filters.html_escape(filters.decode.utf8( _("Problem Scores: ") if section.graded else _("Practice Scores: ")))) __M_writer(u'</dt>\n') for score in section.problem_scores.values(): __M_writer(u' <dd>') __M_writer(filters.html_escape(filters.decode.utf8("{0:.3n}/{1:.3n}".format(float(score.earned),float(score.possible))))) __M_writer(u'</dd>\n') __M_writer(u' </dl>\n') else: __M_writer(u' <p class="hide-scores">\n') if section.show_correctness == 'past_due': if section.graded: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Problem scores are hidden until the due date.")))) __M_writer(u'\n') else: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Practice scores are hidden until the due date.")))) __M_writer(u'\n') else: if section.graded: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Problem scores are hidden.")))) __M_writer(u'\n') else: __M_writer(u' ') __M_writer(filters.html_escape(filters.decode.utf8(_("Practice scores are hidden.")))) __M_writer(u'\n') __M_writer(u' </p>\n') else: __M_writer(u' <p class="no-scores">') __M_writer(filters.html_escape(filters.decode.utf8(_("No problem scores in this section")))) __M_writer(u'</p>\n') __M_writer(u' </div>\n') __M_writer(u' </div>\n </section>\n') finally: loop = __M_loop._exit() __M_writer(u' </section>\n') __M_writer(u' </section>\n </div>\n </div>\n</main>\n') def ccall(caller): def body(): __M_writer = context.writer() __M_writer(u'\n DateUtilFactory.transform(iterationKey=".localized-datetime");\n') return '' return [body] context.caller_stack.nextcaller = runtime.Namespace('caller', context, callables=ccall(__M_caller)) try: __M_writer(filters.html_escape(filters.decode.utf8(static.require_module_async(class_name=u'DateUtilFactory',module_name=u'js/dateutil_factory')))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n') return '' finally: context.caller_stack._pop_frame()
def username(self, obj): return HTML('<a href="{}">{}</a>').format( reverse("admin:auth_user_change", args=(obj.enrollment.user.id, )), obj.enrollment.user.username)
def instructor_dashboard_2(request, course_id): """ Display the instructor dashboard for a course. """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: log.error( u"Unable to find course with course key %s while loading the Instructor Dashboard.", course_id) return HttpResponseServerError() course = get_course_by_id(course_key, depth=0) access = { 'admin': request.user.is_staff, 'instructor': bool(has_access(request.user, 'instructor', course)), 'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user), 'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user), 'staff': bool(has_access(request.user, 'staff', course)), 'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR), } if not access['staff']: raise Http404() is_white_label = CourseMode.is_white_label(course_key) reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS', False) sections = [ _section_course_info(course, access), _section_membership(course, access, is_white_label), _section_cohort_management(course, access), _section_student_admin(course, access), _section_data_download(course, access), ] analytics_dashboard_message = None if settings.ANALYTICS_DASHBOARD_URL: # Construct a URL to the external analytics dashboard analytics_dashboard_url = '{0}/courses/{1}'.format( settings.ANALYTICS_DASHBOARD_URL, unicode(course_key)) link_start = HTML("<a href=\"{}\" target=\"_blank\">").format( analytics_dashboard_url) analytics_dashboard_message = _( "To gain insights into student enrollment and participation {link_start}" "visit {analytics_dashboard_name}, our new course analytics product{link_end}." ) analytics_dashboard_message = Text(analytics_dashboard_message).format( link_start=link_start, link_end=HTML("</a>"), analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME) # Temporarily show the "Analytics" section until we have a better way of linking to Insights sections.append(_section_analytics(course, access)) # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course course_mode_has_price = False paid_modes = CourseMode.paid_modes_for_course(course_key) if len(paid_modes) == 1: course_mode_has_price = True elif len(paid_modes) > 1: log.error( u"Course %s has %s course modes with payment options. Course must only have " u"one paid course mode to enable eCommerce options.", unicode(course_key), len(paid_modes)) if settings.FEATURES.get('INDIVIDUAL_DUE_DATES') and access['instructor']: sections.insert(3, _section_extensions(course)) # Gate access to course email by feature flag & by course-specific authorization if BulkEmailFlag.feature_enabled(course_key): sections.append(_section_send_email(course, access)) # Gate access to Metrics tab by featue flag and staff authorization if settings.FEATURES['CLASS_DASHBOARD'] and access['staff']: sections.append(_section_metrics(course, access)) # Gate access to Ecommerce tab if course_mode_has_price and (access['finance_admin'] or access['sales_admin']): sections.append( _section_e_commerce(course, access, paid_modes[0], is_white_label, reports_enabled)) # Gate access to Special Exam tab depending if either timed exams or proctored exams # are enabled in the course # NOTE: For now, if we only have procotred exams enabled, then only platform Staff # (user.is_staff) will be able to view the special exams tab. This may # change in the future can_see_special_exams = ( ((course.enable_proctored_exams and request.user.is_staff) or course.enable_timed_exams) and settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False)) if can_see_special_exams: sections.append(_section_special_exams(course, access)) # Certificates panel # This is used to generate example certificates # and enable self-generated certificates for a course. certs_enabled = CertificateGenerationConfiguration.current().enabled if certs_enabled and access['admin']: sections.append(_section_certificates(course)) disable_buttons = not _is_small_course(course_key) certificate_white_list = CertificateWhitelist.get_certificate_white_list( course_key) generate_certificate_exceptions_url = reverse( # pylint: disable=invalid-name 'generate_certificate_exceptions', kwargs={ 'course_id': unicode(course_key), 'generate_for': '' }) generate_bulk_certificate_exceptions_url = reverse( # pylint: disable=invalid-name 'generate_bulk_certificate_exceptions', kwargs={'course_id': unicode(course_key)}) certificate_exception_view_url = reverse( 'certificate_exception_view', kwargs={'course_id': unicode(course_key)}) certificate_invalidation_view_url = reverse( # pylint: disable=invalid-name 'certificate_invalidation_view', kwargs={'course_id': unicode(course_key)}) certificate_invalidations = CertificateInvalidation.get_certificate_invalidations( course_key) context = { 'course': course, 'studio_url': get_studio_url(course, 'course'), 'sections': sections, 'disable_buttons': disable_buttons, 'analytics_dashboard_message': analytics_dashboard_message, 'certificate_white_list': certificate_white_list, 'certificate_invalidations': certificate_invalidations, 'generate_certificate_exceptions_url': generate_certificate_exceptions_url, 'generate_bulk_certificate_exceptions_url': generate_bulk_certificate_exceptions_url, 'certificate_exception_view_url': certificate_exception_view_url, 'certificate_invalidation_view_url': certificate_invalidation_view_url, } return render_to_response( 'instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
def _handle_failed_authentication(user, authenticated_user): """ Handles updating the failed login count, inactive user notifications, and logging failed authentications. """ if user: if LoginFailures.is_feature_enabled(): LoginFailures.increment_lockout_counter(user) if authenticated_user and not user.is_active: _log_and_raise_inactive_user_auth_error(user) # if we didn't find this username earlier, the account for this email # doesn't exist, and doesn't have a corresponding password if settings.FEATURES['SQUELCH_PII_IN_LOGS']: loggable_id = user.id if user else "<unknown>" AUDIT_LOG.warning( u"Login failed - password for user.id: {0} is invalid".format( loggable_id)) else: AUDIT_LOG.warning( u"Login failed - password for {0} is invalid".format( user.email)) if user and LoginFailures.is_feature_enabled(): blocked_threshold, failure_count = LoginFailures.check_user_reset_password_threshold( user) if blocked_threshold: if not LoginFailures.is_user_locked_out(user): max_failures_allowed = settings.MAX_FAILED_LOGIN_ATTEMPTS_ALLOWED remaining_attempts = max_failures_allowed - failure_count if not should_redirect_to_logistration_mircrofrontend: # pylint: disable=no-else-raise raise AuthFailedError( Text( _('Email or password is incorrect.' '{li_start}You have {remaining_attempts} more sign-in ' 'attempts before your account is temporarily locked.{li_end}' '{li_start}If you\'ve forgotten your password, click ' '{link_start}here{link_end} to reset.{li_end}')). format(link_start=HTML( '<a http="#login" class="form-toggle" data-type="password-reset">' ), link_end=HTML('</a>'), li_start=HTML('<li>'), li_end=HTML('</li>'), remaining_attempts=remaining_attempts)) else: raise AuthFailedError( Text( _('Email or password is incorrect.\n' 'You have {remaining_attempts} more sign-in ' 'attempts before your account is temporarily locked.\n' 'If you{quote}ve forgotten your password, click ' '{link_start}here{link_end} to reset.\n')). format(quote=HTML("'"), link_start=HTML('<a href="/reset" >'), link_end=HTML('</a>'), remaining_attempts=remaining_attempts)) else: _generate_locked_out_error_message() raise AuthFailedError(_('Email or password is incorrect.'))
def test_render_response_xml(self): # Generate some XML for a string response kwargs = { 'question_text': "Test question", 'explanation_text': "Test explanation", 'answer': 'Test answer', 'hints': [('test prompt', 'test_hint', 'test hint text')] } xml_str = StringResponseXMLFactory().build_xml(**kwargs) # Mock out the template renderer the_system = test_capa_system() the_system.render_template = mock.Mock() the_system.render_template.return_value = "<div class='input-template-render'>Input Template Render</div>" # Create the problem and render the HTML problem = new_loncapa_problem(xml_str, capa_system=the_system) rendered_html = etree.XML(problem.get_html()) # Expect problem has been turned into a <div> self.assertEqual(rendered_html.tag, "div") # Expect that the response has been turned into a <div> with correct attributes response_element = rendered_html.find('div') self.assertEqual(response_element.tag, "div") self.assertEqual(response_element.attrib["aria-label"], "Question 1") # Expect that the response div.wrapper-problem-response # that contains a <div> for the textline textline_element = response_element.find('div') self.assertEqual(textline_element.text, 'Input Template Render') # Expect a child <div> for the solution # with the rendered template solution_element = rendered_html.xpath( '//div[@class="input-template-render"]')[0] self.assertEqual(solution_element.text, 'Input Template Render') # Expect that the template renderer was called with the correct # arguments, once for the textline input and once for # the solution expected_textline_context = { 'STATIC_URL': '/dummy-static/', 'status': the_system.STATUS_CLASS('unsubmitted'), 'value': '', 'preprocessor': None, 'msg': '', 'inline': False, 'hidden': False, 'do_math': False, 'id': '1_2_1', 'trailing_text': '', 'size': None, 'response_data': { 'label': 'Test question', 'descriptions': {} }, 'describedby_html': HTML('aria-describedby="status_1_2_1"') } expected_solution_context = {'id': '1_solution_1'} expected_calls = [ mock.call('textline.html', expected_textline_context), mock.call('solutionspan.html', expected_solution_context), mock.call('textline.html', expected_textline_context), mock.call('solutionspan.html', expected_solution_context) ] self.assertEqual(the_system.render_template.call_args_list, expected_calls)
def _register_course_home_messages(request, course, user_access, course_start_data): """ Register messages to be shown in the course home content page. """ allow_anonymous = allow_public_access(course, [COURSE_VISIBILITY_PUBLIC]) if user_access['is_anonymous'] and not allow_anonymous: sign_in_or_register_text = (_( u'{sign_in_link} or {register_link} and then enroll in this course.' ) if not CourseMode.is_masters_only(course.id) else _(u'{sign_in_link} or {register_link}.')) CourseHomeMessages.register_info_message( request, Text(sign_in_or_register_text).format( sign_in_link=HTML( u'<a href="/login?next={current_url}">{sign_in_label}</a>' ).format( sign_in_label=_('Sign in'), current_url=urlquote_plus(request.path), ), register_link=HTML( u'<a href="/register?next={current_url}">{register_label}</a>' ).format( register_label=_('register'), current_url=urlquote_plus(request.path), )), title=Text( _('You must be enrolled in the course to see course content.') )) if not user_access['is_anonymous'] and not user_access['is_staff'] and \ not user_access['is_enrolled']: title = Text(_(u'Welcome to {course_display_name}')).format( course_display_name=course.display_name) if CourseMode.is_masters_only(course.id): # if a course is a Master's only course, we will not offer user ability to self-enroll CourseHomeMessages.register_info_message( request, Text( _('You must be enrolled in the course to see course content. ' 'Please contact your degree administrator or edX Support if you have questions.' )), title=title) elif not course.invitation_only: CourseHomeMessages.register_info_message( request, Text( _(u'{open_enroll_link}Enroll now{close_enroll_link} to access the full course.' )).format(open_enroll_link=HTML( '<button class="enroll-btn btn-link">'), close_enroll_link=HTML('</button>')), title=title) else: CourseHomeMessages.register_info_message( request, Text( _('You must be enrolled in the course to see course content.' )), )
def instructor_dashboard_2(request, course_id): """ Display the instructor dashboard for a course. """ try: course_key = CourseKey.from_string(course_id) except InvalidKeyError: log.error( u"Unable to find course with course key %s while loading the Instructor Dashboard.", course_id) return HttpResponseServerError() course = get_course_by_id(course_key, depth=0) access = { 'admin': request.user.is_staff, 'instructor': bool(has_access(request.user, 'instructor', course)), 'finance_admin': CourseFinanceAdminRole(course_key).has_user(request.user), 'sales_admin': CourseSalesAdminRole(course_key).has_user(request.user), 'staff': bool(has_access(request.user, 'staff', course)), 'forum_admin': has_forum_access(request.user, course_key, FORUM_ROLE_ADMINISTRATOR), 'data_researcher': request.user.has_perm(permissions.CAN_RESEARCH, course_key), } if not request.user.has_perm(permissions.VIEW_DASHBOARD, course_key): raise Http404() is_white_label = CourseMode.is_white_label(course_key) reports_enabled = configuration_helpers.get_value('SHOW_ECOMMERCE_REPORTS', False) sections = [] if access['staff']: sections.extend([ _section_course_info(course, access), _section_membership(course, access), _section_cohort_management(course, access), _section_discussions_management(course, access), _section_student_admin(course, access), ]) if access['data_researcher']: sections.append(_section_data_download(course, access)) analytics_dashboard_message = None if show_analytics_dashboard_message(course_key) and (access['staff'] or access['instructor']): # Construct a URL to the external analytics dashboard analytics_dashboard_url = '{0}/courses/{1}'.format( settings.ANALYTICS_DASHBOARD_URL, six.text_type(course_key)) link_start = HTML(u"<a href=\"{}\" rel=\"noopener\" target=\"_blank\">" ).format(analytics_dashboard_url) analytics_dashboard_message = _( u"To gain insights into student enrollment and participation {link_start}" u"visit {analytics_dashboard_name}, our new course analytics product{link_end}." ) analytics_dashboard_message = Text(analytics_dashboard_message).format( link_start=link_start, link_end=HTML("</a>"), analytics_dashboard_name=settings.ANALYTICS_DASHBOARD_NAME) # Temporarily show the "Analytics" section until we have a better way of linking to Insights sections.append(_section_analytics(course, access)) # Check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course course_mode_has_price = False paid_modes = CourseMode.paid_modes_for_course(course_key) if len(paid_modes) == 1: course_mode_has_price = True elif len(paid_modes) > 1: log.error( u"Course %s has %s course modes with payment options. Course must only have " u"one paid course mode to enable eCommerce options.", six.text_type(course_key), len(paid_modes)) if access['instructor'] and is_enabled_for_course(course_key): sections.insert(3, _section_extensions(course)) # Gate access to course email by feature flag & by course-specific authorization if is_bulk_email_feature_enabled(course_key) and (access['staff'] or access['instructor']): sections.append(_section_send_email(course, access)) # Gate access to Ecommerce tab if course_mode_has_price and (access['finance_admin'] or access['sales_admin']): sections.append( _section_e_commerce(course, access, paid_modes[0], is_white_label, reports_enabled)) # Gate access to Special Exam tab depending if either timed exams or proctored exams # are enabled in the course user_has_access = any([ request.user.is_staff, CourseStaffRole(course_key).has_user(request.user), CourseInstructorRole(course_key).has_user(request.user) ]) course_has_special_exams = course.enable_proctored_exams or course.enable_timed_exams can_see_special_exams = course_has_special_exams and user_has_access and settings.FEATURES.get( 'ENABLE_SPECIAL_EXAMS', False) if can_see_special_exams: sections.append(_section_special_exams(course, access)) # Certificates panel # This is used to generate example certificates # and enable self-generated certificates for a course. # Note: This is hidden for all CCXs certs_enabled = CertificateGenerationConfiguration.current( ).enabled and not hasattr(course_key, 'ccx') if certs_enabled and access['admin']: sections.append(_section_certificates(course)) openassessment_blocks = modulestore().get_items( course_key, qualifiers={'category': 'openassessment'}) # filter out orphaned openassessment blocks openassessment_blocks = [ block for block in openassessment_blocks if block.parent is not None ] if len(openassessment_blocks) > 0 and access['staff']: sections.append( _section_open_response_assessment(request, course, openassessment_blocks, access)) disable_buttons = not _is_small_course(course_key) certificate_white_list = CertificateWhitelist.get_certificate_white_list( course_key) generate_certificate_exceptions_url = reverse( 'generate_certificate_exceptions', kwargs={ 'course_id': six.text_type(course_key), 'generate_for': '' }) generate_bulk_certificate_exceptions_url = reverse( 'generate_bulk_certificate_exceptions', kwargs={'course_id': six.text_type(course_key)}) certificate_exception_view_url = reverse( 'certificate_exception_view', kwargs={'course_id': six.text_type(course_key)}) certificate_invalidation_view_url = reverse( 'certificate_invalidation_view', kwargs={'course_id': six.text_type(course_key)}) certificate_invalidations = CertificateInvalidation.get_certificate_invalidations( course_key) context = { 'course': course, 'studio_url': get_studio_url(course, 'course'), 'sections': sections, 'disable_buttons': disable_buttons, 'analytics_dashboard_message': analytics_dashboard_message, 'certificate_white_list': certificate_white_list, 'certificate_invalidations': certificate_invalidations, 'generate_certificate_exceptions_url': generate_certificate_exceptions_url, 'generate_bulk_certificate_exceptions_url': generate_bulk_certificate_exceptions_url, 'certificate_exception_view_url': certificate_exception_view_url, 'certificate_invalidation_view_url': certificate_invalidation_view_url, 'xqa_server': settings.FEATURES.get('XQA_SERVER', "http://your_xqa_server.com"), } return render_to_response( 'instructor/instructor_dashboard_2/instructor_dashboard_2.html', context)
def generate_course_expired_fragment(user, course): message = generate_course_expired_message(user, course) if message: return Fragment(HTML(u"""\ <div class="course-expiration-message">{}</div> """).format(message))
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 password_reset_confirm_wrapper(request, uidb36=None, token=None): """ A wrapper around django.contrib.auth.views.password_reset_confirm. Needed because we want to set the user as active at this step. We also optionally do some additional password policy checks. """ # convert old-style base36-encoded user id to base64 uidb64 = uidb36_to_uidb64(uidb36) platform_name = { "platform_name": configuration_helpers.get_value('platform_name', settings.PLATFORM_NAME) } # User can not get this link unless account recovery feature is enabled. if 'is_account_recovery' in request.GET and not is_secondary_email_feature_enabled(): raise Http404 try: uid_int = base36_to_int(uidb36) user = User.objects.get(id=uid_int) except (ValueError, User.DoesNotExist): # if there's any error getting a user, just let django's # password_reset_confirm function handle it. return password_reset_confirm( request, uidb64=uidb64, token=token, extra_context=platform_name ) if UserRetirementRequest.has_user_requested_retirement(user): # Refuse to reset the password of any user that has requested retirement. context = { 'validlink': True, 'form': None, 'title': _('Password reset unsuccessful'), 'err_msg': _('Error in resetting your password.'), } context.update(platform_name) return TemplateResponse( request, 'registration/password_reset_confirm.html', context ) if waffle().is_enabled(PREVENT_AUTH_USER_WRITES): context = { 'validlink': False, 'form': None, 'title': _('Password reset unsuccessful'), 'err_msg': SYSTEM_MAINTENANCE_MSG, } context.update(platform_name) return TemplateResponse( request, 'registration/password_reset_confirm.html', context ) if request.method == 'POST': # We have to make a copy of request.POST because it is a QueryDict object which is immutable until copied. # We have to use request.POST because the password_reset_confirm method takes in the request and a user's # password is set to the request.POST['new_password1'] field. We have to also normalize the new_password2 # field so it passes the equivalence check that new_password1 == new_password2 # In order to switch out of having to do this copy, we would want to move the normalize_password code into # a custom User model's set_password method to ensure it is always happening upon calling set_password. request.POST = request.POST.copy() request.POST['new_password1'] = normalize_password(request.POST['new_password1']) request.POST['new_password2'] = normalize_password(request.POST['new_password2']) password = request.POST['new_password1'] try: validate_password(password, user=user) except ValidationError as err: # We have a password reset attempt which violates some security # policy, or any other validation. Use the existing Django template to communicate that # back to the user. context = { 'validlink': True, 'form': None, 'title': _('Password reset unsuccessful'), 'err_msg': ' '.join(err.messages), } context.update(platform_name) return TemplateResponse( request, 'registration/password_reset_confirm.html', context ) # remember what the old password hash is before we call down old_password_hash = user.password if 'is_account_recovery' in request.GET: response = password_reset_confirm( request, uidb64=uidb64, token=token, extra_context=platform_name, template_name='registration/password_reset_confirm.html', post_reset_redirect='signin_user', ) else: response = password_reset_confirm( request, uidb64=uidb64, token=token, extra_context=platform_name ) # If password reset was unsuccessful a template response is returned (status_code 200). # Check if form is invalid then show an error to the user. # Note if password reset was successful we get response redirect (status_code 302). if response.status_code == 200: form_valid = response.context_data['form'].is_valid() if response.context_data['form'] else False if not form_valid: log.warning( u'Unable to reset password for user [%s] because form is not valid. ' u'A possible cause is that the user had an invalid reset token', user.username, ) response.context_data['err_msg'] = _('Error in resetting your password. Please try again.') return response # get the updated user updated_user = User.objects.get(id=uid_int) if 'is_account_recovery' in request.GET: try: updated_user.email = updated_user.account_recovery.secondary_email updated_user.account_recovery.delete() # emit an event that the user changed their secondary email to the primary email tracker.emit( SETTING_CHANGE_INITIATED, { "setting": "email", "old": user.email, "new": updated_user.email, "user_id": updated_user.id, } ) except ObjectDoesNotExist: log.error( 'Account recovery process initiated without AccountRecovery instance for user {username}'.format( username=updated_user.username ) ) updated_user.save() if response.status_code == 302 and 'is_account_recovery' in request.GET: messages.success( request, HTML(_( '{html_start}Password Creation Complete{html_end}' 'Your password has been created. {bold_start}{email}{bold_end} is now your primary login email.' )).format( support_url=configuration_helpers.get_value('SUPPORT_SITE_LINK', settings.SUPPORT_SITE_LINK), html_start=HTML('<p class="message-title">'), html_end=HTML('</p>'), bold_start=HTML('<b>'), bold_end=HTML('</b>'), email=updated_user.email, ), extra_tags='account-recovery aa-icon submission-success' ) else: response = password_reset_confirm( request, uidb64=uidb64, token=token, extra_context=platform_name ) response_was_successful = response.context_data.get('validlink') if response_was_successful and not user.is_active: user.is_active = True user.save() return response
def student_dashboard(request): # lint-amnesty, pylint: disable=too-many-statements """ Provides the LMS dashboard view TODO: This is lms specific and does not belong in common code. Note: To load the all courses set course_limit=None as parameter in GET. If its not None then default course limit will be used that is set in configuration Arguments: request: The request object. Returns: The dashboard response. """ landing_page = request.POST.get( 'https://rc360service-q6uoj7vcrq-uc.a.run.app/r1/landingpage/getLandingPageDetails', '') # return render_to_response(landing_page) # decrypt_message(landing_page) # print(landing_page,"") user = request.user if not UserProfile.objects.filter(user=user).exists(): return redirect(reverse('account_settings')) platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME) enable_verified_certificates = configuration_helpers.get_value( 'ENABLE_VERIFIED_CERTIFICATES', settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES')) display_course_modes_on_dashboard = configuration_helpers.get_value( 'DISPLAY_COURSE_MODES_ON_DASHBOARD', settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True)) activation_email_support_link = configuration_helpers.get_value( 'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK) or settings.SUPPORT_SITE_LINK hide_dashboard_courses_until_activated = configuration_helpers.get_value( 'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', settings.FEATURES.get('HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', False)) empty_dashboard_message = configuration_helpers.get_value( 'EMPTY_DASHBOARD_MESSAGE', None) disable_course_limit = request and 'course_limit' in request.GET course_limit = get_dashboard_course_limit( ) if not disable_course_limit else None # Get the org whitelist or the org blacklist for the current site site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site( ) course_enrollments = list( get_course_enrollments(user, site_org_whitelist, site_org_blacklist, course_limit)) # Get the entitlements for the user and a mapping to all available sessions for that entitlement # If an entitlement has no available sessions, pass through a mock course overview object (course_entitlements, course_entitlement_available_sessions, unfulfilled_entitlement_pseudo_sessions ) = get_filtered_course_entitlements(user, site_org_whitelist, site_org_blacklist) # Record how many courses there are so that we can get a better # understanding of usage patterns on prod. monitoring_utils.accumulate('num_courses', len(course_enrollments)) # Sort the enrollment pairs by the enrollment date course_enrollments.sort(key=lambda x: x.created, reverse=True) # Retrieve the course modes for each course enrolled_course_ids = [ enrollment.course_id for enrollment in course_enrollments ] __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses( enrolled_course_ids) course_modes_by_course = { course_id: {mode.slug: mode for mode in modes} for course_id, modes in iteritems(unexpired_course_modes) } # Check to see if the student has recently enrolled in a course. # If so, display a notification message confirming the enrollment. enrollment_message = _create_recent_enrollment_message( course_enrollments, course_modes_by_course) course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True) # Display activation message activate_account_message = '' if not user.is_active: activate_account_message = Text( _("Check your {email_start}{email}{email_end} inbox for an account activation link from {platform_name}. " "If you need help, contact {link_start}{platform_name} Support{link_end}." ) ).format( platform_name=platform_name, email_start=HTML("<strong>"), email_end=HTML("</strong>"), email=user.email, link_start=HTML( "<a target='_blank' href='{activation_email_support_link}'>"). format( activation_email_support_link=activation_email_support_link, ), link_end=HTML("</a>"), ) enterprise_message = get_dashboard_consent_notification( request, user, course_enrollments) # Display a message guiding the user to their Enterprise's Learner Portal if enabled enterprise_learner_portal_enabled_message = get_enterprise_learner_portal_enabled_message( request) recovery_email_message = recovery_email_activation_message = None if is_secondary_email_feature_enabled(): try: pending_email = PendingSecondaryEmailChange.objects.get(user=user) # lint-amnesty, pylint: disable=unused-variable except PendingSecondaryEmailChange.DoesNotExist: try: account_recovery_obj = AccountRecovery.objects.get(user=user) # lint-amnesty, pylint: disable=unused-variable except AccountRecovery.DoesNotExist: recovery_email_message = Text( _("Add a recovery email to retain access when single-sign on is not available. " "Go to {link_start}your Account Settings{link_end}.") ).format(link_start=HTML( "<a href='{account_setting_page}'>").format( account_setting_page=reverse('account_settings'), ), link_end=HTML("</a>")) else: recovery_email_activation_message = Text( _("Recovery email is not activated yet. " "Kindly visit your email and follow the instructions to activate it." )) # Disable lookup of Enterprise consent_required_course due to ENT-727 # Will re-enable after fixing WL-1315 consent_required_courses = set() enterprise_customer_name = None # Account activation message account_activation_messages = [ message for message in messages.get_messages(request) if 'account-activation' in message.tags ] # Global staff can see what courses encountered an error on their dashboard staff_access = False errored_courses = {} if has_access(user, 'staff', 'global'): # Show any courses that encountered an error on load staff_access = True errored_courses = modulestore().get_errored_courses() show_courseware_links_for = { enrollment.course_id: has_access(request.user, 'load', enrollment.course_overview) for enrollment in course_enrollments } # Find programs associated with course runs being displayed. This information # is passed in the template context to allow rendering of program-related # information on the dashboard. meter = ProgramProgressMeter(request.site, user, enrollments=course_enrollments) ecommerce_service = EcommerceService() inverted_programs = meter.invert_programs() urls, programs_data = {}, {} bundles_on_dashboard_flag = LegacyWaffleFlag(experiments_namespace, u'bundles_on_dashboard', __name__) # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete if bundles_on_dashboard_flag.is_enabled() and inverted_programs and list( inverted_programs.items()): if len(course_enrollments) < 4: for program in inverted_programs.values(): try: program_uuid = program[0]['uuid'] program_data = get_programs(uuid=program_uuid) program_data = ProgramDataExtender(program_data, request.user).extend() skus = program_data.get('skus') checkout_page_url = ecommerce_service.get_checkout_page_url( *skus) program_data[ 'completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get( 'uuid') programs_data[program_uuid] = program_data except: # pylint: disable=bare-except pass # Construct a dictionary of course mode information # used to render the course list. We re-use the course modes dict # we loaded earlier to avoid hitting the database. course_mode_info = { enrollment.course_id: complete_course_mode_info( enrollment.course_id, enrollment, modes=course_modes_by_course[enrollment.course_id]) for enrollment in course_enrollments } # Determine the per-course verification status # This is a dictionary in which the keys are course locators # and the values are one of: # # VERIFY_STATUS_NEED_TO_VERIFY # VERIFY_STATUS_SUBMITTED # VERIFY_STATUS_APPROVED # VERIFY_STATUS_MISSED_DEADLINE # # Each of which correspond to a particular message to display # next to the course on the dashboard. # # If a course is not included in this dictionary, # there is no verification messaging to display. verify_status_by_course = check_verify_status_by_course( user, course_enrollments) cert_statuses = { enrollment.course_id: cert_info(request.user, enrollment.course_overview) for enrollment in course_enrollments } # only show email settings for Mongo course and when bulk email is turned on show_email_settings_for = frozenset( enrollment.course_id for enrollment in course_enrollments if (is_bulk_email_feature_enabled(enrollment.course_id))) # Verification Attempts # Used to generate the "you must reverify for course x" banner verification_status = IDVerificationService.user_status(user) verification_errors = get_verification_error_reasons_for_display( verification_status['error']) # Gets data for midcourse reverifications, if any are necessary or have failed statuses = ["approved", "denied", "pending", "must_reverify"] reverifications = reverification_info(statuses) enrolled_courses_either_paid = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.is_paid_course()) # If there are *any* denied reverifications that have not been toggled off, # we'll display the banner denied_banner = any(item.display for item in reverifications["denied"]) # get list of courses having pre-requisites yet to be completed courses_having_prerequisites = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.course_overview.pre_requisite_courses) courses_requirements_not_met = get_pre_requisite_courses_not_completed( user, courses_having_prerequisites) if 'notlive' in request.GET: redirect_message = _( "The course you are looking for does not start until {date}." ).format(date=request.GET['notlive']) elif 'course_closed' in request.GET: redirect_message = _( "The course you are looking for is closed for enrollment as of {date}." ).format(date=request.GET['course_closed']) elif 'access_response_error' in request.GET: # This can be populated in a generalized way with fields from access response errors redirect_message = request.GET['access_response_error'] else: redirect_message = '' valid_verification_statuses = [ 'approved', 'must_reverify', 'pending', 'expired' ] display_sidebar_on_dashboard = verification_status['status'] in valid_verification_statuses and \ verification_status['should_display'] # Filter out any course enrollment course cards that are associated with fulfilled entitlements for entitlement in [ e for e in course_entitlements if e.enrollment_course_run is not None ]: course_enrollments = [ enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id ] # landing_page =request.POST['https://rc360service-q6uoj7vcrq-uc.a.run.app/r1/landingpage/getLandingPageDetails'] context = { 'urls': urls, 'landing_page': landing_page, 'programs_data': programs_data, 'enterprise_message': enterprise_message, 'consent_required_courses': consent_required_courses, 'enterprise_customer_name': enterprise_customer_name, 'enrollment_message': enrollment_message, 'redirect_message': Text(redirect_message), 'account_activation_messages': account_activation_messages, 'activate_account_message': activate_account_message, 'course_enrollments': course_enrollments, 'course_entitlements': course_entitlements, 'course_entitlement_available_sessions': course_entitlement_available_sessions, 'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions, 'course_optouts': course_optouts, 'staff_access': staff_access, 'errored_courses': errored_courses, 'show_courseware_links_for': show_courseware_links_for, 'all_course_modes': course_mode_info, 'cert_statuses': cert_statuses, 'credit_statuses': _credit_statuses(user, course_enrollments), 'show_email_settings_for': show_email_settings_for, 'reverifications': reverifications, 'verification_display': verification_status['should_display'], 'verification_status': verification_status['status'], 'verification_expiry': verification_status['verification_expiry'], 'verification_status_by_course': verify_status_by_course, 'verification_errors': verification_errors, 'denied_banner': denied_banner, 'billing_email': settings.PAYMENT_SUPPORT_EMAIL, 'user': user, 'logout_url': reverse('logout'), 'platform_name': platform_name, 'enrolled_courses_either_paid': enrolled_courses_either_paid, 'provider_states': [], 'courses_requirements_not_met': courses_requirements_not_met, 'nav_hidden': True, 'inverted_programs': inverted_programs, 'show_program_listing': ProgramsApiConfig.is_enabled(), 'show_dashboard_tabs': True, 'disable_courseware_js': True, 'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard, 'display_sidebar_on_dashboard': display_sidebar_on_dashboard, 'display_sidebar_account_activation_message': not (user.is_active or hide_dashboard_courses_until_activated), 'display_dashboard_courses': (user.is_active or not hide_dashboard_courses_until_activated), 'empty_dashboard_message': empty_dashboard_message, 'recovery_email_message': recovery_email_message, 'recovery_email_activation_message': recovery_email_activation_message, 'enterprise_learner_portal_enabled_message': enterprise_learner_portal_enabled_message, 'show_load_all_courses_link': show_load_all_courses_link(user, course_limit, course_enrollments), # TODO START: clean up as part of REVEM-199 (START) 'course_info': get_dashboard_course_info(user, course_enrollments), # TODO START: clean up as part of REVEM-199 (END) } context_from_plugins = get_plugins_view_context( ProjectType.LMS, COURSE_DASHBOARD_PLUGIN_VIEW_NAME, context) context.update(context_from_plugins) course = None context.update(get_experiment_user_metadata_context( course, user, )) if ecommerce_service.is_enabled(request.user): context.update({ 'use_ecommerce_payment_flow': True, 'ecommerce_payment_page': ecommerce_service.payment_page_url(), }) # Gather urls for course card resume buttons. resume_button_urls = ['' for entitlement in course_entitlements] for url in get_resume_urls_for_enrollments(user, course_enrollments).values(): resume_button_urls.append(url) # There must be enough urls for dashboard.html. Template creates course # cards for "enrollments + entitlements". context.update({'resume_button_urls': resume_button_urls}) return render_to_response('dashboard.html', context)
class LTIFields: """ Fields to define and obtain LTI tool from provider are set here, except credentials, which should be set in course settings:: `lti_id` is id to connect tool with credentials in course settings. It should not contain :: (double semicolon) `launch_url` is launch URL of tool. `custom_parameters` are additional parameters to navigate to proper book and book page. For example, for Vitalsource provider, `launch_url` should be *https://bc-staging.vitalsource.com/books/book*, and to get to proper book and book page, you should set custom parameters as:: vbid=put_book_id_here book_location=page/put_page_number_here Default non-empty URL for `launch_url` is needed due to oauthlib demand (URL scheme should be presented):: https://github.com/idan/oauthlib/blob/master/oauthlib/oauth1/rfc5849/signature.py#L136 """ display_name = String( display_name=_("Display Name"), help=_( "The display name for this component. " "Analytics reports may also use the display name to identify this component." ), scope=Scope.settings, default="LTI", ) lti_id = String( display_name=_("LTI ID"), help=Text(_( "Enter the LTI ID for the external LTI provider. " "This value must be the same LTI ID that you entered in the " "LTI Passports setting on the Advanced Settings page." "{break_tag}See {docs_anchor_open}the edX LTI documentation{anchor_close} for more details on this setting." )).format( break_tag=HTML(BREAK_TAG), docs_anchor_open=HTML(DOCS_ANCHOR_TAG_OPEN), anchor_close=HTML("</a>") ), default='', scope=Scope.settings ) launch_url = String( display_name=_("LTI URL"), help=Text(_( "Enter the URL of the external tool that this component launches. " "This setting is only used when Hide External Tool is set to False." "{break_tag}See {docs_anchor_open}the edX LTI documentation{anchor_close} for more details on this setting." )).format( break_tag=HTML(BREAK_TAG), docs_anchor_open=HTML(DOCS_ANCHOR_TAG_OPEN), anchor_close=HTML("</a>") ), default='http://www.example.com', scope=Scope.settings) custom_parameters = List( display_name=_("Custom Parameters"), help=Text(_( "Add the key/value pair for any custom parameters, such as the page your e-book should open to or " "the background color for this component." "{break_tag}See {docs_anchor_open}the edX LTI documentation{anchor_close} for more details on this setting." )).format( break_tag=HTML(BREAK_TAG), docs_anchor_open=HTML(DOCS_ANCHOR_TAG_OPEN), anchor_close=HTML("</a>") ), scope=Scope.settings) open_in_a_new_page = Boolean( display_name=_("Open in New Page"), help=_( "Select True if you want students to click a link that opens the LTI tool in a new window. " "Select False if you want the LTI content to open in an IFrame in the current page. " "This setting is only used when Hide External Tool is set to False. " ), default=True, scope=Scope.settings ) has_score = Boolean( display_name=_("Scored"), help=_( "Select True if this component will receive a numerical score from the external LTI system." ), default=False, scope=Scope.settings ) weight = Float( display_name=_("Weight"), help=_( "Enter the number of points possible for this component. " "The default value is 1.0. " "This setting is only used when Scored is set to True." ), default=1.0, scope=Scope.settings, values={"min": 0}, ) module_score = Float( help=_("The score kept in the xblock KVS -- duplicate of the published score in django DB"), default=None, scope=Scope.user_state ) score_comment = String( help=_("Comment as returned from grader, LTI2.0 spec"), default="", scope=Scope.user_state ) hide_launch = Boolean( display_name=_("Hide External Tool"), help=_( "Select True if you want to use this component as a placeholder for syncing with an external grading " "system rather than launch an external tool. " "This setting hides the Launch button and any IFrames for this component." ), default=False, scope=Scope.settings ) # Users will be presented with a message indicating that their e-mail/username would be sent to a third # party application. When "Open in New Page" is not selected, the tool automatically appears without any user action. # lint-amnesty, pylint: disable=line-too-long ask_to_send_username = Boolean( display_name=_("Request user's username"), # Translators: This is used to request the user's username for a third party service. help=_("Select True to request the user's username."), default=False, scope=Scope.settings ) ask_to_send_email = Boolean( display_name=_("Request user's email"), # Translators: This is used to request the user's email for a third party service. help=_("Select True to request the user's email address."), default=False, scope=Scope.settings ) description = String( display_name=_("LTI Application Information"), help=_( "Enter a description of the third party application. If requesting username and/or email, use this text box to inform users " # lint-amnesty, pylint: disable=line-too-long "why their username and/or email will be forwarded to a third party application." ), default="", scope=Scope.settings ) button_text = String( display_name=_("Button Text"), help=_( "Enter the text on the button used to launch the third party application." ), default="", scope=Scope.settings ) accept_grades_past_due = Boolean( display_name=_("Accept grades past deadline"), help=_("Select True to allow third party systems to post grades past the deadline."), default=True, scope=Scope.settings )
class UserMessagesTestCase(TestCase): """ Unit tests for page level user messages. """ def setUp(self): super(UserMessagesTestCase, self).setUp() self.student = UserFactory.create() self.request = RequestFactory().request() self.request.session = {} self.request.user = self.student MessageMiddleware().process_request(self.request) @ddt.data( ('Rock & Roll', '<div class="message-content">Rock & Roll</div>'), (Text('Rock & Roll'), '<div class="message-content">Rock & Roll</div>'), (HTML('<p>Hello, world!</p>'), '<div class="message-content"><p>Hello, world!</p></div>')) @ddt.unpack def test_message_escaping(self, message, expected_message_html): """ Verifies that a user message is escaped correctly. """ PageLevelMessages.register_user_message(self.request, UserMessageType.INFO, message) messages = list(PageLevelMessages.user_messages(self.request)) self.assertEqual(len(messages), 1) self.assertEquals(messages[0].message_html, expected_message_html) @ddt.data( (UserMessageType.ERROR, 'alert-danger', 'fa fa-warning'), (UserMessageType.INFO, 'alert-info', 'fa fa-bullhorn'), (UserMessageType.SUCCESS, 'alert-success', 'fa fa-check-circle'), (UserMessageType.WARNING, 'alert-warning', 'fa fa-warning'), ) @ddt.unpack def test_message_icon(self, message_type, expected_css_class, expected_icon_class): """ Verifies that a user message returns the correct CSS and icon classes. """ PageLevelMessages.register_user_message(self.request, message_type, TEST_MESSAGE) messages = list(PageLevelMessages.user_messages(self.request)) self.assertEqual(len(messages), 1) self.assertEquals(messages[0].css_class, expected_css_class) self.assertEquals(messages[0].icon_class, expected_icon_class) @ddt.data( (normalize_repr( PageLevelMessages.register_error_message), UserMessageType.ERROR), (normalize_repr( PageLevelMessages.register_info_message), UserMessageType.INFO), (normalize_repr(PageLevelMessages.register_success_message), UserMessageType.SUCCESS), (normalize_repr(PageLevelMessages.register_warning_message), UserMessageType.WARNING), ) @ddt.unpack def test_message_type(self, register_message_function, expected_message_type): """ Verifies that each user message function returns the correct type. """ register_message_function(self.request, TEST_MESSAGE) messages = list(PageLevelMessages.user_messages(self.request)) self.assertEqual(len(messages), 1) self.assertEquals(messages[0].type, expected_message_type)
def _format_discounted_price(original_price, discount_price): """Helper method that returns HTML containing a strikeout price with discount.""" # Separate out this string because it has a lot of syntax but no actual information for # translators to translate formatted_discount_price = HTML( '{s_dp}{discount_price}{e_p} {s_st}{s_op}{original_price}{e_p}{e_st}' ).format( original_price=original_price, discount_price=discount_price, s_op=HTML("<span class='price original'>"), s_dp=HTML("<span class='price discount'>"), s_st=HTML("<del aria-hidden='true'>"), e_p=HTML('</span>'), e_st=HTML('</del>'), ) return ( HTML(_( '{s_sr}Original price: {s_op}{original_price}{e_p}, discount price: {e_sr}{formatted_discount_price}' )).format( original_price=original_price, formatted_discount_price=formatted_discount_price, s_sr=HTML("<span class='sr-only'>"), s_op=HTML("<span class='price original'>"), e_p=HTML('</span>'), e_sr=HTML('</span>'), ) )
def course_id(self, obj): return HTML('<a href="{}">{}</a>').format( reverse("admin:course_overviews_courseoverview_change", args=(obj.enrollment.course_id, )), obj.enrollment.course_id)
def __unicode__(self): keys = [field.name for field in self._meta.get_fields() if field.name not in ('created', 'modified')] return HTML(u'{}<{!r}').format( HTML(self.__class__.__name__), {key: HTML(getattr(self, key)) for key in keys} )
def __str__(self): # lint-amnesty, pylint: disable=invalid-str-returned keys = [field.name for field in self._meta.get_fields() if field.name not in ('created', 'modified')] return HTML(u'{}<{!r}').format( HTML(self.__class__.__name__), {key: HTML(getattr(self, key)) for key in keys} )
def render_body(context, **pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(pageargs=pageargs) def requirejs(): return render_requirejs(context._locals(__M_locals)) request = context.get('request', UNDEFINED) EDX_ROOT_URL = context.get('EDX_ROOT_URL', UNDEFINED) static = _mako_get_namespace(context, 'static') LANGUAGE_CODE = context.get('LANGUAGE_CODE', UNDEFINED) def bodyclass(): return render_bodyclass(context._locals(__M_locals)) def title(): return render_title(context._locals(__M_locals)) context_library = context.get('context_library', UNDEFINED) def jsextra(): return render_jsextra(context._locals(__M_locals)) def content(): return render_content(context._locals(__M_locals)) getattr = context.get('getattr', UNDEFINED) uses_bootstrap = context.get('uses_bootstrap', UNDEFINED) def view_notes(): return render_view_notes(context._locals(__M_locals)) user = context.get('user', UNDEFINED) hasattr = context.get('hasattr', UNDEFINED) context_course = context.get('context_course', UNDEFINED) settings = context.get('settings', UNDEFINED) list = context.get('list', UNDEFINED) def page_bundle(): return render_page_bundle(context._locals(__M_locals)) def page_alert(): return render_page_alert(context._locals(__M_locals)) def header_extras(): return render_header_extras(context._locals(__M_locals)) def modal_placeholder(): return render_modal_placeholder(context._locals(__M_locals)) self = context.get('self', UNDEFINED) __M_writer = context.writer() __M_writer(u'\n') __M_writer(u'\n\n') __M_writer(u'\n') __M_writer(u'\n\n') __M_writer( u'\n<!doctype html>\n<!--[if lte IE 9]><html class="ie9 lte9" lang="' ) __M_writer(filters.html_escape(filters.decode.utf8(LANGUAGE_CODE))) __M_writer(u'"><![endif]-->\n<!--[if !IE]><<!--><html lang="') __M_writer(filters.html_escape(filters.decode.utf8(LANGUAGE_CODE))) __M_writer(u'"><!--<![endif]-->\n <head dir="') __M_writer(filters.html_escape(filters.decode.utf8(static.dir_rtl()))) __M_writer( u'">\n <meta charset="utf-8">\n <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">\n <meta name="openedx-release-line" content="' ) __M_writer(filters.html_escape(filters.decode.utf8(RELEASE_LINE))) __M_writer(u'" />\n <title>\n ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'title'): context['self'].title(**pageargs) __M_writer(u' |\n') if context_course: __M_writer(u' ') ctx_loc = context_course.location __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 ['ctx_loc'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n ') __M_writer( filters.html_escape( filters.decode.utf8( context_course.display_name_with_default))) __M_writer(u' |\n') elif context_library: __M_writer(u' ') __M_writer( filters.html_escape( filters.decode.utf8( context_library.display_name_with_default))) __M_writer(u' |\n') __M_writer(u' ') __M_writer( filters.html_escape(filters.decode.utf8(settings.STUDIO_NAME))) __M_writer(u'\n </title>\n\n ') jsi18n_path = "js/i18n/{language}/djangojs.js".format( language=LANGUAGE_CODE) __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 ['jsi18n_path'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n\n') if getattr(settings, 'CAPTURE_CONSOLE_LOG', False): __M_writer( u' <script type="text/javascript">\n var oldOnError = window.onerror;\n window.localStorage.setItem(\'console_log_capture\', JSON.stringify([]));\n\n window.onerror = function (message, url, lineno, colno, error) {\n if (oldOnError) {\n oldOnError.apply(this, arguments);\n }\n\n var messages = JSON.parse(window.localStorage.getItem(\'console_log_capture\'));\n messages.push([message, url, lineno, colno, (error || {}).stack]);\n window.localStorage.setItem(\'console_log_capture\', JSON.stringify(messages));\n }\n </script>\n' ) __M_writer(u'\n <script type="text/javascript" src="') __M_writer( filters.html_escape(filters.decode.utf8(static.url(jsi18n_path)))) __M_writer( u'"></script>\n <meta name="viewport" content="width=device-width,initial-scale=1">\n <meta name="path_prefix" content="' ) __M_writer(filters.html_escape(filters.decode.utf8(EDX_ROOT_URL))) __M_writer(u'">\n\n ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8(static.css(group=u'style-vendor')))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8( static.css(group=u'style-vendor-tinymce-content')))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8( static.css(group=u'style-vendor-tinymce-skin')))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n\n') if uses_bootstrap: __M_writer(u' <link rel="stylesheet" href="') __M_writer( filters.html_escape( filters.decode.utf8(static.url(self.attr.main_css)))) __M_writer(u'" type="text/css" media="all" />\n') else: __M_writer(u' ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8( static.css(group=(self.attr.main_css))))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n') __M_writer(u'\n ') runtime._include_file(context, u'widgets/segment-io.html', _template_uri) __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 </head>\n\n <body class="') __M_writer(filters.html_escape(filters.decode.utf8(static.dir_rtl()))) __M_writer(u' ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'bodyclass'): context['self'].bodyclass(**pageargs) __M_writer(u' lang_') __M_writer(filters.html_escape(filters.decode.utf8(LANGUAGE_CODE))) __M_writer(u'">\n ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'view_notes'): context['self'].view_notes(**pageargs) __M_writer(u'\n\n <a class="nav-skip" href="#main">') __M_writer( filters.html_escape(filters.decode.utf8( _("Skip to main content")))) __M_writer(u'</a>\n\n ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8(static.js(group=u'base_vendor')))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n\n ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8(static.webpack(entry=u'commons')))) finally: context.caller_stack.nextcaller = None __M_writer( u'\n\n <script type="text/javascript">\n window.baseUrl = "' ) __M_writer(js_escaped_string(settings.STATIC_URL)) __M_writer( u'";\n require.config({\n baseUrl: window.baseUrl\n });\n </script>\n\n <script type="text/javascript" src="' ) __M_writer( filters.html_escape( filters.decode.utf8(static.url("cms/js/require-config.js")))) __M_writer( u'"></script>\n\n <!-- view -->\n <div class="wrapper wrapper-view" dir="' ) __M_writer(filters.html_escape(filters.decode.utf8(static.dir_rtl()))) __M_writer(u'">\n ') online_help_token = self.online_help_token() if hasattr( self, 'online_help_token') else None __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 ['online_help_token'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n ') runtime._include_file(context, u'widgets/header.html', _template_uri, online_help_token=online_help_token) __M_writer(u'\n\n ') banner_messages = list(PageLevelMessages.user_messages(request)) __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 ['banner_messages'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n\n') if banner_messages: __M_writer( u' <div class="page-banner">\n <div class="user-messages">\n' ) for message in banner_messages: __M_writer(u' <div class="alert ') __M_writer( filters.html_escape(filters.decode.utf8( message.css_class))) __M_writer( u'" role="alert">\n <span class="icon icon-alert fa ' ) __M_writer( filters.html_escape(filters.decode.utf8( message.icon_class))) __M_writer(u'" aria-hidden="true"></span>\n ') __M_writer( filters.html_escape( filters.decode.utf8(HTML(message.message_html)))) __M_writer(u'\n </div>\n') __M_writer(u' </div>\n </div>\n') __M_writer(u'\n <div id="page-alert">\n ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'page_alert'): context['self'].page_alert(**pageargs) __M_writer( u'\n </div>\n\n <main id="main" aria-label="Content" tabindex="-1">\n <div id="content">\n ' ) if 'parent' not in context._data or not hasattr( context._data['parent'], 'content'): context['self'].content(**pageargs) __M_writer(u'\n </div>\n </main>\n\n') if user.is_authenticated: __M_writer(u' ') runtime._include_file(context, u'widgets/sock.html', _template_uri, online_help_token=online_help_token) __M_writer(u'\n') __M_writer(u' ') runtime._include_file(context, u'widgets/footer.html', _template_uri) __M_writer( u'\n\n <div id="page-notification"></div>\n </div>\n\n <div id="page-prompt"></div>\n\n ' ) if 'parent' not in context._data or not hasattr( context._data['parent'], 'modal_placeholder'): context['self'].modal_placeholder(**pageargs) __M_writer(u'\n\n ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'jsextra'): context['self'].jsextra(**pageargs) __M_writer(u'\n\n') if context_course: __M_writer(u' ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8( static.webpack( entry=u'js/factories/context_course')))) finally: context.caller_stack.nextcaller = None __M_writer( u'\n <script type="text/javascript">\n window.course = new ContextCourse({\n id: "' ) __M_writer(js_escaped_string(context_course.id)) __M_writer(u'",\n name: "') __M_writer( js_escaped_string(context_course.display_name_with_default)) __M_writer(u'",\n url_name: "') __M_writer(js_escaped_string(context_course.location.block_id)) __M_writer(u'",\n org: "') __M_writer(js_escaped_string(context_course.location.org)) __M_writer(u'",\n num: "') __M_writer(js_escaped_string(context_course.location.course)) __M_writer(u'",\n display_course_number: "') __M_writer(js_escaped_string(context_course.display_coursenumber)) __M_writer(u'",\n revision: "') __M_writer(js_escaped_string(context_course.location.branch)) __M_writer(u'",\n self_paced: ') __M_writer(dump_js_escaped_json(context_course.self_paced)) __M_writer(u'\n });\n </script>\n') if user.is_authenticated: __M_writer(u' ') def ccall(caller): def body(): __M_writer = context.writer() return '' return [body] context.caller_stack.nextcaller = runtime.Namespace( 'caller', context, callables=ccall(__M_caller)) try: __M_writer( filters.html_escape( filters.decode.utf8(static.webpack(entry=u'js/sock')))) finally: context.caller_stack.nextcaller = None __M_writer(u'\n') __M_writer(u' ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'page_bundle'): context['self'].page_bundle(**pageargs) __M_writer(u'\n ') runtime._include_file(context, u'widgets/segment-io-footer.html', _template_uri) __M_writer( u'\n <div class="modal-cover"></div>\n </body>\n</html>\n') return '' finally: context.caller_stack._pop_frame()
def format_strikeout_price(user, course, base_price=None, check_for_discount=True): """ Return a formatted price, including a struck-out original price if a discount applies, and also whether a discount was applied, as the tuple (formatted_price, has_discount). """ if base_price is None: base_price = get_course_prices(course, verified_only=True)[0] original_price = format_course_price(base_price) if not check_for_discount or can_receive_discount(user, course): discount_price = base_price * ( (100.0 - discount_percentage(course)) / 100) if discount_price == int(discount_price): discount_price = format_course_price( "{:0.0f}".format(discount_price)) else: discount_price = format_course_price( "{:0.2f}".format(discount_price)) # Separate out this string because it has a lot of syntax but no actual information for # translators to translate formatted_discount_price = HTML( u"{s_dp}{discount_price}{e_p} {s_st}{s_op}{original_price}{e_p}{e_st}" ).format( original_price=original_price, discount_price=discount_price, s_op=HTML("<span class='price original'>"), s_dp=HTML("<span class='price discount'>"), s_st=HTML("<del aria-hidden='true'>"), e_p=HTML("</span>"), e_st=HTML("</del>"), ) return (HTML( _(u"{s_sr}Original price: {s_op}{original_price}{e_p}, discount price: {e_sr}{formatted_discount_price}" )).format( original_price=original_price, formatted_discount_price=formatted_discount_price, s_sr=HTML("<span class='sr-only'>"), s_op=HTML("<span class='price original'>"), e_p=HTML("</span>"), e_sr=HTML("</span>"), ), True) else: return (HTML(u"<span class='price'>{}</span>").format(original_price), False)
def test_ungettext(self): for i in [1, 2]: out = Text(ungettext("1 & {}", "2 & {}", i)).format(HTML("<>")) self.assertEqual(out, "{} & <>".format(i))
class TestCourseSockView(SharedModuleStoreTestCase): """ Tests for the course verification sock fragment view. """ @classmethod def setUpClass(cls): super(TestCourseSockView, cls).setUpClass() # Create four courses cls.standard_course = CourseFactory.create() cls.verified_course = CourseFactory.create() cls.verified_course_update_expired = CourseFactory.create() cls.verified_course_already_enrolled = CourseFactory.create() # Assign each verifiable course an upgrade deadline add_course_mode(cls.verified_course, upgrade_deadline_expired=False) add_course_mode(cls.verified_course_update_expired, upgrade_deadline_expired=True) add_course_mode(cls.verified_course_already_enrolled, upgrade_deadline_expired=False) def setUp(self): super(TestCourseSockView, self).setUp() self.user = UserFactory.create() # Enroll the user in the four courses CourseEnrollmentFactory.create(user=self.user, course_id=self.standard_course.id) CourseEnrollmentFactory.create(user=self.user, course_id=self.verified_course.id) CourseEnrollmentFactory.create( user=self.user, course_id=self.verified_course_update_expired.id) CourseEnrollmentFactory.create( user=self.user, course_id=self.verified_course_already_enrolled.id, mode=CourseMode.VERIFIED) CommerceConfiguration.objects.create( enabled=True, checkout_on_ecommerce_service=True) # Log the user in self.client.login(username=self.user.username, password=TEST_PASSWORD) @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True) def test_standard_course(self): """ Ensure that a course that cannot be verified does not have a visible verification sock. """ response = self.client.get(course_home_url(self.standard_course)) self.assert_verified_sock_is_not_visible(self.standard_course, response) @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True) def test_verified_course(self): """ Ensure that a course that can be verified has a visible verification sock. """ response = self.client.get(course_home_url(self.verified_course)) self.assert_verified_sock_is_visible(self.verified_course, response) @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True) def test_verified_course_updated_expired(self): """ Ensure that a course that has an expired upgrade date does not display the verification sock. """ response = self.client.get( course_home_url(self.verified_course_update_expired)) self.assert_verified_sock_is_not_visible( self.verified_course_update_expired, response) @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True) def test_verified_course_user_already_upgraded(self): """ Ensure that a user that has already upgraded to a verified status cannot see the verification sock. """ response = self.client.get( course_home_url(self.verified_course_already_enrolled)) self.assert_verified_sock_is_not_visible( self.verified_course_already_enrolled, response) @override_waffle_flag(DISPLAY_COURSE_SOCK_FLAG, active=True) @mock.patch( 'openedx.features.course_experience.views.course_sock.format_strikeout_price', mock.Mock(return_value=(HTML("<span>DISCOUNT_PRICE</span>"), True))) def test_upgrade_message_discount(self): response = self.client.get(course_home_url(self.verified_course)) self.assertContains(response, "<span>DISCOUNT_PRICE</span>") def assert_verified_sock_is_visible(self, course, response): return self.assertContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False) def assert_verified_sock_is_not_visible(self, course, response): return self.assertNotContains(response, TEST_VERIFICATION_SOCK_LOCATOR, html=False)
class CourseHomeFragmentViewTests(ModuleStoreTestCase): """ Test Messages Displayed on the Course Home """ CREATE_USER = False def setUp(self): super(CourseHomeFragmentViewTests, self).setUp() CommerceConfiguration.objects.create(checkout_on_ecommerce_service=True) end = now() + timedelta(days=30) self.course = CourseFactory( start=now() - timedelta(days=30), end=end, ) self.url = course_home_url(self.course) CourseMode.objects.create(course_id=self.course.id, mode_slug=CourseMode.AUDIT) self.verified_mode = CourseMode.objects.create( course_id=self.course.id, mode_slug=CourseMode.VERIFIED, min_price=100, expiration_datetime=end, sku='test' ) self.user = UserFactory() self.client.login(username=self.user.username, password=TEST_PASSWORD) name = SHOW_UPGRADE_MSG_ON_COURSE_HOME.waffle_namespace._namespaced_name( SHOW_UPGRADE_MSG_ON_COURSE_HOME.flag_name) self.flag, __ = Flag.objects.update_or_create(name=name, defaults={'everyone': True}) def assert_upgrade_message_not_displayed(self): response = self.client.get(self.url) self.assertNotContains(response, 'section-upgrade') def assert_upgrade_message_displayed(self): response = self.client.get(self.url) self.assertContains(response, 'section-upgrade') url = EcommerceService().get_checkout_page_url(self.verified_mode.sku) self.assertContains(response, '<a class="btn-brand btn-upgrade"') self.assertContains(response, url) self.assertContains( response, u"Upgrade (<span class='price'>${price}</span>)".format(price=self.verified_mode.min_price), ) def test_no_upgrade_message_if_logged_out(self): self.client.logout() self.assert_upgrade_message_not_displayed() def test_no_upgrade_message_if_not_enrolled(self): self.assertEqual(len(CourseEnrollment.enrollments_for_user(self.user)), 0) self.assert_upgrade_message_not_displayed() def test_no_upgrade_message_if_verified_track(self): CourseEnrollment.enroll(self.user, self.course.id, CourseMode.VERIFIED) self.assert_upgrade_message_not_displayed() def test_no_upgrade_message_if_upgrade_deadline_passed(self): self.verified_mode.expiration_datetime = now() - timedelta(days=20) self.verified_mode.save() self.assert_upgrade_message_not_displayed() def test_no_upgrade_message_if_flag_disabled(self): self.flag.everyone = False self.flag.save() CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) self.assert_upgrade_message_not_displayed() def test_display_upgrade_message_if_audit_and_deadline_not_passed(self): CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) self.assert_upgrade_message_displayed() @mock.patch( 'openedx.features.course_experience.views.course_home.format_strikeout_price', mock.Mock(return_value=(HTML("<span>DISCOUNT_PRICE</span>"), True)) ) def test_upgrade_message_discount(self): CourseEnrollment.enroll(self.user, self.course.id, CourseMode.AUDIT) with SHOW_UPGRADE_MSG_ON_COURSE_HOME.override(True): response = self.client.get(self.url) self.assertContains(response, "<span>DISCOUNT_PRICE</span>")
class TestProblemTypeAccess(SharedModuleStoreTestCase, MasqueradeMixin): PROBLEM_TYPES = ['problem', 'openassessment', 'drag-and-drop-v2', 'done', 'edx_sga'] # 'html' is a component that just displays html, in these tests it is used to test that users who do not have access # to graded problems still have access to non-problems COMPONENT_TYPES = PROBLEM_TYPES + ['html'] MODE_TYPES = ['credit', 'honor', 'audit', 'verified', 'professional', 'no-id-professional'] GRADED_SCORE_WEIGHT_TEST_CASES = [ # graded, has_score, weight, is_gated (False, False, 0, False), (False, True, 0, False), (False, False, 1, False), (False, True, 1, False), (True, False, 0, False), (True, True, 0, False), (True, False, 1, False), (True, True, 1, True) ] @classmethod def setUpClass(cls): super(TestProblemTypeAccess, cls).setUpClass() cls.factory = RequestFactory() cls.courses = {} # default course is used for most tests, it includes an audit and verified track and all the problem types # defined in 'PROBLEM_TYPES' and 'GRADED_SCORE_WEIGHT_TEST_CASES' cls.courses['default'] = cls._create_course( run='testcourse1', display_name='Test Course Title', modes=['audit', 'verified'], component_types=cls.COMPONENT_TYPES ) # because default course is used for most tests self.course and self.problem_dict are set for ease of reference cls.course = cls.courses['default']['course'] cls.blocks_dict = cls.courses['default']['blocks'] # Create components with the cartesian product of possible values of # graded/has_score/weight for the test_graded_score_weight_values test. cls.graded_score_weight_blocks = {} for graded, has_score, weight, gated in cls.GRADED_SCORE_WEIGHT_TEST_CASES: case_name = ' Graded: ' + str(graded) + ' Has Score: ' + str(has_score) + ' Weight: ' + str(weight) block = ItemFactory.create( parent=cls.blocks_dict['vertical'], # has_score is determined by XBlock type. It is not a value set on an instance of an XBlock. # Therefore, we create a problem component when has_score is True # and an html component when has_score is False. category='problem' if has_score else 'html', graded=graded, weight=weight, metadata=METADATA if (graded and has_score and weight) else {}, ) # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=cls.blocks_dict['vertical'], category='html', graded=False, ) cls.graded_score_weight_blocks[(graded, has_score, weight)] = block host = os.environ.get('BOK_CHOY_HOSTNAME', '127.0.0.1') metadata_lti_xblock = { 'lti_id': 'correct_lti_id', 'launch_url': 'http://{}:{}/{}'.format(host, '8765', 'correct_lti_endpoint'), 'open_in_a_new_page': False } scored_lti_metadata = {} scored_lti_metadata.update(metadata_lti_xblock) scored_lti_metadata.update(METADATA) # add LTI blocks to default course cls.blocks_dict['lti_block'] = ItemFactory.create( parent=cls.blocks_dict['vertical'], category='lti_consumer', has_score=True, graded=True, metadata=scored_lti_metadata, ) # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=cls.blocks_dict['vertical'], category='html', graded=False, ) cls.blocks_dict['lti_block_not_scored'] = ItemFactory.create( parent=cls.blocks_dict['vertical'], category='lti_consumer', has_score=False, metadata=metadata_lti_xblock, ) # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=cls.blocks_dict['vertical'], category='html', graded=False, ) # add ungraded problem for xblock_handler test cls.blocks_dict['graded_problem'] = ItemFactory.create( parent=cls.blocks_dict['vertical'], category='problem', graded=True, metadata=METADATA, ) # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=cls.blocks_dict['vertical'], category='html', graded=False, ) cls.blocks_dict['ungraded_problem'] = ItemFactory.create( parent=cls.blocks_dict['vertical'], category='problem', graded=False, ) # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=cls.blocks_dict['vertical'], category='html', graded=False, ) cls.blocks_dict['audit_visible_graded_problem'] = ItemFactory.create( parent=cls.blocks_dict['vertical'], category='problem', graded=True, group_access={ CONTENT_GATING_PARTITION_ID: [ CONTENT_TYPE_GATE_GROUP_IDS['limited_access'], CONTENT_TYPE_GATE_GROUP_IDS['full_access'] ] }, ) # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=cls.blocks_dict['vertical'], category='html', graded=False, ) # audit_only course only has an audit track available cls.courses['audit_only'] = cls._create_course( run='audit_only_course_run_1', display_name='Audit Only Test Course Title', modes=['audit'], component_types=['problem', 'html'] ) # all_track_types course has all track types defined in MODE_TYPES cls.courses['all_track_types'] = cls._create_course( run='all_track_types_run_1', display_name='All Track/Mode Types Test Course Title', modes=cls.MODE_TYPES, component_types=['problem', 'html'] ) cls.courses['expired_upgrade_deadline'] = cls._create_course( run='expired_upgrade_deadline_run_1', display_name='Expired Upgrade Deadline Course Title', modes=['audit', 'verified'], component_types=['problem', 'html'], expired_upgrade_deadline=True ) def setUp(self): super(TestProblemTypeAccess, self).setUp() # enroll all users into the all track types course self.users = {} for mode_type in self.MODE_TYPES: self.users[mode_type] = UserFactory.create(username=mode_type) CourseEnrollmentFactory.create( user=self.users[mode_type], course_id=self.courses['all_track_types']['course'].id, mode=mode_type ) # create audit_user for ease of reference self.audit_user = self.users['audit'] # enroll audit and verified users into default course for mode_type in ['audit', 'verified']: CourseEnrollmentFactory.create( user=self.users[mode_type], course_id=self.course.id, mode=mode_type ) # enroll audit user into the audit_only course CourseEnrollmentFactory.create( user=self.audit_user, course_id=self.courses['audit_only']['course'].id, mode='audit' ) # enroll audit user into the upgrade expired course CourseEnrollmentFactory.create( user=self.audit_user, course_id=self.courses['expired_upgrade_deadline']['course'].id, mode='audit' ) ContentTypeGatingConfig.objects.create(enabled=True, enabled_as_of=datetime(2018, 1, 1)) @classmethod def _create_course(cls, run, display_name, modes, component_types, expired_upgrade_deadline=False): """ Helper method to create a course Arguments: run (str): name of course run display_name (str): display name of course modes (list of str): list of modes/tracks this course should have component_types (list of str): list of problem types this course should have Returns: (dict): { 'course': (CourseDescriptorWithMixins): course definition 'blocks': (dict) { 'block_category_1': XBlock representing that block, 'block_category_2': XBlock representing that block, .... } """ start_date = timezone.now() - timedelta(weeks=1) course = CourseFactory.create(run=run, display_name=display_name, start=start_date) for mode in modes: if expired_upgrade_deadline and mode == 'verified': CourseModeFactory.create( course_id=course.id, mode_slug=mode, expiration_datetime=start_date + timedelta(days=365), ) else: CourseModeFactory.create(course_id=course.id, mode_slug=mode) with cls.store.bulk_operations(course.id): blocks_dict = {} chapter = ItemFactory.create( parent=course, display_name='Overview', ) blocks_dict['chapter'] = ItemFactory.create( parent=course, category='chapter', display_name='Week 1', ) blocks_dict['sequential'] = ItemFactory.create( parent=chapter, category='sequential', display_name='Lesson 1', ) blocks_dict['vertical'] = ItemFactory.create( parent=blocks_dict['sequential'], category='vertical', display_name='Lesson 1 Vertical - Unit 1', ) for component_type in component_types: block = ItemFactory.create( parent=blocks_dict['vertical'], category=component_type, graded=True, metadata={} if (component_type == 'html' or len(modes) == 1) else METADATA ) blocks_dict[component_type] = block # Intersperse HTML so that the content-gating renders in all blocks ItemFactory.create( parent=blocks_dict['vertical'], category='html', graded=False, ) return { 'course': course, 'blocks': blocks_dict, } @ddt.data( ('problem', True), ('openassessment', True), ('drag-and-drop-v2', True), ('done', True), ('edx_sga', True), ('lti_block', True), ('ungraded_problem', False), ('lti_block_not_scored', False), ('audit_visible_graded_problem', False), ) @ddt.unpack def test_access_to_problems(self, prob_type, is_gated): _assert_block_is_gated( block=self.blocks_dict[prob_type], user=self.users['audit'], course=self.course, is_gated=is_gated, request_factory=self.factory, ) _assert_block_is_gated( block=self.blocks_dict[prob_type], user=self.users['verified'], course=self.course, is_gated=False, request_factory=self.factory, ) @ddt.data( *GRADED_SCORE_WEIGHT_TEST_CASES ) @ddt.unpack def test_graded_score_weight_values(self, graded, has_score, weight, is_gated): # Verify that graded, has_score and weight must all be true for a component to be gated block = self.graded_score_weight_blocks[(graded, has_score, weight)] _assert_block_is_gated( block=block, user=self.audit_user, course=self.course, is_gated=is_gated, request_factory=self.factory, ) @ddt.data( ('audit', 'problem', 'default', True), ('verified', 'problem', 'default', False), ('audit', 'html', 'default', False), ('verified', 'html', 'default', False), ('audit', 'problem', 'audit_only', False), ('audit', 'html', 'audit_only', False), ('credit', 'problem', 'all_track_types', False), ('credit', 'html', 'all_track_types', False), ('honor', 'problem', 'all_track_types', False), ('honor', 'html', 'all_track_types', False), ('audit', 'problem', 'all_track_types', True), ('audit', 'html', 'all_track_types', False), ('verified', 'problem', 'all_track_types', False), ('verified', 'html', 'all_track_types', False), ('professional', 'problem', 'all_track_types', False), ('professional', 'html', 'all_track_types', False), ('no-id-professional', 'problem', 'all_track_types', False), ('no-id-professional', 'html', 'all_track_types', False), ) @ddt.unpack def test_access_based_on_track(self, user_track, component_type, course, is_gated): """ If a user is enrolled as an audit user they should not have access to graded problems, unless there is no paid track option. All paid type tracks should have access to all types of content. All users should have access to non-problem component types, the 'html' components test that. """ _assert_block_is_gated( block=self.courses[course]['blocks'][component_type], user=self.users[user_track], course=self.courses[course]['course'], is_gated=is_gated, request_factory=self.factory, ) def test_access_expired_upgrade_deadline(self): """ If a user is enrolled as an audit user and the upgrade deadline has passed the user will continue to see gated content, but the upgrade messaging will be removed. """ _assert_block_is_gated( block=self.courses['expired_upgrade_deadline']['blocks']['problem'], user=self.users['audit'], course=self.courses['expired_upgrade_deadline']['course'], is_gated=True, request_factory=self.factory, has_upgrade_link=False ) @ddt.data( ('problem', 'graded_problem', 'audit', 404), ('problem', 'graded_problem', 'verified', 200), ('problem', 'ungraded_problem', 'audit', 200), ('problem', 'ungraded_problem', 'verified', 200), ) @ddt.unpack def test_xblock_handlers(self, xblock_type, xblock_name, user, status_code): """ Test the ajax calls to the problem xblock to ensure the LMS is sending back the expected response codes on requests when content is gated for audit users (404) and when it is available to audit users (200). Content is always available to verified users. """ problem_location = self.blocks_dict[xblock_name].scope_ids.usage_id url = reverse( 'xblock_handler', kwargs={ 'course_id': six.text_type(self.course.id), 'usage_id': quote_slashes(six.text_type(problem_location)), 'handler': 'xmodule_handler', 'suffix': 'problem_show', } ) self.client.login(username=self.users[user].username, password=TEST_PASSWORD) response = self.client.post(url) self.assertEqual(response.status_code, status_code) @ddt.data( InstructorFactory, StaffFactory, BetaTesterFactory, OrgStaffFactory, OrgInstructorFactory, GlobalStaffFactory, ) def test_access_course_team_users(self, role_factory): """ Test that members of the course team do not lose access to graded content """ # There are two types of course team members: instructor and staff # they have different privileges, but for the purpose of this test the important thing is that they should both # have access to all graded content if role_factory == GlobalStaffFactory: user = role_factory.create() else: user = role_factory.create(course_key=self.course.id) CourseEnrollmentFactory.create( user=user, course_id=self.course.id, mode='audit', ) # assert that course team members have access to graded content _assert_block_is_gated( block=self.blocks_dict['problem'], user=user, course=self.course, is_gated=False, request_factory=self.factory, ) @ddt.data( FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_GROUP_MODERATOR ) def test_access_user_with_forum_role(self, role_name): """ Test that users with a given forum role do not lose access to graded content """ user = UserFactory.create() role = RoleFactory(name=role_name, course_id=self.course.id) role.users.add(user) CourseEnrollmentFactory.create( user=user, course_id=self.course.id, mode='audit', ) _assert_block_is_gated( block=self.blocks_dict['problem'], user=user, course=self.course, is_gated=False, request_factory=self.factory, ) @ddt.data( (False, True), (True, False), ) @ddt.unpack def test_content_gating_holdback(self, put_user_in_holdback, is_gated): """ Test that putting a user in the content gating holdback disables content gating. """ user = UserFactory.create() enrollment = CourseEnrollment.enroll(user, self.course.id) if put_user_in_holdback: FBEEnrollmentExclusion.objects.create(enrollment=enrollment) graded, has_score, weight = True, True, 1 block = self.graded_score_weight_blocks[(graded, has_score, weight)] _assert_block_is_gated( block=block, user=user, course=self.course, is_gated=is_gated, request_factory=self.factory, ) @ddt.data( ({'user_partition_id': CONTENT_GATING_PARTITION_ID, 'group_id': CONTENT_TYPE_GATE_GROUP_IDS['limited_access']}, True), ({'user_partition_id': CONTENT_GATING_PARTITION_ID, 'group_id': CONTENT_TYPE_GATE_GROUP_IDS['full_access']}, False), ({'user_partition_id': ENROLLMENT_TRACK_PARTITION_ID, 'group_id': settings.COURSE_ENROLLMENT_MODES['audit']['id']}, True), ({'user_partition_id': ENROLLMENT_TRACK_PARTITION_ID, 'group_id': settings.COURSE_ENROLLMENT_MODES['verified']['id']}, False), ({'role': 'staff'}, False), ({'role': 'student'}, True), ({'username': '******'}, True), ({'username': '******'}, False), ) @ddt.unpack def test_masquerade(self, masquerade_config, is_gated): instructor = UserFactory.create() CourseEnrollmentFactory.create( user=instructor, course_id=self.course.id, mode='audit' ) CourseInstructorRole(self.course.id).add_users(instructor) self.client.login(username=instructor.username, password=TEST_PASSWORD) self.update_masquerade(**masquerade_config) block = self.blocks_dict['problem'] block_view_url = reverse('render_xblock', kwargs={'usage_key_string': six.text_type(block.scope_ids.usage_id)}) response = self.client.get(block_view_url) if is_gated: self.assertEqual(response.status_code, 404) else: self.assertEqual(response.status_code, 200) @ddt.data( InstructorFactory, StaffFactory, BetaTesterFactory, OrgStaffFactory, OrgInstructorFactory, GlobalStaffFactory, ) def test_access_masquerade_as_course_team_users(self, role_factory): """ Test that when masquerading as members of the course team you do not lose access to graded content """ # There are two types of course team members: instructor and staff # they have different privileges, but for the purpose of this test the important thing is that they should both # have access to all graded content staff_user = StaffFactory.create(password=TEST_PASSWORD, course_key=self.course.id) CourseEnrollmentFactory.create( user=staff_user, course_id=self.course.id, mode='audit' ) self.client.login(username=staff_user.username, password=TEST_PASSWORD) if role_factory == GlobalStaffFactory: user = role_factory.create() else: user = role_factory.create(course_key=self.course.id) CourseEnrollment.enroll(user, self.course.id) self.update_masquerade(username=user.username) block = self.blocks_dict['problem'] block_view_url = reverse('render_xblock', kwargs={'usage_key_string': six.text_type(block.scope_ids.usage_id)}) response = self.client.get(block_view_url) self.assertEqual(response.status_code, 200) @ddt.data( FORUM_ROLE_COMMUNITY_TA, FORUM_ROLE_ADMINISTRATOR, FORUM_ROLE_MODERATOR, FORUM_ROLE_GROUP_MODERATOR ) def test_access_masquerade_as_user_with_forum_role(self, role_name): """ Test that when masquerading as a user with a given forum role you do not lose access to graded content """ staff_user = StaffFactory.create(password=TEST_PASSWORD, course_key=self.course.id) CourseEnrollmentFactory.create( user=staff_user, course_id=self.course.id, mode='audit' ) self.client.login(username=staff_user.username, password=TEST_PASSWORD) user = UserFactory.create() role = RoleFactory(name=role_name, course_id=self.course.id) role.users.add(user) CourseEnrollmentFactory.create( user=user, course_id=self.course.id, mode='audit' ) self.update_masquerade(username=user.username) _assert_block_is_gated( block=self.blocks_dict['problem'], user=user, course=self.course, is_gated=False, request_factory=self.factory, ) @patch( 'openedx.features.content_type_gating.partitions.format_strikeout_price', Mock(return_value=(HTML("<span>DISCOUNT_PRICE</span>"), True)) ) def test_discount_display(self): with patch.object(ContentTypeGatingPartition, '_get_checkout_link', return_value='#'): block_content = _get_content_from_lms_index( block=self.blocks_dict['problem'], user_id=self.audit_user.id, course=self.course, request_factory=self.factory, ) assert '<span>DISCOUNT_PRICE</span>' in block_content
def response_a11y_data(self, response, inputfields, responsetype_id, problem_data): """ Construct data to be used for a11y. Arguments: response (object): xml response object inputfields (list): list of inputfields in a responsetype responsetype_id (str): responsetype id problem_data (dict): dict to be filled with response data """ # if there are no inputtypes then don't do anything if not inputfields: return element_to_be_deleted = None label = '' if len(inputfields) > 1: response.set('multiple_inputtypes', 'true') group_label_tag = response.find('label') group_description_tags = response.findall('description') group_label_tag_id = u'multiinput-group-label-{}'.format(responsetype_id) group_label_tag_text = '' if group_label_tag is not None: group_label_tag.tag = 'p' group_label_tag.set('id', group_label_tag_id) group_label_tag.set('class', 'multi-inputs-group-label') group_label_tag_text = stringify_children(group_label_tag) response.set('multiinput-group-label-id', group_label_tag_id) group_description_ids = [] for index, group_description_tag in enumerate(group_description_tags): group_description_tag_id = u'multiinput-group-description-{}-{}'.format(responsetype_id, index) group_description_tag.tag = 'p' group_description_tag.set('id', group_description_tag_id) group_description_tag.set('class', 'multi-inputs-group-description question-description') group_description_ids.append(group_description_tag_id) if group_description_ids: response.set('multiinput-group_description_ids', ' '.join(group_description_ids)) for inputfield in inputfields: problem_data[inputfield.get('id')] = { 'group_label': group_label_tag_text, 'label': inputfield.attrib.get('label', ''), 'descriptions': {} } else: # Extract label value from <label> tag or label attribute from inside the responsetype responsetype_label_tag = response.find('label') if responsetype_label_tag is not None: label = stringify_children(responsetype_label_tag) # store <label> tag containing question text to delete # it later otherwise question will be rendered twice element_to_be_deleted = responsetype_label_tag elif 'label' in inputfields[0].attrib: # in this case we have old problems with label attribute and p tag having question in it # we will pick the first sibling of responsetype if its a p tag and match the text with # the label attribute text. if they are equal then we will use this text as question. # Get first <p> tag before responsetype, this <p> may contains the question text. p_tag = response.xpath('preceding-sibling::*[1][self::p]') if p_tag and p_tag[0].text == inputfields[0].attrib['label']: label = stringify_children(p_tag[0]) element_to_be_deleted = p_tag[0] else: # In this case the problems don't have tag or label attribute inside the responsetype # so we will get the first preceding label tag w.r.t to this responsetype. # This will take care of those multi-question problems that are not using --- in their markdown. label_tag = response.xpath('preceding-sibling::*[1][self::label]') if label_tag: label = stringify_children(label_tag[0]) element_to_be_deleted = label_tag[0] # delete label or p element only if inputtype is fully accessible if inputfields[0].tag in ACCESSIBLE_CAPA_INPUT_TYPES and element_to_be_deleted is not None: element_to_be_deleted.getparent().remove(element_to_be_deleted) # Extract descriptions and set unique id on each description tag description_tags = response.findall('description') description_id = 1 descriptions = OrderedDict() for description in description_tags: descriptions[ "description_%s_%i" % (responsetype_id, description_id) ] = HTML(stringify_children(description)) response.remove(description) description_id += 1 problem_data[inputfields[0].get('id')] = { 'label': HTML(label.strip()) if label else '', 'descriptions': descriptions }
def _add_honor_code_field(self, form_desc, required=True): """Add an honor code field to a form description. Arguments: form_desc: A form description Keyword Arguments: required (bool): Whether this field is required; defaults to True """ separate_honor_and_tos = self._is_field_visible("terms_of_service") # Separate terms of service and honor code checkboxes if separate_honor_and_tos: terms_label = _(u"Honor Code") terms_link = marketing_link("HONOR") # Combine terms of service and honor code checkboxes else: # Translators: This is a legal document users must agree to # in order to register a new account. terms_label = _(u"Terms of Service and Honor Code") #terms_link = marketing_link("HONOR") terms_link = "/tos" # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. label = Text( _(u"I agree to the {platform_name} {terms_of_service_link_start}{terms_of_service}{terms_of_service_link_end}" )).format( platform_name=configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_label, terms_of_service_link_start=HTML( "<a href='{terms_link}' target='_blank'>").format( terms_link=terms_link), terms_of_service_link_end=HTML("</a>"), ) # Translators: "Terms of Service" is a legal document users must agree to # in order to register a new account. error_msg = _( u"You must agree to the {platform_name} {terms_of_service}" ).format(platform_name=configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_label) field_type = 'checkbox' if not separate_honor_and_tos: current_request = crum.get_current_request() field_type = 'plaintext' pp_link = marketing_link("PRIVACY") label = Text( _(u"By creating an account with {platform_name}, you agree \ to abide by our {platform_name} \ {terms_of_service_link_start}{terms_of_service}{terms_of_service_link_end} \ and agree to our {privacy_policy_link_start}Privacy Policy{privacy_policy_link_end}." )).format( platform_name=configuration_helpers.get_value( "PLATFORM_NAME", settings.PLATFORM_NAME), terms_of_service=terms_label, terms_of_service_link_start=HTML( "<a href='{terms_url}' target='_blank'>").format( terms_url=terms_link), terms_of_service_link_end=HTML("</a>"), privacy_policy_link_start=HTML( "<a href='{pp_url}' target='_blank'>").format( pp_url=pp_link), privacy_policy_link_end=HTML("</a>"), ) form_desc.add_field( "honor_code", label=label, field_type=field_type, default=False, required=required, error_messages={"required": error_msg}, )
def render_body(context, online_help_token, use_cookie_banner=False, **pageargs): __M_caller = context.caller_stack._push_frame() try: __M_locals = __M_dict_builtin(use_cookie_banner=use_cookie_banner, pageargs=pageargs, online_help_token=online_help_token) _import_ns = {} _mako_get_namespace(context, '__anon_0x7f4938752a50')._populate( _import_ns, [u'login_query']) csrf_token = _import_ns.get('csrf_token', context.get('csrf_token', UNDEFINED)) settings = _import_ns.get('settings', context.get('settings', UNDEFINED)) def navigation_top(): return render_navigation_top(context._locals(__M_locals)) len = _import_ns.get('len', context.get('len', UNDEFINED)) course = _import_ns.get('course', context.get('course', UNDEFINED)) def js_extra(): return render_js_extra(context._locals(__M_locals)) static = _mako_get_namespace(context, 'static') user = _import_ns.get('user', context.get('user', UNDEFINED)) LANGUAGE_CODE = _import_ns.get('LANGUAGE_CODE', context.get('LANGUAGE_CODE', UNDEFINED)) uses_pattern_library = _import_ns.get( 'uses_pattern_library', context.get('uses_pattern_library', UNDEFINED)) __M_writer = context.writer() __M_writer(u'\n') __M_writer(u'\n\n') __M_writer(u'\n') __M_writer(u'\n') __M_writer(u'\n\n') if 'parent' not in context._data or not hasattr( context._data['parent'], 'navigation_top'): context['self'].navigation_top(**pageargs) __M_writer(u'\n\n') if uses_pattern_library: __M_writer(u' ') if 'parent' not in context._data or not hasattr( context._data['parent'], 'js_extra'): context['self'].js_extra(**pageargs) __M_writer(u'\n') __M_writer(u'\n') unsupported_browser_alert_versions = configuration_helpers.get_value( 'UNSUPPORTED_BROWSER_ALERT_VERSIONS', settings.FEATURES.get('UNSUPPORTED_BROWSER_ALERT_VERSIONS')) __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 ['unsupported_browser_alert_versions'] if __M_key in __M_locals_builtin_stored ])) __M_writer(u'\n') if waffle.switch_is_active('enable_unsupported_browser_alert'): __M_writer(u' <script>\n var $buoop = {\n notify:') __M_writer(filters.decode.utf8(unsupported_browser_alert_versions)) __M_writer( u',\n api:5,\n reminder:0\n };\n function $buo_f() {\n var e = document.createElement("script");\n e.src = "//browser-update.org/update.min.js";\n document.body.appendChild(e);\n };\n try {document.addEventListener("DOMContentLoaded", $buo_f,false)}\n catch(e){window.attachEvent("onload", $buo_f)}\n </script>\n' ) __M_writer(u'\n<header class="global-header ') __M_writer( filters.html_escape(filters.decode.utf8('slim' if course else ''))) __M_writer(u'">\n') if use_cookie_banner: __M_writer(u' ') __M_writer( filters.html_escape( filters.decode.utf8( static.renderReact(component="CookiePolicyBanner", id="cookie-policy-banner", props={})))) __M_writer(u'\n') __M_writer(u' <div class="main-header">\n ') runtime._include_file(context, u'navbar-logo-header.html', _template_uri, online_help_token=online_help_token) __M_writer( u'\n <div class="hamburger-menu" role="button" aria-label=') __M_writer(filters.html_escape(filters.decode.utf8(_("Options Menu")))) __M_writer( u' aria-expanded="false" aria-controls="mobile-menu" tabindex="0">\n <span class="line"></span>\n <span class="line"></span>\n <span class="line"></span>\n <span class="line"></span>\n </div>\n' ) if user.is_authenticated: __M_writer(u' ') runtime._include_file(context, u'navbar-authenticated.html', _template_uri, online_help_token=online_help_token) __M_writer(u'\n') else: __M_writer(u' ') runtime._include_file(context, u'navbar-not-authenticated.html', _template_uri, online_help_token=online_help_token) __M_writer(u'\n') __M_writer( u' </div>\n <div class="mobile-menu hidden" aria-label=') __M_writer(filters.html_escape(filters.decode.utf8(_("More Options")))) __M_writer(u' role="menu" id="mobile-menu"></div>\n</header>\n\n') if course: __M_writer( u'<!--[if lte IE 9]>\n<div class="ie-banner" aria-hidden="true">' ) __M_writer( filters.html_escape( filters.decode.utf8( Text( _('{begin_strong}Warning:{end_strong} Your browser is not fully supported. We strongly recommend using {chrome_link} or {ff_link}.' )). format( begin_strong=HTML('<strong>'), end_strong=HTML('</strong>'), chrome_link=HTML( '<a href="https://www.google.com/chrome" target="_blank">Chrome</a>' ), ff_link=HTML( '<a href="http://www.mozilla.org/firefox" target="_blank">Firefox</a>' ), )))) __M_writer(u'</div>\n<![endif]-->\n') __M_writer(u'\n') if settings.FEATURES.get('ENABLE_COOKIE_CONSENT', False): __M_writer(u' ') runtime._include_file(context, u'../widgets/cookie-consent.html', _template_uri) __M_writer(u'\n') __M_writer(u'\n') if header_language_selector_is_enabled(): __M_writer(u' ') languages = released_languages() __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 ['languages'] if __M_key in __M_locals_builtin_stored])) __M_writer(u'\n') if len(languages) > 1: __M_writer( u' <form action="/i18n/setlang/" method="post" class="settings-language-form" id="language-settings-form">\n <input type="hidden" id="csrf_token" name="csrfmiddlewaretoken" value="' ) __M_writer(filters.html_escape( filters.decode.utf8(csrf_token))) __M_writer(u'">\n') if user.is_authenticated: __M_writer( u' <input title="preference api" type="hidden" class="url-endpoint" value="' ) __M_writer( filters.html_escape( filters.decode.utf8( reverse('preferences_api', kwargs={'username': user.username})))) __M_writer(u'" data-user-is-authenticated="true">\n') else: __M_writer( u' <input title="session update url" type="hidden" class="url-endpoint" value="' ) __M_writer( filters.html_escape( filters.decode.utf8(reverse('session_language')))) __M_writer(u'" data-user-is-authenticated="false">\n') __M_writer(u' <label><span class="sr">') __M_writer( filters.html_escape( filters.decode.utf8(_("Choose Language")))) __M_writer( u'</span>\n <select class="input select language-selector" id="settings-language-value" name="language">\n' ) for language in languages: if language[0] == LANGUAGE_CODE: __M_writer(u' <option value="') __M_writer( filters.html_escape( filters.decode.utf8(language[0]))) __M_writer(u'" selected="selected">') __M_writer( filters.html_escape( filters.decode.utf8(language[1]))) __M_writer(u'</option>\n') else: __M_writer(u' <option value="') __M_writer( filters.html_escape( filters.decode.utf8(language[0]))) __M_writer(u'" >') __M_writer( filters.html_escape( filters.decode.utf8(language[1]))) __M_writer(u'</option>\n') __M_writer( u' </select>\n </label>\n </form>\n' ) return '' finally: context.caller_stack._pop_frame()