AA-225: Only consider scored items for past due assignments

When considering if an assignment is past due for the dates tab,
only look at the scored and graded units in the subsection (i.e.
ignore reading and video units).

This still leaves the "complete" field alone -- i.e. those
subsections will still be left incomplete generally. But for
assignment-focused tasks, they will instead be considered complete.
This commit is contained in:
Michael Terry
2020-07-02 16:01:56 -04:00
parent 1c57bf1be5
commit 939f268da8
6 changed files with 135 additions and 29 deletions

View File

@@ -60,6 +60,9 @@ class StubCompletionService(object):
"""
return {candidate: self._completion_value for candidate in candidates}
def get_completable_children(self, node):
return node.get_children()
def get_complete_on_view_delay_ms(self):
"""
Return the completion-by-viewing delay in milliseconds.
@@ -72,15 +75,15 @@ class StubCompletionService(object):
def vertical_is_complete(self, item):
if item.scope_ids.block_type != 'vertical':
raise ValueError('The passed in xblock is not a vertical type!')
return self._completion_value
return self._completion_value == 1 if self._enabled else None
class BaseVerticalBlockTest(XModuleXmlImportTest):
"""
Tests for the BaseVerticalBlock.
"""
test_html_1 = 'Test HTML 1'
test_html_2 = 'Test HTML 2'
test_html = 'Test HTML'
test_problem = 'Test Problem'
def setUp(self):
super(BaseVerticalBlockTest, self).setUp()
@@ -90,8 +93,8 @@ class BaseVerticalBlockTest(XModuleXmlImportTest):
vertical = xml.VerticalFactory.build(parent=sequence)
self.course = self.process_xml(course)
xml.HtmlFactory(parent=vertical, url_name='test-html-1', text=self.test_html_1)
xml.HtmlFactory(parent=vertical, url_name='test-html-2', text=self.test_html_2)
xml.HtmlFactory(parent=vertical, url_name='test-html', text=self.test_html)
xml.ProblemFactory(parent=vertical, url_name='test-problem', text=self.test_problem)
self.course = self.process_xml(course)
course_seq = self.course.get_children()[0]
@@ -103,8 +106,10 @@ class BaseVerticalBlockTest(XModuleXmlImportTest):
self.vertical = course_seq.get_children()[0]
self.vertical.xmodule_runtime = self.module_system
self.html1block = self.vertical.get_children()[0]
self.html2block = self.vertical.get_children()[1]
self.html_block = self.vertical.get_children()[0]
self.problem_block = self.vertical.get_children()[1]
self.problem_block.has_score = True
self.problem_block.graded = True
self.username = "bilbo"
self.default_context = {"bookmarked": False, "username": self.username}
@@ -151,8 +156,11 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
html = self.module_system.render(
self.vertical, view, self.default_context if context is None else context
).content
self.assertIn(self.test_html_1, html)
self.assertIn(self.test_html_2, html)
self.assertIn(self.test_html, html)
if view == STUDENT_VIEW:
self.assertIn(self.test_problem, html)
else:
self.assertNotIn(self.test_problem, html)
self.assertIn("'due': datetime.datetime({year}, {month}, {day}".format(
year=self.vertical.due.year, month=self.vertical.due.month, day=self.vertical.due.day), html)
if view == STUDENT_VIEW:
@@ -161,9 +169,30 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
self.assert_bookmark_info(self.assertNotIn, html)
if context:
self.assertIn("'subsection_format': '{}'".format(context['format']), html)
self.assertIn("'completed': {}".format(completion_value), html)
self.assertIn("'completed': {}".format(completion_value == 1), html)
self.assertIn("'past_due': {}".format(self.vertical.due < now), html)
@ddt.data(True, False)
def test_render_problem_without_score(self, has_score):
"""
Test the rendering of the student and public view.
"""
self.module_system._services['bookmarks'] = Mock()
self.module_system._services['user'] = StubUserService()
self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0)
now = datetime.now(pytz.UTC)
self.vertical.due = now + timedelta(days=-1)
self.problem_block.has_score = has_score
html = self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context).content
if has_score:
self.assertIn("'completed': False", html)
self.assertIn("'past_due': True", html)
else:
self.assertIn("'completed': None", html)
self.assertIn("'past_due': False", html)
@ddt.unpack
@ddt.data(
(True, 0.9, True),
@@ -176,7 +205,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
"""
Test that mark-completed-on-view-after-delay is only set for relevant child Xblocks.
"""
with patch.object(self.html1block, 'render') as mock_student_view:
with patch.object(self.html_block, 'render') as mock_student_view:
self.module_system._services['completion'] = StubCompletionService(
enabled=completion_enabled,
completion_value=completion_value,
@@ -198,8 +227,8 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
'is_unit_page': True
}
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content
self.assertNotIn(self.test_html_1, html)
self.assertNotIn(self.test_html_2, html)
self.assertNotIn(self.test_html, html)
self.assertNotIn(self.test_problem, html)
# Vertical should render reorderable children on the container page
reorderable_items = set()
@@ -208,5 +237,5 @@ class VerticalBlockTestCase(BaseVerticalBlockTest):
'reorderable_items': reorderable_items,
}
html = self.module_system.render(self.vertical, AUTHOR_VIEW, context).content
self.assertIn(self.test_html_1, html)
self.assertIn(self.test_html_2, html)
self.assertIn(self.test_html, html)
self.assertIn(self.test_problem, html)

View File

@@ -91,8 +91,8 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse
'content': rendered_child.content
})
completed = completion_service and completion_service.vertical_is_complete(self)
past_due = not completed and self.due and self.due < datetime.now(pytz.UTC)
completed = self.is_block_complete_for_assignments(completion_service)
past_due = completed is False and self.due and self.due < datetime.now(pytz.UTC)
fragment_context = {
'items': contents,
'xblock_context': context,
@@ -224,3 +224,40 @@ class VerticalBlock(SequenceFields, XModuleFields, StudioEditableBlock, XmlParse
xblock_body["content_type"] = "Sequence"
return xblock_body
# So far, we only need this here. Move it somewhere more sensible if other bits of code want it too.
def is_block_complete_for_assignments(self, completion_service):
"""
Considers a block complete only if all scored & graded leaf blocks are complete.
This is different from the normal `complete` flag because children of the block that are informative (like
readings or videos) do not count. We only care about actual homework content.
Compare with is_block_structure_complete_for_assignments in course_experience/utils.py, which does the same
calculation, but for a BlockStructure node and its children.
Returns:
True if complete
False if not
None if no assignments present or no completion info present (don't show any past-due or complete info)
"""
if not completion_service or not completion_service.completion_tracking_enabled():
return None
children = completion_service.get_completable_children(self)
children_locations = [child.scope_ids.usage_id for child in children]
completions = completion_service.get_completions(children_locations)
all_complete = None
for child in children:
complete = completions[child.scope_ids.usage_id] == 1
graded = getattr(child, 'graded', False)
has_score = getattr(child, 'has_score', False)
weight = getattr(child, 'weight', 1)
scored = has_score and (weight is None or weight > 0)
if graded and scored:
if not complete:
return False
all_complete = True
return all_complete