From 6eed9667bea003f308aa2ea59c1ca72e185a2e82 Mon Sep 17 00:00:00 2001 From: Zia Fazal Date: Thu, 26 Jun 2014 17:18:27 +0500 Subject: [PATCH] position of user in completions and course avg added completions to response dict Merged course completions leaders and course avg added is_active check pep8 compliant remove serialiser class --- .../api_manager/courses/serializers.py | 9 +++ lms/djangoapps/api_manager/courses/tests.py | 48 +++++++++++++++ lms/djangoapps/api_manager/courses/urls.py | 1 + lms/djangoapps/api_manager/courses/views.py | 59 ++++++++++++++++++- 4 files changed, 114 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/api_manager/courses/serializers.py b/lms/djangoapps/api_manager/courses/serializers.py index d901c1d3e52e..57b04a9504a7 100644 --- a/lms/djangoapps/api_manager/courses/serializers.py +++ b/lms/djangoapps/api_manager/courses/serializers.py @@ -27,3 +27,12 @@ class CourseLeadersSerializer(serializers.Serializer): title = serializers.CharField(source='student__profile__title') avatar_url = serializers.CharField(source='student__profile__avatar_url') points_scored = serializers.IntegerField() + + +class CourseCompletionsLeadersSerializer(serializers.Serializer): + """ Serializer for course completions leaderboard """ + id = serializers.IntegerField(source='user__id') + username = serializers.CharField(source='user__username') + title = serializers.CharField(source='user__profile__title') + avatar_url = serializers.CharField(source='user__profile__avatar_url') + completions = serializers.IntegerField() diff --git a/lms/djangoapps/api_manager/courses/tests.py b/lms/djangoapps/api_manager/courses/tests.py index 37815bbbf605..23a7f26791e9 100644 --- a/lms/djangoapps/api_manager/courses/tests.py +++ b/lms/djangoapps/api_manager/courses/tests.py @@ -1315,6 +1315,54 @@ def test_courses_leaders_list_get(self): response = self.do_get(test_uri) self.assertEqual(response.status_code, 404) + def test_courses_completions_leaders_list_get(self): + completion_uri = '{}/{}/completions/'.format(self.base_courses_uri, self.course.id) + users = [] + for i in xrange(1, 5): + data = { + 'email': 'test{}@example.com'.format(i), + 'username': 'test_user{}'.format(i), + 'password': 'test_pass', + 'first_name': 'John{}'.format(i), + 'last_name': 'Doe{}'.format(i) + } + response = self.do_post(self.base_users_uri, data) + self.assertEqual(response.status_code, 201) + users.append(response.data['id']) + + for i in xrange(1, 26): + if i < 3: + user_id = users[0] + elif i < 8: + user_id = users[1] + elif i < 16: + user_id = users[2] + else: + user_id = users[3] + content_id = self.course_content.id + str(i) + completions_data = {'content_id': content_id, 'user_id': user_id} + response = self.do_post(completion_uri, completions_data) + self.assertEqual(response.status_code, 201) + + test_uri = '{}/{}/metrics/completions/leaders/?{}'.format(self.base_courses_uri, self.test_course_id, 'count=6') + response = self.do_get(test_uri) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['leaders']), 4) + self.assertEqual(response.data['course_avg'], 6.3) + + # without count filter and user_id + test_uri = '{}/{}/metrics/completions/leaders/?user_id={}'.format(self.base_courses_uri, self.test_course_id, + users[3]) + response = self.do_get(test_uri) + self.assertEqual(response.status_code, 200) + self.assertEqual(len(response.data['leaders']), 3) + self.assertEqual(response.data['position'], 1) + self.assertEqual(response.data['completions'], 10) + + # test with bogus course + test_uri = '{}/{}/metrics/completions/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id) + response = self.do_get(test_uri) + self.assertEqual(response.status_code, 404) def test_courses_grades_list_get(self): # Retrieve the list of grades for this course diff --git a/lms/djangoapps/api_manager/courses/urls.py b/lms/djangoapps/api_manager/courses/urls.py index 26e629f67f39..67da8a6f50d3 100644 --- a/lms/djangoapps/api_manager/courses/urls.py +++ b/lms/djangoapps/api_manager/courses/urls.py @@ -31,6 +31,7 @@ url(r'^(?P[^/]+/[^/]+/[^/]+)/projects/*$', courses_views.CoursesProjectList.as_view(), name='courseproject-list'), url(r'^(?P[^/]+/[^/]+/[^/]+)/metrics/*$', courses_views.CourseMetrics.as_view(), name='course-metrics'), url(r'^(?P[^/]+/[^/]+/[^/]+)/metrics/proficiency/leaders/*$', courses_views.CoursesLeadersList.as_view(), name='course-metrics-proficiency-leaders'), + url(r'^(?P[^/]+/[^/]+/[^/]+)/metrics/completions/leaders/*$', courses_views.CoursesCompletionsLeadersList.as_view(), name='course-metrics-completions-leaders'), ) urlpatterns = format_suffix_patterns(urlpatterns) diff --git a/lms/djangoapps/api_manager/courses/views.py b/lms/djangoapps/api_manager/courses/views.py index 19f0a44abb3a..6a32f56f2c72 100644 --- a/lms/djangoapps/api_manager/courses/views.py +++ b/lms/djangoapps/api_manager/courses/views.py @@ -9,7 +9,7 @@ from django.conf import settings from django.contrib.auth.models import Group, User from django.core.exceptions import ObjectDoesNotExist -from django.db.models import Avg, Sum +from django.db.models import Avg, Sum, Count from django.http import Http404 from django.utils.translation import ugettext_lazy as _ @@ -34,7 +34,7 @@ from projects.serializers import ProjectSerializer from .serializers import CourseModuleCompletionSerializer -from .serializers import GradeSerializer, CourseLeadersSerializer +from .serializers import GradeSerializer, CourseLeadersSerializer, CourseCompletionsLeadersSerializer log = logging.getLogger(__name__) @@ -1445,7 +1445,7 @@ class CoursesLeadersList(SecureListAPIView): def get_queryset(self): """ - GET /api/courses/{course_id}/leaders/ + GET /api/courses/{course_id}/metrics/proficiency/leaders/ """ course_id = self.kwargs['course_id'] content_id = self.request.QUERY_PARAMS.get('content_id', None) @@ -1467,3 +1467,56 @@ def get_queryset(self): queryset = queryset.values('student__id', 'student__username', 'student__profile__title', 'student__profile__avatar_url').annotate(points_scored=Sum('grade')).order_by('-points_scored')[:count] return queryset + + +class CoursesCompletionsLeadersList(SecureAPIView): + """ + ### The CoursesCompletionsLeadersList view allows clients to retrieve top 3 users who are leading + in terms of course module completions and course average for the specified Course, if user_id parameter is given + position of user is returned + - URI: ```/api/courses/{course_id}/metrics/completions/leaders/``` + - GET: Returns a JSON representation (array) of the users with points scored + Filters can also be applied + ```/api/courses/{course_id}/metrics/completions/leaders/?content_id={content_id}``` + To get more than 3 users use count parameter + ```/api/courses/{course_id}/metrics/completions/leaders/?count=6``` + ### Use Cases/Notes: + * Example: Display leaders in terms of completions in a given course + * Example: Display top 3 users leading in terms of completions in a given course + """ + + def get(self, request, course_id): # pylint: disable=W0613 + """ + GET /api/courses/{course_id}/metrics/completions/leaders/ + """ + user_id = self.request.QUERY_PARAMS.get('user_id', None) + count = self.request.QUERY_PARAMS.get('count', 3) + data = {} + course_avg = 0 + try: + get_course(course_id) + except ValueError: + raise Http404 + queryset = CourseModuleCompletion.objects.filter(course_id=course_id) + + if user_id: + user_completions = queryset.filter(user__id=user_id).count() + completions_above_user = queryset.filter(user__is_active=True).values('user__id')\ + .annotate(completions=Count('content_id')).filter(completions__gt=user_completions).count() + data['position'] = completions_above_user + 1 + data['completions'] = user_completions + + total_completions = queryset.filter(user__is_active=True).count() + users = CourseModuleCompletion.objects.filter(user__is_active=True)\ + .aggregate(total=Count('user__id', distinct=True)) + + if users and users['total'] > 0: + course_avg = round(total_completions / float(users['total']), 1) + data['course_avg'] = course_avg + + queryset = queryset.filter(user__is_active=True).values('user__id', 'user__username', 'user__profile__title', + 'user__profile__avatar_url')\ + .annotate(completions=Count('content_id')).order_by('-completions')[:count] + serializer = CourseCompletionsLeadersSerializer(queryset, many=True) + data['leaders'] = serializer.data # pylint: disable=E1101 + return Response(data, status=status.HTTP_200_OK)