def get(self, request, course_key): """ Returns a gradebook entry/entries (i.e. both course and subsection-level grade data) for all users enrolled in a course, or a single user enrolled in a course if a `username` parameter is provided. Args: request: A Django request object. course_key: The edx course opaque key of a course object. """ course = get_course_by_id(course_key, depth=None) # We fetch the entire course structure up-front, and use this when iterating # over users to determine their subsection grades. We purposely avoid fetching # the user-specific course structure for each user, because that is very expensive. course_data = CourseData(user=None, course=course) graded_subsections = list(grades_context.graded_subsections_for_course(course_data.collected_structure)) if request.GET.get('username'): with self._get_user_or_raise(request, course_key) as grade_user: course_grade = CourseGradeFactory().read(grade_user, course) entry = self._gradebook_entry(grade_user, course, graded_subsections, course_grade) serializer = StudentGradebookEntrySerializer(entry) return Response(serializer.data) else: q_objects = [] if request.GET.get('user_contains'): search_term = request.GET.get('user_contains') q_objects.append( Q(user__username__icontains=search_term) | Q(programcourseenrollment__program_enrollment__external_user_key__icontains=search_term) | Q(user__email__icontains=search_term) ) if request.GET.get('username_contains'): q_objects.append(Q(user__username__icontains=request.GET.get('username_contains'))) if request.GET.get('cohort_id'): cohort = cohorts.get_cohort_by_id(course_key, request.GET.get('cohort_id')) if cohort: q_objects.append(Q(user__in=cohort.users.all())) else: q_objects.append(Q(user__in=[])) if request.GET.get('enrollment_mode'): q_objects.append(Q(mode=request.GET.get('enrollment_mode'))) entries = [] related_models = ['user'] users = self._paginate_users(course_key, q_objects, related_models) 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 ): if not exc: entry = self._gradebook_entry(user, course, graded_subsections, course_grade) entries.append(entry) serializer = StudentGradebookEntrySerializer(entries, many=True) return self.get_paginated_response(serializer.data)
def get(self, request, course_id): """ Returns a gradebook entry/entries (i.e. both course and subsection-level grade data) for all users enrolled in a course, or a single user enrolled in a course if a `username` parameter is provided. Args: request: A Django request object. course_id: A string representation of a CourseKey object. """ course_key = get_course_key(request, course_id) course = get_course_with_access(request.user, 'staff', course_key, depth=None) # We fetch the entire course structure up-front, and use this when iterating # over users to determine their subsection grades. We purposely avoid fetching # the user-specific course structure for each user, because that is very expensive. course_data = CourseData(user=None, course=course) graded_subsections = list(graded_subsections_for_course(course_data.collected_structure)) if request.GET.get('username'): with self._get_user_or_raise(request, course_key) as grade_user: course_grade = CourseGradeFactory().read(grade_user, course) entry = self._gradebook_entry(grade_user, course, graded_subsections, course_grade) serializer = StudentGradebookEntrySerializer(entry) return Response(serializer.data) else: filter_kwargs = {} related_models = [] if request.GET.get('username_contains'): filter_kwargs['user__username__icontains'] = request.GET.get('username_contains') related_models.append('user') if request.GET.get('cohort_id'): cohort = cohorts.get_cohort_by_id(course_key, request.GET.get('cohort_id')) if cohort: filter_kwargs['user__in'] = cohort.users.all() else: filter_kwargs['user__in'] = [] if request.GET.get('enrollment_mode'): filter_kwargs['mode'] = request.GET.get('enrollment_mode') entries = [] users = self._paginate_users(course_key, filter_kwargs, related_models) 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 ): if not exc: entries.append(self._gradebook_entry(user, course, graded_subsections, course_grade)) serializer = StudentGradebookEntrySerializer(entries, many=True) return self.get_paginated_response(serializer.data)
def mock_course_grade(self, user, **kwargs): """ Helper function to return a mock CourseGrade object. """ course_data = CourseData(user, course=self.course) course_grade = CourseGrade(user=user, course_data=course_data, **kwargs) course_grade.chapter_grades = OrderedDict([ (self.chapter_1.location, { 'sections': [ self.mock_subsection_grade( self.subsections[self.chapter_1.location][0], earned_all=1.0, possible_all=2.0, earned_graded=1.0, possible_graded=2.0, ), self.mock_subsection_grade( self.subsections[self.chapter_1.location][1], earned_all=1.0, possible_all=2.0, earned_graded=1.0, possible_graded=2.0, ), ], 'display_name': 'Chapter 1', }), (self.chapter_2.location, { 'sections': [ self.mock_subsection_grade( self.subsections[self.chapter_2.location][0], earned_all=1.0, possible_all=2.0, earned_graded=1.0, possible_graded=2.0, ), self.mock_subsection_grade( self.subsections[self.chapter_2.location][1], earned_all=1.0, possible_all=2.0, earned_graded=1.0, possible_graded=2.0, ), ], 'display_name': 'Chapter 2', }), ]) return course_grade
def _create_subsection_grade(self, user_id, course_key, usage_key): """ Given a user_id, course_key, and subsection usage_key, creates a new ``PersistentSubsectionGrade``. """ course = get_course(course_key, depth=None) subsection = course.get_child(usage_key) if not subsection: raise Exception('Subsection with given usage_key does not exist.') user = USER_MODEL.objects.get(id=user_id) course_data = CourseData(user, course=course) subsection_grade = CreateSubsectionGrade(subsection, course_data.structure, {}, {}) return subsection_grade.update_or_create_model( user, force_update_subsections=True)
def _create_subsection_grade(self, user, course, subsection): course_data = CourseData(user, course=course) subsection_grade = CreateSubsectionGrade(subsection, course_data.structure, {}, {}) return subsection_grade.update_or_create_model( user, force_update_subsections=True)
def get(self, request, course_key): """ Returns a gradebook entry/entries (i.e. both course and subsection-level grade data) for all users enrolled in a course, or a single user enrolled in a course if a `username` parameter is provided. Args: request: A Django request object. course_key: The edx course opaque key of a course object. """ course = get_course_by_id(course_key, depth=None) # We fetch the entire course structure up-front, and use this when iterating # over users to determine their subsection grades. We purposely avoid fetching # the user-specific course structure for each user, because that is very expensive. course_data = CourseData(user=None, course=course) graded_subsections = list( grades_context.graded_subsections_for_course( course_data.collected_structure)) if request.GET.get('username'): with self._get_user_or_raise(request, course_key) as grade_user: course_grade = CourseGradeFactory().read( grade_user, course, collected_block_structure=course_data.collected_structure) entry = self._gradebook_entry(grade_user, course, graded_subsections, course_grade) serializer = StudentGradebookEntrySerializer(entry) return Response(serializer.data) else: q_objects = [] annotations = {} if request.GET.get('user_contains'): search_term = request.GET.get('user_contains') q_objects.append( Q(user__username__icontains=search_term) | Q(programcourseenrollment__program_enrollment__external_user_key__icontains =search_term) | Q(user__email__icontains=search_term)) if request.GET.get('username_contains'): q_objects.append( Q(user__username__icontains=request.GET.get( 'username_contains'))) if request.GET.get('cohort_id'): cohort = cohorts.get_cohort_by_id(course_key, request.GET.get('cohort_id')) if cohort: q_objects.append(Q(user__in=cohort.users.all())) else: q_objects.append(Q(user__in=[])) if request.GET.get('enrollment_mode'): q_objects.append(Q(mode=request.GET.get('enrollment_mode'))) if request.GET.get('assignment') and ( request.GET.get('assignment_grade_max') or request.GET.get('assignment_grade_min')): subqueryset = PersistentSubsectionGrade.objects.annotate( effective_grade_percentage=Case( When(override__isnull=False, then=(F('override__earned_graded_override') / F('override__possible_graded_override')) * 100), default=(F('earned_graded') / F('possible_graded')) * 100)) grade_conditions = { 'effective_grade_percentage__range': (request.GET.get('assignment_grade_min', 0), request.GET.get('assignment_grade_max', 100)) } annotations['selected_assignment_grade_in_range'] = Exists( subqueryset.filter(course_id=OuterRef('course'), user_id=OuterRef('user'), usage_key=UsageKey.from_string( request.GET.get('assignment')), **grade_conditions)) q_objects.append(Q(selected_assignment_grade_in_range=True)) if request.GET.get('course_grade_min') or request.GET.get( 'course_grade_max'): grade_conditions = {} q_object = Q() course_grade_min = request.GET.get('course_grade_min') if course_grade_min: course_grade_min = float( request.GET.get('course_grade_min')) / 100 grade_conditions['percent_grade__gte'] = course_grade_min if request.GET.get('course_grade_max'): course_grade_max = float( request.GET.get('course_grade_max')) / 100 grade_conditions['percent_grade__lte'] = course_grade_max if not course_grade_min or course_grade_min == 0: subquery_grade_absent = ~Exists( PersistentCourseGrade.objects.filter( course_id=OuterRef('course'), user_id=OuterRef('user_id'), )) annotations['course_grade_absent'] = subquery_grade_absent q_object |= Q(course_grade_absent=True) subquery_grade_in_range = Exists( PersistentCourseGrade.objects.filter( course_id=OuterRef('course'), user_id=OuterRef('user_id'), **grade_conditions)) annotations['course_grade_in_range'] = subquery_grade_in_range q_object |= Q(course_grade_in_range=True) q_objects.append(q_object) entries = [] related_models = ['user'] users = self._paginate_users(course_key, q_objects, related_models, annotations=annotations) users_counts = self._get_users_counts(course_key, q_objects, annotations=annotations) 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): if not exc: entry = self._gradebook_entry(user, course, graded_subsections, course_grade) entries.append(entry) serializer = StudentGradebookEntrySerializer(entries, many=True) return self.get_paginated_response(serializer.data, **users_counts)
def setUpClass(cls): super(GradebookViewTestBase, cls).setUpClass() cls.namespaced_url = 'grades_api:v1:course_gradebook' cls.waffle_flag = waffle_flags()[WRITABLE_GRADEBOOK] cls.course = CourseFactory.create(display_name='test-course', run='run-1') cls.course_key = cls.course.id cls.course_overview = CourseOverviewFactory.create(id=cls.course.id) cls.chapter_1 = ItemFactory.create( category='chapter', parent_location=cls.course.location, display_name="Chapter 1", ) cls.chapter_2 = ItemFactory.create( category='chapter', parent_location=cls.course.location, display_name="Chapter 2", ) cls.subsections = { cls.chapter_1.location: [ ItemFactory.create( category='sequential', parent_location=cls.chapter_1.location, due=datetime(2017, 12, 18, 11, 30, 00), display_name='HW 1', format='Homework', graded=True, ), ItemFactory.create( category='sequential', parent_location=cls.chapter_1.location, due=datetime(2017, 12, 18, 11, 30, 00), display_name='Lab 1', format='Lab', graded=True, ), ], cls.chapter_2.location: [ ItemFactory.create( category='sequential', parent_location=cls.chapter_2.location, due=datetime(2017, 12, 18, 11, 30, 00), display_name='HW 2', format='Homework', graded=True, ), ItemFactory.create( category='sequential', parent_location=cls.chapter_2.location, due=datetime(2017, 12, 18, 11, 30, 00), display_name='Lab 2', format='Lab', graded=True, ), ], } cls.course_data = CourseData(None, course=cls.course) # we have to force the collection of course data from the block_structure API # so that CourseGrade.course_data objects can later have a non-null effective_structure _ = cls.course_data.collected_structure