From d54f79f5bf3e1af17063937df1abc0026843412d Mon Sep 17 00:00:00 2001 From: Robert Raposa Date: Mon, 6 Jun 2016 09:41:02 -0400 Subject: [PATCH] Switch dashboard from GET to POST. --- lms/djangoapps/instructor/tests/test_api.py | 389 ++++++++++++------ lms/djangoapps/instructor/views/api.py | 168 ++++---- .../instructor_dashboard/data_download.coffee | 8 + .../instructor_dashboard/extensions.coffee | 4 + .../instructor_dashboard/membership.coffee | 4 + .../instructor_dashboard/send_email.coffee | 2 + .../instructor_dashboard/student_admin.coffee | 12 + .../src/instructor_dashboard/util.coffee | 2 + .../js/instructor_dashboard/ecommerce.js | 2 + .../student_admin_spec.js | 34 +- .../js/spec/staff_debug_actions_spec.js | 6 +- lms/static/js/staff_debug_actions.js | 2 +- 12 files changed, 399 insertions(+), 234 deletions(-) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index 2e7d864e2b..35c90402c9 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -17,7 +17,7 @@ from django.conf import settings from django.contrib.auth.models import User from django.core import mail from django.core.files.uploadedfile import SimpleUploadedFile -from django.core.urlresolvers import reverse +from django.core.urlresolvers import reverse as django_reverse from django.http import HttpRequest, HttpResponse from django.test import RequestFactory, TestCase from django.test.utils import override_settings @@ -34,7 +34,9 @@ from xmodule.modulestore import ModuleStoreEnum from bulk_email.models import BulkEmailFlag from course_modes.models import CourseMode from courseware.models import StudentModule -from courseware.tests.factories import StaffFactory, InstructorFactory, BetaTesterFactory, UserProfileFactory +from courseware.tests.factories import ( + BetaTesterFactory, GlobalStaffFactory, InstructorFactory, StaffFactory, UserProfileFactory +) from courseware.tests.helpers import LoginEnrollmentTestCase from django_comment_common.models import FORUM_ROLE_COMMUNITY_TA from django_comment_common.utils import seed_permissions_roles @@ -131,6 +133,82 @@ EXECUTIVE_SUMMARY_DATA = ( ) +INSTRUCTOR_GET_ENDPOINTS = set([ + 'get_anon_ids', + 'get_coupon_codes', + 'get_issued_certificates', + 'get_sale_order_records', + 'get_sale_records', +]) +INSTRUCTOR_POST_ENDPOINTS = set([ + 'active_registration_codes', + 'add_users_to_cohorts', + 'bulk_beta_modify_access', + 'calculate_grades_csv', + 'change_due_date', + 'export_ora2_data', + 'generate_registration_codes', + 'get_enrollment_report', + 'get_exec_summary_report', + 'get_grading_config', + 'get_problem_responses', + 'get_proctored_exam_results', + 'get_registration_codes', + 'get_student_progress_url', + 'get_students_features', + 'get_students_who_may_enroll', + 'get_user_invoice_preference', + 'list_background_email_tasks', + 'list_course_role_members', + 'list_email_content', + 'list_entrance_exam_instructor_tasks', + 'list_financial_report_downloads', + 'list_forum_members', + 'list_instructor_tasks', + 'list_report_downloads', + 'mark_student_can_skip_entrance_exam', + 'modify_access', + 'register_and_enroll_students', + 'rescore_entrance_exam', + 'rescore_problem', + 'reset_due_date', + 'reset_student_attempts', + 'reset_student_attempts_for_entrance_exam', + 'sale_validation', + 'show_student_extensions', + 'show_unit_extensions', + 'send_email', + 'spent_registration_codes', + 'students_update_enrollment', + 'update_forum_role_membership', +]) + + +def reverse(endpoint, args=None, kwargs=None, is_dashboard_endpoint=True): + """ + Simple wrapper of Django's reverse that first ensures that we have declared + each endpoint under test. + + Arguments: + args: The args to be passed through to reverse. + endpoint: The endpoint to be passed through to reverse. + kwargs: The kwargs to be passed through to reverse. + is_dashboard_endpoint: True if this is an instructor dashboard endpoint + that must be declared in the INSTRUCTOR_GET_ENDPOINTS or + INSTRUCTOR_GET_ENDPOINTS sets, or false otherwise. + + Returns: + The return of Django's reverse function + + """ + is_endpoint_declared = endpoint in INSTRUCTOR_GET_ENDPOINTS or endpoint in INSTRUCTOR_POST_ENDPOINTS + if is_dashboard_endpoint and is_endpoint_declared is False: + # Verify that all endpoints are declared so we can ensure they are + # properly validated elsewhere. + raise ValueError("The endpoint {} must be declared in ENDPOINTS before use.".format(endpoint)) + return django_reverse(endpoint, args=args, kwargs=kwargs) + + @common_exceptions_400 def view_success(request): # pylint: disable=unused-argument "A dummy view for testing that returns a simple HTTP response" @@ -191,6 +269,61 @@ class TestCommonExceptions400(TestCase): self.assertIn("Task is already running", result["error"]) +@attr('shard_1') +@ddt.ddt +class TestEndpointHttpMethods(SharedModuleStoreTestCase, LoginEnrollmentTestCase): + """ + Ensure that users can make GET requests against endpoints that allow GET, + and not against those that don't allow GET. + """ + + @classmethod + def setUpClass(cls): + """ + Set up test course. + """ + super(TestEndpointHttpMethods, cls).setUpClass() + cls.course = CourseFactory.create() + + def setUp(self): + """ + Set up global staff role so authorization will not fail. + """ + super(TestEndpointHttpMethods, self).setUp() + global_user = GlobalStaffFactory() + self.client.login(username=global_user.username, password='test') + + @ddt.data(*INSTRUCTOR_POST_ENDPOINTS) + def test_endpoints_reject_get(self, data): + """ + Tests that POST endpoints are rejected with 405 when using GET. + """ + url = reverse(data, kwargs={'course_id': unicode(self.course.id)}) + response = self.client.get(url) + + self.assertEqual( + response.status_code, 405, + "Endpoint {} returned status code {} instead of a 405. It should not allow GET.".format( + data, response.status_code + ) + ) + + @ddt.data(*INSTRUCTOR_GET_ENDPOINTS) + def test_endpoints_accept_get(self, data): + """ + Tests that GET endpoints are not rejected with 405 when using GET. + """ + url = reverse(data, kwargs={'course_id': unicode(self.course.id)}) + response = self.client.get(url) + + self.assertNotEqual( + response.status_code, 405, + "Endpoint {} returned status code 405 where it shouldn't, since it should allow GET.".format( + data + ) + ) + + @attr('shard_1') @patch('bulk_email.models.html_to_text', Mock(return_value='Mocking CourseEmail.text_message', autospec=True)) class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTestCase): @@ -271,10 +404,10 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest msg: message to display if assertion fails. """ url = reverse(endpoint, kwargs={'course_id': self.course.id.to_deprecated_string()}) - if endpoint in ['send_email', 'students_update_enrollment', 'bulk_beta_modify_access']: - response = self.client.post(url, args) - else: + if endpoint in INSTRUCTOR_GET_ENDPOINTS: response = self.client.get(url, args) + else: + response = self.client.post(url, args) self.assertEqual( response.status_code, status_code, @@ -1919,13 +2052,13 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_noparams(self): """ Test missing all query parameters. """ url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url) + response = self.client.post(url) self.assertEqual(response.status_code, 400) def test_modify_access_bad_action(self): """ Test with an invalid action parameter. """ url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_staff.email, 'rolename': 'staff', 'action': 'robot-not-an-action', @@ -1935,7 +2068,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_bad_role(self): """ Test with an invalid action parameter. """ url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_staff.email, 'rolename': 'robot-not-a-roll', 'action': 'revoke', @@ -1944,7 +2077,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_allow(self): url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_user.email, 'rolename': 'staff', 'action': 'allow', @@ -1953,7 +2086,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_allow_with_uname(self): url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_instructor.username, 'rolename': 'staff', 'action': 'allow', @@ -1962,7 +2095,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_revoke(self): url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_staff.email, 'rolename': 'staff', 'action': 'revoke', @@ -1971,7 +2104,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_revoke_with_username(self): url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_staff.username, 'rolename': 'staff', 'action': 'revoke', @@ -1980,7 +2113,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_with_fake_user(self): url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': 'GandalfTheGrey', 'rolename': 'staff', 'action': 'revoke', @@ -1997,7 +2130,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe self.other_user.is_active = False self.other_user.save() # pylint: disable=no-member url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_user.username, 'rolename': 'beta', 'action': 'allow', @@ -2013,7 +2146,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_modify_access_revoke_not_allowed(self): """ Test revoking access that a user does not have. """ url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.other_staff.email, 'rolename': 'instructor', 'action': 'revoke', @@ -2025,7 +2158,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe Test that an instructor cannot remove instructor privelages from themself. """ url = reverse('modify_access', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.instructor.email, 'rolename': 'instructor', 'action': 'revoke', @@ -2044,20 +2177,20 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_list_course_role_members_noparams(self): """ Test missing all query parameters. """ url = reverse('list_course_role_members', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url) + response = self.client.post(url) self.assertEqual(response.status_code, 400) def test_list_course_role_members_bad_rolename(self): """ Test with an invalid rolename parameter. """ url = reverse('list_course_role_members', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'rolename': 'robot-not-a-rolename', }) self.assertEqual(response.status_code, 400) def test_list_course_role_members_staff(self): url = reverse('list_course_role_members', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'rolename': 'staff', }) self.assertEqual(response.status_code, 200) @@ -2079,7 +2212,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe def test_list_course_role_members_beta(self): url = reverse('list_course_role_members', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'rolename': 'beta', }) self.assertEqual(response.status_code, 200) @@ -2112,7 +2245,7 @@ class TestInstructorAPILevelsAccess(SharedModuleStoreTestCase, LoginEnrollmentTe Get unique_student_identifier, rolename and action and update forum role. """ url = reverse('update_forum_role_membership', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get( + response = self.client.post( url, { 'unique_student_identifier': identifier, @@ -2185,7 +2318,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment """ enroll user using a registration code """ - redeem_url = reverse('register_code_redemption', args=[code]) + redeem_url = reverse('shoppingcart.views.register_code_redemption', args=[code], is_dashboard_endpoint=False) self.client.login(username=user.username, password='test') response = self.client.get(redeem_url) self.assertEquals(response.status_code, 200) @@ -2265,10 +2398,16 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment mode_slug=CourseMode.HONOR ) # update the quantity of the cart item paid_course_reg_item - resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), {'ItemId': paid_course_reg_item.id, 'qty': '4'}) + resp = self.client.post( + reverse('shoppingcart.views.update_user_cart', is_dashboard_endpoint=False), + {'ItemId': paid_course_reg_item.id, 'qty': '4'} + ) self.assertEqual(resp.status_code, 200) # apply the coupon code to the item in the cart - resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': coupon.code}) + resp = self.client.post( + reverse('shoppingcart.views.use_code', is_dashboard_endpoint=False), + {'code': coupon.code} + ) self.assertEqual(resp.status_code, 200) self.cart.purchase() # get the updated item @@ -2277,7 +2416,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment coupon_redemption = CouponRedemption.objects.select_related('coupon').filter(order=self.cart) sale_order_url = reverse('get_sale_order_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(sale_order_url) + response = self.client.post(sale_order_url) self.assertEqual(response['Content-Type'], 'text/csv') self.assertIn('36', response.content.split('\r\n')[1]) self.assertIn(str(item.unit_cost), response.content.split('\r\n')[1],) @@ -2301,11 +2440,18 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment PaidCourseRegistration.add_to_order(self.cart, self.course.id) # apply the coupon code to the item in the cart - resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': coupon.code}) + resp = self.client.post( + reverse('shoppingcart.views.use_code', is_dashboard_endpoint=False), + {'code': coupon.code} + ) self.assertEqual(resp.status_code, 200) # URL for instructor dashboard - instructor_dashboard = reverse('instructor_dashboard', kwargs={'course_id': self.course.id.to_deprecated_string()}) + instructor_dashboard = reverse( + 'instructor_dashboard', + kwargs={'course_id': self.course.id.to_deprecated_string()}, + is_dashboard_endpoint=False + ) # visit the instructor dashboard page and # check that the coupon redeem count should be 0 resp = self.client.get(instructor_dashboard) @@ -2342,7 +2488,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment 'get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()} ) - response = self.client.get(url + '/csv', {}) + response = self.client.post(url + '/csv', {}) self.assertEqual(response['Content-Type'], 'text/csv') def test_get_sale_records_features_json(self): @@ -2361,7 +2507,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment course_registration_code.save() url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('sale', res_json) @@ -2411,7 +2557,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment course_registration_code.save() url = reverse('get_sale_records', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('sale', res_json) @@ -2459,7 +2605,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment ) problem_location = '' - response = self.client.get(url, {'problem_location': problem_location}) + response = self.client.post(url, {'problem_location': problem_location}) res_json = json.loads(response.content) self.assertEqual(res_json, 'Could not find problem with this location.') @@ -2494,7 +2640,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment ) problem_location = '' - response = self.client.get(url, {'problem_location': problem_location}) + response = self.client.post(url, {'problem_location': problem_location}) res_json = json.loads(response.content) self.assertIn('status', res_json) status = res_json['status'] @@ -2515,7 +2661,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment with patch('instructor_task.api.submit_calculate_problem_responses_csv') as submit_task_function: error = AlreadyRunningError() submit_task_function.side_effect = error - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('status', res_json) self.assertIn('already in progress', res_json['status']) @@ -2529,7 +2675,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment student.profile.city = "Mos Eisley {}".format(student.id) student.profile.save() url = reverse('get_students_features', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('students', res_json) for student in self.students: @@ -2551,7 +2697,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment url = reverse('get_students_features', kwargs={'course_id': unicode(self.course.id)}) set_course_cohort_settings(self.course.id, is_cohorted=is_cohorted) - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertEqual('cohort' in res_json['feature_names'], is_cohorted) @@ -2571,7 +2717,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment url = reverse('get_students_features', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertEqual('team' in res_json['feature_names'], has_teams) @@ -2587,7 +2733,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment kwargs={'course_id': unicode(self.course.id)} ) # Successful case: - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('status', res_json) self.assertNotIn('currently being created', res_json['status']) @@ -2595,7 +2741,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment with patch('instructor_task.api.submit_calculate_may_enroll_csv') as submit_task_function: error = AlreadyRunningError() submit_task_function.side_effect = error - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('status', res_json) self.assertIn('currently being created', res_json['status']) @@ -2610,7 +2756,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment kwargs={'course_id': unicode(self.course.id)} ) # Successful case: - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('status', res_json) self.assertNotIn('currently being created', res_json['status']) @@ -2618,7 +2764,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment with patch('instructor_task.api.submit_proctored_exam_results_report') as submit_task_function: error = AlreadyRunningError() submit_task_function.side_effect = error - response = self.client.get(url, {}) + response = self.client.post(url, {}) res_json = json.loads(response.content) self.assertIn('status', res_json) self.assertIn('currently being created', res_json['status']) @@ -2704,7 +2850,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment self.client.login(username=self.instructor.username, password='test') url = reverse('get_enrollment_report', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertIn('The detailed enrollment report is being created.', response.content) def test_bulk_purchase_detailed_report(self): @@ -2716,11 +2862,16 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment """ paid_course_reg_item = PaidCourseRegistration.add_to_order(self.cart, self.course.id) # update the quantity of the cart item paid_course_reg_item - resp = self.client.post(reverse('shoppingcart.views.update_user_cart'), - {'ItemId': paid_course_reg_item.id, 'qty': '4'}) + resp = self.client.post( + reverse('shoppingcart.views.update_user_cart', is_dashboard_endpoint=False), + {'ItemId': paid_course_reg_item.id, 'qty': '4'} + ) self.assertEqual(resp.status_code, 200) # apply the coupon code to the item in the cart - resp = self.client.post(reverse('shoppingcart.views.use_code'), {'code': self.coupon_code}) + resp = self.client.post( + reverse('shoppingcart.views.use_code', is_dashboard_endpoint=False), + {'code': self.coupon_code} + ) self.assertEqual(resp.status_code, 200) self.cart.purchase() @@ -2754,7 +2905,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment self.client.login(username=self.instructor.username, password='test') url = reverse('get_enrollment_report', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertIn('The detailed enrollment report is being created.', response.content) def test_create_registration_code_without_invoice_and_order(self): @@ -2776,7 +2927,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment self.client.login(username=self.instructor.username, password='test') url = reverse('get_enrollment_report', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertIn('The detailed enrollment report is being created.', response.content) def test_invoice_payment_is_still_pending_for_registration_codes(self): @@ -2801,7 +2952,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment self.client.login(username=self.instructor.username, password='test') url = reverse('get_enrollment_report', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertIn('The detailed enrollment report is being created.', response.content) @patch.object(instructor.views.api, 'anonymous_id_for_user', Mock(return_value='42')) @@ -2811,7 +2962,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment Test the CSV output for the anonymized user ids. """ url = reverse('get_anon_ids', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response['Content-Type'], 'text/csv') body = response.content.replace('\r', '') self.assertTrue(body.startswith( @@ -2829,7 +2980,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment ('mock_file_name_1', 'https://1.mock.url'), ('mock_file_name_2', 'https://2.mock.url'), ] - response = self.client.get(url, {}) + response = self.client.post(url, {}) expected_response = { "downloads": [ @@ -2858,12 +3009,12 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment success_status = "The {report_type} report is being created.".format(report_type=report_type) if report_type == 'problem responses': with patch(task_api_endpoint): - response = self.client.get(url, {'problem_location': ''}) + response = self.client.post(url, {'problem_location': ''}) self.assertIn(success_status, response.content) else: CourseFinanceAdminRole(self.course.id).add_users(self.instructor) with patch(task_api_endpoint): - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertIn(success_status, response.content) @ddt.data(*EXECUTIVE_SUMMARY_DATA) @@ -2881,7 +3032,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment CourseFinanceAdminRole(self.course.id).add_users(self.instructor) with patch(task_api_endpoint): - response = self.client.get(url, {}) + response = self.client.post(url, {}) success_status = "The {report_type} report is being created." \ " To view the status of the report, see Pending" \ " Tasks below".format(report_type=report_type) @@ -2903,7 +3054,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment CourseFinanceAdminRole(self.course.id).add_users(self.instructor) with patch(task_api_endpoint) as mock: mock.side_effect = AlreadyRunningError() - response = self.client.get(url, {}) + response = self.client.post(url, {}) already_running_status = "The {report_type} report is currently being created." \ " To view the status of the report, see Pending Tasks below." \ " You will be able to download the report" \ @@ -2916,7 +3067,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment with patch('instructor_task.api.submit_export_ora2_data') as mock_submit_ora2_task: mock_submit_ora2_task.return_value = True - response = self.client.get(url, {}) + response = self.client.post(url, {}) success_status = "The ORA data report is being generated." self.assertIn(success_status, response.content) @@ -2925,17 +3076,15 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment with patch('instructor_task.api.submit_export_ora2_data') as mock_submit_ora2_task: mock_submit_ora2_task.side_effect = AlreadyRunningError() - response = self.client.get(url, {}) + response = self.client.post(url, {}) already_running_status = "An ORA data report generation task is already in progress." self.assertIn(already_running_status, response.content) def test_get_student_progress_url(self): """ Test that progress_url is in the successful response. """ url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id.to_deprecated_string()}) - url += "?unique_student_identifier={}".format( - quote(self.students[0].email.encode("utf-8")) - ) - response = self.client.get(url) + data = {'unique_student_identifier': self.students[0].email.encode("utf-8")} + response = self.client.post(url, data) self.assertEqual(response.status_code, 200) res_json = json.loads(response.content) self.assertIn('progress_url', res_json) @@ -2943,10 +3092,8 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment def test_get_student_progress_url_from_uname(self): """ Test that progress_url is in the successful response. """ url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id.to_deprecated_string()}) - url += "?unique_student_identifier={}".format( - quote(self.students[0].username.encode("utf-8")) - ) - response = self.client.get(url) + data = {'unique_student_identifier': self.students[0].username.encode("utf-8")} + response = self.client.post(url, data) self.assertEqual(response.status_code, 200) res_json = json.loads(response.content) self.assertIn('progress_url', res_json) @@ -2954,13 +3101,13 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment def test_get_student_progress_url_noparams(self): """ Test that the endpoint 404's without the required query params. """ url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url) + response = self.client.post(url) self.assertEqual(response.status_code, 400) def test_get_student_progress_url_nostudent(self): """ Test that the endpoint 400's when requesting an unknown email. """ url = reverse('get_student_progress_url', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url) + response = self.client.post(url) self.assertEqual(response.status_code, 400) @@ -3001,7 +3148,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_reset_student_attempts_deletall(self): """ Make sure no one can delete all students state on a problem. """ url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'all_students': True, 'delete_module': True, @@ -3011,7 +3158,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_reset_student_attempts_single(self): """ Test reset single student attempts. """ url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.student.email, }) @@ -3028,7 +3175,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_reset_student_attempts_all(self, act): """ Test reset all student attempts. """ url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'all_students': True, }) @@ -3038,7 +3185,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_reset_student_attempts_missingmodule(self): """ Test reset for non-existant problem. """ url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': 'robot-not-a-real-module', 'unique_student_identifier': self.student.email, }) @@ -3047,7 +3194,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_reset_student_attempts_delete(self): """ Test delete single student state. """ url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.student.email, 'delete_module': True, @@ -3066,7 +3213,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_reset_student_attempts_nonsense(self): """ Test failure with both unique_student_identifier and all_students. """ url = reverse('reset_student_attempts', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.student.email, 'all_students': True, @@ -3077,7 +3224,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_rescore_problem_single(self, act): """ Test rescoring of a single student. """ url = reverse('rescore_problem', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.student.email, }) @@ -3088,7 +3235,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_rescore_problem_single_from_uname(self, act): """ Test rescoring of a single student. """ url = reverse('rescore_problem', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.student.username, }) @@ -3099,7 +3246,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_rescore_problem_all(self, act): """ Test rescoring for all students. """ url = reverse('rescore_problem', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'problem_to_reset': self.problem_urlname, 'all_students': True, }) @@ -3111,7 +3258,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes """ Test course has entrance exam id set while resetting attempts""" url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'all_students': True, 'delete_module': False, }) @@ -3121,7 +3268,7 @@ class TestInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginEnrollmentTes def test_rescore_entrance_exam_with_invalid_exam(self): """ Test course has entrance exam id set while re-scoring. """ url = reverse('rescore_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 400) @@ -3225,7 +3372,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Make sure no one can delete all students state on entrance exam. """ url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'all_students': True, 'delete_module': True, }) @@ -3235,7 +3382,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test reset single student attempts for entrance exam. """ url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 200) @@ -3253,7 +3400,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test reset all student attempts for entrance exam. """ url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'all_students': True, }) self.assertEqual(response.status_code, 200) @@ -3263,7 +3410,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test reset for invalid entrance exam. """ url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course_with_invalid_ee.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 400) @@ -3272,7 +3419,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test delete single student entrance exam state. """ url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, 'delete_module': True, }) @@ -3288,7 +3435,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE self.client.login(username=staff_user.username, password='test') url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, 'delete_module': True, }) @@ -3298,7 +3445,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test failure with both unique_student_identifier and all_students. """ url = reverse('reset_student_attempts_for_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, 'all_students': True, }) @@ -3308,7 +3455,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE def test_rescore_entrance_exam_single_student(self, act): """ Test re-scoring of entrance exam for single student. """ url = reverse('rescore_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 200) @@ -3317,7 +3464,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE def test_rescore_entrance_exam_all_student(self): """ Test rescoring for all students. """ url = reverse('rescore_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'all_students': True, }) self.assertEqual(response.status_code, 200) @@ -3325,7 +3472,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE def test_rescore_entrance_exam_all_student_and_single(self): """ Test re-scoring with both all students and single student parameters. """ url = reverse('rescore_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, 'all_students': True, }) @@ -3334,7 +3481,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE def test_rescore_entrance_exam_with_invalid_exam(self): """ Test re-scoring of entrance exam with invalid exam. """ url = reverse('rescore_entrance_exam', kwargs={'course_id': unicode(self.course_with_invalid_ee.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 400) @@ -3343,13 +3490,13 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test list task history for entrance exam AND student. """ # create a re-score entrance exam task url = reverse('rescore_entrance_exam', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 200) url = reverse('list_entrance_exam_instructor_tasks', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 200) @@ -3362,7 +3509,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE def test_list_entrance_exam_instructor_tasks_all_student(self): """ Test list task history for entrance exam AND all student. """ url = reverse('list_entrance_exam_instructor_tasks', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 200) # check response @@ -3373,7 +3520,7 @@ class TestEntranceExamInstructorAPIRegradeTask(SharedModuleStoreTestCase, LoginE """ Test list task history for entrance exam failure if course has invalid exam. """ url = reverse('list_entrance_exam_instructor_tasks', kwargs={'course_id': unicode(self.course_with_invalid_ee.id)}) - response = self.client.get(url, { + response = self.client.post(url, { 'unique_student_identifier': self.student.email, }) self.assertEqual(response.status_code, 400) @@ -3589,7 +3736,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC mock_factory = MockCompletionInfo() with patch('instructor.views.instructor_task_helpers.get_task_completion_info') as mock_completion_info: mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 200) # check response @@ -3608,7 +3755,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC mock_factory = MockCompletionInfo() with patch('instructor.views.instructor_task_helpers.get_task_completion_info') as mock_completion_info: mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 200) # check response @@ -3627,7 +3774,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC mock_factory = MockCompletionInfo() with patch('instructor.views.instructor_task_helpers.get_task_completion_info') as mock_completion_info: mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info - response = self.client.get(url, { + response = self.client.post(url, { 'problem_location_str': self.problem_urlname, }) self.assertEqual(response.status_code, 200) @@ -3648,7 +3795,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC mock_factory = MockCompletionInfo() with patch('instructor.views.instructor_task_helpers.get_task_completion_info') as mock_completion_info: mock_completion_info.side_effect = mock_factory.mock_get_task_completion_info - response = self.client.get(url, { + response = self.client.post(url, { 'problem_location_str': self.problem_urlname, 'unique_student_identifier': self.student.email, }) @@ -3709,7 +3856,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT url = reverse('list_email_content', kwargs={'course_id': self.course.id.to_deprecated_string()}) with patch('instructor.views.api.CourseEmail.objects.get') as mock_email_info: mock_email_info.side_effect = self.get_matching_mock_email - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 200) return response @@ -3760,7 +3907,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT invalid_task.make_invalid_input() task_history_request.return_value = [invalid_task] url = reverse('list_email_content', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 200) self.assertTrue(task_history_request.called) @@ -3786,7 +3933,7 @@ class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentT url = reverse('list_email_content', kwargs={'course_id': self.course.id.to_deprecated_string()}) with patch('instructor.views.api.CourseEmail.objects.get') as mock_email_info: mock_email_info.return_value = email - response = self.client.get(url, {}) + response = self.client.post(url, {}) self.assertEqual(response.status_code, 200) self.assertTrue(task_history_request.called) @@ -3937,7 +4084,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def test_change_due_date(self): url = reverse('change_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week1.location.to_deprecated_string(), 'due_datetime': '12/30/2013 00:00' @@ -3948,7 +4095,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def test_change_to_invalid_due_date(self): url = reverse('change_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week1.location.to_deprecated_string(), 'due_datetime': '01/01/2009 00:00' @@ -3961,7 +4108,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def test_change_nonexistent_due_date(self): url = reverse('change_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week3.location.to_deprecated_string(), 'due_datetime': '12/30/2013 00:00' @@ -3975,7 +4122,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def test_reset_date(self): self.test_change_due_date() url = reverse('reset_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week1.location.to_deprecated_string(), }) @@ -3987,7 +4134,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): def test_reset_nonexistent_extension(self): url = reverse('reset_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week1.location.to_deprecated_string(), }) @@ -3997,7 +4144,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): self.test_change_due_date() url = reverse('show_unit_extensions', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {'url': self.week1.location.to_deprecated_string()}) + response = self.client.post(url, {'url': self.week1.location.to_deprecated_string()}) self.assertEqual(response.status_code, 200, response.content) self.assertEqual(json.loads(response.content), { u'data': [{u'Extended Due Date': u'2013-12-30 00:00', @@ -4011,7 +4158,7 @@ class TestDueDateExtensions(SharedModuleStoreTestCase, LoginEnrollmentTestCase): self.test_change_due_date() url = reverse('show_student_extensions', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, {'student': self.user1.username}) + response = self.client.post(url, {'student': self.user1.username}) self.assertEqual(response.status_code, 200, response.content) self.assertEqual(json.loads(response.content), { u'data': [{u'Extended Due Date': u'2013-12-30 00:00', @@ -4105,7 +4252,7 @@ class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestC """ url = reverse('change_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week1.location.to_deprecated_string(), 'due_datetime': '12/30/2013 00:00' @@ -4118,7 +4265,7 @@ class TestDueDateExtensionsDeletedDate(ModuleStoreTestCase, LoginEnrollmentTestC self.week1 = self.store.update_item(self.week1, self.user1.id) # Now, week1's normal due date is deleted but the extension still exists. url = reverse('reset_due_date', kwargs={'course_id': self.course.id.to_deprecated_string()}) - response = self.client.get(url, { + response = self.client.post(url, { 'student': self.user1.username, 'url': self.week1.location.to_deprecated_string(), }) @@ -4166,14 +4313,14 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase): for __ in xrange(certificate_count): self.generate_certificate(course_id=self.course.id, mode='honor', status=CertificateStatuses.generating) - response = self.client.get(url) + response = self.client.post(url) res_json = json.loads(response.content) self.assertIn('certificates', res_json) self.assertEqual(len(res_json['certificates']), 0) # Certificates with status 'downloadable' should be in response. self.generate_certificate(course_id=self.course.id, mode='honor', status=CertificateStatuses.downloadable) - response = self.client.get(url) + response = self.client.post(url) res_json = json.loads(response.content) self.assertIn('certificates', res_json) self.assertEqual(len(res_json['certificates']), 1) @@ -4188,7 +4335,7 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase): for __ in xrange(certificate_count): self.generate_certificate(course_id=self.course.id, mode='honor', status=CertificateStatuses.downloadable) - response = self.client.get(url) + response = self.client.post(url) res_json = json.loads(response.content) self.assertIn('certificates', res_json) self.assertEqual(len(res_json['certificates']), 1) @@ -4207,7 +4354,7 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase): status=CertificateStatuses.downloadable ) - response = self.client.get(url) + response = self.client.post(url) res_json = json.loads(response.content) self.assertIn('certificates', res_json) @@ -4224,14 +4371,13 @@ class TestCourseIssuedCertificatesData(SharedModuleStoreTestCase): Test for certificate csv features. """ url = reverse('get_issued_certificates', kwargs={'course_id': unicode(self.course.id)}) - url += '?csv=true' # firstly generating downloadable certificates with 'honor' mode certificate_count = 3 for __ in xrange(certificate_count): self.generate_certificate(course_id=self.course.id, mode='honor', status=CertificateStatuses.downloadable) current_date = datetime.date.today().strftime("%B %d, %Y") - response = self.client.get(url) + response = self.client.get(url, {'csv': 'true'}) self.assertEqual(response['Content-Type'], 'text/csv') self.assertEqual(response['Content-Disposition'], 'attachment; filename={0}'.format('issued_certificates.csv')) self.assertEqual( @@ -4680,7 +4826,7 @@ class TestCourseRegistrationCodes(SharedModuleStoreTestCase): ) coupon.save() - response = self.client.get(get_coupon_code_url) + response = self.client.post(get_coupon_code_url) self.assertEqual(response.status_code, 200, response.content) # filter all the coupons for coupon in Coupon.objects.all(): @@ -4721,7 +4867,7 @@ class TestBulkCohorting(SharedModuleStoreTestCase): self.tempdir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.tempdir) - def call_add_users_to_cohorts(self, csv_data, suffix='.csv', method='POST'): + def call_add_users_to_cohorts(self, csv_data, suffix='.csv'): """ Call `add_users_to_cohorts` with a file generated from `csv_data`. """ @@ -4731,10 +4877,7 @@ class TestBulkCohorting(SharedModuleStoreTestCase): file_pointer.write(csv_data.encode('utf-8')) with open(file_name, 'r') as file_pointer: url = reverse('add_users_to_cohorts', kwargs={'course_id': unicode(self.course.id)}) - if method == 'POST': - return self.client.post(url, {'uploaded-file': file_pointer}) - elif method == 'GET': - return self.client.get(url, {'uploaded-file': file_pointer}) + return self.client.post(url, {'uploaded-file': file_pointer}) def expect_error_on_file_content(self, file_content, error, file_suffix='.csv'): """ @@ -4803,14 +4946,6 @@ class TestBulkCohorting(SharedModuleStoreTestCase): response = self.call_add_users_to_cohorts('') self.assertEqual(response.status_code, 403) - def test_post_only(self): - """ - Verify that we can't call the view when we aren't using POST. - """ - self.client.login(username=self.staff_user.username, password='test') - response = self.call_add_users_to_cohorts('', method='GET') - self.assertEqual(response.status_code, 405) - @patch('instructor.views.api.instructor_task.api.submit_cohort_students') @patch('instructor.views.api.store_uploaded_file') def test_success_username(self, mock_store_upload, mock_cohort_task): diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index ca68e24190..0214557686 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -140,51 +140,14 @@ def common_exceptions_400(func): return wrapped -def require_query_params(*args, **kwargs): - """ - Checks for required paremters or renders a 400 error. - (decorator with arguments) - - `args` is a *list of required GET parameter names. - `kwargs` is a **dict of required GET parameter names - to string explanations of the parameter - """ - required_params = [] - required_params += [(arg, None) for arg in args] - required_params += [(key, kwargs[key]) for key in kwargs] - # required_params = e.g. [('action', 'enroll or unenroll'), ['emails', None]] - - def decorator(func): # pylint: disable=missing-docstring - def wrapped(*args, **kwargs): # pylint: disable=missing-docstring - request = args[0] - - error_response_data = { - 'error': 'Missing required query parameter(s)', - 'parameters': [], - 'info': {}, - } - - for (param, extra) in required_params: - default = object() - if request.GET.get(param, default) == default: - error_response_data['parameters'].append(param) - error_response_data['info'][param] = extra - - if len(error_response_data['parameters']) > 0: - return JsonResponse(error_response_data, status=400) - else: - return func(*args, **kwargs) - return wrapped - return decorator - - def require_post_params(*args, **kwargs): """ Checks for required parameters or renders a 400 error. (decorator with arguments) - Functions like 'require_query_params', but checks for - POST parameters rather than GET parameters. + `args` is a *list of required POST parameter names. + `kwargs` is a **dict of required POST parameter names + to string explanations of the parameter """ required_params = [] required_params += [(arg, None) for arg in args] @@ -314,6 +277,7 @@ NAME_INDEX = 2 COUNTRY_INDEX = 3 +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -604,6 +568,7 @@ def create_and_enroll_user(email, username, name, country, password, course_id, return errors +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -768,6 +733,7 @@ def students_update_enrollment(request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('instructor') @@ -848,11 +814,12 @@ def bulk_beta_modify_access(request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('instructor') @common_exceptions_400 -@require_query_params( +@require_post_params( unique_student_identifier="email or username of user to change access", rolename="'instructor', 'staff', 'beta', or 'ccx_coach'", action="'allow' or 'revoke'" @@ -874,10 +841,10 @@ def modify_access(request, course_id): request.user, 'instructor', course_id, depth=None ) try: - user = get_student_from_identifier(request.GET.get('unique_student_identifier')) + user = get_student_from_identifier(request.POST.get('unique_student_identifier')) except User.DoesNotExist: response_payload = { - 'unique_student_identifier': request.GET.get('unique_student_identifier'), + 'unique_student_identifier': request.POST.get('unique_student_identifier'), 'userDoesNotExist': True, } return JsonResponse(response_payload) @@ -892,8 +859,8 @@ def modify_access(request, course_id): } return JsonResponse(response_payload) - rolename = request.GET.get('rolename') - action = request.GET.get('action') + rolename = request.POST.get('rolename') + action = request.POST.get('action') if rolename not in ROLES: error = strip_tags("unknown rolename '{}'".format(rolename)) @@ -928,10 +895,11 @@ def modify_access(request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('instructor') -@require_query_params(rolename="'instructor', 'staff', or 'beta'") +@require_post_params(rolename="'instructor', 'staff', or 'beta'") def list_course_role_members(request, course_id): """ List instructors and staff. @@ -956,7 +924,7 @@ def list_course_role_members(request, course_id): request.user, 'instructor', course_id, depth=None ) - rolename = request.GET.get('rolename') + rolename = request.POST.get('rolename') if rolename not in ROLES: return HttpResponseBadRequest() @@ -980,6 +948,7 @@ def list_course_role_members(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -996,7 +965,7 @@ def get_problem_responses(request, course_id): Responds with BadRequest if problem location is faulty. """ course_key = CourseKey.from_string(course_id) - problem_location = request.GET.get('problem_location', '') + problem_location = request.POST.get('problem_location', '') try: problem_key = UsageKey.from_string(problem_location) @@ -1025,6 +994,7 @@ def get_problem_responses(request, course_id): return JsonResponse({"status": already_running_status}) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1223,6 +1193,7 @@ def get_issued_certificates(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1315,6 +1286,7 @@ def get_students_features(request, course_id, csv=False): # pylint: disable=red @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1422,6 +1394,7 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1446,6 +1419,7 @@ def get_enrollment_report(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1471,6 +1445,7 @@ def get_exec_summary_report(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1495,6 +1470,7 @@ def get_course_survey_results(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -1901,11 +1877,12 @@ def get_anon_ids(request, course_id): # pylint: disable=unused-argument return csv_response(course_id.to_deprecated_string().replace('/', '-') + '-anon-ids.csv', header, rows) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @common_exceptions_400 @require_level('staff') -@require_query_params( +@require_post_params( unique_student_identifier="email or username of student for whom to get progress url" ) def get_student_progress_url(request, course_id): @@ -1913,13 +1890,13 @@ def get_student_progress_url(request, course_id): Get the progress url of a student. Limited to staff access. - Takes query paremeter unique_student_identifier and if the student exists + Takes query parameter unique_student_identifier and if the student exists returns e.g. { 'progress_url': '/../...' } """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) - user = get_student_from_identifier(request.GET.get('unique_student_identifier')) + user = get_student_from_identifier(request.POST.get('unique_student_identifier')) progress_url = reverse('student_progress', kwargs={'course_id': course_id.to_deprecated_string(), 'student_id': user.id}) @@ -1931,10 +1908,11 @@ def get_student_progress_url(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params( +@require_post_params( problem_to_reset="problem urlname to reset" ) @common_exceptions_400 @@ -1961,13 +1939,13 @@ def reset_student_attempts(request, course_id): request.user, 'staff', course_id, depth=None ) - problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) - student_identifier = request.GET.get('unique_student_identifier', None) + problem_to_reset = strip_if_string(request.POST.get('problem_to_reset')) + student_identifier = request.POST.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) - all_students = request.GET.get('all_students', False) in ['true', 'True', True] - delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] + all_students = request.POST.get('all_students', False) in ['true', 'True', True] + delete_module = request.POST.get('delete_module', False) in ['true', 'True', True] # parameter combinations if all_students and student: @@ -2019,6 +1997,7 @@ def reset_student_attempts(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2049,12 +2028,12 @@ def reset_student_attempts_for_entrance_exam(request, course_id): # pylint: dis _("Course has no entrance exam section.") ) - student_identifier = request.GET.get('unique_student_identifier', None) + student_identifier = request.POST.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) - all_students = request.GET.get('all_students', False) in ['true', 'True', True] - delete_module = request.GET.get('delete_module', False) in ['true', 'True', True] + all_students = request.POST.get('all_students', False) in ['true', 'True', True] + delete_module = request.POST.get('delete_module', False) in ['true', 'True', True] # parameter combinations if all_students and student: @@ -2085,10 +2064,11 @@ def reset_student_attempts_for_entrance_exam(request, course_id): # pylint: dis @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('instructor') -@require_query_params(problem_to_reset="problem urlname to reset") +@require_post_params(problem_to_reset="problem urlname to reset") @common_exceptions_400 def rescore_problem(request, course_id): """ @@ -2103,13 +2083,13 @@ def rescore_problem(request, course_id): all_students and unique_student_identifier cannot both be present. """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) - problem_to_reset = strip_if_string(request.GET.get('problem_to_reset')) - student_identifier = request.GET.get('unique_student_identifier', None) + problem_to_reset = strip_if_string(request.POST.get('problem_to_reset')) + student_identifier = request.POST.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) - all_students = request.GET.get('all_students') in ['true', 'True', True] + all_students = request.POST.get('all_students') in ['true', 'True', True] if not (problem_to_reset and (all_students or student)): return HttpResponseBadRequest("Missing query parameters.") @@ -2141,6 +2121,7 @@ def rescore_problem(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('instructor') @@ -2161,12 +2142,12 @@ def rescore_entrance_exam(request, course_id): request.user, 'staff', course_id, depth=None ) - student_identifier = request.GET.get('unique_student_identifier', None) + student_identifier = request.POST.get('unique_student_identifier', None) student = None if student_identifier is not None: student = get_student_from_identifier(student_identifier) - all_students = request.GET.get('all_students') in ['true', 'True', True] + all_students = request.POST.get('all_students') in ['true', 'True', True] if not course.entrance_exam_id: return HttpResponseBadRequest( @@ -2193,6 +2174,7 @@ def rescore_entrance_exam(request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2211,6 +2193,7 @@ def list_background_email_tasks(request, course_id): # pylint: disable=unused-a return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2229,6 +2212,7 @@ def list_email_content(request, course_id): # pylint: disable=unused-argument return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2243,8 +2227,8 @@ def list_instructor_tasks(request, course_id): history for problem AND student (intersection) """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) - problem_location_str = strip_if_string(request.GET.get('problem_location_str', False)) - student = request.GET.get('unique_student_identifier', None) + problem_location_str = strip_if_string(request.POST.get('problem_location_str', False)) + student = request.POST.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) @@ -2274,6 +2258,7 @@ def list_instructor_tasks(request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2287,7 +2272,7 @@ def list_entrance_exam_instructor_tasks(request, course_id): # pylint: disable= """ course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id) course = get_course_by_id(course_id) - student = request.GET.get('unique_student_identifier', None) + student = request.POST.get('unique_student_identifier', None) if student is not None: student = get_student_from_identifier(student) @@ -2308,6 +2293,7 @@ def list_entrance_exam_instructor_tasks(request, course_id): # pylint: disable= return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2327,6 +2313,7 @@ def list_report_downloads(_request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2348,6 +2335,7 @@ def list_financial_report_downloads(_request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2373,6 +2361,7 @@ def export_ora2_data(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2394,6 +2383,7 @@ def calculate_grades_csv(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2420,10 +2410,11 @@ def problem_grade_report(request, course_id): }) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params('rolename') +@require_post_params('rolename') def list_forum_members(request, course_id): """ Lists forum members of a certain rolename. @@ -2442,7 +2433,7 @@ def list_forum_members(request, course_id): request.user, course_id, FORUM_ROLE_ADMINISTRATOR ) - rolename = request.GET.get('rolename') + rolename = request.POST.get('rolename') # default roles require either (staff & forum admin) or (instructor) if not (has_forum_admin or has_instructor_access): @@ -2483,6 +2474,7 @@ def list_forum_members(request, course_id): @transaction.non_atomic_requests +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') @@ -2539,10 +2531,11 @@ def send_email(request, course_id): return JsonResponse(response_payload) +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params( +@require_post_params( unique_student_identifier="email or username of user to change access", rolename="the forum role", action="'allow' or 'revoke'", @@ -2569,9 +2562,9 @@ def update_forum_role_membership(request, course_id): request.user, course_id, FORUM_ROLE_ADMINISTRATOR ) - unique_student_identifier = request.GET.get('unique_student_identifier') - rolename = request.GET.get('rolename') - action = request.GET.get('action') + unique_student_identifier = request.POST.get('unique_student_identifier') + rolename = request.POST.get('rolename') + action = request.POST.get('action') # default roles require either (staff & forum admin) or (instructor) if not (has_forum_admin or has_instructor_access): @@ -2629,18 +2622,19 @@ def _display_unit(unit): @handle_dashboard_error +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params('student', 'url', 'due_datetime') +@require_post_params('student', 'url', 'due_datetime') def change_due_date(request, course_id): """ Grants a due date extension to a student for a particular unit. """ course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id)) - student = require_student_from_identifier(request.GET.get('student')) - unit = find_unit(course, request.GET.get('url')) - due_date = parse_datetime(request.GET.get('due_datetime')) + student = require_student_from_identifier(request.POST.get('student')) + unit = find_unit(course, request.POST.get('url')) + due_date = parse_datetime(request.POST.get('due_datetime')) set_due_date_extension(course, unit, student, due_date) return JsonResponse(_( @@ -2650,17 +2644,18 @@ def change_due_date(request, course_id): @handle_dashboard_error +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params('student', 'url') +@require_post_params('student', 'url') def reset_due_date(request, course_id): """ Rescinds a due date extension for a student on a particular unit. """ course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id)) - student = require_student_from_identifier(request.GET.get('student')) - unit = find_unit(course, request.GET.get('url')) + student = require_student_from_identifier(request.POST.get('student')) + unit = find_unit(course, request.POST.get('url')) set_due_date_extension(course, unit, student, None) if not getattr(unit, "due", None): # It's possible the normal due date was deleted after an extension was granted: @@ -2676,30 +2671,32 @@ def reset_due_date(request, course_id): @handle_dashboard_error +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params('url') +@require_post_params('url') def show_unit_extensions(request, course_id): """ Shows all of the students which have due date extensions for the given unit. """ course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id)) - unit = find_unit(course, request.GET.get('url')) + unit = find_unit(course, request.POST.get('url')) return JsonResponse(dump_module_extensions(course, unit)) @handle_dashboard_error +@require_POST @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_level('staff') -@require_query_params('student') +@require_post_params('student') def show_student_extensions(request, course_id): """ Shows all of the due date extensions granted to a particular student in a particular course. """ - student = require_student_from_identifier(request.GET.get('student')) + student = require_student_from_identifier(request.POST.get('student')) course = get_course_by_id(SlashSeparatedCourseKey.from_deprecated_string(course_id)) return JsonResponse(dump_student_extensions(course, student)) @@ -3076,7 +3073,6 @@ def generate_certificate_exceptions(request, course_id, generate_for=None): return JsonResponse(response_payload) -@csrf_exempt @cache_control(no_cache=True, no_store=True, must_revalidate=True) @require_global_staff @require_POST diff --git a/lms/static/coffee/src/instructor_dashboard/data_download.coffee b/lms/static/coffee/src/instructor_dashboard/data_download.coffee index 7d2ab57af6..4fe762965a 100644 --- a/lms/static/coffee/src/instructor_dashboard/data_download.coffee +++ b/lms/static/coffee/src/instructor_dashboard/data_download.coffee @@ -110,6 +110,7 @@ class DataDownload url = @$list_proctored_exam_results_csv_btn.data 'endpoint' # display html from proctored exam results config endpoint $.ajax + type: 'POST' dataType: 'json' url: url error: (std_ajax_err) => @@ -129,6 +130,7 @@ class DataDownload url = @$survey_results_csv_btn.data 'endpoint' # display html from survey results config endpoint $.ajax + type: 'POST' dataType: 'json' url: url error: (std_ajax_err) => @@ -153,6 +155,7 @@ class DataDownload url += '/csv' $.ajax + type: 'POST' dataType: 'json' url: url error: (std_ajax_err) => @@ -171,6 +174,7 @@ class DataDownload # fetch user list $.ajax + type: 'POST' dataType: 'json' url: url error: (std_ajax_err) => @@ -199,6 +203,7 @@ class DataDownload url = @$list_problem_responses_csv_btn.data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url: url data: @@ -215,6 +220,7 @@ class DataDownload url = @$list_may_enroll_csv_btn.data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url: url error: (std_ajax_err) => @@ -228,6 +234,7 @@ class DataDownload url = @$grade_config_btn.data 'endpoint' # display html from grading config endpoint $.ajax + type: 'POST' dataType: 'json' url: url error: (std_ajax_err) => @@ -244,6 +251,7 @@ class DataDownload @clear_display() url = $(e.target).data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url: url error: std_ajax_err => diff --git a/lms/static/coffee/src/instructor_dashboard/extensions.coffee b/lms/static/coffee/src/instructor_dashboard/extensions.coffee index a615d31524..a45cffce67 100644 --- a/lms/static/coffee/src/instructor_dashboard/extensions.coffee +++ b/lms/static/coffee/src/instructor_dashboard/extensions.coffee @@ -45,6 +45,7 @@ class Extensions due_datetime: @$due_datetime_input.val() $.ajax + type: 'POST' dataType: 'json' url: @$change_due_date.data 'endpoint' data: send_data @@ -60,6 +61,7 @@ class Extensions url: @$url_input.val() $.ajax + type: 'POST' dataType: 'json' url: @$reset_due_date.data 'endpoint' data: send_data @@ -75,6 +77,7 @@ class Extensions send_data = url: @$url_input.val() $.ajax + type: 'POST' dataType: 'json' url: url data: send_data @@ -90,6 +93,7 @@ class Extensions send_data = student: @$student_input.val() $.ajax + type: 'POST' dataType: 'json' url: url data: send_data diff --git a/lms/static/coffee/src/instructor_dashboard/membership.coffee b/lms/static/coffee/src/instructor_dashboard/membership.coffee index 8888f6b856..194f0cd4a3 100644 --- a/lms/static/coffee/src/instructor_dashboard/membership.coffee +++ b/lms/static/coffee/src/instructor_dashboard/membership.coffee @@ -137,6 +137,7 @@ class AuthListWidget extends MemberListWidget # `cb` is called with cb(error, member_list) get_member_list: (cb) -> $.ajax + type: 'POST' dataType: 'json' url: @list_endpoint data: rolename: @rolename @@ -151,6 +152,7 @@ class AuthListWidget extends MemberListWidget # `cb` is called with cb(error, data) modify_member_access: (unique_student_identifier, action, cb) -> $.ajax + type: 'POST' dataType: 'json' url: @modify_endpoint data: @@ -645,6 +647,7 @@ class AuthList # the endpoint comes from data-endpoint of the table $.ajax dataType: 'json' + type: 'POST' url: @$display_table.data 'endpoint' data: rolename: @rolename success: load_auth_list @@ -664,6 +667,7 @@ class AuthList access_change: (email, action, cb) -> $.ajax dataType: 'json' + type: 'POST' url: @$add_section.data 'endpoint' data: email: email diff --git a/lms/static/coffee/src/instructor_dashboard/send_email.coffee b/lms/static/coffee/src/instructor_dashboard/send_email.coffee index 2de77698c5..6edf49d196 100644 --- a/lms/static/coffee/src/instructor_dashboard/send_email.coffee +++ b/lms/static/coffee/src/instructor_dashboard/send_email.coffee @@ -99,6 +99,7 @@ class @SendEmail @$btn_task_history_email.click => url = @$btn_task_history_email.data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url: url success: (data) => @@ -115,6 +116,7 @@ class @SendEmail @$btn_task_history_email_content.click => url = @$btn_task_history_email_content.data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url : url success: (data) => diff --git a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee b/lms/static/coffee/src/instructor_dashboard/student_admin.coffee index 8dd8a19bcd..c9de7cba74 100644 --- a/lms/static/coffee/src/instructor_dashboard/student_admin.coffee +++ b/lms/static/coffee/src/instructor_dashboard/student_admin.coffee @@ -76,6 +76,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({student_id: unique_student_identifier}) $.ajax + type: 'POST' dataType: 'json' url: @$progress_link.data 'endpoint' data: unique_student_identifier: unique_student_identifier @@ -101,6 +102,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({problem_id: problem_to_reset, student_id: unique_student_identifier}) $.ajax + type: 'POST' dataType: 'json' url: @$btn_reset_attempts_single.data 'endpoint' data: send_data @@ -127,6 +129,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) $.ajax + type: 'POST' dataType: 'json' url: @$btn_delete_state_single.data 'endpoint' data: send_data @@ -153,6 +156,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) $.ajax + type: 'POST' dataType: 'json' url: @$btn_rescore_problem_single.data 'endpoint' data: send_data @@ -174,6 +178,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({student_id: unique_student_identifier, problem_id: problem_to_reset}) $.ajax + type: 'POST' dataType: 'json' url: @$btn_task_history_single.data 'endpoint' data: send_data @@ -191,6 +196,7 @@ class @StudentAdmin delete_module: false $.ajax + type: 'POST' dataType: 'json' url: @$btn_reset_entrance_exam_attempts.data 'endpoint' data: send_data @@ -212,6 +218,7 @@ class @StudentAdmin unique_student_identifier: unique_student_identifier $.ajax + type: 'POST' dataType: 'json' url: @$btn_rescore_entrance_exam.data 'endpoint' data: send_data @@ -256,6 +263,7 @@ class @StudentAdmin delete_module: true $.ajax + type: 'POST' dataType: 'json' url: @$btn_delete_entrance_exam_state.data 'endpoint' data: send_data @@ -277,6 +285,7 @@ class @StudentAdmin unique_student_identifier: unique_student_identifier $.ajax + type: 'POST' dataType: 'json' url: @$btn_entrance_exam_task_history.data 'endpoint' data: send_data @@ -304,6 +313,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({problem_id: problem_to_reset}) $.ajax + type: 'POST' dataType: 'json' url: @$btn_reset_attempts_all.data 'endpoint' data: send_data @@ -330,6 +340,7 @@ class @StudentAdmin full_error_message = _.template(error_message)({problem_id: problem_to_reset}) $.ajax + type: 'POST' dataType: 'json' url: @$btn_rescore_problem_all.data 'endpoint' data: send_data @@ -348,6 +359,7 @@ class @StudentAdmin return @$request_response_error_all.text gettext("Please enter a problem location.") $.ajax + type: 'POST' dataType: 'json' url: @$btn_task_history_all.data 'endpoint' data: send_data diff --git a/lms/static/coffee/src/instructor_dashboard/util.coffee b/lms/static/coffee/src/instructor_dashboard/util.coffee index 1f2395efeb..5782107b22 100644 --- a/lms/static/coffee/src/instructor_dashboard/util.coffee +++ b/lms/static/coffee/src/instructor_dashboard/util.coffee @@ -336,6 +336,7 @@ class @PendingInstructorTasks reload_running_tasks_list: => list_endpoint = @$table_running_tasks.data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url: list_endpoint success: (data) => @@ -392,6 +393,7 @@ class ReportDownloads reload_report_downloads: -> endpoint = @$report_downloads_table.data 'endpoint' $.ajax + type: 'POST' dataType: 'json' url: endpoint success: (data) => diff --git a/lms/static/js/instructor_dashboard/ecommerce.js b/lms/static/js/instructor_dashboard/ecommerce.js index 69f0e76734..7f2f67d06c 100644 --- a/lms/static/js/instructor_dashboard/ecommerce.js +++ b/lms/static/js/instructor_dashboard/ecommerce.js @@ -45,6 +45,7 @@ var edx = edx || {}; $('input[name="user-enrollment-report"]').click(function(){ var url = $(this).data('endpoint'); $.ajax({ + type: 'POST', dataType: "json", url: url, success: function (data) { @@ -64,6 +65,7 @@ var edx = edx || {}; $('input[name="exec-summary-report"]').click(function(){ var url = $(this).data('endpoint'); $.ajax({ + type: 'POST', dataType: "json", url: url, success: function (data) { diff --git a/lms/static/js/spec/instructor_dashboard/student_admin_spec.js b/lms/static/js/spec/instructor_dashboard/student_admin_spec.js index 662930be63..ca07df9f8f 100644 --- a/lms/static/js/spec/instructor_dashboard/student_admin_spec.js +++ b/lms/static/js/spec/instructor_dashboard/student_admin_spec.js @@ -36,15 +36,15 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp // Spy on AJAX requests var requests = AjaxHelpers.requests(this); - studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier) + studentadmin.$field_entrance_exam_student_select_grade.val(unique_student_identifier); studentadmin.$btn_reset_entrance_exam_attempts.click(); // Verify that the client contacts the server to start instructor task var params = $.param({ unique_student_identifier: unique_student_identifier, delete_module: false }); - var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate a success response from the server AjaxHelpers.respondWithJson(requests, { @@ -63,8 +63,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp unique_student_identifier: unique_student_identifier, delete_module: false }); - var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate an error response from the server AjaxHelpers.respondWithError(requests, 400,{}); @@ -96,8 +96,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp var params = $.param({ unique_student_identifier: unique_student_identifier }); - var url = dashboard_api_url + '/rescore_entrance_exam?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/rescore_entrance_exam'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate a success response from the server AjaxHelpers.respondWithJson(requests, { @@ -115,8 +115,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp var params = $.param({ unique_student_identifier: unique_student_identifier }); - var url = dashboard_api_url + '/rescore_entrance_exam?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/rescore_entrance_exam'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate an error response from the server AjaxHelpers.respondWithError(requests, 400,{}); @@ -195,8 +195,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp unique_student_identifier: unique_student_identifier, delete_module: true }); - var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate a success response from the server AjaxHelpers.respondWithJson(requests, { @@ -215,8 +215,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp unique_student_identifier: unique_student_identifier, delete_module: true }); - var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/reset_student_attempts_for_entrance_exam'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate an error response from the server AjaxHelpers.respondWithError(requests, 400,{}); @@ -248,8 +248,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp var params = $.param({ unique_student_identifier: unique_student_identifier }); - var url = dashboard_api_url + '/list_entrance_exam_instructor_tasks?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/list_entrance_exam_instructor_tasks'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate a success response from the server AjaxHelpers.respondWithJson(requests, { @@ -279,8 +279,8 @@ define(['jquery', 'coffee/src/instructor_dashboard/student_admin', 'common/js/sp var params = $.param({ unique_student_identifier: unique_student_identifier }); - var url = dashboard_api_url + '/list_entrance_exam_instructor_tasks?' + params; - AjaxHelpers.expectJsonRequest(requests, 'GET', url); + var url = dashboard_api_url + '/list_entrance_exam_instructor_tasks'; + AjaxHelpers.expectPostRequest(requests, url, params); // Simulate an error response from the server AjaxHelpers.respondWithError(requests, 400,{}); diff --git a/lms/static/js/spec/staff_debug_actions_spec.js b/lms/static/js/spec/staff_debug_actions_spec.js index 3486fd7267..bbc63e1855 100644 --- a/lms/static/js/spec/staff_debug_actions_spec.js +++ b/lms/static/js/spec/staff_debug_actions_spec.js @@ -91,7 +91,7 @@ define([ spyOn($, 'ajax'); StaffDebug.reset(locationName, location); - expect($.ajax.calls.mostRecent().args[0].type).toEqual('GET'); + expect($.ajax.calls.mostRecent().args[0].type).toEqual('POST'); expect($.ajax.calls.mostRecent().args[0].data).toEqual({ 'problem_to_reset': location, 'unique_student_identifier': 'userman', @@ -110,7 +110,7 @@ define([ spyOn($, 'ajax'); StaffDebug.sdelete(locationName, location); - expect($.ajax.calls.mostRecent().args[0].type).toEqual('GET'); + expect($.ajax.calls.mostRecent().args[0].type).toEqual('POST'); expect($.ajax.calls.mostRecent().args[0].data).toEqual({ 'problem_to_reset': location, 'unique_student_identifier': 'userman', @@ -130,7 +130,7 @@ define([ spyOn($, 'ajax'); StaffDebug.rescore(locationName, location); - expect($.ajax.calls.mostRecent().args[0].type).toEqual('GET'); + expect($.ajax.calls.mostRecent().args[0].type).toEqual('POST'); expect($.ajax.calls.mostRecent().args[0].data).toEqual({ 'problem_to_reset': location, 'unique_student_identifier': 'userman', diff --git a/lms/static/js/staff_debug_actions.js b/lms/static/js/staff_debug_actions.js index 5774a43356..ef77a416cf 100644 --- a/lms/static/js/staff_debug_actions.js +++ b/lms/static/js/staff_debug_actions.js @@ -31,7 +31,7 @@ var StaffDebug = (function (){ 'delete_module': action.delete_module }; $.ajax({ - type: "GET", + type: "POST", url: get_url(action.method), data: pdata, success: function(data){