diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index 4126a4b0dd..06ebbaff69 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -561,6 +561,7 @@ class SequenceModule(SequenceFields, ProctoringFields, XModule): context['show_bookmark_button'] = show_bookmark_button context['bookmarked'] = is_bookmarked + context['format'] = getattr(self, 'format', '') if render_items: rendered_item = item.render(view, context) diff --git a/common/lib/xmodule/xmodule/tests/test_vertical.py b/common/lib/xmodule/xmodule/tests/test_vertical.py index dd0e187d1f..3287d884d7 100644 --- a/common/lib/xmodule/xmodule/tests/test_vertical.py +++ b/common/lib/xmodule/xmodule/tests/test_vertical.py @@ -6,7 +6,9 @@ Tests for vertical module. from collections import namedtuple +from datetime import datetime, timedelta import json +import pytz import six import ddt @@ -67,6 +69,11 @@ class StubCompletionService(object): def blocks_to_mark_complete_on_view(self, blocks): return {} if self._completion_value == 1.0 else blocks + 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 + class BaseVerticalBlockTest(XModuleXmlImportTest): """ @@ -120,18 +127,24 @@ class VerticalBlockTestCase(BaseVerticalBlockTest): @ddt.unpack @ddt.data( - {'context': None, 'view': STUDENT_VIEW}, - {'context': {}, 'view': STUDENT_VIEW}, - {'context': {}, 'view': PUBLIC_VIEW}, + {'context': None, 'view': STUDENT_VIEW, 'completion_value': 0.0, 'days': 1}, + {'context': {}, 'view': STUDENT_VIEW, 'completion_value': 0.0, 'days': 1}, + {'context': {}, 'view': PUBLIC_VIEW, 'completion_value': 0.0, 'days': 1}, + {'context': {'format': 'Quiz'}, 'view': STUDENT_VIEW, 'completion_value': 1.0, 'days': 1}, # completed + {'context': {'format': 'Exam'}, 'view': STUDENT_VIEW, 'completion_value': 0.0, 'days': 1}, # upcoming + {'context': {'format': 'Homework'}, 'view': STUDENT_VIEW, 'completion_value': 0.0, 'days': -1}, # past due ) - def test_render_student_preview_view(self, context, view): + def test_render_student_preview_view(self, context, view, completion_value, days): """ Test the rendering of the student and public view. """ self.module_system._services['bookmarks'] = Mock() + now = datetime.now(pytz.UTC) + self.vertical.due = now + timedelta(days=days) if view == STUDENT_VIEW: self.module_system._services['user'] = StubUserService() - self.module_system._services['completion'] = StubCompletionService(enabled=True, completion_value=0.0) + self.module_system._services['completion'] = StubCompletionService(enabled=True, + completion_value=completion_value) elif view == PUBLIC_VIEW: self.module_system._services['user'] = StubUserService(is_anonymous=True) @@ -140,10 +153,16 @@ class VerticalBlockTestCase(BaseVerticalBlockTest): ).content self.assertIn(self.test_html_1, html) self.assertIn(self.test_html_2, 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: self.assert_bookmark_info(self.assertIn, html) else: 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("'past_due': {}".format(self.vertical.due < now), html) @ddt.unpack @ddt.data( @@ -163,7 +182,7 @@ class VerticalBlockTestCase(BaseVerticalBlockTest): completion_value=completion_value, ) self.module_system.render(self.vertical, STUDENT_VIEW, self.default_context) - if (mark_completed_enabled): + if mark_completed_enabled: self.assertEqual( mock_student_view.call_args[0][1]['wrap_xblock_data']['mark-completed-on-view-after-delay'], 9876 ) diff --git a/common/lib/xmodule/xmodule/vertical_block.py b/common/lib/xmodule/xmodule/vertical_block.py index 08cfc2e7ee..9654d912e3 100644 --- a/common/lib/xmodule/xmodule/vertical_block.py +++ b/common/lib/xmodule/xmodule/vertical_block.py @@ -5,8 +5,10 @@ VerticalBlock - an XBlock which renders its children in a column. import logging from copy import copy +from datetime import datetime from functools import reduce +import pytz import six from lxml import etree from web_fragments.fragment import Fragment @@ -89,10 +91,16 @@ 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) fragment_context = { 'items': contents, 'xblock_context': context, 'unit_title': self.display_name_with_default if not is_child_of_vertical else None, + 'due': self.due, + 'completed': completed, + 'past_due': past_due, + 'subsection_format': context.get('format', ''), } if view == STUDENT_VIEW: diff --git a/lms/static/sass/course/courseware/_courseware.scss b/lms/static/sass/course/courseware/_courseware.scss index 2e675cd182..ab6b337639 100644 --- a/lms/static/sass/course/courseware/_courseware.scss +++ b/lms/static/sass/course/courseware/_courseware.scss @@ -523,6 +523,33 @@ html.video-fullscreen { } } + .vert-due-date { + color: #686b73; + display: flex; + font-size: 16px; + + .pill { + font-size: 12px; + + padding: 2px 8px; + border-radius: 5px; + margin-left: 8px; + font-style: italic; + font-weight: bold; + vertical-align: top; + + &.completed { + background-color: #f3f3f4; + color: #2d323e; + } + + &.past-due { + background-color: #d1d2d4; + color: black; + } + } + } + .vert-mod { padding: 0; margin: 0; diff --git a/lms/templates/vert_module.html b/lms/templates/vert_module.html index 69620585f7..bc0f541207 100644 --- a/lms/templates/vert_module.html +++ b/lms/templates/vert_module.html @@ -1,10 +1,31 @@ <%page expression_filter="h"/> -<%! from openedx.core.djangolib.markup import HTML %> + +<%namespace name='static' file='/static_content.html'/> + +<%! +from django.utils.translation import gettext as _ + +from openedx.core.djangolib.markup import HTML +%> %if unit_title and show_title: