Commit 913252d4 by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-quality: Quality updates

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