Commit 0929dbac by Zia Fazal Committed by Jonathan Piacenti

ziafazal/api-order-leaders-by-time-of-achievement: order users by time of…

ziafazal/api-order-leaders-by-time-of-achievement: order users by time of achievement if points are equal
parent babd972c
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
Run these tests @ Devstack: Run these tests @ Devstack:
rake fasttest_lms[common/djangoapps/api_manager/courses/tests.py] rake fasttest_lms[common/djangoapps/api_manager/courses/tests.py]
""" """
from datetime import datetime from datetime import datetime, timedelta
import json import json
import uuid import uuid
import mock import mock
...@@ -18,6 +18,7 @@ from django.test.utils import override_settings ...@@ -18,6 +18,7 @@ from django.test.utils import override_settings
from capa.tests.response_xml_factory import StringResponseXMLFactory from capa.tests.response_xml_factory import StringResponseXMLFactory
from courseware.tests.factories import StudentModuleFactory from courseware.tests.factories import StudentModuleFactory
from courseware.models import StudentModule
from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE from courseware.tests.modulestore_config import TEST_DATA_MIXED_MODULESTORE
from django_comment_common.models import Role, FORUM_ROLE_MODERATOR from django_comment_common.models import Role, FORUM_ROLE_MODERATOR
from instructor.access import allow_access from instructor.access import allow_access
...@@ -1542,12 +1543,39 @@ class CoursesApiTests(TestCase): ...@@ -1542,12 +1543,39 @@ class CoursesApiTests(TestCase):
# make the last user an observer to asset that its content is being filtered out from # make the last user an observer to asset that its content is being filtered out from
# the aggregates # the aggregates
allow_access(self.course, self.users[USER_COUNT-1], 'observer') allow_access(self.course, self.users[USER_COUNT-1], 'observer')
# create another module completion to two users with same points
unit = ItemFactory.create(
parent_location=self.sub_section.location,
category="vertical",
metadata={'graded': True, 'format': 'Homework'},
display_name=u"test unit",
)
item = ItemFactory.create(
parent_location=unit.location,
category='mentoring',
data=StringResponseXMLFactory().build_xml(answer='foo'),
metadata={'rerandomize': 'always'},
display_name=u"test problem same points"
)
StudentModuleFactory.create(
grade=2.25,
max_grade=4,
student=self.users[USER_COUNT-3],
course_id=self.course.id,
module_state_key=item.location,
state=json.dumps({'attempts': self.attempts}),
module_type='mentoring'
)
StudentModule.objects.filter(student=self.users[USER_COUNT-3]).update(created=datetime.now()-timedelta(days=1))
test_uri = '{}/{}/metrics/proficiency/leaders/'.format(self.base_courses_uri, self.test_course_id) test_uri = '{}/{}/metrics/proficiency/leaders/'.format(self.base_courses_uri, self.test_course_id)
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['leaders']), 3) self.assertEqual(len(response.data['leaders']), 3)
self.assertEqual(response.data['course_avg'], 3.4) self.assertEqual(response.data['leaders'][0]['username'], 'testuser2')
self.assertEqual(response.data['course_avg'], 3.9)
test_uri = '{}/{}/metrics/proficiency/leaders/?{}'.format(self.base_courses_uri, self.test_course_id, 'count=4') test_uri = '{}/{}/metrics/proficiency/leaders/?{}'.format(self.base_courses_uri, self.test_course_id, 'count=4')
response = self.do_get(test_uri) response = self.do_get(test_uri)
...@@ -1569,9 +1597,9 @@ class CoursesApiTests(TestCase): ...@@ -1569,9 +1597,9 @@ class CoursesApiTests(TestCase):
response = self.do_get(user_filter_uri) response = self.do_get(user_filter_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['leaders']), 3) self.assertEqual(len(response.data['leaders']), 3)
self.assertEqual(response.data['course_avg'], 3.4) self.assertEqual(response.data['course_avg'], 3.9)
self.assertEqual(response.data['position'], 2) self.assertEqual(response.data['position'], 1)
self.assertEqual(response.data['points'], 5) self.assertEqual(response.data['points'], 7)
# Filter by user who has never accessed a course module # Filter by user who has never accessed a course module
test_user = UserFactory.create(username="testusernocoursemod") test_user = UserFactory.create(username="testusernocoursemod")
...@@ -1580,8 +1608,8 @@ class CoursesApiTests(TestCase): ...@@ -1580,8 +1608,8 @@ class CoursesApiTests(TestCase):
response = self.do_get(user_filter_uri) response = self.do_get(user_filter_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['leaders']), 3) self.assertEqual(len(response.data['leaders']), 3)
self.assertEqual(response.data['course_avg'], 3.4) self.assertEqual(response.data['course_avg'], 3.9)
self.assertEqual(response.data['position'], 4) self.assertEqual(response.data['position'], 5)
self.assertEqual(response.data['points'], 0) self.assertEqual(response.data['points'], 0)
# test with bogus course # test with bogus course
...@@ -1598,20 +1626,7 @@ class CoursesApiTests(TestCase): ...@@ -1598,20 +1626,7 @@ class CoursesApiTests(TestCase):
def test_courses_completions_leaders_list_get(self): def test_courses_completions_leaders_list_get(self):
completion_uri = '{}/{}/completions/'.format(self.base_courses_uri, unicode(self.course.id)) completion_uri = '{}/{}/completions/'.format(self.base_courses_uri, unicode(self.course.id))
users = [] # Make last user as observer to make sure that data is being filtered out
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'])
# make the last user an observer to make sure that data is being filtered out
allow_access(self.course, self.users[USER_COUNT-1], 'observer') allow_access(self.course, self.users[USER_COUNT-1], 'observer')
for i in xrange(1, 26): for i in xrange(1, 26):
...@@ -1623,13 +1638,13 @@ class CoursesApiTests(TestCase): ...@@ -1623,13 +1638,13 @@ class CoursesApiTests(TestCase):
display_name=local_content_name display_name=local_content_name
) )
if i < 3: if i < 3:
user_id = users[0] user_id = self.users[0].id
elif i < 8: elif i < 10:
user_id = users[1] user_id = self.users[1].id
elif i < 16: elif i < 17:
user_id = users[2] user_id = self.users[2].id
else: else:
user_id = users[3] user_id = self.users[3].id
content_id = unicode(local_content.scope_ids.usage_id) content_id = unicode(local_content.scope_ids.usage_id)
completions_data = {'content_id': content_id, 'user_id': user_id} completions_data = {'content_id': content_id, 'user_id': user_id}
...@@ -1650,12 +1665,12 @@ class CoursesApiTests(TestCase): ...@@ -1650,12 +1665,12 @@ class CoursesApiTests(TestCase):
# without count filter and user_id # without count filter and user_id
test_uri = '{}/{}/metrics/completions/leaders/?user_id={}'.format(self.base_courses_uri, self.test_course_id, test_uri = '{}/{}/metrics/completions/leaders/?user_id={}'.format(self.base_courses_uri, self.test_course_id,
users[3]) self.users[1].id)
response = self.do_get(test_uri) response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['leaders']), 3) self.assertEqual(len(response.data['leaders']), 3)
self.assertEqual(response.data['position'], 1) self.assertEqual(response.data['position'], 2)
self.assertEqual(response.data['completions'], 38) self.assertEqual(response.data['completions'], 26)
# test with bogus course # test with bogus course
test_uri = '{}/{}/metrics/completions/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id) test_uri = '{}/{}/metrics/completions/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id)
......
...@@ -5,11 +5,12 @@ import logging ...@@ -5,11 +5,12 @@ import logging
import itertools import itertools
from lxml import etree from lxml import etree
from StringIO import StringIO from StringIO import StringIO
from datetime import datetime
from django.conf import settings from django.conf import settings
from django.contrib.auth.models import Group, User from django.contrib.auth.models import Group, User
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Avg, Sum, Count from django.db.models import Avg, Sum, Count, Max
from django.http import Http404 from django.http import Http404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.db.models import Q from django.db.models import Q
...@@ -1548,12 +1549,19 @@ class CoursesLeadersList(SecureListAPIView): ...@@ -1548,12 +1549,19 @@ class CoursesLeadersList(SecureListAPIView):
queryset = queryset.filter(module_state_key=content_key) queryset = queryset.filter(module_state_key=content_key)
if user_id: if user_id:
user_points = StudentModule.objects.filter(course_id__exact=course_key, user_queryset = StudentModule.objects.filter(course_id__exact=course_key, grade__isnull=False,
student__id=user_id).aggregate(points=Sum('grade')) max_grade__isnull=False,
max_grade__gt=0,
student__id=user_id)
user_points = user_queryset.aggregate(points=Sum('grade'))
user_points = user_points['points'] or 0 user_points = user_points['points'] or 0
user_points = round(user_points, 2) user_points = round(user_points, 2)
users_above = queryset.values('student__id').annotate(points=Sum('grade')).\ user_time_scored = user_queryset.aggregate(time_scored=Max('created'))
filter(points__gt=user_points).exclude(student__id=user_id).count() # excluding user to overcome user_time_scored = user_time_scored['time_scored'] or datetime.now()
users_above = queryset.values('student__id').annotate(points=Sum('grade'))\
.annotate(time_scored=Max('created')).\
filter(Q(points__gt=user_points) | Q(points__gte=user_points, time_scored__lt=user_time_scored))\
.exclude(student__id=user_id).count() # excluding user to overcome
# float comparison bug # float comparison bug
data['position'] = users_above + 1 data['position'] = users_above + 1
data['points'] = int(round(user_points)) data['points'] = int(round(user_points))
...@@ -1567,7 +1575,8 @@ class CoursesLeadersList(SecureListAPIView): ...@@ -1567,7 +1575,8 @@ class CoursesLeadersList(SecureListAPIView):
queryset = queryset.filter(student__is_active=True).values('student__id', 'student__username', queryset = queryset.filter(student__is_active=True).values('student__id', 'student__username',
'student__profile__title', 'student__profile__title',
'student__profile__avatar_url')\ 'student__profile__avatar_url')\
.annotate(points_scored=Sum('grade')).order_by('-points_scored')[:count] .annotate(points_scored=Sum('grade')).annotate(time_scored=Max('created'))\
.order_by('-points_scored', 'time_scored')[:count]
serializer = CourseLeadersSerializer(queryset, many=True) serializer = CourseLeadersSerializer(queryset, many=True)
data['leaders'] = serializer.data # pylint: disable=E1101 data['leaders'] = serializer.data # pylint: disable=E1101
...@@ -1608,9 +1617,14 @@ class CoursesCompletionsLeadersList(SecureAPIView): ...@@ -1608,9 +1617,14 @@ class CoursesCompletionsLeadersList(SecureAPIView):
.exclude(user__in=exclude_users) .exclude(user__in=exclude_users)
total_actual_completions = queryset.filter(user__is_active=True).count() total_actual_completions = queryset.filter(user__is_active=True).count()
if user_id: if user_id:
user_completions = queryset.filter(user__id=user_id).count() user_queryset = CourseModuleCompletion.objects.filter(course_id=course_key, user__id=user_id)
user_completions = user_queryset.count()
user_time_completed = user_queryset.aggregate(time_completed=Max('created'))
user_time_completed = user_time_completed['time_completed'] or datetime.now()
completions_above_user = queryset.filter(user__is_active=True).values('user__id')\ completions_above_user = queryset.filter(user__is_active=True).values('user__id')\
.annotate(completions=Count('content_id')).filter(completions__gt=user_completions).count() .annotate(completions=Count('content_id')).annotate(time_completed=Max('created'))\
.filter(Q(completions__gt=user_completions) | Q(completions=user_completions,
time_completed__lt=user_time_completed)).count()
data['position'] = completions_above_user + 1 data['position'] = completions_above_user + 1
completion_percentage = 0 completion_percentage = 0
if total_possible_completions > 0: if total_possible_completions > 0:
...@@ -1625,7 +1639,8 @@ class CoursesCompletionsLeadersList(SecureAPIView): ...@@ -1625,7 +1639,8 @@ class CoursesCompletionsLeadersList(SecureAPIView):
queryset = queryset.filter(user__is_active=True).values('user__id', 'user__username', 'user__profile__title', queryset = queryset.filter(user__is_active=True).values('user__id', 'user__username', 'user__profile__title',
'user__profile__avatar_url')\ 'user__profile__avatar_url')\
.annotate(completions=Count('content_id')).order_by('-completions')[:count] .annotate(completions=Count('content_id')).annotate(time_completed=Max('created'))\
.order_by('-completions', 'time_completed')[:count]
serializer = CourseCompletionsLeadersSerializer(queryset, many=True, serializer = CourseCompletionsLeadersSerializer(queryset, many=True,
context={'total_completions': total_possible_completions}) context={'total_completions': total_possible_completions})
data['leaders'] = serializer.data # pylint: disable=E1101 data['leaders'] = serializer.data # pylint: disable=E1101
......
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