Commit babd972c by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-course-exists-check: Refactored course lookup logic

parent 7cab70b6
...@@ -474,7 +474,7 @@ class CoursesApiTests(TestCase): ...@@ -474,7 +474,7 @@ class CoursesApiTests(TestCase):
def test_courses_groups_list_get(self): def test_courses_groups_list_get(self):
test_uri = '{}/{}/groups'.format(self.base_courses_uri, self.test_course_id) test_uri = '{}/{}/groups'.format(self.base_courses_uri, self.test_course_id)
course_fail_uri = '{}/{}/groups'.format(self.base_courses_uri, '/ed/Open_DemoX/edx_demo_course') course_fail_uri = '{}/{}/groups'.format(self.base_courses_uri, 'ed/Open_DemoX/edx_demo_course')
for i in xrange(2): for i in xrange(2):
data_dict = { data_dict = {
'name': 'Alpha Group {}'.format(i), 'type': 'Programming', 'name': 'Alpha Group {}'.format(i), 'type': 'Programming',
...@@ -1311,7 +1311,6 @@ class CoursesApiTests(TestCase): ...@@ -1311,7 +1311,6 @@ class CoursesApiTests(TestCase):
data = {'group_id': group_id} data = {'group_id': group_id}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
# Create another group and add it to course module # Create another group and add it to course module
data = {'name': 'Beta Group', 'type': 'project'} data = {'name': 'Beta Group', 'type': 'project'}
response = self.do_post(self.base_groups_uri, data) response = self.do_post(self.base_groups_uri, data)
...@@ -1320,7 +1319,6 @@ class CoursesApiTests(TestCase): ...@@ -1320,7 +1319,6 @@ class CoursesApiTests(TestCase):
data = {'group_id': another_group_id} data = {'group_id': another_group_id}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
# create a 5 new users # create a 5 new users
for i in xrange(1, 6): for i in xrange(1, 6):
data = { data = {
...@@ -1344,12 +1342,10 @@ class CoursesApiTests(TestCase): ...@@ -1344,12 +1342,10 @@ class CoursesApiTests(TestCase):
data = {'user_id': created_user_id} data = {'user_id': created_user_id}
response = self.do_post(test_group_users_uri, data) response = self.do_post(test_group_users_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
#enroll one user in Alpha Group and one in Beta Group created user #enroll one user in Alpha Group and one in Beta Group created user
if i >= 2: if i >= 2:
response = self.do_post(test_course_users_uri, data) response = self.do_post(test_course_users_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
response = self.do_get('{}?enrolled={}'.format(test_uri_users, 'True')) response = self.do_get('{}?enrolled={}'.format(test_uri_users, 'True'))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 2) self.assertEqual(len(response.data), 2)
......
...@@ -10,64 +10,32 @@ from xmodule.modulestore.django import modulestore ...@@ -10,64 +10,32 @@ from xmodule.modulestore.django import modulestore
from xmodule.modulestore.exceptions import ItemNotFoundError from xmodule.modulestore.exceptions import ItemNotFoundError
def get_course(request, user, course_id, depth=0): def get_course(request, user, course_id, depth=0, load_content=False):
""" """
Utility method to obtain course components Utility method to obtain course components
""" """
course_descriptor = None course_descriptor = None
course_key = None
course_content = None course_content = None
try: course_key = get_course_key(course_id)
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError:
pass
if course_key: if course_key:
try: course_descriptor = get_course_descriptor(course_key, depth)
course_descriptor = courses.get_course(course_key, depth) if course_descriptor and load_content:
except ValueError: course_content = get_course_content(request, user, course_key, course_descriptor)
pass
if course_descriptor:
field_data_cache = FieldDataCache([course_descriptor], course_key, user)
course_content = module_render.get_module_for_descriptor(
user,
request,
course_descriptor,
field_data_cache,
course_key)
return course_descriptor, course_key, course_content return course_descriptor, course_key, course_content
def get_course_child(request, user, course_key, content_id): def get_course_child(request, user, course_key, content_id, load_content=False):
""" """
Return a course xmodule/xblock to the caller Return a course xmodule/xblock to the caller
""" """
content_descriptor = None child_descriptor = None
content_key = None child_content = None
content = None child_key = get_course_child_key(content_id)
try: if child_key:
content_key = UsageKey.from_string(content_id) child_descriptor = get_course_child_descriptor(child_key)
except InvalidKeyError: if child_descriptor and load_content:
try: child_content = get_course_child_content(request, user, course_key, child_descriptor)
content_key = Location.from_deprecated_string(content_id) return child_descriptor, child_key, child_content
except (InvalidLocationError, InvalidKeyError):
pass
if content_key:
try:
content_descriptor = modulestore().get_item(content_key)
except ItemNotFoundError:
pass
if content_descriptor:
field_data_cache = FieldDataCache([content_descriptor], course_key, user)
content = module_render.get_module_for_descriptor(
user,
request,
content_descriptor,
field_data_cache,
course_key)
return content_descriptor, content_key, content
def get_course_total_score(course_summary): def get_course_total_score(course_summary):
...@@ -92,3 +60,72 @@ def get_course_leaf_nodes(course_key, detached_categories): ...@@ -92,3 +60,72 @@ def get_course_leaf_nodes(course_key, detached_categories):
nodes.extend([unit.location for unit in vertical.get_children() nodes.extend([unit.location for unit in vertical.get_children()
if getattr(unit, 'category') not in detached_categories]) if getattr(unit, 'category') not in detached_categories])
return nodes return nodes
def get_course_key(course_id):
try:
course_key = CourseKey.from_string(course_id)
except InvalidKeyError:
try:
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError:
course_key = None
return course_key
def get_course_descriptor(course_key, depth):
try:
course_descriptor = courses.get_course(course_key, depth)
except ValueError:
course_descriptor = None
return course_descriptor
def get_course_content(request, user, course_key, course_descriptor):
field_data_cache = FieldDataCache([course_descriptor], course_key, user)
course_content = module_render.get_module_for_descriptor(
user,
request,
course_descriptor,
field_data_cache,
course_key)
return course_content
def course_exists(request, user, course_id):
course_key = get_course_key(course_id)
if not course_key:
return False
if not modulestore().has_course(course_key):
return False
return True
def get_course_child_key(content_id):
try:
content_key = UsageKey.from_string(content_id)
except InvalidKeyError:
try:
content_key = Location.from_deprecated_string(content_id)
except (InvalidLocationError, InvalidKeyError):
content_key = None
return content_key
def get_course_child_descriptor(child_key):
try:
content_descriptor = modulestore().get_item(child_key)
except ItemNotFoundError:
content_descriptor = None
return content_descriptor
def get_course_child_content(request, user, course_key, child_descriptor):
field_data_cache = FieldDataCache([child_descriptor], course_key, user)
child_content = module_render.get_module_for_descriptor(
user,
request,
child_descriptor,
field_data_cache,
course_key)
return child_content
...@@ -22,7 +22,7 @@ from capa.tests.response_xml_factory import StringResponseXMLFactory ...@@ -22,7 +22,7 @@ from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory
from django_comment_common.models import Role, FORUM_ROLE_MODERATOR from django_comment_common.models import Role, FORUM_ROLE_MODERATOR
from instructor.access import allow_access from instructor.access import allow_access
from projects.models import Project from projects.models import Project, Workgroup
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from student.models import anonymous_id_for_user from student.models import anonymous_id_for_user
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
...@@ -60,6 +60,7 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -60,6 +60,7 @@ class UsersApiTests(ModuleStoreTestCase):
self.groups_base_uri = '/api/server/groups' self.groups_base_uri = '/api/server/groups'
self.org_base_uri = '/api/server/organizations/' self.org_base_uri = '/api/server/organizations/'
self.workgroups_base_uri = '/api/server/workgroups/' self.workgroups_base_uri = '/api/server/workgroups/'
self.projects_base_uri = '/api/server/projects/'
self.users_base_uri = '/api/server/users' self.users_base_uri = '/api/server/users'
self.sessions_base_uri = '/api/server/sessions' self.sessions_base_uri = '/api/server/sessions'
self.test_bogus_course_id = 'foo/bar/baz' self.test_bogus_course_id = 'foo/bar/baz'
...@@ -1251,47 +1252,46 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -1251,47 +1252,46 @@ class UsersApiTests(ModuleStoreTestCase):
def test_user_workgroups_list(self): def test_user_workgroups_list(self):
test_workgroups_uri = self.workgroups_base_uri test_workgroups_uri = self.workgroups_base_uri
user_id = self.user.id project_1 = Project.objects.create(
# create anonymous user course_id=unicode(self.course.id),
anonymous_id = anonymous_id_for_user(self.user, self.course.id) content_id=unicode(self.course_content.scope_ids.usage_id),
for i in xrange(1, 12): )
course = CourseFactory.create( p1_workgroup_1 = Workgroup.objects.create(
display_name="TEST COURSE {}".format(i), name = 'Workgroup 1',
) project = project_1
)
course_content = ItemFactory.create(
category="videosequence",
parent_location=course.location,
data=self.test_course_data,
display_name="View_Sequence"
)
test_project = Project.objects.create( project_2 = Project.objects.create(
course_id=unicode(course.id), course_id=unicode(self.course2.id),
content_id=unicode(course_content.scope_ids.usage_id) content_id=unicode(self.course2_content.scope_ids.usage_id),
) )
data = { p2_workgroup_1 = Workgroup.objects.create(
'name': 'Workgroup ' + str(i), name = 'Workgroup 2',
'project': test_project.id project = project_2
} )
response = self.do_post(test_workgroups_uri, data) for i in xrange(1,12):
self.assertEqual(response.status_code, 201) test_user = UserFactory()
test_uri = '{}{}/'.format(test_workgroups_uri, str(response.data['id'])) users_uri = '{}{}/users/'.format(self.workgroups_base_uri, 1)
users_uri = '{}users/'.format(test_uri) data = {"id": test_user.id}
data = {"id": user_id}
response = self.do_post(users_uri, data) response = self.do_post(users_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
if test_user.id > 6:
users_uri = '{}{}/users/'.format(self.workgroups_base_uri, 2)
data = {"id": test_user.id}
response = self.do_post(users_uri, data)
self.assertEqual(response.status_code, 201)
# test with anonymous user id # test with anonymous user id
test_uri = '{}/{}/workgroups/?page_size=10'.format(self.users_base_uri, anonymous_id) anonymous_id = anonymous_id_for_user(test_user, self.course.id)
test_uri = '{}/{}/workgroups/?page_size=1'.format(self.users_base_uri, anonymous_id)
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.data['count'], 11) self.assertEqual(response.data['count'], 2)
self.assertEqual(len(response.data['results']), 10) self.assertEqual(len(response.data['results']), 1)
self.assertEqual(response.data['num_pages'], 2) self.assertEqual(response.data['num_pages'], 2)
# test with course_id filter and integer user id # test with course_id filter and integer user id
course_id = {'course_id': unicode(course.id)} course_id = {'course_id': unicode(self.course.id)}
response = self.do_get('{}/{}/workgroups/?{}'.format(self.users_base_uri, user_id, urlencode(course_id))) response = self.do_get('{}/{}/workgroups/?{}'.format(self.users_base_uri, test_user.id, urlencode(course_id)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 1) self.assertEqual(response.data['count'], 1)
self.assertEqual(len(response.data['results']), 1) self.assertEqual(len(response.data['results']), 1)
......
...@@ -34,7 +34,7 @@ from util.password_policy_validators import ( ...@@ -34,7 +34,7 @@ from util.password_policy_validators import (
) )
from api_manager.courses.serializers import CourseModuleCompletionSerializer from api_manager.courses.serializers import CourseModuleCompletionSerializer
from api_manager.courseware_access import get_course, get_course_child, get_course_total_score from api_manager.courseware_access import get_course, get_course_child, get_course_total_score, get_course_key, course_exists
from api_manager.permissions import SecureAPIView, SecureListAPIView, IdsInFilterBackend, HasOrgsFilterBackend from api_manager.permissions import SecureAPIView, SecureListAPIView, IdsInFilterBackend, HasOrgsFilterBackend
from api_manager.models import GroupProfile, APIUser as User from api_manager.models import GroupProfile, APIUser as User
from api_manager.organizations.serializers import OrganizationSerializer from api_manager.organizations.serializers import OrganizationSerializer
...@@ -90,10 +90,9 @@ def _save_content_position(request, user, course_key, position): ...@@ -90,10 +90,9 @@ def _save_content_position(request, user, course_key, position):
parent_content_id = position['parent_content_id'] parent_content_id = position['parent_content_id']
child_content_id = position['child_content_id'] child_content_id = position['child_content_id']
if unicode(course_key) == parent_content_id: if unicode(course_key) == parent_content_id:
parent_descriptor, parent_key, parent_content = get_course(request, user, parent_content_id) # pylint: disable=W0612 parent_descriptor, parent_key, parent_content = get_course(request, user, parent_content_id, load_content=True) # pylint: disable=W0612
else: else:
parent_descriptor, parent_key, parent_content = get_course_child(request, user, course_key, parent_content_id) # pylint: disable=W0612 parent_descriptor, parent_key, parent_content = get_course_child(request, user, course_key, parent_content_id, load_content=True) # pylint: disable=W0612
if not parent_descriptor: if not parent_descriptor:
return None return None
...@@ -126,8 +125,7 @@ def _save_child_position(parent_descriptor, target_child_location): ...@@ -126,8 +125,7 @@ def _save_child_position(parent_descriptor, target_child_location):
# Only save if position changed # Only save if position changed
if position != parent_descriptor.position: if position != parent_descriptor.position:
parent_descriptor.position = position parent_descriptor.position = position
# Save this new position to the underlying KeyValueStore parent_descriptor.save()
parent_descriptor.save()
def _manage_role(course_descriptor, user, role, action): def _manage_role(course_descriptor, user, role, action):
...@@ -807,13 +805,13 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -807,13 +805,13 @@ class UsersCoursesDetail(SecureAPIView):
user = User.objects.get(id=user_id, is_active=True) user = User.objects.get(id=user_id, is_active=True)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
course_descriptor, course_key, course_content = get_course(request, user, course_id) # pylint: disable=W0612 if not course_exists(request, user, course_id):
if not course_descriptor:
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
response_data['user_id'] = user.id response_data['user_id'] = user.id
response_data['course_id'] = course_id response_data['course_id'] = course_id
if request.DATA['positions']: if request.DATA['positions']:
course_key = get_course_key(course_id)
response_data['positions'] = [] response_data['positions'] = []
for position in request.DATA['positions']: for position in request.DATA['positions']:
content_position = _save_content_position( content_position = _save_content_position(
...@@ -865,7 +863,7 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -865,7 +863,7 @@ class UsersCoursesDetail(SecureAPIView):
response_data['position_tree'][current_child_loc.category] = {} response_data['position_tree'][current_child_loc.category] = {}
response_data['position_tree'][current_child_loc.category]['id'] = unicode(current_child_loc) response_data['position_tree'][current_child_loc.category]['id'] = unicode(current_child_loc)
_,_,parent_module = get_course_child(request, user, course_key, unicode(current_child_loc)) _,_,parent_module = get_course_child(request, user, course_key, unicode(current_child_loc), load_content=True)
else: else:
parent_module = None parent_module = None
...@@ -879,9 +877,9 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -879,9 +877,9 @@ class UsersCoursesDetail(SecureAPIView):
user = User.objects.get(id=user_id, is_active=True) user = User.objects.get(id=user_id, is_active=True)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
course_descriptor, course_key, course_content = get_course(request, user, course_id) # pylint: disable=W0612 if not course_exists(request, user, course_id):
if not course_descriptor:
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
course_key = get_course_key(course_id)
CourseEnrollment.unenroll(user, course_key) CourseEnrollment.unenroll(user, course_key)
return Response({}, status=status.HTTP_204_NO_CONTENT) return Response({}, status=status.HTTP_204_NO_CONTENT)
...@@ -1234,9 +1232,9 @@ class UsersRolesList(SecureListAPIView): ...@@ -1234,9 +1232,9 @@ class UsersRolesList(SecureListAPIView):
course_id = self.request.QUERY_PARAMS.get('course_id', None) course_id = self.request.QUERY_PARAMS.get('course_id', None)
if course_id: if course_id:
course_descriptor, course_key, course_content = get_course(self.request, user, course_id) # pylint: disable=W0612 if not course_exists(self.request, user, course_id):
if not course_descriptor:
raise Http404 raise Http404
course_key = get_course_key(course_id)
queryset = queryset.filter(course_id=course_key) queryset = queryset.filter(course_id=course_key)
role = self.request.QUERY_PARAMS.get('role', None) role = self.request.QUERY_PARAMS.get('role', None)
......
...@@ -30,7 +30,7 @@ from .serializers import ProjectSerializer, WorkgroupSerializer, WorkgroupSubmis ...@@ -30,7 +30,7 @@ from .serializers import ProjectSerializer, WorkgroupSerializer, WorkgroupSubmis
from .serializers import WorkgroupReviewSerializer, WorkgroupSubmissionReviewSerializer, WorkgroupPeerReviewSerializer from .serializers import WorkgroupReviewSerializer, WorkgroupSubmissionReviewSerializer, WorkgroupPeerReviewSerializer
def _get_course(request, user, course_id, depth=0): def _get_course(request, user, course_id, depth=0, load_content=False):
""" """
Utility method to obtain course components Utility method to obtain course components
""" """
...@@ -49,7 +49,7 @@ def _get_course(request, user, course_id, depth=0): ...@@ -49,7 +49,7 @@ def _get_course(request, user, course_id, depth=0):
course_descriptor = get_course(course_key, depth=depth) course_descriptor = get_course(course_key, depth=depth)
except ValueError: except ValueError:
pass pass
if course_descriptor: if course_descriptor and load_content:
field_data_cache = FieldDataCache([course_descriptor], course_key, user) field_data_cache = FieldDataCache([course_descriptor], course_key, user)
course_content = module_render.get_module( course_content = module_render.get_module(
user, user,
...@@ -60,7 +60,7 @@ def _get_course(request, user, course_id, depth=0): ...@@ -60,7 +60,7 @@ def _get_course(request, user, course_id, depth=0):
return course_descriptor, course_key, course_content return course_descriptor, course_key, course_content
def _get_course_child(request, user, course_key, content_id): def _get_course_child(request, user, course_key, content_id, load_content=False):
""" """
Return a course xmodule/xblock to the caller Return a course xmodule/xblock to the caller
""" """
...@@ -77,7 +77,7 @@ def _get_course_child(request, user, course_key, content_id): ...@@ -77,7 +77,7 @@ def _get_course_child(request, user, course_key, content_id):
if content_key: if content_key:
store = modulestore() store = modulestore()
content_descriptor = store.get_item(content_key) content_descriptor = store.get_item(content_key)
if content_descriptor: if content_descriptor and load_content:
field_data_cache = FieldDataCache([content_descriptor], course_key, user) field_data_cache = FieldDataCache([content_descriptor], course_key, user)
content = module_render.get_module( content = module_render.get_module(
user, user,
......
...@@ -500,3 +500,11 @@ PROFILE_IMAGE_MIN_BYTES = 100 ...@@ -500,3 +500,11 @@ PROFILE_IMAGE_MIN_BYTES = 100
FEATURES['ENABLE_LTI_PROVIDER'] = True FEATURES['ENABLE_LTI_PROVIDER'] = True
INSTALLED_APPS += ('lti_provider',) INSTALLED_APPS += ('lti_provider',)
AUTHENTICATION_BACKENDS += ('lti_provider.users.LtiBackend',) AUTHENTICATION_BACKENDS += ('lti_provider.users.LtiBackend',)
########################## SECURITY #######################
FEATURES['ENFORCE_PASSWORD_POLICY'] = False
FEATURES['ENABLE_MAX_FAILED_LOGIN_ATTEMPTS'] = False
FEATURES['SQUELCH_PII_IN_LOGS'] = False
FEATURES['PREVENT_CONCURRENT_LOGINS'] = False
FEATURES['ADVANCED_SECURITY'] = False
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment