def test_get_required_content_with_anonymous_user(self): course = CourseFactory() required_content = milestones_helpers.get_required_content(course.id, AnonymousUser()) assert required_content == [] # NOTE (CCB): The initial version of anonymous courseware access is very simple. We avoid accidentally # exposing locked content by simply avoiding anonymous access altogether for courses runs with milestones. milestone = milestones_api.add_milestone({ 'name': 'test', 'namespace': 'test', }) milestones_helpers.add_course_milestone(str(course.id), 'requires', milestone) with pytest.raises(InvalidUserException): milestones_helpers.get_required_content(course.id, AnonymousUser())
def get_entrance_exam_content(user, course): """ Get the entrance exam content information (ie, chapter module) """ required_content = get_required_content(course.id, user) exam_module = None for content in required_content: usage_key = UsageKey.from_string(content).map_into_course(course.id) module_item = modulestore().get_item(usage_key) if not module_item.hide_from_toc and module_item.is_entrance_exam: exam_module = module_item break return exam_module
def get_entrance_exam_content(request, course): """ Get the entrance exam content information (ie, chapter module) """ required_content = get_required_content(course, request.user) exam_module = None for content in required_content: usage_key = course.id.make_usage_key_from_deprecated_string(content) module_item = modulestore().get_item(usage_key) if not module_item.hide_from_toc and module_item.is_entrance_exam: exam_module = module_item break return exam_module
def get_required_content(usage_info, block_structure): """ Get the required content for the course. This takes into account if the user can skip the entrance exam. """ course_key = block_structure.root_block_usage_key.course_key user_can_skip_entrance_exam = EntranceExamConfiguration.user_can_skip_entrance_exam(usage_info.user, course_key) required_content = milestones_helpers.get_required_content(course_key, usage_info.user) if not required_content: return required_content if user_can_skip_entrance_exam: # remove the entrance exam from required content entrance_exam_id = block_structure.get_xblock_field(block_structure.root_block_usage_key, 'entrance_exam_id') required_content = [content for content in required_content if not content == entrance_exam_id] return required_content
def get_entrance_exam_content_info(request, course): """ Get the entrance exam content information e.g. chapter, exam passing state. return exam chapter and its passing state. """ required_content = get_required_content(course, request.user) exam_chapter = None is_exam_passed = True # Iterating the list of required content of this course. for content in required_content: # database lookup to required content pointer usage_key = course.id.make_usage_key_from_deprecated_string(content) module_item = modulestore().get_item(usage_key) if not module_item.hide_from_toc and module_item.is_entrance_exam: # Here we are looking for entrance exam module/chapter in required_content. # If module_item is an entrance exam chapter then set and return its info e.g. exam chapter, exam state. exam_chapter = module_item is_exam_passed = False break return exam_chapter, is_exam_passed
def toc_for_course(request, course, active_chapter, active_section, field_data_cache): ''' Create a table of contents from the module store Return format: [ {'display_name': name, 'url_name': url_name, 'sections': SECTIONS, 'active': bool}, ... ] where SECTIONS is a list [ {'display_name': name, 'url_name': url_name, 'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...] active is set for the section and chapter corresponding to the passed parameters, which are expected to be url_names of the chapter+section. Everything else comes from the xml, or defaults to "". chapters with name 'hidden' are skipped. NOTE: assumes that if we got this far, user has access to course. Returns None if this is not the case. field_data_cache must include data from the course module and 2 levels of its descendents ''' with modulestore().bulk_operations(course.id): course_module = get_module_for_descriptor(request.user, request, course, field_data_cache, course.id) if course_module is None: return None toc_chapters = list() chapters = course_module.get_display_items() # See if the course is gated by one or more content milestones required_content = milestones_helpers.get_required_content(course, request.user) # The user may not actually have to complete the entrance exam, if one is required if not user_must_complete_entrance_exam(request, request.user, course): required_content = [content for content in required_content if not content == course.entrance_exam_id] for chapter in chapters: # Only show required content, if there is required content # chapter.hide_from_toc is read-only (boo) local_hide_from_toc = False if required_content: if unicode(chapter.location) not in required_content: local_hide_from_toc = True # Skip the current chapter if a hide flag is tripped if chapter.hide_from_toc or local_hide_from_toc: continue sections = list() for section in chapter.get_display_items(): active = (chapter.url_name == active_chapter and section.url_name == active_section) if not section.hide_from_toc: sections.append({'display_name': section.display_name_with_default, 'url_name': section.url_name, 'format': section.format if section.format is not None else '', 'due': get_extended_due_date(section), 'active': active, 'graded': section.graded, }) toc_chapters.append({ 'display_name': chapter.display_name_with_default, 'url_name': chapter.url_name, 'sections': sections, 'active': chapter.url_name == active_chapter }) return toc_chapters
def toc_for_course(user, request, course, active_chapter, active_section, field_data_cache): ''' Create a table of contents from the module store Return format: { 'chapters': [ {'display_name': name, 'url_name': url_name, 'sections': SECTIONS, 'active': bool}, ], 'previous_of_active_section': {..}, 'next_of_active_section': {..} } where SECTIONS is a list [ {'display_name': name, 'url_name': url_name, 'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...] where previous_of_active_section and next_of_active_section have information on the next/previous sections of the active section. active is set for the section and chapter corresponding to the passed parameters, which are expected to be url_names of the chapter+section. Everything else comes from the xml, or defaults to "". chapters with name 'hidden' are skipped. NOTE: assumes that if we got this far, user has access to course. Returns None if this is not the case. field_data_cache must include data from the course module and 2 levels of its descendants ''' with modulestore().bulk_operations(course.id): course_module = get_module_for_descriptor( user, request, course, field_data_cache, course.id, course=course ) if course_module is None: return None, None, None toc_chapters = list() chapters = course_module.get_display_items() # Check for content which needs to be completed # before the rest of the content is made available required_content = milestones_helpers.get_required_content(course, user) # Check for gated content gated_content = gating_api.get_gated_content(course, user) # The user may not actually have to complete the entrance exam, if one is required if not user_must_complete_entrance_exam(request, user, course): required_content = [content for content in required_content if not content == course.entrance_exam_id] previous_of_active_section, next_of_active_section = None, None last_processed_section, last_processed_chapter = None, None found_active_section = False for chapter in chapters: # Only show required content, if there is required content # chapter.hide_from_toc is read-only (bool) display_id = slugify(chapter.display_name_with_default_escaped) local_hide_from_toc = False if required_content: if unicode(chapter.location) not in required_content: local_hide_from_toc = True # Skip the current chapter if a hide flag is tripped if chapter.hide_from_toc or local_hide_from_toc: continue sections = list() for section in chapter.get_display_items(): # skip the section if it is gated/hidden from the user if gated_content and unicode(section.location) in gated_content: continue if section.hide_from_toc: continue is_section_active = (chapter.url_name == active_chapter and section.url_name == active_section) if is_section_active: found_active_section = True section_context = { 'display_name': section.display_name_with_default_escaped, 'url_name': section.url_name, 'format': section.format if section.format is not None else '', 'due': section.due, 'active': is_section_active, 'graded': section.graded, } _add_timed_exam_info(user, course, section, section_context) # update next and previous of active section, if applicable if is_section_active: if last_processed_section: previous_of_active_section = last_processed_section.copy() previous_of_active_section['chapter_url_name'] = last_processed_chapter.url_name elif found_active_section and not next_of_active_section: next_of_active_section = section_context.copy() next_of_active_section['chapter_url_name'] = chapter.url_name sections.append(section_context) last_processed_section = section_context last_processed_chapter = chapter toc_chapters.append({ 'display_name': chapter.display_name_with_default_escaped, 'display_id': display_id, 'url_name': chapter.url_name, 'sections': sections, 'active': chapter.url_name == active_chapter }) return { 'chapters': toc_chapters, 'previous_of_active_section': previous_of_active_section, 'next_of_active_section': next_of_active_section, }
def toc_for_course(user, request, course, active_chapter, active_section, field_data_cache): ''' Create a table of contents from the module store Return format: { 'chapters': [ {'display_name': name, 'url_name': url_name, 'sections': SECTIONS, 'active': bool}, ], 'previous_of_active_section': {..}, 'next_of_active_section': {..} } where SECTIONS is a list [ {'display_name': name, 'url_name': url_name, 'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...] where previous_of_active_section and next_of_active_section have information on the next/previous sections of the active section. active is set for the section and chapter corresponding to the passed parameters, which are expected to be url_names of the chapter+section. Everything else comes from the xml, or defaults to "". chapters with name 'hidden' are skipped. NOTE: assumes that if we got this far, user has access to course. Returns None if this is not the case. field_data_cache must include data from the course module and 2 levels of its descendants ''' with modulestore().bulk_operations(course.id): course_module = get_module_for_descriptor(user, request, course, field_data_cache, course.id, course=course) if course_module is None: return None, None, None toc_chapters = list() chapters = course_module.get_display_items() # Check for content which needs to be completed # before the rest of the content is made available required_content = milestones_helpers.get_required_content( course, user) # The user may not actually have to complete the entrance exam, if one is required if not user_must_complete_entrance_exam(request, user, course): required_content = [ content for content in required_content if not content == course.entrance_exam_id ] previous_of_active_section, next_of_active_section = None, None last_processed_section, last_processed_chapter = None, None found_active_section = False for chapter in chapters: # Only show required content, if there is required content # chapter.hide_from_toc is read-only (bool) display_id = slugify(chapter.display_name_with_default_escaped) local_hide_from_toc = False if required_content: if unicode(chapter.location) not in required_content: local_hide_from_toc = True # Skip the current chapter if a hide flag is tripped if chapter.hide_from_toc or local_hide_from_toc: continue sections = list() for section in chapter.get_display_items(): # skip the section if it is hidden from the user if section.hide_from_toc: continue is_section_active = (chapter.url_name == active_chapter and section.url_name == active_section) if is_section_active: found_active_section = True section_context = { 'display_name': section.display_name_with_default_escaped, 'url_name': section.url_name, 'format': section.format if section.format is not None else '', 'due': section.due, 'active': is_section_active, 'graded': section.graded, } _add_timed_exam_info(user, course, section, section_context) # update next and previous of active section, if applicable if is_section_active: if last_processed_section: previous_of_active_section = last_processed_section.copy( ) previous_of_active_section[ 'chapter_url_name'] = last_processed_chapter.url_name elif found_active_section and not next_of_active_section: next_of_active_section = section_context.copy() next_of_active_section[ 'chapter_url_name'] = chapter.url_name sections.append(section_context) last_processed_section = section_context last_processed_chapter = chapter toc_chapters.append({ 'display_name': chapter.display_name_with_default_escaped, 'display_id': display_id, 'url_name': chapter.url_name, 'sections': sections, 'active': chapter.url_name == active_chapter }) return { 'chapters': toc_chapters, 'previous_of_active_section': previous_of_active_section, 'next_of_active_section': next_of_active_section, }
def toc_for_course(user, request, course, active_chapter, active_section, field_data_cache): ''' Create a table of contents from the module store Return format: [ {'display_name': name, 'url_name': url_name, 'sections': SECTIONS, 'active': bool}, ... ] where SECTIONS is a list [ {'display_name': name, 'url_name': url_name, 'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...] active is set for the section and chapter corresponding to the passed parameters, which are expected to be url_names of the chapter+section. Everything else comes from the xml, or defaults to "". chapters with name 'hidden' are skipped. NOTE: assumes that if we got this far, user has access to course. Returns None if this is not the case. field_data_cache must include data from the course module and 2 levels of its descendents ''' with modulestore().bulk_operations(course.id): course_module = get_module_for_descriptor( user, request, course, field_data_cache, course.id, course=course ) if course_module is None: return None toc_chapters = list() chapters = course_module.get_display_items() # See if the course is gated by one or more content milestones required_content = milestones_helpers.get_required_content(course, user) # The user may not actually have to complete the entrance exam, if one is required if not user_must_complete_entrance_exam(request, user, course): required_content = [content for content in required_content if not content == course.entrance_exam_id] for chapter in chapters: # Only show required content, if there is required content # chapter.hide_from_toc is read-only (boo) display_id = slugify(chapter.display_name_with_default) local_hide_from_toc = False if required_content: if unicode(chapter.location) not in required_content: local_hide_from_toc = True # Skip the current chapter if a hide flag is tripped if chapter.hide_from_toc or local_hide_from_toc: continue sections = list() for section in chapter.get_display_items(): active = (chapter.url_name == active_chapter and section.url_name == active_section) if not section.hide_from_toc: section_context = { 'display_name': section.display_name_with_default, 'url_name': section.url_name, 'format': section.format if section.format is not None else '', 'due': section.due, 'active': active, 'graded': section.graded, } # # Add in rendering context if exam is a timed exam (which includes proctored) # section_is_time_limited = ( getattr(section, 'is_time_limited', False) and settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) ) if section_is_time_limited: # We need to import this here otherwise Lettuce test # harness fails. When running in 'harvest' mode, the # test service appears to get into trouble with # circular references (not sure which as edx_proctoring.api # doesn't import anything from edx-platform). Odd thing # is that running: manage.py lms runserver --settings=acceptance # works just fine, it's really a combination of Lettuce and the # 'harvest' management command # # One idea is that there is some coupling between # lettuce and the 'terrain' Djangoapps projects in /common # This would need more investigation from edx_proctoring.api import get_attempt_status_summary # # call into edx_proctoring subsystem # to get relevant proctoring information regarding this # level of the courseware # # This will return None, if (user, course_id, content_id) # is not applicable # timed_exam_attempt_context = None try: timed_exam_attempt_context = get_attempt_status_summary( user.id, unicode(course.id), unicode(section.location) ) except Exception, ex: # pylint: disable=broad-except # safety net in case something blows up in edx_proctoring # as this is just informational descriptions, it is better # to log and continue (which is safe) than to have it be an # unhandled exception log.exception(ex) if timed_exam_attempt_context: # yes, user has proctoring context about # this level of the courseware # so add to the accordion data context section_context.update({ 'proctoring': timed_exam_attempt_context, }) sections.append(section_context) toc_chapters.append({ 'display_name': chapter.display_name_with_default, 'display_id': display_id, 'url_name': chapter.url_name, 'sections': sections, 'active': chapter.url_name == active_chapter }) return toc_chapters
def toc_for_course(request, course, active_chapter, active_section, field_data_cache): ''' Create a table of contents from the module store Return format: [ {'display_name': name, 'url_name': url_name, 'sections': SECTIONS, 'active': bool}, ... ] where SECTIONS is a list [ {'display_name': name, 'url_name': url_name, 'format': format, 'due': due, 'active' : bool, 'graded': bool}, ...] active is set for the section and chapter corresponding to the passed parameters, which are expected to be url_names of the chapter+section. Everything else comes from the xml, or defaults to "". chapters with name 'hidden' are skipped. NOTE: assumes that if we got this far, user has access to course. Returns None if this is not the case. field_data_cache must include data from the course module and 2 levels of its descendents ''' with modulestore().bulk_operations(course.id): course_module = get_module_for_descriptor(request.user, request, course, field_data_cache, course.id) if course_module is None: return None # Check to see if the course is gated on milestone-required content (such as an Entrance Exam) required_content = milestones_helpers.get_required_content( course, request.user) chapters = list() for chapter in course_module.get_display_items(): # Only show required content, if there is required content # chapter.hide_from_toc is read-only (boo) local_hide_from_toc = False if required_content: if unicode(chapter.location) not in required_content: local_hide_from_toc = True # Skip the current chapter if a hide flag is tripped if chapter.hide_from_toc or local_hide_from_toc: continue sections = list() for section in chapter.get_display_items(): active = (chapter.url_name == active_chapter and section.url_name == active_section) if not section.hide_from_toc: sections.append({ 'display_name': section.display_name_with_default, 'url_name': section.url_name, 'format': section.format if section.format is not None else '', 'due': get_extended_due_date(section), 'active': active, 'graded': section.graded, }) chapters.append({ 'display_name': chapter.display_name_with_default, 'url_name': chapter.url_name, 'sections': sections, 'active': chapter.url_name == active_chapter }) return chapters