Commit 4f164247 by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-users-courses-roles: Added support for setting user roles

parent e5a025df
...@@ -17,6 +17,8 @@ from django.test.utils import override_settings ...@@ -17,6 +17,8 @@ from django.test.utils import override_settings
from capa.tests.response_xml_factory import StringResponseXMLFactory from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from django_comment_common.models import Role, FORUM_ROLE_MODERATOR
from instructor.access import allow_access
from student.tests.factories import UserFactory, CourseEnrollmentFactory from student.tests.factories import UserFactory, CourseEnrollmentFactory
from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory from xmodule.modulestore.tests.factories import CourseFactory, ItemFactory
...@@ -189,6 +191,10 @@ class CoursesApiTests(TestCase): ...@@ -189,6 +191,10 @@ class CoursesApiTests(TestCase):
self.client = SecureClient() self.client = SecureClient()
cache.clear() cache.clear()
Role.objects.get_or_create(
name=FORUM_ROLE_MODERATOR,
course_id=self.course.id)
def do_get(self, uri): def do_get(self, uri):
"""Submit an HTTP GET request""" """Submit an HTTP GET request"""
headers = { headers = {
...@@ -1778,3 +1784,90 @@ class CoursesApiTests(TestCase): ...@@ -1778,3 +1784,90 @@ class CoursesApiTests(TestCase):
self.assertEqual(len(response.data['results']), 1) self.assertEqual(len(response.data['results']), 1)
self.assertEqual(response.data['results'][0]['city'], 'Denver') self.assertEqual(response.data['results'][0]['city'], 'Denver')
self.assertEqual(response.data['results'][0]['count'], 5) self.assertEqual(response.data['results'][0]['count'], 5)
def test_courses_roles_list_get(self):
allow_access(self.course, self.users[0], 'staff')
allow_access(self.course, self.users[1], 'instructor')
allow_access(self.course, self.users[2], 'staff')
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 3)
# filter roleset by user
user_id = {'user_id': '{}'.format(self.users[0].id)}
test_uri = '{}?{}'.format(test_uri, urlencode(user_id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1)
def test_courses_roles_list_get_invalid_course(self):
test_uri = '/api/courses/{}/roles/'.format(self.test_bogus_course_id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
def test_courses_roles_list_post(self):
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 0)
data = {'user_id': self.users[0].id, 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data), 1)
def test_courses_roles_list_post_invalid_course(self):
test_uri = '/api/courses/{}/roles/'.format(self.test_bogus_course_id)
data = {'user_id': self.users[0].id, 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404)
def test_courses_roles_list_post_invalid_user(self):
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
data = {'user_id': 23423, 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404)
def test_courses_roles_list_post_invalid_role(self):
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
data = {'user_id': self.users[0].id, 'role': 'invalid_role'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 400)
def test_courses_roles_users_detail_delete(self):
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
data = {'user_id': self.users[0].id, 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201)
response = self.do_get(test_uri)
self.assertEqual(len(response.data), 1)
delete_uri = '{}instructor/users/{}'.format(test_uri, self.users[0].id)
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 204)
response = self.do_get(test_uri)
self.assertEqual(len(response.data), 0)
def test_courses_roles_users_detail_delete_invalid_course(self):
test_uri = '/api/courses/{}/roles/'.format(self.test_bogus_course_id)
delete_uri = '{}instructor/users/{}'.format(test_uri, self.users[0].id)
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 404)
def test_courses_roles_users_detail_delete_invalid_user(self):
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
delete_uri = '{}instructor/users/291231'.format(test_uri)
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 404)
def test_courses_roles_users_detail_delete_invalid_role(self):
test_uri = '/api/courses/{}/roles/'.format(unicode(self.course.id))
delete_uri = '{}invalid_role/users/{}'.format(test_uri, self.users[0].id)
print delete_uri
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 404)
...@@ -20,19 +20,21 @@ urlpatterns = patterns( ...@@ -20,19 +20,21 @@ urlpatterns = patterns(
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/groups/(?P<group_id>[0-9]+)$', courses_views.CoursesGroupsDetail.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/groups/(?P<group_id>[0-9]+)$', courses_views.CoursesGroupsDetail.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/groups/*$', courses_views.CoursesGroupsList.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/groups/*$', courses_views.CoursesGroupsList.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/overview/*$', courses_views.CoursesOverview.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/overview/*$', courses_views.CoursesOverview.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/updates/*$', courses_views.CoursesUpdates.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/static_tabs/(?P<tab_id>[a-zA-Z0-9_+\/:]+)$', courses_views.CoursesStaticTabsDetail.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/static_tabs/(?P<tab_id>[a-zA-Z0-9_+\/:]+)$', courses_views.CoursesStaticTabsDetail.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/static_tabs/*$', courses_views.CoursesStaticTabsList.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/static_tabs/*$', courses_views.CoursesStaticTabsList.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/users/(?P<user_id>[0-9]+)$', courses_views.CoursesUsersDetail.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/users/*$', courses_views.CoursesUsersList.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/completions/*$', courses_views.CourseModuleCompletionList.as_view(), name='completion-list'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/completions/*$', courses_views.CourseModuleCompletionList.as_view(), name='completion-list'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/projects/*$', courses_views.CoursesProjectList.as_view(), name='courseproject-list'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/projects/*$', courses_views.CoursesProjectList.as_view(), name='courseproject-list'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/*$', courses_views.CourseMetrics.as_view(), name='course-metrics'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/*$', courses_views.CourseMetrics.as_view(), name='course-metrics'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/proficiency/leaders/*$', courses_views.CoursesLeadersList.as_view(), name='course-metrics-proficiency-leaders'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/cities/$', courses_views.CoursesCitiesMetrics.as_view(), name='courses-cities-metrics'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/completions/leaders/*$', courses_views.CoursesCompletionsLeadersList.as_view(), name='course-metrics-completions-leaders'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/completions/leaders/*$', courses_views.CoursesCompletionsLeadersList.as_view(), name='course-metrics-completions-leaders'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/workgroups/*$', courses_views.CoursesWorkgroupsList.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/proficiency/leaders/*$', courses_views.CoursesLeadersList.as_view(), name='course-metrics-proficiency-leaders'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/social/$', courses_views.CoursesSocialMetrics.as_view(), name='courses-social-metrics'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/social/$', courses_views.CoursesSocialMetrics.as_view(), name='courses-social-metrics'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/metrics/cities/$', courses_views.CoursesCitiesMetrics.as_view(), name='courses-cities-metrics'), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/roles/(?P<role>[a-z_]+)/users/(?P<user_id>[0-9]+)*$', courses_views.CoursesRolesUsersDetail.as_view(), name='courses-roles-users-detail'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/roles/*$', courses_views.CoursesRolesList.as_view(), name='courses-roles-list'),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/updates/*$', courses_views.CoursesUpdates.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/users/(?P<user_id>[0-9]+)$', courses_views.CoursesUsersDetail.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/users/*$', courses_views.CoursesUsersList.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)/workgroups/*$', courses_views.CoursesWorkgroupsList.as_view()),
url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)$', courses_views.CoursesDetail.as_view()), url(r'^(?P<course_id>[a-zA-Z0-9_+\/:]+)$', courses_views.CoursesDetail.as_view()),
url(r'/*$^', courses_views.CoursesList.as_view()), url(r'/*$^', courses_views.CoursesList.as_view()),
) )
......
...@@ -6,6 +6,7 @@ import itertools ...@@ -6,6 +6,7 @@ import itertools
from lxml import etree from lxml import etree
from StringIO import StringIO from StringIO import StringIO
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
...@@ -20,7 +21,11 @@ from rest_framework.response import Response ...@@ -20,7 +21,11 @@ from rest_framework.response import Response
from courseware.courses import get_course_about_section, get_course_info_section from courseware.courses import get_course_about_section, get_course_info_section
from courseware.models import StudentModule from courseware.models import StudentModule
from courseware.views import get_static_tab_contents from courseware.views import get_static_tab_contents
from django_comment_common.models import FORUM_ROLE_MODERATOR
from instructor.access import allow_access, revoke_access, update_forum_role
from student.models import CourseEnrollment, CourseEnrollmentAllowed from student.models import CourseEnrollment, CourseEnrollmentAllowed
from student.roles import CourseInstructorRole, CourseStaffRole
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from api_manager.courseware_access import get_course, get_course_child from api_manager.courseware_access import get_course, get_course_child
...@@ -31,7 +36,6 @@ from api_manager.users.serializers import UserSerializer, UserCountByCitySeriali ...@@ -31,7 +36,6 @@ from api_manager.users.serializers import UserSerializer, UserCountByCitySeriali
from api_manager.utils import generate_base_uri from api_manager.utils import generate_base_uri
from projects.models import Project, Workgroup from projects.models import Project, Workgroup
from projects.serializers import ProjectSerializer, BasicWorkgroupSerializer from projects.serializers import ProjectSerializer, BasicWorkgroupSerializer
from .serializers import CourseModuleCompletionSerializer from .serializers import CourseModuleCompletionSerializer
from .serializers import GradeSerializer, CourseLeadersSerializer, CourseCompletionsLeadersSerializer from .serializers import GradeSerializer, CourseLeadersSerializer, CourseCompletionsLeadersSerializer
...@@ -975,7 +979,7 @@ class CoursesUsersList(SecureAPIView): ...@@ -975,7 +979,7 @@ class CoursesUsersList(SecureAPIView):
def get(self, request, course_id): def get(self, request, course_id):
""" """
GET /api/courses/{course_id} GET /api/courses/{course_id}/users
""" """
orgs = request.QUERY_PARAMS.get('organizations') orgs = request.QUERY_PARAMS.get('organizations')
groups = request.QUERY_PARAMS.get('groups', None) groups = request.QUERY_PARAMS.get('groups', None)
...@@ -1644,3 +1648,96 @@ class CoursesCitiesMetrics(SecureListAPIView): ...@@ -1644,3 +1648,96 @@ class CoursesCitiesMetrics(SecureListAPIView):
queryset = queryset.values('profile__city').annotate(count=Count('profile__city'))\ queryset = queryset.values('profile__city').annotate(count=Count('profile__city'))\
.filter(count__gt=0).order_by('-count') .filter(count__gt=0).order_by('-count')
return queryset return queryset
class CoursesRolesList(SecureAPIView):
"""
### The CoursesRolesList view allows clients to interact with the Course's roleset
- URI: ```/api/courses/{course_id}/roles```
- GET: Returns a JSON representation of the specified Course roleset
### Use Cases/Notes:
* Use the CoursesRolesList view to manage a User's TA status
* Use GET to retrieve the set of roles configured for a particular course
"""
def get(self, request, course_id): # pylint: disable=W0613
"""
GET /api/courses/{course_id}/roles/
"""
course_id = self.kwargs['course_id']
course_descriptor, course_key, course_content = get_course(self.request, self.request.user, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
raise Http404
response_data = []
instructors = CourseInstructorRole(course_key).users_with_role()
for instructor in instructors:
response_data.append({'id': instructor.id, 'role': 'instructor'})
staff = CourseStaffRole(course_key).users_with_role()
for admin in staff:
response_data.append({'id': admin.id, 'role': 'staff'})
user_id = self.request.QUERY_PARAMS.get('user_id', None)
if user_id:
response_data = list([item for item in response_data if int(item['id']) == int(user_id)])
return Response(response_data, status=status.HTTP_200_OK)
def post(self, request, course_id):
"""
POST /api/courses/{course_id}/roles/
"""
course_id = self.kwargs['course_id']
course_descriptor, course_key, course_content = get_course(self.request, self.request.user, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
raise Http404
user_id = request.DATA.get('user_id', None)
try:
user = User.objects.get(id=user_id)
except ObjectDoesNotExist:
raise Http404
role = request.DATA.get('role', None)
try:
allow_access(course_descriptor, user, role)
update_forum_role(course_key, user, FORUM_ROLE_MODERATOR, 'allow')
except ValueError:
return Response({}, status=status.HTTP_400_BAD_REQUEST)
return Response(request.DATA, status=status.HTTP_201_CREATED)
class CoursesRolesUsersDetail(SecureAPIView):
"""
### The CoursesUsersRolesDetail view allows clients to interact with a specific Course Role
- URI: ```/api/courses/{course_id}/roles/{role}/users/{user_id}```
- DELETE: Removes an existing Course Role specification
### Use Cases/Notes:
* Use the DELETE operation to revoke a particular role for the specified user
"""
def delete(self, request, course_id, role, user_id): # pylint: disable=W0613
"""
DELETE /api/courses/{course_id}/roles/{role}/users/{user_id}
"""
course_id = self.kwargs['course_id']
course_descriptor, course_key, course_content = get_course(self.request, self.request.user, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
return Response({}, status=status.HTTP_404_NOT_FOUND)
user_id = self.kwargs['user_id']
try:
user = User.objects.get(id=user_id)
except ObjectDoesNotExist:
return Response({}, status=status.HTTP_404_NOT_FOUND)
role = self.kwargs['role']
try:
revoke_access(course_descriptor, user, role)
update_forum_role(course_key, user, FORUM_ROLE_MODERATOR, 'revoke')
except ValueError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
return Response({}, status=status.HTTP_204_NO_CONTENT)
...@@ -21,3 +21,9 @@ class UserCountByCitySerializer(serializers.Serializer): ...@@ -21,3 +21,9 @@ class UserCountByCitySerializer(serializers.Serializer):
""" Serializer for user count by city """ """ Serializer for user count by city """
city = serializers.CharField(source='profile__city') city = serializers.CharField(source='profile__city')
count = serializers.IntegerField() count = serializers.IntegerField()
class UserRolesSerializer(serializers.Serializer):
""" Serializer for user roles """
course_id = serializers.CharField(source='course_id')
role = serializers.CharField(source='role')
...@@ -19,6 +19,8 @@ from django.test.utils import override_settings ...@@ -19,6 +19,8 @@ from django.test.utils import override_settings
from capa.tests.response_xml_factory import StringResponseXMLFactory from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory
from django_comment_common.models import Role, FORUM_ROLE_MODERATOR
from instructor.access import allow_access
from projects.models import Project from projects.models import Project
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from student.models import anonymous_id_for_user from student.models import anonymous_id_for_user
...@@ -93,6 +95,10 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -93,6 +95,10 @@ class UsersApiTests(ModuleStoreTestCase):
self.client = SecureClient() self.client = SecureClient()
cache.clear() cache.clear()
Role.objects.get_or_create(
name=FORUM_ROLE_MODERATOR,
course_id=self.course.id)
def do_post(self, uri, data): def do_post(self, uri, data):
"""Submit an HTTP POST request""" """Submit an HTTP POST request"""
headers = { headers = {
...@@ -1346,3 +1352,106 @@ class UsersApiTests(ModuleStoreTestCase): ...@@ -1346,3 +1352,106 @@ class UsersApiTests(ModuleStoreTestCase):
test_uri = '/api/users/{}/courses/{}/metrics/social/'.format(12345, self.course.id) test_uri = '/api/users/{}/courses/{}/metrics/social/'.format(12345, 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)
def test_users_roles_list_get(self):
allow_access(self.course, self.user, 'staff')
course2 = CourseFactory.create(
display_name="TEST COURSE2",
start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16, 14, 30)
)
allow_access(course2, self.user, 'instructor')
course3 = CourseFactory.create(
display_name="TEST COURSE3",
start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16, 14, 30)
)
allow_access(course3, self.user, 'staff')
test_uri = '/api/users/{}/roles/'.format(self.user.id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 3)
# filter roleset by course
course_id = {'course_id': '{}'.format(unicode(course3.id))}
test_uri = '{}?{}'.format(test_uri, urlencode(course_id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 1)
def test_users_roles_list_get_invalid_user(self):
test_uri = '/api/users/23423/roles/'
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
def test_users_roles_list_get_invalid_course(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
course_id = {'course_id': '{}'.format(unicode(self.test_bogus_course_id))}
test_uri = '{}?{}'.format(test_uri, urlencode(course_id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 404)
def test_users_roles_list_post(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 0)
data = {'course_id': unicode(self.course.id), 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201)
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['count'], 1)
def test_users_roles_list_post_invalid_user(self):
test_uri = '/api/users/2131/roles/'
data = {'course_id': unicode(self.course.id), 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404)
def test_users_roles_list_post_invalid_course(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
data = {'course_id': self.test_bogus_course_id, 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 404)
def test_users_roles_list_post_invalid_role(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
data = {'course_id': unicode(self.course.id), 'role': 'invalid_role'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 400)
def test_users_roles_courses_detail_delete(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
data = {'course_id': unicode(self.course.id), 'role': 'instructor'}
response = self.do_post(test_uri, data)
self.assertEqual(response.status_code, 201)
response = self.do_get(test_uri)
self.assertEqual(response.data['count'], 1)
delete_uri = '{}instructor/courses/{}'.format(test_uri, unicode(self.course.id))
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 204)
response = self.do_get(test_uri)
self.assertEqual(response.data['count'], 0)
def test_users_roles_courses_detail_delete_invalid_course(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
delete_uri = '{}instructor/courses/{}'.format(test_uri, self.test_bogus_course_id)
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 404)
def test_users_roles_courses_detail_delete_invalid_user(self):
test_uri = '/api/users/124134/roles/'
delete_uri = '{}instructor/courses/{}'.format(test_uri, unicode(self.course.id))
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 404)
def test_users_roles_courses_detail_delete_invalid_role(self):
test_uri = '/api/users/{}/roles/'.format(self.user.id)
delete_uri = '{}invalid_role/courses/{}'.format(test_uri, unicode(self.course.id))
response = self.do_delete(delete_uri)
self.assertEqual(response.status_code, 404)
...@@ -22,6 +22,8 @@ urlpatterns = patterns( ...@@ -22,6 +22,8 @@ urlpatterns = patterns(
url(r'^(?P<user_id>[a-zA-Z0-9]+)/preferences$', users_views.UsersPreferences.as_view(), name='users-preferences-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/preferences$', users_views.UsersPreferences.as_view(), name='users-preferences-list'),
url(r'^(?P<user_id>[a-zA-Z0-9]+)/preferences/(?P<preference_id>[a-zA-Z0-9_]+)$', users_views.UsersPreferencesDetail.as_view(), name='users-preferences-detail'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/preferences/(?P<preference_id>[a-zA-Z0-9_]+)$', users_views.UsersPreferencesDetail.as_view(), name='users-preferences-detail'),
url(r'^(?P<user_id>[a-zA-Z0-9]+)/organizations/$', users_views.UsersOrganizationsList.as_view(), name='users-organizations-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/organizations/$', users_views.UsersOrganizationsList.as_view(), name='users-organizations-list'),
url(r'^(?P<user_id>[a-zA-Z0-9]+)/roles/(?P<role>[a-z_]+)/courses/(?P<course_id>[a-zA-Z0-9_+\/:]+)$', users_views.UsersRolesCoursesDetail.as_view(), name='users-roles-courses-detail'),
url(r'^(?P<user_id>[a-zA-Z0-9]+)/roles/*$', users_views.UsersRolesList.as_view(), name='users-roles-list'),
url(r'^(?P<user_id>[a-zA-Z0-9]+)/workgroups/$', users_views.UsersWorkgroupsList.as_view(), name='users-workgroups-list'), url(r'^(?P<user_id>[a-zA-Z0-9]+)/workgroups/$', users_views.UsersWorkgroupsList.as_view(), name='users-workgroups-list'),
url(r'^(?P<user_id>[a-zA-Z0-9]+)$', users_views.UsersDetail.as_view(), name='apimgr-users-detail'), url(r'^(?P<user_id>[a-zA-Z0-9]+)$', users_views.UsersDetail.as_view(), name='apimgr-users-detail'),
url(r'/*$^', users_views.UsersList.as_view(), name='apimgr-users-list'), url(r'/*$^', users_views.UsersList.as_view(), name='apimgr-users-list'),
......
...@@ -15,37 +15,32 @@ from rest_framework import status ...@@ -15,37 +15,32 @@ from rest_framework import status
from rest_framework import filters from rest_framework import filters
from rest_framework.response import Response from rest_framework.response import Response
from courseware import grades, module_render
from api_manager.courseware_access import get_course, get_course_child, get_course_total_score
from api_manager.permissions import SecureAPIView, SecureListAPIView, IdsInFilterBackend, HasOrgsFilterBackend
from api_manager.models import GroupProfile, APIUser as User
from api_manager.organizations.serializers import OrganizationSerializer
from api_manager.courses.serializers import CourseModuleCompletionSerializer
from api_manager.utils import generate_base_uri
from projects.serializers import BasicWorkgroupSerializer
from .serializers import UserSerializer, UserCountByCitySerializer
from courseware import module_render
from courseware.model_data import FieldDataCache from courseware.model_data import FieldDataCache
from courseware.models import StudentModule from courseware.models import StudentModule
from courseware.views import get_module_for_descriptor, save_child_position, get_current_child from courseware.views import save_child_position, get_current_child
from django_comment_common.models import FORUM_ROLE_MODERATOR
from instructor.access import allow_access, revoke_access, update_forum_role
from lang_pref import LANGUAGE_KEY from lang_pref import LANGUAGE_KEY
from lms.lib.comment_client.user import User as CommentUser
from lms.lib.comment_client.utils import CommentClientRequestError
from student.models import CourseEnrollment, PasswordHistory, UserProfile from student.models import CourseEnrollment, PasswordHistory, UserProfile
from openedx.core.djangoapps.user_api.models import UserPreference from openedx.core.djangoapps.user_api.models import UserPreference
from xmodule.modulestore.django import modulestore from student.roles import CourseInstructorRole, CourseStaffRole, UserBasedRole
from util.bad_request_rate_limiter import BadRequestRateLimiter
from util.password_policy_validators import ( from util.password_policy_validators import (
validate_password_length, validate_password_complexity, validate_password_length, validate_password_complexity,
validate_password_dictionary validate_password_dictionary
) )
from util.bad_request_rate_limiter import BadRequestRateLimiter
from courseware import grades
from lms.lib.comment_client.user import User as CommentUser from api_manager.courses.serializers import CourseModuleCompletionSerializer
from lms.lib.comment_client.utils import CommentClientRequestError from api_manager.courseware_access import get_course, get_course_child, get_course_total_score
from api_manager.permissions import SecureAPIView, SecureListAPIView, IdsInFilterBackend, HasOrgsFilterBackend
from api_manager.models import GroupProfile, APIUser as User
from api_manager.organizations.serializers import OrganizationSerializer
from api_manager.utils import generate_base_uri
from projects.serializers import BasicWorkgroupSerializer
from .serializers import UserSerializer, UserCountByCitySerializer, UserRolesSerializer
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
AUDIT_LOG = logging.getLogger("audit") AUDIT_LOG = logging.getLogger("audit")
...@@ -713,8 +708,6 @@ class UsersCoursesDetail(SecureAPIView): ...@@ -713,8 +708,6 @@ class UsersCoursesDetail(SecureAPIView):
return Response(response_data, status=status.HTTP_400_BAD_REQUEST) return Response(response_data, status=status.HTTP_400_BAD_REQUEST)
response_data['position'] = content_position response_data['position'] = content_position
return Response(response_data, status=status.HTTP_200_OK) return Response(response_data, status=status.HTTP_200_OK)
def get(self, request, user_id, course_id): def get(self, request, user_id, course_id):
...@@ -1083,3 +1076,95 @@ class UsersMetricsCitiesList(SecureListAPIView): ...@@ -1083,3 +1076,95 @@ class UsersMetricsCitiesList(SecureListAPIView):
queryset = queryset.values('profile__city').annotate(count=Count('profile__city'))\ queryset = queryset.values('profile__city').annotate(count=Count('profile__city'))\
.filter(count__gt=0).order_by('-count') .filter(count__gt=0).order_by('-count')
return queryset return queryset
class UsersRolesList(SecureListAPIView):
"""
### The UsersRolesList view allows clients to interact with the User's roleset
- URI: ```/api/users/{user_id}/courses/{course_id}/roles```
- GET: Returns a JSON representation of the specified Course roleset
### Use Cases/Notes:
* Use the UsersRolesList view to manage a User's TA status
* Use GET to retrieve the set of roles a User plays for a particular course
"""
serializer_class = UserRolesSerializer
def get_queryset(self):
user_id = self.kwargs['user_id']
try:
user = User.objects.get(id=user_id)
except ObjectDoesNotExist:
raise Http404
instructor_courses = UserBasedRole(user, CourseInstructorRole.ROLE).courses_with_role()
staff_courses = UserBasedRole(user, CourseStaffRole.ROLE).courses_with_role()
queryset = instructor_courses | staff_courses
course_id = self.request.QUERY_PARAMS.get('course_id', None)
if course_id:
course_descriptor, course_key, course_content = get_course(self.request, user, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
raise Http404
queryset = queryset.filter(course_id=course_key)
return queryset
def post(self, request, user_id):
"""
POST /api/users/{user_id}/roles/
"""
user_id = self.kwargs['user_id']
try:
user = User.objects.get(id=user_id)
except ObjectDoesNotExist:
raise Http404
course_id = request.DATA.get('course_id', None)
course_descriptor, course_key, course_content = get_course(self.request, self.request.user, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
raise Http404
role = request.DATA.get('role', None)
try:
allow_access(course_descriptor, user, role)
update_forum_role(course_key, user, FORUM_ROLE_MODERATOR, 'allow')
except ValueError:
return Response({}, status=status.HTTP_400_BAD_REQUEST)
return Response(request.DATA, status=status.HTTP_201_CREATED)
class UsersRolesCoursesDetail(SecureAPIView):
"""
### The UsersRolesCoursesDetail view allows clients to interact with a specific User/Course Role
- URI: ```/api/users/{user_id}/roles/{role}/courses/{course_id}/```
- DELETE: Removes an existing Course Role specification
### Use Cases/Notes:
* Use the DELETE operation to revoke a particular role for the specified user
"""
def delete(self, request, user_id, role, course_id): # pylint: disable=W0613
"""
DELETE /api/users/{user_id}/roles/{role}/courses/{course_id}
"""
course_id = self.kwargs['course_id']
print course_id
course_descriptor, course_key, course_content = get_course(self.request, self.request.user, course_id, depth=None) # pylint: disable=W0612
if not course_descriptor:
return Response({}, status=status.HTTP_404_NOT_FOUND)
user_id = self.kwargs['user_id']
print user_id
try:
user = User.objects.get(id=user_id)
except ObjectDoesNotExist:
return Response({}, status=status.HTTP_404_NOT_FOUND)
role = self.kwargs['role']
try:
revoke_access(course_descriptor, user, role)
update_forum_role(course_key, user, FORUM_ROLE_MODERATOR, 'revoke')
except ValueError:
return Response({}, status=status.HTTP_404_NOT_FOUND)
return Response({}, status=status.HTTP_204_NO_CONTENT)
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