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:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user