Commit aafd6a03 by Diana Huang

Add 'format' as a requested field.

parent 31aa776e
...@@ -134,16 +134,6 @@ class CourseHomeA11yTest(CourseHomeBaseTest): ...@@ -134,16 +134,6 @@ class CourseHomeA11yTest(CourseHomeBaseTest):
self.logout_page = LogoutPage(self.browser) self.logout_page = LogoutPage(self.browser)
self.studio_course_outline = StudioCourseOutlinePage(
self.browser,
self.course_info['org'],
self.course_info['number'],
self.course_info['run']
)
# adds graded assignments to course home for testing course outline a11y
self._set_policy_for_subsection("Homework", 0)
def test_course_home_a11y(self): def test_course_home_a11y(self):
""" """
Test the accessibility of the course home page with course outline. Test the accessibility of the course home page with course outline.
...@@ -153,18 +143,6 @@ class CourseHomeA11yTest(CourseHomeBaseTest): ...@@ -153,18 +143,6 @@ class CourseHomeA11yTest(CourseHomeBaseTest):
course_home_page.visit() course_home_page.visit()
course_home_page.a11y_audit.check_for_accessibility_errors() course_home_page.a11y_audit.check_for_accessibility_errors()
def _set_policy_for_subsection(self, policy, section=0):
"""
Set the grading policy for the first subsection in the specified section.
If a section index is not provided, 0 is assumed.
"""
with self._logged_in_session(staff=True):
self.studio_course_outline.visit()
modal = self.studio_course_outline.section_at(section).subsection_at(
0).edit()
modal.policy = policy
modal.save()
@contextmanager @contextmanager
def _logged_in_session(self, staff=False): def _logged_in_session(self, staff=False):
""" """
......
...@@ -51,12 +51,12 @@ def get_blocks( ...@@ -51,12 +51,12 @@ def get_blocks(
""" """
# create ordered list of transformers, adding BlocksAPITransformer at end. # create ordered list of transformers, adding BlocksAPITransformer at end.
transformers = BlockStructureTransformers() transformers = BlockStructureTransformers()
can_view_special_exam = False include_special_exams = False
if requested_fields is not None and 'special_exam' in requested_fields: if requested_fields is not None and 'special_exam_info' in requested_fields:
can_view_special_exam = True include_special_exams = True
if user is not None: if user is not None:
transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS
transformers += [MilestonesTransformer(can_view_special_exam), HiddenContentTransformer()] transformers += [MilestonesTransformer(include_special_exams), HiddenContentTransformer()]
transformers += [ transformers += [
BlocksAPITransformer( BlocksAPITransformer(
block_counts, block_counts,
......
...@@ -45,7 +45,7 @@ SUPPORTED_FIELDS = [ ...@@ -45,7 +45,7 @@ SUPPORTED_FIELDS = [
# 'student_view_multi_device' # 'student_view_multi_device'
SupportedFieldType(StudentViewTransformer.STUDENT_VIEW_MULTI_DEVICE, StudentViewTransformer), SupportedFieldType(StudentViewTransformer.STUDENT_VIEW_MULTI_DEVICE, StudentViewTransformer),
SupportedFieldType('special_exam', MilestonesTransformer), SupportedFieldType('special_exam_info', MilestonesTransformer),
# set the block_field_name to None so the entire data for the transformer is serialized # set the block_field_name to None so the entire data for the transformer is serialized
SupportedFieldType(None, BlockCountsTransformer, BlockCountsTransformer.BLOCK_COUNTS), SupportedFieldType(None, BlockCountsTransformer, BlockCountsTransformer.BLOCK_COUNTS),
......
...@@ -19,7 +19,8 @@ log = logging.getLogger(__name__) ...@@ -19,7 +19,8 @@ log = logging.getLogger(__name__)
class MilestonesTransformer(BlockStructureTransformer): class MilestonesTransformer(BlockStructureTransformer):
""" """
Excludes all special exams (timed, proctored, practice proctored) from the student view. Adds special exams (timed, proctored, practice proctored) to the student view.
May exclude special exams.
Excludes all blocks with unfulfilled milestones from the student view. Excludes all blocks with unfulfilled milestones from the student view.
""" """
WRITE_VERSION = 1 WRITE_VERSION = 1
...@@ -29,8 +30,8 @@ class MilestonesTransformer(BlockStructureTransformer): ...@@ -29,8 +30,8 @@ class MilestonesTransformer(BlockStructureTransformer):
def name(cls): def name(cls):
return "milestones" return "milestones"
def __init__(self, can_view_special_exams=True): def __init__(self, include_special_exams=True):
self.can_view_special_exams = can_view_special_exams self.include_special_exams = include_special_exams
@classmethod @classmethod
def collect(cls, block_structure): def collect(cls, block_structure):
...@@ -51,48 +52,9 @@ class MilestonesTransformer(BlockStructureTransformer): ...@@ -51,48 +52,9 @@ class MilestonesTransformer(BlockStructureTransformer):
Modify block structure according to the behavior of milestones and special exams. Modify block structure according to the behavior of milestones and special exams.
""" """
def add_special_exam_info(block_key): course_key = block_structure.root_block_usage_key.course_key
"""
Adds special exam information to course blocks.
"""
if self.is_special_exam(block_key, block_structure):
#
# call into edx_proctoring subsystem
# to get relevant proctoring information regarding this
# level of the courseware
#
# This will return None, if (user, course_id, content_id)
# is not applicable
#
timed_exam_attempt_context = None
try:
timed_exam_attempt_context = get_attempt_status_summary(
usage_info.user.id,
unicode(block_key.course_key),
unicode(block_key)
)
except ProctoredExamNotFoundException as ex:
log.exception(ex)
if timed_exam_attempt_context:
# yes, user has proctoring context about
# this level of the courseware
# so add to the accordion data context
block_structure.set_transformer_block_field(
block_key,
self,
'special_exam',
timed_exam_attempt_context,
)
root_key = block_structure.root_block_usage_key
course_key = root_key.course_key
user_can_skip = EntranceExamConfiguration.user_can_skip_entrance_exam(usage_info.user, course_key) user_can_skip = EntranceExamConfiguration.user_can_skip_entrance_exam(usage_info.user, course_key)
exam_id = block_structure.get_xblock_field(root_key, 'entrance_exam_id')
required_content = milestones_helpers.get_required_content(course_key, usage_info.user) required_content = milestones_helpers.get_required_content(course_key, usage_info.user)
if user_can_skip:
required_content = [content for content in required_content if not content == exam_id]
def user_gated_from_block(block_key): def user_gated_from_block(block_key):
""" """
...@@ -103,12 +65,11 @@ class MilestonesTransformer(BlockStructureTransformer): ...@@ -103,12 +65,11 @@ class MilestonesTransformer(BlockStructureTransformer):
return False return False
elif self.has_pending_milestones_for_user(block_key, usage_info): elif self.has_pending_milestones_for_user(block_key, usage_info):
return True return True
elif required_content: elif self.gated_by_required_content(block_key, block_structure, user_can_skip, required_content):
if block_key.block_type == 'chapter' and unicode(block_key) not in required_content: return True
return True
elif (settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and elif (settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and
(self.is_special_exam(block_key, block_structure) and (self.is_special_exam(block_key, block_structure) and
not self.can_view_special_exams)): not self.include_special_exams)):
return True return True
return False return False
...@@ -116,7 +77,7 @@ class MilestonesTransformer(BlockStructureTransformer): ...@@ -116,7 +77,7 @@ class MilestonesTransformer(BlockStructureTransformer):
if user_gated_from_block(block_key): if user_gated_from_block(block_key):
block_structure.remove_block(block_key, False) block_structure.remove_block(block_key, False)
else: else:
add_special_exam_info(block_key) self.add_special_exam_info(block_key, block_structure, usage_info)
@staticmethod @staticmethod
def is_special_exam(block_key, block_structure): def is_special_exam(block_key, block_structure):
...@@ -142,3 +103,50 @@ class MilestonesTransformer(BlockStructureTransformer): ...@@ -142,3 +103,50 @@ class MilestonesTransformer(BlockStructureTransformer):
'requires', 'requires',
usage_info.user.id usage_info.user.id
)) ))
def add_special_exam_info(self, block_key, block_structure, usage_info):
"""
Adds special exam information to course blocks.
"""
if self.is_special_exam(block_key, block_structure):
# call into edx_proctoring subsystem to get relevant special exam information
#
# This will return None, if (user, course_id, content_id) is not applicable
special_exam_attempt_context = None
try:
special_exam_attempt_context = get_attempt_status_summary(
usage_info.user.id,
unicode(block_key.course_key),
unicode(block_key)
)
except ProctoredExamNotFoundException as ex:
log.exception(ex)
if special_exam_attempt_context:
# yes, user has proctoring context about
# this level of the courseware
# so add to the accordion data context
block_structure.set_transformer_block_field(
block_key,
self,
'special_exam_info',
special_exam_attempt_context,
)
@staticmethod
def gated_by_required_content(block_key, block_structure, user_can_skip, required_content):
"""
Returns True if the current block associated with the block_key should be gated by the given required_content.
Returns False otherwise.
"""
if not required_content:
return False
exam_id = block_structure.get_xblock_field(block_structure.root_block_usage_key, 'entrance_exam_id')
if user_can_skip:
required_content = [content for content in required_content if not content == exam_id]
if block_key.block_type == 'chapter' and unicode(block_key) not in required_content:
return True
return False
...@@ -186,7 +186,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM ...@@ -186,7 +186,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
self.setup_gated_section(self.blocks['H'], self.blocks['A']) self.setup_gated_section(self.blocks['H'], self.blocks['A'])
self.get_blocks_and_check_against_expected(self.staff, expected_blocks) self.get_blocks_and_check_against_expected(self.staff, expected_blocks)
def test_can_view_special(self): def test_special_exams(self):
""" """
When the block structure transformers are set to allow users to view special exams, When the block structure transformers are set to allow users to view special exams,
ensure that we can see the special exams and not any of the otherwise gated blocks. ensure that we can see the special exams and not any of the otherwise gated blocks.
......
...@@ -51,26 +51,26 @@ from django.utils.translation import ugettext as _ ...@@ -51,26 +51,26 @@ from django.utils.translation import ugettext as _
if subsection.get('due') is None: if subsection.get('due') is None:
data_string = subsection.get('format') data_string = subsection.get('format')
else: else:
if 'special_exam' in subsection: if 'special_exam_info' in subsection:
data_string = _('due {date}') data_string = _('due {date}')
else: else:
data_string = _("{subsection_format} due {{date}}").format(subsection_format=subsection.get('format')) data_string = _("{subsection_format} due {{date}}").format(subsection_format=subsection.get('format'))
%> %>
% if subsection.get('format') or 'special_exam' in subsection: % if subsection.get('format') or 'special_exam_info' in subsection:
<span class="subtitle"> <span class="subtitle">
% if 'special_exam' in subsection: % if 'special_exam' in subsection:
## Display the proctored exam status icon and status message ## Display the proctored exam status icon and status message
<span <span
class="menu-icon icon fa ${subsection['special_exam'].get('suggested_icon', 'fa-pencil-square-o')} ${subsection['special_exam'].get('status', 'eligible')}" class="menu-icon icon fa ${subsection['special_exam_info'].get('suggested_icon', 'fa-pencil-square-o')} ${subsection['special_exam_info'].get('status', 'eligible')}"
aria-hidden="true" aria-hidden="true"
></span> ></span>
<span class="subtitle-name"> <span class="subtitle-name">
${subsection['special_exam'].get('short_description', '')} ${subsection['special_exam_info'].get('short_description', '')}
</span> </span>
## completed proctored exam statuses should not show the due date ## completed proctored exam statuses should not show the due date
## since the exam has already been submitted by the user ## since the exam has already been submitted by the user
% if not subsection['special_exam'].get('in_completed_state', False): % if not subsection['special_exam_info'].get('in_completed_state', False):
<span <span
class="localized-datetime subtitle-name" class="localized-datetime subtitle-name"
data-datetime="${subsection.get('due')}" data-datetime="${subsection.get('due')}"
......
...@@ -47,6 +47,22 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): ...@@ -47,6 +47,22 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
ItemFactory.create(category='vertical', parent_location=section2.location) ItemFactory.create(category='vertical', parent_location=section2.location)
course.last_accessed = None course.last_accessed = None
cls.courses.append(course)
course = CourseFactory.create()
with cls.store.bulk_operations(course.id):
chapter = ItemFactory.create(category='chapter', parent_location=course.location)
section = ItemFactory.create(
category='sequential',
parent_location=chapter.location,
due=datetime.datetime.now(),
graded=True,
format='Homework',
)
ItemFactory.create(category='vertical', parent_location=section.location)
course.last_accessed = section.url_name
cls.courses.append(course)
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
"""Set up and enroll our fake user in the course.""" """Set up and enroll our fake user in the course."""
...@@ -70,14 +86,14 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase): ...@@ -70,14 +86,14 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
response_content = response.content.decode("utf-8") response_content = response.content.decode("utf-8")
if course.last_accessed is not None: self.assertIn('Resume Course', response_content)
self.assertIn('Resume Course', response_content)
else:
self.assertNotIn('Resume Course', response_content)
for chapter in course.children: for chapter in course.children:
self.assertIn(chapter.display_name, response_content) self.assertIn(chapter.display_name, response_content)
for section in chapter.children: for section in chapter.children:
self.assertIn(section.display_name, response_content) self.assertIn(section.display_name, response_content)
if section.graded:
self.assertIn(section.due, response_content)
self.assertIn(section.format, response_content)
for vertical in section.children: for vertical in section.children:
self.assertNotIn(vertical.display_name, response_content) self.assertNotIn(vertical.display_name, response_content)
......
...@@ -48,7 +48,7 @@ class CourseOutlineFragmentView(EdxFragmentView): ...@@ -48,7 +48,7 @@ class CourseOutlineFragmentView(EdxFragmentView):
course_usage_key, course_usage_key,
user=request.user, user=request.user,
nav_depth=3, nav_depth=3,
requested_fields=['children', 'display_name', 'type', 'due', 'graded', 'special_exam'], requested_fields=['children', 'display_name', 'type', 'due', 'graded', 'special_exam_info', 'format'],
block_types_filter=['course', 'chapter', 'sequential'] block_types_filter=['course', 'chapter', 'sequential']
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment