Esempio n. 1
0
    def test_add_entitlement_and_upgrade_audit_enrollment_with_dynamic_deadline(
            self, mock_get_course_runs):
        """
        Verify that if an entitlement is added for a user, if the user has one upgradeable enrollment
        that enrollment is upgraded to the mode of the entitlement and linked to the entitlement regardless of
        dynamic upgrade deadline being set.
        """
        DynamicUpgradeDeadlineConfiguration.objects.create(enabled=True)
        course = CourseFactory.create(self_paced=True)
        course_uuid = uuid.uuid4()
        course_mode = CourseModeFactory(
            course_id=course.id,
            mode_slug=CourseMode.VERIFIED,
            # This must be in the future to ensure it is returned by downstream code.
            expiration_datetime=now() + timedelta(days=1))

        # Set up Entitlement
        entitlement_data = self._get_data_set(self.user, str(course_uuid))
        mock_get_course_runs.return_value = [{'key': str(course.id)}]

        # Add an audit course enrollment for user.
        enrollment = CourseEnrollment.enroll(self.user,
                                             course.id,
                                             mode=CourseMode.AUDIT)

        # Set an upgrade schedule so that dynamic upgrade deadlines are used
        ScheduleFactory.create(
            enrollment=enrollment,
            upgrade_deadline=course_mode.expiration_datetime +
            timedelta(days=-3))

        # The upgrade should complete and ignore the deadline
        response = self.client.post(
            self.entitlements_list_url,
            data=json.dumps(entitlement_data),
            content_type='application/json',
        )
        assert response.status_code == 201
        results = response.data

        course_entitlement = CourseEntitlement.objects.get(
            user=self.user, course_uuid=course_uuid)
        # Assert that enrollment mode is now verified
        enrollment_mode = CourseEnrollment.enrollment_mode_for_user(
            self.user, course.id)[0]
        assert enrollment_mode == course_entitlement.mode
        assert course_entitlement.enrollment_course_run == enrollment
        assert results == CourseEntitlementSerializer(course_entitlement).data
Esempio n. 2
0
    def invalidate(self, mode=None, source=None):
        """
        Invalidate Generated Certificate by marking it 'unavailable'. For additional information see the
        `_revoke_certificate()` function.

        Args:
            mode (String) - learner's current enrollment mode. May be none as the caller likely does not need to
                evaluate the mode before deciding to invalidate the cert.
            source (String) - source requesting invalidation of the certificate for tracking purposes
        """
        if not mode:
            mode, __ = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)

        log.info(f'Marking certificate as unavailable for {self.user.id} : {self.course_id} with mode {mode} from '
                 f'source {source}')
        self._revoke_certificate(status=CertificateStatuses.unavailable, mode=mode, source=source)
Esempio n. 3
0
def _generate_certificate(user, course_key):
    """
    Generate a certificate for this user, in this course run.
    """
    # Retrieve the existing certificate for the learner if it exists
    existing_certificate = GeneratedCertificate.certificate_for_student(
        user, course_key)

    profile = UserProfile.objects.get(user=user)
    profile_name = profile.name

    course = modulestore().get_course(course_key, depth=0)
    course_grade = CourseGradeFactory().read(user, course)
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)

    # Retain the `verify_uuid` from an existing certificate if possible, this will make it possible for the learner to
    # keep the existing URL to their certificate
    if existing_certificate and existing_certificate.verify_uuid:
        uuid = existing_certificate.verify_uuid
    else:
        uuid = uuid4().hex

    cert, created = GeneratedCertificate.objects.update_or_create(
        user=user,
        course_id=course_key,
        defaults={
            'user': user,
            'course_id': course_key,
            'mode': enrollment_mode,
            'name': profile_name,
            'status': CertificateStatuses.downloadable,
            'grade': course_grade.percent,
            'download_url': '',
            'key': '',
            'verify_uuid': uuid,
            'error_reason': ''
        })

    if created:
        created_msg = 'Certificate was created.'
    else:
        created_msg = 'Certificate already existed and was updated.'
    log.info(
        f'Generated certificate with status {cert.status} for {user.id} : {course_key}. {created_msg}'
    )
    return cert
Esempio n. 4
0
    def _attach_course_run_upgrade_url(self, run_mode):
        required_mode_slug = run_mode['type']
        enrolled_mode_slug, _ = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_run_key)
        is_mode_mismatch = required_mode_slug != enrolled_mode_slug
        is_upgrade_required = is_mode_mismatch and CourseEnrollment.is_enrolled(self.user, self.course_run_key)

        if is_upgrade_required:
            # Requires that the ecommerce service be in use.
            required_mode = CourseMode.mode_for_course(self.course_run_key, required_mode_slug)
            ecommerce = EcommerceService()
            sku = getattr(required_mode, 'sku', None)
            if ecommerce.is_enabled(self.user) and sku:
                run_mode['upgrade_url'] = ecommerce.get_checkout_page_url(required_mode.sku)
            else:
                run_mode['upgrade_url'] = None
        else:
            run_mode['upgrade_url'] = None
Esempio n. 5
0
def _can_generate_certificate_common(user, course_key):
    """
    Check if a course certificate can be generated (created if it doesn't already exist, or updated if it does
    exist) for this user, in this course run.

    This method contains checks that are common to both allowlist and V2 regular course certificates.
    """
    if CertificateInvalidation.has_certificate_invalidation(user, course_key):
        # The invalidation list prevents certificate generation
        log.info(
            f'{user.id} : {course_key} is on the certificate invalidation list. Certificate cannot be generated.'
        )
        return False

    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)
    if enrollment_mode is None:
        log.info(
            f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.'
        )
        return False

    if not modes_api.is_eligible_for_certificate(enrollment_mode):
        log.info(
            f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for a '
            f'certificate. Certificate cannot be generated.')
        return False

    if not IDVerificationService.user_is_verified(user):
        log.info(
            f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.'
        )
        return False

    if not _can_generate_certificate_for_status(user, course_key):
        return False

    course = _get_course(course_key)
    if not has_html_certificates_enabled(course):
        log.info(
            f'{course_key} does not have HTML certificates enabled. Certificate cannot be generated for '
            f'{user.id}.')
        return False

    return True
Esempio n. 6
0
    def get_group_for_user(cls, course_key, user, user_partition, **kwargs):  # pylint: disable=unused-argument
        """
        Returns the Group from the specified user partition to which the user
        is assigned, via enrollment mode. If a user is in a Credit mode, the Verified or
        Professional mode for the course is returned instead.

        If a course is using the Verified Track Cohorting pilot feature, this method
        returns None regardless of the user's enrollment mode.
        """
        if is_course_using_cohort_instead(course_key):
            return None

        # First, check if we have to deal with masquerading.
        # If the current user is masquerading as a specific student, use the
        # same logic as normal to return that student's group. If the current
        # user is masquerading as a generic student in a specific group, then
        # return that group.
        if get_course_masquerade(
                user, course_key) and not is_masquerading_as_specific_student(
                    user, course_key):
            return get_masquerading_user_group(course_key, user,
                                               user_partition)

        mode_slug, is_active = CourseEnrollment.enrollment_mode_for_user(
            user, course_key)
        if mode_slug and is_active:
            course_mode = CourseMode.mode_for_course(
                course_key,
                mode_slug,
                modes=CourseMode.modes_for_course(course_key,
                                                  include_expired=True,
                                                  only_selectable=False),
            )
            if course_mode and CourseMode.is_credit_mode(course_mode):
                # We want the verified track even if the upgrade deadline has passed, since we
                # are determining what content to show the user, not whether the user can enroll
                # in the verified track.
                course_mode = CourseMode.verified_mode_for_course(
                    course_key, include_expired=True)
            if not course_mode:
                course_mode = CourseMode.DEFAULT_MODE
            return Group(ENROLLMENT_GROUP_IDS[course_mode.slug]["id"],
                         str(course_mode.name))
        else:
            return None
Esempio n. 7
0
    def test_enroll(self, course_modes, enrollment_mode):
        # Create the course modes (if any) required for this test case
        self._create_course_modes(course_modes)
        enrollment = data.create_course_enrollment(
            self.user.username, six.text_type(self.course.id), enrollment_mode,
            True)

        assert CourseEnrollment.is_enrolled(self.user, self.course.id)
        course_mode, is_active = CourseEnrollment.enrollment_mode_for_user(
            self.user, self.course.id)
        assert is_active
        assert course_mode == enrollment_mode

        # Confirm the returned enrollment and the data match up.
        assert course_mode == enrollment['mode']
        assert is_active == enrollment['is_active']
        assert self.course.display_name_with_default == enrollment[
            'course_details']['course_name']
Esempio n. 8
0
 def validate_user_enrollment_is_valid(self, user, supplied_enrollment):
     """
     Invalid states:
         user not enrolled in course
         enrollment mode from csv doesn't match actual user enrollment
     """
     actual_enrollment_mode, user_enrolled = CourseEnrollment.enrollment_mode_for_user(
         user, self.course.id)
     if not user_enrolled:
         self.validation_errors.append('User ' + user.username +
                                       ' is not enrolled in this course.')
         return False
     if actual_enrollment_mode != supplied_enrollment.strip():
         self.validation_errors.append('User ' + user.username +
                                       ' enrollment mismatch.')
         return False
     self.user_to_actual_enrollment_mode[user.id] = actual_enrollment_mode
     return True
Esempio n. 9
0
def _can_set_cert_status_common(user, course_key):
    """
    Determine whether we can set a custom (non-downloadable) cert status
    """
    if _is_cert_downloadable(user, course_key):
        return False

    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_key)
    if enrollment_mode is None:
        return False

    if not modes_api.is_eligible_for_certificate(enrollment_mode):
        return False

    course_overview = get_course_overview(course_key)
    if not has_html_certificates_enabled_from_course_overview(course_overview):
        return False

    return True
Esempio n. 10
0
def _generate_certificate(user, course_id):
    """
    Generate a certificate for this user, in this course run.
    """
    profile = UserProfile.objects.get(user=user)
    profile_name = profile.name

    course = modulestore().get_course(course_id, depth=0)
    course_grade = CourseGradeFactory().read(user, course)
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_id)
    key = make_hashkey(random.random())
    uuid = uuid4().hex

    cert, created = GeneratedCertificate.objects.update_or_create(
        user=user,
        course_id=course_id,
        defaults={
            'user': user,
            'course_id': course_id,
            'mode': enrollment_mode,
            'name': profile_name,
            'status': CertificateStatuses.downloadable,
            'grade': course_grade.percent,
            'download_url': '',
            'key': key,
            'verify_uuid': uuid
        }
    )

    if created:
        created_msg = 'Certificate was created.'
    else:
        created_msg = 'Certificate already existed and was updated.'
    log.info(
        'Generated certificate with status {status} for {user} : {course}. {created_msg}'.format(
            status=cert.status,
            user=cert.user.id,
            course=cert.course_id,
            created_msg=created_msg
        ))
    return cert
Esempio n. 11
0
    def test_successful_default_enrollment(self):
        # Create the course modes
        for mode in (CourseMode.DEFAULT_MODE_SLUG, 'verified'):
            CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)

        # Enroll the user in the default mode (honor) to emulate
        # automatic enrollment
        params = {
            'enrollment_action': 'enroll',
            'course_id': str(self.course.id)
        }
        self.client.post(reverse('change_enrollment'), params)

        # Explicitly select the honor mode (POST request)
        choose_track_url = reverse('course_modes_choose', args=[str(self.course.id)])
        self.client.post(choose_track_url, self.POST_PARAMS_FOR_COURSE_MODE[CourseMode.DEFAULT_MODE_SLUG])

        # Verify that the user's enrollment remains unchanged
        mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course.id)
        assert mode == CourseMode.DEFAULT_MODE_SLUG
        assert is_active is True
Esempio n. 12
0
 def description(self):
     """
     Returns a description for what experience changes a learner encounters when the course end date passes.
     Note that this currently contains 4 scenarios:
         1. End date is in the future and learner is enrolled in a certificate earning mode
         2. End date is in the future and learner is not enrolled at all or not enrolled
             in a certificate earning mode
         3. End date is in the past
         4. End date does not exist (and now neither does the description)
     """
     if self.date and self.current_time <= self.date:
         mode, is_active = CourseEnrollment.enrollment_mode_for_user(self.user, self.course_id)
         if is_active and CourseMode.is_eligible_for_certificate(mode):
             return _('After this date, the course will be archived, which means you can review the '
                      'course content but can no longer participate in graded assignments or work towards earning '
                      'a certificate.')
         else:
             return _('After the course ends, the course content will be archived and no longer active.')
     elif self.date:
         return _('This course is archived, which means you can review course content but it is no longer active.')
     else:
         return ''
Esempio n. 13
0
    def test_already_enrolled_course_ended(self, mock_get_course_runs):
        """
        Test that already enrolled user can still select a session while
        course has ended but upgrade deadline is in future.
        """
        course_entitlement = CourseEntitlementFactory.create(
            user=self.user, mode=CourseMode.VERIFIED)
        mock_get_course_runs.return_value = self.return_values

        # Setup enrollment period to be in the past
        utc_now = datetime.now(UTC)
        self.course.start = utc_now - timedelta(days=15)
        self.course.end = utc_now - timedelta(days=1)
        self.course = self.update_course(self.course, self.user.id)
        CourseOverview.update_select_courses([self.course.id],
                                             force_update=True)

        CourseEnrollment.enroll(self.user,
                                self.course.id,
                                mode=CourseMode.AUDIT)

        url = reverse(self.ENTITLEMENTS_ENROLLMENT_NAMESPACE,
                      args=[str(course_entitlement.uuid)])

        data = {'course_run_id': str(self.course.id)}
        response = self.client.post(
            url,
            data=json.dumps(data),
            content_type='application/json',
        )
        course_entitlement.refresh_from_db()

        assert response.status_code == 201
        assert CourseEnrollment.is_enrolled(self.user, self.course.id)
        (enrolled_mode, is_active) = CourseEnrollment.enrollment_mode_for_user(
            self.user, self.course.id)
        assert is_active and (enrolled_mode == course_entitlement.mode)
        assert course_entitlement.enrollment_course_run is not None
Esempio n. 14
0
def _listen_for_failing_grade(sender, user, course_id, grade, **kwargs):  # pylint: disable=unused-argument
    """
    Listen for a signal indicating that the user has failed a course run.

    If needed, mark the certificate as notpassing.
    """
    if is_on_certificate_allowlist(user, course_id):
        log.info(
            f'User {user.id} is on the allowlist for {course_id}. The failing grade will not affect the '
            f'certificate.')
        return

    cert = GeneratedCertificate.certificate_for_student(user, course_id)
    if cert is not None:
        if CertificateStatuses.is_passing_status(cert.status):
            enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
                user, course_id)
            cert.mark_notpassing(mode=enrollment_mode,
                                 grade=grade.percent,
                                 source='notpassing_signal')
            log.info(
                f'Certificate marked not passing for {user.id} : {course_id} via failing grade'
            )
Esempio n. 15
0
def certificate_info_for_user(user, course_id, grade, user_is_allowlisted, user_certificate):
    """
    Returns the certificate info for a user for grade report.
    """
    from common.djangoapps.student.models import CourseEnrollment

    certificate_is_delivered = 'N'
    certificate_type = 'N/A'
    status = certificate_status(user_certificate)
    certificate_generated = status['status'] == CertificateStatuses.downloadable
    can_have_certificate = CourseOverview.get_from_id(course_id).may_certify()
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(user, course_id)
    mode_is_verified = enrollment_mode in CourseMode.VERIFIED_MODES
    user_is_verified = grade is not None and mode_is_verified

    eligible_for_certificate = 'Y' if (user_is_allowlisted or user_is_verified or certificate_generated) \
        else 'N'

    if certificate_generated and can_have_certificate:
        certificate_is_delivered = 'Y'
        certificate_type = status['mode']

    return [eligible_for_certificate, certificate_is_delivered, certificate_type]
Esempio n. 16
0
    def test_add_entitlement_inactive_audit_enrollment(self, mock_get_course_runs):
        """
        Verify that if an entitlement is added for a user, if the user has an inactive audit enrollment
        that enrollment is NOT upgraded to the mode of the entitlement and linked to the entitlement.
        """
        course_uuid = uuid.uuid4()
        entitlement_data = self._get_data_set(self.user, str(course_uuid))
        mock_get_course_runs.return_value = [{'key': str(self.course.id)}]  # pylint: disable=no-member

        # Add an audit course enrollment for user.
        enrollment = CourseEnrollment.enroll(
            self.user,
            self.course.id,  # pylint: disable=no-member
            mode=CourseMode.AUDIT
        )
        enrollment.update_enrollment(is_active=False)
        response = self.client.post(
            self.entitlements_list_url,
            data=json.dumps(entitlement_data),
            content_type='application/json',
        )
        assert response.status_code == 201
        results = response.data

        course_entitlement = CourseEntitlement.objects.get(
            user=self.user,
            course_uuid=course_uuid
        )
        # Assert that enrollment mode is now verified
        enrollment_mode, enrollment_active = CourseEnrollment.enrollment_mode_for_user(
            self.user,
            self.course.id  # pylint: disable=no-member
        )
        assert enrollment_mode == CourseMode.AUDIT
        assert enrollment_active is False
        assert course_entitlement.enrollment_course_run is None
        assert results == CourseEntitlementSerializer(course_entitlement).data
Esempio n. 17
0
    def _check_enrollment(self, user, course_key):
        """Check whether the user has an active enrollment and has paid.

        If a user is enrolled in a paid course mode, we assume
        that the user has paid.

        Arguments:
            user (User): The user to check.
            course_key (CourseKey): The key of the course to check.

        Returns:
            Tuple `(has_paid, is_active)` indicating whether the user
            has paid and whether the user has an active account.

        """
        enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(user, course_key)
        has_paid = False

        if enrollment_mode is not None and is_active:
            all_modes = CourseMode.modes_for_course_dict(course_key, include_expired=True)
            course_mode = all_modes.get(enrollment_mode)
            has_paid = (course_mode and course_mode.min_price > 0)

        return (has_paid, bool(is_active))
def get_fulfillable_course_runs_for_entitlement(entitlement, course_runs):
    """
    Looks through the list of course runs and returns the course runs that can
    be applied to the entitlement.

    Args:
        entitlement (CourseEntitlement): The CourseEntitlement to which a
        course run is to be applied.
        course_runs (list): List of course run that we would like to apply
        to the entitlement.

    Return:
        list: A list of sessions that a user can apply to the provided entitlement.
    """
    enrollable_sessions = []

    # Only retrieve list of published course runs that can still be enrolled and upgraded
    search_time = datetime.datetime.now(UTC)
    for course_run in course_runs:
        course_id = CourseKey.from_string(course_run.get('key'))
        (user_enrollment_mode,
         is_active) = CourseEnrollment.enrollment_mode_for_user(
             user=entitlement.user, course_id=course_id)
        is_enrolled_in_mode = is_active and (user_enrollment_mode
                                             == entitlement.mode)
        if (is_enrolled_in_mode and entitlement.enrollment_course_run
                and course_id == entitlement.enrollment_course_run.course_id):
            # User is enrolled in the course so we should include it in the list of enrollable sessions always
            # this will ensure it is available for the UI
            enrollable_sessions.append(course_run)
        elif not is_enrolled_in_mode and is_course_run_entitlement_fulfillable(
                course_id, entitlement, search_time):
            enrollable_sessions.append(course_run)

    enrollable_sessions.sort(key=lambda session: session.get('start'))
    return enrollable_sessions
Esempio n. 19
0
def fire_ungenerated_certificate_task(user,
                                      course_key,
                                      expected_verification_status=None):
    """
    Helper function to fire certificate generation task.
    Auto-generation of certificates is available for following course modes:
        1- VERIFIED
        2- CREDIT_MODE
        3- PROFESSIONAL
        4- NO_ID_PROFESSIONAL_MODE

    Certificate generation task is fired to either generate a certificate
    when there is no generated certificate for user in a particular course or
    update a certificate if it has 'unverified' status.

    Task is fired to attempt an update to a certificate
    with 'unverified' status as this method is called when a user is
    successfully verified, any certificate associated
    with such user can now be verified.

    NOTE: Purpose of restricting other course modes (HONOR and AUDIT) from auto-generation is to reduce
    traffic to workers.
    """

    message = u'Entered into Ungenerated Certificate task for {user} : {course}'
    log.info(message.format(user=user.id, course=course_key))

    if is_using_certificate_allowlist_and_is_on_allowlist(user, course_key):
        log.info(
            '{course} is using allowlist certificates, and the user {user} is on its allowlist. Attempt will be '
            'made to generate an allowlist certificate.'.format(
                course=course_key, user=user.id))
        generate_allowlist_certificate_task(user, course_key)
        return True

    log.info(
        '{course} is not using allowlist certificates (or user {user} is not on its allowlist). The normal '
        'generation logic will be followed.'.format(course=course_key,
                                                    user=user.id))

    allowed_enrollment_modes_list = [
        CourseMode.VERIFIED,
        CourseMode.CREDIT_MODE,
        CourseMode.PROFESSIONAL,
        CourseMode.NO_ID_PROFESSIONAL_MODE,
        CourseMode.MASTERS,
        CourseMode.EXECUTIVE_EDUCATION,
    ]
    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)
    cert = GeneratedCertificate.certificate_for_student(user, course_key)

    generate_learner_certificate = (
        enrollment_mode in allowed_enrollment_modes_list
        and (cert is None or cert.status == 'unverified'))

    if generate_learner_certificate:
        kwargs = {
            'student': six.text_type(user.id),
            'course_key': six.text_type(course_key)
        }
        if expected_verification_status:
            kwargs['expected_verification_status'] = six.text_type(
                expected_verification_status)
        generate_certificate.apply_async(countdown=CERTIFICATE_DELAY_SECONDS,
                                         kwargs=kwargs)
        return True

    message = u'Certificate Generation task failed for {user} : {course}'
    log.info(message.format(user=user.id, course=course_key))
    return False
Esempio n. 20
0
 def is_allowed(self):
     mode, is_active = CourseEnrollment.enrollment_mode_for_user(
         self.user, self.course_id)
     return (is_active and mode == 'verified' and self.verification_status
             in ('expired', 'none', 'must_reverify')
             and not settings.FEATURES.get('ENABLE_INTEGRITY_SIGNATURE'))
Esempio n. 21
0
def set_due_date_extension(course,
                           unit,
                           student,
                           due_date,
                           actor=None,
                           reason=''):
    """
    Sets a due date extension.

    Raises:
        DashboardError if the unit or extended, due date is invalid or user is
        not enrolled in the course.
    """
    mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user=student, course_id=six.text_type(course.id))
    if not mode:
        raise DashboardError(
            _("Could not find student enrollment in the course."))

    # We normally set dates at the subsection level. But technically dates can be anywhere down the tree (and
    # usually are in self paced courses, where the subsection date gets propagated down).
    # So find all children that we need to set the date on, then set those dates.
    course_dates = api.get_dates_for_course(course.id, user=student)
    blocks_to_set = {
        unit
    }  # always include the requested unit, even if it doesn't appear to have a due date now

    def visit(node):
        """
        Visit a node.  Checks to see if node has a due date and appends to
        `blocks_to_set` if it does.  And recurses into children to search for
        nodes with due dates.
        """
        if (node.location, 'due') in course_dates:
            blocks_to_set.add(node)
        for child in node.get_children():
            visit(child)

    visit(unit)

    for block in blocks_to_set:
        if due_date:
            try:
                api.set_date_for_block(course.id,
                                       block.location,
                                       'due',
                                       due_date,
                                       user=student,
                                       reason=reason,
                                       actor=actor)
            except api.MissingDateError:
                raise DashboardError(
                    _(u"Unit {0} has no due date to extend.").format(
                        unit.location))
            except api.InvalidDateError:
                raise DashboardError(
                    _("An extended due date must be later than the original due date."
                      ))
        else:
            api.set_date_for_block(course.id,
                                   block.location,
                                   'due',
                                   None,
                                   user=student,
                                   reason=reason,
                                   actor=actor)
Esempio n. 22
0
    def get(self, request, course_id, error=None):  # lint-amnesty, pylint: disable=too-many-statements
        """Displays the course mode choice page.

        Args:
            request (`Request`): The Django Request object.
            course_id (unicode): The slash-separated course key.

        Keyword Args:
            error (unicode): If provided, display this error message
                on the page.

        Returns:
            Response

        """
        course_key = CourseKey.from_string(course_id)

        # Check whether the user has access to this course
        # based on country access rules.
        embargo_redirect = embargo_api.redirect_if_blocked(
            course_key,
            user=request.user,
            ip_address=get_client_ip(request)[0],
            url=request.path)
        if embargo_redirect:
            return redirect(embargo_redirect)

        enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(
            request.user, course_key)

        increment('track-selection.{}.{}'.format(
            enrollment_mode, 'active' if is_active else 'inactive'))
        increment('track-selection.views')

        if enrollment_mode is None:
            LOG.info(
                'Rendering track selection for unenrolled user, referred by %s',
                request.META.get('HTTP_REFERER'))

        modes = CourseMode.modes_for_course_dict(course_key)
        ecommerce_service = EcommerceService()

        # We assume that, if 'professional' is one of the modes, it should be the *only* mode.
        # If there are both modes, default to 'no-id-professional'.
        has_enrolled_professional = (
            CourseMode.is_professional_slug(enrollment_mode) and is_active)
        if CourseMode.has_professional_mode(
                modes) and not has_enrolled_professional:
            purchase_workflow = request.GET.get("purchase_workflow", "single")
            redirect_url = IDVerificationService.get_verify_location(
                course_id=course_key)
            if ecommerce_service.is_enabled(request.user):
                professional_mode = modes.get(
                    CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get(
                        CourseMode.PROFESSIONAL)
                if purchase_workflow == "single" and professional_mode.sku:
                    redirect_url = ecommerce_service.get_checkout_page_url(
                        professional_mode.sku)
                if purchase_workflow == "bulk" and professional_mode.bulk_sku:
                    redirect_url = ecommerce_service.get_checkout_page_url(
                        professional_mode.bulk_sku)
            return redirect(redirect_url)

        course = modulestore().get_course(course_key)

        # If there isn't a verified mode available, then there's nothing
        # to do on this page.  Send the user to the dashboard.
        if not CourseMode.has_verified_mode(modes):
            return self._redirect_to_course_or_dashboard(
                course, course_key, request.user)

        # If a user has already paid, redirect them to the dashboard.
        if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES +
                          [CourseMode.NO_ID_PROFESSIONAL_MODE]):
            return self._redirect_to_course_or_dashboard(
                course, course_key, request.user)

        donation_for_course = request.session.get("donation_for_course", {})
        chosen_price = donation_for_course.get(str(course_key), None)

        if CourseEnrollment.is_enrollment_closed(request.user, course):
            locale = to_locale(get_language())
            enrollment_end_date = format_datetime(course.enrollment_end,
                                                  'short',
                                                  locale=locale)
            params = six.moves.urllib.parse.urlencode(
                {'course_closed': enrollment_end_date})
            return redirect('{}?{}'.format(reverse('dashboard'), params))

        # When a credit mode is available, students will be given the option
        # to upgrade from a verified mode to a credit mode at the end of the course.
        # This allows students who have completed photo verification to be eligible
        # for university credit.
        # Since credit isn't one of the selectable options on the track selection page,
        # we need to check *all* available course modes in order to determine whether
        # a credit mode is available.  If so, then we show slightly different messaging
        # for the verified track.
        has_credit_upsell = any(
            CourseMode.is_credit_mode(mode)
            for mode in CourseMode.modes_for_course(course_key,
                                                    only_selectable=False))
        course_id = str(course_key)
        gated_content = ContentTypeGatingConfig.enabled_for_enrollment(
            user=request.user, course_key=course_key)
        context = {
            "course_modes_choose_url":
            reverse("course_modes_choose", kwargs={'course_id': course_id}),
            "modes":
            modes,
            "has_credit_upsell":
            has_credit_upsell,
            "course_name":
            course.display_name_with_default,
            "course_org":
            course.display_org_with_default,
            "course_num":
            course.display_number_with_default,
            "chosen_price":
            chosen_price,
            "error":
            error,
            "responsive":
            True,
            "nav_hidden":
            True,
            "content_gating_enabled":
            gated_content,
            "course_duration_limit_enabled":
            CourseDurationLimitConfig.enabled_for_enrollment(
                request.user, course),
        }
        context.update(
            get_experiment_user_metadata_context(
                course,
                request.user,
            ))

        title_content = ''
        if enrollment_mode:
            title_content = _(
                "Congratulations!  You are now enrolled in {course_name}"
            ).format(course_name=course.display_name_with_default)

        context["title_content"] = title_content

        if "verified" in modes:
            verified_mode = modes["verified"]
            context["suggested_prices"] = [
                decimal.Decimal(x.strip())
                for x in verified_mode.suggested_prices.split(",")
                if x.strip()
            ]
            price_before_discount = verified_mode.min_price
            course_price = price_before_discount
            enterprise_customer = enterprise_customer_for_request(request)
            LOG.info(
                '[e-commerce calculate API] Going to hit the API for user [%s] linked to [%s] enterprise',
                request.user.username,
                enterprise_customer.get('name') if isinstance(
                    enterprise_customer, dict) else None  # Test Purpose
            )
            if enterprise_customer and verified_mode.sku:
                course_price = get_course_final_price(request.user,
                                                      verified_mode.sku,
                                                      price_before_discount)

            context["currency"] = verified_mode.currency.upper()
            context["currency_symbol"] = get_currency_symbol(
                verified_mode.currency.upper())
            context["min_price"] = course_price
            context["verified_name"] = verified_mode.name
            context["verified_description"] = verified_mode.description
            # if course_price is equal to price_before_discount then user doesn't entitle to any discount.
            if course_price != price_before_discount:
                context["price_before_discount"] = price_before_discount

            if verified_mode.sku:
                context[
                    "use_ecommerce_payment_flow"] = ecommerce_service.is_enabled(
                        request.user)
                context[
                    "ecommerce_payment_page"] = ecommerce_service.payment_page_url(
                    )
                context["sku"] = verified_mode.sku
                context["bulk_sku"] = verified_mode.bulk_sku

        context['currency_data'] = []
        if waffle.switch_is_active('local_currency'):
            if 'edx-price-l10n' not in request.COOKIES:
                currency_data = get_currency_data()
                try:
                    context['currency_data'] = json.dumps(currency_data)
                except TypeError:
                    pass

        language = get_language()
        context['track_links'] = get_verified_track_links(language)

        duration = get_user_course_duration(request.user, course)
        deadline = duration and get_user_course_expiration_date(
            request.user, course)
        if deadline:
            formatted_audit_access_date = strftime_localized_html(
                deadline, 'SHORT_DATE')
            context['audit_access_deadline'] = formatted_audit_access_date
        fbe_is_on = deadline and gated_content

        # Route to correct Track Selection page.
        # REV-2133 TODO Value Prop: remove waffle flag after testing is completed
        # and happy path version is ready to be rolled out to all users.
        if VALUE_PROP_TRACK_SELECTION_FLAG.is_enabled():
            if not error:  # TODO: Remove by executing REV-2355
                if not enterprise_customer_for_request(
                        request):  # TODO: Remove by executing REV-2342
                    if fbe_is_on:
                        return render_to_response("course_modes/fbe.html",
                                                  context)
                    else:
                        return render_to_response("course_modes/unfbe.html",
                                                  context)

        # If error or enterprise_customer, failover to old choose.html page
        return render_to_response("course_modes/choose.html", context)
Esempio n. 23
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)

        if not course_home_mfe_progress_tab_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff',
                                              request.user.is_staff)

        _, request.user = setup_masquerade(request,
                                           course_key,
                                           staff_access=has_access(
                                               request.user, 'staff',
                                               course_key),
                                           reset_masquerade_data=True)

        course = get_course_with_access(request.user,
                                        'load',
                                        course_key,
                                        check_if_enrolled=True)

        enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(
            request.user, course_key)

        # The block structure is used for both the course_grade and has_scheduled content fields
        # So it is called upfront and reused for optimization purposes
        collected_block_structure = get_block_structure_manager(
            course_key).get_collected()
        course_grade = CourseGradeFactory().read(
            request.user, collected_block_structure=collected_block_structure)

        # Get has_scheduled_content data
        transformers = BlockStructureTransformers()
        transformers += [start_date.StartDateTransformer()]
        usage_key = collected_block_structure.root_block_usage_key
        course_blocks = get_course_blocks(
            request.user,
            usage_key,
            transformers=transformers,
            collected_block_structure=collected_block_structure,
            include_has_scheduled_content=True)
        has_scheduled_content = course_blocks.get_xblock_field(
            usage_key, 'has_scheduled_content')

        # Get user_has_passing_grade data
        user_has_passing_grade = False
        if not request.user.is_anonymous:
            user_grade = course_grade.percent
            user_has_passing_grade = user_grade >= course.lowest_passing_grade

        descriptor = modulestore().get_course(course_key)
        grading_policy = descriptor.grading_policy
        verification_status = IDVerificationService.user_status(request.user)
        verification_link = None
        if verification_status['status'] is None or verification_status[
                'status'] == 'expired':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        elif verification_status['status'] == 'must_reverify':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        verification_data = {
            'link': verification_link,
            'status': verification_status['status'],
            'status_date': verification_status['status_date'],
        }

        data = {
            'end':
            course.end,
            'user_has_passing_grade':
            user_has_passing_grade,
            'certificate_data':
            get_cert_data(request.user, course, enrollment_mode, course_grade),
            'completion_summary':
            get_course_blocks_completion_summary(course_key, request.user),
            'course_grade':
            course_grade,
            'has_scheduled_content':
            has_scheduled_content,
            'section_scores':
            course_grade.chapter_grades.values(),
            'enrollment_mode':
            enrollment_mode,
            'grading_policy':
            grading_policy,
            'studio_url':
            get_studio_url(course, 'settings/grading'),
            'verification_data':
            verification_data,
        }
        context = self.get_serializer_context()
        context['staff_access'] = bool(
            has_access(request.user, 'staff', course))
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 24
0
    def add_cert(self,
                 student,
                 course_id,
                 forced_grade=None,
                 template_file=None,
                 generate_pdf=True):
        """
        Request a new certificate for a student.

        Arguments:
          student   - User.object
          course_id - courseenrollment.course_id (CourseKey)
          forced_grade - a string indicating a grade parameter to pass with
                         the certificate request. If this is given, grading
                         will be skipped.
          generate_pdf - Boolean should a message be sent in queue to generate certificate PDF

        Will change the certificate status to 'generating' or
        `downloadable` in case of web view certificates.

        The course must not be a CCX.

        Certificate must be in the 'unavailable', 'error',
        'deleted' or 'generating' state.

        If a student has a passing grade or is in the whitelist
        table for the course a request will be made for a new cert.

        If a student does not have a passing grade the status
        will change to status.notpassing

        Returns the newly created certificate instance
        """

        if hasattr(course_id, 'ccx'):
            LOGGER.warning(
                ("Cannot create certificate generation task for user %s "
                 "in the course '%s'; "
                 "certificates are not allowed for CCX courses."), student.id,
                str(course_id))
            return None

        valid_statuses = [
            status.generating,
            status.unavailable,
            status.deleted,
            status.error,
            status.notpassing,
            status.downloadable,
            status.auditing,
            status.audit_passing,
            status.audit_notpassing,
            status.unverified,
        ]

        cert_status_dict = certificate_status_for_student(student, course_id)
        cert_status = cert_status_dict.get('status')
        download_url = cert_status_dict.get('download_url')
        cert = None
        if download_url:
            self._log_pdf_cert_generation_discontinued_warning(
                student.id, course_id, cert_status, download_url)
            return None

        if cert_status not in valid_statuses:
            LOGGER.warning(
                ("Cannot create certificate generation task for user %s "
                 "in the course '%s'; "
                 "the certificate status '%s' is not one of %s."), student.id,
                str(course_id), cert_status, str(valid_statuses))
            return None

        profile = UserProfile.objects.get(user=student)
        profile_name = profile.name

        # Needed for access control in grading.
        self.request.user = student
        self.request.session = {}

        is_whitelisted = self.whitelist.filter(user=student,
                                               course_id=course_id,
                                               whitelist=True).exists()
        course_grade = CourseGradeFactory().read(student, course_key=course_id)
        enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
            student, course_id)
        mode_is_verified = enrollment_mode in GeneratedCertificate.VERIFIED_CERTS_MODES
        user_is_verified = IDVerificationService.user_is_verified(student)
        cert_mode = enrollment_mode

        is_eligible_for_certificate = modes_api.is_eligible_for_certificate(
            enrollment_mode, cert_status)
        if is_whitelisted and not is_eligible_for_certificate:
            # check if audit certificates are enabled for audit mode
            is_eligible_for_certificate = enrollment_mode != CourseMode.AUDIT or \
                not settings.FEATURES['DISABLE_AUDIT_CERTIFICATES']

        unverified = False
        # For credit mode generate verified certificate
        if cert_mode in (CourseMode.CREDIT_MODE, CourseMode.MASTERS):
            cert_mode = CourseMode.VERIFIED

        if template_file is not None:
            template_pdf = template_file
        elif mode_is_verified and user_is_verified:
            template_pdf = "certificate-template-{id.org}-{id.course}-verified.pdf".format(
                id=course_id)
        elif mode_is_verified and not user_is_verified:
            template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(
                id=course_id)
            if CourseMode.mode_for_course(course_id, CourseMode.HONOR):
                cert_mode = GeneratedCertificate.MODES.honor
            else:
                unverified = True
        else:
            # honor code and audit students
            template_pdf = "certificate-template-{id.org}-{id.course}.pdf".format(
                id=course_id)

        LOGGER.info((
            "Certificate generated for student %s in the course: %s with template: %s. "
            "given template: %s, "
            "user is verified: %s, "
            "mode is verified: %s,"
            "generate_pdf is: %s"), student.username, str(course_id),
                    template_pdf, template_file, user_is_verified,
                    mode_is_verified, generate_pdf)
        cert, __ = GeneratedCertificate.objects.get_or_create(
            user=student, course_id=course_id)

        cert.mode = cert_mode
        cert.user = student
        cert.grade = course_grade.percent
        cert.course_id = course_id
        cert.name = profile_name
        cert.download_url = ''

        # Strip HTML from grade range label
        grade_contents = forced_grade or course_grade.letter_grade
        passing = False
        try:
            grade_contents = lxml.html.fromstring(
                grade_contents).text_content()
            passing = True
        except (TypeError, XMLSyntaxError, ParserError) as exc:
            LOGGER.info(("Could not retrieve grade for student %s "
                         "in the course '%s' "
                         "because an exception occurred while parsing the "
                         "grade contents '%s' as HTML. "
                         "The exception was: '%s'"), student.id,
                        str(course_id), grade_contents, str(exc))

        # Check if the student is whitelisted
        if is_whitelisted:
            LOGGER.info("Student %s is whitelisted in '%s'", student.id,
                        str(course_id))
            passing = True

        # If this user's enrollment is not eligible to receive a
        # certificate, mark it as such for reporting and
        # analytics. Only do this if the certificate is new, or
        # already marked as ineligible -- we don't want to mark
        # existing audit certs as ineligible.
        cutoff = settings.AUDIT_CERT_CUTOFF_DATE
        if (cutoff and cert.created_date >= cutoff
            ) and not is_eligible_for_certificate:
            cert.status = status.audit_passing if passing else status.audit_notpassing
            cert.save()
            LOGGER.info(
                "Student %s with enrollment mode %s is not eligible for a certificate.",
                student.id, enrollment_mode)
            return cert
        # If they are not passing, short-circuit and don't generate cert
        elif not passing:
            cert.status = status.notpassing
            cert.save()

            LOGGER.info(
                ("Student %s does not have a grade for '%s', "
                 "so their certificate status has been set to '%s'. "
                 "No certificate generation task was sent to the XQueue."),
                student.id, str(course_id), cert.status)
            return cert

        if unverified:
            cert.status = status.unverified
            cert.save()
            LOGGER.info(
                ("User %s has a verified enrollment in course %s "
                 "but is missing ID verification. "
                 "Certificate status has been set to unverified"),
                student.id,
                str(course_id),
            )
            return cert

        # Finally, generate the certificate and send it off.
        return self._generate_cert(cert, student, grade_contents, template_pdf,
                                   generate_pdf)
Esempio n. 25
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff',
                                              request.user.is_staff)

        _, request.user = setup_masquerade(request,
                                           course_key,
                                           staff_access=has_access(
                                               request.user, 'staff',
                                               course_key),
                                           reset_masquerade_data=True)

        user_timezone_locale = user_timezone_locale_prefs(request)
        user_timezone = user_timezone_locale['user_timezone']

        transformers = BlockStructureTransformers()
        transformers += course_blocks_api.get_course_block_access_transformers(
            request.user)
        transformers += [
            BlocksAPITransformer(None, None, depth=3),
        ]
        course = get_course_with_access(request.user,
                                        'load',
                                        course_key,
                                        check_if_enrolled=True)

        enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(
            request.user, course_key)

        course_grade = CourseGradeFactory().read(request.user, course)
        courseware_summary = course_grade.chapter_grades.values()

        verification_status = IDVerificationService.user_status(request.user)
        verification_link = None
        if verification_status['status'] is None or verification_status[
                'status'] == 'expired':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        elif verification_status['status'] == 'must_reverify':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        verification_data = {
            'link': verification_link,
            'status': verification_status['status'],
            'status_date': verification_status['status_date'],
        }

        data = {
            'certificate_data':
            get_cert_data(request.user, course, enrollment_mode, course_grade),
            'courseware_summary':
            courseware_summary,
            'credit_course_requirements':
            credit_course_requirements(course_key, request.user),
            'credit_support_url':
            CREDIT_SUPPORT_URL,
            'enrollment_mode':
            enrollment_mode,
            'studio_url':
            get_studio_url(course, 'settings/grading'),
            'user_timezone':
            user_timezone,
            'verification_data':
            verification_data,
        }
        context = self.get_serializer_context()
        context['staff_access'] = bool(
            has_access(request.user, 'staff', course))
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 26
0
def set_credit_requirement_status(user,
                                  course_key,
                                  req_namespace,
                                  req_name,
                                  status="satisfied",
                                  reason=None):
    """
    Update the user's requirement status.

    This will record whether the user satisfied or failed a particular requirement
    in a course.  If the user has satisfied all requirements, the user will be marked
    as eligible for credit in the course.

    Args:
        user(User): User object to set credit requirement for.
        course_key (CourseKey): Identifier for the course associated with the requirement.
        req_namespace (str): Namespace of the requirement (e.g. "grade" or "reverification")
        req_name (str): Name of the requirement (e.g. "grade" or the location of the ICRV XBlock)

    Keyword Arguments:
        status (str): Status of the requirement (either "satisfied" or "failed")
        reason (dict): Reason of the status
    """
    # Check whether user has credit eligible enrollment.
    enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)
    has_credit_eligible_enrollment = (
        CourseMode.is_credit_eligible_slug(enrollment_mode) and is_active)

    # Refuse to set status of requirement if the user enrollment is not credit eligible.
    if not has_credit_eligible_enrollment:
        return

    # Do not allow students who have requested credit to change their eligibility
    if CreditRequest.get_user_request_status(user.username, course_key):
        log.info(
            u'Refusing to set status of requirement with namespace "%s" and name "%s" because the '
            u'user "%s" has already requested credit for the course "%s".',
            req_namespace, req_name, user.username, course_key)
        return

    # Do not allow a student who has earned eligibility to un-earn eligibility
    eligible_before_update = CreditEligibility.is_user_eligible_for_credit(
        course_key, user.username)
    if eligible_before_update and status == 'failed':
        log.info(
            u'Refusing to set status of requirement with namespace "%s" and name "%s" to "failed" because the '
            u'user "%s" is already eligible for credit in the course "%s".',
            req_namespace, req_name, user.username, course_key)
        return

    # Retrieve all credit requirements for the course
    # We retrieve all of them to avoid making a second query later when
    # we need to check whether all requirements have been satisfied.
    reqs = CreditRequirement.get_course_requirements(course_key)

    # Find the requirement we're trying to set
    req_to_update = next(
        (req for req in reqs
         if req.namespace == req_namespace and req.name == req_name), None)

    # If we can't find the requirement, then the most likely explanation
    # is that there was a lag updating the credit requirements after the course
    # was published.  We *could* attempt to create the requirement here,
    # but that could cause serious performance issues if many users attempt to
    # lock the row at the same time.
    # Instead, we skip updating the requirement and log an error.
    if req_to_update is None:
        log.error(
            (u'Could not update credit requirement in course "%s" '
             u'with namespace "%s" and name "%s" '
             u'because the requirement does not exist. '
             u'The user "%s" should have had their status updated to "%s".'),
            six.text_type(course_key), req_namespace, req_name, user.username,
            status)
        return

    # Update the requirement status
    CreditRequirementStatus.add_or_update_requirement_status(user.username,
                                                             req_to_update,
                                                             status=status,
                                                             reason=reason)

    # If we're marking this requirement as "satisfied", there's a chance that the user has met all eligibility
    # requirements, and should be notified. However, if the user was already eligible, do not send another notification.
    if status == "satisfied" and not eligible_before_update:
        is_eligible, eligibility_record_created = CreditEligibility.update_eligibility(
            reqs, user.username, course_key)
        if eligibility_record_created and is_eligible:
            try:
                send_credit_notifications(user.username, course_key)
            except Exception:  # pylint: disable=broad-except
                log.exception("Error sending email")
Esempio n. 27
0
    def get(self, request, *args, **kwargs):
        course_key_string = kwargs.get('course_key_string')
        course_key = CourseKey.from_string(course_key_string)

        if not course_home_mfe_progress_tab_is_active(course_key):
            raise Http404

        # Enable NR tracing for this view based on course
        monitoring_utils.set_custom_attribute('course_id', course_key_string)
        monitoring_utils.set_custom_attribute('user_id', request.user.id)
        monitoring_utils.set_custom_attribute('is_staff',
                                              request.user.is_staff)

        _, request.user = setup_masquerade(request,
                                           course_key,
                                           staff_access=has_access(
                                               request.user, 'staff',
                                               course_key),
                                           reset_masquerade_data=True)

        course = get_course_with_access(request.user,
                                        'load',
                                        course_key,
                                        check_if_enrolled=True)

        enrollment_mode, _ = CourseEnrollment.enrollment_mode_for_user(
            request.user, course_key)

        course_grade = CourseGradeFactory().read(request.user, course)\

        descriptor = modulestore().get_course(course_key)
        grading_policy = descriptor.grading_policy

        verification_status = IDVerificationService.user_status(request.user)
        verification_link = None
        if verification_status['status'] is None or verification_status[
                'status'] == 'expired':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        elif verification_status['status'] == 'must_reverify':
            verification_link = IDVerificationService.get_verify_location(
                course_id=course_key)
        verification_data = {
            'link': verification_link,
            'status': verification_status['status'],
            'status_date': verification_status['status_date'],
        }

        data = {
            'certificate_data':
            get_cert_data(request.user, course, enrollment_mode, course_grade),
            'completion_summary':
            get_course_blocks_completion_summary(course_key, request.user),
            'course_grade':
            course_grade,
            'section_scores':
            course_grade.chapter_grades.values(),
            'enrollment_mode':
            enrollment_mode,
            'grading_policy':
            grading_policy,
            'studio_url':
            get_studio_url(course, 'settings/grading'),
            'verification_data':
            verification_data,
        }
        context = self.get_serializer_context()
        context['staff_access'] = bool(
            has_access(request.user, 'staff', course))
        context['course_key'] = course_key
        serializer = self.get_serializer_class()(data, context=context)

        return Response(serializer.data)
Esempio n. 28
0
 def is_allowed(self):
     mode, is_active = CourseEnrollment.enrollment_mode_for_user(
         self.user, self.course_id)
     return (is_active and mode == 'verified' and self.verification_status
             in ('expired', 'none', 'must_reverify'))
Esempio n. 29
0
    def test_transfer_students(self):
        """
        Verify the transfer student command works as intended.
        """
        student = UserFactory.create()
        student.set_password(self.PASSWORD)
        student.save()
        mode = 'verified'
        # Original Course
        original_course_location = locator.CourseLocator(
            'Org0', 'Course0', 'Run0')
        course = self._create_course(original_course_location)
        # Enroll the student in 'verified'
        CourseEnrollment.enroll(student, course.id, mode='verified')

        # Create and purchase a verified cert for the original course.
        self._create_and_purchase_verified(student, course.id)

        # New Course 1
        course_location_one = locator.CourseLocator('Org1', 'Course1', 'Run1')
        new_course_one = self._create_course(course_location_one)

        # New Course 2
        course_location_two = locator.CourseLocator('Org2', 'Course2', 'Run2')
        new_course_two = self._create_course(course_location_two)
        original_key = text_type(course.id)
        new_key_one = text_type(new_course_one.id)
        new_key_two = text_type(new_course_two.id)

        # Run the actual management command
        call_command(
            'transfer_students',
            '--from',
            original_key,
            '--to',
            new_key_one,
            new_key_two,
        )
        self.assertTrue(self.signal_fired)

        # Confirm the analytics event was emitted.
        self.mock_tracker.emit.assert_has_calls([
            call(EVENT_NAME_ENROLLMENT_ACTIVATED, {
                'course_id': original_key,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_MODE_CHANGED, {
                'course_id': original_key,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_DEACTIVATED, {
                'course_id': original_key,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_ACTIVATED, {
                'course_id': new_key_one,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_MODE_CHANGED, {
                'course_id': new_key_one,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_ACTIVATED, {
                'course_id': new_key_two,
                'user_id': student.id,
                'mode': mode
            }),
            call(EVENT_NAME_ENROLLMENT_MODE_CHANGED, {
                'course_id': new_key_two,
                'user_id': student.id,
                'mode': mode
            })
        ])
        self.mock_tracker.reset_mock()

        # Confirm the enrollment mode is verified on the new courses, and enrollment is enabled as appropriate.
        self.assertEqual(
            (mode, False),
            CourseEnrollment.enrollment_mode_for_user(student, course.id))
        self.assertEqual(
            (mode, True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_one.id))
        self.assertEqual(
            (mode, True),
            CourseEnrollment.enrollment_mode_for_user(student,
                                                      new_course_two.id))
def _can_generate_v2_certificate(user, course_key):
    """
    Check if a v2 course certificate can be generated (created if it doesn't already exist, or updated if it does
    exist) for this user, in this course run.
    """
    if not _is_using_v2_course_certificates(course_key):
        # This course run is not using the v2 course certificate feature
        log.info(
            f'{course_key} is not using v2 course certificates. Certificate cannot be generated.'
        )
        return False

    if not auto_certificate_generation_enabled():
        # Automatic certificate generation is globally disabled
        log.info(
            f'Automatic certificate generation is globally disabled. Certificate cannot be generated for '
            f'{user.id} : {course_key}.')
        return False

    if CertificateInvalidation.has_certificate_invalidation(user, course_key):
        # The invalidation list prevents certificate generation
        log.info(
            f'{user.id} : {course_key} is on the certificate invalidation list. Certificate cannot be generated.'
        )
        return False

    enrollment_mode, __ = CourseEnrollment.enrollment_mode_for_user(
        user, course_key)
    if enrollment_mode is None:
        log.info(
            f'{user.id} : {course_key} does not have an enrollment. Certificate cannot be generated.'
        )
        return False

    if not modes_api.is_eligible_for_certificate(enrollment_mode):
        log.info(
            f'{user.id} : {course_key} has an enrollment mode of {enrollment_mode}, which is not eligible for an '
            f'allowlist certificate. Certificate cannot be generated.')
        return False

    if not IDVerificationService.user_is_verified(user):
        log.info(
            f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.'
        )
        return False

    if _is_ccx_course(course_key):
        log.info(
            f'{course_key} is a CCX course. Certificate cannot be generated for {user.id}.'
        )
        return False

    course = modulestore().get_course(course_key, depth=0)
    if _is_beta_tester(user, course):
        log.info(
            f'{user.id} is a beta tester in {course_key}. Certificate cannot be generated.'
        )
        return False

    if not _has_passing_grade(user, course):
        log.info(
            f'{user.id} does not have a passing grade in {course_key}. Certificate cannot be generated.'
        )
        return False

    if not _can_generate_certificate_for_status(user, course_key):
        return False

    log.info(f'V2 certificate can be generated for {user.id} : {course_key}')
    return True