diff --git a/lms/djangoapps/course_structure_api/__init__.py b/lms/djangoapps/course_structure_api/__init__.py deleted file mode 100644 index bbc526f894606d380eae37889303949574dc955d..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""" Course structure API """ diff --git a/lms/djangoapps/course_structure_api/urls.py b/lms/djangoapps/course_structure_api/urls.py deleted file mode 100644 index 658e3122406f9d8b870433edd08877bdaf6cbc43..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Course Structure API URI specification. - -Patterns here should simply point to version-specific patterns. -""" -from django.conf.urls import include, url - -urlpatterns = [ - url(r'^v0/', include('course_structure_api.v0.urls', namespace='v0')) -] diff --git a/lms/djangoapps/course_structure_api/v0/__init__.py b/lms/djangoapps/course_structure_api/v0/__init__.py deleted file mode 100644 index eee3a676f438f20a86fdd43e0915c3821bce8bce..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/v0/__init__.py +++ /dev/null @@ -1 +0,0 @@ -""" Version 0 """ diff --git a/lms/djangoapps/course_structure_api/v0/serializers.py b/lms/djangoapps/course_structure_api/v0/serializers.py deleted file mode 100644 index 0c9caeba0feba37f3a5b32a2921c130606d7e654..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/v0/serializers.py +++ /dev/null @@ -1,41 +0,0 @@ -""" Django REST Framework Serializers """ - -from django.core.urlresolvers import reverse -from rest_framework import serializers - -from openedx.core.lib.courses import course_image_url - - -class CourseSerializer(serializers.Serializer): - """ Serializer for Courses """ - id = serializers.CharField() # pylint: disable=invalid-name - name = serializers.CharField(source='display_name') - category = serializers.CharField() - org = serializers.SerializerMethodField() - run = serializers.SerializerMethodField() - course = serializers.SerializerMethodField() - uri = serializers.SerializerMethodField() - image_url = serializers.SerializerMethodField() - start = serializers.DateTimeField() - end = serializers.DateTimeField() - - def get_org(self, course): - """ Gets the course org """ - return course.id.org - - def get_run(self, course): - """ Gets the course run """ - return course.id.run - - def get_course(self, course): - """ Gets the course """ - return course.id.course - - def get_uri(self, course): - """ Builds course detail uri """ - request = self.context['request'] - return request.build_absolute_uri(reverse('course_structure_api:v0:detail', kwargs={'course_id': course.id})) - - def get_image_url(self, course): - """ Get the course image URL """ - return course_image_url(course) diff --git a/lms/djangoapps/course_structure_api/v0/tests.py b/lms/djangoapps/course_structure_api/v0/tests.py deleted file mode 100644 index 3f4338769f0b572691ba405e6fda4139d8dffdbd..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/v0/tests.py +++ /dev/null @@ -1,453 +0,0 @@ -""" -Run these tests @ Devstack: - paver test_system -s lms --fasttest --verbose --test-id=lms/djangoapps/course_structure_api -""" -# pylint: disable=missing-docstring,invalid-name,maybe-no-member,attribute-defined-outside-init -from datetime import datetime - -from django.core.urlresolvers import reverse -from edx_oauth2_provider.tests.factories import AccessTokenFactory, ClientFactory -from mock import Mock, patch -from opaque_keys.edx.locator import CourseLocator - -from capa.tests.response_xml_factory import MultipleChoiceResponseXMLFactory -from courseware.tests.factories import GlobalStaffFactory, StaffFactory -from openedx.core.djangoapps.content.course_structures.models import CourseStructure -from openedx.core.djangoapps.content.course_structures.tasks import update_course_structure -from xmodule.error_module import ErrorDescriptor -from xmodule.modulestore import ModuleStoreEnum -from xmodule.modulestore.tests.django_utils import SharedModuleStoreTestCase -from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory -from xmodule.modulestore.xml import CourseLocationManager -from xmodule.tests import get_test_system - -TEST_SERVER_HOST = 'http://testserver' - - -class CourseViewTestsMixin(object): - """ - Mixin for course view tests. - """ - view = None - - raw_grader = [ - { - "min_count": 24, - "weight": 0.2, - "type": "Homework", - "drop_count": 0, - "short_label": "HW" - }, - { - "min_count": 4, - "weight": 0.8, - "type": "Exam", - "drop_count": 0, - "short_label": "Exam" - } - ] - - def setUp(self): - super(CourseViewTestsMixin, self).setUp() - self.create_user_and_access_token() - - def create_user(self): - self.user = GlobalStaffFactory.create() - - def create_user_and_access_token(self): - self.create_user() - self.oauth_client = ClientFactory.create() - self.access_token = AccessTokenFactory.create(user=self.user, client=self.oauth_client).token - - @classmethod - def create_course_data(cls): - cls.invalid_course_id = 'foo/bar/baz' - cls.course = CourseFactory.create(display_name='An Introduction to API Testing', raw_grader=cls.raw_grader) - cls.course_id = unicode(cls.course.id) - with cls.store.bulk_operations(cls.course.id, emit_signals=False): - cls.sequential = ItemFactory.create( - category="sequential", - parent_location=cls.course.location, - display_name="Lesson 1", - format="Homework", - graded=True - ) - - factory = MultipleChoiceResponseXMLFactory() - args = {'choices': [False, True, False]} - problem_xml = factory.build_xml(**args) - cls.problem = ItemFactory.create( - category="problem", - parent_location=cls.sequential.location, - display_name="Problem 1", - format="Homework", - data=problem_xml, - ) - - cls.video = ItemFactory.create( - category="video", - parent_location=cls.sequential.location, - display_name="Video 1", - ) - - cls.html = ItemFactory.create( - category="html", - parent_location=cls.sequential.location, - display_name="HTML 1", - ) - - cls.empty_course = CourseFactory.create( - start=datetime(2014, 6, 16, 14, 30), - end=datetime(2015, 1, 16), - org="MTD", - # Use mongo so that we can get a test with the deprecated key format. - default_store=ModuleStoreEnum.Type.mongo - ) - - def build_absolute_url(self, path=None): - """ Build absolute URL pointing to test server. - :param path: Path to append to the URL - """ - url = TEST_SERVER_HOST - - if path: - url += path - - return url - - def assertValidResponseCourse(self, data, course): - """ Determines if the given response data (dict) matches the specified course. """ - - course_key = course.id - self.assertEqual(data['id'], unicode(course_key)) - self.assertEqual(data['name'], course.display_name) - self.assertEqual(data['course'], course_key.course) - self.assertEqual(data['org'], course_key.org) - self.assertEqual(data['run'], course_key.run) - - uri = self.build_absolute_url( - reverse('course_structure_api:v0:detail', kwargs={'course_id': unicode(course_key)})) - self.assertEqual(data['uri'], uri) - - def http_get(self, uri, **headers): - """Submit an HTTP GET request""" - - default_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + self.access_token - } - default_headers.update(headers) - - response = self.client.get(uri, follow=True, **default_headers) - return response - - def http_get_for_course(self, course_id=None, **headers): - """Submit an HTTP GET request to the view for the given course""" - - return self.http_get( - reverse(self.view, kwargs={'course_id': course_id or self.course_id}), - **headers - ) - - def test_not_authenticated(self): - """ - Verify that access is denied to non-authenticated users. - """ - raise NotImplementedError - - def test_not_authorized(self): - """ - Verify that access is denied to non-authorized users. - """ - raise NotImplementedError - - -class CourseDetailTestMixin(object): - """ - Mixin for views utilizing only the course_id kwarg. - """ - view_supports_debug_mode = True - - def test_get_invalid_course(self): - """ - The view should return a 404 if the course ID is invalid. - """ - response = self.http_get_for_course(self.invalid_course_id) - self.assertEqual(response.status_code, 404) - - def test_get(self): - """ - The view should return a 200 if the course ID is valid. - """ - response = self.http_get_for_course() - self.assertEqual(response.status_code, 200) - - # Return the response so child classes do not have to repeat the request. - return response - - def test_not_authenticated(self): - """ The view should return HTTP status 401 if no user is authenticated. """ - # HTTP 401 should be returned if the user is not authenticated. - response = self.http_get_for_course(HTTP_AUTHORIZATION=None) - self.assertEqual(response.status_code, 401) - - def test_not_authorized(self): - user = StaffFactory(course_key=self.course.id) - access_token = AccessTokenFactory.create(user=user, client=self.oauth_client).token - auth_header = 'Bearer ' + access_token - - # Access should be granted if the proper access token is supplied. - response = self.http_get_for_course(HTTP_AUTHORIZATION=auth_header) - self.assertEqual(response.status_code, 200) - - # Access should be denied if the user is not course staff. - response = self.http_get_for_course(course_id=unicode(self.empty_course.id), HTTP_AUTHORIZATION=auth_header) - self.assertEqual(response.status_code, 404) - - -class CourseListTests(CourseViewTestsMixin, SharedModuleStoreTestCase): - view = 'course_structure_api:v0:list' - - @classmethod - def setUpClass(cls): - super(CourseListTests, cls).setUpClass() - cls.create_course_data() - - def test_get(self): - """ - The view should return a list of all courses. - """ - response = self.http_get(reverse(self.view)) - self.assertEqual(response.status_code, 200) - data = response.data - courses = data['results'] - self.assertEqual(len(courses), 2) - self.assertEqual(data['count'], 2) - self.assertEqual(data['num_pages'], 1) - - self.assertValidResponseCourse(courses[0], self.empty_course) - self.assertValidResponseCourse(courses[1], self.course) - - def test_get_with_pagination(self): - """ - The view should return a paginated list of courses. - """ - url = "{}?page_size=1".format(reverse(self.view)) - response = self.http_get(url) - self.assertEqual(response.status_code, 200) - - courses = response.data['results'] - self.assertEqual(len(courses), 1) - self.assertValidResponseCourse(courses[0], self.empty_course) - - def test_get_filtering(self): - """ - The view should return a list of details for the specified courses. - """ - url = "{}?course_id={}".format(reverse(self.view), self.course_id) - response = self.http_get(url) - self.assertEqual(response.status_code, 200) - - courses = response.data['results'] - self.assertEqual(len(courses), 1) - self.assertValidResponseCourse(courses[0], self.course) - - def test_not_authenticated(self): - response = self.http_get(reverse(self.view), HTTP_AUTHORIZATION=None) - self.assertEqual(response.status_code, 401) - - def test_not_authorized(self): - """ - Unauthorized users should get an empty list. - """ - user = StaffFactory(course_key=self.course.id) - access_token = AccessTokenFactory.create(user=user, client=self.oauth_client).token - auth_header = 'Bearer ' + access_token - - # Data should be returned if the user is authorized. - response = self.http_get(reverse(self.view), HTTP_AUTHORIZATION=auth_header) - self.assertEqual(response.status_code, 200) - - url = "{}?course_id={}".format(reverse(self.view), self.course_id) - response = self.http_get(url, HTTP_AUTHORIZATION=auth_header) - self.assertEqual(response.status_code, 200) - data = response.data['results'] - self.assertEqual(len(data), 1) - self.assertEqual(data[0]['name'], self.course.display_name) - - # The view should return an empty list if the user cannot access any courses. - url = "{}?course_id={}".format(reverse(self.view), unicode(self.empty_course.id)) - response = self.http_get(url, HTTP_AUTHORIZATION=auth_header) - self.assertEqual(response.status_code, 200) - self.assertDictContainsSubset({'count': 0, u'results': []}, response.data) - - def test_course_error(self): - """ - Ensure the view still returns results even if get_courses() returns an ErrorDescriptor. The ErrorDescriptor - should be filtered out. - """ - - error_descriptor = ErrorDescriptor.from_xml( - '<course></course>', - get_test_system(), - CourseLocationManager(CourseLocator(org='org', course='course', run='run')), - None - ) - - descriptors = [error_descriptor, self.empty_course, self.course] - - with patch('xmodule.modulestore.mixed.MixedModuleStore.get_courses', Mock(return_value=descriptors)): - self.test_get() - - -class CourseDetailTests(CourseDetailTestMixin, CourseViewTestsMixin, SharedModuleStoreTestCase): - view = 'course_structure_api:v0:detail' - - @classmethod - def setUpClass(cls): - super(CourseDetailTests, cls).setUpClass() - cls.create_course_data() - - def test_get(self): - response = super(CourseDetailTests, self).test_get() - self.assertValidResponseCourse(response.data, self.course) - - -class CourseStructureTests(CourseDetailTestMixin, CourseViewTestsMixin, SharedModuleStoreTestCase): - view = 'course_structure_api:v0:structure' - - @classmethod - def setUpClass(cls): - super(CourseStructureTests, cls).setUpClass() - cls.create_course_data() - - def setUp(self): - super(CourseStructureTests, self).setUp() - - # Ensure course structure exists for the course - update_course_structure(unicode(self.course.id)) - - def test_get(self): - """ - If the course structure exists in the database, the view should return the data. Otherwise, the view should - initiate an asynchronous course structure generation and return a 503. - """ - - # Attempt to retrieve data for a course without stored structure - CourseStructure.objects.all().delete() - self.assertFalse(CourseStructure.objects.filter(course_id=self.course.id).exists()) - response = self.http_get_for_course() - self.assertEqual(response.status_code, 503) - self.assertEqual(response['Retry-After'], '120') - - # Course structure generation shouldn't take long. Generate the data and try again. - self.assertTrue(CourseStructure.objects.filter(course_id=self.course.id).exists()) - response = self.http_get_for_course() - self.assertEqual(response.status_code, 200) - - blocks = {} - - def add_block(xblock): - children = xblock.get_children() - blocks[unicode(xblock.location)] = { - u'id': unicode(xblock.location), - u'type': xblock.category, - u'parent': None, - u'display_name': xblock.display_name, - u'format': xblock.format, - u'graded': xblock.graded, - u'children': [unicode(child.location) for child in children] - } - - for child in children: - add_block(child) - - course = self.store.get_course(self.course.id, depth=None) - add_block(course) - - expected = { - u'root': unicode(self.course.location), - u'blocks': blocks - } - - self.maxDiff = None - self.assertDictEqual(response.data, expected) - - -class CourseGradingPolicyTests(CourseDetailTestMixin, CourseViewTestsMixin, SharedModuleStoreTestCase): - view = 'course_structure_api:v0:grading_policy' - - @classmethod - def setUpClass(cls): - super(CourseGradingPolicyTests, cls).setUpClass() - cls.create_course_data() - - def test_get(self): - """ - The view should return grading policy for a course. - """ - response = super(CourseGradingPolicyTests, self).test_get() - - expected = [ - { - "count": 24, - "weight": 0.2, - "assignment_type": "Homework", - "dropped": 0 - }, - { - "count": 4, - "weight": 0.8, - "assignment_type": "Exam", - "dropped": 0 - } - ] - self.assertListEqual(response.data, expected) - - -class CourseGradingPolicyMissingFieldsTests(CourseDetailTestMixin, CourseViewTestsMixin, SharedModuleStoreTestCase): - view = 'course_structure_api:v0:grading_policy' - - # Update the raw grader to have missing keys - raw_grader = [ - { - "min_count": 24, - "weight": 0.2, - "type": "Homework", - "drop_count": 0, - "short_label": "HW" - }, - { - # Deleted "min_count" key - "weight": 0.8, - "type": "Exam", - "drop_count": 0, - "short_label": "Exam" - } - ] - - @classmethod - def setUpClass(cls): - super(CourseGradingPolicyMissingFieldsTests, cls).setUpClass() - cls.create_course_data() - - def test_get(self): - """ - The view should return grading policy for a course. - """ - response = super(CourseGradingPolicyMissingFieldsTests, self).test_get() - - expected = [ - { - "count": 24, - "weight": 0.2, - "assignment_type": "Homework", - "dropped": 0 - }, - { - "count": None, - "weight": 0.8, - "assignment_type": "Exam", - "dropped": 0 - } - ] - self.assertListEqual(response.data, expected) diff --git a/lms/djangoapps/course_structure_api/v0/urls.py b/lms/djangoapps/course_structure_api/v0/urls.py deleted file mode 100644 index 3d2861297e25688f979a0d5180fe2cc3c90dd50b..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/v0/urls.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Courses Structure API v0 URI specification -""" -from django.conf import settings -from django.conf.urls import url - -from course_structure_api.v0 import views - -COURSE_ID_PATTERN = settings.COURSE_ID_PATTERN - -urlpatterns = [ - url(r'^courses/$', views.CourseList.as_view(), name='list'), - url(r'^courses/{}/$'.format(COURSE_ID_PATTERN), views.CourseDetail.as_view(), name='detail'), - url(r'^course_structures/{}/$'.format(COURSE_ID_PATTERN), views.CourseStructure.as_view(), name='structure'), - url( - r'^grading_policies/{}/$'.format(COURSE_ID_PATTERN), - views.CourseGradingPolicy.as_view(), - name='grading_policy' - ), -] diff --git a/lms/djangoapps/course_structure_api/v0/views.py b/lms/djangoapps/course_structure_api/v0/views.py deleted file mode 100644 index 3a799297af6bfd1d654dcce0cda5acfc09c6be64..0000000000000000000000000000000000000000 --- a/lms/djangoapps/course_structure_api/v0/views.py +++ /dev/null @@ -1,293 +0,0 @@ -""" API implementation for course-oriented interactions. """ - -import logging - -from django.conf import settings -from django.http import Http404 -from edx_rest_framework_extensions.authentication import JwtAuthentication -from opaque_keys.edx.keys import CourseKey -from rest_framework.authentication import SessionAuthentication -from rest_framework.exceptions import AuthenticationFailed -from rest_framework.generics import RetrieveAPIView, ListAPIView -from rest_framework.permissions import IsAuthenticated -from rest_framework.response import Response -from rest_framework_oauth.authentication import OAuth2Authentication -from xmodule.modulestore.django import modulestore - -from course_structure_api.v0 import serializers -from courseware import courses -from courseware.access import has_access -from openedx.core.djangoapps.content.course_structures.api.v0 import api, errors -from openedx.core.lib.exceptions import CourseNotFoundError -from student.roles import CourseInstructorRole, CourseStaffRole - -log = logging.getLogger(__name__) - - -class CourseViewMixin(object): - """ - Mixin for views dealing with course content. Also handles authorization and authentication. - """ - lookup_field = 'course_id' - authentication_classes = (JwtAuthentication, OAuth2Authentication, SessionAuthentication,) - permission_classes = (IsAuthenticated,) - - def get_course_or_404(self): - """ - Retrieves the specified course, or raises an Http404 error if it does not exist. - Also checks to ensure the user has permissions to view the course - """ - try: - course_id = self.kwargs.get('course_id') - course_key = CourseKey.from_string(course_id) - course = courses.get_course(course_key) - self.check_course_permissions(self.request.user, course_key) - - return course - except ValueError: - raise Http404 - - @staticmethod - def course_check(func): - """Decorator responsible for catching errors finding and returning a 404 if the user does not have access - to the API function. - - :param func: function to be wrapped - :returns: the wrapped function - """ - - def func_wrapper(self, *args, **kwargs): - """Wrapper function for this decorator. - - :param *args: the arguments passed into the function - :param **kwargs: the keyword arguments passed into the function - :returns: the result of the wrapped function - """ - try: - course_id = self.kwargs.get('course_id') - self.course_key = CourseKey.from_string(course_id) - self.check_course_permissions(self.request.user, self.course_key) - return func(self, *args, **kwargs) - except CourseNotFoundError: - raise Http404 - - return func_wrapper - - def user_can_access_course(self, user, course): - """ - Determines if the user is staff or an instructor for the course. - Always returns True if DEBUG mode is enabled. - """ - return bool( - settings.DEBUG - or has_access(user, CourseStaffRole.ROLE, course) - or has_access(user, CourseInstructorRole.ROLE, course) - ) - - def check_course_permissions(self, user, course): - """ - Checks if the request user can access the course. - Raises 404 if the user does not have course access. - """ - if not self.user_can_access_course(user, course): - raise Http404 - - def perform_authentication(self, request): - """ - Ensures that the user is authenticated (e.g. not an AnonymousUser), unless DEBUG mode is enabled. - """ - super(CourseViewMixin, self).perform_authentication(request) - if request.user.is_anonymous() and not settings.DEBUG: - raise AuthenticationFailed - - -class CourseList(CourseViewMixin, ListAPIView): - """ - **Use Case** - - Get a paginated list of courses in the edX Platform. - - The list can be filtered by course_id. - - Each page in the list can contain up to 10 courses. - - **Example Requests** - - GET /api/course_structure/v0/courses/ - - GET /api/course_structure/v0/courses/?course_id={course_id1},{course_id2} - - **Response Values** - - * count: The number of courses in the edX platform. - - * next: The URI to the next page of courses. - - * previous: The URI to the previous page of courses. - - * num_pages: The number of pages listing courses. - - * results: A list of courses returned. Each collection in the list - contains these fields. - - * id: The unique identifier for the course. - - * name: The name of the course. - - * category: The type of content. In this case, the value is always - "course". - - * org: The organization specified for the course. - - * run: The run of the course. - - * course: The course number. - - * uri: The URI to use to get details of the course. - - * image_url: The URI for the course's main image. - - * start: The course start date. - - * end: The course end date. If course end date is not specified, the - value is null. - """ - serializer_class = serializers.CourseSerializer - - def get_queryset(self): - course_ids = self.request.query_params.get('course_id', None) - - results = [] - if course_ids: - course_ids = course_ids.split(',') - for course_id in course_ids: - course_key = CourseKey.from_string(course_id) - course_descriptor = courses.get_course(course_key) - results.append(course_descriptor) - else: - results = modulestore().get_courses() - - # Ensure only course descriptors are returned. - results = (course for course in results if course.scope_ids.block_type == 'course') - - # Ensure only courses accessible by the user are returned. - results = (course for course in results if self.user_can_access_course(self.request.user, course)) - - # Sort the results in a predictable manner. - return sorted(results, key=lambda course: unicode(course.id)) - - -class CourseDetail(CourseViewMixin, RetrieveAPIView): - """ - **Use Case** - - Get details for a specific course. - - **Example Request**: - - GET /api/course_structure/v0/courses/{course_id}/ - - **Response Values** - - * id: The unique identifier for the course. - - * name: The name of the course. - - * category: The type of content. - - * org: The organization that is offering the course. - - * run: The run of the course. - - * course: The course number. - - * uri: The URI to use to get details about the course. - - * image_url: The URI for the course's main image. - - * start: The course start date. - - * end: The course end date. If course end date is not specified, the - value is null. - """ - serializer_class = serializers.CourseSerializer - - def get_object(self, queryset=None): - return self.get_course_or_404() - - -class CourseStructure(CourseViewMixin, RetrieveAPIView): - """ - **Use Case** - - Get the course structure. This endpoint returns all blocks in the - course. - - **Example requests**: - - GET /api/course_structure/v0/course_structures/{course_id}/ - - **Response Values** - - * root: The ID of the root node of the course structure. - - * blocks: A dictionary that maps block IDs to a collection of - information about each block. Each block contains the following - fields. - - * id: The ID of the block. - - * type: The type of block. Possible values include sequential, - vertical, html, problem, video, and discussion. The type can also be - the name of a custom type of block used for the course. - - * display_name: The display name configured for the block. - - * graded: Whether or not the sequential or problem is graded. The - value is true or false. - - * format: The assignment type. - - * children: If the block has child blocks, a list of IDs of the child - blocks in the order they appear in the course. - """ - - @CourseViewMixin.course_check - def get(self, request, **kwargs): - try: - return Response(api.course_structure(self.course_key)) - except errors.CourseStructureNotAvailableError: - # If we don't have data stored, we will try to regenerate it, so - # return a 503 and as them to retry in 2 minutes. - return Response(status=503, headers={'Retry-After': '120'}) - - -class CourseGradingPolicy(CourseViewMixin, ListAPIView): - """ - **Use Case** - - Get the course grading policy. - - **Example requests**: - - GET /api/course_structure/v0/grading_policies/{course_id}/ - - **Response Values** - - * assignment_type: The type of the assignment, as configured by course - staff. For example, course staff might make the assignment types Homework, - Quiz, and Exam. - - * count: The number of assignments of the type. - - * dropped: Number of assignments of the type that are dropped. - - * weight: The weight, or effect, of the assignment type on the learner's - final grade. - """ - - allow_empty = False - - @CourseViewMixin.course_check - def get(self, request, **kwargs): - return Response(api.course_grading_policy(self.course_key)) diff --git a/lms/envs/common.py b/lms/envs/common.py index dc59000c4aac724995cbcb079a4670f4fca0f165..b2798ac0a390c46be6f53a6cd2e12a1ff30e6a0c 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -2224,9 +2224,6 @@ INSTALLED_APPS = [ # Coursegraph 'openedx.core.djangoapps.coursegraph.apps.CoursegraphConfig', - # Old course structure API - 'course_structure_api', - # Mailchimp Syncing 'mailing', diff --git a/lms/urls.py b/lms/urls.py index ff6a8be4756e1cccf157a6cf7bf5b3a65bfc8427..7a72051748392f5e55d37f51656543566d874f64 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -95,9 +95,6 @@ urlpatterns = [ # Courseware search endpoints url(r'^search/', include('search.urls')), - # Course content API - url(r'^api/course_structure/', include('course_structure_api.urls', namespace='course_structure_api')), - # Course API url(r'^api/courses/', include('course_api.urls')), diff --git a/openedx/core/djangoapps/embargo/middleware.py b/openedx/core/djangoapps/embargo/middleware.py index c1f4fb4970bc70e71e5683d730625cce1eaf4651..293000f7314f905bc86ce91eb1bb57b7d804724f 100644 --- a/openedx/core/djangoapps/embargo/middleware.py +++ b/openedx/core/djangoapps/embargo/middleware.py @@ -54,10 +54,6 @@ class EmbargoMiddleware(object): # accidentally lock ourselves out of Django admin # during testing. re.compile(r'^/admin/'), - - # Do not block access to course metadata. This information is needed for - # sever-to-server calls. - re.compile(r'^/api/course_structure/v[\d+]/courses/{}/$'.format(settings.COURSE_ID_PATTERN)), ] def __init__(self): diff --git a/openedx/core/djangoapps/embargo/tests/test_middleware.py b/openedx/core/djangoapps/embargo/tests/test_middleware.py index 2cb976995712b5f9de54bab03089e5204730ff84..d3d7f2d63684b2f244e5f44fdd63e62511ea1062 100644 --- a/openedx/core/djangoapps/embargo/tests/test_middleware.py +++ b/openedx/core/djangoapps/embargo/tests/test_middleware.py @@ -174,34 +174,3 @@ class EmbargoMiddlewareAccessTests(UrlResetMixin, ModuleStoreTestCase): # even though we would have been blocked by country # access rules. self.assertEqual(response.status_code, 200) - - @patch.dict(settings.FEATURES, {'EMBARGO': True}) - def test_always_allow_course_detail_access(self): - """ Access to the Course Structure API's course detail endpoint should always be granted. """ - # Make the user staff so that it has permissions to access the views. - self.user.is_staff = True - self.user.save() # pylint: disable=no-member - - # Blacklist an IP address - ip_address = "192.168.10.20" - IPFilter.objects.create( - blacklist=ip_address, - enabled=True - ) - - url = reverse('course_structure_api:v0:detail', kwargs={'course_id': unicode(self.course.id)}) - response = self.client.get( - url, - HTTP_X_FORWARDED_FOR=ip_address, - REMOTE_ADDR=ip_address - ) - self.assertEqual(response.status_code, 200) - - # Test with a fully-restricted course - with restrict_course(self.course.id): - response = self.client.get( - url, - HTTP_X_FORWARDED_FOR=ip_address, - REMOTE_ADDR=ip_address - ) - self.assertEqual(response.status_code, 200)