Commit c2ebfde4 by jsa

implement forums endpoint for searching users

parent 526d4b1d
import logging
import json
from django.test.utils import override_settings
from django.test.client import Client, RequestFactory
from django.test.utils import override_settings
from django.contrib.auth.models import User
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from django.core.urlresolvers import reverse
from django.core.management import call_command
from util.testing import UrlResetMixin
from django_comment_common.models import Role
from django_comment_common.utils import seed_permissions_roles
from django_comment_client.base import views
from django_comment_client.tests.unicode import UnicodeTestMixin
from django.core.urlresolvers import reverse
from mock import patch, ANY
from nose.tools import assert_true, assert_equal # pylint: disable=E0611
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from nose.tools import assert_true, assert_equal # pylint: disable=E0611
from mock import patch, ANY
from django_comment_client.base import views
from django_comment_client.tests.unicode import UnicodeTestMixin
from django_comment_common.models import Role, FORUM_ROLE_STUDENT
from django_comment_common.utils import seed_permissions_roles
from student.tests.factories import CourseEnrollmentFactory, UserFactory
from util.testing import UrlResetMixin
from xmodule.modulestore.tests.factories import CourseFactory
from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
log = logging.getLogger(__name__)
......@@ -732,3 +733,85 @@ class CreateSubCommentUnicodeTestCase(ModuleStoreTestCase, UnicodeTestMixin, Moc
self.assertEqual(response.status_code, 200)
self.assertTrue(mock_request.called)
self.assertEqual(mock_request.call_args[1]["data"]["body"], text)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
class UsersEndpointTestCase(ModuleStoreTestCase, MockRequestSetupMixin):
def set_post_counts(self, mock_request, threads_count=1, comments_count=1):
"""
sets up a mock response from the comments service for getting post counts for our other_user
"""
self._set_mock_request_data(mock_request, {
"threads_count": threads_count,
"comments_count": comments_count,
})
def setUp(self):
self.course = CourseFactory.create()
seed_permissions_roles(self.course.id)
self.student = UserFactory.create()
self.enrollment = CourseEnrollmentFactory(user=self.student, course_id=self.course.id)
self.other_user = UserFactory.create(username="other")
CourseEnrollmentFactory(user=self.other_user, course_id=self.course.id)
def make_request(self, method='get', course_id=None, **kwargs):
course_id = course_id or self.course.id
request = getattr(RequestFactory(), method)("dummy_url", kwargs)
request.user = self.student
request.view_name = "users"
return views.users(request, course_id=course_id.to_deprecated_string())
@patch('lms.lib.comment_client.utils.requests.request')
def test_finds_exact_match(self, mock_request):
self.set_post_counts(mock_request)
response = self.make_request(username="other")
self.assertEqual(response.status_code, 200)
self.assertEqual(
json.loads(response.content)["users"],
[{"id": self.other_user.id, "username": self.other_user.username}]
)
@patch('lms.lib.comment_client.utils.requests.request')
def test_finds_no_match(self, mock_request):
self.set_post_counts(mock_request)
response = self.make_request(username="othor")
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.content)["users"], [])
def test_requires_GET(self):
response = self.make_request(method='post', username="other")
self.assertEqual(response.status_code, 405)
def test_requires_username_param(self):
response = self.make_request()
self.assertEqual(response.status_code, 400)
content = json.loads(response.content)
self.assertIn("errors", content)
self.assertNotIn("users", content)
def test_course_does_not_exist(self):
course_id = SlashSeparatedCourseKey.from_deprecated_string("does/not/exist")
response = self.make_request(course_id=course_id, username="other")
self.assertEqual(response.status_code, 404)
content = json.loads(response.content)
self.assertIn("errors", content)
self.assertNotIn("users", content)
def test_requires_requestor_enrolled_in_course(self):
# unenroll self.student from the course.
self.enrollment.delete()
response = self.make_request(username="other")
self.assertEqual(response.status_code, 404)
content = json.loads(response.content)
self.assertTrue(content.has_key("errors"))
self.assertFalse(content.has_key("users"))
@patch('lms.lib.comment_client.utils.requests.request')
def test_requires_matched_user_has_forum_content(self, mock_request):
self.set_post_counts(mock_request, 0, 0)
response = self.make_request(username="other")
self.assertEqual(response.status_code, 200)
self.assertEqual(json.loads(response.content)["users"], [])
......@@ -27,4 +27,5 @@ urlpatterns = patterns('django_comment_client.base.views', # nopep8
url(r'^(?P<commentable_id>[\w\-.]+)/threads/create$', 'create_thread', name='create_thread'),
url(r'^(?P<commentable_id>[\w\-.]+)/follow$', 'follow_commentable', name='follow_commentable'),
url(r'^(?P<commentable_id>[\w\-.]+)/unfollow$', 'unfollow_commentable', name='unfollow_commentable'),
url(r'users$', 'users', name='users'),
)
import time
import random
import os.path
import functools
import logging
import os.path
import random
import time
import urlparse
import functools
import lms.lib.comment_client as cc
import django_comment_client.utils as utils
import django_comment_client.settings as cc_settings
from django.core import exceptions
from django.contrib.auth.decorators import login_required
from django.views.decorators.http import require_POST
from django.views.decorators import csrf
from django.contrib.auth.models import User
from django.core import exceptions
from django.core.files.storage import get_storage_class
from django.http import Http404
from django.utils.translation import ugettext as _
from django.views.decorators import csrf
from django.views.decorators.http import require_GET, require_POST
from opaque_keys.edx.keys import CourseKey
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from courseware.access import has_access
from courseware.courses import get_course_with_access, get_course_by_id
from course_groups.cohorts import get_cohort_id, is_commentable_cohorted
from django_comment_client.utils import JsonResponse, JsonError, extract, add_courseware_context
import django_comment_client.settings as cc_settings
from django_comment_client.utils import (
add_courseware_context,
get_annotated_content_info,
get_ability,
JsonError,
JsonResponse,
safe_content
)
from django_comment_client.permissions import check_permissions_by_view, cached_has_permission
from courseware.access import has_access
from opaque_keys.edx.locations import SlashSeparatedCourseKey
from opaque_keys.edx.keys import CourseKey
import lms.lib.comment_client as cc
log = logging.getLogger(__name__)
......@@ -51,9 +55,9 @@ def permitted(fn):
def ajax_content_response(request, course_id, content):
user_info = cc.User.from_django_user(request.user).to_dict()
annotated_content_info = utils.get_annotated_content_info(course_id, content, request.user, user_info)
annotated_content_info = get_annotated_content_info(course_id, content, request.user, user_info)
return JsonResponse({
'content': utils.safe_content(content),
'content': safe_content(content),
'annotated_content_info': annotated_content_info,
})
......@@ -133,7 +137,7 @@ def create_thread(request, course_id, commentable_id):
if request.is_ajax():
return ajax_content_response(request, course_id, data)
else:
return JsonResponse(utils.safe_content(data))
return JsonResponse(safe_content(data))
@require_POST
......@@ -154,7 +158,7 @@ def update_thread(request, course_id, thread_id):
if request.is_ajax():
return ajax_content_response(request, SlashSeparatedCourseKey.from_deprecated_string(course_id), thread.to_dict())
else:
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
def _create_comment(request, course_key, thread_id=None, parent_id=None):
......@@ -195,7 +199,7 @@ def _create_comment(request, course_key, thread_id=None, parent_id=None):
if request.is_ajax():
return ajax_content_response(request, course_key, comment.to_dict())
else:
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -222,7 +226,7 @@ def delete_thread(request, course_id, thread_id):
"""
thread = cc.Thread.find(thread_id)
thread.delete()
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -241,7 +245,7 @@ def update_comment(request, course_id, comment_id):
if request.is_ajax():
return ajax_content_response(request, SlashSeparatedCourseKey.from_deprecated_string(course_id), comment.to_dict())
else:
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -255,7 +259,7 @@ def endorse_comment(request, course_id, comment_id):
comment = cc.Comment.find(comment_id)
comment.endorsed = request.POST.get('endorsed', 'false').lower() == 'true'
comment.save()
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -271,8 +275,8 @@ def openclose_thread(request, course_id, thread_id):
thread.save()
thread = thread.to_dict()
return JsonResponse({
'content': utils.safe_content(thread),
'ability': utils.get_ability(SlashSeparatedCourseKey.from_deprecated_string(course_id), thread, request.user),
'content': safe_content(thread),
'ability': get_ability(SlashSeparatedCourseKey.from_deprecated_string(course_id), thread, request.user),
})
......@@ -300,7 +304,7 @@ def delete_comment(request, course_id, comment_id):
"""
comment = cc.Comment.find(comment_id)
comment.delete()
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -313,7 +317,7 @@ def vote_for_comment(request, course_id, comment_id, value):
user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id)
user.vote(comment, value)
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -327,7 +331,7 @@ def undo_vote_for_comment(request, course_id, comment_id):
user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id)
user.unvote(comment)
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -341,7 +345,7 @@ def vote_for_thread(request, course_id, thread_id, value):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
user.vote(thread, value)
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -355,7 +359,7 @@ def flag_abuse_for_thread(request, course_id, thread_id):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
thread.flagAbuse(user, thread)
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -372,7 +376,7 @@ def un_flag_abuse_for_thread(request, course_id, thread_id):
thread = cc.Thread.find(thread_id)
remove_all = cached_has_permission(request.user, 'openclose_thread', course_id) or has_access(request.user, 'staff', course)
thread.unFlagAbuse(user, thread, remove_all)
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -386,7 +390,7 @@ def flag_abuse_for_comment(request, course_id, comment_id):
user = cc.User.from_django_user(request.user)
comment = cc.Comment.find(comment_id)
comment.flagAbuse(user, comment)
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -403,7 +407,7 @@ def un_flag_abuse_for_comment(request, course_id, comment_id):
remove_all = cached_has_permission(request.user, 'openclose_thread', course_key) or has_access(request.user, 'staff', course)
comment = cc.Comment.find(comment_id)
comment.unFlagAbuse(user, comment, remove_all)
return JsonResponse(utils.safe_content(comment.to_dict()))
return JsonResponse(safe_content(comment.to_dict()))
@require_POST
......@@ -417,7 +421,7 @@ def undo_vote_for_thread(request, course_id, thread_id):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
user.unvote(thread)
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -431,7 +435,7 @@ def pin_thread(request, course_id, thread_id):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
thread.pin(user, thread_id)
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -445,7 +449,7 @@ def un_pin_thread(request, course_id, thread_id):
user = cc.User.from_django_user(request.user)
thread = cc.Thread.find(thread_id)
thread.un_pin(user, thread_id)
return JsonResponse(utils.safe_content(thread.to_dict()))
return JsonResponse(safe_content(thread.to_dict()))
@require_POST
......@@ -598,3 +602,40 @@ def upload(request, course_id): # ajax upload file to a question or answer
'file_url': file_url,
}
})
@require_GET
@login_required
def users(request, course_id):
"""
Given a `username` query parameter, find matches for users in the forum for this course.
Only exact matches are supported here, so the length of the result set will either be 0 or 1.
"""
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
try:
course = get_course_with_access(request.user, 'load_forum', course_key)
except Http404:
# course didn't exist, or requesting user does not have access to it.
return JsonError(status=404)
try:
username = request.GET['username']
except KeyError:
# 400 is default status for JsonError
return JsonError(["username parameter is required"])
user_objs = []
try:
matched_user = User.objects.get(username=username)
cc_user = cc.User.from_django_user(matched_user)
cc_user.course_id=course_key
cc_user.retrieve(complete=False)
if (cc_user['threads_count'] + cc_user['comments_count']) > 0:
user_objs.append({
'id': matched_user.id,
'username': matched_user.username,
})
except User.DoesNotExist:
pass
return JsonResponse({"users": user_objs})
......@@ -117,6 +117,7 @@ class User(models.Model):
def _retrieve(self, *args, **kwargs):
url = self.url(action='get', params=self.attributes)
retrieve_params = self.default_retrieve_params
retrieve_params.update(kwargs)
if self.attributes.get('course_id'):
retrieve_params['course_id'] = self.course_id.to_deprecated_string()
try:
......
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