Merge pull request #1508 from edx/dhm/expect_json

Change expect_json to put parsed json in new attr
This commit is contained in:
Don Mitchell
2013-10-29 14:02:32 -07:00
21 changed files with 126 additions and 113 deletions

View File

@@ -105,7 +105,7 @@ class ChecklistTestCase(CourseTestCase):
self.assertEqual('CourseOutline', get_first_item(payload).get('action_url'))
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'))
persisted_checklist = self.get_persisted_checklists()[1]
# Verify that persisted checklist does not have expanded action URLs.

View File

@@ -6,7 +6,6 @@ import mock
from textwrap import dedent
from django.test.client import Client
from django.test.utils import override_settings
from django.conf import settings
from django.core.urlresolvers import reverse
@@ -20,7 +19,7 @@ from datetime import timedelta
from django.contrib.auth.models import User
from django.dispatch import Signal
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
@@ -98,7 +97,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
# Save the data that we've just changed to the db.
self.user.save()
self.client = Client()
self.client = AjaxEnabledTestClient()
self.client.login(username=uname, password=password)
def tearDown(self):
@@ -420,7 +419,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
if tab['type'] == 'static_tab':
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]))
@@ -755,7 +754,7 @@ class ContentStoreToyCourseTest(ModuleStoreTestCase):
expected_children = []
for child_loc_url in source_item.children:
child_loc = Location(child_loc_url)
child_loc = child_loc._replace(
child_loc = child_loc.replace(
tag=dest_location.tag,
org=dest_location.org,
course=dest_location.course
@@ -1333,7 +1332,7 @@ class ContentStoreTest(ModuleStoreTestCase):
self.user.is_staff = True
self.user.save()
self.client = Client()
self.client = AjaxEnabledTestClient()
self.client.login(username=uname, password=password)
self.course_data = {
@@ -1344,8 +1343,7 @@ class ContentStoreTest(ModuleStoreTestCase):
}
def tearDown(self):
mongo = MongoClient()
mongo.drop_database(TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'])
MongoClient().drop_database(TEST_DATA_CONTENTSTORE['DOC_STORE_CONFIG']['db'])
_CONTENTSTORE.clear()
def test_create_course(self):
@@ -1394,7 +1392,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_create_course_duplicate_course(self):
"""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.')
def assert_course_creation_failed(self, error_message):
@@ -1403,7 +1401,7 @@ class ContentStoreTest(ModuleStoreTestCase):
"""
course_id = _get_course_id(self.course_data)
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)
data = parse_json(resp)
self.assertEqual(data['ErrMsg'], error_message)
@@ -1413,7 +1411,7 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_create_course_duplicate_number(self):
"""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['run'] = '2013_Summer'
@@ -1422,13 +1420,13 @@ class ContentStoreTest(ModuleStoreTestCase):
def test_create_course_case_change(self):
"""Test new course creation - error path due to case insensitive name equality"""
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']
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.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']
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.')
@@ -1437,14 +1435,14 @@ class ContentStoreTest(ModuleStoreTestCase):
"""
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']
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.course_data['number'] = cache_current
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)
def test_create_course_with_bad_organization(self):
@@ -1487,7 +1485,7 @@ class ContentStoreTest(ModuleStoreTestCase):
"""
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)
def test_course_index_view_with_no_courses(self):
@@ -1546,7 +1544,7 @@ class ContentStoreTest(ModuleStoreTestCase):
'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)
data = parse_json(resp)
@@ -1564,7 +1562,7 @@ class ContentStoreTest(ModuleStoreTestCase):
'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)
payload = parse_json(resp)
@@ -1929,7 +1927,7 @@ def _create_course(test, 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)
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)
data = parse_json(response)
test.assertNotIn('ErrMsg', data)

View File

@@ -176,7 +176,7 @@ class CourseDetailsViewTest(CourseTestCase):
payload['end_date'] = CourseDetailsViewTest.convert_datetime_to_iso(details.end_date)
payload['enrollment_start'] = CourseDetailsViewTest.convert_datetime_to_iso(details.enrollment_start)
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))
@staticmethod
@@ -462,6 +462,6 @@ class CourseGraderUpdatesTest(CourseTestCase):
"short_label": "yo momma",
"weight": 17.3,
}
resp = self.client.post(self.url, grader)
resp = self.client.ajax_post(self.url, grader)
self.assertEqual(resp.status_code, 200)
obj = json.loads(resp.content)

View File

@@ -22,7 +22,7 @@ class CourseUpdateTest(CourseTestCase):
'course': self.course.location.course,
'provided_id': ''})
resp = self.client.post(url, json.dumps(payload), "application/json")
resp = self.client.ajax_post(url, payload)
return json.loads(resp.content)
@@ -66,7 +66,6 @@ class CourseUpdateTest(CourseTestCase):
payload = json.loads(resp.content)
self.assertTrue(len(payload) == 2)
# can't test non-json paylod b/c expect_json throws error
# try json w/o required fields
self.assertContains(self.client.post(url, json.dumps({'garbage': 1}),
"application/json"),
@@ -86,7 +85,7 @@ class CourseUpdateTest(CourseTestCase):
payload = {'content': content,
'date': 'January 21, 2013'}
self.assertContains(
self.client.post(url, json.dumps(payload), "application/json"),
self.client.ajax_post(url, payload),
'Failed to save', status_code=400)
# update w/ malformed html
@@ -98,7 +97,7 @@ class CourseUpdateTest(CourseTestCase):
'provided_id': ''})
self.assertContains(
self.client.post(url, json.dumps(payload), "application/json"),
self.client.ajax_post(url, payload),
'<garbage')
# set to valid html which would break an xml parser
@@ -152,7 +151,7 @@ class CourseUpdateTest(CourseTestCase):
'course': self.course.location.course,
'provided_id': ''})
resp = self.client.post(url, json.dumps(payload), "application/json")
resp = self.client.ajax_post(url, payload)
payload = json.loads(resp.content)

View File

@@ -54,7 +54,7 @@ class Basetranscripts(CourseTestCase):
'category': '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.assertEqual(resp.status_code, 200)
@@ -200,7 +200,7 @@ class TestUploadtranscripts(Basetranscripts):
'category': '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')
data = '<non_video youtube="0.75:JMD_ifUUfsU,1.0:hI10vDNYz4M" />'
modulestore().update_item(item_location, data)
@@ -411,7 +411,7 @@ class TestDownloadtranscripts(Basetranscripts):
'category': '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')
subs_id = str(uuid4())
data = textwrap.dedent("""
@@ -661,7 +661,7 @@ class TestChecktranscripts(Basetranscripts):
'category': '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')
subs_id = str(uuid4())
data = textwrap.dedent("""

View File

@@ -29,6 +29,14 @@ def registration(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)
class CourseTestCase(ModuleStoreTestCase):
def setUp(self):
@@ -53,7 +61,7 @@ class CourseTestCase(ModuleStoreTestCase):
self.user.is_staff = True
self.user.save()
self.client = Client()
self.client = AjaxEnabledTestClient()
self.client.login(username=uname, password=password)
self.course = CourseFactory.create(

View File

@@ -323,7 +323,7 @@ def assignment_type_update(request, org, course, category, name):
rsp = CourseGradingModel.get_section_grader_type(location)
elif request.method in ('POST', 'PUT'): # post or put, doesn't matter.
rsp = CourseGradingModel.update_section_grader_type(
location, request.POST
location, request.json
)
return JsonResponse(rsp)
@@ -332,7 +332,7 @@ def assignment_type_update(request, org, course, category, name):
@expect_json
def create_draft(request):
"Create a draft"
location = request.POST['id']
location = request.json['id']
# check permissions for this user within this course
if not has_access(request.user, location):
@@ -351,7 +351,7 @@ def publish_draft(request):
"""
Publish a draft
"""
location = request.POST['id']
location = request.json['id']
# check permissions for this user within this course
if not has_access(request.user, location):
@@ -370,7 +370,7 @@ def publish_draft(request):
@expect_json
def unpublish_unit(request):
"Unpublish a unit"
location = request.POST['id']
location = request.json['id']
# check permissions for this user within this course
if not has_access(request.user, location):
@@ -413,6 +413,6 @@ def module_info(request, module_location):
elif request.method in ("POST", "PUT"):
rsp = set_module_info(
get_modulestore(location),
location, request.POST
location, request.json
)
return JsonResponse(rsp)

View File

@@ -153,10 +153,10 @@ def create_new_course(request):
if not is_user_in_creator_group(request.user):
raise PermissionDenied()
org = request.POST.get('org')
number = request.POST.get('number')
display_name = request.POST.get('display_name')
run = request.POST.get('run')
org = request.json.get('org')
number = request.json.get('number')
display_name = request.json.get('display_name')
run = request.json.get('run')
try:
dest_location = Location('i4x', org, number, 'course', run)
@@ -297,7 +297,7 @@ def course_info_updates(request, org, course, provided_id=None):
return JsonResponse(get_course_updates(location))
elif request.method == 'DELETE':
try:
return JsonResponse(delete_course_update(location, request.POST, provided_id))
return JsonResponse(delete_course_update(location, request.json, provided_id))
except:
return HttpResponseBadRequest(
"Failed to delete",
@@ -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:
elif request.method in ('POST', 'PUT'):
try:
return JsonResponse(update_course_updates(location, request.POST, provided_id))
return JsonResponse(update_course_updates(location, request.json, provided_id))
except:
return HttpResponseBadRequest(
"Failed to save",
@@ -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.
return JsonResponse(
manager.update_from_json(request.POST),
manager.update_from_json(request.json),
encoder=CourseSettingsEncoder
)
@@ -447,14 +447,14 @@ def course_grader_updates(request, org, course, name, grader_index=None):
else: # post or put, doesn't matter.
return JsonResponse(CourseGradingModel.update_grader_from_json(
Location(location),
request.POST
request.json
))
# # NB: expect_json failed on ["key", "key2"] and json payload
@require_http_methods(("GET", "POST", "PUT", "DELETE"))
@login_required
@ensure_csrf_cookie
@expect_json
def course_advanced_updates(request, org, course, name):
"""
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):
json.loads(request.body)
))
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
filter_tabs = True
@@ -489,7 +485,7 @@ def course_advanced_updates(request, org, course, name):
# the user has indicated that they want the notes module enabled in
# their course
# 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
course_module = modulestore().get_item(location)
@@ -505,7 +501,7 @@ def course_advanced_updates(request, org, course, name):
component_types = tab_component_map.get(tab_type)
found_ac_type = False
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
changed, new_tabs = add_extra_panel_tab(
tab_type,
@@ -515,7 +511,7 @@ def course_advanced_updates(request, org, course, name):
# metadata along to CourseMetadata.update_from_json
if changed:
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
# the metadata
filter_tabs = False
@@ -531,14 +527,14 @@ def course_advanced_updates(request, org, course, name):
)
if changed:
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
# the metadata
filter_tabs = False
try:
return JsonResponse(CourseMetadata.update_from_json(
location,
request_body,
request.json,
filter_tabs=filter_tabs
))
except (TypeError, ValueError) as err:

View File

@@ -47,7 +47,7 @@ def save_item(request):
# little smarter and able to pass something more akin to {unset: [field, field]}
try:
item_location = request.POST['id']
item_location = request.json['id']
except KeyError:
import inspect
@@ -76,30 +76,27 @@ def save_item(request):
store = get_modulestore(Location(item_location))
if request.POST.get('data') is not None:
data = request.POST['data']
if request.json.get('data'):
data = request.json['data']
store.update_item(item_location, data)
# cdodge: note calling request.POST.get('children') will return None if children is an empty array
# so it lead to a bug whereby the last component to be deleted in the UI was not actually
# deleting the children object from the children collection
if 'children' in request.POST and request.POST['children'] is not None:
children = request.POST['children']
if request.json.get('children') is not None:
children = request.json['children']
store.update_children(item_location, children)
# 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
# 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
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)
# 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
# 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]
if value is None:
@@ -126,10 +123,10 @@ def save_item(request):
@expect_json
def create_item(request):
"""View for create items."""
parent_location = Location(request.POST['parent_location'])
category = request.POST['category']
parent_location = Location(request.json['parent_location'])
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):
raise PermissionDenied()
@@ -140,7 +137,7 @@ def create_item(request):
# get the metadata, display_name, and definition from the request
metadata = {}
data = None
template_id = request.POST.get('boilerplate')
template_id = request.json.get('boilerplate')
if template_id is not None:
clz = XModuleDescriptor.load_class(category)
if clz is not None:
@@ -169,7 +166,7 @@ def create_item(request):
@expect_json
def delete_item(request):
"""View for removing items."""
item_location = request.POST['id']
item_location = request.json['id']
item_location = Location(item_location)
# check permissions for this user within this course
@@ -177,8 +174,8 @@ def delete_item(request):
raise PermissionDenied()
# optional parameter to delete all children (default False)
delete_children = request.POST.get('delete_children', False)
delete_all_versions = request.POST.get('delete_all_versions', False)
delete_children = request.json.get('delete_children', False)
delete_all_versions = request.json.get('delete_all_versions', False)
store = get_modulestore(item_location)

View File

@@ -46,7 +46,7 @@ def initialize_course_tabs(course):
@expect_json
def reorder_static_tabs(request):
"Order the static tabs in the requested order"
tabs = request.POST['tabs']
tabs = request.json['tabs']
course = get_course_for_item(tabs[0])
if not has_access(request.user, course.location):