Commit d201c06f by Matt Drayer Committed by Jonathan Piacenti

mattdrayer/api-course-avg-release: Account for ungraded students

parent fb28f68f
...@@ -1573,7 +1573,7 @@ class CoursesApiTests(TestCase): ...@@ -1573,7 +1573,7 @@ class CoursesApiTests(TestCase):
parent_location=self.chapter.location, parent_location=self.chapter.location,
category='mentoring', category='mentoring',
data=StringResponseXMLFactory().build_xml(answer='foo'), data=StringResponseXMLFactory().build_xml(answer='foo'),
display_name=u"test problem smae points", display_name=u"test problem same points",
metadata={'rerandomize': 'always', 'graded': True, 'format': "Midterm Exam"} metadata={'rerandomize': 'always', 'graded': True, 'format': "Midterm Exam"}
) )
...@@ -1591,7 +1591,6 @@ class CoursesApiTests(TestCase): ...@@ -1591,7 +1591,6 @@ 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)
StudentGradebook.objects.filter(user=user).update(created=timezone.now() - timedelta(days=1))
test_uri = '{}/{}/metrics/grades/leaders/'.format(self.base_courses_uri, self.test_course_id) test_uri = '{}/{}/metrics/grades/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)
...@@ -1615,11 +1614,16 @@ class CoursesApiTests(TestCase): ...@@ -1615,11 +1614,16 @@ class CoursesApiTests(TestCase):
# 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")
CourseEnrollmentFactory.create(user=test_user, course_id=self.course.id)
user_filter_uri = '{}?user_id={}'.format(test_uri, test_user.id) user_filter_uri = '{}?user_id={}'.format(test_uri, test_user.id)
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(response.data['user_grade'], 0) self.assertEqual(response.data['user_grade'], 0)
self.assertEqual(response.data['user_position'], 6) self.assertEqual(response.data['user_position'], 6)
# Also, with this new user now added the course average should be different
self.assertNotEqual(response.data['course_avg'], expected_course_average)
rounded_avg = float("{0:.2f}".format(response.data['course_avg']))
self.assertEqual(rounded_avg, 0.33)
# test with bogus course # test with bogus course
bogus_test_uri = '{}/{}/metrics/grades/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id) bogus_test_uri = '{}/{}/metrics/grades/leaders/'.format(self.base_courses_uri, self.test_bogus_course_id)
......
...@@ -1433,11 +1433,7 @@ class CoursesMetricsGradesList(SecureListAPIView): ...@@ -1433,11 +1433,7 @@ class CoursesMetricsGradesList(SecureListAPIView):
queryset_grade_min = queryset.aggregate(Min('grade')) queryset_grade_min = queryset.aggregate(Min('grade'))
queryset_grade_count = queryset.aggregate(Count('grade')) queryset_grade_count = queryset.aggregate(Count('grade'))
course_queryset = StudentGradebook.objects.filter(course_id__exact=course_key).exclude(user__in=exclude_users) course_metrics = StudentGradebook.generate_leaderboard(course_key, exclude_users=exclude_users)
course_queryset_grade_avg = course_queryset.aggregate(Avg('grade'))
course_queryset_grade_max = course_queryset.aggregate(Max('grade'))
course_queryset_grade_min = course_queryset.aggregate(Min('grade'))
course_queryset_grade_count = course_queryset.aggregate(Count('grade'))
response_data = {} response_data = {}
base_uri = generate_base_uri(request) base_uri = generate_base_uri(request)
...@@ -1448,10 +1444,10 @@ class CoursesMetricsGradesList(SecureListAPIView): ...@@ -1448,10 +1444,10 @@ class CoursesMetricsGradesList(SecureListAPIView):
response_data['grade_minimum'] = queryset_grade_min['grade__min'] response_data['grade_minimum'] = queryset_grade_min['grade__min']
response_data['grade_count'] = queryset_grade_count['grade__count'] response_data['grade_count'] = queryset_grade_count['grade__count']
response_data['course_grade_average'] = course_queryset_grade_avg['grade__avg'] response_data['course_grade_average'] = course_metrics['course_avg']
response_data['course_grade_maximum'] = course_queryset_grade_max['grade__max'] response_data['course_grade_maximum'] = course_metrics['course_max']
response_data['course_grade_minimum'] = course_queryset_grade_min['grade__min'] response_data['course_grade_minimum'] = course_metrics['course_min']
response_data['course_grade_count'] = course_queryset_grade_count['grade__count'] response_data['course_grade_count'] = course_metrics['course_count']
response_data['grades'] = [] response_data['grades'] = []
for row in queryset: for row in queryset:
......
...@@ -5,11 +5,12 @@ from django.utils import timezone ...@@ -5,11 +5,12 @@ from django.utils import timezone
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.db.models import Avg from django.db.models import Avg, Max, Min, Count
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.dispatch import receiver from django.dispatch import receiver
from model_utils.models import TimeStampedModel from model_utils.models import TimeStampedModel
from student.models import CourseEnrollment
from xmodule_django.models import CourseKeyField from xmodule_django.models import CourseKeyField
...@@ -48,19 +49,34 @@ class StudentGradebook(TimeStampedModel): ...@@ -48,19 +49,34 @@ class StudentGradebook(TimeStampedModel):
'user_position': 4, 'user_position': 4,
'user_grade': 0.89 'user_grade': 0.89
} }
If there is a discrepancy between the number of gradebook entries and the overall number of enrolled
users (excluding any users who should be excluded), then we modify the course average to account for
those users who currently lack gradebook entries. We assume zero grades for these users because they
have not yet submitted a response to a scored assessment which means no grade has been calculated.
""" """
data = {} data = {}
total_user_count = CourseEnrollment.users_enrolled_in(course_key).count()
queryset = StudentGradebook.objects.select_related('user')\ queryset = StudentGradebook.objects.select_related('user')\
.filter(course_id__exact=course_key, user__is_active=True) .filter(course_id__exact=course_key, user__is_active=True)
gradebook_user_count = len(queryset)
if exclude_users: if exclude_users:
total_user_count = total_user_count - len(exclude_users)
queryset = queryset.exclude(user__in=exclude_users) queryset = queryset.exclude(user__in=exclude_users)
gradebook_user_count = len(queryset)
# print StudentGradebook.objects.select_related('user')\ # Calculate the class average
# .filter(course_id__exact=course_key, user__is_active=True).query course_avg = queryset.aggregate(Avg('grade'))['grade__avg']
# assert 0 if gradebook_user_count < total_user_count:
# Take into account any ungraded students (assumes zeros for grades...)
course_avg = course_avg / total_user_count * gradebook_user_count
data['course_avg'] = course_avg
data['course_max'] = queryset.aggregate(Max('grade'))['grade__max']
data['course_min'] = queryset.aggregate(Min('grade'))['grade__min']
data['course_count'] = queryset.aggregate(Count('grade'))['grade__count']
# Construct the leaderboard as a queryset # Construct the leaderboard as a queryset
data['course_avg'] = queryset.aggregate(Avg('grade'))['grade__avg']
data['queryset'] = queryset.values( data['queryset'] = queryset.values(
'user__id', 'user__id',
'user__username', 'user__username',
......
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