Commit fb28f68f by Zia Fazal Committed by Jonathan Piacenti

merged with master

move slash base course id logic in get_course_key
parent b9b7940a
...@@ -51,7 +51,16 @@ def _fake_get_get_course_social_stats(course_id): ...@@ -51,7 +51,16 @@ def _fake_get_get_course_social_stats(course_id):
'2': {'one': 'two'} '2': {'one': 'two'}
} }
@mock.patch("lms.lib.comment_client.user.get_course_social_stats", _fake_get_get_course_social_stats)
def _fake_get_course_thread_stats(course_id):
return {
'num_threads': 5,
'num_active_threads': 3
}
@mock.patch("api_manager.courses.views.get_course_social_stats", _fake_get_get_course_social_stats)
@mock.patch("api_manager.courses.views.get_course_thread_stats", _fake_get_course_thread_stats)
@override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE) @override_settings(MODULESTORE=TEST_DATA_MIXED_MODULESTORE)
@override_settings(EDX_API_KEY=TEST_API_KEY) @override_settings(EDX_API_KEY=TEST_API_KEY)
@mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False, @mock.patch.dict("django.conf.settings.FEATURES", {'ENFORCE_PASSWORD_POLICY': False,
...@@ -208,6 +217,13 @@ class CoursesApiTests(TestCase): ...@@ -208,6 +217,13 @@ class CoursesApiTests(TestCase):
grade_dict = {'value': points_scored, 'max_value': points_possible, 'user_id': user.id} grade_dict = {'value': points_scored, 'max_value': points_possible, 'user_id': user.id}
module.system.publish(module, 'grade', grade_dict) module.system.publish(module, 'grade', grade_dict)
for j, user in enumerate(self.users):
StudentModuleFactory.create(
course_id=self.course.id,
module_type='sequential',
module_state_key=self.item.location,
)
self.test_course_id = unicode(self.course.id) self.test_course_id = unicode(self.course.id)
self.test_bogus_course_id = 'i4x://foo/bar/baz' self.test_bogus_course_id = 'i4x://foo/bar/baz'
self.test_course_name = self.course.display_name self.test_course_name = self.course.display_name
...@@ -1736,6 +1752,7 @@ class CoursesApiTests(TestCase): ...@@ -1736,6 +1752,7 @@ class CoursesApiTests(TestCase):
def test_courses_data_metrics(self): def test_courses_data_metrics(self):
test_uri = self.base_courses_uri + '/' + self.test_course_id + '/users' test_uri = self.base_courses_uri + '/' + self.test_course_id + '/users'
completion_uri = '{}/{}/completions/'.format(self.base_courses_uri, unicode(self.course.id))
test_user_uri = self.base_users_uri test_user_uri = self.base_users_uri
users_to_add = 5 users_to_add = 5
for i in xrange(0, users_to_add): for i in xrange(0, users_to_add):
...@@ -1753,11 +1770,44 @@ class CoursesApiTests(TestCase): ...@@ -1753,11 +1770,44 @@ class CoursesApiTests(TestCase):
response = self.do_post(test_uri, post_data) response = self.do_post(test_uri, post_data)
self.assertEqual(response.status_code, 201) self.assertEqual(response.status_code, 201)
#create an organization
data = {
'name': 'Test Organization',
'display_name': 'Test Org Display Name',
'users': [created_user_id]
}
response = self.do_post(self.base_organizations_uri, data)
self.assertEqual(response.status_code, 201)
org_id = response.data['id']
for i in xrange(1, 5):
local_content_name = 'Video_Sequence{}'.format(i)
local_content = ItemFactory.create(
category="videosequence",
parent_location=self.chapter.location,
data=self.test_data,
display_name=local_content_name
)
content_id = unicode(local_content.scope_ids.usage_id)
completions_data = {'content_id': content_id, 'user_id': created_user_id, 'stage': None}
response = self.do_post(completion_uri, completions_data)
self.assertEqual(response.status_code, 201)
# get course metrics # get course metrics
course_metrics_uri = '{}/{}/metrics/'.format(self.base_courses_uri, self.test_course_id) course_metrics_uri = '{}/{}/metrics/'.format(self.base_courses_uri, self.test_course_id)
response = self.do_get(course_metrics_uri) response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['users_enrolled'], users_to_add + USER_COUNT) self.assertEqual(response.data['users_enrolled'], users_to_add + USER_COUNT)
self.assertEqual(response.data['users_started'], 1)
self.assertIsNotNone(response.data['grade_cutoffs'])
# get course metrics by organization
course_metrics_uri = '{}/{}/metrics/?organization={}'.format(self.base_courses_uri, self.test_course_id, org_id)
response = self.do_get(course_metrics_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['users_enrolled'], 1)
self.assertEqual(response.data['users_started'], 1)
# test with bogus course # test with bogus course
course_metrics_uri = '{}/{}/metrics/'.format(self.base_courses_uri, self.test_bogus_course_id) course_metrics_uri = '{}/{}/metrics/'.format(self.base_courses_uri, self.test_bogus_course_id)
......
...@@ -29,7 +29,8 @@ from student.roles import CourseRole, CourseAccessRole, CourseInstructorRole, Co ...@@ -29,7 +29,8 @@ from student.roles import CourseRole, CourseAccessRole, CourseInstructorRole, Co
from xmodule.modulestore.django import modulestore from xmodule.modulestore.django import modulestore
from api_manager.courseware_access import get_course, get_course_child, get_course_leaf_nodes, get_course_key, course_exists, get_modulestore from api_manager.courseware_access import get_course, get_course_child, get_course_leaf_nodes, get_course_key, \
course_exists, get_modulestore
from api_manager.models import CourseGroupRelationship, CourseContentGroupRelationship, GroupProfile, \ from api_manager.models import CourseGroupRelationship, CourseContentGroupRelationship, GroupProfile, \
CourseModuleCompletion CourseModuleCompletion
from api_manager.permissions import SecureAPIView, SecureListAPIView from api_manager.permissions import SecureAPIView, SecureListAPIView
...@@ -41,6 +42,7 @@ from .serializers import CourseModuleCompletionSerializer, CourseSerializer ...@@ -41,6 +42,7 @@ from .serializers import CourseModuleCompletionSerializer, CourseSerializer
from .serializers import GradeSerializer, CourseLeadersSerializer, CourseCompletionsLeadersSerializer from .serializers import GradeSerializer, CourseLeadersSerializer, CourseCompletionsLeadersSerializer
from lms.lib.comment_client.user import get_course_social_stats from lms.lib.comment_client.user import get_course_social_stats
from lms.lib.comment_client.thread import get_course_thread_stats
from lms.lib.comment_client.utils import CommentClientRequestError from lms.lib.comment_client.utils import CommentClientRequestError
from opaque_keys.edx.keys import CourseKey from opaque_keys.edx.keys import CourseKey
...@@ -1476,8 +1478,9 @@ class CoursesProjectList(SecureListAPIView): ...@@ -1476,8 +1478,9 @@ class CoursesProjectList(SecureListAPIView):
class CoursesMetrics(SecureAPIView): class CoursesMetrics(SecureAPIView):
""" """
### The CoursesMetrics view allows clients to retrieve a list of Metrics for the specified Course ### The CoursesMetrics view allows clients to retrieve a list of Metrics for the specified Course
- URI: ```/api/courses/{course_id}/metrics/``` - URI: ```/api/courses/{course_id}/metrics/?organization={organization_id}```
- GET: Returns a JSON representation (array) of the set of course metrics - GET: Returns a JSON representation (array) of the set of course metrics
- metrics can be filtered by organization by adding organization parameter to GET request
### Use Cases/Notes: ### Use Cases/Notes:
* Example: Display number of users enrolled in a given course * Example: Display number of users enrolled in a given course
""" """
...@@ -1488,11 +1491,31 @@ class CoursesMetrics(SecureAPIView): ...@@ -1488,11 +1491,31 @@ class CoursesMetrics(SecureAPIView):
""" """
if not course_exists(request, request.user, course_id): if not course_exists(request, request.user, course_id):
return Response({}, status=status.HTTP_404_NOT_FOUND) return Response({}, status=status.HTTP_404_NOT_FOUND)
course_key = get_course_key(course_id) course_descriptor, course_key, course_content = get_course(request, request.user, course_id)
users_enrolled = CourseEnrollment.num_enrolled_in(course_key) slash_course_id = get_course_key(course_id, slashseparated=True)
exclude_users = _get_aggregate_exclusion_user_ids(course_key)
users_enrolled_qs = CourseEnrollment.users_enrolled_in(course_key).exclude(id__in=exclude_users)
organization = request.QUERY_PARAMS.get('organization', None)
if organization:
users_enrolled_qs = users_enrolled_qs.filter(organizations=organization)
users_started_qs = CourseModuleCompletion.objects.filter(course_id=course_id).exclude(user_id__in=exclude_users)
if organization:
users_started_qs = users_started_qs.filter(user__organizations=organization)
data = { data = {
'users_enrolled': users_enrolled 'users_enrolled': users_enrolled_qs.count(),
'users_started': users_started_qs.values('user').distinct().count(),
'grade_cutoffs': course_descriptor.grading_policy['GRADE_CUTOFFS']
} }
thread_stats = {}
try:
thread_stats = get_course_thread_stats(slash_course_id)
except CommentClientRequestError, e:
data = {
"err_msg": str(e)
}
return Response(data, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
data.update(thread_stats)
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
...@@ -1633,18 +1656,10 @@ class CoursesMetricsSocial(SecureListAPIView): ...@@ -1633,18 +1656,10 @@ class CoursesMetricsSocial(SecureListAPIView):
def get(self, request, course_id): # pylint: disable=W0613 def get(self, request, course_id): # pylint: disable=W0613
try: try:
# be robust to the try of course_id we get from caller slash_course_id = get_course_key(course_id, slashseparated=True)
try:
# assume new style
course_key = CourseKey.from_string(course_id)
slash_course_id = course_key.to_deprecated_string()
except:
# assume course_id passed in is legacy format
slash_course_id = course_id
# the forum service expects the legacy slash separated string format # the forum service expects the legacy slash separated string format
data = get_course_social_stats(slash_course_id) data = get_course_social_stats(slash_course_id)
course_key = get_course_key(course_id)
# remove any excluded users from the aggregate # remove any excluded users from the aggregate
exclude_users = _get_aggregate_exclusion_user_ids(course_key) exclude_users = _get_aggregate_exclusion_user_ids(course_key)
......
...@@ -67,7 +67,7 @@ def get_course_leaf_nodes(course_key, detached_categories): ...@@ -67,7 +67,7 @@ def get_course_leaf_nodes(course_key, detached_categories):
return nodes return nodes
def get_course_key(course_id): def get_course_key(course_id, slashseparated=False):
try: try:
course_key = CourseKey.from_string(course_id) course_key = CourseKey.from_string(course_id)
except InvalidKeyError: except InvalidKeyError:
...@@ -75,6 +75,11 @@ def get_course_key(course_id): ...@@ -75,6 +75,11 @@ def get_course_key(course_id):
course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id) course_key = SlashSeparatedCourseKey.from_deprecated_string(course_id)
except InvalidKeyError: except InvalidKeyError:
course_key = None course_key = None
if slashseparated:
try:
course_key = course_key.to_deprecated_string()
except:
course_key = course_id
return course_key return course_key
......
...@@ -198,6 +198,18 @@ class Thread(models.Model): ...@@ -198,6 +198,18 @@ class Thread(models.Model):
self._update_from_response(response) self._update_from_response(response)
def get_course_thread_stats(course_id):
"""
Helper method to get threads stats by course
"""
url = _url_for_course_thread_stats(course_id)
response = perform_request(
'get',
url
)
return response
def _url_for_flag_abuse_thread(thread_id): def _url_for_flag_abuse_thread(thread_id):
return "{prefix}/threads/{thread_id}/abuse_flag".format(prefix=settings.PREFIX, thread_id=thread_id) return "{prefix}/threads/{thread_id}/abuse_flag".format(prefix=settings.PREFIX, thread_id=thread_id)
...@@ -212,3 +224,7 @@ def _url_for_pin_thread(thread_id): ...@@ -212,3 +224,7 @@ def _url_for_pin_thread(thread_id):
def _url_for_un_pin_thread(thread_id): def _url_for_un_pin_thread(thread_id):
return "{prefix}/threads/{thread_id}/unpin".format(prefix=settings.PREFIX, thread_id=thread_id) return "{prefix}/threads/{thread_id}/unpin".format(prefix=settings.PREFIX, thread_id=thread_id)
def _url_for_course_thread_stats(course_id):
return "{prefix}/courses/{course_id}/stats".format(prefix=settings.PREFIX, course_id=course_id)
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