add number of total users and number of filtered users to the gradebook API
This commit is contained in:
@@ -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'])
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user