Add 'format' as a requested field.
This commit is contained in:
@@ -134,16 +134,6 @@ class CourseHomeA11yTest(CourseHomeBaseTest):
|
||||
|
||||
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):
|
||||
"""
|
||||
Test the accessibility of the course home page with course outline.
|
||||
@@ -153,18 +143,6 @@ class CourseHomeA11yTest(CourseHomeBaseTest):
|
||||
course_home_page.visit()
|
||||
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
|
||||
def _logged_in_session(self, staff=False):
|
||||
"""
|
||||
|
||||
@@ -51,12 +51,12 @@ def get_blocks(
|
||||
"""
|
||||
# create ordered list of transformers, adding BlocksAPITransformer at end.
|
||||
transformers = BlockStructureTransformers()
|
||||
can_view_special_exam = False
|
||||
if requested_fields is not None and 'special_exam' in requested_fields:
|
||||
can_view_special_exam = True
|
||||
include_special_exams = False
|
||||
if requested_fields is not None and 'special_exam_info' in requested_fields:
|
||||
include_special_exams = True
|
||||
if user is not None:
|
||||
transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS
|
||||
transformers += [MilestonesTransformer(can_view_special_exam), HiddenContentTransformer()]
|
||||
transformers += [MilestonesTransformer(include_special_exams), HiddenContentTransformer()]
|
||||
transformers += [
|
||||
BlocksAPITransformer(
|
||||
block_counts,
|
||||
|
||||
@@ -45,7 +45,7 @@ SUPPORTED_FIELDS = [
|
||||
# 'student_view_multi_device'
|
||||
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
|
||||
SupportedFieldType(None, BlockCountsTransformer, BlockCountsTransformer.BLOCK_COUNTS),
|
||||
|
||||
@@ -19,7 +19,8 @@ log = logging.getLogger(__name__)
|
||||
|
||||
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.
|
||||
"""
|
||||
WRITE_VERSION = 1
|
||||
@@ -29,8 +30,8 @@ class MilestonesTransformer(BlockStructureTransformer):
|
||||
def name(cls):
|
||||
return "milestones"
|
||||
|
||||
def __init__(self, can_view_special_exams=True):
|
||||
self.can_view_special_exams = can_view_special_exams
|
||||
def __init__(self, include_special_exams=True):
|
||||
self.include_special_exams = include_special_exams
|
||||
|
||||
@classmethod
|
||||
def collect(cls, block_structure):
|
||||
@@ -51,48 +52,9 @@ class MilestonesTransformer(BlockStructureTransformer):
|
||||
Modify block structure according to the behavior of milestones and special exams.
|
||||
"""
|
||||
|
||||
def add_special_exam_info(block_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
|
||||
course_key = block_structure.root_block_usage_key.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)
|
||||
if user_can_skip:
|
||||
required_content = [content for content in required_content if not content == exam_id]
|
||||
|
||||
def user_gated_from_block(block_key):
|
||||
"""
|
||||
@@ -103,12 +65,11 @@ class MilestonesTransformer(BlockStructureTransformer):
|
||||
return False
|
||||
elif self.has_pending_milestones_for_user(block_key, usage_info):
|
||||
return True
|
||||
elif required_content:
|
||||
if block_key.block_type == 'chapter' and unicode(block_key) not in required_content:
|
||||
return True
|
||||
elif self.gated_by_required_content(block_key, block_structure, user_can_skip, required_content):
|
||||
return True
|
||||
elif (settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and
|
||||
(self.is_special_exam(block_key, block_structure) and
|
||||
not self.can_view_special_exams)):
|
||||
not self.include_special_exams)):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -116,7 +77,7 @@ class MilestonesTransformer(BlockStructureTransformer):
|
||||
if user_gated_from_block(block_key):
|
||||
block_structure.remove_block(block_key, False)
|
||||
else:
|
||||
add_special_exam_info(block_key)
|
||||
self.add_special_exam_info(block_key, block_structure, usage_info)
|
||||
|
||||
@staticmethod
|
||||
def is_special_exam(block_key, block_structure):
|
||||
@@ -142,3 +103,50 @@ class MilestonesTransformer(BlockStructureTransformer):
|
||||
'requires',
|
||||
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
|
||||
self.setup_gated_section(self.blocks['H'], self.blocks['A'])
|
||||
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,
|
||||
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 _
|
||||
if subsection.get('due') is None:
|
||||
data_string = subsection.get('format')
|
||||
else:
|
||||
if 'special_exam' in subsection:
|
||||
if 'special_exam_info' in subsection:
|
||||
data_string = _('due {date}')
|
||||
else:
|
||||
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">
|
||||
% if 'special_exam' in subsection:
|
||||
## Display the proctored exam status icon and status message
|
||||
<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"
|
||||
></span>
|
||||
<span class="subtitle-name">
|
||||
${subsection['special_exam'].get('short_description', '')}
|
||||
${subsection['special_exam_info'].get('short_description', '')}
|
||||
</span>
|
||||
|
||||
## completed proctored exam statuses should not show the due date
|
||||
## 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
|
||||
class="localized-datetime subtitle-name"
|
||||
data-datetime="${subsection.get('due')}"
|
||||
|
||||
@@ -47,6 +47,22 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
ItemFactory.create(category='vertical', parent_location=section2.location)
|
||||
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
|
||||
def setUpTestData(cls):
|
||||
"""Set up and enroll our fake user in the course."""
|
||||
@@ -70,14 +86,14 @@ class TestCourseOutlinePage(SharedModuleStoreTestCase):
|
||||
self.assertEqual(response.status_code, 200)
|
||||
response_content = response.content.decode("utf-8")
|
||||
|
||||
if course.last_accessed is not None:
|
||||
self.assertIn('Resume Course', response_content)
|
||||
else:
|
||||
self.assertNotIn('Resume Course', response_content)
|
||||
self.assertIn('Resume Course', response_content)
|
||||
for chapter in course.children:
|
||||
self.assertIn(chapter.display_name, response_content)
|
||||
for section in chapter.children:
|
||||
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:
|
||||
self.assertNotIn(vertical.display_name, response_content)
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ class CourseOutlineFragmentView(EdxFragmentView):
|
||||
course_usage_key,
|
||||
user=request.user,
|
||||
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']
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user