Commit f0b60717 by Jonathan Piacenti

Courseware Navigation API update for jump_to_children

parent 7ed5672e
......@@ -6,6 +6,7 @@ Run these tests @ Devstack:
from datetime import datetime
import json
import uuid
from django.utils import timezone
import mock
from random import randint
from urllib import urlencode
......@@ -23,6 +24,7 @@ from courseware import module_render
from courseware.tests.factories import StudentModuleFactory
from courseware.model_data import FieldDataCache
from django_comment_common.models import Role, FORUM_ROLE_MODERATOR
from gradebook.models import StudentGradebook
from instructor.access import allow_access
from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
......@@ -92,7 +94,6 @@ class CoursesApiTests(ModuleStoreTestCase):
)
return module
def setUp(self):
self.test_server_prefix = 'https://testserver'
self.base_courses_uri = '/api/server/courses'
......@@ -141,11 +142,32 @@ class CoursesApiTests(ModuleStoreTestCase):
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(
category="video",
parent_location=self.course_content.location,
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(
......@@ -330,7 +352,6 @@ class CoursesApiTests(ModuleStoreTestCase):
self.assertIsNotNone(course['course_image_url'])
self.assertItemsEqual(courses, courses_in_result)
def test_course_detail_without_date_values(self):
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)
......@@ -387,7 +408,7 @@ class CoursesApiTests(ModuleStoreTestCase):
chapter = response.data['content'][0]
self.assertEqual(chapter['category'], 'chapter')
self.assertEqual(chapter['name'], 'Overview')
self.assertEqual(len(chapter['children']), 5)
self.assertEqual(len(chapter['children']), 6)
sequence = chapter['children'][0]
self.assertEqual(sequence['category'], 'videosequence')
......@@ -2456,3 +2477,21 @@ class CoursesApiTests(ModuleStoreTestCase):
delete_uri = '{}invalid_role/users/{}'.format(test_uri, self.users[0].id)
response = self.do_delete(delete_uri)
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
)
......@@ -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/*$'.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}/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'/*$^', courses_views.CoursesList.as_view()),
)
......
......@@ -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.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 gradebook.models import StudentGradebook
from instructor.access import revoke_access, update_forum_role
......@@ -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.utils import CommentClientRequestError
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 projects.models import Project, Workgroup
from projects.serializers import ProjectSerializer, BasicWorkgroupSerializer
from student.models import CourseEnrollment, CourseEnrollmentAllowed
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.search import path_to_location
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
......@@ -1961,3 +1962,47 @@ class CoursesRolesUsersDetail(SecureAPIView):
return Response({}, status=status.HTTP_404_NOT_FOUND)
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)
......@@ -623,28 +623,34 @@ def _index_bulk_op(request, course_key, chapter, section, position):
return result
@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)
def item_finder(request, course_key, module_id):
items = modulestore().get_items(course_key, qualifiers={'name': module_id})
if len(items) == 0:
raise Http404(
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:
log.warning(
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", ""), items[0].location.to_deprecated_string()
))
return jump_to(request, course_id, items[0].location.to_deprecated_string())
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, unicode(items[0].location))
@ensure_csrf_cookie
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment