def test_normal_sequence(self): ms_seq = self._create_seq_in_new_section(display_name="Normal Sequence") outline_seq, usage_key = self._outline_seq_data(ms_seq) assert outline_seq.usage_key == usage_key assert outline_seq.title == "Normal Sequence" assert outline_seq.visibility == VisibilityData() assert outline_seq.exam == ExamData() assert outline_seq.inaccessible_after_due is False
def test_proctored_exam_seq(self): ms_seq = self._create_seq_in_new_section( is_time_limited=True, is_proctored_enabled=True, ) outline_seq, _usage_key = self._outline_seq_data(ms_seq) assert outline_seq.exam == ExamData( is_time_limited=True, is_proctored_enabled=True, )
def _make_section_data(section): """ Generate a CourseSectionData from a SectionDescriptor. This method does a lot of the work to convert modulestore fields to an input that the learning_sequences app expects. It doesn't check for specific classes (i.e. you could create your own Sequence-like XBlock), but it will raise a CourseStructureError if anything you pass in is missing fields that we expect in a SectionDescriptor or its SequenceDescriptor children. """ _check_section_fields(section) sequences_data = [] for sequence in section.get_children(): _check_sequence_fields(sequence) sequences_data.append( CourseLearningSequenceData( usage_key=_remove_version_info(sequence.location), title=sequence.display_name_with_default, inaccessible_after_due=sequence.hide_after_due, exam=ExamData( is_practice_exam=sequence.is_practice_exam, is_proctored_enabled=sequence.is_proctored_enabled, is_time_limited=sequence.is_time_limited, ), visibility=VisibilityData( hide_from_toc=sequence.hide_from_toc, visible_to_staff_only=sequence.visible_to_staff_only, ), )) section_data = CourseSectionData( usage_key=_remove_version_info(section.location), title=section.display_name_with_default, sequences=sequences_data, visibility=VisibilityData( hide_from_toc=section.hide_from_toc, visible_to_staff_only=section.visible_to_staff_only, ), ) return section_data
def _make_section_data(section): """ Return a (CourseSectionData, List[ContentDataError]) from a SectionBlock. Can return None for CourseSectionData if it's not really a SectionBlock that was passed in. This method does a lot of the work to convert modulestore fields to an input that the learning_sequences app expects. OLX import permits structures that are much less constrained than Studio's UI allows for, so whenever we run into something that does not meet our Course -> Section -> Subsection hierarchy expectations, we add a support-team-readable error message to our list of ContentDataErrors to pass back. At this point in the code, everything has already been deserialized into SectionBlocks and SequenceBlocks, but we're going to phrase our messages in ways that would make sense to someone looking at the import OLX, since that is the layer that the course teams and support teams are working with. """ section_errors = [] # First check if it's not a section at all, and short circuit if it isn't. if section.location.block_type != 'chapter': section_errors.append(_error_for_not_section(section)) return (None, section_errors) # We haven't officially killed off problemset and videosequence yet, so # treat them as equivalent to sequential for now. valid_sequence_tags = ['sequential', 'problemset', 'videosequence'] sequences_data = [] for sequence in section.get_children(): if sequence.location.block_type not in valid_sequence_tags: section_errors.append(_error_for_not_sequence(section, sequence)) continue sequences_data.append( CourseLearningSequenceData( usage_key=_remove_version_info(sequence.location), title=sequence.display_name_with_default, inaccessible_after_due=sequence.hide_after_due, exam=ExamData( is_practice_exam=sequence.is_practice_exam, is_proctored_enabled=sequence.is_proctored_enabled, is_time_limited=sequence.is_time_limited, ), visibility=VisibilityData( hide_from_toc=sequence.hide_from_toc, visible_to_staff_only=sequence.visible_to_staff_only, ), )) section_data = CourseSectionData( usage_key=_remove_version_info(section.location), title=section.display_name_with_default, sequences=sequences_data, visibility=VisibilityData( hide_from_toc=section.hide_from_toc, visible_to_staff_only=section.visible_to_staff_only, ), ) return section_data, section_errors
def _make_section_data(section): """ Return a (CourseSectionData, List[ContentDataError]) from a SectionBlock. Can return None for CourseSectionData if it's not really a SectionBlock that was passed in. This method does a lot of the work to convert modulestore fields to an input that the learning_sequences app expects. OLX import permits structures that are much less constrained than Studio's UI allows for, so whenever we run into something that does not meet our Course -> Section -> Subsection hierarchy expectations, we add a support-team-readable error message to our list of ContentDataErrors to pass back. At this point in the code, everything has already been deserialized into SectionBlocks and SequenceBlocks, but we're going to phrase our messages in ways that would make sense to someone looking at the import OLX, since that is the layer that the course teams and support teams are working with. """ section_errors = [] # First check if it's not a section at all, and short circuit if it isn't. if section.location.block_type != 'chapter': section_errors.append(_error_for_not_section(section)) return (None, section_errors) section_user_partition_groups, error = _make_user_partition_groups( section.location, section.group_access ) # Invalid user partition errors aren't fatal. Just log and continue on. if error: section_errors.append(error) # We haven't officially killed off problemset and videosequence yet, so # treat them as equivalent to sequential for now. valid_sequence_tags = ['sequential', 'problemset', 'videosequence'] sequences_data = [] for sequence in section.get_children(): if sequence.location.block_type not in valid_sequence_tags: section_errors.append(_error_for_not_sequence(section, sequence)) continue seq_user_partition_groups, error = _make_user_partition_groups( sequence.location, sequence.group_access ) if error: section_errors.append(error) # Bubble up User Partition Group settings from Units if appropriate. sequence_upg_from_units = _bubbled_up_groups_from_units( [unit.group_access for unit in sequence.get_children()] ) for user_partition_id, group_ids in sequence_upg_from_units.items(): # If there's an existing user partition ID set at the sequence # level, we respect it, even if it seems nonsensical. The hack of # bubbling things up from the Unit level is only done if there's # no conflicting value set at the Sequence level. if user_partition_id not in seq_user_partition_groups: section_errors.append( _make_bubbled_up_error(sequence.location, user_partition_id, group_ids) ) seq_user_partition_groups[user_partition_id] = group_ids else: section_errors.append( _make_not_bubbled_up_error( sequence.location, sequence.group_access, user_partition_id, group_ids ) ) sequences_data.append( CourseLearningSequenceData( usage_key=_remove_version_info(sequence.location), title=sequence.display_name_with_default, inaccessible_after_due=sequence.hide_after_due, exam=ExamData( is_practice_exam=sequence.is_practice_exam, is_proctored_enabled=sequence.is_proctored_enabled, is_time_limited=sequence.is_time_limited, ), visibility=VisibilityData( hide_from_toc=sequence.hide_from_toc, visible_to_staff_only=sequence.visible_to_staff_only, ), user_partition_groups=seq_user_partition_groups, ) ) section_data = CourseSectionData( usage_key=_remove_version_info(section.location), title=section.display_name_with_default, sequences=sequences_data, visibility=VisibilityData( hide_from_toc=section.hide_from_toc, visible_to_staff_only=section.visible_to_staff_only, ), user_partition_groups=section_user_partition_groups, ) return section_data, section_errors