Skip to content

Commit

Permalink
Merge pull request openedx#113 from edx-solutions/ziafazal/course-com…
Browse files Browse the repository at this point in the history
…pletion-leaders

course completions leaderboard
  • Loading branch information
mattdrayer committed Jun 27, 2014
2 parents ec041b7 + 6eed966 commit 65c89de
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 3 deletions.
9 changes: 9 additions & 0 deletions lms/djangoapps/api_manager/courses/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
48 changes: 48 additions & 0 deletions lms/djangoapps/api_manager/courses/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions lms/djangoapps/api_manager/courses/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/projects/*$', courses_views.CoursesProjectList.as_view(), name='courseproject-list'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/metrics/*$', courses_views.CourseMetrics.as_view(), name='course-metrics'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/metrics/proficiency/leaders/*$', courses_views.CoursesLeadersList.as_view(), name='course-metrics-proficiency-leaders'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/metrics/completions/leaders/*$', courses_views.CoursesCompletionsLeadersList.as_view(), name='course-metrics-completions-leaders'),
)

urlpatterns = format_suffix_patterns(urlpatterns)
59 changes: 56 additions & 3 deletions lms/djangoapps/api_manager/courses/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 _

Expand All @@ -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__)

Expand Down Expand Up @@ -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)
Expand All @@ -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)

0 comments on commit 65c89de

Please sign in to comment.