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': course_grades.summary(student, course) } for student in enrolled_students] return student_info, page
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': course_grades.summary(student, course) } for student in enrolled_students ] return student_info, page
def student_grades(student, request, course, use_offline=False): # pylint: disable=unused-argument ''' This is the main interface to get grades. It has the same parameters as grades.grade, as well as use_offline. If use_offline is True then this will look for an offline computed gradeset in the DB. ''' if not use_offline: return course_grades.summary(student, course) try: ocg = models.OfflineComputedGrade.objects.get(user=student, course_id=course.id) except models.OfflineComputedGrade.DoesNotExist: return dict( raw_scores=[], section_breakdown=[], msg='Error: no offline gradeset available for {}, {}'.format(student, course.id) ) gradeset = json.loads(ocg.gradeset) # Convert score dicts back to Score tuples: def score_from_dict(encoded): """ Given a formerly JSON-encoded Score tuple, return the Score tuple """ if encoded['module_id']: encoded['module_id'] = UsageKey.from_string(encoded['module_id']) return Score(**encoded) totaled_scores = gradeset['totaled_scores'] for section in totaled_scores: totaled_scores[section] = [score_from_dict(score) for score in totaled_scores[section]] gradeset['raw_scores'] = [score_from_dict(score) for score in gradeset['raw_scores']] return gradeset
def get_grade_summary(self): """ calls course_grades.summary for current user and course. the keywords for the returned object are - grade : A final letter grade. - percent : The final percent for the class (rounded up). - section_breakdown : A breakdown of each section that makes up the grade. (For display) - grade_breakdown : A breakdown of the major components that make up the final grade. (For display) """ return course_grades.summary(self.student_user, self.course)
def offline_grade_calculation(course_key): ''' Compute grades for all students for a specified course, and save results to the DB. ''' tstart = time.time() enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1).prefetch_related("groups").order_by( 'username') enc = MyEncoder() print "{} enrolled students".format(len(enrolled_students)) course = get_course_by_id(course_key) for student in enrolled_students: request = DummyRequest() request.user = student request.session = {} gradeset = course_grades.summary(student, course, keep_raw_scores=True) # Convert Score namedtuples to dicts: totaled_scores = gradeset['totaled_scores'] for section in totaled_scores: totaled_scores[section] = [ score._asdict() for score in totaled_scores[section] ] gradeset['raw_scores'] = [ score._asdict() for score in gradeset['raw_scores'] ] # Encode as JSON and save: gradeset_str = enc.encode(gradeset) ocg, _created = models.OfflineComputedGrade.objects.get_or_create( user=student, course_id=course_key) ocg.gradeset = gradeset_str ocg.save() print "%s done" % student # print statement used because this is run by a management command tend = time.time() dt = tend - tstart ocgl = models.OfflineComputedGradeLog(course_id=course_key, seconds=dt, nstudents=len(enrolled_students)) ocgl.save() print ocgl print "All Done!"
def handle(self, *args, **options): course_id = options['course'] print "Fetching ungraded students for {0}".format(course_id) ungraded = GeneratedCertificate.objects.filter( # pylint: disable=no-member course_id__exact=course_id).filter(grade__exact='') course = courses.get_course_by_id(course_id) factory = RequestFactory() request = factory.get('/') for cert in ungraded: # grade the student grade = course_grades.summary(cert.user, course) print "grading {0} - {1}".format(cert.user, grade['percent']) cert.grade = grade['percent'] if not options['noop']: cert.save()
def handle(self, *args, **options): course_id = options['course'] print "Fetching ungraded students for {0}".format(course_id) ungraded = GeneratedCertificate.objects.filter( # pylint: disable=no-member course_id__exact=course_id ).filter(grade__exact='') course = courses.get_course_by_id(course_id) factory = RequestFactory() request = factory.get('/') for cert in ungraded: # grade the student grade = course_grades.summary(cert.user, course) print "grading {0} - {1}".format(cert.user, grade['percent']) cert.grade = grade['percent'] if not options['noop']: cert.save()
def student_grades(student, request, course, keep_raw_scores=False, use_offline=False): ''' This is the main interface to get grades. It has the same parameters as grades.grade, as well as use_offline. If use_offline is True then this will look for an offline computed gradeset in the DB. ''' if not use_offline: return course_grades.summary(student, course, keep_raw_scores=keep_raw_scores) try: ocg = models.OfflineComputedGrade.objects.get(user=student, course_id=course.id) except models.OfflineComputedGrade.DoesNotExist: return dict( raw_scores=[], section_breakdown=[], msg='Error: no offline gradeset available for {}, {}'.format( student, course.id)) gradeset = json.loads(ocg.gradeset) # Convert score dicts back to Score tuples: def score_from_dict(encoded): """ Given a formerly JSON-encoded Score tuple, return the Score tuple """ if encoded['module_id']: encoded['module_id'] = UsageKey.from_string(encoded['module_id']) return Score(**encoded) totaled_scores = gradeset['totaled_scores'] for section in totaled_scores: totaled_scores[section] = [ score_from_dict(score) for score in totaled_scores[section] ] gradeset['raw_scores'] = [ score_from_dict(score) for score in gradeset['raw_scores'] ] return gradeset
def offline_grade_calculation(course_key): ''' Compute grades for all students for a specified course, and save results to the DB. ''' tstart = time.time() enrolled_students = User.objects.filter( courseenrollment__course_id=course_key, courseenrollment__is_active=1 ).prefetch_related("groups").order_by('username') enc = MyEncoder() print "{} enrolled students".format(len(enrolled_students)) course = get_course_by_id(course_key) for student in enrolled_students: request = DummyRequest() request.user = student request.session = {} gradeset = course_grades.summary(student, course) # Convert Score namedtuples to dicts: totaled_scores = gradeset['totaled_scores'] for section in totaled_scores: totaled_scores[section] = [score._asdict() for score in totaled_scores[section]] gradeset['raw_scores'] = [score._asdict() for score in gradeset['raw_scores']] # Encode as JSON and save: gradeset_str = enc.encode(gradeset) ocg, _created = models.OfflineComputedGrade.objects.get_or_create(user=student, course_id=course_key) ocg.gradeset = gradeset_str ocg.save() print "%s done" % student # print statement used because this is run by a management command tend = time.time() dt = tend - tstart ocgl = models.OfflineComputedGradeLog(course_id=course_key, seconds=dt, nstudents=len(enrolled_students)) ocgl.save() print ocgl print "All Done!"
def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None, generate_pdf=True): """ Request a new certificate for a student. Arguments: student - User.object course_id - courseenrollment.course_id (CourseKey) forced_grade - a string indicating a grade parameter to pass with the certificate request. If this is given, grading will be skipped. generate_pdf - Boolean should a message be sent in queue to generate certificate PDF Will change the certificate status to 'generating' or `downloadable` in case of web view certificates. Certificate must be in the 'unavailable', 'error', 'deleted' or 'generating' state. If a student has a passing grade or is in the whitelist table for the course a request will be made for a new cert. If a student has allow_certificate set to False in the userprofile table the status will change to 'restricted' If a student does not have a passing grade the status will change to status.notpassing Returns the newly created certificate instance """ valid_statuses = [ status.generating, status.unavailable, status.deleted, status.error, status.notpassing, status.downloadable, status.auditing, status.audit_passing, status.audit_notpassing, ] cert_status = certificate_status_for_student(student, course_id)['status'] cert = None if cert_status not in valid_statuses: LOGGER.warning( (u"Cannot create certificate generation task for user %s " u"in the course '%s'; " u"the certificate status '%s' is not one of %s."), student.id, unicode(course_id), cert_status, unicode(valid_statuses)) return None # The caller can optionally pass a course in to avoid # re-fetching it from Mongo. If they have not provided one, # get it from the modulestore. if course is None: course = modulestore().get_course(course_id, depth=0) profile = UserProfile.objects.get(user=student) profile_name = profile.name # Needed for access control in grading. self.request.user = student self.request.session = {} is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists() grade = course_grades.summary(student, course) enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user( student, course_id) mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES user_is_verified = SoftwareSecurePhotoVerification.user_is_verified( student) cert_mode = enrollment_mode is_eligible_for_certificate = is_whitelisted or CourseMode.is_eligible_for_certificate( enrollment_mode) unverified = False # For credit mode generate verified certificate if cert_mode == CourseMode.CREDIT_MODE: cert_mode = CourseMode.VERIFIED if template_file is not None: template_pdf = template_file elif mode_is_verified and user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format( id=course_id) elif mode_is_verified and not user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format( id=course_id) if CourseMode.mode_for_course(course_id, CourseMode.HONOR): cert_mode = GeneratedCertificate.MODES.honor else: unverified = True else: # honor code and audit students template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format( id=course_id) if forced_grade: grade['grade'] = forced_grade LOGGER.info(( u"Certificate generated for student %s in the course: %s with template: %s. " u"given template: %s, " u"user is verified: %s, " u"mode is verified: %s"), student.username, unicode(course_id), template_pdf, template_file, user_is_verified, mode_is_verified) cert, created = GeneratedCertificate.objects.get_or_create( user=student, course_id=course_id) # pylint: disable=no-member cert.mode = cert_mode cert.user = student cert.grade = grade['percent'] cert.course_id = course_id cert.name = profile_name cert.download_url = '' # Strip HTML from grade range label grade_contents = grade.get('grade', None) try: grade_contents = lxml.html.fromstring( grade_contents).text_content() passing = True except (TypeError, XMLSyntaxError, ParserError) as exc: LOGGER.info((u"Could not retrieve grade for student %s " u"in the course '%s' " u"because an exception occurred while parsing the " u"grade contents '%s' as HTML. " u"The exception was: '%s'"), student.id, unicode(course_id), grade_contents, unicode(exc)) # Log if the student is whitelisted if is_whitelisted: LOGGER.info(u"Student %s is whitelisted in '%s'", student.id, unicode(course_id)) passing = True else: passing = False # If this user's enrollment is not eligible to receive a # certificate, mark it as such for reporting and # analytics. Only do this if the certificate is new, or # already marked as ineligible -- we don't want to mark # existing audit certs as ineligible. cutoff = settings.AUDIT_CERT_CUTOFF_DATE if (cutoff and cert.created_date >= cutoff ) and not is_eligible_for_certificate: cert.status = CertificateStatuses.audit_passing if passing else CertificateStatuses.audit_notpassing cert.save() LOGGER.info( u"Student %s with enrollment mode %s is not eligible for a certificate.", student.id, enrollment_mode) return cert # If they are not passing, short-circuit and don't generate cert elif not passing: cert.status = status.notpassing cert.save() LOGGER.info( (u"Student %s does not have a grade for '%s', " u"so their certificate status has been set to '%s'. " u"No certificate generation task was sent to the XQueue."), student.id, unicode(course_id), cert.status) return cert # Check to see whether the student is on the the embargoed # country restricted list. If so, they should not receive a # certificate -- set their status to restricted and log it. if self.restricted.filter(user=student).exists(): cert.status = status.restricted cert.save() LOGGER.info( (u"Student %s is in the embargoed country restricted " u"list, so their certificate status has been set to '%s' " u"for the course '%s'. " u"No certificate generation task was sent to the XQueue."), student.id, cert.status, unicode(course_id)) return cert if unverified: cert.status = status.unverified cert.save() LOGGER.info( (u"User %s has a verified enrollment in course %s " u"but is missing ID verification. " u"Certificate status has been set to unverified"), student.id, unicode(course_id), ) return cert # Finally, generate the certificate and send it off. return self._generate_cert(cert, course, student, grade_contents, template_pdf, generate_pdf)
def add_cert(self, student, course_id, course=None, forced_grade=None, template_file=None, generate_pdf=True): """ Request a new certificate for a student. Arguments: student - User.object course_id - courseenrollment.course_id (CourseKey) forced_grade - a string indicating a grade parameter to pass with the certificate request. If this is given, grading will be skipped. generate_pdf - Boolean should a message be sent in queue to generate certificate PDF Will change the certificate status to 'generating' or `downloadable` in case of web view certificates. The course must not be a CCX. Certificate must be in the 'unavailable', 'error', 'deleted' or 'generating' state. If a student has a passing grade or is in the whitelist table for the course a request will be made for a new cert. If a student has allow_certificate set to False in the userprofile table the status will change to 'restricted' If a student does not have a passing grade the status will change to status.notpassing Returns the newly created certificate instance """ if hasattr(course_id, "ccx"): LOGGER.warning( ( u"Cannot create certificate generation task for user %s " u"in the course '%s'; " u"certificates are not allowed for CCX courses." ), student.id, unicode(course_id), ) return None valid_statuses = [ status.generating, status.unavailable, status.deleted, status.error, status.notpassing, status.downloadable, status.auditing, status.audit_passing, status.audit_notpassing, ] cert_status = certificate_status_for_student(student, course_id)["status"] cert = None if cert_status not in valid_statuses: LOGGER.warning( ( u"Cannot create certificate generation task for user %s " u"in the course '%s'; " u"the certificate status '%s' is not one of %s." ), student.id, unicode(course_id), cert_status, unicode(valid_statuses), ) return None # The caller can optionally pass a course in to avoid # re-fetching it from Mongo. If they have not provided one, # get it from the modulestore. if course is None: course = modulestore().get_course(course_id, depth=0) profile = UserProfile.objects.get(user=student) profile_name = profile.name # Needed for access control in grading. self.request.user = student self.request.session = {} is_whitelisted = self.whitelist.filter(user=student, course_id=course_id, whitelist=True).exists() grade = course_grades.summary(student, course) enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(student, course_id) mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES user_is_verified = SoftwareSecurePhotoVerification.user_is_verified(student) cert_mode = enrollment_mode is_eligible_for_certificate = is_whitelisted or CourseMode.is_eligible_for_certificate(enrollment_mode) unverified = False # For credit mode generate verified certificate if cert_mode == CourseMode.CREDIT_MODE: cert_mode = CourseMode.VERIFIED if template_file is not None: template_pdf = template_file elif mode_is_verified and user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(id=course_id) elif mode_is_verified and not user_is_verified: template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id) if CourseMode.mode_for_course(course_id, CourseMode.HONOR): cert_mode = GeneratedCertificate.MODES.honor else: unverified = True else: # honor code and audit students template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(id=course_id) if forced_grade: grade["grade"] = forced_grade LOGGER.info( ( u"Certificate generated for student %s in the course: %s with template: %s. " u"given template: %s, " u"user is verified: %s, " u"mode is verified: %s" ), student.username, unicode(course_id), template_pdf, template_file, user_is_verified, mode_is_verified, ) cert, created = GeneratedCertificate.objects.get_or_create( user=student, course_id=course_id ) # pylint: disable=no-member cert.mode = cert_mode cert.user = student cert.grade = grade["percent"] cert.course_id = course_id cert.name = profile_name cert.download_url = "" # Strip HTML from grade range label grade_contents = grade.get("grade", None) try: grade_contents = lxml.html.fromstring(grade_contents).text_content() passing = True except (TypeError, XMLSyntaxError, ParserError) as exc: LOGGER.info( ( u"Could not retrieve grade for student %s " u"in the course '%s' " u"because an exception occurred while parsing the " u"grade contents '%s' as HTML. " u"The exception was: '%s'" ), student.id, unicode(course_id), grade_contents, unicode(exc), ) # Log if the student is whitelisted if is_whitelisted: LOGGER.info(u"Student %s is whitelisted in '%s'", student.id, unicode(course_id)) passing = True else: passing = False # If this user's enrollment is not eligible to receive a # certificate, mark it as such for reporting and # analytics. Only do this if the certificate is new, or # already marked as ineligible -- we don't want to mark # existing audit certs as ineligible. cutoff = settings.AUDIT_CERT_CUTOFF_DATE if (cutoff and cert.created_date >= cutoff) and not is_eligible_for_certificate: cert.status = CertificateStatuses.audit_passing if passing else CertificateStatuses.audit_notpassing cert.save() LOGGER.info( u"Student %s with enrollment mode %s is not eligible for a certificate.", student.id, enrollment_mode ) return cert # If they are not passing, short-circuit and don't generate cert elif not passing: cert.status = status.notpassing cert.save() LOGGER.info( ( u"Student %s does not have a grade for '%s', " u"so their certificate status has been set to '%s'. " u"No certificate generation task was sent to the XQueue." ), student.id, unicode(course_id), cert.status, ) return cert # Check to see whether the student is on the the embargoed # country restricted list. If so, they should not receive a # certificate -- set their status to restricted and log it. if self.restricted.filter(user=student).exists(): cert.status = status.restricted cert.save() LOGGER.info( ( u"Student %s is in the embargoed country restricted " u"list, so their certificate status has been set to '%s' " u"for the course '%s'. " u"No certificate generation task was sent to the XQueue." ), student.id, cert.status, unicode(course_id), ) return cert if unverified: cert.status = status.unverified cert.save() LOGGER.info( ( u"User %s has a verified enrollment in course %s " u"but is missing ID verification. " u"Certificate status has been set to unverified" ), student.id, unicode(course_id), ) return cert # Finally, generate the certificate and send it off. return self._generate_cert(cert, course, student, grade_contents, template_pdf, generate_pdf)
def handle(self, *args, **options): if os.path.exists(options['output']): raise CommandError("File {0} already exists".format( options['output'])) status_interval = 100 # parse out the course into a coursekey if options['course']: try: course_key = CourseKey.from_string(options['course']) # if it's not a new-style course key, parse it from an old-style # course key except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string( options['course']) print "Fetching enrolled students for {0}".format(course_key) enrolled_students = User.objects.filter( courseenrollment__course_id=course_key) factory = RequestMock() request = factory.get('/') total = enrolled_students.count() print "Total enrolled: {0}".format(total) course = courses.get_course_by_id(course_key) total = enrolled_students.count() start = datetime.datetime.now() rows = [] header = None print "Fetching certificate data" cert_grades = { cert.user.username: cert.grade for cert in list( GeneratedCertificate.objects.filter( # pylint: disable=no-member course_id=course_key).prefetch_related('user')) } print "Grading students" for count, student in enumerate(enrolled_students): count += 1 if count % status_interval == 0: # Print a status update with an approximation of # how much time is left based on how long the last # interval took diff = datetime.datetime.now() - start timeleft = diff * (total - count) / status_interval hours, remainder = divmod(timeleft.seconds, 3600) minutes, __ = divmod(remainder, 60) print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format( count, total, hours, minutes) start = datetime.datetime.now() request.user = student grade = course_grades.summary(student, course) if not header: header = [ section['label'] for section in grade[u'section_breakdown'] ] rows.append( ["email", "username", "certificate-grade", "grade"] + header) percents = { section['label']: section['percent'] for section in grade[u'section_breakdown'] } row_percents = [percents[label] for label in header] if student.username in cert_grades: rows.append([ student.email, student.username, cert_grades[student.username], grade['percent'] ] + row_percents, ) else: rows.append( [student.email, student.username, "N/A", grade['percent'] ] + row_percents) with open(options['output'], 'wb') as f: writer = csv.writer(f) writer.writerows(rows)
def handle(self, *args, **options): if os.path.exists(options['output']): raise CommandError("File {0} already exists".format( options['output'])) STATUS_INTERVAL = 100 # parse out the course into a coursekey if options['course']: try: course_key = CourseKey.from_string(options['course']) # if it's not a new-style course key, parse it from an old-style # course key except InvalidKeyError: course_key = SlashSeparatedCourseKey.from_deprecated_string(options['course']) print "Fetching enrolled students for {0}".format(course_key) enrolled_students = User.objects.filter( courseenrollment__course_id=course_key ) factory = RequestMock() request = factory.get('/') total = enrolled_students.count() print "Total enrolled: {0}".format(total) course = courses.get_course_by_id(course_key) total = enrolled_students.count() start = datetime.datetime.now() rows = [] header = None print "Fetching certificate data" cert_grades = { cert.user.username: cert.grade for cert in list( GeneratedCertificate.objects.filter( # pylint: disable=no-member course_id=course_key ).prefetch_related('user') ) } print "Grading students" for count, student in enumerate(enrolled_students): count += 1 if count % STATUS_INTERVAL == 0: # Print a status update with an approximation of # how much time is left based on how long the last # interval took diff = datetime.datetime.now() - start timeleft = diff * (total - count) / STATUS_INTERVAL hours, remainder = divmod(timeleft.seconds, 3600) minutes, __ = divmod(remainder, 60) print "{0}/{1} completed ~{2:02}:{3:02}m remaining".format( count, total, hours, minutes) start = datetime.datetime.now() request.user = student grade = course_grades.summary(student, request, course) if not header: header = [section['label'] for section in grade[u'section_breakdown']] rows.append(["email", "username", "certificate-grade", "grade"] + header) percents = {section['label']: section['percent'] for section in grade[u'section_breakdown']} row_percents = [percents[label] for label in header] if student.username in cert_grades: rows.append([student.email, student.username, cert_grades[student.username], grade['percent']] + row_percents) else: rows.append([student.email, student.username, "N/A", grade['percent']] + row_percents) with open(options['output'], 'wb') as f: writer = csv.writer(f) writer.writerows(rows)