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 = generate_grade_report_for_verified_only( ) self.task_progress = TaskProgress(self.action_name, total=None, start_time=time()) self.course = get_course_by_id(self.course_id) self.file_name = 'problem_grade_report'
def _batch_users(self, context): """ Returns a generator of batches of users. """ def grouper(iterable, chunk_size=100, fillvalue=None): args = [iter(iterable)] * chunk_size return zip_longest(*args, fillvalue=fillvalue) def get_enrolled_learners_for_course(course_id, verified_only=False): """ Get all the enrolled users in a course chunk by chunk. This generator method fetches & loads the enrolled user objects on demand which in chunk size defined. This method is a workaround to avoid out-of-memory errors. """ filter_kwargs = { 'courseenrollment__course_id': course_id, } if verified_only: filter_kwargs['courseenrollment__mode'] = CourseMode.VERIFIED user_ids_list = get_user_model().objects.filter( **filter_kwargs).values_list('id', flat=True).order_by('id') user_chunks = grouper(user_ids_list) for user_ids in user_chunks: user_ids = [ user_id for user_id in user_ids if user_id is not None ] min_id = min(user_ids) max_id = max(user_ids) users = get_user_model().objects.filter( id__gte=min_id, id__lte=max_id, **filter_kwargs).select_related('profile') yield users course_id = context.course_id report_for_verified_only = generate_grade_report_for_verified_only() return get_enrolled_learners_for_course( course_id=course_id, verified_only=report_for_verified_only)
def _batch_users(self, context): """ Returns a generator of batches of users. """ def grouper(iterable, chunk_size=self.USER_BATCH_SIZE, fillvalue=None): args = [iter(iterable)] * chunk_size return zip_longest(*args, fillvalue=fillvalue) def get_enrolled_learners_for_course(course_id, verified_only=False): """ Get enrolled learners in a course. Arguments: course_id (CourseLocator): course_id to return enrollees for. verified_only (boolean): is a boolean when True, returns only verified enrollees. """ if optimize_get_learners_switch_enabled(): TASK_LOG.info(u'%s, Creating Course Grade with optimization', task_log_message) return users_for_course_v2(course_id, verified_only=verified_only) TASK_LOG.info(u'%s, Creating Course Grade without optimization', task_log_message) return users_for_course(course_id, verified_only=verified_only) def users_for_course(course_id, verified_only=False): """ Get all the enrolled users in a course. This method fetches & loads the enrolled user objects at once which may cause out-of-memory errors in large courses. This method will be removed when `OPTIMIZE_GET_LEARNERS_FOR_COURSE` waffle flag is removed. """ users = CourseEnrollment.objects.users_enrolled_in( course_id, include_inactive=True, verified_only=verified_only, ) users = users.select_related('profile') return grouper(users) def users_for_course_v2(course_id, verified_only=False): """ Get all the enrolled users in a course chunk by chunk. This generator method fetches & loads the enrolled user objects on demand which in chunk size defined. This method is a workaround to avoid out-of-memory errors. """ filter_kwargs = { 'courseenrollment__course_id': course_id, } if verified_only: filter_kwargs['courseenrollment__mode'] = CourseMode.VERIFIED user_ids_list = get_user_model().objects.filter(**filter_kwargs).values_list('id', flat=True).order_by('id') user_chunks = grouper(user_ids_list) for user_ids in user_chunks: user_ids = [user_id for user_id in user_ids if user_id is not None] min_id = min(user_ids) max_id = max(user_ids) users = get_user_model().objects.filter( id__gte=min_id, id__lte=max_id, **filter_kwargs ).select_related('profile') yield users course_id = context.course_id task_log_message = u'{}, Task type: {}'.format(context.task_info_string, context.action_name) report_for_verified_only = generate_grade_report_for_verified_only() return get_enrolled_learners_for_course(course_id=course_id, verified_only=report_for_verified_only)
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 report_for_verified_only = generate_grade_report_for_verified_only() enrolled_students = CourseEnrollment.objects.users_enrolled_in( course_id=course_id, include_inactive=True, verified_only=report_for_verified_only, ) 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'})