Commit 913252d4 by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-quality: Quality updates

parent af299af1
...@@ -5,80 +5,84 @@ text space ...@@ -5,80 +5,84 @@ text space
from textwrap import dedent from textwrap import dedent
TEST_COURSE_UPDATES_CONTENT = dedent(""" TEST_COURSE_UPDATES_CONTENT = dedent(
<ol> """
<li> <ol>
<h2>April 18, 2014</h2> <li>
This does not have a paragraph tag around it <h2>April 18, 2014</h2>
</li> This does not have a paragraph tag around it
<li> </li>
<h2>April 17, 2014</h2> <li>
Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag <h2>April 17, 2014</h2>
</li> Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag
<li> </li>
<h2>April 16, 2014</h2> <li>
Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag<p>one more</p> <h2>April 16, 2014</h2>
</li> Some text before paragraph tag<p>This is inside paragraph tag</p>Some text after tag<p>one more</p>
<li> </li>
<h2>April 15, 2014</h2> <li>
<p>A perfectly</p><p>formatted piece</p><p>of HTML</p> <h2>April 15, 2014</h2>
</li> <p>A perfectly</p><p>formatted piece</p><p>of HTML</p>
</ol> </li>
""" </ol>
"""
) )
TEST_STATIC_TAB1_CONTENT = dedent(""" TEST_STATIC_TAB1_CONTENT = dedent(
<div>This is static tab1</div> """
""" <div>This is static tab1</div>
"""
) )
TEST_STATIC_TAB2_CONTENT = dedent(""" TEST_STATIC_TAB2_CONTENT = dedent(
<div>This is static tab2</div> """
""" <div>This is static tab2</div>
"""
) )
TEST_COURSE_OVERVIEW_CONTENT = dedent(""" TEST_COURSE_OVERVIEW_CONTENT = dedent(
<section class="about"> """
<h2>About This Course</h2> <section class="about">
<p>Include your long course description here. The long course description should contain 150-400 words.</p> <h2>About This Course</h2>
<p>Include your long course description here. The long course description should contain 150-400 words.</p>
<p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p> <p>This is paragraph 2 of the long course description. Add more paragraphs as needed. Make sure to enclose them in paragraph tags.</p>
</section> </section>
<section class="prerequisites"> <section class="prerequisites">
<h2>Prerequisites</h2> <h2>Prerequisites</h2>
<p>Add information about course prerequisites here.</p> <p>Add information about course prerequisites here.</p>
</section> </section>
<section class="course-staff"> <section class="course-staff">
<h2>Course Staff</h2> <h2>Course Staff</h2>
<article class="teacher"> <article class="teacher">
<div class="teacher-image"> <div class="teacher-image">
<img src="/images/pl-faculty.png" align="left" style="margin:0 20 px 0" alt="Course Staff Image #1"> <img src="/images/pl-faculty.png" align="left" style="margin:0 20 px 0" alt="Course Staff Image #1">
</div> </div>
<h3>Staff Member #1</h3> <h3>Staff Member #1</h3>
<p>Biography of instructor/staff member #1</p> <p>Biography of instructor/staff member #1</p>
</article> </article>
<article class="teacher"> <article class="teacher">
<div class="teacher-image"> <div class="teacher-image">
<img src="/images/pl-faculty.png" align="left" style="margin:0 20 px 0" alt="Course Staff Image #2"> <img src="/images/pl-faculty.png" align="left" style="margin:0 20 px 0" alt="Course Staff Image #2">
</div> </div>
<h3>Staff Member #2</h3> <h3>Staff Member #2</h3>
<p>Biography of instructor/staff member #2</p> <p>Biography of instructor/staff member #2</p>
</article> </article>
<article class="author"> <article class="author">
<div class="author-image"> <div class="author-image">
<img src="/images/pl-author.png" align="left" style="margin:0 20 px 0" alt="Author Name"> <img src="/images/pl-author.png" align="left" style="margin:0 20 px 0" alt="Author Name">
</div> </div>
<h3>Author Name</h3> <h3>Author Name</h3>
<p>Biography of Author Name</p> <p>Biography of Author Name</p>
</article> </article>
</section> </section>
<section class="faq">
<p>Some text here</p>
</section>
""")
<section class="faq">
<p>Some text here</p>
</section>
"""
)
...@@ -2,10 +2,9 @@ ...@@ -2,10 +2,9 @@
""" """
Run these tests @ Devstack: Run these tests @ Devstack:
rake fasttest_lms[common/djangoapps/api_manager/tests/test_group_views.py] rake fasttest_lms[common/djangoapps/api_manager/courses/tests.py]
""" """
import simplejson as json import simplejson as json
import unittest
import uuid import uuid
from random import randint from random import randint
...@@ -36,7 +35,6 @@ class CoursesApiTests(TestCase): ...@@ -36,7 +35,6 @@ class CoursesApiTests(TestCase):
""" Test suite for Courses API views """ """ Test suite for Courses API views """
def setUp(self): def setUp(self):
self.maxDiff = 3000
self.test_server_prefix = 'https://testserver' self.test_server_prefix = 'https://testserver'
self.base_courses_uri = '/api/courses' self.base_courses_uri = '/api/courses'
self.base_groups_uri = '/api/groups' self.base_groups_uri = '/api/groups'
...@@ -137,6 +135,7 @@ class CoursesApiTests(TestCase): ...@@ -137,6 +135,7 @@ class CoursesApiTests(TestCase):
return response return response
def _find_item_by_class(self, items, class_name): def _find_item_by_class(self, items, class_name):
"""Helper method to match a single matching item"""
for item in items: for item in items:
if item['class'] == class_name: if item['class'] == class_name:
return item return item
...@@ -444,7 +443,7 @@ class CoursesApiTests(TestCase): ...@@ -444,7 +443,7 @@ class CoursesApiTests(TestCase):
#try a bogus course_id to test failure case #try a bogus course_id to test failure case
test_course = CourseFactory.create() test_course = CourseFactory.create()
test_uri = '{}/{}/overview'.format(self.base_courses_uri, test_course.id) test_uri = '{}/{}/overview'.format(self.base_courses_uri, test_course.id)
test_updates = ItemFactory.create( ItemFactory.create(
category="about", category="about",
parent_location=test_course.location, parent_location=test_course.location,
data='', data='',
...@@ -487,8 +486,7 @@ class CoursesApiTests(TestCase): ...@@ -487,8 +486,7 @@ class CoursesApiTests(TestCase):
def test_courses_updates_get_invalid_content(self): def test_courses_updates_get_invalid_content(self):
#try a bogus course_id to test failure case #try a bogus course_id to test failure case
test_course = CourseFactory.create() test_course = CourseFactory.create()
test_course_data = '<html>{}</html>'.format(str(uuid.uuid4())) ItemFactory.create(
test_updates = ItemFactory.create(
category="course_info", category="course_info",
parent_location=test_course.location, parent_location=test_course.location,
data='', data='',
...@@ -604,7 +602,6 @@ class CoursesApiTests(TestCase): ...@@ -604,7 +602,6 @@ class CoursesApiTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['enrollments']), 0) self.assertEqual(len(response.data['enrollments']), 0)
def test_courses_users_list_post_existing_user(self): def test_courses_users_list_post_existing_user(self):
# create a new user (note, this calls into the /users/ subsystem) # create a new user (note, this calls into the /users/ subsystem)
test_uri = self.base_courses_uri + '/' + self.test_course_id + '/users' test_uri = self.base_courses_uri + '/' + self.test_course_id + '/users'
......
...@@ -8,7 +8,8 @@ from rest_framework.urlpatterns import format_suffix_patterns ...@@ -8,7 +8,8 @@ from rest_framework.urlpatterns import format_suffix_patterns
from api_manager.courses import views as courses_views from api_manager.courses import views as courses_views
urlpatterns = patterns('', urlpatterns = patterns(
'',
url(r'/*$^', courses_views.CoursesList.as_view()), url(r'/*$^', courses_views.CoursesList.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)$', courses_views.CoursesDetail.as_view()), url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)$', courses_views.CoursesDetail.as_view()),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)/submodules/*$', courses_views.ModulesList.as_view()), url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/modules/(?P<module_id>[a-zA-Z0-9/_:]+)/submodules/*$', courses_views.ModulesList.as_view()),
......
...@@ -27,6 +27,7 @@ from xmodule.modulestore import Location, InvalidLocationError ...@@ -27,6 +27,7 @@ from xmodule.modulestore import Location, InvalidLocationError
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def _generate_base_uri(request): def _generate_base_uri(request):
""" """
Constructs the protocol:host:path component of the resource uri Constructs the protocol:host:path component of the resource uri
...@@ -129,7 +130,7 @@ def _serialize_module_with_children(request, course_descriptor, descriptor, dept ...@@ -129,7 +130,7 @@ def _serialize_module_with_children(request, course_descriptor, descriptor, dept
request, request,
course_descriptor, course_descriptor,
child, child,
depth-1 depth - 1
)) ))
return data return data
...@@ -240,7 +241,7 @@ def _parse_updates_html(html): ...@@ -240,7 +241,7 @@ def _parse_updates_html(html):
class ModulesList(APIView): class ModulesList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, module_id=None, format=None): def get(self, request, course_id, module_id=None):
""" """
GET retrieves the list of submodules for a given module GET retrieves the list of submodules for a given module
We don't know where in the module hierarchy we are -- could even be the top We don't know where in the module hierarchy we are -- could even be the top
...@@ -273,7 +274,7 @@ class ModulesList(APIView): ...@@ -273,7 +274,7 @@ class ModulesList(APIView):
class ModulesDetail(APIView): class ModulesDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, module_id, format=None): def get(self, request, course_id, module_id):
""" """
GET retrieves an existing module from the system GET retrieves an existing module from the system
""" """
...@@ -308,7 +309,7 @@ class ModulesDetail(APIView): ...@@ -308,7 +309,7 @@ class ModulesDetail(APIView):
class CoursesList(APIView): class CoursesList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, format=None): def get(self, request):
""" """
GET returns the list of available courses GET returns the list of available courses
""" """
...@@ -328,7 +329,7 @@ class CoursesList(APIView): ...@@ -328,7 +329,7 @@ class CoursesList(APIView):
class CoursesDetail(APIView): class CoursesDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, format=None): def get(self, request, course_id):
""" """
GET retrieves an existing course from the system and returns summary information about the submodules GET retrieves an existing course from the system and returns summary information about the submodules
to the specified depth to the specified depth
...@@ -365,7 +366,7 @@ class CoursesDetail(APIView): ...@@ -365,7 +366,7 @@ class CoursesDetail(APIView):
class CoursesGroupsList(APIView): class CoursesGroupsList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, course_id, format=None): def post(self, request, course_id):
""" """
POST creates a new course-group relationship in the system POST creates a new course-group relationship in the system
""" """
...@@ -402,7 +403,7 @@ class CoursesGroupsList(APIView): ...@@ -402,7 +403,7 @@ class CoursesGroupsList(APIView):
class CoursesGroupsDetail(APIView): class CoursesGroupsDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, group_id, format=None): def get(self, request, course_id, group_id):
""" """
GET retrieves an existing course-group relationship from the system GET retrieves an existing course-group relationship from the system
""" """
...@@ -432,7 +433,7 @@ class CoursesGroupsDetail(APIView): ...@@ -432,7 +433,7 @@ class CoursesGroupsDetail(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def delete(self, request, course_id, group_id, format=None): def delete(self, request, course_id, group_id):
""" """
DELETE removes/inactivates/etc. an existing course-group relationship DELETE removes/inactivates/etc. an existing course-group relationship
""" """
...@@ -441,13 +442,15 @@ class CoursesGroupsDetail(APIView): ...@@ -441,13 +442,15 @@ class CoursesGroupsDetail(APIView):
existing_relationship = CourseGroupRelationship.objects.get(course_id=course_id, group=existing_group).delete() existing_relationship = CourseGroupRelationship.objects.get(course_id=course_id, group=existing_group).delete()
except ObjectDoesNotExist: except ObjectDoesNotExist:
pass pass
return Response({}, status=status.HTTP_204_NO_CONTENT) response_data = {}
response_data['uri'] = _generate_base_uri(request)
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
class CoursesOverview(APIView): class CoursesOverview(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, format=None): def get(self, request, course_id):
""" """
GET retrieves the course overview module, which - in MongoDB - is stored with the following GET retrieves the course overview module, which - in MongoDB - is stored with the following
naming convention {"_id.org":"i4x", "_id.course":<course_num>, "_id.category":"about", "_id.name":"overview"} naming convention {"_id.org":"i4x", "_id.course":<course_num>, "_id.category":"about", "_id.name":"overview"}
...@@ -474,7 +477,7 @@ class CoursesOverview(APIView): ...@@ -474,7 +477,7 @@ class CoursesOverview(APIView):
class CoursesUpdates(APIView): class CoursesUpdates(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, format=None): def get(self, request, course_id):
""" """
GET retrieves the course overview module, which - in MongoDB - is stored with the following GET retrieves the course overview module, which - in MongoDB - is stored with the following
naming convention {"_id.org":"i4x", "_id.course":<course_num>, "_id.category":"course_info", "_id.name":"updates"} naming convention {"_id.org":"i4x", "_id.course":<course_num>, "_id.category":"course_info", "_id.name":"updates"}
...@@ -561,7 +564,7 @@ class CoursesStaticTabsDetail(APIView): ...@@ -561,7 +564,7 @@ class CoursesStaticTabsDetail(APIView):
class CoursesUsersList(APIView): class CoursesUsersList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, course_id, format=None): def post(self, request, course_id):
""" """
POST enrolls a student in the course. Note, this can be a user_id or POST enrolls a student in the course. Note, this can be a user_id or
just an email, in case the user does not exist in the system just an email, in case the user does not exist in the system
...@@ -603,11 +606,13 @@ class CoursesUsersList(APIView): ...@@ -603,11 +606,13 @@ class CoursesUsersList(APIView):
else: else:
return Response({}, status=status.HTTP_400_BAD_REQUEST) return Response({}, status=status.HTTP_400_BAD_REQUEST)
def get(self, request, course_id, format=None): def get(self, request, course_id):
""" """
GET returns a list of users enrolled in the course_id GET returns a list of users enrolled in the course_id
""" """
response_data = OrderedDict() response_data = OrderedDict()
base_uri = _generate_base_uri(request)
response_data['uri'] = base_uri
try: try:
existing_course = get_course(course_id) existing_course = get_course(course_id)
except ValueError: except ValueError:
...@@ -634,10 +639,11 @@ class CoursesUsersList(APIView): ...@@ -634,10 +639,11 @@ class CoursesUsersList(APIView):
response_data['pending_enrollments'].append(cea.email) response_data['pending_enrollments'].append(cea.email)
return Response(response_data) return Response(response_data)
class CoursesUsersDetail(APIView): class CoursesUsersDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, course_id, user_id, format=None): def get(self, request, course_id, user_id):
""" """
GET identifies an ACTIVE course enrollment for the specified user GET identifies an ACTIVE course enrollment for the specified user
""" """
...@@ -671,11 +677,10 @@ class CoursesUsersDetail(APIView): ...@@ -671,11 +677,10 @@ class CoursesUsersDetail(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def delete(self, request, course_id, user_id, format=None): def delete(self, request, course_id, user_id):
""" """
DELETE unenrolls the specified user from the specified course DELETE unenrolls the specified user from the specified course
""" """
response_data = OrderedDict()
try: try:
existing_course = get_course(course_id) existing_course = get_course(course_id)
except ValueError: except ValueError:
...@@ -688,4 +693,7 @@ class CoursesUsersDetail(APIView): ...@@ -688,4 +693,7 @@ class CoursesUsersDetail(APIView):
user = None user = None
if user: if user:
CourseEnrollment.unenroll(user, course_id) CourseEnrollment.unenroll(user, course_id)
return Response({}, status=status.HTTP_204_NO_CONTENT) response_data = {}
base_uri = _generate_base_uri(request)
response_data['uri'] = base_uri
return Response(response_data, status=status.HTTP_204_NO_CONTENT)
...@@ -344,7 +344,6 @@ class GroupsApiTests(ModuleStoreTestCase): ...@@ -344,7 +344,6 @@ class GroupsApiTests(ModuleStoreTestCase):
self.assertEqual(users[0]['first_name'], 'Joe') self.assertEqual(users[0]['first_name'], 'Joe')
self.assertEqual(users[0]['last_name'], 'Smith') self.assertEqual(users[0]['last_name'], 'Smith')
def test_group_users_list_get_invalid_group(self): def test_group_users_list_get_invalid_group(self):
test_uri = self.base_groups_uri + '/1231241/users' test_uri = self.base_groups_uri + '/1231241/users'
response = self.do_get(test_uri) response = self.do_get(test_uri)
...@@ -582,7 +581,6 @@ class GroupsApiTests(ModuleStoreTestCase): ...@@ -582,7 +581,6 @@ class GroupsApiTests(ModuleStoreTestCase):
data = {'name': 'Tango Group'} data = {'name': 'Tango Group'}
tango_response = self.do_post(self.base_groups_uri, data) tango_response = self.do_post(self.base_groups_uri, data)
self.assertEqual(tango_response.status_code, 201) self.assertEqual(tango_response.status_code, 201)
tango_group_id = tango_response.data['id']
tango_uri = tango_response.data['uri'] tango_uri = tango_response.data['uri']
data = {'group_id': bravo_group_id, 'relationship_type': relationship_type} data = {'group_id': bravo_group_id, 'relationship_type': relationship_type}
tango_groups_uri = tango_uri + '/groups' tango_groups_uri = tango_uri + '/groups'
...@@ -756,7 +754,6 @@ class GroupsApiTests(ModuleStoreTestCase): ...@@ -756,7 +754,6 @@ class GroupsApiTests(ModuleStoreTestCase):
data = {'name': self.test_group_name} data = {'name': self.test_group_name}
response = self.do_post(self.base_groups_uri, data) response = self.do_post(self.base_groups_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
group_id = response.data['id']
test_uri = response.data['uri'] + '/courses' test_uri = response.data['uri'] + '/courses'
data = {'course_id': self.test_course_id} data = {'course_id': self.test_course_id}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
...@@ -774,7 +771,6 @@ class GroupsApiTests(ModuleStoreTestCase): ...@@ -774,7 +771,6 @@ class GroupsApiTests(ModuleStoreTestCase):
data = {'name': self.test_group_name} data = {'name': self.test_group_name}
response = self.do_post(self.base_groups_uri, data) response = self.do_post(self.base_groups_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
group_id = response.data['id']
test_uri = response.data['uri'] + '/courses' test_uri = response.data['uri'] + '/courses'
data = {'course_id': "987/23/896"} data = {'course_id': "987/23/896"}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
...@@ -859,7 +855,6 @@ class GroupsApiTests(ModuleStoreTestCase): ...@@ -859,7 +855,6 @@ class GroupsApiTests(ModuleStoreTestCase):
data = {'name': self.test_group_name} data = {'name': self.test_group_name}
response = self.do_post(self.base_groups_uri, data) response = self.do_post(self.base_groups_uri, data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
group_id = response.data['id']
test_uri = '{}/courses/{}'.format(response.data['uri'], self.course.id) test_uri = '{}/courses/{}'.format(response.data['uri'], self.course.id)
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
...@@ -5,7 +5,8 @@ from rest_framework.urlpatterns import format_suffix_patterns ...@@ -5,7 +5,8 @@ from rest_framework.urlpatterns import format_suffix_patterns
from api_manager.groups import views as groups_views from api_manager.groups import views as groups_views
urlpatterns = patterns('', urlpatterns = patterns(
'',
url(r'/*$^', groups_views.GroupsList.as_view()), url(r'/*$^', groups_views.GroupsList.as_view()),
url(r'^(?P<group_id>[0-9]+)$', groups_views.GroupsDetail.as_view()), url(r'^(?P<group_id>[0-9]+)$', groups_views.GroupsDetail.as_view()),
url(r'^(?P<group_id>[0-9]+)/courses/*$', groups_views.GroupsCoursesList.as_view()), url(r'^(?P<group_id>[0-9]+)/courses/*$', groups_views.GroupsCoursesList.as_view()),
......
...@@ -38,7 +38,7 @@ def _generate_base_uri(request): ...@@ -38,7 +38,7 @@ def _generate_base_uri(request):
class GroupsList(APIView): class GroupsList(APIView):
permissions_classes = (ApiKeyHeaderPermission,) permissions_classes = (ApiKeyHeaderPermission,)
def post(self, request, format=None): def post(self, request):
""" """
POST creates a new group in the system POST creates a new group in the system
""" """
...@@ -74,7 +74,7 @@ class GroupsList(APIView): ...@@ -74,7 +74,7 @@ class GroupsList(APIView):
response_status = status.HTTP_201_CREATED response_status = status.HTTP_201_CREATED
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def get(self, request, format=None): def get(self, request):
""" """
GET retrieves a list of groups in the system filtered by type GET retrieves a list of groups in the system filtered by type
""" """
...@@ -103,7 +103,7 @@ class GroupsList(APIView): ...@@ -103,7 +103,7 @@ class GroupsList(APIView):
class GroupsDetail(APIView): class GroupsDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id, format=None): def post(self, request, group_id):
response_data = {} response_data = {}
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
print base_uri print base_uri
...@@ -124,7 +124,7 @@ class GroupsDetail(APIView): ...@@ -124,7 +124,7 @@ class GroupsDetail(APIView):
response_data['uri'] = _generate_base_uri(request) response_data['uri'] = _generate_base_uri(request)
return Response(response_data, status=status.HTTP_201_CREATED) return Response(response_data, status=status.HTTP_201_CREATED)
def get(self, request, group_id, format=None): def get(self, request, group_id):
""" """
GET retrieves an existing group from the system GET retrieves an existing group from the system
""" """
...@@ -163,7 +163,7 @@ class GroupsDetail(APIView): ...@@ -163,7 +163,7 @@ class GroupsDetail(APIView):
class GroupsUsersList(APIView): class GroupsUsersList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id, format=None): def post(self, request, group_id):
""" """
POST creates a new group-user relationship in the system POST creates a new group-user relationship in the system
""" """
...@@ -194,7 +194,7 @@ class GroupsUsersList(APIView): ...@@ -194,7 +194,7 @@ class GroupsUsersList(APIView):
response_status = status.HTTP_409_CONFLICT response_status = status.HTTP_409_CONFLICT
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def get(self, request, group_id, format=None): def get(self, request, group_id):
""" """
GET retrieves the list of users related to the specified group GET retrieves the list of users related to the specified group
""" """
...@@ -220,7 +220,7 @@ class GroupsUsersList(APIView): ...@@ -220,7 +220,7 @@ class GroupsUsersList(APIView):
class GroupsUsersDetail(APIView): class GroupsUsersDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, group_id, user_id, format=None): def get(self, request, group_id, user_id):
""" """
GET retrieves an existing group-user relationship from the system GET retrieves an existing group-user relationship from the system
""" """
...@@ -241,8 +241,7 @@ class GroupsUsersDetail(APIView): ...@@ -241,8 +241,7 @@ class GroupsUsersDetail(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def delete(self, request, group_id, user_id):
def delete(self, request, group_id, user_id, format=None):
""" """
DELETE removes/inactivates/etc. an existing group-user relationship DELETE removes/inactivates/etc. an existing group-user relationship
""" """
...@@ -258,7 +257,7 @@ class GroupsUsersDetail(APIView): ...@@ -258,7 +257,7 @@ class GroupsUsersDetail(APIView):
class GroupsGroupsList(APIView): class GroupsGroupsList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id, format=None): def post(self, request, group_id):
""" """
POST creates a new group-group relationship in the system POST creates a new group-group relationship in the system
""" """
...@@ -290,8 +289,7 @@ class GroupsGroupsList(APIView): ...@@ -290,8 +289,7 @@ class GroupsGroupsList(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def get(self, request, group_id):
def get(self, request, group_id, format=None):
""" """
GET retrieves the existing group-group relationships for the specified group GET retrieves the existing group-group relationships for the specified group
""" """
...@@ -333,7 +331,7 @@ class GroupsGroupsList(APIView): ...@@ -333,7 +331,7 @@ class GroupsGroupsList(APIView):
class GroupsGroupsDetail(APIView): class GroupsGroupsDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, group_id, related_group_id, format=None): def get(self, request, group_id, related_group_id):
""" """
GET retrieves an existing group-group relationship from the system GET retrieves an existing group-group relationship from the system
""" """
...@@ -357,7 +355,7 @@ class GroupsGroupsDetail(APIView): ...@@ -357,7 +355,7 @@ class GroupsGroupsDetail(APIView):
response_status = status.HTTP_200_OK response_status = status.HTTP_200_OK
return Response(response_data, response_status) return Response(response_data, response_status)
def delete(self, request, group_id, related_group_id, format=None): def delete(self, request, group_id, related_group_id):
""" """
DELETE removes/inactivates/etc. an existing group-group relationship DELETE removes/inactivates/etc. an existing group-group relationship
""" """
...@@ -388,7 +386,7 @@ class GroupsGroupsDetail(APIView): ...@@ -388,7 +386,7 @@ class GroupsGroupsDetail(APIView):
class GroupsCoursesList(APIView): class GroupsCoursesList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, group_id, format=None): def post(self, request, group_id):
""" """
POST creates a new group-course relationship in the system POST creates a new group-course relationship in the system
""" """
...@@ -422,7 +420,7 @@ class GroupsCoursesList(APIView): ...@@ -422,7 +420,7 @@ class GroupsCoursesList(APIView):
response_status = status.HTTP_409_CONFLICT response_status = status.HTTP_409_CONFLICT
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def get(self, request, group_id, format=None): def get(self, request, group_id):
""" """
GET returns all courses that has a relationship to the group GET returns all courses that has a relationship to the group
""" """
...@@ -448,7 +446,7 @@ class GroupsCoursesList(APIView): ...@@ -448,7 +446,7 @@ class GroupsCoursesList(APIView):
class GroupsCoursesDetail(APIView): class GroupsCoursesDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, group_id, course_id, format=None): def get(self, request, group_id, course_id):
""" """
GET retrieves an existing group-course relationship from the system GET retrieves an existing group-course relationship from the system
""" """
...@@ -469,7 +467,7 @@ class GroupsCoursesDetail(APIView): ...@@ -469,7 +467,7 @@ class GroupsCoursesDetail(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def delete(self, request, group_id, course_id, format=None): def delete(self, request, group_id, course_id):
""" """
DELETE removes/inactivates/etc. an existing group-course relationship DELETE removes/inactivates/etc. an existing group-course relationship
""" """
......
...@@ -30,7 +30,6 @@ class Migration(SchemaMigration): ...@@ -30,7 +30,6 @@ class Migration(SchemaMigration):
)) ))
db.send_create_signal('api_manager', ['LinkedGroupRelationship']) db.send_create_signal('api_manager', ['LinkedGroupRelationship'])
def backwards(self, orm): def backwards(self, orm):
# Deleting model 'GroupRelationship' # Deleting model 'GroupRelationship'
db.delete_table('api_manager_grouprelationship') db.delete_table('api_manager_grouprelationship')
...@@ -38,7 +37,6 @@ class Migration(SchemaMigration): ...@@ -38,7 +37,6 @@ class Migration(SchemaMigration):
# Deleting model 'LinkedGroupRelationship' # Deleting model 'LinkedGroupRelationship'
db.delete_table('api_manager_linkedgrouprelationship') db.delete_table('api_manager_linkedgrouprelationship')
models = { models = {
'api_manager.grouprelationship': { 'api_manager.grouprelationship': {
'Meta': {'object_name': 'GroupRelationship'}, 'Meta': {'object_name': 'GroupRelationship'},
...@@ -80,4 +78,4 @@ class Migration(SchemaMigration): ...@@ -80,4 +78,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['api_manager'] complete_apps = ['api_manager']
\ No newline at end of file
...@@ -25,7 +25,6 @@ class Migration(SchemaMigration): ...@@ -25,7 +25,6 @@ class Migration(SchemaMigration):
)) ))
db.send_create_signal('api_manager', ['GroupProfile']) db.send_create_signal('api_manager', ['GroupProfile'])
def backwards(self, orm): def backwards(self, orm):
# Deleting model 'CourseGroupRelationship' # Deleting model 'CourseGroupRelationship'
db.delete_table('api_manager_coursegrouprelationship') db.delete_table('api_manager_coursegrouprelationship')
...@@ -33,7 +32,6 @@ class Migration(SchemaMigration): ...@@ -33,7 +32,6 @@ class Migration(SchemaMigration):
# Deleting model 'GroupProfile' # Deleting model 'GroupProfile'
db.delete_table('auth_groupprofile') db.delete_table('auth_groupprofile')
models = { models = {
'api_manager.coursegrouprelationship': { 'api_manager.coursegrouprelationship': {
'Meta': {'object_name': 'CourseGroupRelationship'}, 'Meta': {'object_name': 'CourseGroupRelationship'},
...@@ -88,4 +86,4 @@ class Migration(SchemaMigration): ...@@ -88,4 +86,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['api_manager'] complete_apps = ['api_manager']
\ No newline at end of file
...@@ -13,12 +13,10 @@ class Migration(SchemaMigration): ...@@ -13,12 +13,10 @@ class Migration(SchemaMigration):
self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True), self.gf('django.db.models.fields.CharField')(max_length=255, null=True, blank=True),
keep_default=False) keep_default=False)
def backwards(self, orm): def backwards(self, orm):
# Deleting field 'GroupProfile.name' # Deleting field 'GroupProfile.name'
db.delete_column('auth_groupprofile', 'name') db.delete_column('auth_groupprofile', 'name')
models = { models = {
'api_manager.coursegrouprelationship': { 'api_manager.coursegrouprelationship': {
'Meta': {'object_name': 'CourseGroupRelationship'}, 'Meta': {'object_name': 'CourseGroupRelationship'},
...@@ -74,4 +72,4 @@ class Migration(SchemaMigration): ...@@ -74,4 +72,4 @@ class Migration(SchemaMigration):
} }
} }
complete_apps = ['api_manager'] complete_apps = ['api_manager']
\ No newline at end of file
...@@ -19,7 +19,6 @@ TEST_API_KEY = str(uuid.uuid4()) ...@@ -19,7 +19,6 @@ TEST_API_KEY = str(uuid.uuid4())
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@patch.dict("django.conf.settings.FEATURES", {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False}) @patch.dict("django.conf.settings.FEATURES", {'ENABLE_MAX_FAILED_LOGIN_ATTEMPTS': False})
class SessionApiRateLimitingProtectionTest(TestCase): class SessionApiRateLimitingProtectionTest(TestCase):
""" """
Test api_manager.session.login.ratelimit Test api_manager.session.login.ratelimit
...@@ -37,7 +36,6 @@ class SessionApiRateLimitingProtectionTest(TestCase): ...@@ -37,7 +36,6 @@ class SessionApiRateLimitingProtectionTest(TestCase):
cache.clear() cache.clear()
self.session_url = '/api/sessions' self.session_url = '/api/sessions'
def test_login_ratelimiting_protection(self): def test_login_ratelimiting_protection(self):
""" Try (and fail) login user 30 times on invalid password """ """ Try (and fail) login user 30 times on invalid password """
......
...@@ -222,7 +222,7 @@ class SessionApiSecurityTest(TestCase): ...@@ -222,7 +222,7 @@ class SessionApiSecurityTest(TestCase):
""" """
Make Post/Delete/Get requests with params Make Post/Delete/Get requests with params
""" """
post_params, extra, = {'username': username, 'password': password}, {} post_params, extra, = {'username': username, 'password': password}, {}
patched_audit_log = 'api_manager.sessions.views.AUDIT_LOG' patched_audit_log = 'api_manager.sessions.views.AUDIT_LOG'
request_method = kwargs.get('request_method', 'POST') request_method = kwargs.get('request_method', 'POST')
if kwargs.get('email'): if kwargs.get('email'):
......
...@@ -5,7 +5,8 @@ from rest_framework.urlpatterns import format_suffix_patterns ...@@ -5,7 +5,8 @@ from rest_framework.urlpatterns import format_suffix_patterns
from api_manager.sessions import views as sessions_views from api_manager.sessions import views as sessions_views
urlpatterns = patterns('', urlpatterns = patterns(
'',
url(r'/*$^', sessions_views.SessionsList.as_view()), url(r'/*$^', sessions_views.SessionsList.as_view()),
url(r'^(?P<session_id>[a-z0-9]+)$', sessions_views.SessionsDetail.as_view()), url(r'^(?P<session_id>[a-z0-9]+)$', sessions_views.SessionsDetail.as_view()),
) )
......
...@@ -43,7 +43,7 @@ def _generate_base_uri(request): ...@@ -43,7 +43,7 @@ def _generate_base_uri(request):
class SessionsList(APIView): class SessionsList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, format=None): def post(self, request):
""" """
POST creates a new system session, supported authentication modes: POST creates a new system session, supported authentication modes:
1. Open edX username/password 1. Open edX username/password
...@@ -116,7 +116,7 @@ class SessionsList(APIView): ...@@ -116,7 +116,7 @@ class SessionsList(APIView):
class SessionsDetail(APIView): class SessionsDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, session_id, format=None): def get(self, request, session_id):
""" """
GET retrieves an existing system session GET retrieves an existing system session
""" """
...@@ -140,7 +140,7 @@ class SessionsDetail(APIView): ...@@ -140,7 +140,7 @@ class SessionsDetail(APIView):
else: else:
return Response(response_data, status=status.HTTP_404_NOT_FOUND) return Response(response_data, status=status.HTTP_404_NOT_FOUND)
def delete(self, request, session_id, format=None): def delete(self, request, session_id):
""" """
DELETE flushes an existing system session from the system DELETE flushes an existing system session from the system
""" """
......
""" BASE API VIEWS """ """ BASE API VIEWS """
from rest_framework import status from rest_framework import status
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
...@@ -22,11 +21,12 @@ def _generate_base_uri(request): ...@@ -22,11 +21,12 @@ def _generate_base_uri(request):
) )
return resource_uri return resource_uri
class SystemDetail(APIView): class SystemDetail(APIView):
"""Manages system-level information about the Open edX API"""
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, format=None): def get(self, request):
"""Returns top-level descriptive information about the Open edX API"""
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
response_data = {} response_data = {}
response_data['name'] = "Open edX System API" response_data['name'] = "Open edX System API"
...@@ -37,10 +37,10 @@ class SystemDetail(APIView): ...@@ -37,10 +37,10 @@ class SystemDetail(APIView):
class ApiDetail(APIView): class ApiDetail(APIView):
"""Manages top-level information about the Open edX API"""
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, format=None): def get(self, request):
"""Returns top-level descriptive information about the Open edX API"""
base_uri = _generate_base_uri(request) base_uri = _generate_base_uri(request)
response_data = {} response_data = {}
response_data['name'] = "Open edX API" response_data['name'] = "Open edX API"
......
...@@ -12,11 +12,12 @@ from django.conf.urls import include, patterns, url ...@@ -12,11 +12,12 @@ from django.conf.urls import include, patterns, url
from api_manager.system import views as system_views from api_manager.system import views as system_views
urlpatterns = patterns('', urlpatterns = patterns(
url(r'^$', system_views.ApiDetail.as_view()), '',
url(r'^system$', system_views.SystemDetail.as_view()), url(r'^$', system_views.ApiDetail.as_view()),
url(r'^users/*', include('api_manager.users.urls')), url(r'^system$', system_views.SystemDetail.as_view()),
url(r'^groups/*', include('api_manager.groups.urls')), url(r'^users/*', include('api_manager.users.urls')),
url(r'^sessions/*', include('api_manager.sessions.urls')), url(r'^groups/*', include('api_manager.groups.urls')),
url(r'^courses/*', include('api_manager.courses.urls')), url(r'^sessions/*', include('api_manager.sessions.urls')),
) url(r'^courses/*', include('api_manager.courses.urls')),
)
...@@ -12,8 +12,10 @@ from django.core.cache import cache ...@@ -12,8 +12,10 @@ from django.core.cache import cache
from datetime import datetime, timedelta from datetime import datetime, timedelta
from freezegun import freeze_time from freezegun import freeze_time
from pytz import UTC from pytz import UTC
TEST_API_KEY = str(uuid.uuid4()) TEST_API_KEY = str(uuid.uuid4())
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': True}) @patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': True})
@patch.dict("django.conf.settings.FEATURES", {'ADVANCED_SECURITY': True}) @patch.dict("django.conf.settings.FEATURES", {'ADVANCED_SECURITY': True})
...@@ -52,7 +54,7 @@ class UserPasswordResetTest(TestCase): ...@@ -52,7 +54,7 @@ class UserPasswordResetTest(TestCase):
reset_time = timezone.now() + timedelta(days=5) reset_time = timezone.now() + timedelta(days=5)
with patch.object(timezone, 'now', return_value=reset_time): with patch.object(timezone, 'now', return_value=reset_time):
response = self._do_post_request(self.session_url, 'test2', 'Test.Me64!', secure=True) response = self._do_post_request(self.session_url, 'test2', 'Test.Me64!', secure=True)
message =_( message = _(
'Your password has expired due to password policy on this account. ' 'Your password has expired due to password policy on this account. '
'You must reset your password before you can log in again.' 'You must reset your password before you can log in again.'
) )
...@@ -63,7 +65,7 @@ class UserPasswordResetTest(TestCase): ...@@ -63,7 +65,7 @@ class UserPasswordResetTest(TestCase):
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
pass_reset_url, password='Test.Me64@', secure=True pass_reset_url, password='Test.Me64@', secure=True
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
#login successful after reset password #login successful after reset password
response = self._do_post_request(self.session_url, 'test2', 'Test.Me64@', secure=True) response = self._do_post_request(self.session_url, 'test2', 'Test.Me64@', secure=True)
...@@ -167,11 +169,13 @@ class UserPasswordResetTest(TestCase): ...@@ -167,11 +169,13 @@ class UserPasswordResetTest(TestCase):
pass_reset_url = '{}/{}'.format(self.user_url, user_id) pass_reset_url = '{}/{}'.format(self.user_url, user_id)
for i in xrange(30): for i in xrange(30):
password = u'test_password{0}'.format(i) password = u'test_password{0}'.format(i)
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
'{}/{}'.format(self.user_url, i+200), password=password, secure=True '{}/{}'.format(self.user_url, i + 200),
) password=password,
self._assert_response(response, status=404) secure=True
)
self._assert_response(response, status=404)
response = self._do_post_pass_reset_request( response = self._do_post_pass_reset_request(
'{}/{}'.format(self.user_url, '31'), password='Test.Me64@', secure=True '{}/{}'.format(self.user_url, '31'), password='Test.Me64@', secure=True
...@@ -215,14 +219,11 @@ class UserPasswordResetTest(TestCase): ...@@ -215,14 +219,11 @@ class UserPasswordResetTest(TestCase):
extra['wsgi.url_scheme'] = 'https' extra['wsgi.url_scheme'] = 'https'
return self.client.post(url, post_params, headers=headers, **extra) return self.client.post(url, post_params, headers=headers, **extra)
def _assert_response(self, response, status=200, success=None, message=None): def _assert_response(self, response, status=200, message=None):
""" """
Assert that the response had status 200 and returned a valid Assert that the response had status 200 and returned a valid
JSON-parseable dict. JSON-parseable dict.
If success is provided, assert that the response had that
value for 'success' in the JSON dict.
If message is provided, assert that the response contained that If message is provided, assert that the response contained that
value for 'message' in the JSON dict. value for 'message' in the JSON dict.
""" """
...@@ -233,4 +234,3 @@ class UserPasswordResetTest(TestCase): ...@@ -233,4 +234,3 @@ class UserPasswordResetTest(TestCase):
msg = ("'%s' did not contain '%s'" % msg = ("'%s' did not contain '%s'" %
(response_dict['message'], message)) (response_dict['message'], message))
self.assertTrue(message in response_dict['message'], msg) self.assertTrue(message in response_dict['message'], msg)
...@@ -90,12 +90,11 @@ class UsersApiTests(TestCase): ...@@ -90,12 +90,11 @@ class UsersApiTests(TestCase):
def test_user_list_post_inactive(self): def test_user_list_post_inactive(self):
test_uri = '/api/users' test_uri = '/api/users'
local_username = self.test_username + str(randint(11, 99)) local_username = self.test_username + str(randint(11, 99))
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name, 'is_active': False } data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name, 'is_active': False}
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)
self.assertEqual(response.data['is_active'], False) self.assertEqual(response.data['is_active'], False)
def test_user_list_post_duplicate(self): def test_user_list_post_duplicate(self):
test_uri = '/api/users' test_uri = '/api/users'
local_username = self.test_username + str(randint(11, 99)) local_username = self.test_username + str(randint(11, 99))
...@@ -135,7 +134,7 @@ class UsersApiTests(TestCase): ...@@ -135,7 +134,7 @@ class UsersApiTests(TestCase):
data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name} data = {'email': self.test_email, 'username': local_username, 'password': self.test_password, 'first_name': self.test_first_name, 'last_name': self.test_last_name}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
test_uri = test_uri + '/' + str(response.data['id']) test_uri = test_uri + '/' + str(response.data['id'])
data = {'is_active': False } data = {'is_active': False}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['is_active'], False) self.assertEqual(response.data['is_active'], False)
...@@ -145,7 +144,7 @@ class UsersApiTests(TestCase): ...@@ -145,7 +144,7 @@ class UsersApiTests(TestCase):
def test_user_detail_post_invalid_user(self): def test_user_detail_post_invalid_user(self):
test_uri = '/api/users/123124124' test_uri = '/api/users/123124124'
data = {'is_active': False } data = {'is_active': False}
response = self.do_post(test_uri, data) response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
......
...@@ -5,7 +5,8 @@ from rest_framework.urlpatterns import format_suffix_patterns ...@@ -5,7 +5,8 @@ from rest_framework.urlpatterns import format_suffix_patterns
from api_manager.users import views as users_views from api_manager.users import views as users_views
urlpatterns = patterns('', urlpatterns = patterns(
'',
url(r'/*$^', users_views.UsersList.as_view()), url(r'/*$^', users_views.UsersList.as_view()),
url(r'^(?P<user_id>[0-9]+)$', users_views.UsersDetail.as_view()), url(r'^(?P<user_id>[0-9]+)$', users_views.UsersDetail.as_view()),
url(r'^(?P<user_id>[0-9]+)/courses/*$', users_views.UsersCoursesList.as_view()), url(r'^(?P<user_id>[0-9]+)/courses/*$', users_views.UsersCoursesList.as_view()),
......
...@@ -30,6 +30,7 @@ from util.bad_request_rate_limiter import BadRequestRateLimiter ...@@ -30,6 +30,7 @@ from util.bad_request_rate_limiter import BadRequestRateLimiter
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
AUDIT_LOG = logging.getLogger("audit") AUDIT_LOG = logging.getLogger("audit")
def _generate_base_uri(request): def _generate_base_uri(request):
""" """
Constructs the protocol:host:path component of the resource uri Constructs the protocol:host:path component of the resource uri
...@@ -44,6 +45,7 @@ def _generate_base_uri(request): ...@@ -44,6 +45,7 @@ def _generate_base_uri(request):
) )
return resource_uri return resource_uri
def _serialize_user(response_data, user): def _serialize_user(response_data, user):
""" """
Loads the object data into the response dict Loads the object data into the response dict
...@@ -57,6 +59,7 @@ def _serialize_user(response_data, user): ...@@ -57,6 +59,7 @@ def _serialize_user(response_data, user):
response_data['is_active'] = user.is_active response_data['is_active'] = user.is_active
return response_data return response_data
def _save_module_position(request, user, course_id, course_descriptor, position): def _save_module_position(request, user, course_id, course_descriptor, position):
""" """
Records the indicated position for the specified course Records the indicated position for the specified course
...@@ -94,7 +97,7 @@ def _save_module_position(request, user, course_id, course_descriptor, position) ...@@ -94,7 +97,7 @@ def _save_module_position(request, user, course_id, course_descriptor, position)
class UsersList(APIView): class UsersList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, format=None): def post(self, request):
""" """
POST creates a new user in the system POST creates a new user in the system
""" """
...@@ -176,7 +179,7 @@ class UsersList(APIView): ...@@ -176,7 +179,7 @@ class UsersList(APIView):
class UsersDetail(APIView): class UsersDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, user_id, format=None): def get(self, request, user_id):
""" """
GET retrieves an existing user from the system GET retrieves an existing user from the system
""" """
...@@ -195,7 +198,7 @@ class UsersDetail(APIView): ...@@ -195,7 +198,7 @@ class UsersDetail(APIView):
except ObjectDoesNotExist: except ObjectDoesNotExist:
return Response(response_data, status=status.HTTP_404_NOT_FOUND) return Response(response_data, status=status.HTTP_404_NOT_FOUND)
def post(self, request, user_id, format=None): def post(self, request, user_id):
""" """
POST provides the ability to update information about an existing user POST provides the ability to update information about an existing user
""" """
...@@ -287,7 +290,7 @@ class UsersDetail(APIView): ...@@ -287,7 +290,7 @@ class UsersDetail(APIView):
class UsersGroupsList(APIView): class UsersGroupsList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, user_id, format=None): def post(self, request, user_id):
""" """
POST creates a new user-group relationship in the system POST creates a new user-group relationship in the system
""" """
...@@ -320,7 +323,7 @@ class UsersGroupsList(APIView): ...@@ -320,7 +323,7 @@ class UsersGroupsList(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def get(self, request, user_id, format=None): def get(self, request, user_id):
""" """
GET retrieves the list of groups related to the specified user GET retrieves the list of groups related to the specified user
""" """
...@@ -328,8 +331,10 @@ class UsersGroupsList(APIView): ...@@ -328,8 +331,10 @@ class UsersGroupsList(APIView):
existing_user = User.objects.get(id=user_id) existing_user = User.objects.get(id=user_id)
except ObjectDoesNotExist: except ObjectDoesNotExist:
return Response({}, status.HTTP_404_NOT_FOUND) return Response({}, status.HTTP_404_NOT_FOUND)
groups = existing_user.groups.all()
response_data = {} response_data = {}
base_uri = _generate_base_uri(request)
response_data['uri'] = base_uri
groups = existing_user.groups.all()
response_data['groups'] = [] response_data['groups'] = []
for group in groups: for group in groups:
group_profile = GroupProfile.objects.get(group_id=group.id) group_profile = GroupProfile.objects.get(group_id=group.id)
...@@ -344,12 +349,11 @@ class UsersGroupsList(APIView): ...@@ -344,12 +349,11 @@ class UsersGroupsList(APIView):
class UsersGroupsDetail(APIView): class UsersGroupsDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def get(self, request, user_id, group_id, format=None): def get(self, request, user_id, group_id):
""" """
GET retrieves an existing user-group relationship from the system GET retrieves an existing user-group relationship from the system
""" """
response_data = {} response_data = {}
base_uri = _generate_base_uri(request)
try: try:
existing_user = User.objects.get(id=user_id, is_active=True) existing_user = User.objects.get(id=user_id, is_active=True)
existing_relationship = existing_user.groups.get(id=group_id) existing_relationship = existing_user.groups.get(id=group_id)
...@@ -359,13 +363,13 @@ class UsersGroupsDetail(APIView): ...@@ -359,13 +363,13 @@ class UsersGroupsDetail(APIView):
if existing_user and existing_relationship: if existing_user and existing_relationship:
response_data['user_id'] = existing_user.id response_data['user_id'] = existing_user.id
response_data['group_id'] = existing_relationship.id response_data['group_id'] = existing_relationship.id
response_data['uri'] = base_uri response_data['uri'] = _generate_base_uri(request)
response_status = status.HTTP_200_OK response_status = status.HTTP_200_OK
else: else:
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def delete(self, request, user_id, group_id, format=None): def delete(self, request, user_id, group_id):
""" """
DELETE removes/inactivates/etc. an existing user-group relationship DELETE removes/inactivates/etc. an existing user-group relationship
""" """
...@@ -378,7 +382,7 @@ class UsersGroupsDetail(APIView): ...@@ -378,7 +382,7 @@ class UsersGroupsDetail(APIView):
class UsersCoursesList(APIView): class UsersCoursesList(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, user_id, format=None): def post(self, request, user_id):
""" """
POST creates a new course enrollment for a user POST creates a new course enrollment for a user
""" """
...@@ -404,7 +408,7 @@ class UsersCoursesList(APIView): ...@@ -404,7 +408,7 @@ class UsersCoursesList(APIView):
status_code = status.HTTP_404_NOT_FOUND status_code = status.HTTP_404_NOT_FOUND
return Response(response_data, status=status_code) return Response(response_data, status=status_code)
def get(self, request, user_id, format=None): def get(self, request, user_id):
""" """
GET creates the list of enrolled courses for a user GET creates the list of enrolled courses for a user
""" """
...@@ -435,7 +439,7 @@ class UsersCoursesList(APIView): ...@@ -435,7 +439,7 @@ class UsersCoursesList(APIView):
class UsersCoursesDetail(APIView): class UsersCoursesDetail(APIView):
permission_classes = (ApiKeyHeaderPermission,) permission_classes = (ApiKeyHeaderPermission,)
def post(self, request, user_id, course_id, format=None): def post(self, request, user_id, course_id):
""" """
POST creates an ACTIVE course enrollment for the specified user POST creates an ACTIVE course enrollment for the specified user
""" """
...@@ -465,7 +469,7 @@ class UsersCoursesDetail(APIView): ...@@ -465,7 +469,7 @@ class UsersCoursesDetail(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def get(self, request, user_id, course_id, format=None): def get(self, request, user_id, course_id):
""" """
GET identifies an ACTIVE course enrollment for the specified user GET identifies an ACTIVE course enrollment for the specified user
""" """
...@@ -495,7 +499,7 @@ class UsersCoursesDetail(APIView): ...@@ -495,7 +499,7 @@ class UsersCoursesDetail(APIView):
response_status = status.HTTP_404_NOT_FOUND response_status = status.HTTP_404_NOT_FOUND
return Response(response_data, status=response_status) return Response(response_data, status=response_status)
def delete(self, request, user_id, course_id, format=None): def delete(self, request, user_id, course_id):
""" """
DELETE unenrolls the specified user from a course DELETE unenrolls the specified user from a course
""" """
......
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