Ejemplo n.º 1
0
 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'
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
    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)
Ejemplo n.º 4
0
    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'})