def __init__(self, _xmodule_instance_args, _entry_id, course_id, _task_input, action_name): task_id = _xmodule_instance_args.get( 'task_id') if _xmodule_instance_args is not None else None self.task_info_string = ('Task: {task_id}, ' 'InstructorTask ID: {entry_id}, ' 'Course: {course_id}, ' 'Input: {task_input}').format( task_id=task_id, entry_id=_entry_id, course_id=course_id, task_input=_task_input, ) self.task_id = task_id self.entry_id = _entry_id self.task_input = _task_input self.action_name = action_name self.course_id = course_id self.report_for_verified_only = problem_grade_report_verified_only( self.course_id) self.task_progress = TaskProgress(self.action_name, total=None, start_time=time()) self.upload_filename = _task_input.get('filename', 'problem_grade_report') self.upload_dir = _task_input.get('upload_parent_dir', '')
def generate(cls, _xmodule_instance_args, _entry_id, course_id, _task_input, action_name): """ Generate a CSV containing all students' problem grades within a given `course_id`. """ def log_task_info(message): """ Updates the status on the celery task to the given message. Also logs the update. """ fmt = u'Task: {task_id}, InstructorTask ID: {entry_id}, Course: {course_id}, Input: {task_input}' task_info_string = fmt.format(task_id=task_id, entry_id=_entry_id, course_id=course_id, task_input=_task_input) TASK_LOG.info(u'%s, Task type: %s, %s, %s', task_info_string, action_name, message, task_progress.state) start_time = time() start_date = datetime.now(UTC) status_interval = 100 task_id = _xmodule_instance_args.get( 'task_id') if _xmodule_instance_args is not None else None enrolled_students = CourseEnrollment.objects.users_enrolled_in( course_id, include_inactive=True, verified_only=problem_grade_report_verified_only(course_id), ) task_progress = TaskProgress(action_name, enrolled_students.count(), start_time) # This struct encapsulates both the display names of each static item in the # header row as values as well as the django User field names of those items # as the keys. It is structured in this way to keep the values related. header_row = OrderedDict([('id', 'Student ID'), ('email', 'Email'), ('username', 'Username')]) course = get_course_by_id(course_id) log_task_info(u'Retrieving graded scorable blocks') graded_scorable_blocks = cls._graded_scorable_blocks_to_header(course) # Just generate the static fields for now. rows = [ list(header_row.values()) + ['Enrollment Status', 'Grade'] + _flatten(list(graded_scorable_blocks.values())) ] error_rows = [list(header_row.values()) + ['error_msg']] # Bulk fetch and cache enrollment states so we can efficiently determine # whether each user is currently enrolled in the course. log_task_info(u'Fetching enrollment status') CourseEnrollment.bulk_fetch_enrollment_states(enrolled_students, course_id) for student, course_grade, error in CourseGradeFactory().iter( enrolled_students, course): student_fields = [ getattr(student, field_name) for field_name in header_row ] task_progress.attempted += 1 if not course_grade: err_msg = text_type(error) # There was an error grading this student. if not err_msg: err_msg = u'Unknown error' error_rows.append(student_fields + [err_msg]) task_progress.failed += 1 continue enrollment_status = _user_enrollment_status(student, course_id) earned_possible_values = [] for block_location in graded_scorable_blocks: try: problem_score = course_grade.problem_scores[block_location] except KeyError: earned_possible_values.append( [u'Not Available', u'Not Available']) else: if problem_score.first_attempted: earned_possible_values.append( [problem_score.earned, problem_score.possible]) else: earned_possible_values.append( [u'Not Attempted', problem_score.possible]) rows.append(student_fields + [enrollment_status, course_grade.percent] + _flatten(earned_possible_values)) task_progress.succeeded += 1 if task_progress.attempted % status_interval == 0: step = u'Calculating Grades' task_progress.update_task_state(extra_meta={'step': step}) log_message = u'{0} {1}/{2}'.format(step, task_progress.attempted, task_progress.total) log_task_info(log_message) log_task_info('Uploading CSV to store') # Perform the upload if any students have been successfully graded if len(rows) > 1: upload_csv_to_report_store(rows, 'problem_grade_report', course_id, start_date) # If there are any error rows, write them out as well if len(error_rows) > 1: upload_csv_to_report_store(error_rows, 'problem_grade_report_err', course_id, start_date) return task_progress.update_task_state( extra_meta={'step': 'Uploading CSV'})