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
c4f0a89d
Commit
c4f0a89d
authored
Apr 13, 2017
by
Diana Huang
Committed by
GitHub
Apr 13, 2017
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14828 from edx/diana/new-transformer
Add existing sidebar information to the course outline page
parents
c58b3a14
196d379c
Hide whitespace changes
Inline
Side-by-side
Showing
20 changed files
with
501 additions
and
122 deletions
+501
-122
common/djangoapps/util/milestones_helpers.py
+2
-2
common/test/acceptance/pages/lms/course_home.py
+1
-1
common/test/acceptance/pages/lms/courseware.py
+1
-0
common/test/acceptance/tests/lms/test_lms.py
+44
-69
common/test/acceptance/tests/lms/test_lms_course_home.py
+139
-0
common/test/acceptance/tests/lms/test_lms_courseware.py
+1
-1
common/test/acceptance/tests/lms/test_lms_entrance_exams.py
+29
-1
common/test/acceptance/tests/studio/test_import_export.py
+16
-5
lms/djangoapps/course_api/blocks/api.py
+6
-2
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
+105
-13
lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
+31
-4
lms/djangoapps/courseware/entrance_exams.py
+1
-1
lms/djangoapps/courseware/module_render.py
+1
-1
lms/static/sass/features/_course-outline.scss
+17
-6
openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
+79
-8
openedx/features/course_experience/tests/views/test_course_outline.py
+20
-4
openedx/features/course_experience/views/course_outline.py
+2
-1
setup.py
+1
-1
No files found.
common/djangoapps/util/milestones_helpers.py
View file @
c4f0a89d
...
...
@@ -207,7 +207,7 @@ def remove_course_milestones(course_key, user, relationship):
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,
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):
# Get all of the outstanding milestones for this course, for this user
try
:
milestone_paths
=
get_course_milestones_fulfillment_paths
(
unicode
(
course
.
id
),
unicode
(
course
_key
),
serialize_user
(
user
)
)
except
InvalidMilestoneRelationshipTypeException
:
...
...
common/test/acceptance/pages/lms/course_home.py
View file @
c4f0a89d
...
...
@@ -56,7 +56,7 @@ class CourseOutlinePage(PageObject):
SECTION_SELECTOR
=
'.outline-item.section:nth-of-type({0})'
SECTION_TITLES_SELECTOR
=
'.section-name span'
SUBSECTION_SELECTOR
=
SECTION_SELECTOR
+
' .subsection:nth-of-type({1}) .outline-item'
SUBSECTION_TITLES_SELECTOR
=
SECTION_SELECTOR
+
' .subsection
a span:first-child
'
SUBSECTION_TITLES_SELECTOR
=
SECTION_SELECTOR
+
' .subsection
.subsection-title
'
OUTLINE_RESUME_COURSE_SELECTOR
=
'.outline-item .resume-right'
def
__init__
(
self
,
browser
,
parent_page
):
...
...
common/test/acceptance/pages/lms/courseware.py
View file @
c4f0a89d
...
...
@@ -30,6 +30,7 @@ class CoursewarePage(CoursePage):
def
is_browser_on_page
(
self
):
return
self
.
q
(
css
=
'.course-content'
)
.
present
# TODO: TNL-6546: Remove and find callers
@property
def
chapter_count_in_navigation
(
self
):
"""
...
...
common/test/acceptance/tests/lms/test_lms.py
View file @
c4f0a89d
...
...
@@ -25,7 +25,6 @@ from common.test.acceptance.pages.common.logout import LogoutPage
from
common.test.acceptance.pages.lms
import
BASE_URL
from
common.test.acceptance.pages.lms.account_settings
import
AccountSettingsPage
from
common.test.acceptance.pages.lms.auto_auth
import
AutoAuthPage
from
common.test.acceptance.pages.lms.bookmarks
import
BookmarksPage
from
common.test.acceptance.pages.lms.create_mode
import
ModeCreationPage
from
common.test.acceptance.pages.lms.course_home
import
CourseHomePage
from
common.test.acceptance.pages.lms.course_info
import
CourseInfoPage
...
...
@@ -635,6 +634,7 @@ class CourseWikiTest(UniqueCourseTest):
children_page
.
a11y_audit
.
check_for_accessibility_errors
()
@attr
(
shard
=
1
)
class
HighLevelTabTest
(
UniqueCourseTest
):
"""
Tests that verify each of the high-level tabs available within a course.
...
...
@@ -688,7 +688,6 @@ class HighLevelTabTest(UniqueCourseTest):
# Auto-auth register for the course
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
)
.
visit
()
@attr
(
shard
=
1
)
def
test_course_info
(
self
):
"""
Navigate to the course info page.
...
...
@@ -706,7 +705,6 @@ class HighLevelTabTest(UniqueCourseTest):
self
.
assertEqual
(
len
(
handout_links
),
1
)
self
.
assertIn
(
'demoPDF.pdf'
,
handout_links
[
0
])
@attr
(
shard
=
1
)
def
test_progress
(
self
):
"""
Navigate to the progress page.
...
...
@@ -724,7 +722,6 @@ class HighLevelTabTest(UniqueCourseTest):
actual_scores
=
self
.
progress_page
.
scores
(
CHAPTER
,
SECTION
)
self
.
assertEqual
(
actual_scores
,
EXPECTED_SCORES
)
@attr
(
shard
=
1
)
def
test_static_tab
(
self
):
"""
Navigate to a static tab (course content)
...
...
@@ -734,7 +731,6 @@ class HighLevelTabTest(UniqueCourseTest):
self
.
tab_nav
.
go_to_tab
(
'Test Static Tab'
)
self
.
assertTrue
(
self
.
tab_nav
.
is_on_tab
(
'Test Static Tab'
))
@attr
(
shard
=
1
)
def
test_static_tab_with_mathjax
(
self
):
"""
Navigate to a static tab (course content)
...
...
@@ -747,7 +743,6 @@ class HighLevelTabTest(UniqueCourseTest):
# Verify that Mathjax has rendered
self
.
tab_nav
.
mathjax_has_rendered
()
@attr
(
shard
=
1
)
def
test_wiki_tab_first_time
(
self
):
"""
Navigate to the course wiki tab. When the wiki is accessed for
...
...
@@ -769,7 +764,6 @@ class HighLevelTabTest(UniqueCourseTest):
self
.
assertEqual
(
expected_article_name
,
course_wiki
.
article_name
)
# TODO: TNL-6546: This whole function will be able to go away, replaced by test_course_home below.
@attr
(
shard
=
1
)
def
test_courseware_nav
(
self
):
"""
Navigate to a particular unit in the course.
...
...
@@ -805,13 +799,9 @@ class HighLevelTabTest(UniqueCourseTest):
self
.
courseware_page
.
nav
.
go_to_section
(
'Test Section 2'
,
'Test Subsection 3'
)
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
@attr
(
shard
=
1
)
def
test_course_home
(
self
):
def
test_course_home_tab
(
self
):
"""
Navigate to the course home page using the tab.
Includes smoke test of course outline, courseware page, and breadcrumbs.
"""
# TODO: TNL-6546: Use tab navigation and remove course_home_page.visit().
#self.course_info_page.visit()
...
...
@@ -825,56 +815,6 @@ class HighLevelTabTest(UniqueCourseTest):
# Check that the tab lands on the course home page.
self
.
assertTrue
(
self
.
course_home_page
.
is_browser_on_page
())
# Check that the course navigation appears correctly
EXPECTED_SECTIONS
=
{
'Test Section'
:
[
'Test Subsection'
],
'Test Section 2'
:
[
'Test Subsection 2'
,
'Test Subsection 3'
]
}
actual_sections
=
self
.
course_home_page
.
outline
.
sections
for
section
,
subsections
in
EXPECTED_SECTIONS
.
iteritems
():
self
.
assertIn
(
section
,
actual_sections
)
self
.
assertEqual
(
actual_sections
[
section
],
EXPECTED_SECTIONS
[
section
])
# Navigate to a particular section
self
.
course_home_page
.
outline
.
go_to_section
(
'Test Section'
,
'Test Subsection'
)
# Check the sequence items on the courseware page
EXPECTED_ITEMS
=
[
'Test Problem 1'
,
'Test Problem 2'
,
'Test HTML'
]
actual_items
=
self
.
courseware_page
.
nav
.
sequence_items
self
.
assertEqual
(
len
(
actual_items
),
len
(
EXPECTED_ITEMS
))
for
expected
in
EXPECTED_ITEMS
:
self
.
assertIn
(
expected
,
actual_items
)
# Use outline breadcrumb to get back to course home page.
self
.
courseware_page
.
nav
.
go_to_outline
()
# Navigate to a particular section other than the default landing section.
self
.
course_home_page
.
outline
.
go_to_section
(
'Test Section 2'
,
'Test Subsection 3'
)
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
# Verify that we can navigate to the bookmarks page
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
click_bookmarks_button
()
bookmarks_page
=
BookmarksPage
(
self
.
browser
,
self
.
course_id
)
self
.
assertTrue
(
bookmarks_page
.
is_browser_on_page
())
# Test "Resume Course" button from header
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
resume_course_from_header
()
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
# Test "Resume Course" button from within outline
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
outline
.
resume_course_from_outline
()
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
@attr
(
'a11y'
)
def
test_course_home_a11y
(
self
):
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
a11y_audit
.
check_for_accessibility_errors
()
@attr
(
shard
=
1
)
class
PDFTextBooksTabTest
(
UniqueCourseTest
):
...
...
@@ -1233,7 +1173,7 @@ class EntranceExamTest(UniqueCourseTest):
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
.
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
.
browser
,
self
.
course_info
[
'org'
],
...
...
@@ -1246,18 +1186,53 @@ class EntranceExamTest(UniqueCourseTest):
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
page.
Given that I am on the course page
When I view the course that has an entrance exam
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'
# visit course page and make sure there is not entrance exam chapter.
self
.
courseware_page
.
visit
()
self
.
courseware_page
.
wait_for_page
()
courseware_page
.
visit
()
courseware_page
.
wait_for_page
()
self
.
assertFalse
(
element_has_text
(
page
=
self
.
courseware_page
,
page
=
courseware_page
,
css_selector
=
entrance_exam_link_selector
,
text
=
'Entrance Exam'
))
...
...
@@ -1276,10 +1251,10 @@ class EntranceExamTest(UniqueCourseTest):
AutoAuthPage
(
self
.
browser
,
course_id
=
self
.
course_id
,
staff
=
False
)
.
visit
()
# visit course info page and make sure there is an "Entrance Exam" section.
self
.
courseware_page
.
visit
()
self
.
courseware_page
.
wait_for_page
()
courseware_page
.
visit
()
courseware_page
.
wait_for_page
()
self
.
assertTrue
(
element_has_text
(
page
=
self
.
courseware_page
,
page
=
courseware_page
,
css_selector
=
entrance_exam_link_selector
,
text
=
'Entrance Exam'
))
...
...
common/test/acceptance/tests/lms/test_lms_course_home.py
0 → 100644
View file @
c4f0a89d
# -*- coding: utf-8 -*-
"""
End-to-end tests for the LMS that utilize the course home page and course outline.
"""
from
contextlib
import
contextmanager
from
nose.plugins.attrib
import
attr
from
..helpers
import
auto_auth
,
load_data_str
,
UniqueCourseTest
from
...fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
from
...pages.lms.bookmarks
import
BookmarksPage
from
...pages.lms.course_home
import
CourseHomePage
from
...pages.lms.courseware
import
CoursewarePage
class
CourseHomeBaseTest
(
UniqueCourseTest
):
"""
Provides base setup for course home tests.
"""
USERNAME
=
"STUDENT_TESTER"
EMAIL
=
"student101@example.com"
def
setUp
(
self
):
"""
Initialize pages and install a course fixture.
"""
super
(
CourseHomeBaseTest
,
self
)
.
setUp
()
self
.
course_home_page
=
CourseHomePage
(
self
.
browser
,
self
.
course_id
)
self
.
courseware_page
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
# Install a course with sections and problems
course_fix
=
CourseFixture
(
self
.
course_info
[
'org'
],
self
.
course_info
[
'number'
],
self
.
course_info
[
'run'
],
self
.
course_info
[
'display_name'
]
)
course_fix
.
add_children
(
XBlockFixtureDesc
(
'static_tab'
,
'Test Static Tab'
,
data
=
r"static tab data with mathjax \(E=mc^2\)"
),
XBlockFixtureDesc
(
'chapter'
,
'Test Section'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Problem 1'
,
data
=
load_data_str
(
'multiple_choice.xml'
)),
XBlockFixtureDesc
(
'problem'
,
'Test Problem 2'
,
data
=
load_data_str
(
'formula_problem.xml'
)),
XBlockFixtureDesc
(
'html'
,
'Test HTML'
),
)
),
XBlockFixtureDesc
(
'chapter'
,
'Test Section 2'
)
.
add_children
(
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 2'
),
XBlockFixtureDesc
(
'sequential'
,
'Test Subsection 3'
)
.
add_children
(
XBlockFixtureDesc
(
'problem'
,
'Test Problem A'
,
data
=
load_data_str
(
'multiple_choice.xml'
))
),
)
)
.
install
()
# Auto-auth register for the course.
auto_auth
(
self
.
browser
,
self
.
USERNAME
,
self
.
EMAIL
,
False
,
self
.
course_id
)
class
CourseHomeTest
(
CourseHomeBaseTest
):
"""
Tests the course home page with course outline.
"""
def
test_course_home
(
self
):
"""
Smoke test of course outline, breadcrumbs to and from cours outline, and bookmarks.
"""
self
.
course_home_page
.
visit
()
# TODO: TNL-6546: Remove unified_course_view.
self
.
course_home_page
.
unified_course_view
=
True
self
.
courseware_page
.
nav
.
unified_course_view
=
True
# Check that the tab lands on the course home page.
self
.
assertTrue
(
self
.
course_home_page
.
is_browser_on_page
())
# Check that the course navigation appears correctly
EXPECTED_SECTIONS
=
{
'Test Section'
:
[
'Test Subsection'
],
'Test Section 2'
:
[
'Test Subsection 2'
,
'Test Subsection 3'
]
}
actual_sections
=
self
.
course_home_page
.
outline
.
sections
for
section
,
subsections
in
EXPECTED_SECTIONS
.
iteritems
():
self
.
assertIn
(
section
,
actual_sections
)
self
.
assertEqual
(
actual_sections
[
section
],
EXPECTED_SECTIONS
[
section
])
# Navigate to a particular section
self
.
course_home_page
.
outline
.
go_to_section
(
'Test Section'
,
'Test Subsection'
)
# Check the sequence items on the courseware page
EXPECTED_ITEMS
=
[
'Test Problem 1'
,
'Test Problem 2'
,
'Test HTML'
]
actual_items
=
self
.
courseware_page
.
nav
.
sequence_items
self
.
assertEqual
(
len
(
actual_items
),
len
(
EXPECTED_ITEMS
))
for
expected
in
EXPECTED_ITEMS
:
self
.
assertIn
(
expected
,
actual_items
)
# Use outline breadcrumb to get back to course home page.
self
.
courseware_page
.
nav
.
go_to_outline
()
# Navigate to a particular section other than the default landing section.
self
.
course_home_page
.
outline
.
go_to_section
(
'Test Section 2'
,
'Test Subsection 3'
)
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
# Verify that we can navigate to the bookmarks page
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
click_bookmarks_button
()
bookmarks_page
=
BookmarksPage
(
self
.
browser
,
self
.
course_id
)
self
.
assertTrue
(
bookmarks_page
.
is_browser_on_page
())
# Test "Resume Course" button from header
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
resume_course_from_header
()
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
# Test "Resume Course" button from within outline
self
.
course_home_page
.
visit
()
self
.
course_home_page
.
outline
.
resume_course_from_outline
()
self
.
assertTrue
(
self
.
courseware_page
.
nav
.
is_on_section
(
'Test Section 2'
,
'Test Subsection 3'
))
@attr
(
'a11y'
)
class
CourseHomeA11yTest
(
CourseHomeBaseTest
):
"""
Tests the accessibility of the course home page with course outline.
"""
def
setUp
(
self
):
super
(
CourseHomeA11yTest
,
self
)
.
setUp
()
def
test_course_home_a11y
(
self
):
"""
Test the accessibility of the course home page with course outline.
"""
course_home_page
=
CourseHomePage
(
self
.
browser
,
self
.
course_id
)
course_home_page
.
visit
()
course_home_page
.
a11y_audit
.
check_for_accessibility_errors
()
common/test/acceptance/tests/lms/test_lms_courseware.py
View file @
c4f0a89d
...
...
@@ -129,7 +129,7 @@ class CoursewareTest(UniqueCourseTest):
@ddt.ddt
class
ProctoredExamTest
(
UniqueCourseTest
):
"""
Test
courseware
.
Test
s for proctored exams
.
"""
USERNAME
=
"STUDENT_TESTER"
EMAIL
=
"student101@example.com"
...
...
common/test/acceptance/tests/lms/test_lms_entrance_exams.py
View file @
c4f0a89d
...
...
@@ -6,6 +6,7 @@ from textwrap import dedent
from
common.test.acceptance.tests.helpers
import
UniqueCourseTest
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.problem
import
ProblemPage
from
common.test.acceptance.fixtures.course
import
CourseFixture
,
XBlockFixtureDesc
...
...
@@ -92,6 +93,8 @@ class EntranceExamPassTest(EntranceExamTest):
When I pass entrance exam
Then I can see complete TOC of course
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
()
problem_page
=
ProblemPage
(
self
.
browser
)
...
...
@@ -102,4 +105,29 @@ class EntranceExamPassTest(EntranceExamTest):
problem_page
.
click_submit
()
self
.
courseware_page
.
wait_for_page
()
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 @
c4f0a89d
...
...
@@ -14,6 +14,7 @@ from common.test.acceptance.pages.studio.import_export import (
ImportCoursePage
)
from
common.test.acceptance.pages.studio.library
import
LibraryEditPage
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.staff_view
import
StaffCoursewarePage
...
...
@@ -282,9 +283,13 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
When I visit the import page
And I upload a course that has an entrance exam section named 'Entrance Exam'
And I visit the course outline page again
The section named 'Entrance Exam' should now be available.
And when I switch the view mode to student view and Visit CourseWare
Then I see one section in the sidebar that is 'Entrance Exam'
The section named 'Entrance Exam' should now be available
When I visit the LMS Course Home page
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
()
# Should not exist yet.
...
...
@@ -300,10 +305,16 @@ class TestEntranceExamCourseImport(ImportTestMixin, StudioCourseTest):
self
.
landing_page
.
section
(
"Section"
)
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
(
'Learner'
)
self
.
assertEqual
(
course_home
.
outline
.
num_sections
,
1
)
courseware
=
CoursewarePage
(
self
.
browser
,
self
.
course_id
)
courseware
.
wait_for_page
()
courseware
.
visit
()
StaffCoursewarePage
(
self
.
browser
,
self
.
course_id
)
.
set_staff_view_mode
(
'Learner'
)
self
.
assertEqual
(
courseware
.
num_sections
,
1
)
self
.
assertIn
(
"To access course materials, you must score"
,
courseware
.
entrance_exam_message_selector
.
text
[
0
]
)
...
...
lms/djangoapps/course_api/blocks/api.py
View file @
c4f0a89d
...
...
@@ -7,7 +7,7 @@ from lms.djangoapps.course_blocks.transformers.hidden_content import HiddenConte
from
openedx.core.djangoapps.content.block_structure.transformers
import
BlockStructureTransformers
from
.transformers.blocks_api
import
BlocksAPITransformer
from
.transformers.milestones
import
MilestonesTransformer
from
.transformers.milestones
import
Milestones
AndSpecialExams
Transformer
from
.serializers
import
BlockSerializer
,
BlockDictSerializer
...
...
@@ -51,8 +51,12 @@ def get_blocks(
"""
# create ordered list of transformers, adding BlocksAPITransformer at end.
transformers
=
BlockStructureTransformers
()
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
+
[
MilestonesTransformer
(),
HiddenContentTransformer
()]
transformers
+=
COURSE_BLOCK_ACCESS_TRANSFORMERS
transformers
+=
[
MilestonesAndSpecialExamsTransformer
(
include_special_exams
),
HiddenContentTransformer
()]
transformers
+=
[
BlocksAPITransformer
(
block_counts
,
...
...
lms/djangoapps/course_api/blocks/tests/test_api.py
View file @
c4f0a89d
...
...
@@ -146,7 +146,7 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
self
.
_get_blocks
(
course
,
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
(
...
...
@@ -164,5 +164,5 @@ class TestGetBlocksQueryCounts(SharedModuleStoreTestCase):
self
.
_get_blocks
(
course
,
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 @
c4f0a89d
...
...
@@ -6,6 +6,7 @@ from lms.djangoapps.course_blocks.transformers.visibility import VisibilityTrans
from
.student_view
import
StudentViewTransformer
from
.block_counts
import
BlockCountsTransformer
from
.navigation
import
BlockNavigationTransformer
from
.milestones
import
MilestonesAndSpecialExamsTransformer
class
SupportedFieldType
(
object
):
...
...
@@ -44,6 +45,8 @@ SUPPORTED_FIELDS = [
# 'student_view_multi_device'
SupportedFieldType
(
StudentViewTransformer
.
STUDENT_VIEW_MULTI_DEVICE
,
StudentViewTransformer
),
SupportedFieldType
(
'special_exam_info'
,
MilestonesAndSpecialExamsTransformer
),
# set the block_field_name to None so the entire data for the transformer is serialized
SupportedFieldType
(
None
,
BlockCountsTransformer
,
BlockCountsTransformer
.
BLOCK_COUNTS
),
...
...
lms/djangoapps/course_api/blocks/transformers/milestones.py
View file @
c4f0a89d
...
...
@@ -2,19 +2,31 @@
Milestones Transformer
"""
import
logging
from
django.conf
import
settings
from
openedx.core.djangoapps.content.block_structure.transformer
import
(
BlockStructureTransformer
,
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
log
=
logging
.
getLogger
(
__name__
)
class
MilestonesTransformer
(
FilteringTransformerMixin
,
BlockStructureTransformer
):
class
MilestonesAndSpecialExamsTransformer
(
BlockStructureTransformer
):
"""
Excludes all special exams (timed, proctored, practice proctored) from the student view.
Excludes all blocks with unfulfilled milestones from the student view.
A transformer that handles both milestones and special (timed) exams.
It excludes all blocks with unfulfilled milestones from the student view. An entrance exam is considered a
milestone, and is not considered a "special exam".
It also includes or excludes all special (timed) exams (timed, proctored, practice proctored) in/from the
student view, based on the value of `include_special_exams`.
"""
WRITE_VERSION
=
1
READ_VERSION
=
1
...
...
@@ -23,6 +35,9 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer
def
name
(
cls
):
return
"milestones"
def
__init__
(
self
,
include_special_exams
=
True
):
self
.
include_special_exams
=
include_special_exams
@classmethod
def
collect
(
cls
,
block_structure
):
"""
...
...
@@ -35,28 +50,42 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer
block_structure
.
request_xblock_fields
(
'is_proctored_enabled'
)
block_structure
.
request_xblock_fields
(
'is_practice_exam'
)
block_structure
.
request_xblock_fields
(
'is_timed_exam'
)
block_structure
.
request_xblock_fields
(
'entrance_exam_id'
)
def
transform_block_filters
(
self
,
usage_info
,
block_structure
):
if
usage_info
.
has_staff_access
:
return
[
block_structure
.
create_universal_filter
()]
def
transform
(
self
,
usage_info
,
block_structure
):
"""
Modify block structure according to the behavior of milestones and special exams.
"""
required_content
=
self
.
get_required_content
(
usage_info
,
block_structure
)
def
user_gated_from_block
(
block_key
):
"""
Checks whether the user is gated from accessing this block, first via special exams,
then via a general milestones check.
"""
return
(
settings
.
FEATURES
.
get
(
'ENABLE_SPECIAL_EXAMS'
,
False
)
and
self
.
is_special_exam
(
block_key
,
block_structure
)
)
or
self
.
has_pending_milestones_for_user
(
block_key
,
usage_info
)
return
[
block_structure
.
create_removal_filter
(
user_gated_from_block
)]
if
usage_info
.
has_staff_access
:
return
False
elif
self
.
has_pending_milestones_for_user
(
block_key
,
usage_info
):
return
True
elif
self
.
gated_by_required_content
(
block_key
,
block_structure
,
required_content
):
return
True
elif
(
settings
.
FEATURES
.
get
(
'ENABLE_SPECIAL_EXAMS'
,
False
)
and
(
self
.
is_special_exam
(
block_key
,
block_structure
)
and
not
self
.
include_special_exams
)):
return
True
return
False
for
block_key
in
block_structure
.
topological_traversal
():
if
user_gated_from_block
(
block_key
):
block_structure
.
remove_block
(
block_key
,
False
)
elif
self
.
is_special_exam
(
block_key
,
block_structure
):
self
.
add_special_exam_info
(
block_key
,
block_structure
,
usage_info
)
@staticmethod
def
is_special_exam
(
block_key
,
block_structure
):
"""
Test whether the block is a special exam. These exams are always excluded
from the student view.
Test whether the block is a special exam.
"""
return
(
block_structure
.
get_xblock_field
(
block_key
,
'is_proctored_enabled'
)
or
...
...
@@ -76,3 +105,66 @@ class MilestonesTransformer(FilteringTransformerMixin, BlockStructureTransformer
'requires'
,
usage_info
.
user
.
id
))
# TODO: As part of a cleanup effort, this transformer should be split into
# MilestonesTransformer and SpecialExamsTransformer, which are completely independent.
def
add_special_exam_info
(
self
,
block_key
,
block_structure
,
usage_info
):
"""
For special exams, add the special exam information to the course blocks.
"""
special_exam_attempt_context
=
None
try
:
# Calls 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
=
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
:
# This user has special exam context for this block so add it.
block_structure
.
set_transformer_block_field
(
block_key
,
self
,
'special_exam_info'
,
special_exam_attempt_context
,
)
@staticmethod
def
get_required_content
(
usage_info
,
block_structure
):
"""
Get the required content for the course.
This takes into account if the user can skip the entrance exam.
"""
course_key
=
block_structure
.
root_block_usage_key
.
course_key
user_can_skip_entrance_exam
=
EntranceExamConfiguration
.
user_can_skip_entrance_exam
(
usage_info
.
user
,
course_key
)
required_content
=
milestones_helpers
.
get_required_content
(
course_key
,
usage_info
.
user
)
if
not
required_content
:
return
required_content
if
user_can_skip_entrance_exam
:
# remove the entrance exam from required content
entrance_exam_id
=
block_structure
.
get_xblock_field
(
block_structure
.
root_block_usage_key
,
'entrance_exam_id'
)
required_content
=
[
content
for
content
in
required_content
if
not
content
==
entrance_exam_id
]
return
required_content
@staticmethod
def
gated_by_required_content
(
block_key
,
block_structure
,
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
if
block_key
.
block_type
==
'chapter'
and
unicode
(
block_key
)
not
in
required_content
:
return
True
return
False
lms/djangoapps/course_api/blocks/transformers/tests/test_milestones.py
View file @
c4f0a89d
...
...
@@ -9,9 +9,10 @@ from gating import api as lms_gating_api
from
lms.djangoapps.course_blocks.transformers.tests.helpers
import
CourseStructureTestCase
from
milestones.tests.utils
import
MilestonesTestCaseMixin
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
..milestones
import
MilestonesTransformer
from
..milestones
import
Milestones
AndSpecialExams
Transformer
from
...api
import
get_course_blocks
...
...
@@ -22,7 +23,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
"""
Test behavior of ProctoredExamTransformer
"""
TRANSFORMER_CLASS_TO_TEST
=
MilestonesTransformer
TRANSFORMER_CLASS_TO_TEST
=
Milestones
AndSpecialExams
Transformer
def
setUp
(
self
):
"""
...
...
@@ -38,6 +39,8 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
# Enroll user in course.
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
):
"""
Test helper to create a gating requirement.
...
...
@@ -157,7 +160,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
self
.
course
.
enable_subsection_gating
=
True
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
)
# clear the request cache to simulate a new request
...
...
@@ -171,7 +174,7 @@ class MilestonesTransformerTestCase(CourseStructureTestCase, MilestonesTestCaseM
self
.
user
,
)
with
self
.
assertNumQueries
(
6
):
with
self
.
assertNumQueries
(
8
):
self
.
get_blocks_and_check_against_expected
(
self
.
user
,
self
.
ALL_BLOCKS_EXCEPT_SPECIAL
)
def
test_staff_access
(
self
):
...
...
@@ -183,6 +186,30 @@ 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_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.
"""
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
):
"""
Calls the course API as the specified user and checks the
...
...
lms/djangoapps/courseware/entrance_exams.py
View file @
c4f0a89d
...
...
@@ -55,7 +55,7 @@ def get_entrance_exam_content(user, course):
"""
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
for
content
in
required_content
:
...
...
lms/djangoapps/courseware/module_render.py
View file @
c4f0a89d
...
...
@@ -162,7 +162,7 @@ def toc_for_course(user, request, course, active_chapter, active_section, field_
# Check for content which needs to be completed
# 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
if
user_can_skip_entrance_exam
(
user
,
course
):
...
...
lms/static/sass/features/_course-outline.scss
View file @
c4f0a89d
...
...
@@ -35,21 +35,32 @@
list-style-type
:
none
;
a
.outline-item
{
display
:
block
;
display
:
flex
;
justify-content
:
space-between
;
padding
:
(
$baseline
/
2
);
&
:hover
{
background-color
:
palette
(
primary
,
x-back
);
text-decoration
:
none
;
}
.subsection-text
{
.details
{
font-size
:
$body-font-size
;
color
:
$lms-gray
;
font-style
:
italic
;
}
}
.subsection-actions
{
.resume-right
{
position
:
relative
;
top
:
calc
(
50%
-
(
#{
$baseline
}
/
2
));
}
}
}
&
.current
{
border
:
1px
solid
$lms-active-color
;
.resume-right
{
@include
float
(
right
);
}
}
}
}
...
...
openedx/features/course_experience/templates/course_experience/course-outline-fragment.html
View file @
c4f0a89d
...
...
@@ -38,14 +38,81 @@ from django.utils.translation import ugettext as _
href=
"${ subsection['lms_web_url'] }"
id=
"${ subsection['id'] }"
>
<span>
${ subsection['display_name'] }
</span>
<span
class=
"sr-only"
>
${ _("This is your last visited course section.") }
</span>
% if subsection['current']:
<span
class=
"resume-right"
>
<b>
${ _("Resume Course") }
</b>
<span
class=
"icon fa fa-arrow-circle-right"
aria-hidden=
"true"
></span>
</span>
% endif
<div
class=
"subsection-text"
>
## Subsection title
<span
class=
"subsection-title"
>
${ subsection['display_name'] }
</span>
<div
class=
"details"
>
## There are behavior differences between rendering of subsections which have
## exams (timed, graded, etc) and those that do not.
##
## Exam subsections expose exam status message field as well as a status icon
<
%
if
subsection
.
get
('
due
')
is
None:
#
examples:
Homework
,
Lab
,
etc
.
data_string =
subsection.get('format')
else:
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_info' in subsection:
<span
class=
"subtitle"
>
% if 'special_exam' in subsection:
## Display the exam status icon and status message
<span
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_info'].get('short_description', '')}
</span>
## completed exam statuses should not show the due date
## since the exam has already been submitted by the user
% if not subsection['special_exam_info'].get('in_completed_state', False):
<span
class=
"localized-datetime subtitle-name"
data-datetime=
"${subsection.get('due')}"
data-string=
"${data_string}"
data-timezone=
"${user_timezone}"
data-language=
"${user_language}"
></span>
% endif
% else:
## non-graded section, we just show the exam format and the due date
## this is the standard case in edx-platform
<span
class=
"localized-datetime subtitle-name"
data-datetime=
"${subsection.get('due')}"
data-string=
"${data_string}"
data-timezone=
"${user_timezone}"
data-language=
"${user_language}"
></span>
% if 'graded' in subsection and subsection['graded']:
<span
class=
"menu-icon icon fa fa-pencil-square-o"
aria-hidden=
"true"
></span>
<span
class=
"sr"
>
${_("This content is graded")}
</span>
% endif
% endif
</span>
% endif
</div>
<!-- /details -->
</div>
<!-- /subsection-text -->
<div
class=
"subsection-actions"
>
## Resume button (if last visited section)
% if subsection['current']:
<span
class=
"sr-only"
>
${ _("This is your last visited course section.") }
</span>
<span
class=
"resume-right"
>
<b>
${ _("Resume Course") }
</b>
<span
class=
"icon fa fa-arrow-circle-right"
aria-hidden=
"true"
></span>
</span>
%endif
</div>
</a>
</li>
% endfor
...
...
@@ -54,3 +121,7 @@ from django.utils.translation import ugettext as _
% endfor
</ol>
</div>
<
%
static:require_module_async
module_name=
"js/dateutil_factory"
class_name=
"DateUtilFactory"
>
DateUtilFactory.transform('.localized-datetime');
</
%
static:require
_module_async
>
openedx/features/course_experience/tests/views/test_course_outline.py
View file @
c4f0a89d
...
...
@@ -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
)
...
...
openedx/features/course_experience/views/course_outline.py
View file @
c4f0a89d
...
...
@@ -29,6 +29,7 @@ class CourseOutlineFragmentView(EdxFragmentView):
for
i
in
range
(
len
(
children
)):
child_id
=
block
[
'children'
][
i
]
child_detail
=
self
.
populate_children
(
all_blocks
[
child_id
],
all_blocks
,
course_position
)
block
[
'children'
][
i
]
=
child_detail
block
[
'children'
][
i
][
'current'
]
=
course_position
==
child_detail
[
'block_id'
]
...
...
@@ -47,7 +48,7 @@ class CourseOutlineFragmentView(EdxFragmentView):
course_usage_key
,
user
=
request
.
user
,
nav_depth
=
3
,
requested_fields
=
[
'children'
,
'display_name'
,
'type'
],
requested_fields
=
[
'children'
,
'display_name'
,
'type'
,
'due'
,
'graded'
,
'special_exam_info'
,
'format'
],
block_types_filter
=
[
'course'
,
'chapter'
,
'sequential'
]
)
...
...
setup.py
View file @
c4f0a89d
...
...
@@ -52,7 +52,7 @@ setup(
"visibility = lms.djangoapps.course_blocks.transformers.visibility:VisibilityTransformer"
,
"hidden_content = lms.djangoapps.course_blocks.transformers.hidden_content:HiddenContentTransformer"
,
"course_blocks_api = lms.djangoapps.course_api.blocks.transformers.blocks_api:BlocksAPITransformer"
,
"milestones = lms.djangoapps.course_api.blocks.transformers.milestones:MilestonesTransformer"
,
"milestones = lms.djangoapps.course_api.blocks.transformers.milestones:Milestones
AndSpecialExams
Transformer"
,
"grades = lms.djangoapps.grades.transformer:GradesTransformer"
,
],
}
...
...
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