Esempio n. 1
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.
    """
    certificate_is_delivered = 'N'
    certificate_type = 'N/A'
    status = _certificate_status(user_certificate)
    certificate_generated = status[
        'status'] == CertificateStatuses.downloadable
    course_overview = get_course_overview_or_none(course_id)
    if not course_overview:
        return None
    can_have_certificate = _should_certificate_be_visible(
        course_overview.certificates_display_behavior,
        course_overview.certificates_show_before_end,
        course_overview.has_ended(),
        course_overview.certificate_available_date, course_overview.self_paced)
    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. 2
0
    def get(self, request, ora_location, *args, **kwargs):
        try:
            init_data = {}

            # Get ORA block and config (incl. rubric)
            ora_usage_key = UsageKey.from_string(ora_location)
            init_data["oraMetadata"] = modulestore().get_item(ora_usage_key)

            # Get course metadata
            course_id = str(ora_usage_key.course_key)
            init_data["courseMetadata"] = get_course_overview_or_none(
                course_id)

            # Get list of submissions for this ORA
            init_data["submissions"] = get_submissions(request, ora_location)

            response_data = InitializeSerializer(init_data).data
            log.info(response_data)
            return Response(response_data)

        # Catch bad ORA location
        except (InvalidKeyError, ItemNotFoundError):
            log.error(f"Bad ORA location provided: {ora_location}")
            return BadOraLocationResponse()

        # Issues with the XBlock handlers
        except XBlockInternalError as ex:
            log.error(ex)
            return InternalErrorResponse(context=ex.context)

        # Blanket exception handling in case something blows up
        except Exception as ex:
            log.exception(ex)
            return UnknownErrorResponse()
Esempio n. 3
0
    def get_enrollments_can_take_proctored_exams(self,
                                                 course_id,
                                                 text_search=None):
        """
        Return all enrollments for a course that are in a mode that makes the corresponding user
        eligible to take proctored exams.

        NOTE: Due to performance concerns, this method returns a QuerySet. Ordinarily, model implementations
        should not be exposed to clients in this way. However, the clients may need to do additional computation
        in the database to avoid performance penalties.

        Parameters:
        * course_id: course ID for the course
        * text_search: the string against which to do a match on users' username or email; optional
        """
        course_id_coursekey = CourseKey.from_string(course_id)
        course_overview = get_course_overview_or_none(course_id_coursekey)
        if not course_overview or not course_overview.enable_proctored_exams:
            return None

        allow_honor_mode = settings.PROCTORING_BACKENDS.get(
            course_overview.proctoring_provider,
            {}).get('allow_honor_mode', False)
        enrollments = self._get_enrollments_for_course_proctoring_eligible_modes(
            course_id, allow_honor_mode)

        enrollments = enrollments.select_related('user')
        if text_search:
            user_filters = Q(user__username__icontains=text_search) | Q(
                user__email__icontains=text_search)
            enrollments = enrollments.filter(user_filters)

        return enrollments
def _can_generate_certificate_common(user, course_key, enrollment_mode):
    """
    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 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

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

    is_eligible_for_cert = modes_api.is_eligible_for_certificate(
        enrollment_mode)
    if not is_eligible_for_cert:
        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 the IDV check fails we then check if the course-run requires ID verification. Honor and Professional-No-ID
    # modes do not require IDV for certificate generation.
    if _required_verification_missing(user):
        if enrollment_mode not in CourseMode.NON_VERIFIED_MODES:
            log.info(
                f'{user.id} does not have a verified id. Certificate cannot be generated for {course_key}.'
            )
            return False

        log.info(
            f'{user.id} : {course_key} is eligible for a certificate without requiring a verified ID. '
            'Skipping results of the ID verification check.')

    if not _can_generate_certificate_for_status(user, course_key,
                                                enrollment_mode):
        return False

    course_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        log.info(
            f'{course_key} does not a course overview. Certificate cannot be generated for {user.id}.'
        )
        return False

    if not has_html_certificates_enabled(course_overview):
        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. 5
0
 def test_get_course_overview_or_none_success(self):
     """
     Test for `test_get_course_overview_or_none` function when the overview exists.
     """
     course_overview = CourseOverviewFactory.create()
     retrieved_course_overview = get_course_overview_or_none(
         course_overview.id)
     assert course_overview.id == retrieved_course_overview.id
Esempio n. 6
0
 def test_get_course_overview_or_none_missing(self):
     """
     Test for `test_get_course_overview_or_none` function when the overview does not exist.
     """
     course_run_key = CourseKey.from_string(
         'course-v1:coping+with+deletions')
     retrieved_course_overview = get_course_overview_or_none(course_run_key)
     assert retrieved_course_overview is None
Esempio n. 7
0
def regenerate_certificate_for_user(request):
    """
    Regenerate certificates for a user.

    This is meant to be used by support staff through the UI in lms/djangoapps/support

    Arguments:
        request (HttpRequest): The request object

    Returns:
        HttpResponse

    Example Usage:

        POST /certificates/regenerate
            * username: "******"
            * course_key: "edX/DemoX/Demo_Course"

        Response: 200 OK

    """
    # Check the POST parameters, returning a 400 response if they're not valid.
    params, response = _validate_post_params(request.POST)
    if response is not None:
        return response

    user = params["user"]
    course_key = params["course_key"]

    course_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        msg = _("The course {course_key} does not exist").format(
            course_key=course_key)
        return HttpResponseBadRequest(msg)

    # Check that the user is enrolled in the course
    if not CourseEnrollment.is_enrolled(user, course_key):
        msg = _("User {user_id} is not enrolled in the course {course_key}"
                ).format(user_id=user.id, course_key=course_key)
        return HttpResponseBadRequest(msg)

    # Attempt to regenerate certificates
    try:
        generate_certificate_task(user, course_key)
    except:  # pylint: disable=bare-except
        # We are pessimistic about the kinds of errors that might get thrown by the
        # certificates API.  This may be overkill, but we're logging everything so we can
        # track down unexpected errors.
        log.exception(
            f"Could not regenerate certificate for user {user.id} in course {course_key}"
        )
        return HttpResponseServerError(
            _("An unexpected error occurred while regenerating certificates."))

    log.info(
        f"Started regenerating certificates for user {user.id} in course {course_key} from the support page."
    )
    return HttpResponse(200)
Esempio n. 8
0
    def _generate_cert(self, cert, student, grade_contents, template_pdf,
                       generate_pdf):
        """
        Generate a certificate for the student. If `generate_pdf` is True,
        sends a request to XQueue.
        """
        course_id = str(cert.course_id)
        course_overview = get_course_overview_or_none(course_id)
        if not course_overview:
            LOGGER.warning(
                f"Skipping cert generation for {student.id} due to missing course overview for {course_id}"
            )
            return cert

        key = make_hashkey(random.random())
        cert.key = key
        contents = {
            'action': 'create',
            'username': student.username,
            'course_id': course_id,
            'course_name': course_overview.display_name or course_id,
            'name': cert.name,
            'grade': grade_contents,
            'template_pdf': template_pdf,
        }
        if generate_pdf:
            cert.status = status.generating
        else:
            cert.status = status.downloadable
            cert.verify_uuid = uuid4().hex

        cert.save()
        logging.info(
            'certificate generated for user: %s with generate_pdf status: %s',
            student.username, generate_pdf)

        if generate_pdf:
            try:
                self._send_to_xqueue(contents, key)
            except XQueueAddToQueueError as exc:
                cert.status = ExampleCertificate.STATUS_ERROR
                cert.error_reason = str(exc)
                cert.save()
                LOGGER.critical(
                    ("Could not add certificate task to XQueue.  "
                     "The course was '%s' and the student was '%s'."
                     "The certificate task status has been marked as 'error' "
                     "and can be re-submitted with a management command."),
                    course_id, student.id)
            else:
                LOGGER.info(("The certificate status has been set to '%s'.  "
                             "Sent a certificate grading task to the XQueue "
                             "with the key '%s'. "), cert.status, key)
        return cert
Esempio n. 9
0
def regenerate_user_certificates(student,
                                 course_key,
                                 forced_grade=None,
                                 template_file=None,
                                 insecure=False):
    """
    Add the regen-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'.

    This method has not yet been updated (it predates the certificates revamp).

    Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        grade_value - The grade string, such as "Distinction"
        template_file - The template file used to render this certificate
        insecure - (Boolean)
    """
    if can_generate_certificate_task(student, course_key):
        log.info(
            f"{course_key} is using V2 certificates. Attempt will be made to regenerate a V2 certificate for "
            f"user {student.id}.")
        return generate_certificate_task(student, course_key)

    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    course_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        log.info(
            f"Canceling certificate generation for user {student.id} : {course_key} due to a missing course "
            f"overview.")
        return False

    generate_pdf = not has_html_certificates_enabled(course_overview)
    log.info(
        f"Started regenerating certificates for user {student.id} in course {course_key} with generate_pdf "
        f"status: {generate_pdf}.")

    xqueue.regen_cert(student,
                      course_key,
                      forced_grade=forced_grade,
                      template_file=template_file,
                      generate_pdf=generate_pdf)
    return True
Esempio n. 10
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_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        log.info(
            f'{course_key} does not a course overview. Certificate cannot be generated for {user.id}.'
        )
        return False

    if not has_html_certificates_enabled(course_overview):
        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. 11
0
def emit_certificate_event(event_name,
                           user,
                           course_id,
                           course_overview=None,
                           event_data=None):
    """
    Utility function responsible for emitting certificate events.

    We currently track the following events:
    - `edx.certificate.created` - Emit when a course certificate with the `downloadable` status has been awarded to a
                                  learner.
    - `edx.certificate.revoked`- Emit when a course certificate with the `downloadable` status has been taken away from
                                 a learner.
    - `edx.certificate.shared` - Emit when a learner shares their course certificate to social media (LinkedIn,
                                 Facebook, or Twitter).
    - `edx.certificate.evidence_visited` - Emit when a user (other than the learner who owns a certificate) views a
                                           course certificate (e.g., someone views a course certificate shared on a
                                           LinkedIn profile).

    Args:
        event_name (String) - Text describing the action/event that we are tracking. Examples include `revoked`,
                              `created`, etc.
        user (User) - The User object of the learner associated with this event.
        course_id (CourseLocator) - The course-run key associated with this event.
        course_overview (CourseOverview) - Optional. The CourseOverview of the course-run associated with this event.
        event_data (dictionary) - Optional. Dictionary containing any additional data we want to be associated with an
                                  event.
    """
    event_name = '.'.join(['edx', 'certificate', event_name])

    if not course_overview:
        course_overview = get_course_overview_or_none(course_id)

    context = {'org_id': course_overview.org, 'course_id': str(course_id)}

    data = {
        'user_id':
        user.id,
        'course_id':
        str(course_id),
        'certificate_url':
        get_certificate_url(user.id,
                            course_id,
                            uuid=event_data['certificate_id'])
    }
    event_data = event_data or {}
    event_data.update(data)

    with tracker.get_tracker().context(event_name, context):
        tracker.emit(event_name, event_data)
Esempio n. 12
0
def generate_certificate_for_user(request):
    """
    Generate certificates for a user.

    This is meant to be used by support staff through the UI in lms/djangoapps/support

    Arguments:
        request (HttpRequest): The request object

    Returns:
        HttpResponse

    Example Usage:

        POST /certificates/generate
            * username: "******"
            * course_key: "edX/DemoX/Demo_Course"

        Response: 200 OK

    """
    # Check the POST parameters, returning a 400 response if they're not valid.
    params, response = _validate_post_params(request.POST)
    if response is not None:
        return response

    course_overview = get_course_overview_or_none(params["course_key"])
    if not course_overview:
        msg = _("The course {course_key} does not exist").format(
            course_key=params["course_key"])
        return HttpResponseBadRequest(msg)

    # Check that the user is enrolled in the course
    if not CourseEnrollment.is_enrolled(params["user"], params["course_key"]):
        msg = _("User {username} is not enrolled in the course {course_key}"
                ).format(username=params["user"].username,
                         course_key=params["course_key"])
        return HttpResponseBadRequest(msg)

    # Attempt to generate certificate
    generate_certificates_for_students(request,
                                       params["course_key"],
                                       student_set="specific_student",
                                       specific_student_id=params["user"].id)
    return HttpResponse(200)
Esempio n. 13
0
    def get(self, request, username, course_id):
        """
        Retrieves certificate information for a user in a specified course run.

        Args:
            request (Request): Django request object.
            username (string): URI element specifying the user's username.
            course_id (string): URI element specifying the course location.

        Return:
            A JSON serialized representation of the certificate.
        """
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            log.warning('Course ID string "%s" is not valid', course_id)
            return Response(status=404,
                            data={'error_code': 'course_id_not_valid'})

        user_cert = get_certificate_for_user(username=username,
                                             course_key=course_key)
        if user_cert is None:
            return Response(status=404,
                            data={'error_code': 'no_certificate_for_user'})

        course_overview = get_course_overview_or_none(course_id)
        # return 404 if it's not a PDF certificates and there is no active certificate configuration.
        if not user_cert['is_pdf_certificate'] and (
                not course_overview
                or not course_overview.has_any_active_web_certificate):
            return Response(
                status=404,
                data={'error_code': 'no_certificate_configuration_for_course'})

        return Response({
            "username": user_cert.get('username'),
            "course_id": str(user_cert.get('course_key')),
            "certificate_type": user_cert.get('type'),
            "created_date": user_cert.get('created'),
            "status": user_cert.get('status'),
            "is_passing": user_cert.get('is_passing'),
            "download_url": user_cert.get('download_url'),
            "grade": user_cert.get('grade')
        })
Esempio n. 14
0
def get_certificate_url(user_id=None,
                        course_id=None,
                        uuid=None,
                        user_certificate=None):
    """
    Returns the certificate URL
    """
    url = ''

    course_overview = get_course_overview_or_none(_safe_course_key(course_id))
    if not course_overview:
        return url

    if has_html_certificates_enabled(course_overview):
        url = _certificate_html_url(uuid)
    else:
        url = _certificate_download_url(user_id,
                                        course_id,
                                        user_certificate=user_certificate)
    return url
def _can_set_cert_status_common(user, course_key, enrollment_mode):
    """
    Determine whether we can set a custom (non-downloadable) cert status
    """
    if _is_cert_downloadable(user, course_key):
        return False

    if enrollment_mode is None:
        return False

    if not modes_api.is_eligible_for_certificate(enrollment_mode):
        return False

    course_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        return False

    if not has_html_certificates_enabled(course_overview):
        return False

    return True
Esempio n. 16
0
def _format_certificate_for_user(username, cert):
    """
    Helper function to serialize a user certificate.

    Arguments:
        username (unicode): The identifier of the user.
        cert (GeneratedCertificate): a user certificate

    Returns: dict
    """
    course_overview = get_course_overview_or_none(cert.course_id)
    if cert.download_url or course_overview:
        return {
            "username":
            username,
            "course_key":
            cert.course_id,
            "type":
            cert.mode,
            "status":
            cert.status,
            "grade":
            cert.grade,
            "created":
            cert.created_date,
            "modified":
            cert.modified_date,
            "is_passing":
            CertificateStatuses.is_passing_status(cert.status),
            "is_pdf_certificate":
            bool(cert.download_url),
            "download_url":
            (cert.download_url or get_certificate_url(cert.user.id,
                                                      cert.course_id,
                                                      uuid=cert.verify_uuid,
                                                      user_certificate=cert)
             if cert.status == CertificateStatuses.downloadable else None),
        }

    return None
Esempio n. 17
0
def certificates_viewable_for_course(course):
    """
    Returns True if certificates are viewable for any student enrolled in the course, False otherwise.

    Arguments:
        course (CourseOverview or course descriptor): The course to check if certificates are viewable

    Returns:
        boolean: whether the certificates are viewable or not
    """

    # The CourseOverview contains validation logic on the certificates_display_behavior and certificate_available_date
    # fields. Thus, we prefer to use the CourseOverview, but will fall back to the course in case the CourseOverview is
    # not available.
    if not isinstance(course, CourseOverview):
        course_overview = get_course_overview_or_none(course.id)
        if course_overview:
            course = course_overview

    return _should_certificate_be_visible(course.certificates_display_behavior,
                                          course.certificates_show_before_end,
                                          course.has_ended(),
                                          course.certificate_available_date,
                                          course.self_paced)
Esempio n. 18
0
def certificate_downloadable_status(student, course_key):
    """
    Check the student existing certificates against a given course.
    if status is not generating and not downloadable or error then user can view the generate button.

    Args:
        student (user object): logged-in user
        course_key (CourseKey): ID associated with the course

    Returns:
        Dict containing student passed status also download url, uuid for cert if available
    """
    current_status = _certificate_status_for_student(student, course_key)

    # If the certificate status is an error user should view that status is "generating".
    # On the back-end, need to monitor those errors and re-submit the task.

    response_data = {
        'is_downloadable':
        False,
        'is_generating':
        True if current_status['status'] in [
            CertificateStatuses.generating,  # pylint: disable=simplifiable-if-expression
            CertificateStatuses.error
        ] else False,
        'is_unverified':
        True if current_status['status'] == CertificateStatuses.unverified else
        False,  # pylint: disable=simplifiable-if-expression
        'download_url':
        None,
        'uuid':
        None,
    }

    course_overview = get_course_overview_or_none(course_key)

    if settings.FEATURES.get("ENABLE_V2_CERT_DISPLAY_SETTINGS"):
        display_behavior_is_valid = (
            course_overview.certificates_display_behavior ==
            CertificatesDisplayBehaviors.END_WITH_DATE)
    else:
        display_behavior_is_valid = True

    if (not certificates_viewable_for_course(course_overview)
            and CertificateStatuses.is_passing_status(current_status['status'])
            and display_behavior_is_valid
            and course_overview.certificate_available_date):
        response_data['earned_but_not_available'] = True
        response_data[
            'certificate_available_date'] = course_overview.certificate_available_date

    may_view_certificate = _should_certificate_be_visible(
        course_overview.certificates_display_behavior,
        course_overview.certificates_show_before_end,
        course_overview.has_ended(),
        course_overview.certificate_available_date, course_overview.self_paced)
    if current_status[
            'status'] == CertificateStatuses.downloadable and may_view_certificate:
        response_data['is_downloadable'] = True
        response_data['download_url'] = current_status[
            'download_url'] or get_certificate_url(student.id, course_key,
                                                   current_status['uuid'])
        response_data['is_pdf_certificate'] = bool(
            current_status['download_url'])
        response_data['uuid'] = current_status['uuid']

    return response_data
Esempio n. 19
0
def generate_user_certificates(student, course_key, insecure=False, generation_mode='batch', forced_grade=None):
    """
    It will add the add-cert request into the xqueue.

    A new record will be created to track the certificate
    generation task.  If an error occurs while adding the certificate
    to the queue, the task will have status 'error'. It also emits
    `edx.certificate.created` event for analytics.

    This method has not yet been updated (it predates the certificates revamp). If modifying this method,
    see also generate_user_certificates() in generation_handler.py (which is very similar but is not called from a
    celery task). In the future these methods will be unified.

   Args:
        student (User)
        course_key (CourseKey)

    Keyword Arguments:
        insecure - (Boolean)
        generation_mode - who has requested certificate generation. Its value should `batch`
        in case of django command and `self` if student initiated the request.
        forced_grade - a string indicating to replace grade parameter. if present grading
                       will be skipped.
    """
    beta_testers_queryset = list_with_level(course_key, 'beta')
    if beta_testers_queryset.filter(username=student.username):
        log.info(f"Canceling Certificate Generation task for user {student.id} : {course_key}. User is a Beta Tester.")
        return

    xqueue = XQueueCertInterface()
    if insecure:
        xqueue.use_https = False

    course_overview = get_course_overview_or_none(course_key)
    if not course_overview:
        log.info(f"Canceling Certificate Generation task for user {student.id} : {course_key} due to a missing course"
                 f"overview.")
        return

    generate_pdf = not has_html_certificates_enabled(course_overview)

    cert = xqueue.add_cert(
        student,
        course_key,
        generate_pdf=generate_pdf,
        forced_grade=forced_grade
    )

    log.info(f"Queued Certificate Generation task for {student.id} : {course_key}")

    # If cert_status is not present in certificate valid_statuses (for example unverified) then
    # add_cert returns None and raises AttributeError while accessing cert attributes.
    if cert is None:
        return

    if CertificateStatuses.is_passing_status(cert.status):
        emit_certificate_event('created', student, course_key, course_overview, {
            'user_id': student.id,
            'course_id': str(course_key),
            'certificate_id': cert.verify_uuid,
            'enrollment_mode': cert.mode,
            'generation_mode': generation_mode
        })
    return cert.status
Esempio n. 20
0
def render_html_view(request, course_id, certificate=None):
    """
    This public view generates an HTML representation of the specified user and course
    If a certificate is not available, we display a "Sorry!" screen instead
    It can be overridden by setting `OVERRIDE_RENDER_CERTIFICATE_VIEW` to an alternative implementation.
    """
    user = certificate.user if certificate else request.user
    user_id = user.id
    preview_mode = request.GET.get('preview', None)
    platform_name = configuration_helpers.get_value("platform_name",
                                                    settings.PLATFORM_NAME)
    configuration = CertificateHtmlViewConfiguration.get_config()

    # Kick the user back to the "Invalid" screen if the feature is disabled globally
    if not settings.FEATURES.get('CERTIFICATES_HTML_VIEW', False):
        return _render_invalid_certificate(request, course_id, platform_name,
                                           configuration)

    # Load the course and user objects
    try:
        course_key = CourseKey.from_string(course_id)
        course = get_course_by_id(course_key)

    # For any course or user exceptions, kick the user back to the "Invalid" screen
    except (InvalidKeyError, Http404) as exception:
        error_str = ("Invalid cert: error finding course %s "
                     "Specific error: %s")
        log.info(error_str, course_id, str(exception))
        return _render_invalid_certificate(request, course_id, platform_name,
                                           configuration)

    course_overview = get_course_overview_or_none(course_key)

    # Kick the user back to the "Invalid" screen if the feature is disabled for the course
    if not course.cert_html_view_enabled:
        log.info(
            "Invalid cert: HTML certificates disabled for %s. User id: %d",
            course_id,
            user_id,
        )
        return _render_invalid_certificate(request, course_id, platform_name,
                                           configuration)

    # Load user's certificate
    user_certificate = _get_user_certificate(request, user, course_key,
                                             course_overview, preview_mode)
    if not user_certificate:
        log.info(
            "Invalid cert: User %d does not have eligible cert for %s.",
            user_id,
            course_id,
        )
        return _render_invalid_certificate(request, course_id, platform_name,
                                           configuration)

    # Get the active certificate configuration for this course
    # If we do not have an active certificate, we'll need to send the user to the "Invalid" screen
    # Passing in the 'preview' parameter, if specified, will return a configuration, if defined
    active_configuration = get_active_web_certificate(course, preview_mode)
    if active_configuration is None:
        log.info(
            "Invalid cert: course %s does not have an active configuration. User id: %d",
            course_id,
            user_id,
        )
        return _render_invalid_certificate(request, course_id, platform_name,
                                           configuration)

    # Get data from Discovery service that will be necessary for rendering this Certificate.
    catalog_data = _get_catalog_data_for_course(course_key)

    # Determine whether to use the standard or custom template to render the certificate.
    custom_template = None
    custom_template_language = None
    if settings.FEATURES.get('CUSTOM_CERTIFICATE_TEMPLATES_ENABLED', False):
        log.info("Custom certificate for course %s", course_id)
        custom_template, custom_template_language = _get_custom_template_and_language(
            course.id, user_certificate.mode,
            catalog_data.pop('content_language', None))

    # Determine the language that should be used to render the certificate.
    # For the standard certificate template, use the user language. For custom templates, use
    # the language associated with the template.
    user_language = translation.get_language()
    certificate_language = custom_template_language if custom_template else user_language

    log.info("certificate language is: %s for the course: %s",
             certificate_language, course_key)

    # Generate the certificate context in the correct language, then render the template.
    with translation.override(certificate_language):
        context = {'user_language': user_language}

        _update_context_with_basic_info(context, course_id, platform_name,
                                        configuration)

        context['certificate_data'] = active_configuration

        # Append/Override the existing view context values with any mode-specific ConfigurationModel values
        context.update(configuration.get(user_certificate.mode, {}))

        # Append organization info
        _update_organization_context(context, course)

        # Append course info
        _update_course_context(request, context, course, platform_name)

        # Append course run info from discovery
        context.update(catalog_data)

        # Append user info
        _update_context_with_user_info(context, user, user_certificate)

        # Append social sharing info
        _update_social_context(request, context, course, user_certificate,
                               platform_name)

        # Append/Override the existing view context values with certificate specific values
        _update_certificate_context(context, course, course_overview,
                                    user_certificate, platform_name)

        # Append badge info
        _update_badge_context(context, course, user)

        # Add certificate header/footer data to current context
        context.update(
            get_certificate_header_context(is_secure=request.is_secure()))
        context.update(get_certificate_footer_context())

        # Append/Override the existing view context values with any course-specific static values from Advanced Settings
        context.update(course.cert_html_view_overrides)

        # Track certificate view events
        _track_certificate_events(request, course, user, user_certificate)

        # Render the certificate
        return _render_valid_certificate(request, context, custom_template)
Esempio n. 21
0
def search_certificates(request):
    """
    Search for certificates for a particular user OR along with the given course.

    Supports search by either username or email address along with course id.

    First filter the records for the given username/email and then filter against the given course id (if given).
    Show the 'Regenerate' button if a record found in 'generatedcertificate' model otherwise it will show the Generate
    button.

    Arguments:
        request (HttpRequest): The request object.

    Returns:
        JsonResponse

    Example Usage:
        GET /certificates/[email protected]
        GET /certificates/[email protected]&course_id=xyz

        Response: 200 OK
        Content-Type: application/json
        [
            {
                "username": "******",
                "course_key": "edX/DemoX/Demo_Course",
                "type": "verified",
                "status": "downloadable",
                "download_url": "http://www.example.com/cert.pdf",
                "grade": "0.98",
                "created": 2015-07-31T00:00:00Z,
                "modified": 2015-07-31T00:00:00Z
            }
        ]

    """
    unbleached_filter = urllib.parse.unquote(
        urllib.parse.quote_plus(request.GET.get("user", "")))
    user_filter = bleach.clean(unbleached_filter)
    if not user_filter:
        msg = _("user is not given.")
        return HttpResponseBadRequest(msg)

    try:
        user = User.objects.get(Q(email=user_filter) | Q(username=user_filter))
    except User.DoesNotExist:
        return HttpResponseBadRequest(
            _("user '{user}' does not exist").format(user=user_filter))

    certificates = get_certificates_for_user(user.username)
    for cert in certificates:
        cert["course_key"] = str(cert["course_key"])
        cert["created"] = cert["created"].isoformat()
        cert["modified"] = cert["modified"].isoformat()
        cert["regenerate"] = not cert['is_pdf_certificate']

    course_id = urllib.parse.quote_plus(request.GET.get("course_id", ""),
                                        safe=':/')
    if course_id:
        try:
            course_key = CourseKey.from_string(course_id)
        except InvalidKeyError:
            return HttpResponseBadRequest(
                _("Course id '{course_id}' is not valid").format(
                    course_id=course_id))
        else:
            course_overview = get_course_overview_or_none(course_key)
            if not course_overview:
                msg = _(
                    "The course does not exist against the given key '{course_key}'"
                ).format(course_key=course_key)
                return HttpResponseBadRequest(msg)

            certificates = [
                certificate for certificate in certificates
                if certificate['course_key'] == course_id
            ]
            if not certificates:
                return JsonResponse([{
                    'username': user.username,
                    'course_key': course_id,
                    'regenerate': False
                }])

    return JsonResponse(certificates)