Beispiel #1
0
def is_mode_upsellable(user, enrollment, course=None):
    """
    Return whether the user is enrolled in a mode that can be upselled to another mode,
    usually audit upselled to verified.
    The partition code allows this function to more accurately return results for masquerading users.

    Arguments:
        user (:class:`.AuthUser`): The user from the request.user property
        enrollment (:class:`.CourseEnrollment`): The enrollment under consideration.
        course (:class:`.ModulestoreCourse`): Optional passed in modulestore course.
            If provided, it is expected to correspond to `enrollment.course.id`.
            If not provided, the course will be loaded from the modulestore.
            We use the course to retrieve user partitions when calculating whether
            the upgrade link will be shown.
    """
    partition_service = PartitionService(enrollment.course.id, course=course)
    enrollment_track_partition = partition_service.get_user_partition(
        ENROLLMENT_TRACK_PARTITION_ID)
    group = partition_service.get_group(user, enrollment_track_partition)
    current_mode = None
    if group:
        try:
            current_mode = [
                mode.get('slug')
                for mode in settings.COURSE_ENROLLMENT_MODES.values()
                if mode['id'] == group.id
            ].pop()
        except IndexError:
            pass
    upsellable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES
    return upsellable_mode
Beispiel #2
0
def _get_enrollment_track_groups(course_key):
    """
    Helper method that returns an array of the Groups in the EnrollmentTrackUserPartition for the given course.
    If no such partition exists on the course, an empty array is returned.
    """
    partition_service = PartitionService(course_key)
    partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
    return partition.groups if partition else []
Beispiel #3
0
def _get_enrollment_track_groups(course_key):
    """
    Helper method that returns an array of the Groups in the EnrollmentTrackUserPartition for the given course.
    If no such partition exists on the course, an empty array is returned.
    """
    partition_service = PartitionService(course_key)
    partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
    return partition.groups if partition else []
Beispiel #4
0
 def get_group_id_by_course_mode_name(self, course_id, mode_name):
     """
     Get the needed group_id from the Enrollment_Track partition for the specific masquerading track.
     """
     partition_service = PartitionService(course_id)
     enrollment_track_user_partition = partition_service.get_user_partition(ENROLLMENT_TRACK_PARTITION_ID)
     for group in enrollment_track_user_partition.groups:
         if group.name == mode_name:
             return group.id
     return None
Beispiel #5
0
    def __init__(self, **kwargs):
        request_cache_dict = DEFAULT_REQUEST_CACHE.data
        store = modulestore()

        services = kwargs.setdefault('services', {})
        user = kwargs.get('user')
        if user and user.is_authenticated:
            services['completion'] = CompletionService(
                user=user, context_key=kwargs.get('course_id'))
        services['fs'] = xblock.reference.plugins.FSService()
        services['i18n'] = ModuleI18nService
        services['library_tools'] = LibraryToolsService(
            store, user_id=user.id if user else None)
        services['partitions'] = PartitionService(
            course_id=kwargs.get('course_id'), cache=request_cache_dict)
        services['settings'] = SettingsService()
        services['user_tags'] = UserTagsService(self)
        if badges_enabled():
            services['badging'] = BadgingService(
                course_id=kwargs.get('course_id'), modulestore=store)
        self.request_token = kwargs.pop('request_token', None)
        services['teams'] = TeamsService()
        services['teams_configuration'] = TeamsConfigurationService()
        services['call_to_action'] = CallToActionService()
        super(LmsModuleSystem, self).__init__(**kwargs)
Beispiel #6
0
def get_group_id_for_user(user, course_discussion_settings):
    """
    Given a user, return the group_id for that user according to the course_discussion_settings.
    If discussions are not divided, this method will return None.
    It will also return None if the user is in no group within the specified division_scheme.
    """
    division_scheme = _get_course_division_scheme(course_discussion_settings)
    if division_scheme == CourseDiscussionSettings.COHORT:
        return get_cohort_id(user, course_discussion_settings.course_id)
    elif division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK:
        partition_service = PartitionService(course_discussion_settings.course_id)
        group_id = partition_service.get_user_group_id_for_partition(user, ENROLLMENT_TRACK_PARTITION_ID)
        # We negate the group_ids from dynamic partitions so that they will not conflict
        # with cohort IDs (which are an auto-incrementing integer field, starting at 1).
        return -1 * group_id if group_id is not None else None
    else:
        return None
Beispiel #7
0
def get_group_id_for_user(user, course_discussion_settings):
    """
    Given a user, return the group_id for that user according to the course_discussion_settings.
    If discussions are not divided, this method will return None.
    It will also return None if the user is in no group within the specified division_scheme.
    """
    division_scheme = _get_course_division_scheme(course_discussion_settings)
    if division_scheme == CourseDiscussionSettings.COHORT:
        return get_cohort_id(user, course_discussion_settings.course_id)
    elif division_scheme == CourseDiscussionSettings.ENROLLMENT_TRACK:
        partition_service = PartitionService(course_discussion_settings.course_id)
        group_id = partition_service.get_user_group_id_for_partition(user, ENROLLMENT_TRACK_PARTITION_ID)
        # We negate the group_ids from dynamic partitions so that they will not conflict
        # with cohort IDs (which are an auto-incrementing integer field, starting at 1).
        return -1 * group_id if group_id is not None else None
    else:
        return None
Beispiel #8
0
def can_show_verified_upgrade(user, enrollment, course=None):
    """
    Return whether this user can be shown upgrade message.

    Arguments:
        user (:class:`.AuthUser`): The user from the request.user property
        enrollment (:class:`.CourseEnrollment`): The enrollment under consideration.
            If None, then the enrollment is considered to be upgradeable.
        course (:class:`.ModulestoreCourse`): Optional passed in modulestore course.
            If provided, it is expected to correspond to `enrollment.course.id`.
            If not provided, the course will be loaded from the modulestore.
            We use the course to retrieve user partitions when calculating whether
            the upgrade link will be shown.
    """
    # Return `true` if user is not enrolled in course
    if enrollment is None:
        return False
    partition_service = PartitionService(enrollment.course.id, course=course)
    enrollment_track_partition = partition_service.get_user_partition(
        ENROLLMENT_TRACK_PARTITION_ID)
    group = partition_service.get_group(user, enrollment_track_partition)
    current_mode = None
    if group:
        try:
            current_mode = [
                mode.get('slug')
                for mode in settings.COURSE_ENROLLMENT_MODES.values()
                if mode['id'] == group.id
            ].pop()
        except IndexError:
            pass
    upgradable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES

    if not upgradable_mode:
        return False

    upgrade_deadline = enrollment.upgrade_deadline

    if upgrade_deadline is None:
        return False

    if datetime.datetime.now(utc).date() > upgrade_deadline.date():
        return False

    # Show the summary if user enrollment is in which allow user to upsell
    return enrollment.is_active and enrollment.mode in CourseMode.UPSELL_TO_VERIFIED_MODES
Beispiel #9
0
 def _user_experiment_group_names(self, user, context):
     """
     Returns a list of names of course experiments in which the given user
     belongs.
     """
     experiment_group_names = []
     for partition in context.course_experiments:
         group = PartitionService(context.course_id).get_group(user, partition, assign=False)
         experiment_group_names.append(group.name if group else '')
     return experiment_group_names
Beispiel #10
0
def can_show_verified_upgrade(user, course_id, enrollment):
    """
    Check if we are able to show verified upgrade message based
    on the enrollment and current user partition
    """
    if not enrollment:
        return False
    partition_service = PartitionService(course_id)
    enrollment_track_partition = partition_service.get_user_partition(
        ENROLLMENT_TRACK_PARTITION_ID)
    group = partition_service.get_group(user, enrollment_track_partition)
    current_mode = None
    if group:
        try:
            current_mode = [
                mode.get('slug')
                for mode in settings.COURSE_ENROLLMENT_MODES.values()
                if mode['id'] == group.id
            ].pop()
        except IndexError:
            pass
    upgradable_mode = not current_mode or current_mode in CourseMode.UPSELL_TO_VERIFIED_MODES
    return upgradable_mode and verified_upgrade_link_is_valid(enrollment)
Beispiel #11
0
 def __init__(self, **kwargs):
     request_cache_dict = RequestCache.get_request_cache().data
     services = kwargs.setdefault('services', {})
     services['fs'] = xblock.reference.plugins.FSService()
     services['i18n'] = ModuleI18nService
     services['library_tools'] = LibraryToolsService(modulestore())
     services['partitions'] = PartitionService(
         course_id=kwargs.get('course_id'), cache=request_cache_dict)
     store = modulestore()
     services['settings'] = SettingsService()
     services['user_tags'] = UserTagsService(self)
     if badges_enabled():
         services['badging'] = BadgingService(
             course_id=kwargs.get('course_id'), modulestore=store)
     self.request_token = kwargs.pop('request_token', None)
     super(LmsModuleSystem, self).__init__(**kwargs)
    def handle(self, *args, **options):

        errors = []

        module_store = modulestore()

        print "Starting Swap from Auto Track Cohort Pilot command"

        verified_track_cohorts_setting = self._latest_settings()

        if not verified_track_cohorts_setting:
            raise CommandError("No MigrateVerifiedTrackCohortsSetting found")

        if not verified_track_cohorts_setting.enabled:
            raise CommandError("No enabled MigrateVerifiedTrackCohortsSetting found")

        old_course_key = verified_track_cohorts_setting.old_course_key
        rerun_course_key = verified_track_cohorts_setting.rerun_course_key
        audit_cohort_names = verified_track_cohorts_setting.get_audit_cohort_names()

        # Verify that the MigrateVerifiedTrackCohortsSetting has all required fields
        if not old_course_key:
            raise CommandError("No old_course_key set for MigrateVerifiedTrackCohortsSetting with ID: '%s'"
                               % verified_track_cohorts_setting.id)

        if not rerun_course_key:
            raise CommandError("No rerun_course_key set for MigrateVerifiedTrackCohortsSetting with ID: '%s'"
                               % verified_track_cohorts_setting.id)

        if not audit_cohort_names:
            raise CommandError("No audit_cohort_names set for MigrateVerifiedTrackCohortsSetting with ID: '%s'"
                               % verified_track_cohorts_setting.id)

        print "Running for MigrateVerifiedTrackCohortsSetting with old_course_key='%s' and rerun_course_key='%s'" % \
              (verified_track_cohorts_setting.old_course_key, verified_track_cohorts_setting.rerun_course_key)

        # Get the CourseUserGroup IDs for the audit course names from the old course
        audit_course_user_group_ids = CourseUserGroup.objects.filter(
            name__in=audit_cohort_names,
            course_id=old_course_key,
            group_type=CourseUserGroup.COHORT,
        ).values_list('id', flat=True)

        if not audit_course_user_group_ids:
            raise CommandError(
                "No Audit CourseUserGroup found for course_id='%s' with group_type='%s' for names='%s'"
                % (old_course_key, CourseUserGroup.COHORT, audit_cohort_names)
            )

        # Get all of the audit CourseCohorts from the above IDs that are RANDOM
        random_audit_course_user_group_ids = CourseCohort.objects.filter(
            course_user_group_id__in=audit_course_user_group_ids,
            assignment_type=CourseCohort.RANDOM
        ).values_list('course_user_group_id', flat=True)

        if not random_audit_course_user_group_ids:
            raise CommandError(
                "No Audit CourseCohorts found for course_user_group_ids='%s' with assignment_type='%s"
                % (audit_course_user_group_ids, CourseCohort.RANDOM)
            )

        # Get the CourseUserGroupPartitionGroup for the above IDs, these contain the partition IDs and group IDs
        # that are set for group_access inside of modulestore
        random_audit_course_user_group_partition_groups = list(CourseUserGroupPartitionGroup.objects.filter(
            course_user_group_id__in=random_audit_course_user_group_ids
        ))

        if not random_audit_course_user_group_partition_groups:
            raise CommandError(
                "No Audit CourseUserGroupPartitionGroup found for course_user_group_ids='%s'"
                % random_audit_course_user_group_ids
            )

        # Get the single VerifiedTrackCohortedCourse for the old course
        try:
            verified_track_cohorted_course = VerifiedTrackCohortedCourse.objects.get(course_key=old_course_key)
        except VerifiedTrackCohortedCourse.DoesNotExist:
            raise CommandError("No VerifiedTrackCohortedCourse found for course: '%s'" % old_course_key)

        if not verified_track_cohorted_course.enabled:
            raise CommandError("VerifiedTrackCohortedCourse not enabled for course: '%s'" % old_course_key)

        # Get the single CourseUserGroupPartitionGroup for the verified_track
        # based on the verified_track name for the old course
        try:
            verified_course_user_group = CourseUserGroup.objects.get(
                course_id=old_course_key,
                group_type=CourseUserGroup.COHORT,
                name=verified_track_cohorted_course.verified_cohort_name
            )
        except CourseUserGroup.DoesNotExist:
            raise CommandError(
                "No Verified CourseUserGroup found for course_id='%s' with group_type='%s' for names='%s'"
                % (old_course_key, CourseUserGroup.COHORT, verified_track_cohorted_course.verified_cohort_name)
            )

        try:
            verified_course_user_group_partition_group = CourseUserGroupPartitionGroup.objects.get(
                course_user_group_id=verified_course_user_group.id
            )
        except CourseUserGroupPartitionGroup.DoesNotExist:
            raise CommandError(
                "No Verified CourseUserGroupPartitionGroup found for course_user_group_ids='%s'"
                % random_audit_course_user_group_ids
            )

        # Verify the enrollment track CourseModes exist for the new course
        try:
            CourseMode.objects.get(
                course_id=rerun_course_key,
                mode_slug=CourseMode.AUDIT
            )
        except CourseMode.DoesNotExist:
            raise CommandError("Audit CourseMode is not defined for course: '%s'" % rerun_course_key)

        try:
            CourseMode.objects.get(
                course_id=rerun_course_key,
                mode_slug=CourseMode.VERIFIED
            )
        except CourseMode.DoesNotExist:
            raise CommandError("Verified CourseMode is not defined for course: '%s'" % rerun_course_key)

        items = module_store.get_items(rerun_course_key)
        if not items:
            raise CommandError("Items for Course with key '%s' not found." % rerun_course_key)

        items_to_update = []

        all_cohorted_track_group_ids = set()
        for audit_course_user_group_partition_group in random_audit_course_user_group_partition_groups:
            all_cohorted_track_group_ids.add(audit_course_user_group_partition_group.group_id)
            all_cohorted_track_group_ids.add(verified_course_user_group_partition_group.group_id)

        for item in items:
            # Verify that there exists group access for this xblock, otherwise skip these checks
            if item.group_access:
                set_audit_enrollment_track = False
                set_verified_enrollment_track = False

                # Check the partition and group IDs for the audit course groups, if they exist in
                # the xblock's access settings then set the audit track flag to true
                for audit_course_user_group_partition_group in random_audit_course_user_group_partition_groups:
                    audit_partition_group_access = item.group_access.get(
                        audit_course_user_group_partition_group.partition_id,
                        None
                    )
                    if (audit_partition_group_access
                            and audit_course_user_group_partition_group.group_id in audit_partition_group_access):
                        print "Queueing XBlock at location: '%s' for Audit Content Group update " % item.location
                        set_audit_enrollment_track = True

                # Check the partition and group IDs for the verified course group, if it exists in
                # the xblock's access settings then set the verified track flag to true
                verified_partition_group_access = item.group_access.get(
                    verified_course_user_group_partition_group.partition_id,
                    None
                )
                if verified_partition_group_access:
                    non_verified_track_access_groups = (set(verified_partition_group_access) -
                                                        all_cohorted_track_group_ids)
                    # If the item has group_access that is not the
                    # verified or audit group IDs then raise an error
                    # This only needs to be checked for this partition_group once
                    if non_verified_track_access_groups:
                        errors.append(
                            "Non audit/verified cohorted content group set for xblock, location '%s' with IDs '%s'"
                            % (item.location, non_verified_track_access_groups)
                        )
                    if verified_course_user_group_partition_group.group_id in verified_partition_group_access:
                        print "Queueing XBlock at location: '%s' for Verified Content Group update " % item.location
                        set_verified_enrollment_track = True

                # Add the enrollment track ids to a group access array
                enrollment_track_group_access = []
                if set_audit_enrollment_track:
                    enrollment_track_group_access.append(settings.COURSE_ENROLLMENT_MODES['audit'])
                if set_verified_enrollment_track:
                    enrollment_track_group_access.append(settings.COURSE_ENROLLMENT_MODES['verified'])

                # If there are no errors, and either the audit track, or verified
                #  track needed an update, set the access, update and publish
                if set_verified_enrollment_track or set_audit_enrollment_track:
                    # Sets whether or not an xblock has changes
                    has_changes = module_store.has_changes(item)

                    # Check that the xblock does not have changes and add it to be updated, otherwise add an error
                    if not has_changes:
                        item.group_access = {ENROLLMENT_TRACK_PARTITION_ID: enrollment_track_group_access}
                        items_to_update.append(item)
                    else:
                        errors.append("XBlock '%s' with location '%s' needs access changes, but is a draft"
                                      % (item.display_name, item.location))

        partitions_to_delete = random_audit_course_user_group_partition_groups
        partitions_to_delete.append(verified_course_user_group_partition_group)

        # If there are no errors iterate over and update all of the items that had the access changed
        if not errors:
            for item in items_to_update:
                module_store.update_item(item, ModuleStoreEnum.UserID.mgmt_command)
                module_store.publish(item.location, ModuleStoreEnum.UserID.mgmt_command)
                print "Updated and published XBlock at location: '%s'" % item.location

        # Check if we should delete any partition groups if there are no errors.
        # If there are errors, none of the xblock items will have been updated,
        # so this section will throw errors for each partition in use
        if partitions_to_delete and not errors:
            partition_service = PartitionService(rerun_course_key)
            course = partition_service.get_course()
            for partition_to_delete in partitions_to_delete:
                # Get the user partition, and the index of that partition in the course
                partition = partition_service.get_user_partition(partition_to_delete.partition_id)
                if partition:
                    partition_index = course.user_partitions.index(partition)
                    group_id = int(partition_to_delete.group_id)

                    # Sanity check to verify that all of the groups being deleted are empty,
                    # since they should have been converted to use enrollment tracks instead.
                    # Taken from contentstore/views/course.py.remove_content_or_experiment_group
                    usages = GroupConfiguration.get_partitions_usage_info(module_store, course)
                    used = group_id in usages
                    if used:
                        errors.append("Content group '%s' is in use and cannot be deleted."
                                      % partition_to_delete.group_id)

                    # If there are not errors, proceed to update the course and user_partitions
                    if not errors:
                        # Remove the groups that match the group ID of the partition to be deleted
                        # Else if there are no match groups left, remove the user partition
                        matching_groups = [group for group in partition.groups if group.id == group_id]
                        if matching_groups:
                            group_index = partition.groups.index(matching_groups[0])
                            partition.groups.pop(group_index)
                            # Update the course user partition with the updated groups
                            if partition.groups:
                                course.user_partitions[partition_index] = partition
                            else:
                                course.user_partitions.pop(partition_index)
                        module_store.update_item(course, ModuleStoreEnum.UserID.mgmt_command)

        # If there are any errors, join them together and raise the CommandError
        if errors:
            raise CommandError(
                ("Error for MigrateVerifiedTrackCohortsSetting with ID='%s'\n" % verified_track_cohorts_setting.id) +
                "\t\n".join(errors)
            )

        print "Finished for MigrateVerifiedTrackCohortsSetting with ID='%s" % verified_track_cohorts_setting.id
Beispiel #13
0
def upload_user_grades_csv(_xmodule_instance_args, _entry_id, course_id, _task_input, action_name):  # pylint: disable=too-many-statements
    """
    For a given `course_id`, for given usernames generates a grades CSV file,
    and store using a `ReportStore`. Once created, the files can
    be accessed by instantiating another `ReportStore` (via
    `ReportStore.from_config()`) and calling `link_for()` on it.

    Unenrolled users and unknown usernames are stored in *_err_*.csv
    This task is very close to the .upload_grades_csv from instructor_tasks.task_helper
    The difference is that we filter enrolled students against requested usernames and
    we push info about this into PLP
    """
    start_time = time()
    start_date = datetime.now(UTC)
    status_interval = 100
    fmt = u'Task: {task_id}, InstructorTask ID: {entry_id}, Course: {course_id}, Input: {task_input}'
    task_info_string = fmt.format(
        task_id=_xmodule_instance_args.get('task_id') if _xmodule_instance_args is not None else None,
        entry_id=_entry_id,
        course_id=course_id,
        task_input=_task_input
    )
    TASK_LOG.info(u'%s, Task type: %s, Starting task execution', task_info_string, action_name)

    extended_kwargs_id = _task_input.get("extended_kwargs_id")
    extended_kwargs = InstructorTaskExtendedKwargs.get_kwargs_for_id(extended_kwargs_id)
    usernames = extended_kwargs.get("usernames", None)

    err_rows = [["id", "username", "error_msg"]]
    if usernames is None:
        message = "Error occured during edx task execution: no usersnames in InstructorTaskExtendedKwargs."
        TASK_LOG.error(u'%s, Task type: %s, ' + message, task_info_string)
        err_rows.append(["-1", "__", message])
        usernames = []

    enrolled_students = CourseEnrollment.objects.users_enrolled_in(course_id)
    enrolled_students = enrolled_students.filter(username__in=usernames)
    total_enrolled_students = enrolled_students.count()
    requester_id = _task_input.get("requester_id")
    task_progress = TaskProgress(action_name, total_enrolled_students, start_time)

    course = get_course_by_id(course_id)
    course_is_cohorted = is_course_cohorted(course.id)
    teams_enabled = course.teams_enabled
    cohorts_header = ['Cohort Name'] if course_is_cohorted else []
    teams_header = ['Team Name'] if teams_enabled else []

    experiment_partitions = get_split_user_partitions(course.user_partitions)
    group_configs_header = [u'Experiment Group ({})'.format(partition.name) for partition in experiment_partitions]

    certificate_info_header = ['Certificate Eligible', 'Certificate Delivered', 'Certificate Type']
    certificate_whitelist = CertificateWhitelist.objects.filter(course_id=course_id, whitelist=True)
    whitelisted_user_ids = [entry.user_id for entry in certificate_whitelist]

    # Loop over all our students and build our CSV lists in memory
    rows = []
    current_step = {'step': 'Calculating Grades'}

    TASK_LOG.info(
        u'%s, Task type: %s, Current step: %s, Starting grade calculation for total students: %s',
        task_info_string,
        action_name,
        current_step,
        total_enrolled_students,
    )
    found_students = User.objects.filter(username__in=usernames)
    # Check invalid usernames
    if len(found_students)!= len(usernames):
        found_students_usernames = [x.username for x in found_students]
        for u in usernames:
            if u not in found_students_usernames:
                err_rows.append([-1, u, "invalid_username"])
    # Check not enrolled requested students
    if found_students != enrolled_students:
        diff = found_students.exclude(id__in=enrolled_students)
        for u in diff:
            if u in diff:
                err_rows.append([u.id, u.username, "enrollment_for_username_not_found"])

    total_enrolled_students = enrolled_students.count()
    student_counter = 0
    TASK_LOG.info(
        u'%s, Task type: %s, Current step: %s, Starting grade calculation for total students: %s',
        task_info_string,
        action_name,
        current_step,

        total_enrolled_students
    )

    graded_assignments = course.grading.graded_assignments(course_id)
    grade_header = course.grading.grade_header(graded_assignments)

    rows.append(
        ["Student ID", "Email", "Username", "Last Name", "First Name", "Second Name", "Grade", "Grade Percent"] +
        grade_header +
        cohorts_header +
        group_configs_header +
        teams_header +
        ['Enrollment Track', 'Verification Status'] +
        certificate_info_header
    )
    for student, course_grade, err_msg in CourseGradeFactory().iter(course, enrolled_students):
        # Periodically update task status (this is a cache write)
        if task_progress.attempted % status_interval == 0:
            task_progress.update_task_state(extra_meta=current_step)
        task_progress.attempted += 1

        # Now add a log entry after each student is graded to get a sense
        # of the task's progress
        student_counter += 1
        TASK_LOG.info(
            u'%s, Task type: %s, Current step: %s, Grade calculation in-progress for students: %s/%s',
            task_info_string,
            action_name,
            current_step,
            student_counter,
            total_enrolled_students
        )

        if not course_grade:
            # An empty course_grade means we failed to grade a student.
            task_progress.failed += 1
            err_rows.append([student.id, student.username, err_msg])
            continue

        # We were able to successfully grade this student for this course.
        task_progress.succeeded += 1

        cohorts_group_name = []
        if course_is_cohorted:
            group = get_cohort(student, course_id, assign=False)
            cohorts_group_name.append(group.name if group else '')

        group_configs_group_names = []
        for partition in experiment_partitions:
            group = PartitionService(course_id).get_group(student, partition, assign=False)
            group_configs_group_names.append(group.name if group else '')

        team_name = []
        if teams_enabled:
            try:
                membership = CourseTeamMembership.objects.get(user=student, team__course_id=course_id)
                team_name.append(membership.team.name)
            except CourseTeamMembership.DoesNotExist:
                team_name.append('')

        enrollment_mode = CourseEnrollment.enrollment_mode_for_user(student, course_id)[0]
        verification_status = SoftwareSecurePhotoVerification.verification_status_for_user(
            student,
            course_id,
            enrollment_mode
        )
        certificate_info = certificate_info_for_user(
            student,
            course_id,
            course_grade.letter_grade,
            student.id in whitelisted_user_ids
        )
        second_name = ''
        try:
            up = UserProfile.objects.get(user=student)
            if up.goals:
                second_name = json.loads(up.goals).get('second_name', '')
        except ValueError:
            pass
        if certificate_info[0] == 'Y':
            TASK_LOG.info(
                u'Student is marked eligible_for_certificate'
                u'(user=%s, course_id=%s, grade_percent=%s gradecutoffs=%s, allow_certificate=%s, is_whitelisted=%s)',
                student,
                course_id,
                course_grade.percent,
                course.grade_cutoffs,
                student.profile.allow_certificate,
                student.id in whitelisted_user_ids
            )

        grade_results = course.grading.grade_results(graded_assignments, course_grade)

        grade_results = list(chain.from_iterable(grade_results))

        rows.append(
            [student.id, student.email, student.username, student.last_name, student.first_name,
             second_name, course_grade.percent, course_grade.percent*100] +
            grade_results + cohorts_group_name + group_configs_group_names + team_name +
            [enrollment_mode] + [verification_status] + certificate_info
        )
    TASK_LOG.info(
        u'%s, Task type: %s, Current step: %s, Grade calculation completed for students: %s/%s',
        task_info_string,
        action_name,
        current_step,
        student_counter,
        total_enrolled_students
    )

    # By this point, we've got the rows we're going to stuff into our CSV files.
    current_step = {'step': 'Uploading CSVs'}
    task_progress.update_task_state(extra_meta=current_step)
    TASK_LOG.info(u'%s, Task type: %s, Current step: %s', task_info_string, action_name, current_step)

    # Perform the actual upload
    custom_grades_download = get_custom_grade_config()

    report_hash_unique_hash = hex(random.getrandbits(32))[2:]
    report_name = 'plp_grade_users_report_{}_id_{}'.format(report_hash_unique_hash, requester_id)
    err_report_name = 'plp_grade_users_report_err_{}_id_{}'.format(report_hash_unique_hash, requester_id)
    upload_csv_to_report_store(rows, report_name, course_id, start_date, config_name=custom_grades_download)

    # If there are any error rows (don't count the header), write them out as well
    has_errors = len(err_rows) > 1
    if has_errors:
        upload_csv_to_report_store(err_rows, err_report_name, course_id, start_date, config_name=custom_grades_download)

    callback_url = _task_input.get("callback_url", None)

    if callback_url:
        report_store = ReportStore.from_config(config_name=custom_grades_download)
        files_urls_pairs = report_store.links_for(course_id)
        find_by_name = lambda name: [url for filename, url in files_urls_pairs if name in filename][0]
        try:
            csv_url = find_by_name(report_name)
            csv_err_url = find_by_name(err_report_name) if has_errors else None
            PlpApiClient().push_grade_api_result(callback_url, csv_url, csv_err_url)
        except Exception as e:
            TASK_LOG.error("Failed push to PLP:{}".format(str(e)))

    # One last update before we close out...
    TASK_LOG.info(u'%s, Task type: %s, Finalizing grade task', task_info_string, action_name)
    return task_progress.update_task_state(extra_meta=current_step)