Commit e4df2c2c by stv

Add 'forum_roles' endpoint to user_api

Expose a new endpoint to query a list of users based on a
role name (Moderator, Student, etc.) and a course_id.

This will initially be used by the notifier to send daily digest
messages to forum moderators.
parent dd8d8fc5
......@@ -148,3 +148,4 @@ Sébastien Hinderer <Sebastien.Hinderer@inria.fr>
Kristin Stephens <ksteph@cs.berkeley.edu>
Ben Patterson <bpatterson@edx.org>
Luis Duarte <lduarte1991@gmail.com>
Steven Burch <stv@stanford.edu>
......@@ -8,11 +8,14 @@ from student.tests.factories import UserFactory
from unittest import SkipTest
from user_api.models import UserPreference
from user_api.tests.factories import UserPreferenceFactory
from django_comment_common import models
from xmodule.modulestore.locations import SlashSeparatedCourseKey
TEST_API_KEY = "test_api_key"
USER_LIST_URI = "/user_api/v1/users/"
USER_PREFERENCE_LIST_URI = "/user_api/v1/user_prefs/"
ROLE_LIST_URI = "/user_api/v1/forum_roles/Moderator/users/"
@override_settings(EDX_API_KEY=TEST_API_KEY)
......@@ -104,6 +107,20 @@ class EmptyUserTestCase(ApiTestCase):
self.assertEqual(result["results"], [])
class EmptyRoleTestCase(ApiTestCase):
"""Test that the endpoint supports empty result sets"""
course_id = SlashSeparatedCourseKey.from_deprecated_string("org/course/run")
LIST_URI = ROLE_LIST_URI + "?course_id=" + course_id.to_deprecated_string()
def test_get_list_empty(self):
"""Test that the endpoint properly returns empty result sets"""
result = self.get_json(self.LIST_URI)
self.assertEqual(result["count"], 0)
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
self.assertEqual(result["results"], [])
class UserApiTestCase(ApiTestCase):
def setUp(self):
super(UserApiTestCase, self).setUp()
......@@ -121,6 +138,92 @@ class UserApiTestCase(ApiTestCase):
]
class RoleTestCase(UserApiTestCase):
course_id = SlashSeparatedCourseKey.from_deprecated_string("org/course/run")
LIST_URI = ROLE_LIST_URI + "?course_id=" + course_id.to_deprecated_string()
def setUp(self):
super(RoleTestCase, self).setUp()
(role, _) = models.Role.objects.get_or_create(
name=models.FORUM_ROLE_MODERATOR,
course_id=self.course_id
)
for user in self.users:
user.roles.add(role)
def test_options_list(self):
self.assertAllowedMethods(self.LIST_URI, ["OPTIONS", "GET", "HEAD"])
def test_post_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("post", self.LIST_URI))
def test_put_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("put", self.LIST_URI))
def test_patch_list_not_allowed(self):
raise SkipTest("Django 1.4's test client does not support patch")
def test_delete_list_not_allowed(self):
self.assertHttpMethodNotAllowed(self.request_with_auth("delete", self.LIST_URI))
def test_list_unauthorized(self):
self.assertHttpForbidden(self.client.get(self.LIST_URI))
@override_settings(DEBUG=True)
@override_settings(EDX_API_KEY=None)
def test_debug_auth(self):
self.assertHttpOK(self.client.get(self.LIST_URI))
@override_settings(DEBUG=False)
@override_settings(EDX_API_KEY=TEST_API_KEY)
def test_basic_auth(self):
# ensure that having basic auth headers in the mix does not break anything
self.assertHttpOK(
self.request_with_auth("get", self.LIST_URI,
**self.basic_auth("someuser", "somepass")))
self.assertHttpForbidden(
self.client.get(self.LIST_URI, **self.basic_auth("someuser", "somepass")))
def test_get_list_nonempty(self):
result = self.get_json(self.LIST_URI)
users = result["results"]
self.assertEqual(result["count"], len(self.users))
self.assertEqual(len(users), len(self.users))
self.assertIsNone(result["next"])
self.assertIsNone(result["previous"])
for user in users:
self.assertUserIsValid(user)
def test_required_parameter(self):
response = self.request_with_auth("get", ROLE_LIST_URI)
self.assertHttpBadRequest(response)
def test_get_list_pagination(self):
first_page = self.get_json(self.LIST_URI, data={
"page_size": 3,
"course_id": self.course_id.to_deprecated_string(),
})
self.assertEqual(first_page["count"], 5)
first_page_next_uri = first_page["next"]
self.assertIsNone(first_page["previous"])
first_page_users = first_page["results"]
self.assertEqual(len(first_page_users), 3)
second_page = self.get_json(first_page_next_uri)
self.assertEqual(second_page["count"], 5)
self.assertIsNone(second_page["next"])
second_page_prev_uri = second_page["previous"]
second_page_users = second_page["results"]
self.assertEqual(len(second_page_users), 2)
self.assertEqual(self.get_json(second_page_prev_uri), first_page)
for user in first_page_users + second_page_users:
self.assertUserIsValid(user)
all_user_uris = [user["url"] for user in first_page_users + second_page_users]
self.assertEqual(len(set(all_user_uris)), 5)
class UserViewSetTest(UserApiTestCase):
LIST_URI = USER_LIST_URI
......
......@@ -14,4 +14,8 @@ urlpatterns = patterns(
r'^v1/preferences/(?P<pref_key>{})/users/$'.format(UserPreference.KEY_REGEX),
user_api_views.PreferenceUsersListView.as_view()
),
url(
r'^v1/forum_roles/(?P<name>[a-zA-Z]+)/users/$',
user_api_views.ForumRoleUsersListView.as_view()
),
)
......@@ -4,9 +4,14 @@ from rest_framework import authentication
from rest_framework import filters
from rest_framework import generics
from rest_framework import permissions
from rest_framework import status
from rest_framework import viewsets
from rest_framework.exceptions import ParseError
from rest_framework.response import Response
from user_api.serializers import UserSerializer, UserPreferenceSerializer
from user_api.models import UserPreference
from django_comment_common.models import Role
from xmodule.modulestore.locations import SlashSeparatedCourseKey
class ApiKeyHeaderPermission(permissions.BasePermission):
......@@ -35,6 +40,30 @@ class UserViewSet(viewsets.ReadOnlyModelViewSet):
paginate_by_param = "page_size"
class ForumRoleUsersListView(generics.ListAPIView):
"""
Forum roles are represented by a list of user dicts
"""
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (ApiKeyHeaderPermission,)
serializer_class = UserSerializer
paginate_by = 10
paginate_by_param = "page_size"
def get_queryset(self):
"""
Return a list of users with the specified role/course pair
"""
name = self.kwargs['name']
course_id_string = self.request.QUERY_PARAMS.get('course_id')
if not course_id_string:
raise ParseError('course_id must be specified')
course_id = SlashSeparatedCourseKey.from_deprecated_string(course_id_string)
role = Role.objects.get_or_create(course_id=course_id, name=name)[0]
users = role.users.all()
return users
class UserPreferenceViewSet(viewsets.ReadOnlyModelViewSet):
authentication_classes = (authentication.SessionAuthentication,)
permission_classes = (ApiKeyHeaderPermission,)
......
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