Commit c04fcc7b by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api: Fixed NRE corner case for grades leaderboard

parent 6cd45965
...@@ -187,6 +187,11 @@ class CoursesApiTests(TestCase): ...@@ -187,6 +187,11 @@ class CoursesApiTests(TestCase):
display_name=u"test unit 2", display_name=u"test unit 2",
) )
self.empty_course = CourseFactory.create(
start=datetime(2014, 6, 16, 14, 30),
end=datetime(2015, 1, 16),
org="MTD"
)
self.users = [UserFactory.create(username="testuser" + str(__), profile='test') for __ in xrange(USER_COUNT)] self.users = [UserFactory.create(username="testuser" + str(__), profile='test') for __ in xrange(USER_COUNT)]
...@@ -1630,6 +1635,13 @@ class CoursesApiTests(TestCase): ...@@ -1630,6 +1635,13 @@ class CoursesApiTests(TestCase):
response = self.do_get(bogus_test_uri) response = self.do_get(bogus_test_uri)
self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
def test_courses_metrics_grades_leaders_list_get_empty_course(self):
test_uri = '{}/{}/metrics/grades/leaders/'.format(self.base_courses_uri, unicode(self.empty_course.id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['course_avg'], 0)
self.assertEqual(len(response.data['leaders']), 0)
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))
# Make last user as observer to make sure that data is being filtered out # Make last user as observer to make sure that data is being filtered out
...@@ -1722,6 +1734,14 @@ class CoursesApiTests(TestCase): ...@@ -1722,6 +1734,14 @@ class CoursesApiTests(TestCase):
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertEqual(len(response.data['grades']), user_index) self.assertEqual(len(response.data['grades']), user_index)
def test_courses_metrics_grades_list_get_empty_course(self):
# Retrieve the list of grades for this course
# All the course/item/user scaffolding was handled in Setup
test_uri = '{}/{}/metrics/grades'.format(self.base_courses_uri, unicode(self.empty_course.id))
response = self.do_get(test_uri)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.data['grade_count'], 0)
self.assertEqual(response.data['course_grade_maximum'], 0)
def test_courses_grades_list_get_invalid_course(self): def test_courses_grades_list_get_invalid_course(self):
# Retrieve the list of grades for this course # Retrieve the list of grades for this course
......
...@@ -56,52 +56,64 @@ class StudentGradebook(TimeStampedModel): ...@@ -56,52 +56,64 @@ class StudentGradebook(TimeStampedModel):
have not yet submitted a response to a scored assessment which means no grade has been calculated. have not yet submitted a response to a scored assessment which means no grade has been calculated.
""" """
data = {} data = {}
data['course_avg'] = 0
data['course_max'] = 0
data['course_min'] = 0
data['course_count'] = 0
data['queryset'] = []
total_user_count = CourseEnrollment.users_enrolled_in(course_key).count() total_user_count = CourseEnrollment.users_enrolled_in(course_key).count()
queryset = StudentGradebook.objects.select_related('user')\ if total_user_count:
.filter(course_id__exact=course_key, user__is_active=True)
gradebook_user_count = len(queryset)
if exclude_users: # Generate the base data set we're going to work with
total_user_count = total_user_count - len(exclude_users) queryset = StudentGradebook.objects.select_related('user')\
queryset = queryset.exclude(user__in=exclude_users) .filter(course_id__exact=course_key, user__is_active=True)
gradebook_user_count = len(queryset) gradebook_user_count = len(queryset)
# Calculate the class average # Remove any users who should not be considered for the statistics
course_avg = queryset.aggregate(Avg('grade'))['grade__avg'] if exclude_users:
if gradebook_user_count < total_user_count: total_user_count = total_user_count - len(exclude_users)
# Take into account any ungraded students (assumes zeros for grades...) queryset = queryset.exclude(user__in=exclude_users)
course_avg = course_avg / total_user_count * gradebook_user_count gradebook_user_count = len(queryset)
data['course_avg'] = course_avg
data['course_max'] = queryset.aggregate(Max('grade'))['grade__max'] # Calculate the class average
data['course_min'] = queryset.aggregate(Min('grade'))['grade__min'] course_avg = queryset.aggregate(Avg('grade'))['grade__avg']
data['course_count'] = queryset.aggregate(Count('grade'))['grade__count'] if gradebook_user_count < total_user_count:
# Take into account any ungraded students (assumes zeros for grades...)
# Construct the leaderboard as a queryset course_avg = course_avg / total_user_count * gradebook_user_count
data['queryset'] = queryset.values(
'user__id', # Fill up the response container
'user__username', data['course_avg'] = course_avg
'user__profile__title', data['course_max'] = queryset.aggregate(Max('grade'))['grade__max']
'user__profile__avatar_url', data['course_min'] = queryset.aggregate(Min('grade'))['grade__min']
'grade', data['course_count'] = queryset.aggregate(Count('grade'))['grade__count']
'created')\
.order_by('-grade', 'created')[:count] # Construct the leaderboard as a queryset
data['queryset'] = queryset.values(
# If a user_id value was provided, we need to provide some additional user-specific data to the caller 'user__id',
if user_id: 'user__username',
user_grade = 0 'user__profile__title',
user_time_scored = timezone.now() 'user__profile__avatar_url',
try: 'grade',
user_queryset = StudentGradebook.objects.get(course_id__exact=course_key, user__id=user_id) 'created')\
except StudentGradebook.DoesNotExist: .order_by('-grade', 'created')[:count]
user_queryset = None
if user_queryset: # If a user_id value was provided, we need to provide some additional user-specific data to the caller
user_grade = user_queryset.grade if user_id:
user_time_scored = user_queryset.created user_grade = 0
users_above = queryset.filter(grade__gte=user_grade)\ user_time_scored = timezone.now()
.exclude(user__id=user_id)\ try:
.exclude(grade=user_grade, created__lt=user_time_scored) user_queryset = StudentGradebook.objects.get(course_id__exact=course_key, user__id=user_id)
data['user_position'] = len(users_above) + 1 except StudentGradebook.DoesNotExist:
data['user_grade'] = user_grade user_queryset = None
if user_queryset:
user_grade = user_queryset.grade
user_time_scored = user_queryset.created
users_above = queryset.filter(grade__gte=user_grade)\
.exclude(user__id=user_id)\
.exclude(grade=user_grade, created__lt=user_time_scored)
data['user_position'] = len(users_above) + 1
data['user_grade'] = user_grade
return data return data
......
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