Esempio n. 1
0
def _listen_for_id_verification_status_changed(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user's id verification status has changed.

    If needed, generate a certificate task.
    """
    if not auto_certificate_generation_enabled():
        return

    user_enrollments = CourseEnrollment.enrollments_for_user(user=user)

    grade_factory = CourseGradeFactory()
    expected_verification_status = IDVerificationService.user_status(user)
    expected_verification_status = expected_verification_status['status']
    for enrollment in user_enrollments:
        if can_generate_certificate_task(user, enrollment.course_id):
            log.info(
                f'{enrollment.course_id} is using V2 certificates. Attempt will be made to generate a V2 '
                f'certificate for {user.id}. Id verification status is {expected_verification_status}'
            )
            generate_certificate_task(user, enrollment.course_id)
        elif grade_factory.read(user=user,
                                course=enrollment.course_overview).passed:
            if _fire_ungenerated_certificate_task(
                    user, enrollment.course_id, expected_verification_status):
                message = (
                    'Certificate generation task initiated for {user} : {course} via track change '
                    + 'with verification status of {status}')
                log.info(
                    message.format(user=user.id,
                                   course=enrollment.course_id,
                                   status=expected_verification_status))
Esempio n. 2
0
def _listen_for_id_verification_status_changed(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Catches a track change signal, determines user status,
    calls _fire_ungenerated_certificate_task for passing grades
    """
    if not auto_certificate_generation_enabled():
        return

    user_enrollments = CourseEnrollment.enrollments_for_user(user=user)

    grade_factory = CourseGradeFactory()
    expected_verification_status = IDVerificationService.user_status(user)
    expected_verification_status = expected_verification_status['status']
    for enrollment in user_enrollments:
        if is_using_certificate_allowlist_and_is_on_allowlist(
                user, enrollment.course_id):
            log.info(
                f'{enrollment.course_id} is using allowlist certificates, and the user {user.id} is on its '
                f'allowlist. Attempt will be made to generate an allowlist certificate. Id verification status '
                f'is {expected_verification_status}')
            generate_allowlist_certificate_task(user, enrollment.course_id)
        elif grade_factory.read(user=user,
                                course=enrollment.course_overview).passed:
            if _fire_ungenerated_certificate_task(
                    user, enrollment.course_id, expected_verification_status):
                message = (
                    u'Certificate generation task initiated for {user} : {course} via track change '
                    + u'with verification status of {status}')
                log.info(
                    message.format(user=user.id,
                                   course=enrollment.course_id,
                                   status=expected_verification_status))
    def _set_email_expiry_date(self, verification, user, email_config):
        """
        If already DEFAULT Number of emails are sent, then verify that user is enrolled in at least
        one verified course run for which the course has not ended else stop sending emails

        Setting email_expiry_date to None will prevent from sending any emails in future to the learner

        :param user: User for which course enrollments will be fetched
        :param email_config: Contains configurations like resend_days and default_emails value
        """
        send_expiry_email_again = True
        email_duration = email_config['resend_days'] * (
            email_config['default_emails'] - 1)
        days_since_expiry = (now() - verification.expiration_datetime).days

        if days_since_expiry >= email_duration:
            send_expiry_email_again = False

            enrollments = CourseEnrollment.enrollments_for_user(user=user)
            for enrollment in enrollments:
                if CourseMode.VERIFIED == enrollment.mode and not enrollment.course.has_ended(
                ):
                    send_expiry_email_again = True
                    break

        verification_qs = SoftwareSecurePhotoVerification.objects.filter(
            pk=verification.pk)
        email_date = now().replace(
            hour=0, minute=0, second=0,
            microsecond=0) if send_expiry_email_again else None
        verification_qs.update(expiry_email_date=email_date)
Esempio n. 4
0
    def __init__(self, site, user, enrollments=None, uuid=None, mobile_only=False):
        self.site = site
        self.user = user
        self.mobile_only = mobile_only

        self.enrollments = enrollments or list(CourseEnrollment.enrollments_for_user(self.user))
        self.enrollments.sort(key=lambda e: e.created, reverse=True)

        self.enrolled_run_modes = {}
        self.course_run_ids = []
        for enrollment in self.enrollments:
            # enrollment.course_id is really a CourseKey (╯ಠ_ಠ)╯︵ ┻━┻
            enrollment_id = str(enrollment.course_id)
            mode = enrollment.mode
            if mode == CourseMode.NO_ID_PROFESSIONAL_MODE:
                mode = CourseMode.PROFESSIONAL
            self.enrolled_run_modes[enrollment_id] = mode
            # We can't use dict.keys() for this because the course run ids need to be ordered
            self.course_run_ids.append(enrollment_id)

        self.entitlements = list(CourseEntitlement.unexpired_entitlements_for_user(self.user))
        self.course_uuids = [str(entitlement.course_uuid) for entitlement in self.entitlements]

        if uuid:
            self.programs = [get_programs(uuid=uuid)]
        else:
            self.programs = attach_program_detail_url(get_programs(self.site), self.mobile_only)
Esempio n. 5
0
    def test_generate_enrollment_status_hash(self):
        """ Verify the method returns a hash of a user's current enrollments. """
        # Return None for anonymous users
        self.assertIsNone(
            CourseEnrollment.generate_enrollment_status_hash(AnonymousUser()))

        # No enrollments
        expected = hashlib.md5(self.user.username.encode('utf-8')).hexdigest()  # lint-amnesty, pylint: disable=no-member
        self.assertEqual(
            CourseEnrollment.generate_enrollment_status_hash(self.user),
            expected)
        self.assert_enrollment_status_hash_cached(self.user, expected)

        # No active enrollments
        enrollment_mode = 'verified'
        course_id = self.course.id  # pylint: disable=no-member
        enrollment = CourseEnrollmentFactory.create(user=self.user,
                                                    course_id=course_id,
                                                    mode=enrollment_mode,
                                                    is_active=False)
        self.assertEqual(
            CourseEnrollment.generate_enrollment_status_hash(self.user),
            expected)
        self.assert_enrollment_status_hash_cached(self.user, expected)

        # One active enrollment
        enrollment.is_active = True
        enrollment.save()
        expected = '{username}&{course_id}={mode}'.format(
            username=self.user.username,
            course_id=str(course_id).lower(),
            mode=enrollment_mode.lower())
        expected = hashlib.md5(expected.encode('utf-8')).hexdigest()
        self.assertEqual(
            CourseEnrollment.generate_enrollment_status_hash(self.user),
            expected)
        self.assert_enrollment_status_hash_cached(self.user, expected)

        # Multiple enrollments
        CourseEnrollmentFactory.create(user=self.user)
        enrollments = CourseEnrollment.enrollments_for_user(
            self.user).order_by(Lower('course_id'))
        hash_elements = [self.user.username]
        hash_elements += [
            '{course_id}={mode}'.format(
                course_id=str(enrollment.course_id).lower(),
                mode=enrollment.mode.lower()) for enrollment in enrollments
        ]
        expected = hashlib.md5(
            '&'.join(hash_elements).encode('utf-8')).hexdigest()
        self.assertEqual(
            CourseEnrollment.generate_enrollment_status_hash(self.user),
            expected)
        self.assert_enrollment_status_hash_cached(self.user, expected)
Esempio n. 6
0
def show_load_all_courses_link(user, course_limit, course_enrollments):
    """
    By default dashboard will show limited courses based on the course limit
    set in configuration.

    A link would be provided provided at the bottom to load all the courses if there are any courses.
    """

    if course_limit is None:
        return False

    total_enrollments = CourseEnrollment.enrollments_for_user(user).count()
    return len(course_enrollments) < total_enrollments
Esempio n. 7
0
    def get_serialized_data(self, api_version):
        '''
        Return data from CourseEnrollmentSerializer
        '''
        if api_version == API_V05:
            serializer = CourseEnrollmentSerializerv05
        else:
            serializer = CourseEnrollmentSerializer

        return serializer(
            CourseEnrollment.enrollments_for_user(self.user)[0],
            context={'request': self.request, 'api_version': api_version},
        ).data
def _listen_for_id_verification_status_changed(sender, user, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user's id verification status has changed.
    """
    if not auto_certificate_generation_enabled():
        return

    user_enrollments = CourseEnrollment.enrollments_for_user(user=user)
    expected_verification_status = IDVerificationService.user_status(user)
    expected_verification_status = expected_verification_status['status']

    for enrollment in user_enrollments:
        log.info(
            f'Attempt will be made to generate a course certificate for {user.id} : {enrollment.course_id}. Id '
            f'verification status is {expected_verification_status}')
        generate_certificate_task(user, enrollment.course_id)
Esempio n. 9
0
    def _get_enterprise_course_enrollments(self, enterprise_uuid, user):
        """
        Return only enterprise enrollments for a user.
        """
        enterprise_enrollment_course_ids = list(
            EnterpriseCourseEnrollment.objects.filter(
                enterprise_customer_user__user_id=user.id,
                enterprise_customer_user__enterprise_customer__uuid=
                enterprise_uuid,
            ).values_list('course_id', flat=True))

        course_enrollments = CourseEnrollment.enrollments_for_user(
            user).filter(course_id__in=enterprise_enrollment_course_ids
                         ).select_related('course')

        return list(course_enrollments)
Esempio n. 10
0
def get_enrollment_course_names_and_short_ids_by_user(user):
    """
    Get comma separated course names and short ids, for all enrolled courses.

    Args:
        user (user object): User model object

    Returns:
        Tuple of comma separated course short ids and course names
    """
    enrollments = CourseEnrollment.enrollments_for_user(user).values_list(
        'course__course_meta__short_id', 'course__display_name'
    )

    if not enrollments:
        return '', ''

    enrollments = sorted(enrollments, key=lambda enrollment: enrollment[0])
    short_ids, display_names = zip(*enrollments)
    return ','.join(map(str, short_ids)), ','.join(display_names)
Esempio n. 11
0
def check_verify_status_by_course(user, course_enrollments):
    """
    Determine the per-course verification statuses for a given user.

    The possible statuses are:
        * VERIFY_STATUS_NEED_TO_VERIFY: The student has not yet submitted photos for verification.
        * VERIFY_STATUS_SUBMITTED: The student has submitted photos for verification,
          but has have not yet been approved.
        * VERIFY_STATUS_RESUBMITTED: The student has re-submitted photos for re-verification while
          they still have an active but expiring ID verification
        * VERIFY_STATUS_APPROVED: The student has been successfully verified.
        * VERIFY_STATUS_MISSED_DEADLINE: The student did not submit photos within the course's deadline.
        * VERIFY_STATUS_NEED_TO_REVERIFY: The student has an active verification, but it is
            set to expire before the verification deadline for the course.

    It is is also possible that a course does NOT have a verification status if:
        * The user is not enrolled in a verified mode, meaning that the user didn't pay.
        * The course does not offer a verified mode.
        * The user submitted photos but an error occurred while verifying them.
        * The user submitted photos but the verification was denied.

    In the last two cases, we rely on messages in the sidebar rather than displaying
    messages for each course.

    Arguments:
        user (User): The currently logged-in user.
        course_enrollments (list[CourseEnrollment]): The courses the user is enrolled in.

    Returns:
        dict: Mapping of course keys verification status dictionaries.
            If no verification status is applicable to a course, it will not
            be included in the dictionary.
            The dictionaries have these keys:
                * status (str): One of the enumerated status codes.
                * days_until_deadline (int): Number of days until the verification deadline.
                * verification_good_until (str): Date string for the verification expiration date.

    """
    status_by_course = {}

    # Retrieve all verifications for the user, sorted in descending
    # order by submission datetime
    verifications = IDVerificationService.verifications_for_user(user)

    # Check whether the user has an active or pending verification attempt
    has_active_or_pending = IDVerificationService.user_has_valid_or_pending(
        user)

    # Retrieve expiration_datetime of most recent approved verification
    expiration_datetime = IDVerificationService.get_expiration_datetime(
        user, ['approved'])
    verification_expiring_soon = is_verification_expiring_soon(
        expiration_datetime)

    # Retrieve verification deadlines for the enrolled courses
    course_deadlines = VerificationDeadline.deadlines_for_enrollments(
        CourseEnrollment.enrollments_for_user(user))
    recent_verification_datetime = None

    for enrollment in course_enrollments:

        # If the user hasn't enrolled as verified, then the course
        # won't display state related to its verification status.
        if enrollment.mode in CourseMode.VERIFIED_MODES:

            # Retrieve the verification deadline associated with the course.
            # This could be None if the course doesn't have a deadline.
            deadline = course_deadlines.get(enrollment.course_id)

            relevant_verification = verification_for_datetime(
                deadline, verifications)

            # Picking the max verification datetime on each iteration only with approved status
            if relevant_verification is not None and relevant_verification.status == "approved":
                recent_verification_datetime = max(
                    recent_verification_datetime
                    if recent_verification_datetime is not None else
                    relevant_verification.expiration_datetime,
                    relevant_verification.expiration_datetime)

            # By default, don't show any status related to verification
            status = None
            should_display = True

            # Check whether the user was approved or is awaiting approval
            if relevant_verification is not None:
                should_display = relevant_verification.should_display_status_to_user(
                )

                if relevant_verification.status == "approved":
                    if verification_expiring_soon:
                        status = VERIFY_STATUS_NEED_TO_REVERIFY
                    else:
                        status = VERIFY_STATUS_APPROVED
                elif relevant_verification.status == "submitted":
                    if verification_expiring_soon:
                        status = VERIFY_STATUS_RESUBMITTED
                    else:
                        status = VERIFY_STATUS_SUBMITTED

            # If the user didn't submit at all, then tell them they need to verify
            # If the deadline has already passed, then tell them they missed it.
            # If they submitted but something went wrong (error or denied),
            # then don't show any messaging next to the course, since we already
            # show messages related to this on the left sidebar.
            submitted = (relevant_verification is not None
                         and relevant_verification.status
                         not in ["created", "ready"])
            if status is None and not submitted:
                if deadline is None or deadline > datetime.now(UTC):
                    if IDVerificationService.user_is_verified(
                            user) and verification_expiring_soon:
                        # The user has an active verification, but the verification
                        # is set to expire within "EXPIRING_SOON_WINDOW" days (default is 4 weeks).
                        # Tell the student to reverify.
                        status = VERIFY_STATUS_NEED_TO_REVERIFY
                    elif not IDVerificationService.user_is_verified(user):
                        status = VERIFY_STATUS_NEED_TO_VERIFY
                else:
                    # If a user currently has an active or pending verification,
                    # then they may have submitted an additional attempt after
                    # the verification deadline passed.  This can occur,
                    # for example, when the support team asks a student
                    # to reverify after the deadline so they can receive
                    # a verified certificate.
                    # In this case, we still want to show them as "verified"
                    # on the dashboard.
                    if has_active_or_pending:
                        status = VERIFY_STATUS_APPROVED

                    # Otherwise, the student missed the deadline, so show
                    # them as "honor" (the kind of certificate they will receive).
                    else:
                        status = VERIFY_STATUS_MISSED_DEADLINE

            # Set the status for the course only if we're displaying some kind of message
            # Otherwise, leave the course out of the dictionary.
            if status is not None:
                days_until_deadline = None

                now = datetime.now(UTC)
                if deadline is not None and deadline > now:
                    days_until_deadline = (deadline - now).days

                status_by_course[enrollment.course_id] = {
                    'status': status,
                    'days_until_deadline': days_until_deadline,
                    'should_display': should_display,
                }

    if recent_verification_datetime:
        for key, value in iteritems(status_by_course):  # pylint: disable=unused-variable
            status_by_course[key][
                'verification_good_until'] = recent_verification_datetime.strftime(
                    "%m/%d/%Y")

    return status_by_course
Esempio n. 12
0
 def test_no_upgrade_message_if_not_enrolled(self):
     self.assertEqual(len(CourseEnrollment.enrollments_for_user(self.user)),
                      0)
     self.assert_upgrade_message_not_displayed()
Esempio n. 13
0
 def _enrollments_for_user(self, user):
     """ Return the specified user's course enrollments """
     if user not in self._user_enrollments:
         self._user_enrollments[user] = CourseEnrollment.enrollments_for_user(user)
     return self._user_enrollments[user]