add number of total users and number of filtered users to the gradebook API

This commit is contained in:
Michael Roytman
2019-07-18 19:47:12 -04:00
parent cffcce7df0
commit 77dc0fa9f0
4 changed files with 103 additions and 31 deletions

View File

@@ -32,7 +32,6 @@ from lms.djangoapps.grades.grade_utils import are_grades_frozen
from lms.djangoapps.grades.models import (
PersistentSubsectionGrade,
PersistentSubsectionGradeOverride,
PersistentSubsectionGradeOverrideHistory
)
from lms.djangoapps.grades.rest_api.serializers import (
StudentGradebookEntrySerializer,
@@ -406,10 +405,12 @@ class GradebookView(GradeViewMixin, PaginatedAPIView):
}
**Paginated GET response**
When requesting gradebook entries for all users, the response is paginated and contains the following values:
* count: The total number of user gradebook entries for this course.
* next: The URL containing the next page of data.
* previous: The URL containing the previous page of data.
* results: A list of user gradebook entries, structured as above.
* total_users_count: The total number of active users in the course.
* filtered_users_count: The total number of active users that match
the filter associated with the provided query parameters.
Note: It's important that `GradeViewMixin` is the first inherited class here, so that
self.api_error returns error responses as expected.
@@ -553,6 +554,8 @@ class GradebookView(GradeViewMixin, PaginatedAPIView):
related_models = ['user']
users = self._paginate_users(course_key, q_objects, related_models)
users_counts = self._get_users_counts(course_key, q_objects)
with bulk_gradebook_view_context(course_key, users):
for user, course_grade, exc in CourseGradeFactory().iter(
users, course_key=course_key, collected_block_structure=course_data.collected_structure
@@ -562,7 +565,43 @@ class GradebookView(GradeViewMixin, PaginatedAPIView):
entries.append(entry)
serializer = StudentGradebookEntrySerializer(entries, many=True)
return self.get_paginated_response(serializer.data)
return self.get_paginated_response(serializer.data, **users_counts)
def _get_users_counts(self, course_key, course_enrollment_filters):
"""
Return a dictionary containing data about the total number of users and total number
of users matching a given filter in a given course.
Arguments:
course_key: the opaque key for the course
course_enrollment_filters: a list of Q objects representing filters to be applied to CourseEnrollments
Returns:
dict:
total_users_count: the number of total active users in the course
filtered_users_count: the number of active users in the course that match
the given course_enrollment_filters
"""
filter_args = [
Q(course_id=course_key) & Q(is_active=True)
]
total_users_count = CourseEnrollment.objects.filter(*filter_args).count()
filter_args.extend(course_enrollment_filters or [])
# if course_enrollment_filters is empty, then the number of filtered users will equal the total number of users
filtered_users_count = (
total_users_count
if not course_enrollment_filters
else CourseEnrollment.objects.filter(*filter_args).count()
)
return {
'total_users_count': total_users_count,
'filtered_users_count': filtered_users_count,
}
GradebookUpdateResponseItem = namedtuple('GradebookUpdateResponseItem', ['user_id', 'usage_id', 'success', 'reason'])

View File

@@ -696,11 +696,12 @@ class GradebookViewTest(GradebookViewTestBase):
self.assertEqual(expected_results, actual_data)
@ddt.data(
'login_staff',
'login_course_admin',
'login_course_staff',
['login_staff', 4],
['login_course_admin', 5],
['login_course_staff', 5]
)
def test_gradebook_data_filter_username_contains(self, login_method):
@ddt.unpack
def test_gradebook_data_filter_username_contains(self, login_method, num_enrollments):
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade:
mock_grade.return_value = self.mock_course_grade(
self.program_student, passed=True, percent=0.75
@@ -738,12 +739,16 @@ class GradebookViewTest(GradebookViewTestBase):
self.assertIsNone(actual_data['previous'])
self.assertEqual(expected_results, actual_data['results'])
self.assertEqual(actual_data['total_users_count'], num_enrollments)
self.assertEqual(actual_data['filtered_users_count'], 2)
@ddt.data(
'login_staff',
'login_course_admin',
'login_course_staff',
['login_staff', 4],
['login_course_admin', 5],
['login_course_staff', 5]
)
def test_gradebook_data_filter_masters_track_username_contains(self, login_method):
@ddt.unpack
def test_gradebook_data_filter_masters_track_username_contains(self, login_method, num_enrollments):
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade:
mock_grade.return_value = self.mock_course_grade(
self.program_masters_student, passed=True, percent=0.75
@@ -782,12 +787,16 @@ class GradebookViewTest(GradebookViewTestBase):
self.assertIsNone(actual_data['previous'])
self.assertEqual(expected_results, actual_data['results'])
self.assertEqual(actual_data['total_users_count'], num_enrollments)
self.assertEqual(actual_data['filtered_users_count'], 2)
@ddt.data(
'login_staff',
'login_course_admin',
'login_course_staff',
['login_staff', 4],
['login_course_admin', 5],
['login_course_staff', 5]
)
def test_gradebook_data_filter_email_contains(self, login_method):
@ddt.unpack
def test_gradebook_data_filter_email_contains(self, login_method, num_enrollments):
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade:
mock_grade.return_value = self.mock_course_grade(
self.other_student, passed=True, percent=0.85
@@ -816,12 +825,16 @@ class GradebookViewTest(GradebookViewTestBase):
self.assertIsNone(actual_data['previous'])
self.assertEqual(expected_results, actual_data['results'])
self.assertEqual(actual_data['total_users_count'], num_enrollments)
self.assertEqual(actual_data['filtered_users_count'], 1)
@ddt.data(
'login_staff',
'login_course_admin',
'login_course_staff',
['login_staff', 4],
['login_course_admin', 5],
['login_course_staff', 5]
)
def test_gradebook_data_filter_external_user_key_contains(self, login_method):
@ddt.unpack
def test_gradebook_data_filter_external_user_key_contains(self, login_method, num_enrollments):
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade:
mock_grade.return_value = self.mock_course_grade(
self.program_student, passed=True, percent=0.75
@@ -858,8 +871,9 @@ class GradebookViewTest(GradebookViewTestBase):
actual_data = dict(resp.data)
self.assertIsNone(actual_data['next'])
self.assertIsNone(actual_data['previous'])
#self.assertEqual(expected_results, actual_data['results'])
assert expected_results == actual_data['results']
self.assertEqual(expected_results, actual_data['results'])
self.assertEqual(actual_data['total_users_count'], num_enrollments)
self.assertEqual(actual_data['filtered_users_count'], 2)
@ddt.data(
'login_staff',
@@ -880,11 +894,12 @@ class GradebookViewTest(GradebookViewTestBase):
self._assert_empty_response(resp)
@ddt.data(
'login_staff',
'login_course_admin',
'login_course_staff',
['login_staff', 4],
['login_course_admin', 5],
['login_course_staff', 5]
)
def test_filter_cohort_id_and_enrollment_mode(self, login_method):
@ddt.unpack
def test_filter_cohort_id_and_enrollment_mode(self, login_method, num_enrollments):
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade:
mock_grade.return_value = self.mock_course_grade(self.student, passed=True, percent=0.85)
@@ -913,6 +928,8 @@ class GradebookViewTest(GradebookViewTestBase):
self.assertIsNone(actual_data['next'])
self.assertIsNone(actual_data['previous'])
self.assertEqual(expected_results, actual_data['results'])
self.assertEqual(actual_data['total_users_count'], num_enrollments)
self.assertEqual(actual_data['filtered_users_count'], 1)
@ddt.data(
'login_staff',
@@ -932,11 +949,12 @@ class GradebookViewTest(GradebookViewTestBase):
self._assert_empty_response(resp)
@ddt.data(
'login_staff',
'login_course_admin',
'login_course_staff',
['login_staff', 5, 3],
['login_course_admin', 6, 4],
['login_course_staff', 6, 4],
)
def test_filter_enrollment_mode(self, login_method):
@ddt.unpack
def test_filter_enrollment_mode(self, login_method, num_enrollments, num_filtered_enrollments):
with patch('lms.djangoapps.grades.course_grade_factory.CourseGradeFactory.read') as mock_grade:
mock_grade.side_effect = [
self.mock_course_grade(self.student, passed=True, percent=0.85),
@@ -959,6 +977,10 @@ class GradebookViewTest(GradebookViewTestBase):
)
self._assert_data_all_users(resp)
actual_data = dict(resp.data)
self.assertEqual(actual_data['total_users_count'], num_enrollments)
self.assertEqual(actual_data['filtered_users_count'], num_filtered_enrollments)
@ddt.data(
'login_staff',

View File

@@ -40,6 +40,17 @@ class CourseEnrollmentPagination(CursorPagination):
return self.page_size
def get_paginated_response(self, data, **kwargs): # pylint: disable=arguments-differ
"""
Return a response given serialized page data and kwargs. Each key-value pair is added to the response.
"""
resp = super(CourseEnrollmentPagination, self).get_paginated_response(data)
for (key, value) in kwargs.items():
resp.data[key] = value
return resp
class GradeViewMixin(DeveloperErrorViewMixin):
"""

View File

@@ -337,12 +337,12 @@ class PaginatedAPIView(APIView):
return None
return self.paginator.paginate_queryset(queryset, self.request, view=self)
def get_paginated_response(self, data):
def get_paginated_response(self, data, *args, **kwargs):
"""
Return a paginated style `Response` object for the given output data.
"""
assert self.paginator is not None
return self.paginator.get_paginated_response(data)
return self.paginator.get_paginated_response(data, *args, **kwargs)
def get_course_key(request, course_id=None):