Commit 80c83f0b by Don Mitchell

Merge pull request #1508 from edx/dhm/expect_json

Change expect_json to put parsed json in new attr
parents 9c6d0d6d 45453fae
...@@ -41,6 +41,27 @@ in the set contentstore.views.item.DETACHED_CATEGORIES nor 'course'. ...@@ -41,6 +41,27 @@ in the set contentstore.views.item.DETACHED_CATEGORIES nor 'course'.
Studio: Bug fix for text loss in Course Updates when the text exists Studio: Bug fix for text loss in Course Updates when the text exists
before the first tag. before the first tag.
Common: expect_json decorator now puts the parsed json payload into a json attr
on the request instead of overwriting the POST attr
---------- split mongo backend refactoring changelog section ------------
Studio: course catalog, assets, checklists, course outline pages now use course
id syntax w/ restful api style
Common:
separate the non-sql db connection configuration from the modulestore (xblock modeling) configuration.
in split, separate the the db connection and atomic crud ops into a distinct module & class from modulestore
Common: location mapper: % encode periods and dollar signs when used as key in the mapping dict
Common: location mapper: added a bunch of new helper functions for generating
old location style info from a CourseLocator
Common: locators: allow - ~ and . in course, branch, and block ids.
---------- end split mongo backend section ---------
Blades: Hovering over CC button in video player, when transcripts are hidden, Blades: Hovering over CC button in video player, when transcripts are hidden,
will cause them to show up. Moving the mouse from the CC button will auto hide will cause them to show up. Moving the mouse from the CC button will auto hide
them. You can hover over the CC button and then move the mouse to the them. You can hover over the CC button and then move the mouse to the
...@@ -396,22 +417,6 @@ Studio: Add feedback to end user if there is a problem exporting a course ...@@ -396,22 +417,6 @@ Studio: Add feedback to end user if there is a problem exporting a course
Studio: Improve link re-writing on imports into a different course-id Studio: Improve link re-writing on imports into a different course-id
---------- split mongo backend refactoring changelog section ------------
Studio: course catalog and course outline pages new use course id syntax w/ restful api style
Common:
separate the non-sql db connection configuration from the modulestore (xblock modeling) configuration.
in split, separate the the db connection and atomic crud ops into a distinct module & class from modulestore
Common: location mapper: % encode periods and dollar signs when used as key in the mapping dict
Common: location mapper: added a bunch of new helper functions for generating old location style info from a CourseLocator
Common: locators: allow - ~ and . in course, branch, and block ids.
---------- end split mongo backend section ---------
XQueue: Fixed (hopefully) worker crash when the connection to RabbitMQ is XQueue: Fixed (hopefully) worker crash when the connection to RabbitMQ is
dropped suddenly. dropped suddenly.
......
...@@ -105,7 +105,7 @@ class ChecklistTestCase(CourseTestCase): ...@@ -105,7 +105,7 @@ class ChecklistTestCase(CourseTestCase):
self.assertEqual('CourseOutline', get_first_item(payload).get('action_url')) self.assertEqual('CourseOutline', get_first_item(payload).get('action_url'))
get_first_item(payload)['is_checked'] = True get_first_item(payload)['is_checked'] = True
returned_checklist = json.loads(self.client.post(update_url, json.dumps(payload), "application/json").content) returned_checklist = json.loads(self.client.ajax_post(update_url, payload).content)
self.assertTrue(get_first_item(returned_checklist).get('is_checked')) self.assertTrue(get_first_item(returned_checklist).get('is_checked'))
persisted_checklist = self.get_persisted_checklists()[1] persisted_checklist = self.get_persisted_checklists()[1]
# Verify that persisted checklist does not have expanded action URLs. # Verify that persisted checklist does not have expanded action URLs.
......
...@@ -6,7 +6,6 @@ import mock ...@@ -6,7 +6,6 @@ import mock
from textwrap import dedent from textwrap import dedent
from django.test.client import Client
from django.test.utils import override_settings from django.test.utils import override_settings
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
...@@ -20,7 +19,7 @@ from datetime import timedelta ...@@ -20,7 +19,7 @@ from datetime import timedelta
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.dispatch import Signal from django.dispatch import Signal
from contentstore.utils import get_modulestore from contentstore.utils import get_modulestore
from contentstore.tests.utils import parse_json from contentstore.tests.utils import parse_json, AjaxEnabledTestClient
from auth.authz import add_user_to_creator_group from auth.authz import add_user_to_creator_group
...@@ -98,7 +97,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -98,7 +97,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# Save the data that we've just changed to the db. # Save the data that we've just changed to the db.
self.user.save() self.user.save()
self.client = Client() self.client = AjaxEnabledTestClient()
self.client.login(username=uname, password=password) self.client.login(username=uname, password=password)
def tearDown(self): def tearDown(self):
...@@ -420,7 +419,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -420,7 +419,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
if tab['type'] == 'static_tab': if tab['type'] == 'static_tab':
reverse_tabs.insert(0, 'i4x://edX/999/static_tab/{0}'.format(tab['url_slug'])) reverse_tabs.insert(0, 'i4x://edX/999/static_tab/{0}'.format(tab['url_slug']))
self.client.post(reverse('reorder_static_tabs'), json.dumps({'tabs': reverse_tabs}), "application/json") self.client.ajax_post(reverse('reorder_static_tabs'), {'tabs': reverse_tabs})
course = module_store.get_item(Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None])) course = module_store.get_item(Location(['i4x', 'edX', '999', 'course', 'Robot_Super_Course', None]))
...@@ -755,7 +754,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase): ...@@ -755,7 +754,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
expected_children = [] expected_children = []
for child_loc_url in source_item.children: for child_loc_url in source_item.children:
child_loc = Location(child_loc_url) child_loc = Location(child_loc_url)
child_loc = child_loc._replace( child_loc = child_loc.replace(
tag=dest_location.tag, tag=dest_location.tag,
org=dest_location.org, org=dest_location.org,
course=dest_location.course course=dest_location.course
...@@ -1333,7 +1332,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1333,7 +1332,7 @@ class ContentStoreTest(ModuleStoreTestCase):
self.user.is_staff = True self.user.is_staff = True
self.user.save() self.user.save()
self.client = Client() self.client = AjaxEnabledTestClient()
self.client.login(username=uname, password=password) self.client.login(username=uname, password=password)
self.course_data = { self.course_data = {
...@@ -1344,8 +1343,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1344,8 +1343,7 @@ class ContentStoreTest(ModuleStoreTestCase):
} }
def tearDown(self): def tearDown(self):
mongo = MongoClient() MongoClient().drop_database(TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'])
mongo.drop_database(TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'])
_CONTENTSTORE.clear() _CONTENTSTORE.clear()
def test_create_course(self): def test_create_course(self):
...@@ -1394,7 +1392,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1394,7 +1392,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_create_course_duplicate_course(self): def test_create_course_duplicate_course(self):
"""Test new course creation - error path""" """Test new course creation - error path"""
self.client.post(reverse('create_new_course'), self.course_data) self.client.ajax_post(reverse('create_new_course'), self.course_data)
self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.') self.assert_course_creation_failed('There is already a course defined with the same organization, course number, and course run. Please change either organization or course number to be unique.')
def assert_course_creation_failed(self, error_message): def assert_course_creation_failed(self, error_message):
...@@ -1403,7 +1401,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1403,7 +1401,7 @@ class ContentStoreTest(ModuleStoreTestCase):
""" """
course_id = _get_course_id(self.course_data) course_id = _get_course_id(self.course_data)
initially_enrolled = CourseEnrollment.is_enrolled(self.user, course_id) initially_enrolled = CourseEnrollment.is_enrolled(self.user, course_id)
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
data = parse_json(resp) data = parse_json(resp)
self.assertEqual(data['ErrMsg'], error_message) self.assertEqual(data['ErrMsg'], error_message)
...@@ -1413,7 +1411,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1413,7 +1411,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_create_course_duplicate_number(self): def test_create_course_duplicate_number(self):
"""Test new course creation - error path""" """Test new course creation - error path"""
self.client.post(reverse('create_new_course'), self.course_data) self.client.ajax_post(reverse('create_new_course'), self.course_data)
self.course_data['display_name'] = 'Robot Super Course Two' self.course_data['display_name'] = 'Robot Super Course Two'
self.course_data['run'] = '2013_Summer' self.course_data['run'] = '2013_Summer'
...@@ -1422,13 +1420,13 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1422,13 +1420,13 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_create_course_case_change(self): def test_create_course_case_change(self):
"""Test new course creation - error path due to case insensitive name equality""" """Test new course creation - error path due to case insensitive name equality"""
self.course_data['number'] = 'capital' self.course_data['number'] = 'capital'
self.client.post(reverse('create_new_course'), self.course_data) self.client.ajax_post(reverse('create_new_course'), self.course_data)
cache_current = self.course_data['org'] cache_current = self.course_data['org']
self.course_data['org'] = self.course_data['org'].lower() self.course_data['org'] = self.course_data['org'].lower()
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change at least one field to be unique.') self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change at least one field to be unique.')
self.course_data['org'] = cache_current self.course_data['org'] = cache_current
self.client.post(reverse('create_new_course'), self.course_data) self.client.ajax_post(reverse('create_new_course'), self.course_data)
cache_current = self.course_data['number'] cache_current = self.course_data['number']
self.course_data['number'] = self.course_data['number'].upper() self.course_data['number'] = self.course_data['number'].upper()
self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change at least one field to be unique.') self.assert_course_creation_failed('There is already a course defined with the same organization and course number. Please change at least one field to be unique.')
...@@ -1437,14 +1435,14 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1437,14 +1435,14 @@ class ContentStoreTest(ModuleStoreTestCase):
""" """
Test that a new course can be created whose name is a substring of an existing course Test that a new course can be created whose name is a substring of an existing course
""" """
self.client.post(reverse('create_new_course'), self.course_data) self.client.ajax_post(reverse('create_new_course'), self.course_data)
cache_current = self.course_data['number'] cache_current = self.course_data['number']
self.course_data['number'] = '{}a'.format(self.course_data['number']) self.course_data['number'] = '{}a'.format(self.course_data['number'])
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
self.course_data['number'] = cache_current self.course_data['number'] = cache_current
self.course_data['org'] = 'a{}'.format(self.course_data['org']) self.course_data['org'] = 'a{}'.format(self.course_data['org'])
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
def test_create_course_with_bad_organization(self): def test_create_course_with_bad_organization(self):
...@@ -1487,7 +1485,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1487,7 +1485,7 @@ class ContentStoreTest(ModuleStoreTestCase):
""" """
Checks that the course did not get created due to a PermissionError. Checks that the course did not get created due to a PermissionError.
""" """
resp = self.client.post(reverse('create_new_course'), self.course_data) resp = self.client.ajax_post(reverse('create_new_course'), self.course_data)
self.assertEqual(resp.status_code, 403) self.assertEqual(resp.status_code, 403)
def test_course_index_view_with_no_courses(self): def test_course_index_view_with_no_courses(self):
...@@ -1546,7 +1544,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1546,7 +1544,7 @@ class ContentStoreTest(ModuleStoreTestCase):
'display_name': 'Section One', 'display_name': 'Section One',
} }
resp = self.client.post(reverse('create_item'), section_data) resp = self.client.ajax_post(reverse('create_item'), section_data)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
data = parse_json(resp) data = parse_json(resp)
...@@ -1564,7 +1562,7 @@ class ContentStoreTest(ModuleStoreTestCase): ...@@ -1564,7 +1562,7 @@ class ContentStoreTest(ModuleStoreTestCase):
'category': 'problem' 'category': 'problem'
} }
resp = self.client.post(reverse('create_item'), problem_data) resp = self.client.ajax_post(reverse('create_item'), problem_data)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
payload = parse_json(resp) payload = parse_json(resp)
...@@ -1929,7 +1927,7 @@ def _create_course(test, course_data): ...@@ -1929,7 +1927,7 @@ def _create_course(test, course_data):
course_id = _get_course_id(course_data) course_id = _get_course_id(course_data)
new_location = loc_mapper().translate_location(course_id, CourseDescriptor.id_to_location(course_id), False, True) new_location = loc_mapper().translate_location(course_id, CourseDescriptor.id_to_location(course_id), False, True)
response = test.client.post(reverse('create_new_course'), course_data) response = test.client.ajax_post(reverse('create_new_course'), course_data)
test.assertEqual(response.status_code, 200) test.assertEqual(response.status_code, 200)
data = parse_json(response) data = parse_json(response)
test.assertNotIn('ErrMsg', data) test.assertNotIn('ErrMsg', data)
......
...@@ -176,7 +176,7 @@ class CourseDetailsViewTest(CourseTestCase): ...@@ -176,7 +176,7 @@ class CourseDetailsViewTest(CourseTestCase):
payload['end_date'] = CourseDetailsViewTest.convert_datetime_to_iso(details.end_date) payload['end_date'] = CourseDetailsViewTest.convert_datetime_to_iso(details.end_date)
payload['enrollment_start'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_start) payload['enrollment_start'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_start)
payload['enrollment_end'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_end) payload['enrollment_end'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_end)
resp = self.client.post(url, json.dumps(payload), "application/json") resp = self.client.ajax_post(url, payload)
self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, field + str(val)) self.compare_details_with_encoding(json.loads(resp.content), details.__dict__, field + str(val))
@staticmethod @staticmethod
...@@ -462,6 +462,6 @@ class CourseGraderUpdatesTest(CourseTestCase): ...@@ -462,6 +462,6 @@ class CourseGraderUpdatesTest(CourseTestCase):
"short_label": "yo momma", "short_label": "yo momma",
"weight": 17.3, "weight": 17.3,
} }
resp = self.client.post(self.url, grader) resp = self.client.ajax_post(self.url, grader)
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
obj = json.loads(resp.content) obj = json.loads(resp.content)
...@@ -22,7 +22,7 @@ class CourseUpdateTest(CourseTestCase): ...@@ -22,7 +22,7 @@ class CourseUpdateTest(CourseTestCase):
'course': self.course.location.course, 'course': self.course.location.course,
'provided_id': ''}) 'provided_id': ''})
resp = self.client.post(url, json.dumps(payload), "application/json") resp = self.client.ajax_post(url, payload)
return json.loads(resp.content) return json.loads(resp.content)
...@@ -66,7 +66,6 @@ class CourseUpdateTest(CourseTestCase): ...@@ -66,7 +66,6 @@ class CourseUpdateTest(CourseTestCase):
payload = json.loads(resp.content) payload = json.loads(resp.content)
self.assertTrue(len(payload) == 2) self.assertTrue(len(payload) == 2)
# can't test non-json paylod b/c expect_json throws error
# try json w/o required fields # try json w/o required fields
self.assertContains(self.client.post(url, json.dumps({'garbage': 1}), self.assertContains(self.client.post(url, json.dumps({'garbage': 1}),
"application/json"), "application/json"),
...@@ -86,7 +85,7 @@ class CourseUpdateTest(CourseTestCase): ...@@ -86,7 +85,7 @@ class CourseUpdateTest(CourseTestCase):
payload = {'content': content, payload = {'content': content,
'date': 'January 21, 2013'} 'date': 'January 21, 2013'}
self.assertContains( self.assertContains(
self.client.post(url, json.dumps(payload), "application/json"), self.client.ajax_post(url, payload),
'Failed to save', status_code=400) 'Failed to save', status_code=400)
# update w/ malformed html # update w/ malformed html
...@@ -98,7 +97,7 @@ class CourseUpdateTest(CourseTestCase): ...@@ -98,7 +97,7 @@ class CourseUpdateTest(CourseTestCase):
'provided_id': ''}) 'provided_id': ''})
self.assertContains( self.assertContains(
self.client.post(url, json.dumps(payload), "application/json"), self.client.ajax_post(url, payload),
'<garbage') '<garbage')
# set to valid html which would break an xml parser # set to valid html which would break an xml parser
...@@ -152,7 +151,7 @@ class CourseUpdateTest(CourseTestCase): ...@@ -152,7 +151,7 @@ class CourseUpdateTest(CourseTestCase):
'course': self.course.location.course, 'course': self.course.location.course,
'provided_id': ''}) 'provided_id': ''})
resp = self.client.post(url, json.dumps(payload), "application/json") resp = self.client.ajax_post(url, payload)
payload = json.loads(resp.content) payload = json.loads(resp.content)
......
...@@ -54,7 +54,7 @@ class Basetranscripts(CourseTestCase): ...@@ -54,7 +54,7 @@ class Basetranscripts(CourseTestCase):
'category': 'video', 'category': 'video',
'type': 'video' 'type': 'video'
} }
resp = self.client.post(reverse('create_item'), data) resp = self.client.ajax_post(reverse('create_item'), data)
self.item_location = json.loads(resp.content).get('id') self.item_location = json.loads(resp.content).get('id')
self.assertEqual(resp.status_code, 200) self.assertEqual(resp.status_code, 200)
...@@ -200,7 +200,7 @@ class TestUploadtranscripts(Basetranscripts): ...@@ -200,7 +200,7 @@ class TestUploadtranscripts(Basetranscripts):
'category': 'non_video', 'category': 'non_video',
'type': 'non_video' 'type': 'non_video'
} }
resp = self.client.post(reverse('create_item'), data) resp = self.client.ajax_post(reverse('create_item'), data)
item_location = json.loads(resp.content).get('id') item_location = json.loads(resp.content).get('id')
data = '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />' data = '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />'
modulestore().update_item(item_location, data) modulestore().update_item(item_location, data)
...@@ -411,7 +411,7 @@ class TestDownloadtranscripts(Basetranscripts): ...@@ -411,7 +411,7 @@ class TestDownloadtranscripts(Basetranscripts):
'category': 'videoalpha', 'category': 'videoalpha',
'type': 'videoalpha' 'type': 'videoalpha'
} }
resp = self.client.post(reverse('create_item'), data) resp = self.client.ajax_post(reverse('create_item'), data)
item_location = json.loads(resp.content).get('id') item_location = json.loads(resp.content).get('id')
subs_id = str(uuid4()) subs_id = str(uuid4())
data = textwrap.dedent(""" data = textwrap.dedent("""
...@@ -661,7 +661,7 @@ class TestChecktranscripts(Basetranscripts): ...@@ -661,7 +661,7 @@ class TestChecktranscripts(Basetranscripts):
'category': 'not_video', 'category': 'not_video',
'type': 'not_video' 'type': 'not_video'
} }
resp = self.client.post(reverse('create_item'), data) resp = self.client.ajax_post(reverse('create_item'), data)
item_location = json.loads(resp.content).get('id') item_location = json.loads(resp.content).get('id')
subs_id = str(uuid4()) subs_id = str(uuid4())
data = textwrap.dedent(""" data = textwrap.dedent("""
......
...@@ -29,6 +29,14 @@ def registration(email): ...@@ -29,6 +29,14 @@ def registration(email):
return Registration.objects.get(user__email=email) return Registration.objects.get(user__email=email)
class AjaxEnabledTestClient(Client):
def ajax_post(self, path, data=None, content_type="application/json", **kwargs):
if not isinstance(data, basestring):
data = json.dumps(data or {})
kwargs.setdefault("HTTP_X_REQUESTED_WITH", "XMLHttpRequest")
return self.post(path=path, data=data, content_type=content_type, **kwargs)
@override_settings(MODULESTORE=TEST_MODULESTORE) @override_settings(MODULESTORE=TEST_MODULESTORE)
class CourseTestCase(ModuleStoreTestCase): class CourseTestCase(ModuleStoreTestCase):
def setUp(self): def setUp(self):
...@@ -53,7 +61,7 @@ class CourseTestCase(ModuleStoreTestCase): ...@@ -53,7 +61,7 @@ class CourseTestCase(ModuleStoreTestCase):
self.user.is_staff = True self.user.is_staff = True
self.user.save() self.user.save()
self.client = Client() self.client = AjaxEnabledTestClient()
self.client.login(username=uname, password=password) self.client.login(username=uname, password=password)
self.course = CourseFactory.create( self.course = CourseFactory.create(
......
...@@ -323,7 +323,7 @@ def assignment_type_update(request, org, course, category, name): ...@@ -323,7 +323,7 @@ def assignment_type_update(request, org, course, category, name):
rsp = CourseGradingModel.get_section_grader_type(location) rsp = CourseGradingModel.get_section_grader_type(location)
elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. elif request.method in ('POST', 'PUT'): # post or put, doesn't matter.
rsp = CourseGradingModel.update_section_grader_type( rsp = CourseGradingModel.update_section_grader_type(
location, request.POST location, request.json
) )
return JsonResponse(rsp) return JsonResponse(rsp)
...@@ -332,7 +332,7 @@ def assignment_type_update(request, org, course, category, name): ...@@ -332,7 +332,7 @@ def assignment_type_update(request, org, course, category, name):
@expect_json @expect_json
def create_draft(request): def create_draft(request):
"Create a draft" "Create a draft"
location = request.POST['id'] location = request.json['id']
# check permissions for this user within this course # check permissions for this user within this course
if not has_access(request.user, location): if not has_access(request.user, location):
...@@ -351,7 +351,7 @@ def publish_draft(request): ...@@ -351,7 +351,7 @@ def publish_draft(request):
""" """
Publish a draft Publish a draft
""" """
location = request.POST['id'] location = request.json['id']
# check permissions for this user within this course # check permissions for this user within this course
if not has_access(request.user, location): if not has_access(request.user, location):
...@@ -370,7 +370,7 @@ def publish_draft(request): ...@@ -370,7 +370,7 @@ def publish_draft(request):
@expect_json @expect_json
def unpublish_unit(request): def unpublish_unit(request):
"Unpublish a unit" "Unpublish a unit"
location = request.POST['id'] location = request.json['id']
# check permissions for this user within this course # check permissions for this user within this course
if not has_access(request.user, location): if not has_access(request.user, location):
...@@ -413,6 +413,6 @@ def module_info(request, module_location): ...@@ -413,6 +413,6 @@ def module_info(request, module_location):
elif request.method in ("POST", "PUT"): elif request.method in ("POST", "PUT"):
rsp = set_module_info( rsp = set_module_info(
get_modulestore(location), get_modulestore(location),
location, request.POST location, request.json
) )
return JsonResponse(rsp) return JsonResponse(rsp)
...@@ -153,10 +153,10 @@ def create_new_course(request): ...@@ -153,10 +153,10 @@ def create_new_course(request):
if not is_user_in_creator_group(request.user): if not is_user_in_creator_group(request.user):
raise PermissionDenied() raise PermissionDenied()
org = request.POST.get('org') org = request.json.get('org')
number = request.POST.get('number') number = request.json.get('number')
display_name = request.POST.get('display_name') display_name = request.json.get('display_name')
run = request.POST.get('run') run = request.json.get('run')
try: try:
dest_location = Location('i4x', org, number, 'course', run) dest_location = Location('i4x', org, number, 'course', run)
...@@ -297,7 +297,7 @@ def course_info_updates(request, org, course, provided_id=None): ...@@ -297,7 +297,7 @@ def course_info_updates(request, org, course, provided_id=None):
return JsonResponse(get_course_updates(location)) return JsonResponse(get_course_updates(location))
elif request.method == 'DELETE': elif request.method == 'DELETE':
try: try:
return JsonResponse(delete_course_update(location, request.POST, provided_id)) return JsonResponse(delete_course_update(location, request.json, provided_id))
except: except:
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Failed to delete", "Failed to delete",
...@@ -306,7 +306,7 @@ def course_info_updates(request, org, course, provided_id=None): ...@@ -306,7 +306,7 @@ def course_info_updates(request, org, course, provided_id=None):
# can be either and sometimes django is rewriting one to the other: # can be either and sometimes django is rewriting one to the other:
elif request.method in ('POST', 'PUT'): elif request.method in ('POST', 'PUT'):
try: try:
return JsonResponse(update_course_updates(location, request.POST, provided_id)) return JsonResponse(update_course_updates(location, request.json, provided_id))
except: except:
return HttpResponseBadRequest( return HttpResponseBadRequest(
"Failed to save", "Failed to save",
...@@ -415,7 +415,7 @@ def course_settings_updates(request, org, course, name, section): ...@@ -415,7 +415,7 @@ def course_settings_updates(request, org, course, name, section):
) )
elif request.method in ('POST', 'PUT'): # post or put, doesn't matter. elif request.method in ('POST', 'PUT'): # post or put, doesn't matter.
return JsonResponse( return JsonResponse(
manager.update_from_json(request.POST), manager.update_from_json(request.json),
encoder=CourseSettingsEncoder encoder=CourseSettingsEncoder
) )
...@@ -447,14 +447,14 @@ def course_grader_updates(request, org, course, name, grader_index=None): ...@@ -447,14 +447,14 @@ def course_grader_updates(request, org, course, name, grader_index=None):
else: # post or put, doesn't matter. else: # post or put, doesn't matter.
return JsonResponse(CourseGradingModel.update_grader_from_json( return JsonResponse(CourseGradingModel.update_grader_from_json(
Location(location), Location(location),
request.POST request.json
)) ))
# # NB: expect_json failed on ["key", "key2"] and json payload
@require_http_methods(("GET", "POST", "PUT", "DELETE")) @require_http_methods(("GET", "POST", "PUT", "DELETE"))
@login_required @login_required
@ensure_csrf_cookie @ensure_csrf_cookie
@expect_json
def course_advanced_updates(request, org, course, name): def course_advanced_updates(request, org, course, name):
""" """
Restful CRUD operations on metadata. The payload is a json rep of the Restful CRUD operations on metadata. The payload is a json rep of the
...@@ -473,10 +473,6 @@ def course_advanced_updates(request, org, course, name): ...@@ -473,10 +473,6 @@ def course_advanced_updates(request, org, course, name):
json.loads(request.body) json.loads(request.body)
)) ))
else: else:
# NOTE: request.POST is messed up because expect_json
# cloned_request.POST.copy() is creating a defective entry w/ the whole
# payload as the key
request_body = json.loads(request.body)
# Whether or not to filter the tabs key out of the settings metadata # Whether or not to filter the tabs key out of the settings metadata
filter_tabs = True filter_tabs = True
...@@ -489,7 +485,7 @@ def course_advanced_updates(request, org, course, name): ...@@ -489,7 +485,7 @@ def course_advanced_updates(request, org, course, name):
# the user has indicated that they want the notes module enabled in # the user has indicated that they want the notes module enabled in
# their course # their course
# TODO refactor the above into distinct advanced policy settings # TODO refactor the above into distinct advanced policy settings
if ADVANCED_COMPONENT_POLICY_KEY in request_body: if ADVANCED_COMPONENT_POLICY_KEY in request.json:
# Get the course so that we can scrape current tabs # Get the course so that we can scrape current tabs
course_module = modulestore().get_item(location) course_module = modulestore().get_item(location)
...@@ -505,7 +501,7 @@ def course_advanced_updates(request, org, course, name): ...@@ -505,7 +501,7 @@ def course_advanced_updates(request, org, course, name):
component_types = tab_component_map.get(tab_type) component_types = tab_component_map.get(tab_type)
found_ac_type = False found_ac_type = False
for ac_type in component_types: for ac_type in component_types:
if ac_type in request_body[ADVANCED_COMPONENT_POLICY_KEY]: if ac_type in request.json[ADVANCED_COMPONENT_POLICY_KEY]:
# Add tab to the course if needed # Add tab to the course if needed
changed, new_tabs = add_extra_panel_tab( changed, new_tabs = add_extra_panel_tab(
tab_type, tab_type,
...@@ -515,7 +511,7 @@ def course_advanced_updates(request, org, course, name): ...@@ -515,7 +511,7 @@ def course_advanced_updates(request, org, course, name):
# metadata along to CourseMetadata.update_from_json # metadata along to CourseMetadata.update_from_json
if changed: if changed:
course_module.tabs = new_tabs course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs}) request.json.update({'tabs': new_tabs})
# Indicate that tabs should not be filtered out of # Indicate that tabs should not be filtered out of
# the metadata # the metadata
filter_tabs = False filter_tabs = False
...@@ -531,14 +527,14 @@ def course_advanced_updates(request, org, course, name): ...@@ -531,14 +527,14 @@ def course_advanced_updates(request, org, course, name):
) )
if changed: if changed:
course_module.tabs = new_tabs course_module.tabs = new_tabs
request_body.update({'tabs': new_tabs}) request.json.update({'tabs': new_tabs})
# Indicate that tabs should *not* be filtered out of # Indicate that tabs should *not* be filtered out of
# the metadata # the metadata
filter_tabs = False filter_tabs = False
try: try:
return JsonResponse(CourseMetadata.update_from_json( return JsonResponse(CourseMetadata.update_from_json(
location, location,
request_body, request.json,
filter_tabs=filter_tabs filter_tabs=filter_tabs
)) ))
except (TypeError, ValueError) as err: except (TypeError, ValueError) as err:
......
...@@ -47,7 +47,7 @@ def save_item(request): ...@@ -47,7 +47,7 @@ def save_item(request):
# little smarter and able to pass something more akin to {unset: [field, field]} # little smarter and able to pass something more akin to {unset: [field, field]}
try: try:
item_location = request.POST['id'] item_location = request.json['id']
except KeyError: except KeyError:
import inspect import inspect
...@@ -76,30 +76,27 @@ def save_item(request): ...@@ -76,30 +76,27 @@ def save_item(request):
store = get_modulestore(Location(item_location)) store = get_modulestore(Location(item_location))
if request.POST.get('data') is not None: if request.json.get('data'):
data = request.POST['data'] data = request.json['data']
store.update_item(item_location, data) store.update_item(item_location, data)
# cdodge: note calling request.POST.get('children') will return None if children is an empty array if request.json.get('children') is not None:
# so it lead to a bug whereby the last component to be deleted in the UI was not actually children = request.json['children']
# deleting the children object from the children collection
if 'children' in request.POST and request.POST['children'] is not None:
children = request.POST['children']
store.update_children(item_location, children) store.update_children(item_location, children)
# cdodge: also commit any metadata which might have been passed along # cdodge: also commit any metadata which might have been passed along
if request.POST.get('nullout') is not None or request.POST.get('metadata') is not None: if request.json.get('nullout') is not None or request.json.get('metadata') is not None:
# the postback is not the complete metadata, as there's system metadata which is # the postback is not the complete metadata, as there's system metadata which is
# not presented to the end-user for editing. So let's fetch the original and # not presented to the end-user for editing. So let's fetch the original and
# 'apply' the submitted metadata, so we don't end up deleting system metadata # 'apply' the submitted metadata, so we don't end up deleting system metadata
existing_item = modulestore().get_item(item_location) existing_item = modulestore().get_item(item_location)
for metadata_key in request.POST.get('nullout', []): for metadata_key in request.json.get('nullout', []):
setattr(existing_item, metadata_key, None) setattr(existing_item, metadata_key, None)
# update existing metadata with submitted metadata (which can be partial) # update existing metadata with submitted metadata (which can be partial)
# IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If # IMPORTANT NOTE: if the client passed 'null' (None) for a piece of metadata that means 'remove it'. If
# the intent is to make it None, use the nullout field # the intent is to make it None, use the nullout field
for metadata_key, value in request.POST.get('metadata', {}).items(): for metadata_key, value in request.json.get('metadata', {}).items():
field = existing_item.fields[metadata_key] field = existing_item.fields[metadata_key]
if value is None: if value is None:
...@@ -126,10 +123,10 @@ def save_item(request): ...@@ -126,10 +123,10 @@ def save_item(request):
@expect_json @expect_json
def create_item(request): def create_item(request):
"""View for create items.""" """View for create items."""
parent_location = Location(request.POST['parent_location']) parent_location = Location(request.json['parent_location'])
category = request.POST['category'] category = request.json['category']
display_name = request.POST.get('display_name') display_name = request.json.get('display_name')
if not has_access(request.user, parent_location): if not has_access(request.user, parent_location):
raise PermissionDenied() raise PermissionDenied()
...@@ -140,7 +137,7 @@ def create_item(request): ...@@ -140,7 +137,7 @@ def create_item(request):
# get the metadata, display_name, and definition from the request # get the metadata, display_name, and definition from the request
metadata = {} metadata = {}
data = None data = None
template_id = request.POST.get('boilerplate') template_id = request.json.get('boilerplate')
if template_id is not None: if template_id is not None:
clz = XModuleDescriptor.load_class(category) clz = XModuleDescriptor.load_class(category)
if clz is not None: if clz is not None:
...@@ -169,7 +166,7 @@ def create_item(request): ...@@ -169,7 +166,7 @@ def create_item(request):
@expect_json @expect_json
def delete_item(request): def delete_item(request):
"""View for removing items.""" """View for removing items."""
item_location = request.POST['id'] item_location = request.json['id']
item_location = Location(item_location) item_location = Location(item_location)
# check permissions for this user within this course # check permissions for this user within this course
...@@ -177,8 +174,8 @@ def delete_item(request): ...@@ -177,8 +174,8 @@ def delete_item(request):
raise PermissionDenied() raise PermissionDenied()
# optional parameter to delete all children (default False) # optional parameter to delete all children (default False)
delete_children = request.POST.get('delete_children', False) delete_children = request.json.get('delete_children', False)
delete_all_versions = request.POST.get('delete_all_versions', False) delete_all_versions = request.json.get('delete_all_versions', False)
store = get_modulestore(item_location) store = get_modulestore(item_location)
......
...@@ -46,7 +46,7 @@ def initialize_course_tabs(course): ...@@ -46,7 +46,7 @@ def initialize_course_tabs(course):
@expect_json @expect_json
def reorder_static_tabs(request): def reorder_static_tabs(request):
"Order the static tabs in the requested order" "Order the static tabs in the requested order"
tabs = request.POST['tabs'] tabs = request.json['tabs']
course = get_course_for_item(tabs[0]) course = get_course_for_item(tabs[0])
if not has_access(request.user, course.location): if not has_access(request.user, course.location):
......
...@@ -35,6 +35,19 @@ define ["domReady", "jquery", "underscore.string", "backbone", "gettext", ...@@ -35,6 +35,19 @@ define ["domReady", "jquery", "underscore.string", "backbone", "gettext",
) )
msg.show() msg.show()
$.postJSON = (url, data, callback) ->
# shift arguments if data argument was omitted
if $.isFunction(data)
callback = data
data = `undefined`
$.ajax
url: url
type: "POST"
contentType: "application/json; charset=utf-8"
dataType: "json"
data: JSON.stringify(data)
success: callback
if onTouchBasedDevice() if onTouchBasedDevice()
$('body').addClass 'touch-based-device' $('body').addClass 'touch-based-device'
......
...@@ -64,7 +64,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1", ...@@ -64,7 +64,7 @@ define ["backbone", "jquery", "underscore", "gettext", "xblock/runtime.v1",
createItem: (parent, payload) -> createItem: (parent, payload) ->
payload.parent_location = parent payload.parent_location = parent
$.post( $.postJSON(
"/create_item" "/create_item"
payload payload
(data) => (data) =>
......
...@@ -82,7 +82,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views ...@@ -82,7 +82,7 @@ define ["jquery", "jquery.ui", "backbone", "js/views/feedback_prompt", "js/views
deleting = new NotificationView.Mini deleting = new NotificationView.Mini
title: gettext('Deleting&hellip;') title: gettext('Deleting&hellip;')
deleting.show() deleting.show()
$.post('/delete_item', { $.postJSON('/delete_item', {
id: $component.data('id') id: $component.data('id')
}, => }, =>
$component.remove() $component.remove()
......
...@@ -134,7 +134,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -134,7 +134,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
title: gettext('Deleting&hellip;'), title: gettext('Deleting&hellip;'),
deleting.show() deleting.show()
$component = $(event.currentTarget).parents('.component') $component = $(event.currentTarget).parents('.component')
$.post('/delete_item', { $.postJSON('/delete_item', {
id: $component.data('id') id: $component.data('id')
}, => }, =>
deleting.hide() deleting.hide()
...@@ -163,7 +163,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -163,7 +163,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
deleteDraft: (event) -> deleteDraft: (event) ->
@wait(true) @wait(true)
$.post('/delete_item', { $.postJSON('/delete_item', {
id: @$el.data('id') id: @$el.data('id')
delete_children: true delete_children: true
}, => }, =>
...@@ -177,7 +177,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -177,7 +177,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
createDraft: (event) -> createDraft: (event) ->
@wait(true) @wait(true)
$.post('/create_draft', { $.postJSON('/create_draft', {
id: @$el.data('id') id: @$el.data('id')
}, => }, =>
analytics.track "Created Draft", analytics.track "Created Draft",
...@@ -191,7 +191,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -191,7 +191,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@wait(true) @wait(true)
@saveDraft() @saveDraft()
$.post('/publish_draft', { $.postJSON('/publish_draft', {
id: @$el.data('id') id: @$el.data('id')
}, => }, =>
analytics.track "Published Draft", analytics.track "Published Draft",
...@@ -211,7 +211,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone", ...@@ -211,7 +211,7 @@ define ["jquery", "jquery.ui", "gettext", "backbone",
@wait(true) @wait(true)
$.post(target_url, { $.postJSON(target_url, {
id: @$el.data('id') id: @$el.data('id')
}, => }, =>
analytics.track "Set Unit Visibility", analytics.track "Set Unit Visibility",
......
...@@ -230,7 +230,7 @@ function createNewUnit(e) { ...@@ -230,7 +230,7 @@ function createNewUnit(e) {
}); });
$.post('/create_item', { $.postJSON('/create_item', {
'parent_location': parent, 'parent_location': parent,
'category': category, 'category': category,
'display_name': 'New Unit' 'display_name': 'New Unit'
...@@ -279,7 +279,7 @@ function _deleteItem($el, type) { ...@@ -279,7 +279,7 @@ function _deleteItem($el, type) {
}); });
deleting.show(); deleting.show();
$.post('/delete_item', $.postJSON('/delete_item',
{'id': id, {'id': id,
'delete_children': true, 'delete_children': true,
'delete_all_versions': true}, 'delete_all_versions': true},
......
...@@ -32,7 +32,7 @@ require(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"], ...@@ -32,7 +32,7 @@ require(["domReady", "jquery", "underscore", "js/utils/cancel_on_escape"],
'run': run 'run': run
}); });
$.post('/create_new_course', { $.postJSON('/create_new_course', {
'org': org, 'org': org,
'number': number, 'number': number,
'display_name': display_name, 'display_name': display_name,
......
...@@ -132,7 +132,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe ...@@ -132,7 +132,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
'display_name': display_name 'display_name': display_name
}); });
$.post('/create_item', { $.postJSON('/create_item', {
'parent_location': parent, 'parent_location': parent,
'category': category, 'category': category,
'display_name': display_name 'display_name': display_name
...@@ -182,7 +182,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe ...@@ -182,7 +182,7 @@ define(["domReady", "jquery", "jquery.ui", "underscore", "gettext", "js/views/fe
}); });
$.post('/create_item', { $.postJSON('/create_item', {
'parent_location': parent, 'parent_location': parent,
'category': category, 'category': category,
'display_name': display_name 'display_name': display_name
......
...@@ -14,17 +14,17 @@ def expect_json(view_function): ...@@ -14,17 +14,17 @@ def expect_json(view_function):
request.POST with the contents. request.POST with the contents.
""" """
@wraps(view_function) @wraps(view_function)
def expect_json_with_cloned_request(request, *args, **kwargs): def parse_json_into_request(request, *args, **kwargs):
# cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information # cdodge: fix postback errors in CMS. The POST 'content-type' header can include additional information
# e.g. 'charset', so we can't do a direct string compare # e.g. 'charset', so we can't do a direct string compare
if request.META.get('CONTENT_TYPE', '').lower().startswith("application/json"): if "application/json" in request.META.get('CONTENT_TYPE', ''):
cloned_request = copy.copy(request) request.json = json.loads(request.body)
cloned_request.POST = json.loads(request.body)
return view_function(cloned_request, *args, **kwargs)
else: else:
return view_function(request, *args, **kwargs) request.json = {}
return expect_json_with_cloned_request return view_function(request, *args, **kwargs)
return parse_json_into_request
class JsonResponse(HttpResponse): class JsonResponse(HttpResponse):
......
...@@ -323,7 +323,6 @@ def _get_next(course_id, grader_id, location): ...@@ -323,7 +323,6 @@ def _get_next(course_id, grader_id, location):
'error': STAFF_ERROR_MESSAGE}) 'error': STAFF_ERROR_MESSAGE})
@expect_json
def save_grade(request, course_id): def save_grade(request, course_id):
""" """
Save the grade and feedback for a submission, and, if all goes well, return Save the grade and feedback for a submission, and, if all goes well, return
......
...@@ -274,8 +274,6 @@ if settings.COURSEWARE_ENABLED: ...@@ -274,8 +274,6 @@ if settings.COURSEWARE_ENABLED:
'open_ended_grading.staff_grading_service.get_next', name='staff_grading_get_next'), 'open_ended_grading.staff_grading_service.get_next', name='staff_grading_get_next'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$',
'open_ended_grading.staff_grading_service.save_grade', name='staff_grading_save_grade'), 'open_ended_grading.staff_grading_service.save_grade', name='staff_grading_save_grade'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/save_grade$',
'open_ended_grading.staff_grading_service.save_grade', name='staff_grading_save_grade'),
url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_problem_list$', url(r'^courses/(?P<course_id>[^/]+/[^/]+/[^/]+)/staff_grading/get_problem_list$',
'open_ended_grading.staff_grading_service.get_problem_list', name='staff_grading_get_problem_list'), 'open_ended_grading.staff_grading_service.get_problem_list', name='staff_grading_get_problem_list'),
......
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