Example #1
0
def get_user_course_response_task(users, course_str, depth, callback_url):
    """
    Get a list of users grades' for a course
    """
    user_grades = {}
    grades_schema = {}
    course_key = CourseKey.from_string(str(course_str))
    course = courses.get_course(course_key)
    for user in users:
        course_grade = CourseGradeFactory().update(user, course)
        if depth == "all":
            grades_schema = get_user_grades(user.id, course_str)
        else:
            grades_schema = "Showing course grade summary, specify depth=all in query params."
        user_grades[user.username] = {
            'name': "{} {}".format(user.first_name, user.last_name),
            'email': user.email,
            'start_date': course.start,
            'end_date':
            course.end if not None else "This course has no end date.",
            'all_grades': grades_schema,
            "passed": course_grade.passed,
            "percent": course_grade.percent
        }

    #requests.post(str(callback_url), data=user_grades)
    return user_grades
Example #2
0
    def report(self):
        """
        Generates a report for the Ed2go completion report endpoint.

        Returns:
            A dictionary containing the report values.
        """
        course_grade = CourseGradeFactory().create(self.user,
                                                   get_course(self.course_key))
        persistent_grade = PersistentCourseGrade.objects.filter(
            user_id=self.user.id, course_id=self.course_key).first()

        return {
            c.REP_REGISTRATION_KEY:
            self.registration_key,
            c.REP_PERCENT_PROGRESS:
            round(self.progress * 100, 2),
            c.REP_LAST_ACCESS_DT:
            self.user.last_login.strftime('%Y-%m-%dT%H:%M:%SZ'),
            c.REP_COURSE_PASSED:
            str(course_grade.passed).lower(),
            c.REP_PERCENT_OVERALL_SCORE:
            course_grade.percent,
            c.REP_COMPLETION_DT:
            persistent_grade.passed_timestamp if persistent_grade else '',
            c.REP_TIME_SPENT:
            format_timedelta(
                CourseSession.total_time(user=self.user,
                                         course_key=self.course_key)),
        }
Example #3
0
def get_course_descriptor(course_key, depth):
    """
    Returns course descriptor
    """
    try:
        course_descriptor = courses.get_course(course_key, depth)
    except ValueError:
        course_descriptor = None
    return course_descriptor
Example #4
0
def is_course_public(course_key: CourseKey) -> AccessResponse:
    """
    This checks if a course is publicly accessible or not.
    """
    try:
        course = get_course(course_key, depth=0)
    except CourseRunNotFound:
        return ACCESS_DENIED
    return check_public_access(course, [COURSE_VISIBILITY_PUBLIC])
Example #5
0
    def handle(self, *args, **options):
        course_id = options['course_id']

        course_key = CourseKey.from_string(course_id)

        course = get_course(course_key)
        if not course:
            raise CommandError(u'Invalid course id: {}'.format(course_id))

        if course.discussion_link:
            self.stdout.write(course.discussion_link)
Example #6
0
 def _create_subsection_grade(self, user_id, course_key, usage_key):
     """
     Given a user_id, course_key, and subsection usage_key,
     creates a new ``PersistentSubsectionGrade``.
     """
     course = get_course(course_key, depth=None)
     subsection = course.get_child(usage_key)
     if not subsection:
         raise Exception('Subsection with given usage_key does not exist.')
     user = USER_MODEL.objects.get(id=user_id)
     course_data = CourseData(user, course=course)
     subsection_grade = CreateSubsectionGrade(subsection, course_data.structure, {}, {})
     return subsection_grade.update_or_create_model(user, force_update_subsections=True)
Example #7
0
    def _is_data_valid(self, request):
        """
        Validates the data posted to the view.

        Arguments
            request -- HTTP request

        Returns
            Tuple (data_is_valid, course_key, error_msg)
        """
        course_id = request.data.get('course_id')

        if not course_id:
            return False, None, u'Field course_id is missing.'

        try:
            course_key = CourseKey.from_string(course_id)
            courses.get_course(course_key)
        except (InvalidKeyError, ValueError) as ex:
            log.exception(u'Unable to locate course matching %s.', course_id)
            return False, None, text_type(ex)

        return True, course_key, None
Example #8
0
def _create_subsection_grade(user_id, course_key, usage_key):
    """
    Given a user_id, course_key, and subsection usage_key,
    creates a new ``PersistentSubsectionGrade``.
    """
    from lms.djangoapps.courseware.courses import get_course
    from django.contrib.auth import get_user_model
    course = get_course(course_key, depth=None)
    subsection = course.get_child(usage_key)
    if not subsection:
        raise Exception('Subsection with given usage_key does not exist.')
    user = get_user_model().objects.get(id=user_id)
    subsection_grade = CreateSubsectionGrade(subsection, course_data.CourseData(user, course=course).structure, {}, {})
    return subsection_grade.update_or_create_model(user, force_update_subsections=True)
Example #9
0
    def dump_one(self, *args, **options):
        if not args:
            raise CommandError("Course id not specified")
        if len(args) > 2:
            raise CommandError("Only one course id may be specified")
        raw_course_key = args[0]

        if len(args) == 1:
            output_file_location = self.get_default_file_location(
                raw_course_key)
        else:
            output_file_location = args[1]

        try:
            course_key = CourseKey.from_string(raw_course_key)
        except InvalidKeyError:
            course_key = CourseLocator.from_string(raw_course_key)

        course = get_course(course_key)
        if not course:
            raise CommandError("Invalid course id: {}".format(course_key))

        target_discussion_ids = None
        if options.get(self.COHORTED_ONLY_PARAMETER, False):
            cohorted_discussions = get_legacy_discussion_settings(
                course_key).cohorted_discussions
            if not cohorted_discussions:
                raise MissingCohortedConfigCommandError(
                    "Only cohorted discussions are marked for export, "
                    "but no cohorted discussions found for the course")
            else:
                target_discussion_ids = cohorted_discussions

        raw_end_date = options.get(self.END_DATE_PARAMETER, None)
        end_date = dateutil.parser.parse(
            raw_end_date) if raw_end_date else None
        data = Extractor().extract(
            course_key,
            end_date=end_date,
            thread_type=(options.get(self.THREAD_TYPE_PARAMETER, None)),
            thread_ids=target_discussion_ids,
        )

        filter_str = self._get_filter_string_representation(options)

        self.stdout.write("Writing social stats ({}) to {}\n".format(
            filter_str, output_file_location))
        with open(output_file_location, 'wb') as output_stream:
            Exporter(output_stream).export(data)
Example #10
0
 def _create_subsection_grade(self, user_id, course_key, usage_key):
     """
     Given a user_id, course_key, and subsection usage_key,
     creates a new ``PersistentSubsectionGrade``.
     """
     course = get_course(course_key, depth=None)
     subsection = course.get_child(usage_key)
     if not subsection:
         raise Exception('Subsection with given usage_key does not exist.')
     user = USER_MODEL.objects.get(id=user_id)
     course_data = CourseData(user, course=course)
     subsection_grade = CreateSubsectionGrade(subsection,
                                              course_data.structure, {}, {})
     return subsection_grade.update_or_create_model(
         user, force_update_subsections=True)
Example #11
0
def get_random_cohort(course_key):
    """
    Helper method to get a cohort for random assignment.

    If there are multiple cohorts of type RANDOM in the course, one of them will be randomly selected.
    If there are no existing cohorts of type RANDOM in the course, one will be created.
    """
    course = courses.get_course(course_key)
    cohorts = get_course_cohorts(course, assignment_type=CourseCohort.RANDOM)
    if cohorts:
        cohort = local_random().choice(cohorts)
    else:
        cohort = CourseCohort.create(
            cohort_name=DEFAULT_COHORT_NAME,
            course_id=course_key,
            assignment_type=CourseCohort.RANDOM).course_user_group
    return cohort
Example #12
0
    def post(self, request, *args, **kwargs):
        """
        Attempt to enroll the user.
        """
        user = request.user
        valid, course_key, error = self._is_data_valid(request)
        if not valid:
            return DetailResponse(error, status=HTTP_406_NOT_ACCEPTABLE)

        embargo_response = embargo_api.get_embargo_response(
            request, course_key, user)

        if embargo_response:
            return embargo_response

        # Don't do anything if an enrollment already exists
        course_id = six.text_type(course_key)
        enrollment = CourseEnrollment.get_enrollment(user, course_key)
        if enrollment and enrollment.is_active:
            msg = Messages.ENROLLMENT_EXISTS.format(course_id=course_id,
                                                    username=user.username)
            return DetailResponse(msg, status=HTTP_409_CONFLICT)

        # Check to see if enrollment for this course is closed.
        course = courses.get_course(course_key)
        if CourseEnrollment.is_enrollment_closed(user, course):
            msg = Messages.ENROLLMENT_CLOSED.format(course_id=course_id)
            log.info(u'Unable to enroll user %s in closed course %s.', user.id,
                     course_id)
            return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)

        # If there is no audit or honor course mode, this most likely
        # a Prof-Ed course. Return an error so that the JS redirects
        # to track selection.
        honor_mode = CourseMode.mode_for_course(course_key, CourseMode.HONOR)
        audit_mode = CourseMode.mode_for_course(course_key, CourseMode.AUDIT)

        # Check to see if the User has an entitlement and enroll them if they have one for this course
        if CourseEntitlement.check_for_existing_entitlement_and_enroll(
                user=user, course_run_key=course_key):
            return JsonResponse(
                {
                    'redirect_destination':
                    reverse('courseware', args=[six.text_type(course_id)]),
                }, )

        # Accept either honor or audit as an enrollment mode to
        # maintain backwards compatibility with existing courses
        default_enrollment_mode = audit_mode or honor_mode
        course_name = None
        course_announcement = None
        if course is not None:
            course_name = course.display_name
            course_announcement = course.announcement
        if default_enrollment_mode:
            msg = Messages.ENROLL_DIRECTLY.format(username=user.username,
                                                  course_id=course_id)
            if not default_enrollment_mode.sku:
                # If there are no course modes with SKUs, return a different message.
                msg = Messages.NO_SKU_ENROLLED.format(
                    enrollment_mode=default_enrollment_mode.slug,
                    course_id=course_id,
                    course_name=course_name,
                    username=user.username,
                    announcement=course_announcement)
            log.info(msg)
            self._enroll(course_key, user, default_enrollment_mode.slug)
            mode = CourseMode.AUDIT if audit_mode else CourseMode.HONOR
            SAILTHRU_AUDIT_PURCHASE.send(sender=None,
                                         user=user,
                                         mode=mode,
                                         course_id=course_id)
            self._handle_marketing_opt_in(request, course_key, user)
            return DetailResponse(msg)
        else:
            msg = Messages.NO_DEFAULT_ENROLLMENT_MODE.format(
                course_id=course_id)
            return DetailResponse(msg, status=HTTP_406_NOT_ACCEPTABLE)
Example #13
0
    def handle(self, *args, **options):
        if len(args) < 1 or len(args) > 2:
            print(Command.help)
            return

        dry_run = True
        create_log = False

        msg_string = 'Script started on {}'.format(
            datetime.datetime.now().ctime())

        if 'repair' in args:
            dry_run = False
        if 'dryrun' in args:
            dry_run = True
        if 'createlog' in args:
            create_log = True
            file_handler = logging.FileHandler(
                'repair_internal_admin_instructor_role.log')
            file_handler.setLevel(logging.DEBUG)
            formatter = logging.Formatter(
                '%(asctime)s-%(name)s-%(levelname)s-%(message)s')
            file_handler.setFormatter(formatter)
            log.addHandler(file_handler)

        log.info(msg_string)

        if dry_run:
            msg_string = 'Script started in dry run mode, this will print for all internal admins courses for which ' \
                         'we need to remove or add theirs instructor role'
        else:
            msg_string = 'Script started in repair mode, this will permanently remove or add instructor role of ' \
                         'internal admins. THIS IS IRREVERSIBLE!'

        log.info(msg_string)

        internal_admin_group_name = 'mcka_role_internal_admin'
        internal_tag_group_type = 'tag:internal'
        instructor_role = 'instructor'
        staff_role = 'staff'
        number_of_removed_roles = 0
        number_of_added_roles = 0

        #get all internal admins
        try:
            internal_admin_group = Group.objects.get(
                name__icontains=internal_admin_group_name)
        except ObjectDoesNotExist:
            internal_admin_group = None

        if internal_admin_group:
            internal_admins = internal_admin_group.user_set.all()

        #get internal tagged courses
        try:
            internal_courses_group = Group.objects.get(
                groupprofile__group_type=internal_tag_group_type)
        except ObjectDoesNotExist:
            internal_courses_group = None

        if internal_courses_group:
            internal_courses = CourseGroupRelationship.objects.filter(
                group=internal_courses_group)
            internal_courses_ids = []
            for internal_course in internal_courses:
                internal_courses_ids.append(internal_course.course_id)

        # for all internal admins check their roles and remove instructor role on course
        # if he doesn't have staff role on course and course isn't tagged internal
        for internal_admin in internal_admins:
            user_roles = CourseAccessRole.objects.filter(user=internal_admin)

            instructor_courses = []
            staff_courses = []
            for user_role in user_roles:
                if user_role.role == instructor_role:
                    instructor_courses.append(user_role.course_id)
                if user_role.role == staff_role:
                    staff_courses.append(user_role.course_id)

            for instructor_course in instructor_courses:
                if str(
                        instructor_course
                ) not in internal_courses_ids and instructor_course not in staff_courses:
                    number_of_removed_roles += 1
                    if dry_run:
                        msg_string = 'Remove instructor role for internal admin ' + str(internal_admin.id) +\
                                     ' on course ' + str(instructor_course) + '.'
                        log.info(msg_string)
                    else:
                        role_to_delete = CourseAccessRole.objects.get(
                            user=internal_admin,
                            role=instructor_role,
                            course_id=instructor_course)
                        role_to_delete.delete()

        # for all internal tagged course check roles and if internal admins don't have instructor role on course add it
        for internal_course in internal_courses:
            course_id = get_course_key(internal_course.course_id)
            course_roles = CourseAccessRole.objects.filter(course_id=course_id)
            instructor_users = []
            for course_role in course_roles:
                if course_role.role == instructor_role:
                    instructor_users.append(course_role.user_id)

            for internal_admin in internal_admins:
                if internal_admin.id not in instructor_users:
                    number_of_added_roles += 1
                    if dry_run:
                        msg_string = 'Add instructor role for internal admin ' + str(internal_admin.id) + \
                                     ' on course ' + str(course_id) + '.'
                        log.info(msg_string)
                    else:
                        course = courses.get_course(course_id, 0)
                        new_role = CourseAccessRole(user=internal_admin,
                                                    role=instructor_role,
                                                    course_id=course.id,
                                                    org=course.org)
                        new_role.save()

        msg_string = 'Number of removed roles: ' + str(
            number_of_removed_roles) + '.'
        log.info(msg_string)
        msg_string = 'Number of added roles: ' + str(
            number_of_added_roles) + '.'
        log.info(msg_string)

        log.info(
            '---------------------------------------------------------------------------------------------------'
        )

        if create_log:
            print(
                'Script started in create log mode, please open repair_internal_admin_instructor_role.log file.'
            )
Example #14
0
 def course(self):
     # TODO using 'modulestore().get_course(self._course_id)' doesn't work. return None
     from lms.djangoapps.courseware.courses import get_course
     return get_course(self.course_id)
Example #15
0
def cohort_handler(request, course_key_string, cohort_id=None):
    """
    The restful handler for cohort requests. Requires JSON.
    GET
        If a cohort ID is specified, returns a JSON representation of the cohort
            (name, id, user_count, assignment_type, user_partition_id, group_id).
        If no cohort ID is specified, returns the JSON representation of all cohorts.
           This is returned as a dict with the list of cohort information stored under the
           key `cohorts`.
    PUT or POST or PATCH
        If a cohort ID is specified, updates the cohort with the specified ID. Currently the only
        properties that can be updated are `name`, `user_partition_id` and `group_id`.
        Returns the JSON representation of the updated cohort.
        If no cohort ID is specified, creates a new cohort and returns the JSON representation of the updated
        cohort.
    """
    course_key = CourseKey.from_string(course_key_string)
    if not has_course_author_access(request.user, course_key):
        raise Http404(
            'The requesting user does not have course author permissions.')

    course = get_course(course_key)

    if request.method == 'GET':
        if not cohort_id:
            all_cohorts = [
                _get_cohort_representation(c, course)
                for c in cohorts.get_course_cohorts(course)
            ]
            return JsonResponse({'cohorts': all_cohorts})
        else:
            cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
            return JsonResponse(_get_cohort_representation(cohort, course))
    else:
        name = request.json.get('name')
        assignment_type = request.json.get('assignment_type')
        if not name:
            # Note: error message not translated because it is not exposed to the user (UI prevents this state).
            return JsonResponse({"error": "Cohort name must be specified."},
                                400)
        if not assignment_type:
            # Note: error message not translated because it is not exposed to the user (UI prevents this state).
            return JsonResponse(
                {"error": "Assignment type must be specified."}, 400)
        # If cohort_id is specified, update the existing cohort. Otherwise, create a new cohort.
        if cohort_id:
            cohort = cohorts.get_cohort_by_id(course_key, cohort_id)
            if name != cohort.name:
                if cohorts.is_cohort_exists(course_key, name):
                    err_msg = gettext(
                        "A cohort with the same name already exists.")
                    return JsonResponse({"error": str(err_msg)}, 400)
                cohort.name = name
                cohort.save()
            try:
                cohorts.set_assignment_type(cohort, assignment_type)
            except ValueError as err:
                return JsonResponse({"error": str(err)}, 400)
        else:
            try:
                cohort = cohorts.add_cohort(course_key, name, assignment_type)
            except ValueError as err:
                return JsonResponse({"error": str(err)}, 400)

        group_id = request.json.get('group_id')
        if group_id is not None:
            user_partition_id = request.json.get('user_partition_id')
            if user_partition_id is None:
                # Note: error message not translated because it is not exposed to the user (UI prevents this state).
                return JsonResponse(
                    {
                        "error":
                        "If group_id is specified, user_partition_id must also be specified."
                    }, 400)
            existing_group_id, existing_partition_id = cohorts.get_group_info_for_cohort(
                cohort)
            if group_id != existing_group_id or user_partition_id != existing_partition_id:
                unlink_cohort_partition_group(cohort)
                link_cohort_to_partition_group(cohort, user_partition_id,
                                               group_id)
        else:
            # If group_id was specified as None, unlink the cohort if it previously was associated with a group.
            existing_group_id, _ = cohorts.get_group_info_for_cohort(cohort)
            if existing_group_id is not None:
                unlink_cohort_partition_group(cohort)

        return JsonResponse(_get_cohort_representation(cohort, course))
Example #16
0
def is_course_public(course_key: CourseKey) -> AccessResponse:
    """
    This checks if a course is publicly accessible or not.
    """
    course = get_course(course_key, depth=0)
    return check_public_access(course, [COURSE_VISIBILITY_PUBLIC])
Example #17
0
    def handle(self, *args, **options):

        # build a list of CourseKeys from any course IDs given
        course_key_list = []
        for course_id in options["course_id"]:
            try:
                course_key_list.append(CourseKey.from_string(course_id))
            except (InvalidKeyError, ValueError):
                log.error("Invalid course ID: %s", course_id)
                sys.exit(1)

        # get the Learndot:edX course mappings
        course_mappings = CourseMapping.objects.all()
        if course_key_list:
            course_mappings = course_mappings.filter(
                edx_course_key__in=course_key_list)

        if not course_mappings.exists():
            if options["course_id"]:
                log.error(
                    "No course mappings were found for your specified course IDs."
                )
            else:
                log.error("No course mappings were found.")
            sys.exit(1)

        learndot_client = LearndotAPIClient()

        # for each mapped course, go through its enrollments, get the
        # course grade for each enrolled user, and if the user has passed,
        # update the Learndot enrolment

        date_settings = {
            'TIMEZONE': settings.TIME_ZONE,
            'RETURN_AS_TIMEZONE_AWARE': True
        }
        end_enrollments_date = dateparser.parse(options["end"],
                                                settings=date_settings)
        start_enrolments_date = dateparser.parse(options["start"],
                                                 settings=date_settings)

        for cm in course_mappings:
            try:
                course = get_course(cm.edx_course_key)
            except (InvalidKeyError, ValueError):
                log.error("Invalid edX course found in map: %s",
                          cm.edx_course_key)
                continue

            log.info("Processing enrollments in course %s", cm.edx_course_key)

            enrollments = CourseEnrollment.objects.filter(
                course_id=cm.edx_course_key,
                created__range=[start_enrolments_date, end_enrollments_date],
            )

            if options["users"]:
                enrollments = enrollments.filter(
                    user__username__in=options["users"])

            for enrollment in enrollments:
                contact_id = learndot_client.get_contact_id(enrollment.user)
                if not contact_id:
                    log.info(
                        "Not setting enrolment status for user %s in course %s, because contact_id is None .",
                        enrollment.user, cm.edx_course_key)
                    continue
                #
                # Disturbingly enough, if persistent grades are not
                # enabled, it just takes looking up the grade to get
                # the Learndot enrolment updated, because when
                # CourseGradeFactory constructs the CourseGrade in its
                # read() method, it will actually call its _update()
                # method, which sends the COURSE_GRADE_NOW_PASSED
                # signal, which of course fires
                # edxlearndot.signals.listen_for_passing_grade.
                #
                # However, if the edX instance has persistent course
                # grades enabled, the CourseGrade doesn't have to be
                # constructed, so the signal isn't fired, and we have
                # to explicitly update Learndot.
                #
                course_grade = CourseGradeFactory().read(
                    enrollment.user, course)
                if not course_grade:
                    log.info(
                        "Not setting enrolment status for user %s in course %s, because no grade is available.",
                        enrollment.user, cm.edx_course_key)
                elif course_grade.passed and should_persist_grades(
                        cm.edx_course_key):
                    log.info(
                        "Grades are persistent; explicitly updating Learndot enrolment."
                    )
                    learndot_client.check_if_enrolment_and_set_status_to_passed(
                        contact_id,
                        cm.learndot_component_id,
                        unconditional=options["unconditional"])
Example #18
0
def get_user_grades(user_id, course_str):
    """
    Get a single user's grades for  course. 
    """
    user = USER_MODEL.objects.get(id=user_id)
    course_key = CourseKey.from_string(str(course_str))
    course = courses.get_course(course_key)
    course_grade = CourseGradeFactory().update(user, course)
    course_structure = get_course_in_cache(course.id)
    courseware_summary = course_grade.chapter_grades.values()
    grade_summary = course_grade.summary
    grades_schema = {}
    courseware_summary = course_grade.chapter_grades.items()
    chapter_schema = {}
    for key, chapter in courseware_summary:
        subsection_schema = {}
        for section in chapter['sections']:
            section_children = course_structure.get_children(section.location)
            verticals = course_structure.get_children(section.location)
            vertical_schema = {}
            for vertical_key in verticals:
                sections_scores = {}
                problem_keys = course_structure.get_children(vertical_key)
                for problem_key in problem_keys:
                    if problem_key in section.problem_scores:
                        problem_score = section.problem_scores[problem_key]
                        xblock_content_url = reverse(
                            'courseware.views.views.render_xblock',
                            kwargs={'usage_key_string': unicode(problem_key)},
                        )
                        xblock_structure_url = generate_xblock_structure_url(
                            course_str, problem_key, user)
                        sections_scores[str(problem_key)] = {
                            "date":
                            problem_score.first_attempted
                            if problem_score.first_attempted is not None else
                            "Not attempted",
                            "earned":
                            problem_score.earned,
                            "possible":
                            problem_score.possible,
                            "xblock_content_url":
                            "{}{}".format(settings.LMS_ROOT_URL,
                                          xblock_content_url),
                            "xblock_structure_url":
                            "{}{}".format(settings.LMS_ROOT_URL,
                                          xblock_structure_url)
                        }
                    else:
                        sections_scores[str(
                            problem_key)] = "This block has no grades"
                vertical_structure_url = generate_xblock_structure_url(
                    course_str, vertical_key, user)
                vertical_schema[str(vertical_key)] = {
                    'problem_blocks': sections_scores,
                    "vertical_structure_url": vertical_structure_url
                }
            subsection_structure_url = generate_xblock_structure_url(
                course_str, section.location, user)
            subsection_schema[str(section.location)] = {
                "verticals": vertical_schema,
                "section_score":
                course_grade.score_for_module(section.location),
                "subsection_structure_url": subsection_structure_url
            }
        chapter_structure_url = generate_xblock_structure_url(
            course_str, key, user)
        chapter_schema[str(key)] = {
            "sections": subsection_schema,
            "chapter_structure_url": chapter_structure_url
        }

    return chapter_schema
Example #19
0
def perform_delegate_email_batches(entry_id, course_id, task_input, action_name):
    """
    Delegates emails by querying for the list of recipients who should
    get the mail, chopping up into batches of no more than settings.BULK_EMAIL_EMAILS_PER_TASK
    in size, and queueing up worker jobs.
    """
    entry = InstructorTask.objects.get(pk=entry_id)
    # Get inputs to use in this task from the entry.
    user_id = entry.requester.id
    task_id = entry.task_id

    # Perfunctory check, since expansion is made for convenience of other task
    # code that doesn't need the entry_id.
    if course_id != entry.course_id:
        format_msg = "Course id conflict: explicit value %r does not match task value %r"
        log.warning("Task %s: " + format_msg, task_id, course_id, entry.course_id)  # lint-amnesty, pylint: disable=logging-not-lazy
        raise ValueError(format_msg % (course_id, entry.course_id))

    # Fetch the CourseEmail.
    email_id = task_input['email_id']
    try:
        email_obj = CourseEmail.objects.get(id=email_id)
    except CourseEmail.DoesNotExist:
        # The CourseEmail object should be committed in the view function before the task
        # is submitted and reaches this point.
        log.warning("Task %s: Failed to get CourseEmail with id %s", task_id, email_id)
        raise

    # Check to see if email batches have already been defined.  This seems to
    # happen sometimes when there is a loss of connection while a task is being
    # queued.  When this happens, the same task gets called again, and a whole
    # new raft of subtasks gets queued up.  We will assume that if subtasks
    # have already been defined, there is no need to redefine them below.
    # So we just return right away.  We don't raise an exception, because we want
    # the current task to be marked with whatever it had been marked with before.
    if len(entry.subtasks) > 0 and len(entry.task_output) > 0:
        log.warning("Task %s has already been processed for email %s!  InstructorTask = %s", task_id, email_id, entry)
        progress = json.loads(entry.task_output)
        return progress

    # Sanity check that course for email_obj matches that of the task referencing it.
    if course_id != email_obj.course_id:
        format_msg = "Course id conflict: explicit value %r does not match email value %r"
        log.warning("Task %s: " + format_msg, task_id, course_id, email_obj.course_id)  # lint-amnesty, pylint: disable=logging-not-lazy
        raise ValueError(format_msg % (course_id, email_obj.course_id))

    # Fetch the course object.
    course = get_course(course_id)

    # Get arguments that will be passed to every subtask.
    targets = email_obj.targets.all()
    global_email_context = _get_course_email_context(course)

    recipient_qsets = [
        target.get_users(course_id, user_id)
        for target in targets
    ]
    # Use union here to combine the qsets instead of the | operator.  This avoids generating an
    # inefficient OUTER JOIN query that would read the whole user table.
    combined_set = recipient_qsets[0].union(*recipient_qsets[1:]) if len(recipient_qsets) > 1 \
        else recipient_qsets[0]
    recipient_fields = ['profile__name', 'email', 'username']

    log.info("Task %s: Preparing to queue subtasks for sending emails for course %s, email %s",
             task_id, course_id, email_id)

    total_recipients = combined_set.count()

    # Weird things happen if we allow empty querysets as input to emailing subtasks
    # The task appears to hang at "0 out of 0 completed" and never finishes.
    if total_recipients == 0:
        msg = "Bulk Email Task: Empty recipient set"
        log.warning(msg)
        raise ValueError(msg)

    def _create_send_email_subtask(to_list, initial_subtask_status):
        """Creates a subtask to send email to a given recipient list."""
        subtask_id = initial_subtask_status.task_id
        new_subtask = send_course_email.subtask(
            (
                entry_id,
                email_id,
                to_list,
                global_email_context,
                initial_subtask_status.to_dict(),
            ),
            task_id=subtask_id,
        )
        return new_subtask

    progress = queue_subtasks_for_query(
        entry,
        action_name,
        _create_send_email_subtask,
        [combined_set],
        recipient_fields,
        settings.BULK_EMAIL_EMAILS_PER_TASK,
        total_recipients,
    )

    # We want to return progress here, as this is what will be stored in the
    # AsyncResult for the parent task as its return value.
    # The AsyncResult will then be marked as SUCCEEDED, and have this return value as its "result".
    # That's okay, for the InstructorTask will have the "real" status, and monitoring code
    # should be using that instead.
    return progress
Example #20
0
    def post(self, request):

        query_params = self.request.query_params
        depth = query_params.get('depth', None)
        # create the list based on the post parameters

        serializer = GradeBulkAPIViewSerializer(data=request.data)

        try:
            course_ids = request.data['course_ids']
        except KeyError:
            course_ids = None
        try:
            usernames = request.data['usernames']
        except KeyError:
            usernames = None
        try:
            email_extension = request.data['email_extension']
        except KeyError:
            email_extension = None
        try:
            callback_url = request.data['callback_url']
        except KeyError:
            callback_url = None

        # compile list of email adresses

        if email_extension is not None and usernames is not None:
            list_of_emails_or_usernames = _build_emails(
                usernames, email_extension)
        else:
            list_of_emails_or_usernames = usernames

        if course_ids is None and email_extension is None:
            return generate_error_response('email_extention')

        # Set up a dictionaries/list to contain the user grades and course grades
        # catching the incorrect courses in a "course_failure" list
        course_results = {}
        course_success = {}
        course_failure = []
        user_grades = {}

        if course_ids is None:
            user_list = USER_MODEL.objects.filter(
                Q(username__in=usernames)
                | Q(email__in=list_of_emails_or_usernames), )

            user_courses = CourseEnrollment.objects.filter(user__in=user_list)
            for course_enrollment in user_courses:
                course_str = str(course_enrollment.course_id)
                course = get_course(course_enrollment.course_id)
                course_key = CourseKey.from_string(str(course_str))
                user_grades = get_user_course_response(course, user_list,
                                                       course_str, depth)
                course_success[course_str] = user_grades

        if course_ids is not None:
            for course_str in course_ids:
                if usernames is not None:
                    user_list = USER_MODEL.objects.filter(
                        Q(username__in=usernames)
                        | Q(email__in=list_of_emails_or_usernames),
                        courseenrollment__course_id=CourseKey.from_string(
                            course_str)).order_by('username').select_related(
                                'profile')
                else:
                    # Get all users enrolled given a course key
                    user_list = USER_MODEL.objects.filter(
                        courseenrollment__course_id=CourseKey.from_string(
                            course_str)).order_by('username').select_related(
                                'profile')
                try:
                    course_key = CourseKey.from_string(str(course_str))
                    course = courses.get_course(course_key)
                    user_grades = get_user_course_response(
                        course, user_list, course_str, depth)
                    course_success[course_str] = user_grades
                    user_grades = {}
                except Exception as e:
                    log.error(e)
                    pass
                    user_grades = {}
                except InvalidKeyError:
                    log.error(
                        'Invalid key, {} does not exist'.format(course_str))
                    course_failure.append(
                        "{} does not exist".format(course_str))
                    pass
                except ValueError:
                    log.error('Value error, {} could not be found.'.format(
                        course_str))
                    course_failure.append(
                        "{} does not exist".format(course_str))

        course_results["successes"] = course_success
        course_results["failures"] = course_failure

        return Response(course_results)