def assert_access_to_gated_content(self, user, expected_access): """ Verifies access to gated content for the given user is as expected. """ # clear the request cache to flush any cached access results RequestCache.clear_request_cache() # access to gating content (seq1) remains constant self.assertTrue(bool(has_access(user, 'load', self.seq1, self.course.id))) # access to gated content (seq2) is as expected self.assertEquals(bool(has_access(user, 'load', self.seq2, self.course.id)), expected_access)
def assert_access_to_gated_content(self, user): """ Verifies access to gated content for the given user is as expected. """ # clear the request cache to flush any cached access results RequestCache.clear_all_namespaces() # access to gating content (seq1) remains constant self.assertTrue(bool(has_access(user, 'load', self.seq1, self.course.id))) # access to gated content (seq2) remains constant, access is prevented in SeqModule loading self.assertTrue(bool(has_access(user, 'load', self.seq2, self.course.id)))
def _get_course(self, request, course_id): """ Returns the course after parsing the id, checking access, and checking existence. """ try: course_key = get_course_key(request, course_id) except InvalidKeyError: raise self.api_error( status_code=status.HTTP_400_BAD_REQUEST, developer_message='The provided course key cannot be parsed.', error_code='invalid_course_key' ) if not has_access(request.user, 'staff', course_key): raise self.api_error( status_code=status.HTTP_403_FORBIDDEN, developer_message='The course does not exist.', error_code='user_or_course_does_not_exist', ) course = modulestore().get_course(course_key, depth=0) if not course: raise self.api_error( status_code=status.HTTP_404_NOT_FOUND, developer_message='The course does not exist.', error_code='user_or_course_does_not_exist', ) return course
def get_course_calendar(user, course_key_string): try: from icalendar import Calendar, Event except ImportError: logging.error('Calendar module not installed') return course_key = CourseKey.from_string(course_key_string) checked = ['course', 'vertical', 'sequential'] items = modulestore().get_items(course_key) hour = timedelta(hours=1) cal = Calendar() for num, item in enumerate(items): if not item.category in checked: continue if not item.graded: continue if not has_access(user, 'load', item, course_key=item.location.course_key): continue if not item.due: continue if item.category != 'course': format = item.format or item.get_parent().format else: format = 'course' url = u'http://{}{}'.format(settings.SITE_NAME, _reverse_usage(item)) event = Event() summary = u'Type: {}; Name: {}({})'.format(format, item.display_name, url).encode('utf-8') event.add('summary', summary) event.add('dtstart', item.due - hour) event.add('dtend', item.due) cal.add_component(event) text = cal.to_ical().decode('utf-8') return text
def transform_block_filters(self, usage_info, block_structure): user = usage_info.user result_list = SplitTestTransformer().transform_block_filters(usage_info, block_structure) user_partitions = block_structure.get_transformer_data(self, 'user_partitions') if not user_partitions: return [block_structure.create_universal_filter()] user_groups = get_user_partition_groups(usage_info.course_key, user_partitions, user, 'id') for block_key in block_structure.topological_traversal(): transformer_block_field = block_structure.get_transformer_block_field( block_key, self, 'merged_group_access' ) access_denying_partition_id = transformer_block_field.get_access_denying_partition( user_groups ) access_denying_partition = get_partition_from_id(user_partitions, access_denying_partition_id) if not has_access(user, 'staff', block_key) and access_denying_partition: user_group = user_groups.get(access_denying_partition.id) allowed_groups = transformer_block_field.get_allowed_groups()[access_denying_partition.id] access_denied_message = access_denying_partition.access_denied_message( block_key, user, user_group, allowed_groups ) block_structure.override_xblock_field( block_key, 'authorization_denial_reason', access_denying_partition.name ) block_structure.override_xblock_field( block_key, 'authorization_denial_message', access_denied_message ) group_access_filter = block_structure.create_removal_filter( lambda block_key: ( not has_access(user, 'staff', block_key) and block_structure.get_transformer_block_field( block_key, self, 'merged_group_access' ).get_access_denying_partition(user_groups) is not None and block_structure.get_xblock_field(block_key, 'authorization_denial_message') is None ) ) result_list.append(group_access_filter) return result_list
def check_results(user, expected_accessible_blocks, blocks_with_differing_access): """ Verifies the results of transforming the blocks in the course for the given user. """ self.client.login(username=user.username, password=self.password) block_structure = get_course_blocks(user, self.course.location, transformers=transformers) # Enumerate through all the blocks that were created in the # course for i, xblock_key in enumerate(self.xblock_keys): # verify existence of the block block_structure_result = block_structure.has_block(xblock_key) has_access_result = bool(has_access(user, 'load', self.get_block(i), course_key=self.course.id)) # compare with expected value self.assertEquals( block_structure_result, i in expected_accessible_blocks, "block_structure return value {0} not equal to expected value for block {1} for user {2}".format( block_structure_result, i, user.username ) ) # compare with has_access result if i in blocks_with_differing_access: self.assertNotEqual( block_structure_result, has_access_result, "block structure ({0}) & has_access ({1}) results are equal for block {2} for user {3}".format( block_structure_result, has_access_result, i, user.username ) ) else: self.assertEquals( block_structure_result, has_access_result, "block structure ({0}) & has_access ({1}) results not equal for block {2} for user {3}".format( block_structure_result, has_access_result, i, user.username ) ) self.client.logout()
def transform_block_filters(self, usage_info, block_structure): user = usage_info.user result_list = SplitTestTransformer().transform_block_filters(usage_info, block_structure) user_partitions = block_structure.get_transformer_data(self, 'user_partitions') if not user_partitions: return [block_structure.create_universal_filter()] user_groups = get_user_partition_groups(usage_info.course_key, user_partitions, user, 'id') group_access_filter = block_structure.create_removal_filter( lambda block_key: not ( has_access(user, 'staff', block_key) or block_structure.get_transformer_block_field(block_key, self, 'merged_group_access').check_group_access( user_groups ) ) ) result_list.append(group_access_filter) return result_list
def student_dashboard(request): """ 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. """ user = request.user #Added by Mahendra if request.is_ajax(): if request.method == 'GET': if user.is_authenticated: if request.GET.get('disclaimer'): log.info(u'disclaimer %s', request.GET.get('disclaimer')) usr = request.user.id cid = request.GET.get('cid') disclaimer = disclaimer_agreement_status(course_id=cid, user_id=usr, status='1') disclaimer.save() else: usr = request.user.id cid = request.GET.get('cid') view_counter = user_view_counter.objects.filter( course_id=cid, user=usr) if view_counter: update_counter = user_view_counter.objects.filter( course_id=cid, user=usr).update(counter=F('counter') + 1) else: countr = user_view_counter(user_id=usr, course_id=cid, counter=1) countr.save() 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) except PendingSecondaryEmailChange.DoesNotExist: try: account_recovery_obj = AccountRecovery.objects.get(user=user) 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 = WaffleFlag(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) site_domain = request.site if 'notlive' in request.GET: if 'viatris' in str(site_domain): redirect_message = _( "The webinar you are looking for does not start until {date}." ).format(date=request.GET['notlive']) else: 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 ] context = { 'urls': urls, '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)
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) course_usage_key = modulestore().make_course_usage_key(course_key) # Enable NR tracing for this view based on course monitoring_utils.set_custom_metric('course_id', course_key_string) monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) _, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) course_tools = CourseToolsPluginManager.get_enabled_course_tools( request, course_key) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] dates_tab_link = request.build_absolute_uri( reverse('dates', args=[course.id])) if course_home_mfe_dates_tab_is_active(course.id): dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates') transformers = BlockStructureTransformers() transformers += course_blocks_api.get_course_block_access_transformers( request.user) transformers += [ BlocksAPITransformer(None, None, depth=3), ] course_blocks = get_course_blocks(request.user, course_usage_key, transformers, include_completion=True) dates_widget = { 'course_date_blocks': [ block for block in date_blocks if not isinstance(block, TodaysDate) ], 'dates_tab_link': dates_tab_link, 'user_timezone': user_timezone, } data = { 'course_tools': course_tools, 'course_blocks': course_blocks, 'dates_widget': dates_widget, } context = self.get_serializer_context() context['course_key'] = course_key serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def should_remove(self, user): """ Test to see if this result should be removed due to access restriction """ if has_access(user, 'staff', self.get_course_key()): return False return self.get_usage_key() not in self.get_course_blocks(user).get_block_keys()
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) course_usage_key = modulestore().make_course_usage_key(course_key) # Enable NR tracing for this view based on course monitoring_utils.set_custom_metric('course_id', course_key_string) monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) _, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) enrollment = CourseEnrollment.get_enrollment(request.user, course_key) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled( course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC is_enrolled = enrollment and enrollment.is_active is_staff = has_access(request.user, 'staff', course_key) show_handouts = is_enrolled or is_staff or allow_public handouts_html = get_course_info_section( request, request.user, course, 'handouts') if show_handouts else '' course_tools = CourseToolsPluginManager.get_enabled_course_tools( request, course_key) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] dates_tab_link = request.build_absolute_uri( reverse('dates', args=[course.id])) if course_home_mfe_dates_tab_is_active(course.id): dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates') transformers = BlockStructureTransformers() transformers += get_course_block_access_transformers(request.user) transformers += [ BlocksAPITransformer(None, None, depth=3), ] course_blocks = get_course_blocks(request.user, course_usage_key, transformers, include_completion=True) dates_widget = { 'course_date_blocks': [ block for block in date_blocks if not isinstance(block, TodaysDate) ], 'dates_tab_link': dates_tab_link, 'user_timezone': user_timezone, } data = { 'course_blocks': course_blocks, 'course_tools': course_tools, 'dates_widget': dates_widget, 'handouts_html': handouts_html, } context = self.get_serializer_context() context['course_key'] = course_key serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def test__user_passed_as_none(self): """Ensure has_access handles a user being passed as null""" access.has_access(None, 'staff', 'global', None)
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) course_usage_key = modulestore().make_course_usage_key(course_key) if not course_home_mfe_outline_tab_is_active(course_key): raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_metric('course_id', course_key_string) monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) _masquerade, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) course_overview = CourseOverview.get_from_id(course_key) enrollment = CourseEnrollment.get_enrollment(request.user, course_key) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled(course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC is_enrolled = enrollment and enrollment.is_active is_staff = has_access(request.user, 'staff', course_key) show_enrolled = is_enrolled or is_staff show_handouts = show_enrolled or allow_public handouts_html = get_course_info_section(request, request.user, course, 'handouts') if show_handouts else '' # TODO: TNL-7185 Legacy: Refactor to return the offer & expired data and format the message in the MFE offer_html = generate_offer_html(request.user, course_overview) course_expired_html = generate_course_expired_message(request.user, course_overview) welcome_message_html = None if get_course_tag(request.user, course_key, PREFERENCE_KEY) != 'False': if LATEST_UPDATE_FLAG.is_enabled(course_key): welcome_message_html = LatestUpdateFragmentView().latest_update_html(request, course) else: welcome_message_html = WelcomeMessageFragmentView().welcome_message_html(request, course) enroll_alert = { 'can_enroll': True, 'extra_text': None, } if not show_enrolled: if CourseMode.is_masters_only(course_key): enroll_alert['can_enroll'] = False enroll_alert['extra_text'] = _('Please contact your degree administrator or ' 'edX Support if you have questions.') elif course.invitation_only: enroll_alert['can_enroll'] = False course_tools = CourseToolsPluginManager.get_enabled_course_tools(request, course_key) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] dates_tab_link = request.build_absolute_uri(reverse('dates', args=[course.id])) if course_home_mfe_dates_tab_is_active(course.id): dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates') transformers = BlockStructureTransformers() transformers += get_course_block_access_transformers(request.user) transformers += [ BlocksAPITransformer(None, None, depth=4), ] course_blocks = get_course_blocks(request.user, course_usage_key, transformers, include_completion=True) has_visited_course = False try: resume_block = get_key_to_last_completed_block(request.user, course.id) has_visited_course = True except UnavailableCompletionData: resume_block = course_usage_key resume_path = reverse('jump_to', kwargs={ 'course_id': course_key_string, 'location': str(resume_block) }) resume_course_url = request.build_absolute_uri(resume_path) resume_course = { 'has_visited_course': has_visited_course, 'url': resume_course_url, } dates_widget = { 'course_date_blocks': [block for block in date_blocks if not isinstance(block, TodaysDate)], 'dates_tab_link': dates_tab_link, 'user_timezone': user_timezone, } data = { 'course_blocks': course_blocks, 'course_expired_html': course_expired_html, 'course_tools': course_tools, 'dates_widget': dates_widget, 'enroll_alert': enroll_alert, 'handouts_html': handouts_html, 'offer_html': offer_html, 'resume_course': resume_course, 'welcome_message_html': welcome_message_html, } context = self.get_serializer_context() context['course_key'] = course_key serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def ensure_staff_access(self, block_location): """ Another DRY helper. """ block = modulestore().get_item(block_location) assert access.has_access(self.staff, 'load', block, self.course.id)
def check_access(self, user, block_location, is_accessible): """ DRY helper. """ assert bool(access.has_access(user, 'load', modulestore().get_item(block_location), self.course.id))\ is is_accessible
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) course_usage_key = modulestore().make_course_usage_key(course_key) if not course_home_mfe_outline_tab_is_active(course_key): raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) monitoring_utils.set_custom_attribute('user_id', request.user.id) monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) _masquerade, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) course_overview = CourseOverview.get_from_id(course_key) enrollment = CourseEnrollment.get_enrollment(request.user, course_key) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled( course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] dates_tab_link = request.build_absolute_uri( reverse('dates', args=[course.id])) if course_home_mfe_dates_tab_is_active(course.id): dates_tab_link = get_microfrontend_url(course_key=course.id, view_name='dates') # Set all of the defaults access_expiration = None course_blocks = None course_goals = {'goal_options': [], 'selected_goal': None} course_tools = CourseToolsPluginManager.get_enabled_course_tools( request, course_key) dates_widget = { 'course_date_blocks': [], 'dates_tab_link': dates_tab_link, 'user_timezone': user_timezone, } enroll_alert = { 'can_enroll': True, 'extra_text': None, } handouts_html = None offer_data = None resume_course = { 'has_visited_course': False, 'url': None, } welcome_message_html = None is_enrolled = enrollment and enrollment.is_active is_staff = bool(has_access(request.user, 'staff', course_key)) show_enrolled = is_enrolled or is_staff if show_enrolled: course_blocks = get_course_outline_block_tree( request, course_key_string, request.user) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) dates_widget['course_date_blocks'] = [ block for block in date_blocks if not isinstance(block, TodaysDate) ] handouts_html = get_course_info_section(request, request.user, course, 'handouts') welcome_message_html = get_current_update_for_user(request, course) offer_data = generate_offer_data(request.user, course_overview) access_expiration = get_access_expiration_data( request.user, course_overview) # Only show the set course goal message for enrolled, unverified # users in a course that allows for verified statuses. is_already_verified = CourseEnrollment.is_enrolled_as_verified( request.user, course_key) if not is_already_verified and has_course_goal_permission( request, course_key_string, {'is_enrolled': is_enrolled}): course_goals = { 'goal_options': valid_course_goals_ordered(include_unsure=True), 'selected_goal': None } selected_goal = get_course_goal(request.user, course_key) if selected_goal: course_goals['selected_goal'] = { 'key': selected_goal.goal_key, 'text': get_course_goal_text(selected_goal.goal_key), } try: resume_block = get_key_to_last_completed_block( request.user, course.id) resume_course['has_visited_course'] = True resume_path = reverse('jump_to', kwargs={ 'course_id': course_key_string, 'location': str(resume_block) }) resume_course['url'] = request.build_absolute_uri(resume_path) except UnavailableCompletionData: start_block = get_start_block(course_blocks) resume_course['url'] = start_block['lms_web_url'] elif allow_public_outline or allow_public: course_blocks = get_course_outline_block_tree( request, course_key_string, None) if allow_public: handouts_html = get_course_info_section( request, request.user, course, 'handouts') if not show_enrolled: if CourseMode.is_masters_only(course_key): enroll_alert['can_enroll'] = False enroll_alert['extra_text'] = _( 'Please contact your degree administrator or ' 'edX Support if you have questions.') elif course.invitation_only: enroll_alert['can_enroll'] = False data = { 'access_expiration': access_expiration, 'course_blocks': course_blocks, 'course_goals': course_goals, 'course_tools': course_tools, 'dates_widget': dates_widget, 'enroll_alert': enroll_alert, 'handouts_html': handouts_html, 'has_ended': course.has_ended(), 'offer': offer_data, 'resume_course': resume_course, 'welcome_message_html': welcome_message_html, } context = self.get_serializer_context() context['course_overview'] = course_overview context['enable_links'] = show_enrolled or allow_public context['enrollment'] = enrollment serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) student_id = kwargs.get('student_id') if not course_home_mfe_progress_tab_is_active(course_key): raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) monitoring_utils.set_custom_attribute('user_id', request.user.id) monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff) is_staff = bool(has_access(request.user, 'staff', course_key)) student = self._get_student_user(request, course_key, student_id, is_staff) username = get_enterprise_learner_generic_name( request) or student.username course = get_course_with_access(student, 'load', course_key, check_if_enrolled=False) course_overview = CourseOverview.get_from_id(course_key) enrollment = CourseEnrollment.get_enrollment(student, course_key) enrollment_mode = getattr(enrollment, 'mode', None) if not (enrollment and enrollment.is_active) and not is_staff: return Response('User not enrolled.', status=401) # The block structure is used for both the course_grade and has_scheduled content fields # So it is called upfront and reused for optimization purposes collected_block_structure = get_block_structure_manager( course_key).get_collected() course_grade = CourseGradeFactory().read( student, collected_block_structure=collected_block_structure) # Get has_scheduled_content data transformers = BlockStructureTransformers() transformers += [ start_date.StartDateTransformer(), ContentTypeGateTransformer() ] usage_key = collected_block_structure.root_block_usage_key course_blocks = get_course_blocks( student, usage_key, transformers=transformers, collected_block_structure=collected_block_structure, include_has_scheduled_content=True) has_scheduled_content = course_blocks.get_xblock_field( usage_key, 'has_scheduled_content') # Get user_has_passing_grade data user_has_passing_grade = False if not student.is_anonymous: user_grade = course_grade.percent user_has_passing_grade = user_grade >= course.lowest_passing_grade descriptor = modulestore().get_course(course_key) grading_policy = descriptor.grading_policy verification_status = IDVerificationService.user_status(student) verification_link = None if verification_status['status'] is None or verification_status[ 'status'] == 'expired': verification_link = IDVerificationService.get_verify_location( course_id=course_key) elif verification_status['status'] == 'must_reverify': verification_link = IDVerificationService.get_verify_location( course_id=course_key) verification_data = { 'link': verification_link, 'status': verification_status['status'], 'status_date': verification_status['status_date'], } access_expiration = get_access_expiration_data(request.user, course_overview) data = { 'access_expiration': access_expiration, 'certificate_data': get_cert_data(student, course, enrollment_mode, course_grade), 'completion_summary': get_course_blocks_completion_summary(course_key, student), 'course_grade': course_grade, 'credit_course_requirements': credit_course_requirements(course_key, student), 'end': course.end, 'enrollment_mode': enrollment_mode, 'grading_policy': grading_policy, 'has_scheduled_content': has_scheduled_content, 'section_scores': list(course_grade.chapter_grades.values()), 'studio_url': get_studio_url(course, 'settings/grading'), 'username': username, 'user_has_passing_grade': user_has_passing_grade, 'verification_data': verification_data, } context = self.get_serializer_context() context['staff_access'] = is_staff context['course_blocks'] = course_blocks context['course_key'] = course_key # course_overview and enrollment will be used by VerifiedModeSerializer context['course_overview'] = course_overview context['enrollment'] = enrollment serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def is_enabled(cls, course, user=None): return user and user.is_authenticated and \ bool(CourseEnrollment.is_enrolled(user, course.id) or has_access(user, 'staff', course, course.id))
def is_enabled(cls, course, user=None): """ Returns true if the specified user has staff access. """ return bool(user and has_access(user, 'staff', course, course.id))
def test_has_access_in_preview_mode_with_group(self): """ Test that a user masquerading as a member of a group sees appropriate content in preview mode. """ # Note about UserPartition and UserPartition Group IDs: these must not conflict with IDs used # by dynamic user partitions. partition_id = MINIMUM_STATIC_PARTITION_ID group_0_id = MINIMUM_STATIC_PARTITION_ID + 1 group_1_id = MINIMUM_STATIC_PARTITION_ID + 2 user_partition = UserPartition( partition_id, 'Test User Partition', '', [Group(group_0_id, 'Group 1'), Group(group_1_id, 'Group 2')], scheme_id='cohort') self.course.user_partitions.append(user_partition) self.course.cohort_config = {'cohorted': True} chapter = ItemFactory.create(category="chapter", parent_location=self.course.location) chapter.group_access = {partition_id: [group_0_id]} modulestore().update_item(self.course, ModuleStoreEnum.UserID.test) # User should not be able to preview when masquerading as student (and not in the group above). with patch('lms.djangoapps.courseware.access.get_user_role' ) as mock_user_role: mock_user_role.return_value = 'student' assert not bool( access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id)) # Should be able to preview when in staff or instructor role. for mocked_role in ['staff', 'instructor']: with patch('lms.djangoapps.courseware.access.get_user_role' ) as mock_user_role: mock_user_role.return_value = mocked_role assert bool( access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id)) # Now install masquerade group and set staff as a member of that. assert 200 == masquerade_as_group_member(self.global_staff, self.course, partition_id, group_0_id) # Can load the chapter since user is in the group. assert bool( access.has_access(self.global_staff, 'load', chapter, course_key=self.course.id)) # Move the user to be a part of the second group. assert 200 == masquerade_as_group_member(self.global_staff, self.course, partition_id, group_1_id) # Cannot load the chapter since user is in a different group. assert not bool( access.has_access( self.global_staff, 'load', chapter, course_key=self.course.id))
def render_to_fragment(self, request, course_id=None, **kwargs): """ Renders the course's home page as a fragment. """ course_key = CourseKey.from_string(course_id) course = get_course_with_access(request.user, 'load', course_key) # Render the course dates as a fragment dates_fragment = CourseDatesFragmentView().render_to_fragment( request, course_id=course_id, **kwargs) # Render the full content to enrolled users, as well as to course and global staff. # Unenrolled users who are not course or global staff are given only a subset. enrollment = CourseEnrollment.get_enrollment(request.user, course_key) user_access = { 'is_anonymous': request.user.is_anonymous, 'is_enrolled': enrollment and enrollment.is_active, 'is_staff': has_access(request.user, 'staff', course_key), } allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled( course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE # Set all the fragments outline_fragment = None update_message_fragment = None course_sock_fragment = None offer_banner_fragment = None course_expiration_fragment = None has_visited_course = None resume_course_url = None handouts_html = None if user_access['is_enrolled'] or user_access['is_staff']: outline_fragment = CourseOutlineFragmentView().render_to_fragment( request, course_id=course_id, **kwargs) if LATEST_UPDATE_FLAG.is_enabled(course_key): update_message_fragment = LatestUpdateFragmentView( ).render_to_fragment(request, course_id=course_id, **kwargs) else: update_message_fragment = WelcomeMessageFragmentView( ).render_to_fragment(request, course_id=course_id, **kwargs) course_sock_fragment = CourseSockFragmentView().render_to_fragment( request, course=course, **kwargs) has_visited_course, resume_course_url = self._get_resume_course_info( request, course_id) handouts_html = self._get_course_handouts(request, course) course_overview = CourseOverview.get_from_id(course.id) offer_banner_fragment = get_first_purchase_offer_banner_fragment( request.user, course_overview) course_expiration_fragment = generate_course_expired_fragment( request.user, course_overview) elif allow_public_outline or allow_public: outline_fragment = CourseOutlineFragmentView().render_to_fragment( request, course_id=course_id, user_is_enrolled=False, **kwargs) course_sock_fragment = CourseSockFragmentView().render_to_fragment( request, course=course, **kwargs) if allow_public: handouts_html = self._get_course_handouts(request, course) else: # Redirect the user to the dashboard if they are not enrolled and # this is a course that does not support direct enrollment. if not can_self_enroll_in_course(course_key): raise CourseAccessRedirect(reverse('dashboard')) # Get the course tools enabled for this user and course course_tools = CourseToolsPluginManager.get_enabled_course_tools( request, course_key) # Check if the user can access the course goal functionality has_goal_permission = has_course_goal_permission( request, course_id, user_access) # Grab the current course goal and the acceptable course goal keys mapped to translated values current_goal = get_course_goal(request.user, course_key) goal_options = get_course_goal_options() # Get the course goals api endpoint goal_api_url = get_goal_api_url(request) # Grab the course home messages fragment to render any relevant django messages course_home_message_fragment = CourseHomeMessageFragmentView( ).render_to_fragment(request, course_id=course_id, user_access=user_access, **kwargs) # Get info for upgrade messaging upgrade_price = None upgrade_url = None has_discount = False # TODO Add switch to control deployment if SHOW_UPGRADE_MSG_ON_COURSE_HOME.is_enabled( course_key) and enrollment and enrollment.upgrade_deadline: upgrade_url = EcommerceService().upgrade_url( request.user, course_key) upgrade_price, has_discount = format_strikeout_price( request.user, course) # Render the course home fragment context = { 'request': request, 'csrf': csrf(request)['csrf_token'], 'course': course, 'course_key': course_key, 'outline_fragment': outline_fragment, 'handouts_html': handouts_html, 'course_home_message_fragment': course_home_message_fragment, 'offer_banner_fragment': offer_banner_fragment, 'course_expiration_fragment': course_expiration_fragment, 'has_visited_course': has_visited_course, 'resume_course_url': resume_course_url, 'course_tools': course_tools, 'dates_fragment': dates_fragment, 'username': request.user.username, 'goal_api_url': goal_api_url, 'has_goal_permission': has_goal_permission, 'goal_options': goal_options, 'current_goal': current_goal, 'update_message_fragment': update_message_fragment, 'course_sock_fragment': course_sock_fragment, 'disable_courseware_js': True, 'uses_pattern_library': True, 'upgrade_price': upgrade_price, 'upgrade_url': upgrade_url, 'has_discount': has_discount, } html = render_to_string('course_experience/course-home-fragment.html', context) return Fragment(html)
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) # Enable NR tracing for this view based on course monitoring_utils.set_custom_metric('course_id', course_key_string) monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) _, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True ) user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] transformers = BlockStructureTransformers() transformers += course_blocks_api.get_course_block_access_transformers(request.user) transformers += [ BlocksAPITransformer(None, None, depth=3), ] course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=True) enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(request.user, course_key) course_grade = CourseGradeFactory().read(request.user, course) courseware_summary = course_grade.chapter_grades.values() verification_status = IDVerificationService.user_status(request.user) verification_link = None if verification_status['status'] is None or verification_status['status'] == 'expired': verification_link = IDVerificationService.get_verify_location('verify_student_verify_now', course_id=course_key) elif verification_status['status'] == 'must_reverify': verification_link = IDVerificationService.get_verify_location('verify_student_reverify', course_id=course_key) verification_data = { 'link': verification_link, 'status': verification_status['status'], 'status_date': verification_status['status_date'], } data = { 'certificate_data': get_cert_data(request.user, course, enrollment_mode, course_grade), 'courseware_summary': courseware_summary, 'credit_course_requirements': credit_course_requirements(course_key, request.user), 'credit_support_url': CREDIT_SUPPORT_URL, 'enrollment_mode': enrollment_mode, 'studio_url': get_studio_url(course, 'settings/grading'), 'user_timezone': user_timezone, 'verification_data': verification_data, } context = self.get_serializer_context() context['staff_access'] = bool(has_access(request.user, 'staff', course)) context['course_key'] = course_key serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def _attach_course_run_can_enroll(self, run_mode): run_mode['can_enroll'] = bool( has_access(self.user, 'enroll', self.course_overview))
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 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 CourseEnrollment.objects.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 can_access_others_blocks(requesting_user, course_key): """ Returns whether the requesting_user can access the blocks for other users in the given course. """ return has_access(requesting_user, CourseStaffRole.ROLE, course_key)
def check_course_access(course, user, action, check_if_enrolled=False, check_survey_complete=True): """ Check that the user has the access to perform the specified action on the course (CourseDescriptor|CourseOverview). check_if_enrolled: If true, additionally verifies that the user is enrolled. check_survey_complete: If true, additionally verifies that the user has completed the survey. """ # Allow staff full access to the course even if not enrolled if has_access(user, 'staff', course.id): return request = get_current_request() check_content_start_date_for_masquerade_user(course.id, user, request, course.start) access_response = has_access(user, action, course, course.id) if not access_response: # Redirect if StartDateError if isinstance(access_response, StartDateError): start_date = strftime_localized(course.start, 'SHORT_DATE') params = QueryDict(mutable=True) params['notlive'] = start_date raise CourseAccessRedirect( '{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode()), access_response) # Redirect if AuditExpiredError if isinstance(access_response, AuditExpiredError): params = QueryDict(mutable=True) params[ 'access_response_error'] = access_response.additional_context_user_message raise CourseAccessRedirect( '{dashboard_url}?{params}'.format( dashboard_url=reverse('dashboard'), params=params.urlencode()), access_response) # Redirect if the user must answer a survey before entering the course. if isinstance(access_response, MilestoneAccessError): raise CourseAccessRedirect( '{dashboard_url}'.format(dashboard_url=reverse('dashboard'), ), access_response) # Deliberately return a non-specific error message to avoid # leaking info about access control settings raise CoursewareAccessException(access_response) if check_if_enrolled: # If the user is not enrolled, redirect them to the about page if not CourseEnrollment.is_enrolled(user, course.id): raise CourseAccessRedirect( reverse('about_course', args=[six.text_type(course.id)])) # Redirect if the user must answer a survey before entering the course. if check_survey_complete and action == 'load': if is_survey_required_and_unanswered(user, course): raise CourseAccessRedirect( reverse('course_survey', args=[six.text_type(course.id)]))
def to_representation(self, course_overview): course_id = six.text_type(course_overview.id) request = self.context.get('request') api_version = self.context.get('api_version') return { # identifiers 'id': course_id, 'name': course_overview.display_name, 'number': course_overview.display_number_with_default, 'org': course_overview.display_org_with_default, # dates 'start': course_overview.start, 'start_display': course_overview.start_display, 'start_type': course_overview.start_type, 'end': course_overview.end, # notification info 'subscription_id': course_overview.clean_id(padding_char='_'), # access info 'courseware_access': has_access(request.user, 'load_mobile', course_overview).to_json(), # various URLs # course_image is sent in both new and old formats # (within media to be compatible with the new Course API) 'media': { 'course_image': { 'uri': course_overview.course_image_url, 'name': 'Course Image', } }, 'course_image': course_overview.course_image_url, 'course_about': get_link_for_about_page(course_overview), 'course_sharing_utm_parameters': get_encoded_course_sharing_utm_params(), 'course_updates': reverse( 'course-updates-list', kwargs={ 'api_version': api_version, 'course_id': course_id }, request=request, ), 'course_handouts': reverse( 'course-handouts-list', kwargs={ 'api_version': api_version, 'course_id': course_id }, request=request, ), 'discussion_url': reverse( 'discussion_course', kwargs={'course_id': course_id}, request=request, ) if course_overview.is_discussion_tab_enabled() else None, # This is an old API that was removed as part of DEPR-4. We keep the # field present in case API parsers expect it, but this API is now # removed. 'video_outline': None, }
def _attach_course_run_can_enroll(self, run_mode): run_mode['can_enroll'] = bool(has_access(self.user, 'enroll', self.course_overview))
def get(self, request, *args, **kwargs): # pylint: disable=too-many-statements course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) course_usage_key = modulestore().make_course_usage_key(course_key) # pylint: disable=unused-variable if course_home_legacy_is_active(course_key): raise Http404 # Enable NR tracing for this view based on course monitoring_utils.set_custom_attribute('course_id', course_key_string) monitoring_utils.set_custom_attribute('user_id', request.user.id) monitoring_utils.set_custom_attribute('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) masquerade_object, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) user_is_masquerading = is_masquerading( request.user, course_key, course_masquerade=masquerade_object) course_overview = get_course_overview_or_404(course_key) enrollment = CourseEnrollment.get_enrollment(request.user, course_key) enrollment_mode = getattr(enrollment, 'mode', None) allow_anonymous = COURSE_ENABLE_UNENROLLED_ACCESS_FLAG.is_enabled( course_key) allow_public = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC allow_public_outline = allow_anonymous and course.course_visibility == COURSE_VISIBILITY_PUBLIC_OUTLINE # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] if course_home_legacy_is_active(course.id): dates_tab_link = request.build_absolute_uri( reverse('dates', args=[course.id])) else: dates_tab_link = get_learning_mfe_home_url(course_key=course.id, url_fragment='dates') # Set all of the defaults access_expiration = None cert_data = None course_blocks = None course_goals = { 'selected_goal': None, 'weekly_learning_goal_enabled': False, } course_tools = CourseToolsPluginManager.get_enabled_course_tools( request, course_key) dates_widget = { 'course_date_blocks': [], 'dates_tab_link': dates_tab_link, 'user_timezone': user_timezone, } enroll_alert = { 'can_enroll': True, 'extra_text': None, } handouts_html = None offer_data = None resume_course = { 'has_visited_course': False, 'url': None, } welcome_message_html = None is_enrolled = enrollment and enrollment.is_active is_staff = bool(has_access(request.user, 'staff', course_key)) show_enrolled = is_enrolled or is_staff enable_proctored_exams = False if show_enrolled: course_blocks = get_course_outline_block_tree( request, course_key_string, request.user) date_blocks = get_course_date_blocks(course, request.user, request, num_assignments=1) dates_widget['course_date_blocks'] = [ block for block in date_blocks if not isinstance(block, TodaysDate) ] handouts_html = get_course_info_section(request, request.user, course, 'handouts') welcome_message_html = get_current_update_for_user(request, course) offer_data = generate_offer_data(request.user, course_overview) access_expiration = get_access_expiration_data( request.user, course_overview) cert_data = get_cert_data(request.user, course, enrollment.mode) if is_enrolled else None enable_proctored_exams = course_overview.enable_proctored_exams if (is_enrolled and ENABLE_COURSE_GOALS.is_enabled(course_key)): course_goals['weekly_learning_goal_enabled'] = True selected_goal = get_course_goal(request.user, course_key) if selected_goal: course_goals['selected_goal'] = { 'days_per_week': selected_goal.days_per_week, 'subscribed_to_reminders': selected_goal.subscribed_to_reminders, } try: resume_block = get_key_to_last_completed_block( request.user, course.id) resume_course['has_visited_course'] = True resume_path = reverse('jump_to', kwargs={ 'course_id': course_key_string, 'location': str(resume_block) }) resume_course['url'] = request.build_absolute_uri(resume_path) except UnavailableCompletionData: start_block = get_start_block(course_blocks) resume_course['url'] = start_block['lms_web_url'] elif allow_public_outline or allow_public or user_is_masquerading: course_blocks = get_course_outline_block_tree( request, course_key_string, None) if allow_public or user_is_masquerading: handouts_html = get_course_info_section( request, request.user, course, 'handouts') if not is_enrolled: if CourseMode.is_masters_only(course_key): enroll_alert['can_enroll'] = False enroll_alert['extra_text'] = _( 'Please contact your degree administrator or ' '{platform_name} Support if you have questions.').format( platform_name=settings.PLATFORM_NAME) elif course_is_invitation_only(course): enroll_alert['can_enroll'] = False # Sometimes there are sequences returned by Course Blocks that we # don't actually want to show to the user, such as when a sequence is # composed entirely of units that the user can't access. The Learning # Sequences API knows how to roll this up, so we use it determine which # sequences we should remove from course_blocks. # # The long term goal is to remove the Course Blocks API call entirely, # so this is a tiny first step in that migration. if course_blocks: user_course_outline = get_user_course_outline( course_key, request.user, datetime.now(tz=timezone.utc)) available_seq_ids = { str(usage_key) for usage_key in user_course_outline.sequences } # course_blocks is a reference to the root of the course, so we go # through the chapters (sections) to look for sequences to remove. for chapter_data in course_blocks['children']: chapter_data['children'] = [ seq_data for seq_data in chapter_data['children'] if (seq_data['id'] in available_seq_ids or # Edge case: Sometimes we have weird course structures. # We expect only sequentials here, but if there is # another type, just skip it (don't filter it out). seq_data['type'] != 'sequential') ] if 'children' in chapter_data else [] user_has_passing_grade = False if not request.user.is_anonymous: user_grade = CourseGradeFactory().read(request.user, course) if user_grade: user_has_passing_grade = user_grade.passed data = { 'access_expiration': access_expiration, 'cert_data': cert_data, 'course_blocks': course_blocks, 'course_goals': course_goals, 'course_tools': course_tools, 'dates_widget': dates_widget, 'enable_proctored_exams': enable_proctored_exams, 'enroll_alert': enroll_alert, 'enrollment_mode': enrollment_mode, 'handouts_html': handouts_html, 'has_ended': course.has_ended(), 'offer': offer_data, 'resume_course': resume_course, 'user_has_passing_grade': user_has_passing_grade, 'welcome_message_html': welcome_message_html, } context = self.get_serializer_context() context['course_overview'] = course_overview context['enable_links'] = show_enrolled or allow_public context['enrollment'] = enrollment serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def get(self, request, *args, **kwargs): course_key_string = kwargs.get('course_key_string') course_key = CourseKey.from_string(course_key_string) if not course_home_mfe_dates_tab_is_active(course_key): return Response(status=status.HTTP_404_NOT_FOUND) # Enable NR tracing for this view based on course monitoring_utils.set_custom_metric('course_id', course_key_string) monitoring_utils.set_custom_metric('user_id', request.user.id) monitoring_utils.set_custom_metric('is_staff', request.user.is_staff) course = get_course_with_access(request.user, 'load', course_key, check_if_enrolled=False) _, request.user = setup_masquerade( request, course_key, staff_access=has_access(request.user, 'staff', course_key), reset_masquerade_data=True, ) blocks = get_course_date_blocks(course, request.user, request, include_access=True, include_past_dates=True) missed_deadlines, missed_gated_content = dates_banner_should_display( course_key, request.user) learner_is_full_access = not ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key, ) # User locale settings user_timezone_locale = user_timezone_locale_prefs(request) user_timezone = user_timezone_locale['user_timezone'] data = { 'has_ended': course.has_ended(), 'course_date_blocks': [block for block in blocks if not isinstance(block, TodaysDate)], 'missed_deadlines': missed_deadlines, 'missed_gated_content': missed_gated_content, 'learner_is_full_access': learner_is_full_access, 'user_timezone': user_timezone, 'verified_upgrade_link': verified_upgrade_deadline_link(request.user, course=course), } context = self.get_serializer_context() context['learner_is_full_access'] = learner_is_full_access serializer = self.get_serializer_class()(data, context=context) return Response(serializer.data)
def list_course_keys(request, username, role): """ Yield all available CourseKeys for the user having the given role. The courses returned include those for which the user identified by `username` has the given role. Additionally, the logged in user should have permission to view courses available to that user. Note: This function does not use branding to determine courses. Arguments: request (HTTPRequest): Used to identify the logged-in user and to instantiate the course module to retrieve the course about description username (string): The name of the user the logged-in user would like to be identified as Keyword Arguments: role (string): Course keys are filtered such that only those for which the user has the specified role are returned. Return value: Yield `CourseKey` objects representing the collection of courses. """ user = get_effective_user(request.user, username) all_course_keys = CourseOverview.get_all_course_keys() # Global staff have access to all courses. Filter courses for non-global staff. if GlobalStaff().has_user(user): return all_course_keys if role == 'staff': # This short-circuit implementation bypasses has_access() which we think is too slow for some users when # evaluating staff-level course access for Insights. Various tickets have context on this issue: CR-2487, # TNL-7448, DESUPPORT-416, and probably more. # # This is a simplified implementation that does not consider org-level access grants (e.g. when course_id is # empty). filtered_course_keys = ( CourseAccessRole.objects.filter( user=user, # Having the instructor role implies staff access. role__in=['staff', 'instructor'], ) # We need to check against CourseOverview so that we don't return any Libraries. .extra(tables=['course_overviews_courseoverview'], where=['course_id = course_overviews_courseoverview.id']) # For good measure, make sure we don't return empty course IDs. .exclude(course_id=CourseKeyField.Empty).order_by( 'course_id').values_list('course_id', flat=True).distinct()) else: # This is the original implementation which still covers the case where role = "instructor": filtered_course_keys = LazySequence( (course_key for course_key in all_course_keys if has_access(user, role, course_key)), est_len=len(all_course_keys)) return filtered_course_keys
def can_access_all_blocks(requesting_user, course_key): """ Returns whether the requesting_user can access all the blocks in the course. """ return has_access(requesting_user, CourseStaffRole.ROLE, course_key)
def preprocess_collection(user, course, collection): """ Prepare `collection(notes_list)` provided by edx-notes-api for rendering in a template: add information about ancestor blocks, convert "updated" to date Raises: ItemNotFoundError - when appropriate module is not found. """ # pylint: disable=too-many-statements store = modulestore() filtered_collection = list() cache = {} include_path_info = ('course_structure' not in settings.NOTES_DISABLED_TABS) with store.bulk_operations(course.id): for model in collection: update = { u"updated": dateutil_parse(model["updated"]), } model.update(update) usage_id = model["usage_id"] if usage_id in list(cache.keys()): model.update(cache[usage_id]) filtered_collection.append(model) continue usage_key = UsageKey.from_string(usage_id) # Add a course run if necessary. usage_key = usage_key.replace( course_key=store.fill_in_run(usage_key.course_key)) try: item = store.get_item(usage_key) except ItemNotFoundError: log.debug(u"Module not found: %s", usage_key) continue if not has_access(user, "load", item, course_key=course.id): log.debug(u"User %s does not have an access to %s", user, item) continue unit = get_parent_unit(item) if unit is None: log.debug(u"Unit not found: %s", usage_key) continue if include_path_info: section = unit.get_parent() if not section: log.debug(u"Section not found: %s", usage_key) continue if section.location in list(cache.keys()): usage_context = cache[section.location] usage_context.update({ "unit": get_module_context(course, unit), }) model.update(usage_context) cache[usage_id] = cache[unit.location] = usage_context filtered_collection.append(model) continue chapter = section.get_parent() if not chapter: log.debug(u"Chapter not found: %s", usage_key) continue if chapter.location in list(cache.keys()): usage_context = cache[chapter.location] usage_context.update({ "unit": get_module_context(course, unit), "section": get_module_context(course, section), }) model.update(usage_context) cache[usage_id] = cache[unit.location] = cache[ section.location] = usage_context filtered_collection.append(model) continue usage_context = { "unit": get_module_context(course, unit), "section": get_module_context(course, section) if include_path_info else {}, "chapter": get_module_context(course, chapter) if include_path_info else {}, } model.update(usage_context) if include_path_info: cache[section.location] = cache[ chapter.location] = usage_context cache[usage_id] = cache[unit.location] = usage_context filtered_collection.append(model) return filtered_collection
def should_remove(self, user): """ Test to see if this result should be removed due to access restriction """ if has_access(user, 'staff', self.get_course_key()): return False return self.get_usage_key() not in self.get_course_blocks( user).get_block_keys()
def reset_course_deadlines(request): """ Set the start_date of a schedule to today, which in turn will adjust due dates for sequentials belonging to a self paced course Request Parameters: course_key: course key research_event_data: any data that should be included in the research tracking event Example: sending the location of where the reset deadlines banner (i.e. outline-tab) IMPORTANT NOTE: If updates are happening to the logic here, ALSO UPDATE the `reset_course_deadlines` function in common/djangoapps/util/views.py as well. """ course_key = request.data.get('course_key', None) research_event_data = request.data.get('research_event_data', {}) # If body doesnt contain 'course_key', return 400 to client. if not course_key: raise ParseError(_("'course_key' is required.")) try: course_key = CourseKey.from_string(course_key) course_masquerade, user = setup_masquerade( request, course_key, has_access(request.user, 'staff', course_key)) # We ignore the missed_deadlines because this endpoint is used in the Learning MFE for # learners who have remaining attempts on a problem and reset their due dates in order to # submit additional attempts. This can apply for 'completed' (submitted) content that would # not be marked as past_due _missed_deadlines, missed_gated_content = dates_banner_should_display( course_key, user) if not missed_gated_content: reset_self_paced_schedule(user, course_key) course_overview = course_detail(request, user.username, course_key) # For context here, research_event_data should already contain `location` indicating # the page/location dates were reset from and could also contain `block_id` if reset # within courseware. research_event_data.update({ 'courserun_key': str(course_key), 'is_masquerading': is_masquerading(user, course_key, course_masquerade), 'is_staff': has_access(user, 'staff', course_key).has_access, 'org_key': course_overview.display_org_with_default, 'user_id': user.id, }) tracker.emit('edx.ui.lms.reset_deadlines.clicked', research_event_data) body_link = get_learning_mfe_home_url(course_key=course_key, url_fragment='dates') return Response({ 'body': format_html('<a href="{}">{}</a>', body_link, _('View all dates')), 'header': _('Your due dates have been successfully shifted to help you stay on track.' ), 'link': body_link, 'link_text': _('View all dates'), 'message': _('Deadlines successfully reset.'), }) except Exception as reset_deadlines_exception: log.exception('Error occurred while trying to reset deadlines!') raise UnableToResetDeadlines from reset_deadlines_exception