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
3f355241
Commit
3f355241
authored
Dec 22, 2016
by
Mushtaq Ali
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Get concise course outline data for move dialog box
Get ancestor info for the given xblock - TNL-6061
parent
84ad88b7
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
173 additions
and
89 deletions
+173
-89
cms/djangoapps/contentstore/views/course.py
+7
-2
cms/djangoapps/contentstore/views/item.py
+94
-77
cms/djangoapps/contentstore/views/tests/test_course_index.py
+17
-9
cms/djangoapps/contentstore/views/tests/test_item.py
+55
-1
No files found.
cms/djangoapps/contentstore/views/course.py
View file @
3f355241
...
...
@@ -336,11 +336,16 @@ def _course_outline_json(request, course_module):
"""
Returns a JSON representation of the course module and recursively all of its children.
"""
is_concise
=
request
.
GET
.
get
(
'formats'
)
==
'concise'
include_children_predicate
=
lambda
xblock
:
not
xblock
.
category
==
'vertical'
if
is_concise
:
include_children_predicate
=
lambda
xblock
:
xblock
.
has_children
return
create_xblock_info
(
course_module
,
include_child_info
=
True
,
course_outline
=
True
,
include_children_predicate
=
lambda
xblock
:
not
xblock
.
category
==
'vertical'
,
course_outline
=
False
if
is_concise
else
True
,
include_children_predicate
=
include_children_predicate
,
is_concise
=
is_concise
,
user
=
request
.
user
)
...
...
cms/djangoapps/contentstore/views/item.py
View file @
3f355241
...
...
@@ -98,6 +98,7 @@ def xblock_handler(request, usage_key_string):
GET
json: returns representation of the xblock (locator id, data, and metadata).
if ?fields=graderType, it returns the graderType for the unit instead of the above.
if ?fields=ancestorInfo, it returns ancestor info of the xblock.
html: returns HTML for rendering the xblock (which includes both the "preview" view and the "editor" view)
PUT or POST or PATCH
json: if xblock locator is specified, update the xblock instance. The json payload can contain
...
...
@@ -149,6 +150,10 @@ def xblock_handler(request, usage_key_string):
if
'graderType'
in
fields
:
# right now can't combine output of this w/ output of _get_module_info, but worthy goal
return
JsonResponse
(
CourseGradingModel
.
get_section_grader_type
(
usage_key
))
elif
'ancestorInfo'
in
fields
:
xblock
=
_get_xblock
(
usage_key
,
request
.
user
)
ancestor_info
=
_create_xblock_ancestor_info
(
xblock
,
is_concise
=
True
)
return
JsonResponse
(
ancestor_info
)
# TODO: pass fields to _get_module_info and only return those
with
modulestore
()
.
bulk_operations
(
usage_key
.
course_key
):
response
=
_get_module_info
(
_get_xblock
(
usage_key
,
request
.
user
))
...
...
@@ -887,7 +892,7 @@ def _get_gating_info(course, xblock):
def
create_xblock_info
(
xblock
,
data
=
None
,
metadata
=
None
,
include_ancestor_info
=
False
,
include_child_info
=
False
,
course_outline
=
False
,
include_children_predicate
=
NEVER
,
parent_xblock
=
None
,
graders
=
None
,
user
=
None
,
course
=
None
):
user
=
None
,
course
=
None
,
is_concise
=
False
):
"""
Creates the information needed for client-side XBlockInfo.
...
...
@@ -897,6 +902,7 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
There are three optional boolean parameters:
include_ancestor_info - if true, ancestor info is added to the response
include_child_info - if true, direct child info is included in the response
is_concise - if true, returns the concise version of xblock info, default is false.
course_outline - if true, the xblock is being rendered on behalf of the course outline.
There are certain expensive computations that do not need to be included in this case.
...
...
@@ -933,20 +939,22 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
graders
,
include_children_predicate
=
include_children_predicate
,
user
=
user
,
course
=
course
course
=
course
,
is_concise
=
is_concise
)
else
:
child_info
=
None
release_date
=
_get_release_date
(
xblock
,
user
)
if
xblock
.
category
!=
'course'
:
if
xblock
.
category
!=
'course'
and
not
is_concise
:
visibility_state
=
_compute_visibility_state
(
xblock
,
child_info
,
is_xblock_unit
and
has_changes
,
is_self_paced
(
course
)
)
else
:
visibility_state
=
None
published
=
modulestore
()
.
has_published_version
(
xblock
)
if
not
is_library_block
else
None
published_on
=
get_default_time_display
(
xblock
.
published_on
)
if
published
and
xblock
.
published_on
else
None
# defining the default value 'True' for delete, duplicate, drag and add new child actions
# in xblock_actions for each xblock.
...
...
@@ -970,83 +978,89 @@ def create_xblock_info(xblock, data=None, metadata=None, include_ancestor_info=F
pct_sign
=
_
(
'
%
'
))
xblock_info
=
{
"id"
:
unicode
(
xblock
.
location
),
"display_name"
:
xblock
.
display_name_with_default
,
"category"
:
xblock
.
category
,
"edited_on"
:
get_default_time_display
(
xblock
.
subtree_edited_on
)
if
xblock
.
subtree_edited_on
else
None
,
"published"
:
published
,
"published_on"
:
get_default_time_display
(
xblock
.
published_on
)
if
published
and
xblock
.
published_on
else
None
,
"studio_url"
:
xblock_studio_url
(
xblock
,
parent_xblock
),
"released_to_students"
:
datetime
.
now
(
UTC
)
>
xblock
.
start
,
"release_date"
:
release_date
,
"visibility_state"
:
visibility_state
,
"has_explicit_staff_lock"
:
xblock
.
fields
[
'visible_to_staff_only'
]
.
is_set_on
(
xblock
),
"self_paced"
:
is_self_paced
(
course
),
"start"
:
xblock
.
fields
[
'start'
]
.
to_json
(
xblock
.
start
),
"graded"
:
xblock
.
graded
,
"due_date"
:
get_default_time_display
(
xblock
.
due
),
"due"
:
xblock
.
fields
[
'due'
]
.
to_json
(
xblock
.
due
),
"format"
:
xblock
.
format
,
"course_graders"
:
[
grader
.
get
(
'type'
)
for
grader
in
graders
],
"has_changes"
:
has_changes
,
"actions"
:
xblock_actions
,
"explanatory_message"
:
explanatory_message
,
"group_access"
:
xblock
.
group_access
,
"user_partitions"
:
get_user_partition_info
(
xblock
,
course
=
course
),
'id'
:
unicode
(
xblock
.
location
),
'display_name'
:
xblock
.
display_name_with_default
,
'category'
:
xblock
.
category
}
if
xblock
.
category
==
'sequential'
:
if
is_concise
:
if
child_info
and
len
(
child_info
.
get
(
'children'
,
[]))
>
0
:
xblock_info
[
'child_info'
]
=
child_info
else
:
xblock_info
.
update
({
"hide_after_due"
:
xblock
.
hide_after_due
,
'edited_on'
:
get_default_time_display
(
xblock
.
subtree_edited_on
)
if
xblock
.
subtree_edited_on
else
None
,
'published'
:
published
,
'published_on'
:
published_on
,
'studio_url'
:
xblock_studio_url
(
xblock
,
parent_xblock
),
'released_to_students'
:
datetime
.
now
(
UTC
)
>
xblock
.
start
,
'release_date'
:
release_date
,
'visibility_state'
:
visibility_state
,
'has_explicit_staff_lock'
:
xblock
.
fields
[
'visible_to_staff_only'
]
.
is_set_on
(
xblock
),
'start'
:
xblock
.
fields
[
'start'
]
.
to_json
(
xblock
.
start
),
'graded'
:
xblock
.
graded
,
'due_date'
:
get_default_time_display
(
xblock
.
due
),
'due'
:
xblock
.
fields
[
'due'
]
.
to_json
(
xblock
.
due
),
'format'
:
xblock
.
format
,
'course_graders'
:
[
grader
.
get
(
'type'
)
for
grader
in
graders
],
'has_changes'
:
has_changes
,
'actions'
:
xblock_actions
,
'explanatory_message'
:
explanatory_message
,
'group_access'
:
xblock
.
group_access
,
'user_partitions'
:
get_user_partition_info
(
xblock
,
course
=
course
),
})
# update xblock_info with special exam information if the feature flag is enabled
if
settings
.
FEATURES
.
get
(
'ENABLE_SPECIAL_EXAMS'
):
if
xblock
.
category
==
'course'
:
if
xblock
.
category
==
'sequential'
:
xblock_info
.
update
({
"enable_proctored_exams"
:
xblock
.
enable_proctored_exams
,
"create_zendesk_tickets"
:
xblock
.
create_zendesk_tickets
,
"enable_timed_exams"
:
xblock
.
enable_timed_exams
})
elif
xblock
.
category
==
'sequential'
:
xblock_info
.
update
({
"is_proctored_exam"
:
xblock
.
is_proctored_exam
,
"is_practice_exam"
:
xblock
.
is_practice_exam
,
"is_time_limited"
:
xblock
.
is_time_limited
,
"exam_review_rules"
:
xblock
.
exam_review_rules
,
"default_time_limit_minutes"
:
xblock
.
default_time_limit_minutes
,
'hide_after_due'
:
xblock
.
hide_after_due
,
})
# Update with gating info
xblock_info
.
update
(
_get_gating_info
(
course
,
xblock
))
if
xblock
.
category
==
'sequential'
:
# Entrance exam subsection should be hidden. in_entrance_exam is
# inherited metadata, all children will have it.
if
getattr
(
xblock
,
"in_entrance_exam"
,
False
):
xblock_info
[
"is_header_visible"
]
=
False
if
data
is
not
None
:
xblock_info
[
"data"
]
=
data
if
metadata
is
not
None
:
xblock_info
[
"metadata"
]
=
metadata
if
include_ancestor_info
:
xblock_info
[
'ancestor_info'
]
=
_create_xblock_ancestor_info
(
xblock
,
course_outline
)
if
child_info
:
xblock_info
[
'child_info'
]
=
child_info
if
visibility_state
==
VisibilityState
.
staff_only
:
xblock_info
[
"ancestor_has_staff_lock"
]
=
ancestor_has_staff_lock
(
xblock
,
parent_xblock
)
else
:
xblock_info
[
"ancestor_has_staff_lock"
]
=
False
# update xblock_info with special exam information if the feature flag is enabled
if
settings
.
FEATURES
.
get
(
'ENABLE_SPECIAL_EXAMS'
):
if
xblock
.
category
==
'course'
:
xblock_info
.
update
({
'enable_proctored_exams'
:
xblock
.
enable_proctored_exams
,
'create_zendesk_tickets'
:
xblock
.
create_zendesk_tickets
,
'enable_timed_exams'
:
xblock
.
enable_timed_exams
})
elif
xblock
.
category
==
'sequential'
:
xblock_info
.
update
({
'is_proctored_exam'
:
xblock
.
is_proctored_exam
,
'is_practice_exam'
:
xblock
.
is_practice_exam
,
'is_time_limited'
:
xblock
.
is_time_limited
,
'exam_review_rules'
:
xblock
.
exam_review_rules
,
'default_time_limit_minutes'
:
xblock
.
default_time_limit_minutes
,
})
if
course_outline
:
if
xblock_info
[
"has_explicit_staff_lock"
]:
xblock_info
[
"staff_only_message"
]
=
True
elif
child_info
and
child_info
[
"children"
]:
xblock_info
[
"staff_only_message"
]
=
all
([
child
[
"staff_only_message"
]
for
child
in
child_info
[
"children"
]])
# Update with gating info
xblock_info
.
update
(
_get_gating_info
(
course
,
xblock
))
if
xblock
.
category
==
'sequential'
:
# Entrance exam subsection should be hidden. in_entrance_exam is
# inherited metadata, all children will have it.
if
getattr
(
xblock
,
'in_entrance_exam'
,
False
):
xblock_info
[
'is_header_visible'
]
=
False
if
data
is
not
None
:
xblock_info
[
'data'
]
=
data
if
metadata
is
not
None
:
xblock_info
[
'metadata'
]
=
metadata
if
include_ancestor_info
:
xblock_info
[
'ancestor_info'
]
=
_create_xblock_ancestor_info
(
xblock
,
course_outline
,
include_child_info
=
True
)
if
child_info
:
xblock_info
[
'child_info'
]
=
child_info
if
visibility_state
==
VisibilityState
.
staff_only
:
xblock_info
[
'ancestor_has_staff_lock'
]
=
ancestor_has_staff_lock
(
xblock
,
parent_xblock
)
else
:
xblock_info
[
"staff_only_message"
]
=
False
xblock_info
[
'ancestor_has_staff_lock'
]
=
False
if
course_outline
:
if
xblock_info
[
'has_explicit_staff_lock'
]:
xblock_info
[
'staff_only_message'
]
=
True
elif
child_info
and
child_info
[
'children'
]:
xblock_info
[
'staff_only_message'
]
=
all
(
[
child
[
'staff_only_message'
]
for
child
in
child_info
[
'children'
]]
)
else
:
xblock_info
[
'staff_only_message'
]
=
False
return
xblock_info
...
...
@@ -1156,14 +1170,14 @@ def _compute_visibility_state(xblock, child_info, is_unit_with_changes, is_cours
return
VisibilityState
.
ready
def
_create_xblock_ancestor_info
(
xblock
,
course_outline
):
def
_create_xblock_ancestor_info
(
xblock
,
course_outline
=
False
,
include_child_info
=
False
,
is_concise
=
False
):
"""
Returns information about the ancestors of an xblock. Note that the direct parent will also return
information about all of its children.
"""
ancestors
=
[]
def
collect_ancestor_info
(
ancestor
,
include_child_info
=
False
):
def
collect_ancestor_info
(
ancestor
,
include_child_info
=
False
,
is_concise
=
False
):
"""
Collect xblock info regarding the specified xblock and its ancestors.
"""
...
...
@@ -1173,16 +1187,18 @@ def _create_xblock_ancestor_info(xblock, course_outline):
ancestor
,
include_child_info
=
include_child_info
,
course_outline
=
course_outline
,
include_children_predicate
=
direct_children_only
include_children_predicate
=
direct_children_only
,
is_concise
=
is_concise
))
collect_ancestor_info
(
get_parent_xblock
(
ancestor
))
collect_ancestor_info
(
get_parent_xblock
(
xblock
),
include_child_info
=
Tru
e
)
collect_ancestor_info
(
get_parent_xblock
(
ancestor
)
,
is_concise
=
is_concise
)
collect_ancestor_info
(
get_parent_xblock
(
xblock
),
include_child_info
=
include_child_info
,
is_concise
=
is_concis
e
)
return
{
'ancestors'
:
ancestors
}
def
_create_xblock_child_info
(
xblock
,
course_outline
,
graders
,
include_children_predicate
=
NEVER
,
user
=
None
,
course
=
None
):
# pylint: disable=line-too-long
def
_create_xblock_child_info
(
xblock
,
course_outline
,
graders
,
include_children_predicate
=
NEVER
,
user
=
None
,
course
=
None
,
is_concise
=
False
):
# pylint: disable=line-too-long
"""
Returns information about the children of an xblock, as well as about the primary category
of xblock expected as children.
...
...
@@ -1203,6 +1219,7 @@ def _create_xblock_child_info(xblock, course_outline, graders, include_children_
graders
=
graders
,
user
=
user
,
course
=
course
,
is_concise
=
is_concise
)
for
child
in
xblock
.
get_children
()
]
return
child_info
...
...
cms/djangoapps/contentstore/views/tests/test_course_index.py
View file @
3f355241
...
...
@@ -352,11 +352,16 @@ class TestCourseOutline(CourseTestCase):
parent_location
=
self
.
vertical
.
location
,
category
=
"video"
,
display_name
=
"My Video"
)
def
test_json_responses
(
self
):
@ddt.data
(
True
,
False
)
def
test_json_responses
(
self
,
is_concise
):
"""
Verify the JSON responses returned for the course.
Arguments:
is_concise (Boolean) : If True, fetch concise version of course outline.
"""
outline_url
=
reverse_course_url
(
'course_handler'
,
self
.
course
.
id
)
outline_url
=
outline_url
+
'?format=concise'
if
is_concise
else
outline_url
resp
=
self
.
client
.
get
(
outline_url
,
HTTP_ACCEPT
=
'application/json'
)
json_response
=
json
.
loads
(
resp
.
content
)
...
...
@@ -364,8 +369,9 @@ class TestCourseOutline(CourseTestCase):
self
.
assertEqual
(
json_response
[
'category'
],
'course'
)
self
.
assertEqual
(
json_response
[
'id'
],
unicode
(
self
.
course
.
location
))
self
.
assertEqual
(
json_response
[
'display_name'
],
self
.
course
.
display_name
)
self
.
assertTrue
(
json_response
[
'published'
])
self
.
assertIsNone
(
json_response
[
'visibility_state'
])
if
not
is_concise
:
self
.
assertTrue
(
json_response
[
'published'
])
self
.
assertIsNone
(
json_response
[
'visibility_state'
])
# Now verify the first child
children
=
json_response
[
'child_info'
][
'children'
]
...
...
@@ -374,24 +380,26 @@ class TestCourseOutline(CourseTestCase):
self
.
assertEqual
(
first_child_response
[
'category'
],
'chapter'
)
self
.
assertEqual
(
first_child_response
[
'id'
],
unicode
(
self
.
chapter
.
location
))
self
.
assertEqual
(
first_child_response
[
'display_name'
],
'Week 1'
)
self
.
assertTrue
(
json_response
[
'published'
])
self
.
assertEqual
(
first_child_response
[
'visibility_state'
],
VisibilityState
.
unscheduled
)
if
not
is_concise
:
self
.
assertTrue
(
json_response
[
'published'
])
self
.
assertEqual
(
first_child_response
[
'visibility_state'
],
VisibilityState
.
unscheduled
)
self
.
assertGreater
(
len
(
first_child_response
[
'child_info'
][
'children'
]),
0
)
# Finally, validate the entire response for consistency
self
.
assert_correct_json_response
(
json_response
)
self
.
assert_correct_json_response
(
json_response
,
is_concise
)
def
assert_correct_json_response
(
self
,
json_response
):
def
assert_correct_json_response
(
self
,
json_response
,
is_concise
=
False
):
"""
Asserts that the JSON response is syntactically consistent
"""
self
.
assertIsNotNone
(
json_response
[
'display_name'
])
self
.
assertIsNotNone
(
json_response
[
'id'
])
self
.
assertIsNotNone
(
json_response
[
'category'
])
self
.
assertTrue
(
json_response
[
'published'
])
if
not
is_concise
:
self
.
assertTrue
(
json_response
[
'published'
])
if
json_response
.
get
(
'child_info'
,
None
):
for
child_response
in
json_response
[
'child_info'
][
'children'
]:
self
.
assert_correct_json_response
(
child_response
)
self
.
assert_correct_json_response
(
child_response
,
is_concise
)
def
test_course_outline_initial_state
(
self
):
course_module
=
modulestore
()
.
get_item
(
self
.
course
.
location
)
...
...
cms/djangoapps/contentstore/views/tests/test_item.py
View file @
3f355241
...
...
@@ -20,7 +20,8 @@ from contentstore.views.component import (
)
from
contentstore.views.item
import
(
create_xblock_info
,
ALWAYS
,
VisibilityState
,
_xblock_type_and_display_name
,
add_container_page_publishing_info
create_xblock_info
,
_get_module_info
,
ALWAYS
,
VisibilityState
,
_xblock_type_and_display_name
,
add_container_page_publishing_info
)
from
contentstore.tests.utils
import
CourseTestCase
from
student.tests.factories
import
UserFactory
...
...
@@ -384,6 +385,59 @@ class GetItemTest(ItemTest):
])
self
.
assertEqual
(
result
[
"group_access"
],
{})
@ddt.data
(
'ancestorInfo'
,
''
)
def
test_ancestor_info
(
self
,
field_type
):
"""
Test that we get correct ancestor info.
Arguments:
field_type (string): If field_type=ancestorInfo, fetch ancestor info of the XBlock otherwise not.
"""
# Create a parent chapter
chap1
=
self
.
create_xblock
(
parent_usage_key
=
self
.
course
.
location
,
display_name
=
'chapter1'
,
category
=
'chapter'
)
chapter_usage_key
=
self
.
response_usage_key
(
chap1
)
# create a sequential
seq1
=
self
.
create_xblock
(
parent_usage_key
=
chapter_usage_key
,
display_name
=
'seq1'
,
category
=
'sequential'
)
seq_usage_key
=
self
.
response_usage_key
(
seq1
)
# create a vertical
vert1
=
self
.
create_xblock
(
parent_usage_key
=
seq_usage_key
,
display_name
=
'vertical1'
,
category
=
'vertical'
)
vert_usage_key
=
self
.
response_usage_key
(
vert1
)
# create problem and an html component
problem1
=
self
.
create_xblock
(
parent_usage_key
=
vert_usage_key
,
display_name
=
'problem1'
,
category
=
'problem'
)
problem_usage_key
=
self
.
response_usage_key
(
problem1
)
def
assert_xblock_info
(
xblock
,
xblock_info
):
"""
Assert we have correct xblock info.
Arguments:
xblock (XBlock): An XBlock item.
xblock_info (dict): A dict containing xblock information.
"""
self
.
assertEqual
(
unicode
(
xblock
.
location
),
xblock_info
[
'id'
])
self
.
assertEqual
(
xblock
.
display_name
,
xblock_info
[
'display_name'
])
self
.
assertEqual
(
xblock
.
category
,
xblock_info
[
'category'
])
for
usage_key
in
(
problem_usage_key
,
vert_usage_key
,
seq_usage_key
,
chapter_usage_key
):
xblock
=
self
.
get_item_from_modulestore
(
usage_key
)
url
=
reverse_usage_url
(
'xblock_handler'
,
usage_key
)
+
'?fields={field_type}'
.
format
(
field_type
=
field_type
)
response
=
self
.
client
.
get
(
url
)
self
.
assertEqual
(
response
.
status_code
,
200
)
response
=
json
.
loads
(
response
.
content
)
if
field_type
==
'ancestorInfo'
:
self
.
assertIn
(
'ancestors'
,
response
)
for
ancestor_info
in
response
[
'ancestors'
]:
parent_xblock
=
xblock
.
get_parent
()
assert_xblock_info
(
parent_xblock
,
ancestor_info
)
xblock
=
parent_xblock
else
:
self
.
assertNotIn
(
'ancestors'
,
response
)
self
.
assertEqual
(
_get_module_info
(
xblock
),
response
)
@ddt.ddt
class
DeleteItem
(
ItemTest
):
...
...
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