Example #1
0
 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
Example #2
0
 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,
     )
Example #3
0
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
Example #5
0
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