From 8f65ab53f03352600d3cf1a3af28b4d02afa7288 Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Thu, 28 Feb 2019 13:10:31 -0500 Subject: [PATCH] Revert "[BB-728] Add problem response report API" --- lms/djangoapps/instructor/permissions.py | 25 -- lms/djangoapps/instructor/tests/test_api.py | 119 ++------- lms/djangoapps/instructor/urls.py | 33 --- lms/djangoapps/instructor/views/api.py | 243 ++++++------------ lms/djangoapps/instructor/views/api_urls.py | 20 +- .../instructor/views/instructor_dashboard.py | 34 +-- lms/urls.py | 3 - 7 files changed, 111 insertions(+), 366 deletions(-) delete mode 100644 lms/djangoapps/instructor/permissions.py delete mode 100644 lms/djangoapps/instructor/urls.py diff --git a/lms/djangoapps/instructor/permissions.py b/lms/djangoapps/instructor/permissions.py deleted file mode 100644 index 5717a971e7..0000000000 --- a/lms/djangoapps/instructor/permissions.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Instructor permissions for class based views -""" - -from django.http import Http404 -from opaque_keys.edx.keys import CourseKey -from opaque_keys import InvalidKeyError -from rest_framework import permissions - -from courseware.access import has_access -from courseware.courses import get_course_by_id - - -class IsCourseStaff(permissions.BasePermission): - """ - Check if the requesting user is a course's staff member - """ - def has_permission(self, request, view): - try: - course_key = CourseKey.from_string(view.kwargs.get('course_id')) - except InvalidKeyError: - raise Http404() - - course = get_course_by_id(course_key) - return has_access(request.user, 'staff', course) diff --git a/lms/djangoapps/instructor/tests/test_api.py b/lms/djangoapps/instructor/tests/test_api.py index 1f8caec6fc..a43e487fdc 100644 --- a/lms/djangoapps/instructor/tests/test_api.py +++ b/lms/djangoapps/instructor/tests/test_api.py @@ -47,7 +47,6 @@ from courseware.tests.factories import ( 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 -from edx_oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory from lms.djangoapps.instructor.tests.utils import FakeContentTask, FakeEmail, FakeEmailInfo from lms.djangoapps.instructor.views.api import ( _split_input_list, @@ -142,7 +141,7 @@ REPORTS_DATA = ( }, { 'report_type': 'problem responses', - 'instructor_api_endpoint': 'api_instructor:get_problem_responses', + 'instructor_api_endpoint': 'get_problem_responses', 'task_api_endpoint': 'lms.djangoapps.instructor_task.api.submit_calculate_problem_responses_csv', 'extra_instructor_api_kwargs': {}, } @@ -178,7 +177,7 @@ INSTRUCTOR_POST_ENDPOINTS = set([ 'get_enrollment_report', 'get_exec_summary_report', 'get_grading_config', - 'api_instructor:get_problem_responses', + 'get_problem_responses', 'get_proctored_exam_results', 'get_registration_codes', 'get_student_enrollment_status', @@ -192,8 +191,8 @@ INSTRUCTOR_POST_ENDPOINTS = set([ 'list_entrance_exam_instructor_tasks', 'list_financial_report_downloads', 'list_forum_members', - 'api_instructor:list_instructor_tasks', - 'api_instructor:list_report_downloads', + 'list_instructor_tasks', + 'list_report_downloads', 'mark_student_can_skip_entrance_exam', 'modify_access', 'register_and_enroll_students', @@ -449,9 +448,9 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest {'unique_student_identifier': self.user.email, 'rolename': 'Moderator', 'action': 'allow'}), ('list_forum_members', {'rolename': FORUM_ROLE_COMMUNITY_TA}), ('send_email', {'send_to': '["staff"]', 'subject': 'test', 'message': 'asdf'}), - ('api_instructor:list_instructor_tasks', {}), + ('list_instructor_tasks', {}), ('list_background_email_tasks', {}), - ('api_instructor:list_report_downloads', {}), + ('list_report_downloads', {}), ('list_financial_report_downloads', {}), ('calculate_grades_csv', {}), ('get_students_features', {}), @@ -459,7 +458,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest ('get_students_who_may_enroll', {}), ('get_exec_summary_report', {}), ('get_proctored_exam_results', {}), - ('api_instructor:get_problem_responses', {}), + ('get_problem_responses', {}), ('export_ora2_data', {}), ('rescore_problem', {'problem_to_reset': self.problem_urlname, 'unique_student_identifier': self.user.email}), @@ -539,7 +538,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest mock_problem_key.course_key = self.course.id with patch.object(UsageKey, 'from_string') as patched_method: patched_method.return_value = mock_problem_key - self._access_endpoint('api_instructor:get_problem_responses', {}, 200, msg) + self._access_endpoint('get_problem_responses', {}, 200, msg) def test_staff_level(self): """ @@ -558,7 +557,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest # TODO: make these work if endpoint in ['update_forum_role_membership', 'list_forum_members']: continue - elif endpoint == 'api_instructor:get_problem_responses': + elif endpoint == 'get_problem_responses': self._access_problem_responses_endpoint( "Staff member should be allowed to access endpoint " + endpoint ) @@ -594,7 +593,7 @@ class TestInstructorAPIDenyLevels(SharedModuleStoreTestCase, LoginEnrollmentTest # TODO: make these work if endpoint in ['update_forum_role_membership']: continue - elif endpoint == 'api_instructor:get_problem_responses': + elif endpoint == 'get_problem_responses': self._access_problem_responses_endpoint( "Instructor should be allowed to access endpoint " + endpoint ) @@ -2899,7 +2898,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment message when users submit an invalid problem location. """ url = reverse( - 'api_instructor:get_problem_responses', + 'get_problem_responses', kwargs={'course_id': unicode(self.course.id)} ) problem_location = '' @@ -2934,7 +2933,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment message if CSV generation was started successfully. """ url = reverse( - 'api_instructor:get_problem_responses', + 'get_problem_responses', kwargs={'course_id': unicode(self.course.id)} ) problem_location = '' @@ -2954,7 +2953,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment message if CSV generation is already in progress. """ url = reverse( - 'api_instructor:get_problem_responses', + 'get_problem_responses', kwargs={'course_id': unicode(self.course.id)} ) task_type = 'problem_responses_csv' @@ -3281,7 +3280,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment """ ex_status = 503 ex_reason = 'Slow Down' - url = reverse('api_instructor:list_report_downloads', kwargs={'course_id': text_type(self.course.id)}) + url = reverse('list_report_downloads', kwargs={'course_id': text_type(self.course.id)}) with patch('openedx.core.storage.S3ReportStorage.listdir', side_effect=BotoServerError(ex_status, ex_reason)): response = self.client.post(url, {}) mock_error.assert_called_with( @@ -3295,7 +3294,7 @@ class TestInstructorAPILevelsDataDump(SharedModuleStoreTestCase, LoginEnrollment self.assertEqual(res_json, {"downloads": []}) def test_list_report_downloads(self): - url = reverse('api_instructor:list_report_downloads', kwargs={'course_id': text_type(self.course.id)}) + url = reverse('list_report_downloads', kwargs={'course_id': text_type(self.course.id)}) with patch('lms.djangoapps.instructor_task.models.DjangoStorageReportStore.links_for') as mock_links_for: mock_links_for.return_value = [ ('mock_file_name_1', 'https://1.mock.url'), @@ -4100,7 +4099,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC def test_list_instructor_tasks_running(self, act): """ Test list of all running tasks. """ act.return_value = self.tasks - url = reverse('api_instructor:list_instructor_tasks', kwargs={'course_id': text_type(self.course.id)}) + url = reverse('list_instructor_tasks', kwargs={'course_id': text_type(self.course.id)}) mock_factory = MockCompletionInfo() with patch( 'lms.djangoapps.instructor.views.instructor_task_helpers.get_task_completion_info' @@ -4142,7 +4141,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC def test_list_instructor_tasks_problem(self, act): """ Test list task history for problem. """ act.return_value = self.tasks - url = reverse('api_instructor:list_instructor_tasks', kwargs={'course_id': text_type(self.course.id)}) + url = reverse('list_instructor_tasks', kwargs={'course_id': text_type(self.course.id)}) mock_factory = MockCompletionInfo() with patch( 'lms.djangoapps.instructor.views.instructor_task_helpers.get_task_completion_info' @@ -4165,7 +4164,7 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC def test_list_instructor_tasks_problem_student(self, act): """ Test list task history for problem AND student. """ act.return_value = self.tasks - url = reverse('api_instructor:list_instructor_tasks', kwargs={'course_id': text_type(self.course.id)}) + url = reverse('list_instructor_tasks', kwargs={'course_id': text_type(self.course.id)}) mock_factory = MockCompletionInfo() with patch( 'lms.djangoapps.instructor.views.instructor_task_helpers.get_task_completion_info' @@ -4187,88 +4186,6 @@ class TestInstructorAPITaskLists(SharedModuleStoreTestCase, LoginEnrollmentTestC self.assertEqual(actual_tasks, expected_tasks) -class TestInstructorAPIOAuth(SharedModuleStoreTestCase, LoginEnrollmentTestCase): - """ - Test instructor API OAuth endpoint support. - """ - password = 'password' - - @classmethod - def setUpClass(cls): - super(TestInstructorAPIOAuth, cls).setUpClass() - cls.course = CourseFactory.create( - entrance_exam_id='i4x://{}/{}/chapter/Entrance_exam'.format('test_org', 'test_course') - ) - - def setUp(self): - super(TestInstructorAPIOAuth, self).setUp() - self.instructor = InstructorFactory(course_key=self.course.id) - - @patch.object(lms.djangoapps.instructor_task.api, 'get_running_instructor_tasks') - def test_list_instructor_tasks_oauth(self, act): - """ - Test if list_instructor_tasks endpoints supports OAuth - """ - act.return_value = [] - url = reverse('api_instructor:list_instructor_tasks', kwargs={'course_id': unicode(self.course.id)}) - # OAuth Client - oauth_client = ClientFactory.create() - access_token = AccessTokenFactory.create( - user=self.instructor, - client=oauth_client - ).token - headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + access_token - } - mock_factory = MockCompletionInfo() - with patch( - 'lms.djangoapps.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.post(url, {}, **headers) - self.assertEqual(response.status_code, 200) - - def test_get_problem_responses_oauth(self): - """ - Test whether get_problem_responses allows access via OAuth - """ - url = reverse('api_instructor:get_problem_responses', kwargs={'course_id': unicode(self.course.id)}) - problem_location = '' - - # OAuth Client - oauth_client = ClientFactory.create() - access_token = AccessTokenFactory.create( - user=self.instructor, - client=oauth_client - ).token - headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + access_token - } - - response = self.client.post(url, {'problem_location': problem_location}, **headers) - # Http error 400 means Bad request, but our user was authorized - self.assertEqual(response.status_code, 400) - - def test_list_report_downloads_oauth(self): - """ - Test whether list_report_downloads allows access via OAuth - """ - url = reverse('api_instructor:list_report_downloads', kwargs={'course_id': unicode(self.course.id)}) - - # OAuth Client - oauth_client = ClientFactory.create() - access_token = AccessTokenFactory.create( - user=self.instructor, - client=oauth_client - ).token - headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + access_token - } - - response = self.client.post(url, {}, **headers) - self.assertEqual(response.status_code, 200) - - @patch.object(lms.djangoapps.instructor_task.api, 'get_instructor_task_history', autospec=True) class TestInstructorEmailContentList(SharedModuleStoreTestCase, LoginEnrollmentTestCase): """ diff --git a/lms/djangoapps/instructor/urls.py b/lms/djangoapps/instructor/urls.py deleted file mode 100644 index 55b77fa318..0000000000 --- a/lms/djangoapps/instructor/urls.py +++ /dev/null @@ -1,33 +0,0 @@ -""" -Instructor API endpoint new urls. -""" - -from django.conf import settings -from django.conf.urls import url - -import lms.djangoapps.instructor.views.api - - -urlpatterns = [ - url( - r'^v1/course/{}/tasks$'.format( - settings.COURSE_ID_PATTERN, - ), - lms.djangoapps.instructor.views.api.InstructorTasks.as_view(), - name='list_instructor_tasks', - ), - url( - r'^v1/course/{}/reports$'.format( - settings.COURSE_ID_PATTERN, - ), - lms.djangoapps.instructor.views.api.ReportDownloadsList.as_view(), - name='list_report_downloads', - ), - url( - r'^v1/course/{}/reports/problem_responses$'.format( - settings.COURSE_ID_PATTERN, - ), - lms.djangoapps.instructor.views.api.ProblemResponseReport.as_view(), - name='get_problem_responses', - ), -] diff --git a/lms/djangoapps/instructor/views/api.py b/lms/djangoapps/instructor/views/api.py index fc1d2ddce0..42654ea15a 100644 --- a/lms/djangoapps/instructor/views/api.py +++ b/lms/djangoapps/instructor/views/api.py @@ -41,7 +41,6 @@ from edx_rest_framework_extensions.auth.session.authentication import SessionAut from opaque_keys import InvalidKeyError from opaque_keys.edx.keys import CourseKey, UsageKey from rest_framework import permissions, status -from rest_framework.authentication import SessionAuthentication from rest_framework.response import Response from rest_framework.views import APIView from six import text_type @@ -82,7 +81,6 @@ from lms.djangoapps.instructor.enrollment import ( send_mail_to_student, unenroll_email ) -from lms.djangoapps.instructor.permissions import IsCourseStaff from lms.djangoapps.instructor.views import INVOICE_KEY from lms.djangoapps.instructor.views.instructor_task_helpers import extract_email_features, extract_task_features from lms.djangoapps.instructor_task.api import submit_override_score @@ -996,67 +994,45 @@ def list_course_role_members(request, course_id): return JsonResponse(response_payload) -class ProblemResponseReport(DeveloperErrorViewMixin, APIView): +@transaction.non_atomic_requests +@require_POST +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_level('staff') +@common_exceptions_400 +def get_problem_responses(request, course_id): """ - **Use Cases** + Initiate generation of a CSV file containing all student answers + to a given problem. - Initiate generation of a CSV file containing all student answers - to a given problem. + Responds with JSON + {"status": "... status message ...", "task_id": created_task_UUID} - **Example Requests**: + if initiation is successful (or generation task is already running). - POST /api/instructor/v1/course/{}/reports - - **Response Values** - { - "task_id": "task_id" - "status": "... status message ..." - } - Responds with BadRequest if problem location is faulty. + Responds with BadRequest if problem location is faulty. """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, SessionAuthentication,) - permission_classes = (permissions.IsAuthenticated, IsCourseStaff) + course_key = CourseKey.from_string(course_id) + problem_location = request.POST.get('problem_location', '') + report_type = _('problem responses') - # The non-atomic decorator is required because this view calls a celery - # task which uses the 'outer_atomic' context manager. - @method_decorator(transaction.non_atomic_requests) - def dispatch(self, *args, **kwargs): # pylint: disable=W0221 - return super(ProblemResponseReport, self).dispatch(*args, **kwargs) + try: + problem_key = UsageKey.from_string(problem_location) + # Are we dealing with an "old-style" problem location? + run = problem_key.run + if not run: + problem_key = UsageKey.from_string(problem_location).map_into_course(course_key) + if problem_key.course_key != course_key: + raise InvalidKeyError(type(problem_key), problem_key) + except InvalidKeyError: + return JsonResponseBadRequest(_("Could not find problem with this location.")) - @cache_control(no_cache=True, no_store=True, must_revalidate=True) - def post(self, request, course_id): - """ - Initiate generation of a CSV file containing all student answers - to a given problem. - """ - course_key = CourseKey.from_string(course_id) - problem_location = request.POST.get('problem_location', '') - report_type = _('problem responses') + task = lms.djangoapps.instructor_task.api.submit_calculate_problem_responses_csv( + request, course_key, problem_location + ) + success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type) - try: - problem_key = UsageKey.from_string(problem_location) - # Are we dealing with an "old-style" problem location? - run = problem_key.run - if not run: - problem_key = problem_key.map_into_course(course_key) - if problem_key.course_key != course_key: - raise InvalidKeyError(type(problem_key), problem_key) - except InvalidKeyError: - return JsonResponseBadRequest(_("Could not find problem with this location.")) - - try: - task = lms.djangoapps.instructor_task.api.submit_calculate_problem_responses_csv( - request, course_key, problem_location - ) - success_status = SUCCESS_MESSAGE_TEMPLATE.format(report_type=report_type) - - return JsonResponse({ - "status": success_status, - "task_id": task.task_id - }) - except AlreadyRunningError as err: - error_message = unicode(err) - return JsonResponseBadRequest(error_message) + return JsonResponse({"status": success_status, "task_id": task.task_id}) @require_POST @@ -2425,84 +2401,50 @@ def list_email_content(request, course_id): # pylint: disable=unused-argument return JsonResponse(response_payload) -class InstructorTasks(DeveloperErrorViewMixin, APIView): +@require_POST +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_level('staff') +def list_instructor_tasks(request, course_id): """ - **Use Cases** + List instructor tasks. - Lists currently running instructor tasks - - **Parameters** + Takes optional query paremeters. - With no arguments, lists running tasks. - `problem_location_str` lists task history for problem - `problem_location_str` and `unique_student_identifier` lists task history for problem AND student (intersection) - - **Example Requests**: - - POST /api/instructor/v1/course/{}/tasks - - **Response Values** - { - "tasks": [ - { - "status": "Incomplete", - "task_type": "grade_problems", - "task_id": "2519ff31-22d9-4a62-91e2-55495895b355", - "created": "2019-01-15T18:00:15.902470+00:00", - "task_input": "{}", - "duration_sec": "unknown", - "task_message": "No status information available", - "requester": "staff", - "task_state": "PROGRESS" - } - ] - } """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, SessionAuthentication,) - permission_classes = (permissions.IsAuthenticated, IsCourseStaff) + course_id = CourseKey.from_string(course_id) + 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) - @cache_control(no_cache=True, no_store=True, must_revalidate=True) - def post(self, request, course_id): - """ - List instructor tasks. - """ - course_id = CourseKey.from_string(course_id) - 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) + if student and not problem_location_str: + return HttpResponseBadRequest( + "unique_student_identifier must accompany problem_location_str" + ) - if student and not problem_location_str: - return HttpResponseBadRequest( - "unique_student_identifier must accompany problem_location_str" - ) - - if problem_location_str: - try: - module_state_key = course_id.make_usage_key_from_deprecated_string(problem_location_str) - except InvalidKeyError: - return HttpResponseBadRequest() - if student: - # Specifying for a single student's history on this problem - tasks = lms.djangoapps.instructor_task.api.get_instructor_task_history( - course_id, - module_state_key, - student - ) - else: - # Specifying for single problem's history - tasks = lms.djangoapps.instructor_task.api.get_instructor_task_history( - course_id, - module_state_key - ) + if problem_location_str: + try: + module_state_key = UsageKey.from_string(problem_location_str).map_into_course(course_id) + except InvalidKeyError: + return HttpResponseBadRequest() + if student: + # Specifying for a single student's history on this problem + tasks = lms.djangoapps.instructor_task.api.get_instructor_task_history(course_id, module_state_key, student) else: - # If no problem or student, just get currently running tasks - tasks = lms.djangoapps.instructor_task.api.get_running_instructor_tasks(course_id) + # Specifying for single problem's history + tasks = lms.djangoapps.instructor_task.api.get_instructor_task_history(course_id, module_state_key) + else: + # If no problem or student, just get currently running tasks + tasks = lms.djangoapps.instructor_task.api.get_running_instructor_tasks(course_id) - response_payload = { - 'tasks': map(extract_task_features, tasks), - } - return JsonResponse(response_payload) + response_payload = { + 'tasks': map(extract_task_features, tasks), + } + return JsonResponse(response_payload) @require_POST @@ -2547,49 +2489,28 @@ def list_entrance_exam_instructor_tasks(request, course_id): return JsonResponse(response_payload) -class ReportDownloadsList(DeveloperErrorViewMixin, APIView): +@require_POST +@ensure_csrf_cookie +@cache_control(no_cache=True, no_store=True, must_revalidate=True) +@require_level('staff') +def list_report_downloads(request, course_id): """ - **Use Cases** + List grade CSV files that are available for download for this course. - Lists reports available for download - - **Example Requests**: - - POST /api/instructor/v1/course/{}/tasks - - **Response Values** - { - "downloads": [ - { - "url": "https://1.mock.url", - "link": "mock_file_name_1", - "name": "mock_file_name_1" - } - ] - } + Takes the following query parameters: + - (optional) report_name - name of the report """ - authentication_classes = (JwtAuthentication, OAuth2AuthenticationAllowInactiveUser, SessionAuthentication,) - permission_classes = (permissions.IsAuthenticated, IsCourseStaff) + course_id = CourseKey.from_string(course_id) + report_store = ReportStore.from_config(config_name='GRADES_DOWNLOAD') + report_name = request.POST.get("report_name", None) - @cache_control(no_cache=True, no_store=True, must_revalidate=True) - def post(self, request, course_id): - """ - List grade CSV files that are available for download for this course. - - Takes the following query parameters: - - (optional) report_name - name of the report - """ - course_id = CourseKey.from_string(course_id) - report_store = ReportStore.from_config(config_name='GRADES_DOWNLOAD') - report_name = request.POST.get("report_name", None) - - response_payload = { - 'downloads': [ - dict(name=name, url=url, link=HTML(u'{}').format(HTML(url), Text(name))) - for name, url in report_store.links_for(course_id) if report_name is None or name == report_name - ] - } - return JsonResponse(response_payload) + response_payload = { + 'downloads': [ + dict(name=name, url=url, link=HTML(u'{}').format(HTML(url), Text(name))) + for name, url in report_store.links_for(course_id) if report_name is None or name == report_name + ] + } + return JsonResponse(response_payload) @require_POST diff --git a/lms/djangoapps/instructor/views/api_urls.py b/lms/djangoapps/instructor/views/api_urls.py index 610e190067..674c9419eb 100644 --- a/lms/djangoapps/instructor/views/api_urls.py +++ b/lms/djangoapps/instructor/views/api_urls.py @@ -12,6 +12,7 @@ urlpatterns = [ url(r'^list_course_role_members$', api.list_course_role_members, name='list_course_role_members'), url(r'^modify_access$', api.modify_access, name='modify_access'), url(r'^bulk_beta_modify_access$', api.bulk_beta_modify_access, name='bulk_beta_modify_access'), + url(r'^get_problem_responses$', api.get_problem_responses, name='get_problem_responses'), url(r'^get_grading_config$', api.get_grading_config, name='get_grading_config'), url(r'^get_students_features(?P/csv)?$', api.get_students_features, name='get_students_features'), url(r'^get_issued_certificates/$', api.get_issued_certificates, name='get_issued_certificates'), @@ -33,6 +34,7 @@ urlpatterns = [ name='list_entrance_exam_instructor_tasks'), url(r'^mark_student_can_skip_entrance_exam', api.mark_student_can_skip_entrance_exam, name='mark_student_can_skip_entrance_exam'), + url(r'^list_instructor_tasks$', api.list_instructor_tasks, name='list_instructor_tasks'), url(r'^list_background_email_tasks$', api.list_background_email_tasks, name='list_background_email_tasks'), url(r'^list_email_content$', api.list_email_content, name='list_email_content'), url(r'^list_forum_members$', api.list_forum_members, name='list_forum_members'), @@ -47,6 +49,7 @@ urlpatterns = [ url(r'^get_proctored_exam_results$', api.get_proctored_exam_results, name='get_proctored_exam_results'), # Grade downloads... + url(r'^list_report_downloads$', api.list_report_downloads, name='list_report_downloads'), url(r'calculate_grades_csv$', api.calculate_grades_csv, name='calculate_grades_csv'), url(r'problem_grade_report$', api.problem_grade_report, name='problem_grade_report'), @@ -88,21 +91,4 @@ urlpatterns = [ url(r'^generate_bulk_certificate_exceptions', api.generate_bulk_certificate_exceptions, name='generate_bulk_certificate_exceptions'), url(r'^certificate_invalidation_view/$', api.certificate_invalidation_view, name='certificate_invalidation_view'), - - # Instructor endpoints moved to the new API, kept here for backwards compatibility - url( - r'^list_instructor_tasks$', - api.InstructorTasks.as_view(), - name='list_instructor_tasks_old', - ), - url( - r'^get_problem_responses$', - api.ProblemResponseReport.as_view(), - name='get_problem_responses_old', - ), - url( - r'^list_report_downloads$', - api.ReportDownloadsList.as_view(), - name='list_report_downloads_old', - ), ] diff --git a/lms/djangoapps/instructor/views/instructor_dashboard.py b/lms/djangoapps/instructor/views/instructor_dashboard.py index b2a9193421..c60fc0167f 100644 --- a/lms/djangoapps/instructor/views/instructor_dashboard.py +++ b/lms/djangoapps/instructor/views/instructor_dashboard.py @@ -295,10 +295,7 @@ def _section_e_commerce(course, access, paid_mode, coupons_enabled, reports_enab 'exec_summary_report_url': reverse('get_exec_summary_report', kwargs={'course_id': unicode(course_key)}), 'list_financial_report_downloads_url': reverse('list_financial_report_downloads', kwargs={'course_id': unicode(course_key)}), - 'list_instructor_tasks_url': reverse( - 'api_instructor:list_instructor_tasks', - kwargs={'course_id': unicode(course_key)} - ), + 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}), 'look_up_registration_code': reverse('look_up_registration_code', kwargs={'course_id': unicode(course_key)}), 'coupons': coupons, 'sales_admin': access['sales_admin'], @@ -397,7 +394,7 @@ def _section_certificates(course): kwargs={'course_id': course.id} ), 'list_instructor_tasks_url': reverse( - 'api_instructor:list_instructor_tasks', + 'list_instructor_tasks', kwargs={'course_id': course.id} ), } @@ -457,10 +454,7 @@ def _section_course_info(course, access): 'start_date': course.start, 'end_date': course.end, 'num_sections': len(course.children), - 'list_instructor_tasks_url': reverse( - 'api_instructor:list_instructor_tasks', - kwargs={'course_id': unicode(course_key)} - ), + 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}), } if settings.FEATURES.get('DISPLAY_ANALYTICS_ENROLLMENTS'): @@ -594,10 +588,7 @@ def _section_student_admin(course, access): 'mark_student_can_skip_entrance_exam', kwargs={'course_id': unicode(course_key)}, ), - 'list_instructor_tasks_url': reverse( - 'api_instructor:list_instructor_tasks', - kwargs={'course_id': unicode(course_key)} - ), + 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}), 'list_entrace_exam_instructor_tasks_url': reverse('list_entrance_exam_instructor_tasks', kwargs={'course_id': unicode(course_key)}), 'spoc_gradebook_url': reverse('spoc_gradebook', kwargs={'course_id': unicode(course_key)}), @@ -636,10 +627,7 @@ def _section_data_download(course, access): 'section_display_name': _('Data Download'), 'access': access, 'show_generate_proctored_exam_report_button': show_proctored_report_button, - 'get_problem_responses_url': reverse( - 'api_instructor:get_problem_responses', - kwargs={'course_id': unicode(course_key)} - ), + 'get_problem_responses_url': reverse('get_problem_responses', kwargs={'course_id': unicode(course_key)}), 'get_grading_config_url': reverse('get_grading_config', kwargs={'course_id': unicode(course_key)}), 'get_students_features_url': reverse('get_students_features', kwargs={'course_id': unicode(course_key)}), 'get_issued_certificates_url': reverse( @@ -650,14 +638,8 @@ def _section_data_download(course, access): ), 'get_anon_ids_url': reverse('get_anon_ids', kwargs={'course_id': unicode(course_key)}), 'list_proctored_results_url': reverse('get_proctored_exam_results', kwargs={'course_id': unicode(course_key)}), - 'list_instructor_tasks_url': reverse( - 'api_instructor:list_instructor_tasks', - kwargs={'course_id': unicode(course_key)} - ), - 'list_report_downloads_url': reverse( - 'api_instructor:list_report_downloads', - kwargs={'course_id': unicode(course_key)} - ), + 'list_instructor_tasks_url': reverse('list_instructor_tasks', kwargs={'course_id': unicode(course_key)}), + 'list_report_downloads_url': reverse('list_report_downloads', kwargs={'course_id': unicode(course_key)}), 'calculate_grades_csv_url': reverse('calculate_grades_csv', kwargs={'course_id': unicode(course_key)}), 'problem_grade_report_url': reverse('problem_grade_report', kwargs={'course_id': unicode(course_key)}), 'course_has_survey': True if course.course_survey_name else False, @@ -714,7 +696,7 @@ def _section_send_email(course, access): 'course_modes': course_modes, 'default_cohort_name': DEFAULT_COHORT_NAME, 'list_instructor_tasks_url': reverse( - 'api_instructor:list_instructor_tasks', kwargs={'course_id': unicode(course_key)} + 'list_instructor_tasks', kwargs={'course_id': unicode(course_key)} ), 'email_background_tasks_url': reverse( 'list_background_email_tasks', kwargs={'course_id': unicode(course_key)} diff --git a/lms/urls.py b/lms/urls.py index a542700a85..ba2be98a95 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -505,9 +505,6 @@ urlpatterns += [ include(COURSE_URLS) ), - # Instructor API (accessible via OAuth) - url(r'^api/instructor/', include('lms.djangoapps.instructor.urls', namespace='api_instructor')), - # Discussions Management url( r'^courses/{}/discussions/settings$'.format(