diff --git a/lms/djangoapps/courseware/tests/test_courses.py b/lms/djangoapps/courseware/tests/test_courses.py index ffb2a0dcc3..9cb46095d1 100644 --- a/lms/djangoapps/courseware/tests/test_courses.py +++ b/lms/djangoapps/courseware/tests/test_courses.py @@ -443,3 +443,17 @@ class TestGetCourseAssignments(CompletionWaffleTestMixin, ModuleStoreTestCase): assignments = get_course_assignments(course.location.context_key, self.user, None) assert len(assignments) == 1 assert assignments[0].complete + + def test_completion_does_not_count_empty_sequentials(self): + """ + Test that we treat a sequential with no content as incomplete. + + This can happen with unreleased assignments, for example (start date in future). + """ + course = CourseFactory() + chapter = ItemFactory(parent=course, category='chapter', graded=True, due=datetime.datetime.now()) + ItemFactory(parent=chapter, category='sequential') + + assignments = get_course_assignments(course.location.context_key, self.user, None) + assert len(assignments) == 1 + assert not assignments[0].complete diff --git a/openedx/features/course_experience/utils.py b/openedx/features/course_experience/utils.py index 1968174c69..023df00f47 100644 --- a/openedx/features/course_experience/utils.py +++ b/openedx/features/course_experience/utils.py @@ -214,6 +214,15 @@ def is_block_structure_complete_for_assignments(block_data, block_key): if children: return all(is_block_structure_complete_for_assignments(block_data, child_key) for child_key in children) + category = block_data.get_xblock_field(block_key, 'category') + if category in ('course', 'chapter', 'sequential', 'vertical'): + # If there are no children for these "hierarchy" block types, just bail. This could be because the + # content isn't available yet (start date in future) or we're too late and the block has hide_after_due + # set. Or maybe a different transformer cut off content for whatever reason. Regardless of the cause - if the + # user can't see this content and we continue, we might accidentally say this block is complete because it + # isn't scored (which most hierarchy blocks wouldn't be). + return False + complete = block_data.get_xblock_field(block_key, 'complete', False) graded = block_data.get_xblock_field(block_key, 'graded', False) has_score = block_data.get_xblock_field(block_key, 'has_score', False)