Commit 9e734442 by Diana Huang

Initial version of new transformer.

parent c58b3a14
...@@ -207,7 +207,7 @@ def remove_course_milestones(course_key, user, relationship): ...@@ -207,7 +207,7 @@ def remove_course_milestones(course_key, user, relationship):
milestones_api.remove_user_milestone({'id': user.id}, milestone) milestones_api.remove_user_milestone({'id': user.id}, milestone)
def get_required_content(course, user): def get_required_content(course_key, user):
""" """
Queries milestones subsystem to see if the specified course is gated on one or more milestones, Queries milestones subsystem to see if the specified course is gated on one or more milestones,
and if those milestones can be fulfilled via completion of a particular course content module and if those milestones can be fulfilled via completion of a particular course content module
...@@ -217,7 +217,7 @@ def get_required_content(course, user): ...@@ -217,7 +217,7 @@ def get_required_content(course, user):
# Get all of the outstanding milestones for this course, for this user # Get all of the outstanding milestones for this course, for this user
try: try:
milestone_paths = get_course_milestones_fulfillment_paths( milestone_paths = get_course_milestones_fulfillment_paths(
unicode(course.id), unicode(course_key),
serialize_user(user) serialize_user(user)
) )
except InvalidMilestoneRelationshipTypeException: except InvalidMilestoneRelationshipTypeException:
......
...@@ -30,6 +30,7 @@ class CoursewarePage(CoursePage): ...@@ -30,6 +30,7 @@ class CoursewarePage(CoursePage):
def is_browser_on_page(self): def is_browser_on_page(self):
return self.q(css='.course-content').present return self.q(css='.course-content').present
# TODO: TNL-6546: Remove and find callers
@property @property
def chapter_count_in_navigation(self): def chapter_count_in_navigation(self):
""" """
......
...@@ -1233,7 +1233,7 @@ class EntranceExamTest(UniqueCourseTest): ...@@ -1233,7 +1233,7 @@ class EntranceExamTest(UniqueCourseTest):
self.course_info['run'], self.course_info['display_name'] self.course_info['run'], self.course_info['display_name']
).install() ).install()
self.courseware_page = CoursewarePage(self.browser, self.course_id) self.course_home_page = CourseHomePage(self.browser, self.course_id)
self.settings_page = SettingsPage( self.settings_page = SettingsPage(
self.browser, self.browser,
self.course_info['org'], self.course_info['org'],
...@@ -1246,18 +1246,53 @@ class EntranceExamTest(UniqueCourseTest): ...@@ -1246,18 +1246,53 @@ class EntranceExamTest(UniqueCourseTest):
def test_entrance_exam_section(self): def test_entrance_exam_section(self):
""" """
Scenario: Any course that is enabled for an entrance exam, should have
entrance exam section in the course outline.
Given that I visit the course outline
And entrance exams are not yet enabled
Then I should not see an "Entrance Exam" section
When I log in as staff
And enable entrance exams
And I visit the course outline again as student
Then there should be an "Entrance Exam" chapter.'
"""
# visit the course outline and make sure there is no "Entrance Exam" section.
self.course_home_page.visit()
self.assertFalse('Entrance Exam' in self.course_home_page.outline.sections.keys())
# Logout and login as a staff.
LogoutPage(self.browser).visit()
AutoAuthPage(self.browser, course_id=self.course_id, staff=True).visit()
# visit course settings page and set/enabled entrance exam for that course.
self.settings_page.visit()
self.settings_page.entrance_exam_field.click()
self.settings_page.save_changes()
# Logout and login as a student.
LogoutPage(self.browser).visit()
AutoAuthPage(self.browser, course_id=self.course_id, staff=False).visit()
# visit the course outline and make sure there is an "Entrance Exam" section.
self.course_home_page.visit()
self.assertTrue('Entrance Exam' in self.course_home_page.outline.sections.keys())
# TODO: TNL-6546: Remove test
def test_entrance_exam_section_2(self):
"""
Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at course Scenario: Any course that is enabled for an entrance exam, should have entrance exam chapter at course
page. page.
Given that I am on the course page Given that I am on the course page
When I view the course that has an entrance exam When I view the course that has an entrance exam
Then there should be an "Entrance Exam" chapter.' Then there should be an "Entrance Exam" chapter.'
""" """
courseware_page = CoursewarePage(self.browser, self.course_id)
entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading' entrance_exam_link_selector = '.accordion .course-navigation .chapter .group-heading'
# visit course page and make sure there is not entrance exam chapter. # visit course page and make sure there is not entrance exam chapter.
self.courseware_page.visit() courseware_page.visit()
self.courseware_page.wait_for_page() courseware_page.wait_for_page()
self.assertFalse(element_has_text( self.assertFalse(element_has_text(
page=self.courseware_page, page=courseware_page,
css_selector=entrance_exam_link_selector, css_selector=entrance_exam_link_selector,
text='Entrance Exam' text='Entrance Exam'
)) ))
...@@ -1276,10 +1311,10 @@ class EntranceExamTest(UniqueCourseTest): ...@@ -1276,10 +1311,10 @@ class EntranceExamTest(UniqueCourseTest):
AutoAuthPage(self.browser, course_id=self.course_id, staff=False).visit() AutoAuthPage(self.browser, course_id=self.course_id, staff=False).visit()
# visit course info page and make sure there is an "Entrance Exam" section. # visit course info page and make sure there is an "Entrance Exam" section.
self.courseware_page.visit() courseware_page.visit()
self.courseware_page.wait_for_page() courseware_page.wait_for_page()
self.assertTrue(element_has_text( self.assertTrue(element_has_text(
page=self.courseware_page, page=courseware_page,
css_selector=entrance_exam_link_selector, css_selector=entrance_exam_link_selector,
text='Entrance Exam' text='Entrance Exam'
)) ))
......
...@@ -6,6 +6,7 @@ from textwrap import dedent ...@@ -6,6 +6,7 @@ from textwrap import dedent
from common.test.acceptance.tests.helpers import UniqueCourseTest from common.test.acceptance.tests.helpers import UniqueCourseTest
from common.test.acceptance.pages.studio.auto_auth import AutoAuthPage from common.test.acceptance.pages.studio.auto_auth import AutoAuthPage
from common.test.acceptance.pages.lms.course_home import CourseHomePage
from common.test.acceptance.pages.lms.courseware import CoursewarePage from common.test.acceptance.pages.lms.courseware import CoursewarePage
from common.test.acceptance.pages.lms.problem import ProblemPage from common.test.acceptance.pages.lms.problem import ProblemPage
from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc from common.test.acceptance.fixtures.course import CourseFixture, XBlockFixtureDesc
...@@ -92,6 +93,8 @@ class EntranceExamPassTest(EntranceExamTest): ...@@ -92,6 +93,8 @@ class EntranceExamPassTest(EntranceExamTest):
When I pass entrance exam When I pass entrance exam
Then I can see complete TOC of course Then I can see complete TOC of course
And I can see message indicating my pass status And I can see message indicating my pass status
When I switch to course home page
Then I see 2 sections
""" """
self.courseware_page.visit() self.courseware_page.visit()
problem_page = ProblemPage(self.browser) problem_page = ProblemPage(self.browser)
...@@ -102,4 +105,29 @@ class EntranceExamPassTest(EntranceExamTest): ...@@ -102,4 +105,29 @@ class EntranceExamPassTest(EntranceExamTest):
problem_page.click_submit() problem_page.click_submit()
self.courseware_page.wait_for_page() self.courseware_page.wait_for_page()
self.assertTrue(self.courseware_page.has_passed_message()) self.assertTrue(self.courseware_page.has_passed_message())
self.assertEqual(self.courseware_page.chapter_count_in_navigation, 2)
course_home_page = CourseHomePage(self.browser, self.course_id)
course_home_page.visit()
self.assertEqual(course_home_page.outline.num_sections, 2)
# TODO: TNL-6546: Delete test using outline on courseware
def test_course_is_unblocked_as_soon_as_student_passes_entrance_exam_2(self):
"""
Scenario: Ensure that entrance exam status message is updated and courseware is unblocked as soon as
student passes entrance exam.
Given I have a course with entrance exam as pre-requisite
When I pass entrance exam
Then I can see complete TOC of course
And I can see message indicating my pass status
"""
self.courseware_page.visit()
problem_page = ProblemPage(self.browser)
self.assertEqual(problem_page.wait_for_page().problem_name,
'HEIGHT OF EIFFEL TOWER')
self.assertTrue(self.courseware_page.has_entrance_exam_message())
self.assertFalse(self.courseware_page.has_passed_message())
problem_page.click_choice('choice_1')
problem_page.click_submit()
self.courseware_page.wait_for_page()
self.assertTrue(self.courseware_page.has_passed_message())
self.assertEqual(self.courseware_page.num_sections, 2)
...@@ -14,6 +14,7 @@ from common.test.acceptance.pages.studio.import_export import ( ...@@ -14,6 +14,7 @@ from common.test.acceptance.pages.studio.import_export import (
ImportCoursePage) ImportCoursePage)
from common.test.acceptance.pages.studio.library import LibraryEditPage from common.test.acceptance.pages.studio.library import LibraryEditPage
from common.test.acceptance.pages.studio.overview import CourseOutlinePage from common.test.acceptance.pages.studio.overview import CourseOutlinePage
from common.test.acceptance.pages.lms.course_home import CourseHomePage
from common.test.acceptance.pages.lms.courseware import CoursewarePage from common.test.acceptance.pages.lms.courseware import CoursewarePage
from common.test.acceptance.pages.lms.staff_view import StaffCoursewarePage from common.test.acceptance.pages.lms.staff_view import StaffCoursewarePage
...@@ -282,9 +283,13 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest): ...@@ -282,9 +283,13 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
When I visit the import page When I visit the import page
And I upload a course that has an entrance exam section named 'Entrance Exam' And I upload a course that has an entrance exam section named 'Entrance Exam'
And I visit the course outline page again And I visit the course outline page again
The section named 'Entrance Exam' should now be available. The section named 'Entrance Exam' should now be available
And when I switch the view mode to student view and Visit CourseWare When I visit the LMS Course Home page
Then I see one section in the sidebar that is 'Entrance Exam' Then I should see a section named 'Section' or 'Entrance Exam'
When I switch the view mode to student view
Then I should only see a section named 'Entrance Exam'
When I visit the courseware page
Then a message regarding the 'Entrance Exam'
""" """
self.landing_page.visit() self.landing_page.visit()
# Should not exist yet. # Should not exist yet.
...@@ -300,10 +305,16 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest): ...@@ -300,10 +305,16 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
self.landing_page.section("Section") self.landing_page.section("Section")
self.landing_page.view_live() self.landing_page.view_live()
course_home = CourseHomePage(self.browser, self.course_id)
course_home.visit()
self.assertEqual(course_home.outline.num_sections, 2)
course_home.preview.set_staff_view_mode('Student')
self.assertEqual(course_home.outline.num_sections, 1)
courseware = CoursewarePage(self.browser, self.course_id) courseware = CoursewarePage(self.browser, self.course_id)
courseware.wait_for_page() courseware.visit()
StaffCoursewarePage(self.browser, self.course_id).set_staff_view_mode('Learner') StaffCoursewarePage(self.browser, self.course_id).set_staff_view_mode('Student')
self.assertEqual(courseware.num_sections, 1)
self.assertIn( self.assertIn(
"To access course materials, you must score", courseware.entrance_exam_message_selector.text[0] "To access course materials, you must score", courseware.entrance_exam_message_selector.text[0]
) )
......
...@@ -51,8 +51,12 @@ def get_blocks( ...@@ -51,8 +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
if requested_fields is not None and 'special_exam' in requested_fields:
can_view_special_exam = True
if user is not None: if user is not None:
transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS + [MilestonesTransformer(), HiddenContentTransformer()] transformers += COURSE_BLOCK_ACCESS_TRANSFORMERS
transformers += [MilestonesTransformer(can_view_special_exam), HiddenContentTransformer()]
transformers += [ transformers += [
BlocksAPITransformer( BlocksAPITransformer(
block_counts, block_counts,
......
...@@ -146,7 +146,7 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase): ...@@ -146,7 +146,7 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
self._get_blocks( self._get_blocks(
course, course,
expected_mongo_queries=0, expected_mongo_queries=0,
expected_sql_queries=5 if with_storage_backing else 4, expected_sql_queries=6 if with_storage_backing else 5,
) )
@ddt.data( @ddt.data(
...@@ -164,5 +164,5 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase): ...@@ -164,5 +164,5 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
self._get_blocks( self._get_blocks(
course, course,
expected_mongo_queries, expected_mongo_queries,
expected_sql_queries=13 if with_storage_backing else 5, expected_sql_queries=14 if with_storage_backing else 6,
) )
...@@ -6,6 +6,7 @@ from lms.djangoapps.course_blocks.transformers.visibility import VisibilityTrans ...@@ -6,6 +6,7 @@ from lms.djangoapps.course_blocks.transformers.visibility import VisibilityTrans
from .student_view import StudentViewTransformer from .student_view import StudentViewTransformer
from .block_counts import BlockCountsTransformer from .block_counts import BlockCountsTransformer
from .navigation import BlockNavigationTransformer from .navigation import BlockNavigationTransformer
from .milestones import MilestonesTransformer
class SupportedFieldType(object): class SupportedFieldType(object):
...@@ -44,6 +45,8 @@ SUPPORTED_FIELDS = [ ...@@ -44,6 +45,8 @@ 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),
# 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),
......
...@@ -2,16 +2,22 @@ ...@@ -2,16 +2,22 @@
Milestones Transformer Milestones Transformer
""" """
import logging
from django.conf import settings from django.conf import settings
from openedx.core.djangoapps.content.block_structure.transformer import ( from openedx.core.djangoapps.content.block_structure.transformer import (
BlockStructureTransformer, BlockStructureTransformer,
FilteringTransformerMixin, FilteringTransformerMixin,
) )
from edx_proctoring.exceptions import ProctoredExamNotFoundException
from edx_proctoring.api import get_attempt_status_summary
from student.models import EntranceExamConfiguration
from util import milestones_helpers from util import milestones_helpers
log = logging.getLogger(__name__)
class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer):
class MilestonesTransformer(BlockStructureTransformer):
""" """
Excludes all special exams (timed, proctored, practice proctored) from the student view. Excludes all special exams (timed, proctored, practice proctored) from the student view.
Excludes all blocks with unfulfilled milestones from the student view. Excludes all blocks with unfulfilled milestones from the student view.
...@@ -23,6 +29,9 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer ...@@ -23,6 +29,9 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer
def name(cls): def name(cls):
return "milestones" return "milestones"
def __init__(self, can_view_special_exams=True):
self.can_view_special_exams = can_view_special_exams
@classmethod @classmethod
def collect(cls, block_structure): def collect(cls, block_structure):
""" """
...@@ -35,22 +44,79 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer ...@@ -35,22 +44,79 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer
block_structure.request_xblock_fields('is_proctored_enabled') block_structure.request_xblock_fields('is_proctored_enabled')
block_structure.request_xblock_fields('is_practice_exam') block_structure.request_xblock_fields('is_practice_exam')
block_structure.request_xblock_fields('is_timed_exam') block_structure.request_xblock_fields('is_timed_exam')
block_structure.request_xblock_fields('entrance_exam_id')
def transform(self, usage_info, block_structure):
"""
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,
)
def transform_block_filters(self, usage_info, block_structure): root_key = block_structure.root_block_usage_key
if usage_info.has_staff_access: course_key = root_key.course_key
return [block_structure.create_universal_filter()] 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): def user_gated_from_block(block_key):
""" """
Checks whether the user is gated from accessing this block, first via special exams, Checks whether the user is gated from accessing this block, first via special exams,
then via a general milestones check. then via a general milestones check.
""" """
return ( if usage_info.has_staff_access:
settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and return False
self.is_special_exam(block_key, block_structure) elif self.has_pending_milestones_for_user(block_key, usage_info):
) or 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 (settings.FEATURES.get('ENABLE_SPECIAL_EXAMS', False) and
(self.is_special_exam(block_key, block_structure) and
not self.can_view_special_exams)):
return True
return False
return [block_structure.create_removal_filter(user_gated_from_block)] for block_key in block_structure.topological_traversal():
if user_gated_from_block(block_key):
block_structure.remove_block(block_key, False)
else:
add_special_exam_info(block_key)
@staticmethod @staticmethod
def is_special_exam(block_key, block_structure): def is_special_exam(block_key, block_structure):
......
...@@ -9,6 +9,7 @@ from gating import api as lms_gating_api ...@@ -9,6 +9,7 @@ from gating import api as lms_gating_api
from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase from lms.djangoapps.course_blocks.transformers.tests.helpers import CourseStructureTestCase
from milestones.tests.utils import MilestonesTestCaseMixin from milestones.tests.utils import MilestonesTestCaseMixin
from openedx.core.lib.gating import api as gating_api from openedx.core.lib.gating import api as gating_api
from openedx.core.djangoapps.content.block_structure.transformers import BlockStructureTransformers
from student.tests.factories import CourseEnrollmentFactory from student.tests.factories import CourseEnrollmentFactory
from ..milestones import MilestonesTransformer from ..milestones import MilestonesTransformer
...@@ -38,6 +39,8 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM ...@@ -38,6 +39,8 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
# Enroll user in course. # Enroll user in course.
CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, is_active=True) CourseEnrollmentFactory.create(user=self.user, course_id=self.course.id, is_active=True)
self.transformers = BlockStructureTransformers([self.TRANSFORMER_CLASS_TO_TEST(False)])
def setup_gated_section(self, gated_block, gating_block): def setup_gated_section(self, gated_block, gating_block):
""" """
Test helper to create a gating requirement. Test helper to create a gating requirement.
...@@ -157,7 +160,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM ...@@ -157,7 +160,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
self.course.enable_subsection_gating = True self.course.enable_subsection_gating = True
self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref]) self.setup_gated_section(self.blocks[gated_block_ref], self.blocks[gating_block_ref])
with self.assertNumQueries(6): with self.assertNumQueries(8):
self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion) self.get_blocks_and_check_against_expected(self.user, expected_blocks_before_completion)
# clear the request cache to simulate a new request # clear the request cache to simulate a new request
...@@ -171,7 +174,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM ...@@ -171,7 +174,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
self.user, self.user,
) )
with self.assertNumQueries(6): with self.assertNumQueries(8):
self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL) self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS_EXCEPT_SPECIAL)
def test_staff_access(self): def test_staff_access(self):
...@@ -183,6 +186,30 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM ...@@ -183,6 +186,30 @@ 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):
"""
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.
"""
self.transformers = BlockStructureTransformers([self.TRANSFORMER_CLASS_TO_TEST(True)])
self.course.enable_subsection_gating = True
self.setup_gated_section(self.blocks['H'], self.blocks['A'])
expected_blocks = (
'course', 'A', 'B', 'C', 'ProctoredExam', 'D', 'E', 'PracticeExam', 'F', 'G', 'TimedExam', 'J', 'K'
)
self.get_blocks_and_check_against_expected(self.user, expected_blocks)
# clear the request cache to simulate a new request
self.clear_caches()
# this call triggers reevaluation of prerequisites fulfilled by the gating block.
with patch('gating.api._get_subsection_percentage', Mock(return_value=100)):
lms_gating_api.evaluate_prerequisite(
self.course,
Mock(location=self.blocks['A'].location),
self.user,
)
self.get_blocks_and_check_against_expected(self.user, self.ALL_BLOCKS)
def get_blocks_and_check_against_expected(self, user, expected_blocks): def get_blocks_and_check_against_expected(self, user, expected_blocks):
""" """
Calls the course API as the specified user and checks the Calls the course API as the specified user and checks the
......
...@@ -55,7 +55,7 @@ def get_entrance_exam_content(user, course): ...@@ -55,7 +55,7 @@ def get_entrance_exam_content(user, course):
""" """
Get the entrance exam content information (ie, chapter module) Get the entrance exam content information (ie, chapter module)
""" """
required_content = get_required_content(course, user) required_content = get_required_content(course.id, user)
exam_module = None exam_module = None
for content in required_content: for content in required_content:
......
...@@ -162,7 +162,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_ ...@@ -162,7 +162,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
# Check for content which needs to be completed # Check for content which needs to be completed
# before the rest of the content is made available # before the rest of the content is made available
required_content = milestones_helpers.get_required_content(course, user) required_content = milestones_helpers.get_required_content(course.id, user)
# The user may not actually have to complete the entrance exam, if one is required # The user may not actually have to complete the entrance exam, if one is required
if user_can_skip_entrance_exam(user, course): if user_can_skip_entrance_exam(user, course):
......
...@@ -27,6 +27,7 @@ from django.utils.translation import ugettext as _ ...@@ -27,6 +27,7 @@ from django.utils.translation import ugettext as _
</div> </div>
<ol class="outline-item focusable" role="group" tabindex="0"> <ol class="outline-item focusable" role="group" tabindex="0">
% for subsection in section.get('children') or []: % for subsection in section.get('children') or []:
${ subsection.get('special_exam', '') }
<li <li
class="subsection ${ 'current' if subsection['current'] else '' }" class="subsection ${ 'current' if subsection['current'] else '' }"
role="treeitem" role="treeitem"
......
...@@ -47,7 +47,7 @@ class CourseOutlineFragmentView(EdxFragmentView): ...@@ -47,7 +47,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'], requested_fields=['children', 'display_name', 'type', 'due', 'graded', 'special_exam'],
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