Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
9e734442
Commit
9e734442
authored
Mar 28, 2017
by
Diana Huang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Initial version of new transformer.
parent
c58b3a14
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
209 additions
and
33 deletions
+209
-33
common/djangoapps/util/milestones_helpers.py
+2
-2
common/test/acceptance/pages/lms/courseware.py
+1
-0
common/test/acceptance/tests/lms/test_lms.py
+42
-7
common/test/acceptance/tests/lms/test_lms_entrance_exams.py
+29
-1
common/test/acceptance/tests/studio/test_import_export.py
+17
-6
lms/djangoapps/course_api/blocks/api.py
+5
-1
lms/djangoapps/course_api/blocks/tests/test_api.py
+2
-2
lms/djangoapps/course_api/blocks/transformers/__init__.py
+3
-0
lms/djangoapps/course_api/blocks/transformers/milestones.py
+75
-9
lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
+29
-2
lms/djangoapps/courseware/entrance_exams.py
+1
-1
lms/djangoapps/courseware/module_render.py
+1
-1
openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
+1
-0
openedx/features/course_experience/views/course_outline.py
+1
-1
No files found.
common/djangoapps/util/milestones_helpers.py
View file @
9e734442
...
@@ -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
:
...
...
common/test/acceptance/pages/lms/courseware.py
View file @
9e734442
...
@@ -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
):
"""
"""
...
...
common/test/acceptance/tests/lms/test_lms.py
View file @
9e734442
...
@@ -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
.
course
ware_page
=
Coursewar
ePage
(
self
.
browser
,
self
.
course_id
)
self
.
course
_home_page
=
CourseHom
ePage
(
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'
))
))
...
...
common/test/acceptance/tests/lms/test_lms_entrance_exams.py
View file @
9e734442
...
@@ -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
)
common/test/acceptance/tests/studio/test_import_export.py
View file @
9e734442
...
@@ -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
]
)
)
...
...
lms/djangoapps/course_api/blocks/api.py
View file @
9e734442
...
@@ -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
,
...
...
lms/djangoapps/course_api/blocks/tests/test_api.py
View file @
9e734442
...
@@ -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
=
1
3
if
with_storage_backing
else
5
,
expected_sql_queries
=
1
4
if
with_storage_backing
else
6
,
)
)
lms/djangoapps/course_api/blocks/transformers/__init__.py
View file @
9e734442
...
@@ -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
),
...
...
lms/djangoapps/course_api/blocks/transformers/milestones.py
View file @
9e734442
...
@@ -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
):
...
...
lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
View file @
9e734442
...
@@ -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
...
...
lms/djangoapps/courseware/entrance_exams.py
View file @
9e734442
...
@@ -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
:
...
...
lms/djangoapps/courseware/module_render.py
View file @
9e734442
...
@@ -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
):
...
...
openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
View file @
9e734442
...
@@ -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"
...
...
openedx/features/course_experience/views/course_outline.py
View file @
9e734442
...
@@ -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'
]
)
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment