def test_passing_grade_allowlist(self): with override_waffle_switch(AUTO_CERTIFICATE_GENERATION, active=True): # User who is not on the allowlist GeneratedCertificateFactory(user=self.user, course_id=self.course.id, status=CertificateStatuses.error) with mock_passing_grade(): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate_task', return_value=None) as mock_cert_task: CourseGradeFactory().update(self.user, self.course) mock_cert_task.assert_called_with(self.user, self.course.id) # User who is on the allowlist u = UserFactory.create() c = CourseFactory() course_key = c.id # pylint: disable=no-member CertificateAllowlistFactory(user=u, course_id=course_key) GeneratedCertificateFactory(user=u, course_id=course_key, status=CertificateStatuses.error) with mock_passing_grade(): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate_task', return_value=None) as mock_cert_task: CourseGradeFactory().update(u, c) mock_cert_task.assert_called_with(u, course_key)
def test_failing_grade_allowlist(self): # User who is not on the allowlist GeneratedCertificateFactory( user=self.user, course_id=self.course.id, status=CertificateStatuses.downloadable ) CourseGradeFactory().update(self.user, self.course) cert = GeneratedCertificate.certificate_for_student(self.user, self.course.id) assert cert.status == CertificateStatuses.notpassing # User who is on the allowlist u = UserFactory.create() c = CourseFactory() course_key = c.id # pylint: disable=no-member CertificateAllowlistFactory( user=u, course_id=course_key ) GeneratedCertificateFactory( user=u, course_id=course_key, status=CertificateStatuses.downloadable ) CourseGradeFactory().update(u, c) cert = GeneratedCertificate.certificate_for_student(u, course_key) assert cert.status == CertificateStatuses.downloadable
def course_grade(learner, course): """ Compatibility function to retrieve course grades Returns the course grade for the specified learner and course """ if RELEASE_LINE == 'ginkgo': return CourseGradeFactory().create(learner, course) else: # Assume Hawthorn or greater return CourseGradeFactory().read(learner, course)
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) 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, 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): if not exc: entries.append( self._gradebook_entry(user, course, course_grade)) 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 _get_single_user_grade(self, request, course_key): """ Returns a grade response for the user object corresponding to the request's 'username' parameter, or the current request.user if no 'username' was provided. Args: request (Request): django request object to check for username or request.user object course_key (CourseLocator): The course to retrieve user grades for. Returns: A serializable list of grade responses """ if 'username' in request.GET: username = request.GET.get('username') else: username = request.user.username grade_user = USER_MODEL.objects.get(username=username) if not enrollment_data.get_course_enrollment(username, str(course_key)): raise CourseEnrollment.DoesNotExist course_grade = CourseGradeFactory().read(grade_user, course_key=course_key) return Response( [self._make_grade_response(grade_user, course_key, course_grade)])
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. """ username = request.GET.get('username') course_key = get_course_key(request, course_id) course = get_course_with_access(request.user, 'staff', course_key, depth=None) if username: with self._get_user_or_raise(request, course_id) as grade_user: course_grade = CourseGradeFactory().read(grade_user, course) entry = self._gradebook_entry(grade_user, course, course_grade) serializer = StudentGradebookEntrySerializer(entry) return Response(serializer.data) else: # list gradebook data for all course enrollees entries = [] for user, course_grade, exc in self._iter_user_grades(course_key): if not exc: entries.append(self._gradebook_entry(user, course, course_grade)) serializer = StudentGradebookEntrySerializer(entries, many=True) return self.get_paginated_response(serializer.data)
def setUp(self): super(TestGradeUcursosExportView, self).setUp() self.grade_factory = CourseGradeFactory() with patch('student.models.cc.User.save'): # staff user self.client_instructor = Client() self.client_student = Client() self.client_anonymous = Client() self.user_instructor = UserFactory( username='******', password='******', email='*****@*****.**', is_staff=True) role = CourseInstructorRole(self.course.id) role.add_users(self.user_instructor) self.client_instructor.login( username='******', password='******') self.student = UserFactory( username='******', password='******', email='*****@*****.**') self.student_2 = UserFactory( username='******', password='******', email='*****@*****.**') # Enroll the student in the course CourseEnrollmentFactory( user=self.student, course_id=self.course.id, mode='honor') CourseEnrollmentFactory( user=self.student_2, course_id=self.course.id, mode='honor') self.client_student.login( username='******', password='******')
def setUp(self): super(TestCorfoGenerateXBlock, self).setUp() CorfoCodeMappingContent.objects.create(id_content=200, content='testtest') self.grade_factory = CourseGradeFactory() self.xblock = self.make_an_xblock() with patch('common.djangoapps.student.models.cc.User.save'): # staff user self.client = Client() user = UserFactory( username='******', password='******', email='*****@*****.**', is_staff=True) self.client.login(username='******', password='******') CourseEnrollmentFactory( user=user, course_id=self.course.id) # user student self.student_client = Client() self.student = UserFactory( username='******', password='******', email='*****@*****.**') CourseEnrollmentFactory( user=self.student, course_id=self.course.id) self.assertTrue( self.student_client.login( username='******', password='******'))
def send_xapi_statements(self, lrs_configuration, days): """ Send xAPI analytics data of the enterprise learners to the given LRS. Arguments: lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations of the LRS where to send xAPI learner analytics. days (int): Include course enrollment of this number of days. """ persistent_course_grades = self.get_course_completions( lrs_configuration.enterprise_customer, days) users = self.prefetch_users(persistent_course_grades) course_overviews = self.prefetch_courses(persistent_course_grades) for persistent_course_grade in persistent_course_grades: try: user = users.get(persistent_course_grade.user_id) course_overview = course_overviews.get( persistent_course_grade.course_id) course_grade = CourseGradeFactory().read( user, course_key=persistent_course_grade.course_id) send_course_completion_statement(lrs_configuration, user, course_overview, course_grade) except ClientError: LOGGER.exception( 'Client error while sending course completion to xAPI for' ' enterprise customer {enterprise_customer}.'.format( enterprise_customer=lrs_configuration. enterprise_customer.name))
def get_user_course_response_task(users, course_str, depth, callback_url): """ Get a list of users grades' for a course """ user_grades = {} grades_schema = {} course_key = CourseKey.from_string(str(course_str)) course = courses.get_course(course_key) for user in users: course_grade = CourseGradeFactory().update(user, course) if depth == "all": grades_schema = get_user_grades(user.id, course_str) else: grades_schema = "Showing course grade summary, specify depth=all in query params." user_grades[user.username] = { 'name': "{} {}".format(user.first_name, user.last_name), 'email': user.email, 'start_date': course.start, 'end_date': course.end if not None else "This course has no end date.", 'all_grades': grades_schema, "passed": course_grade.passed, "percent": course_grade.percent } #requests.post(str(callback_url), data=user_grades) return user_grades
def get_course_grades(user, course): """ Gets course grades for a given student """ grades = CourseGradeFactory().read(user, course) return grades
def _rows_for_users(self, context, users): """ Returns a list of rows for the given users for this report. """ with modulestore().bulk_operations(context.course_id): bulk_context = _CourseGradeBulkContext(context, users) success_rows, error_rows = [], [] for user, course_grade, error in CourseGradeFactory().iter( users, course=context.course, collected_block_structure=context.course_structure, course_key=context.course_id, ): if not course_grade: # An empty gradeset means we failed to grade a student. error_rows.append([user.id, user.username, text_type(error)]) else: success_rows.append( [user.id, user.email, user.username] + self._user_grades(course_grade, context) + self._user_cohort_group_names(user, context) + self._user_experiment_group_names(user, context) + self._user_team_names(user, bulk_context.teams) + self._user_verification_mode(user, context, bulk_context.enrollments) + self._user_certificate_info(user, context, course_grade, bulk_context.certs) + [_user_enrollment_status(user, context.course_id)] ) return success_rows, error_rows
def get_user_course_response(course, users, course_str, depth): """ Get a list of users grades' for a course """ user_grades = {} grades_schema = {} for user in users: course_grade = CourseGradeFactory().update(user, course) if depth == "all": grades_schema = get_user_grades(user, course, course_str, course_grade) else: grades_schema = "Showing course grade summary, specify depth=all in query params." user_grades[user.username] = { 'name': "{} {}".format(user.first_name, user.last_name), 'email': user.email, 'start_date': course.start, 'end_date': course.end if not None else "This course has no end date.", 'all_grades': grades_schema, "passed": course_grade.passed, "percent": course_grade.percent, "total_course_grade_raw": course_grade._compute_course_grade_total_raw(), "enrollment_date": str(CourseEnrollment.get_enrollment(user, course.id).created) } return user_grades
def get(self, request, course_id): """ Gets a course progress status. Args: request (Request): Django request object. course_id (string): URI element specifying the course location. Return: A JSON serialized representation of the requesting user's current grade status. """ course = self._get_course(course_id, request.user, 'load') if isinstance(course, Response): # Returns a 404 if course_id is invalid, or request.user is not enrolled in the course return course grade_user = self._get_effective_user(request, course) if isinstance(grade_user, Response): # Returns a 403 if the request.user can't access grades for the requested user, # or a 404 if the requested user does not exist. return grade_user course_grade = CourseGradeFactory().read(grade_user, course) return Response([{ 'username': grade_user.username, 'course_key': course_id, 'passed': course_grade.passed, 'percent': course_grade.percent, 'letter_grade': course_grade.letter_grade, }])
def __init__(self, site, user, enrollments=None, uuid=None): self.site = site self.user = user self.enrollments = enrollments or list( CourseEnrollment.enrollments_for_user(self.user)) self.enrollments.sort(key=lambda e: e.created, reverse=True) self.enrolled_run_modes = {} self.course_run_ids = [] for enrollment in self.enrollments: # enrollment.course_id is really a CourseKey (╯ಠ_ಠ)╯︵ ┻━┻ enrollment_id = unicode(enrollment.course_id) mode = enrollment.mode if mode == CourseMode.NO_ID_PROFESSIONAL_MODE: mode = CourseMode.PROFESSIONAL self.enrolled_run_modes[enrollment_id] = mode # We can't use dict.keys() for this because the course run ids need to be ordered self.course_run_ids.append(enrollment_id) self.entitlements = list( CourseEntitlement.unexpired_entitlements_for_user(self.user)) self.course_uuids = [ str(entitlement.course_uuid) for entitlement in self.entitlements ] self.course_grade_factory = CourseGradeFactory() if uuid: self.programs = [get_programs(self.site, uuid=uuid)] else: self.programs = attach_program_detail_url(get_programs(self.site))
def test_grade_changed(self, mock_send_grade_if_interesting): user = UserFactory() course = XModuleCourseFactory() self.assertFalse(mock_send_grade_if_interesting.called) CourseGradeFactory().update(user, course=course) self.assertTrue(mock_send_grade_if_interesting.called)
def check_state(self, user, descriptor, expected_score, expected_max_score, expected_attempts=1): """ Check that the StudentModule state contains the expected values. The student module is found for the test course, given the `username` and problem `descriptor`. Values checked include the number of attempts, the score, and the max score for a problem. """ module = self.get_student_module(user.username, descriptor) self.assertEqual(module.grade, expected_score) self.assertEqual(module.max_grade, expected_max_score) state = json.loads(module.state) attempts = state['attempts'] self.assertEqual(attempts, expected_attempts) if attempts > 0: self.assertIn('correct_map', state) self.assertIn('student_answers', state) self.assertGreater(len(state['correct_map']), 0) self.assertGreater(len(state['student_answers']), 0) # assume only one problem in the subsection and the grades # are in sync. expected_subsection_grade = expected_score course_grade = CourseGradeFactory().read(user, self.course) self.assertEquals( course_grade.graded_subsections_by_format['Homework'][self.problem_section.location].graded_total.earned, expected_subsection_grade, )
def _iter_user_grades(self, course_key, course_enrollment_filter=None, related_models=None): """ Args: course_key (CourseLocator): The course to retrieve grades for. course_enrollment_filter: Optional dictionary of keyword arguments to pass to `CourseEnrollment.filter()`. related_models: Optional list of related models to join to the CourseEnrollment table. Returns: An iterator of CourseGrade objects for users enrolled in the given course. """ filter_kwargs = { 'course_id': course_key, 'is_active': True, } filter_kwargs.update(course_enrollment_filter or {}) enrollments_in_course = CourseEnrollment.objects.filter( **filter_kwargs) if related_models: enrollments_in_course = enrollments_in_course.select_related( *related_models) paged_enrollments = self.paginate_queryset(enrollments_in_course) users = (enrollment.user for enrollment in paged_enrollments) grades = CourseGradeFactory().iter(users, course_key=course_key) for user, course_grade, exc in grades: yield user, course_grade, exc
def test_grade_changed(self, mock_send_grade_if_interesting): user = UserFactory() course = XModuleCourseFactory() assert not mock_send_grade_if_interesting.called CourseGradeFactory().update(user, course=course) assert mock_send_grade_if_interesting.called
def get_grade_book_page(request, course, course_key): """ Get student records per page along with page information i.e current page, total pages and offset information. """ # Unsanitized offset current_offset = request.GET.get('offset', 0) enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1 ).order_by('username').select_related("profile") total_students = enrolled_students.count() page = calculate_page_info(current_offset, total_students) offset = page["offset"] total_pages = page["total_pages"] if total_pages > 1: # Apply limit on queryset only if total number of students are greater then MAX_STUDENTS_PER_PAGE_GRADE_BOOK. enrolled_students = enrolled_students[offset: offset + MAX_STUDENTS_PER_PAGE_GRADE_BOOK] with modulestore().bulk_operations(course.location.course_key): student_info = [ { 'username': student.username, 'id': student.id, 'email': student.email, 'grade_summary': CourseGradeFactory().create(student, course).summary } for student in enrolled_students ] return student_info, page
def get_course_info_for_application_review(courses, user): """ Get courses info for application review page. Args: courses (list): List of courses user (User): User object Returns: dict: Contains course name and percent grade | status """ courses_info = {} for course in courses: if not CourseEnrollment.is_enrolled(user, course.id): courses_info[course.display_name] = NOT_STARTED continue grade = CourseGradeFactory().read(user, course_key=course.id) if grade.passed: courses_info[ course. display_name] = f'{convert_float_point_to_percentage(grade.percent)}%' continue courses_info[course.display_name] = IN_PROGRESS return courses_info
def test_user_has_passing_grade(self): CourseEnrollment.enroll(self.user, self.course.id) self.course._grading_policy['GRADE_CUTOFFS']['Pass'] = 0 # pylint: disable=protected-access self.update_course(self.course, self.user.id) CourseGradeFactory().update(self.user, self.course) response = self.client.get(self.url) assert response.status_code == 200 assert response.data['user_has_passing_grade'] is True
def _get_course_grade_passed(user, course_id): """ Get 'passed' (Boolean representing whether the course has been passed according to the course's grading policy.) """ course_key = CourseKey.from_string(course_id) course_grade = CourseGradeFactory().read(user, course_key=course_key) return course_grade.passed
def _add_entrance_exam_to_context(self, courseware_context): """ Adds entrance exam related information to the given context. """ if course_has_entrance_exam(self.course) and getattr(self.chapter, 'is_entrance_exam', False): courseware_context['entrance_exam_passed'] = user_has_passed_entrance_exam(self.effective_user, self.course) courseware_context['entrance_exam_current_score'] = get_entrance_exam_score_ratio( CourseGradeFactory().create(self.effective_user, self.course), get_entrance_exam_usage_key(self.course), )
def test_without_cert(self): with override_waffle_switch(AUTO_CERTIFICATE_GENERATION, active=True): with mock.patch( 'lms.djangoapps.certificates.signals.generate_certificate_task', return_value=None) as mock_cert_task: grade_factory = CourseGradeFactory() with mock_passing_grade(): grade_factory.update(self.user, self.course) mock_cert_task.assert_called_with(self.user, self.course_key)
def user_course_passed(self): from lms.djangoapps.grades.course_grade_factory import CourseGradeFactory from django.contrib.auth.models import User try: user = User.objects.get(id=self.scope_ids.user_id) response = CourseGradeFactory().read(user, course_key=self.course_id) return response.passed except User.DoesNotExist: return False
def get_grade_data(self, user, course_key, grade_cutoffs): """ Collects and formats the grades data for a particular user and course. Args: user (User) course_key (CourseKey) grade_cutoffs: # TODO: LEARNER-3854: Complete docstring if implementing Learner Analytics. """ course_grade = CourseGradeFactory().read(user, course_key=course_key) grades = [] total_earned = 0 total_possible = 0 # answered_percent seems to be unused and it does not take into account assignment type weightings answered_percent = None chapter_grades = course_grade.chapter_grades.values() for chapter in chapter_grades: # Note: this code exists on the progress page. We should be able to remove it going forward. if not chapter['display_name'] == "hidden": for subsection_grade in chapter['sections']: log.info(subsection_grade.display_name) possible = subsection_grade.graded_total.possible earned = subsection_grade.graded_total.earned passing_grade = math.ceil(possible * grade_cutoffs['Pass']) grades.append({ 'assignment_type': subsection_grade.format, 'total_earned': earned, 'total_possible': possible, 'passing_grade': passing_grade, 'display_name': subsection_grade.display_name, 'location': unicode(subsection_grade.location), 'assigment_url': reverse('jump_to_id', kwargs={ 'course_id': unicode(course_key), 'module_id': unicode(subsection_grade.location), }) }) if earned > 0: total_earned += earned total_possible += possible if total_possible > 0: answered_percent = float(total_earned) / total_possible return (grades, answered_percent, course_grade.percent)
def send_xapi_statements(self, lrs_configuration, days): """ Send xAPI analytics data of the enterprise learners to the given LRS. Arguments: lrs_configuration (XAPILRSConfiguration): Configuration object containing LRS configurations of the LRS where to send xAPI learner analytics. days (int): Include course enrollment of this number of days. """ persistent_course_grades = self.get_course_completions( lrs_configuration.enterprise_customer, days) users = self.prefetch_users(persistent_course_grades) course_overviews = self.prefetch_courses(persistent_course_grades) for persistent_course_grade in persistent_course_grades: error_message = None user = users.get(persistent_course_grade.user_id) course_overview = course_overviews.get( persistent_course_grade.course_id) course_grade = CourseGradeFactory().read( user, course_key=persistent_course_grade.course_id) xapi_transmission_queryset = XAPILearnerDataTransmissionAudit.objects.filter( user=user, course_id=persistent_course_grade.course_id) if not xapi_transmission_queryset.exists(): LOGGER.warning( 'XAPILearnerDataTransmissionAudit object does not exist for user: {username}, course: ' '{course_id} so skipping the course completion statement to xapi.' ) continue try: send_course_completion_statement(lrs_configuration, user, course_overview, course_grade) except ClientError: error_message = 'Client error while sending course completion to xAPI for ' \ 'enterprise customer: {enterprise_customer}, user: {username} ' \ 'and course: {course_id}'.format( enterprise_customer=lrs_configuration.enterprise_customer.name, username=user.username if user else '', course_id=persistent_course_grade.course_id ) LOGGER.exception(error_message) status = 500 else: LOGGER.info( 'Successfully sent course completion to xAPI for user: {username} for course: {course_id}' .format(username=user.username if user else '', course_id=persistent_course_grade.course_id)) status = 200 fields = {'status': status, 'error_message': error_message} if status == 200: fields.update({ 'grade': course_grade.percent, 'timestamp': course_grade.passed_timestamp }) xapi_transmission_queryset.update(**fields)
def user_course_passed(user, course_key): """ Get if user passed course with percert """ response = CourseGradeFactory().read(user, course_key=course_key) if response is None: logger.error( 'CorfoGenerateCode - Error to get CourseGradeFactory().read(...), user: {}, course: {}' .format(user, str(course_key))) return None, None return response.passed, response.percent