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
a1d58561
Commit
a1d58561
authored
Jul 13, 2015
by
Kelketek
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #453 from open-craft/jump_to_children
Jump to children
parents
47dbf5c4
a1afd2b0
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
130 additions
and
25 deletions
+130
-25
common/lib/xmodule/xmodule/modulestore/search.py
+2
-1
lms/djangoapps/api_manager/courses/tests.py
+41
-4
lms/djangoapps/api_manager/courses/urls.py
+1
-0
lms/djangoapps/api_manager/courses/views.py
+47
-2
lms/djangoapps/courseware/views.py
+39
-18
No files found.
common/lib/xmodule/xmodule/modulestore/search.py
View file @
a1d58561
...
@@ -81,6 +81,7 @@ def path_to_location(modulestore, usage_key):
...
@@ -81,6 +81,7 @@ def path_to_location(modulestore, usage_key):
# pull out the location names
# pull out the location names
chapter
=
path
[
1
]
.
name
if
n
>
1
else
None
chapter
=
path
[
1
]
.
name
if
n
>
1
else
None
section
=
path
[
2
]
.
name
if
n
>
2
else
None
section
=
path
[
2
]
.
name
if
n
>
2
else
None
vertical
=
path
[
3
]
.
name
if
n
>
3
else
None
# Figure out the position
# Figure out the position
position
=
None
position
=
None
...
@@ -104,4 +105,4 @@ def path_to_location(modulestore, usage_key):
...
@@ -104,4 +105,4 @@ def path_to_location(modulestore, usage_key):
position_list
.
append
(
str
(
child_locs
.
index
(
path
[
path_index
+
1
])
+
1
))
position_list
.
append
(
str
(
child_locs
.
index
(
path
[
path_index
+
1
])
+
1
))
position
=
"_"
.
join
(
position_list
)
position
=
"_"
.
join
(
position_list
)
return
(
course_id
,
chapter
,
section
,
position
)
return
(
course_id
,
chapter
,
section
,
vertical
,
position
,
path
[
-
1
]
)
lms/djangoapps/api_manager/courses/tests.py
View file @
a1d58561
...
@@ -102,7 +102,6 @@ class CoursesApiTests(ModuleStoreTestCase):
...
@@ -102,7 +102,6 @@ class CoursesApiTests(ModuleStoreTestCase):
)
)
return
module
return
module
def
setUp
(
self
):
def
setUp
(
self
):
self
.
test_server_prefix
=
'https://testserver'
self
.
test_server_prefix
=
'https://testserver'
self
.
base_courses_uri
=
'/api/server/courses'
self
.
base_courses_uri
=
'/api/server/courses'
...
@@ -151,11 +150,32 @@ class CoursesApiTests(ModuleStoreTestCase):
...
@@ -151,11 +150,32 @@ class CoursesApiTests(ModuleStoreTestCase):
display_name
=
"Video_Sequence"
,
display_name
=
"Video_Sequence"
,
)
)
self
.
course_content2
=
ItemFactory
.
create
(
category
=
"sequential"
,
parent_location
=
self
.
chapter
.
location
,
data
=
self
.
test_data
,
display_name
=
"Sequential"
,
)
self
.
content_child
=
ItemFactory
.
create
(
self
.
content_child
=
ItemFactory
.
create
(
category
=
"video"
,
category
=
"video"
,
parent_location
=
self
.
course_content
.
location
,
parent_location
=
self
.
course_content
.
location
,
data
=
self
.
test_data
,
data
=
self
.
test_data
,
display_name
=
"Video_Resources"
,
display_name
=
"Video"
,
)
self
.
content_child2
=
ItemFactory
.
create
(
category
=
"vertical"
,
parent_location
=
self
.
course_content2
.
location
,
data
=
self
.
test_data
,
display_name
=
"Vertical Sequence"
)
self
.
content_subchild
=
ItemFactory
.
create
(
category
=
"video"
,
parent_location
=
self
.
content_child2
.
location
,
data
=
self
.
test_data
,
display_name
=
"Child Video"
,
)
)
self
.
overview
=
ItemFactory
.
create
(
self
.
overview
=
ItemFactory
.
create
(
...
@@ -340,7 +360,6 @@ class CoursesApiTests(ModuleStoreTestCase):
...
@@ -340,7 +360,6 @@ class CoursesApiTests(ModuleStoreTestCase):
self
.
assertIsNotNone
(
course
[
'course_image_url'
])
self
.
assertIsNotNone
(
course
[
'course_image_url'
])
self
.
assertItemsEqual
(
courses
,
courses_in_result
)
self
.
assertItemsEqual
(
courses
,
courses_in_result
)
def
test_course_detail_without_date_values
(
self
):
def
test_course_detail_without_date_values
(
self
):
create_course_with_out_date_values
=
CourseFactory
.
create
()
# pylint: disable=C0103
create_course_with_out_date_values
=
CourseFactory
.
create
()
# pylint: disable=C0103
test_uri
=
self
.
base_courses_uri
+
'/'
+
unicode
(
create_course_with_out_date_values
.
id
)
test_uri
=
self
.
base_courses_uri
+
'/'
+
unicode
(
create_course_with_out_date_values
.
id
)
...
@@ -397,7 +416,7 @@ class CoursesApiTests(ModuleStoreTestCase):
...
@@ -397,7 +416,7 @@ class CoursesApiTests(ModuleStoreTestCase):
chapter
=
response
.
data
[
'content'
][
0
]
chapter
=
response
.
data
[
'content'
][
0
]
self
.
assertEqual
(
chapter
[
'category'
],
'chapter'
)
self
.
assertEqual
(
chapter
[
'category'
],
'chapter'
)
self
.
assertEqual
(
chapter
[
'name'
],
'Overview'
)
self
.
assertEqual
(
chapter
[
'name'
],
'Overview'
)
self
.
assertEqual
(
len
(
chapter
[
'children'
]),
5
)
self
.
assertEqual
(
len
(
chapter
[
'children'
]),
6
)
sequence
=
chapter
[
'children'
][
0
]
sequence
=
chapter
[
'children'
][
0
]
self
.
assertEqual
(
sequence
[
'category'
],
'videosequence'
)
self
.
assertEqual
(
sequence
[
'category'
],
'videosequence'
)
...
@@ -2472,3 +2491,21 @@ class CoursesApiTests(ModuleStoreTestCase):
...
@@ -2472,3 +2491,21 @@ class CoursesApiTests(ModuleStoreTestCase):
delete_uri
=
'{}invalid_role/users/{}'
.
format
(
test_uri
,
self
.
users
[
0
]
.
id
)
delete_uri
=
'{}invalid_role/users/{}'
.
format
(
test_uri
,
self
.
users
[
0
]
.
id
)
response
=
self
.
do_delete
(
delete_uri
)
response
=
self
.
do_delete
(
delete_uri
)
self
.
assertEqual
(
response
.
status_code
,
404
)
self
.
assertEqual
(
response
.
status_code
,
404
)
def
test_course_navigation
(
self
):
test_uri
=
'{}/{}/navigation/{}'
.
format
(
self
.
base_courses_uri
,
unicode
(
self
.
course
.
id
),
self
.
content_subchild
.
location
.
block_id
)
response
=
self
.
do_get
(
test_uri
)
self
.
maxDiff
=
None
self
.
assertEqual
(
{
'chapter'
:
unicode
(
self
.
chapter
.
location
),
'vertical'
:
unicode
(
self
.
content_child2
.
location
),
'section'
:
unicode
(
self
.
course_content2
.
location
),
'course_key'
:
unicode
(
self
.
course
.
id
),
'final_target_id'
:
unicode
(
self
.
content_subchild
.
location
),
'position'
:
'1'
,
},
response
.
data
)
lms/djangoapps/api_manager/courses/urls.py
View file @
a1d58561
...
@@ -40,6 +40,7 @@ urlpatterns = patterns(
...
@@ -40,6 +40,7 @@ urlpatterns = patterns(
url
(
r'^{0}/users/(?P<user_id>[0-9]+)$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesUsersDetail
.
as_view
()),
url
(
r'^{0}/users/(?P<user_id>[0-9]+)$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesUsersDetail
.
as_view
()),
url
(
r'^{0}/users/*$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesUsersList
.
as_view
()),
url
(
r'^{0}/users/*$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesUsersList
.
as_view
()),
url
(
r'^{0}/workgroups/*$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesWorkgroupsList
.
as_view
()),
url
(
r'^{0}/workgroups/*$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesWorkgroupsList
.
as_view
()),
url
(
r'^{0}/navigation/{1}$'
.
format
(
COURSE_ID_PATTERN
,
settings
.
USAGE_KEY_PATTERN
),
courses_views
.
CourseNavView
.
as_view
()),
url
(
r'^{0}$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesDetail
.
as_view
()),
url
(
r'^{0}$'
.
format
(
COURSE_ID_PATTERN
),
courses_views
.
CoursesDetail
.
as_view
()),
url
(
r'/*$^'
,
courses_views
.
CoursesList
.
as_view
()),
url
(
r'/*$^'
,
courses_views
.
CoursesList
.
as_view
()),
)
)
...
...
lms/djangoapps/api_manager/courses/views.py
View file @
a1d58561
...
@@ -21,7 +21,7 @@ from rest_framework.response import Response
...
@@ -21,7 +21,7 @@ from rest_framework.response import Response
from
courseware.courses
import
get_course_about_section
,
get_course_info_section
,
course_image_url
from
courseware.courses
import
get_course_about_section
,
get_course_info_section
,
course_image_url
from
courseware.models
import
StudentModule
from
courseware.models
import
StudentModule
from
courseware.views
import
get_static_tab_contents
from
courseware.views
import
get_static_tab_contents
,
item_finder
from
django_comment_common.models
import
FORUM_ROLE_MODERATOR
from
django_comment_common.models
import
FORUM_ROLE_MODERATOR
from
gradebook.models
import
StudentGradebook
from
gradebook.models
import
StudentGradebook
from
instructor.access
import
revoke_access
,
update_forum_role
from
instructor.access
import
revoke_access
,
update_forum_role
...
@@ -29,13 +29,14 @@ from lms.lib.comment_client.user import get_course_social_stats
...
@@ -29,13 +29,14 @@ from lms.lib.comment_client.user import get_course_social_stats
from
lms.lib.comment_client.thread
import
get_course_thread_stats
from
lms.lib.comment_client.thread
import
get_course_thread_stats
from
lms.lib.comment_client.utils
import
CommentClientRequestError
from
lms.lib.comment_client.utils
import
CommentClientRequestError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
from
opaque_keys.edx.keys
import
CourseKey
,
UsageKey
from
progress.models
import
StudentProgress
from
progress.models
import
StudentProgress
from
projects.models
import
Project
,
Workgroup
from
projects.models
import
Project
,
Workgroup
from
projects.serializers
import
ProjectSerializer
,
BasicWorkgroupSerializer
from
projects.serializers
import
ProjectSerializer
,
BasicWorkgroupSerializer
from
student.models
import
CourseEnrollment
,
CourseEnrollmentAllowed
from
student.models
import
CourseEnrollment
,
CourseEnrollmentAllowed
from
student.roles
import
CourseRole
,
CourseAccessRole
,
CourseInstructorRole
,
CourseStaffRole
,
CourseObserverRole
,
CourseAssistantRole
,
UserBasedRole
,
get_aggregate_exclusion_user_ids
from
student.roles
import
CourseRole
,
CourseAccessRole
,
CourseInstructorRole
,
CourseStaffRole
,
CourseObserverRole
,
CourseAssistantRole
,
UserBasedRole
,
get_aggregate_exclusion_user_ids
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.search
import
path_to_location
from
api_manager.courseware_access
import
get_course
,
get_course_child
,
get_course_leaf_nodes
,
get_course_key
,
\
from
api_manager.courseware_access
import
get_course
,
get_course_child
,
get_course_leaf_nodes
,
get_course_key
,
\
course_exists
,
get_modulestore
,
get_course_descriptor
course_exists
,
get_modulestore
,
get_course_descriptor
...
@@ -1961,3 +1962,47 @@ class CoursesRolesUsersDetail(SecureAPIView):
...
@@ -1961,3 +1962,47 @@ class CoursesRolesUsersDetail(SecureAPIView):
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
({},
status
=
status
.
HTTP_404_NOT_FOUND
)
return
Response
({},
status
=
status
.
HTTP_204_NO_CONTENT
)
return
Response
({},
status
=
status
.
HTTP_204_NO_CONTENT
)
class
CourseNavView
(
SecureAPIView
):
"""
### The CourseNavView view exposes navigation information for particular usage id: course, chapter, section and
vertical keys, position in innermost container and last addressable block/module on the path (usually the same
usage id that was passed as an argument)
- URI: ```/api/courses/{course_id}/navigation/{module_id}```
- GET: Gets navigation information
"""
def
_get_full_location_key_by_module_id
(
self
,
request
,
course_key
,
module_id
):
"""
Gets full location id by module id
"""
items
=
item_finder
(
request
,
course_key
,
module_id
)
return
items
[
0
]
.
location
def
get
(
self
,
request
,
course_id
,
usage_key_string
):
# pylint: disable=W0613
"""
GET /api/courses/{course_id}/navigation/{module_id}
"""
try
:
_
,
course_key
,
__
=
get_course
(
request
,
request
.
user
,
course_id
)
usage_key
=
self
.
_get_full_location_key_by_module_id
(
request
,
course_key
,
usage_key_string
)
except
InvalidKeyError
:
raise
Http404
(
u"Invalid course_key or usage_key"
)
(
course_key
,
chapter
,
section
,
vertical
,
position
,
final_target_id
)
=
path_to_location
(
modulestore
(),
usage_key
)
chapter_key
=
course_key
.
make_usage_key
(
'chapter'
,
chapter
)
section_key
=
course_key
.
make_usage_key
(
'sequential'
,
section
)
vertical_key
=
course_key
.
make_usage_key
(
'vertical'
,
vertical
)
result
=
{
'course_key'
:
unicode
(
course_key
),
'chapter'
:
unicode
(
chapter_key
),
'section'
:
unicode
(
section_key
),
'vertical'
:
unicode
(
vertical_key
),
'position'
:
unicode
(
position
),
'final_target_id'
:
unicode
(
final_target_id
)
}
return
Response
(
result
,
status
=
status
.
HTTP_200_OK
)
lms/djangoapps/courseware/views.py
View file @
a1d58561
...
@@ -23,6 +23,7 @@ from django.utils.timezone import UTC
...
@@ -23,6 +23,7 @@ from django.utils.timezone import UTC
from
django.views.decorators.http
import
require_GET
from
django.views.decorators.http
import
require_GET
from
django.http
import
Http404
,
HttpResponse
from
django.http
import
Http404
,
HttpResponse
from
django.shortcuts
import
redirect
from
django.shortcuts
import
redirect
from
opaque_keys.edx.keys
import
CourseKey
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
,
marketing_link
from
edxmako.shortcuts
import
render_to_response
,
render_to_string
,
marketing_link
from
django_future.csrf
import
ensure_csrf_cookie
from
django_future.csrf
import
ensure_csrf_cookie
from
django.views.decorators.cache
import
cache_control
from
django.views.decorators.cache
import
cache_control
...
@@ -60,6 +61,7 @@ from util.milestones_helpers import get_prerequisite_courses_display
...
@@ -60,6 +61,7 @@ from util.milestones_helpers import get_prerequisite_courses_display
from
microsite_configuration
import
microsite
from
microsite_configuration
import
microsite
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
BlockUsageLocator
from
instructor.enrollment
import
uses_shib
from
instructor.enrollment
import
uses_shib
from
util.db
import
commit_on_success_with_read_committed
from
util.db
import
commit_on_success_with_read_committed
...
@@ -492,7 +494,8 @@ def _index_bulk_op(request, course_key, chapter, section, position):
...
@@ -492,7 +494,8 @@ def _index_bulk_op(request, course_key, chapter, section, position):
# Save where we are in the chapter
# Save where we are in the chapter
save_child_position
(
chapter_module
,
section
)
save_child_position
(
chapter_module
,
section
)
context
[
'fragment'
]
=
section_module
.
render
(
STUDENT_VIEW
)
section_render_context
=
{
'activate_block_id'
:
request
.
GET
.
get
(
'activate_block_id'
)}
context
[
'fragment'
]
=
section_module
.
render
(
STUDENT_VIEW
,
section_render_context
)
context
[
'section_title'
]
=
section_descriptor
.
display_name_with_default
context
[
'section_title'
]
=
section_descriptor
.
display_name_with_default
else
:
else
:
# section is none, so display a message
# section is none, so display a message
...
@@ -559,29 +562,32 @@ def _index_bulk_op(request, course_key, chapter, section, position):
...
@@ -559,29 +562,32 @@ def _index_bulk_op(request, course_key, chapter, section, position):
return
result
return
result
def
item_finder
(
request
,
course_key
,
module_id
):
@ensure_csrf_cookie
@ensure_valid_course_key
def
jump_to_id
(
request
,
course_id
,
module_id
):
"""
This entry point allows for a shorter version of a jump to where just the id of the element is
passed in. This assumes that id is unique within the course_id namespace
"""
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
items
=
modulestore
()
.
get_items
(
course_key
,
qualifiers
=
{
'name'
:
module_id
})
items
=
modulestore
()
.
get_items
(
course_key
,
qualifiers
=
{
'name'
:
module_id
})
if
len
(
items
)
==
0
:
if
len
(
items
)
==
0
:
raise
Http404
(
raise
Http404
(
u"Could not find id: {0} in course_id: {1}. Referer: {2}"
.
format
(
u"Could not find id: {0} in course_id: {1}. Referer: {2}"
.
format
(
module_id
,
course_
id
,
request
.
META
.
get
(
"HTTP_REFERER"
,
""
)
module_id
,
course_
key
,
request
.
META
.
get
(
"HTTP_REFERER"
,
""
)
))
))
if
len
(
items
)
>
1
:
if
len
(
items
)
>
1
:
log
.
warning
(
log
.
warning
(
u"Multiple items found with id: {0} in course_id: {1}. Referer: {2}. Using first: {3}"
.
format
(
u"Multiple items found with id: {0} in course_id: {1}. Referer: {2}. Using first: {3}"
.
format
(
module_id
,
course_
id
,
request
.
META
.
get
(
"HTTP_REFERER"
,
""
),
items
[
0
]
.
location
.
to_deprecated_string
(
)
module_id
,
course_
key
,
request
.
META
.
get
(
"HTTP_REFERER"
,
""
),
unicode
(
items
[
0
]
)
))
))
return
items
@ensure_csrf_cookie
@ensure_valid_course_key
def
jump_to_id
(
request
,
course_id
,
module_id
):
"""
This entry point allows for a shorter version of a jump to where just the id of the element is
passed in. This assumes that id is unique within the course_id namespace
"""
course_key
=
CourseKey
.
from_string
(
course_id
)
items
=
item_finder
(
request
,
course_key
,
module_id
)
return
jump_to
(
request
,
course_id
,
items
[
0
]
.
location
.
to_deprecated_string
(
))
return
jump_to
(
request
,
course_id
,
unicode
(
items
[
0
]
.
location
))
@ensure_csrf_cookie
@ensure_csrf_cookie
...
@@ -600,7 +606,7 @@ def jump_to(request, course_id, location):
...
@@ -600,7 +606,7 @@ def jump_to(request, course_id, location):
except
InvalidKeyError
:
except
InvalidKeyError
:
raise
Http404
(
u"Invalid course_key or usage_key"
)
raise
Http404
(
u"Invalid course_key or usage_key"
)
try
:
try
:
(
course_key
,
chapter
,
section
,
position
)
=
path_to_location
(
modulestore
(),
usage_key
)
(
course_key
,
chapter
,
section
,
vertical
,
position
,
final_target_id
)
=
path_to_location
(
modulestore
(),
usage_key
)
except
ItemNotFoundError
:
except
ItemNotFoundError
:
raise
Http404
(
u"No data at this location: {0}"
.
format
(
usage_key
))
raise
Http404
(
u"No data at this location: {0}"
.
format
(
usage_key
))
except
NoPathToItem
:
except
NoPathToItem
:
...
@@ -609,14 +615,29 @@ def jump_to(request, course_id, location):
...
@@ -609,14 +615,29 @@ def jump_to(request, course_id, location):
# choose the appropriate view (and provide the necessary args) based on the
# choose the appropriate view (and provide the necessary args) based on the
# args provided by the redirect.
# args provided by the redirect.
# Rely on index to do all error handling and access control.
# Rely on index to do all error handling and access control.
if
chapter
is
None
:
if
chapter
is
None
:
re
turn
redirect
(
'courseware'
,
course_id
=
course_key
.
to_deprecated_string
(
))
re
direct_url
=
reverse
(
'courseware'
,
args
=
(
unicode
(
course_key
),
))
elif
section
is
None
:
elif
section
is
None
:
re
turn
redirect
(
'courseware_chapter'
,
course_id
=
course_key
.
to_deprecated_string
(),
chapter
=
chapter
)
re
direct_url
=
reverse
(
'courseware_chapter'
,
args
=
(
unicode
(
course_key
),
chapter
)
)
elif
position
is
None
:
elif
position
is
None
:
return
redirect
(
'courseware_section'
,
course_id
=
course_key
.
to_deprecated_string
(),
chapter
=
chapter
,
section
=
section
)
redirect_url
=
reverse
(
'courseware_section'
,
args
=
(
unicode
(
course_key
),
chapter
,
section
)
)
else
:
else
:
return
redirect
(
'courseware_position'
,
course_id
=
course_key
.
to_deprecated_string
(),
chapter
=
chapter
,
section
=
section
,
position
=
position
)
# Here we use the navigation_index from the position returned from
# path_to_location - we can only navigate to the topmost vertical at the
# moment
redirect_url
=
reverse
(
'courseware_position'
,
args
=
(
unicode
(
course_key
),
chapter
,
section
,
position
)
)
redirect_url
+=
"?{}"
.
format
(
urllib
.
urlencode
({
'activate_block_id'
:
unicode
(
final_target_id
)}))
return
redirect
(
redirect_url
)
@ensure_csrf_cookie
@ensure_csrf_cookie
...
...
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