Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 47 additions & 1 deletion cms/djangoapps/contentstore/tests/test_course_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from contentstore.tests.utils import CourseTestCase
from xmodule.modulestore.django import loc_mapper
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
from xmodule.modulestore import parsers

class TestCourseIndex(CourseTestCase):
Expand Down Expand Up @@ -83,3 +83,49 @@ def test_course_staff_access(self):

# test access
self.check_index_and_outline(course_staff_client)

def test_json_responses(self):
outline_url = self.course_locator.url_reverse('course/')
chapter = ItemFactory.create(parent_location=self.course.location, category='chapter', display_name="Week 1")
lesson = ItemFactory.create(parent_location=chapter.location, category='sequential', display_name="Lesson 1")
subsection = ItemFactory.create(parent_location=lesson.location, category='vertical', display_name='Subsection 1')
ItemFactory.create(parent_location=subsection.location, category="video", display_name="My Video")

resp = self.client.get(outline_url, HTTP_ACCEPT='application/json')
json_response = json.loads(resp.content)

# First spot check some values in the root response
self.assertEqual(json_response['category'], 'course')
self.assertEqual(json_response['id'], 'MITx.999.Robot_Super_Course/branch/draft/block/Robot_Super_Course')
self.assertEqual(json_response['display_name'], 'Robot Super Course')
self.assertTrue(json_response['is_container'])
self.assertFalse(json_response['is_draft'])

# Now verify that the first child
children = json_response['children']
self.assertTrue(len(children) > 0)
first_child_response = children[0]
self.assertEqual(first_child_response['category'], 'chapter')
self.assertEqual(first_child_response['id'], 'MITx.999.Robot_Super_Course/branch/draft/block/Week_1')
self.assertEqual(first_child_response['display_name'], 'Week 1')
self.assertTrue(first_child_response['is_container'])
self.assertFalse(first_child_response['is_draft'])
self.assertTrue(len(first_child_response['children']) > 0)

# Finally, validate the entire response for consistency
self.assert_correct_json_response(json_response)

def assert_correct_json_response(self, json_response):
"""
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.assertIsNotNone(json_response['is_draft'])
self.assertIsNotNone(json_response['is_container'])
if json_response['is_container']:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I don't think this test has any sections in the course (any children). You should probably add some content to the course to make it more interesting.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Somehow I thought that this was a toy course as the other tests seemed to be doing interesting things with it. I would have noticed if it wasn't so painful to set up the debugger. I'll manually add content as you suggest, and then I can verify exactly what is returned too.

for child_response in json_response['children']:
self.assert_correct_json_response(child_response)
else:
self.assertFalse('children' in json_response)
36 changes: 34 additions & 2 deletions cms/djangoapps/contentstore/views/course.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,10 @@ def course_handler(request, tag=None, package_id=None, branch=None, version_guid
DELETE
json: delete this branch from this course (leaving off /branch/draft would imply delete the course)
"""
if 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
response_format = request.REQUEST.get('format', 'html')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary (vs. just checking HTTP_ACCEPT?).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same logic I added when I implemented the assets JSON service. It makes it easier to test the service in a browser. Ideally we'd have support an URL like:

http://localhost:8001/course/AndyA.AA101.2014/branch/draft/block/2014.json

but that is hard to do until we implement something like the Python REST Framework.

It also makes it easier on the client, as it isn't always straightforward to set the HTTP_ACCEPT header and have it passed through all the layers of backbone etc.

if response_format == 'json' or 'application/json' in request.META.get('HTTP_ACCEPT', 'application/json'):
if request.method == 'GET':
raise NotImplementedError('coming soon')
return JsonResponse(_course_json(request, package_id, branch, version_guid, block))
elif request.method == 'POST': # not sure if this is only post. If one will have ids, it goes after access
return create_new_course(request)
elif not has_course_access(
Expand All @@ -125,6 +126,37 @@ def course_handler(request, tag=None, package_id=None, branch=None, version_guid
return HttpResponseNotFound()


@login_required
def _course_json(request, package_id, branch, version_guid, block):
"""
Returns a JSON overview of a course
"""
__, course = _get_locator_and_course(
package_id, branch, version_guid, block, request.user, depth=None
)
return _xmodule_json(course, course.location.course_id)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pass the locator so you don't have to figure it out again in _xmodule_json.

Oh, I see that your method is recursive. If not passing in locator, you can use underscore to indicate that the variable is not used.



def _xmodule_json(xmodule, course_id):
"""
Returns a JSON overview of an XModule
"""
locator = loc_mapper().translate_location(
course_id, xmodule.location, published=False, add_entry_if_missing=True
)
is_container = xmodule.has_children
result = {
'display_name': xmodule.display_name,
'id': unicode(locator),
'category': xmodule.category,
'is_draft': getattr(xmodule, 'is_draft', False),
'is_container': is_container,
}
if is_container:
result['children'] = [_xmodule_json(child, course_id) for child in xmodule.get_children()]
return result


@login_required
@ensure_csrf_cookie
def course_listing(request):
Expand Down