Example #1
0
def _enrollment_mode_display(enrollment_mode, verification_status, course_id):
    """Checking enrollment mode and status and returns the display mode
     Args:
        enrollment_mode (str): enrollment mode.
        verification_status (str) : verification status of student

    Returns:
        display_mode (str) : display mode for certs
    """
    course_mode_slugs = [mode.slug for mode in CourseMode.modes_for_course(course_id)]

    if enrollment_mode == CourseMode.VERIFIED:
        if (
            is_integrity_signature_enabled(course_id)
            or verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED, VERIFY_STATUS_APPROVED]
        ):
            display_mode = DISPLAY_VERIFIED
        elif DISPLAY_HONOR in course_mode_slugs:
            display_mode = DISPLAY_HONOR
        else:
            display_mode = DISPLAY_AUDIT
    elif enrollment_mode in [CourseMode.PROFESSIONAL, CourseMode.NO_ID_PROFESSIONAL_MODE]:
        display_mode = DISPLAY_PROFESSIONAL
    else:
        display_mode = enrollment_mode

    return display_mode
Example #2
0
def _update_course_context(request, context, course, platform_name):
    """
    Updates context dictionary with course info.
    """
    context['full_course_image_url'] = request.build_absolute_uri(
        course_image_url(course))
    course_title_from_cert = context['certificate_data'].get(
        'course_title', '')
    accomplishment_copy_course_name = course_title_from_cert if course_title_from_cert else course.display_name
    context[
        'accomplishment_copy_course_name'] = accomplishment_copy_course_name
    course_number = course.display_coursenumber if course.display_coursenumber else course.number
    context['course_number'] = course_number
    context[
        'is_integrity_signature_enabled_for_course'] = is_integrity_signature_enabled(
            course.location.course_key)
    if context['organization_long_name']:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _(
            'a course of study offered by {partner_short_name}, '
            'an online learning initiative of '
            '{partner_long_name}.').format(
                partner_short_name=context['organization_short_name'],
                partner_long_name=context['organization_long_name'],
                platform_name=platform_name)
    else:
        # Translators:  This text represents the description of course
        context['accomplishment_copy_course_description'] = _(
            'a course of study offered by '
            '{partner_short_name}.').format(
                partner_short_name=context['organization_short_name'],
                platform_name=platform_name)
Example #3
0
 def _get_fragments(self):
     """
     Provide a tuple of string[]s that should be (in, not_in) the email
     """
     course_module = modulestore().get_course(self.course.id)
     proctoring_provider = capwords(
         course_module.proctoring_provider.replace('_', ' '))
     id_verification_url = IDVerificationService.get_verify_location()
     fragments = [
         ("You are enrolled in {} at {}. This course contains proctored exams."
          .format(self.course.display_name, settings.PLATFORM_NAME)),
         ("Proctored exams are timed exams that you take while proctoring software monitors "
          "your computer's desktop, webcam video, and audio."),
         proctoring_provider,
         escape(
             "Carefully review the system requirements as well as the steps to take a proctored "
             "exam in order to ensure that you are prepared."),
         settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''),
     ]
     idv_fragments = [
         escape(
             "Before taking a graded proctored exam, you must have approved ID verification photos."
         ),
         id_verification_url,
     ]
     if not is_integrity_signature_enabled(self.course.id):
         fragments.extend(idv_fragments)
         return (fragments, [])
     return (fragments, idv_fragments)
Example #4
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 is_integrity_signature_enabled(self.course_id)
     )
Example #5
0
 def user_needs_integrity_signature(self):
     """
     Boolean describing whether the user needs to sign the integrity agreement for a course.
     """
     if (is_integrity_signature_enabled(self.course_key)
             and not self.is_staff and self.enrollment_object
             and self.enrollment_object.mode
             in CourseMode.CERTIFICATE_RELEVANT_MODES):
         signature = get_integrity_signature(self.effective_user.username,
                                             str(self.course_key))
         if not signature:
             return True
     return False
Example #6
0
def generate_proctoring_requirements_email_context(user, course_id):
    """
    Constructs a dictionary for use in proctoring requirements email context

    Arguments:
        user: Currently logged-in user
        course_id: ID of the proctoring-enabled course the user is enrolled in
    """
    course_module = modulestore().get_course(course_id)
    return {
        'user': user,
        'course_name': course_module.display_name,
        'proctoring_provider': capwords(course_module.proctoring_provider.replace('_', ' ')),
        'proctoring_requirements_url': settings.PROCTORING_SETTINGS.get('LINK_URLS', {}).get('faq', ''),
        'idv_required': not is_integrity_signature_enabled(course_id),
        'id_verification_url': IDVerificationService.get_verify_location(),
    }
Example #7
0
def _regenerate_certs(certs, batch_size, sleep_seconds, count, check_integrity_signature_flag):
    """
    Triggers generate certificate task for a given set of certificates
    """
    for cert in certs:
        # not ideal to replicate this check, but we have to in the case that we are regenerating certs for all courses.
        if check_integrity_signature_flag and not is_integrity_signature_enabled(cert.course_id):
            log.warning(
                f'Skipping regenerating cert_id={cert.id} because {cert.course_id} does not have honor code enabled'
            )
        else:
            user = User.objects.get(id=cert.user_id)
            generate_certificate_task(user, cert.course_id, generation_mode='batch', delay_seconds=0)
            count += 1
            if count % batch_size == 0:
                log.info(f'Regenerated {count} unverified certificates. Sleeping for {sleep_seconds} seconds.')
                time.sleep(sleep_seconds)
    return count
Example #8
0
def _handle_course(course_key, batch_size, sleep_seconds, count):
    """
    Regenerates unverified status certificates for the designated course with delay seconds between certs.
    Returns how many certs were originally unverified and regenerated.
    """

    if not is_integrity_signature_enabled(course_key):
        log.warning(f'Skipping {course_key} which does not have honor code enabled')
        return 0

    certs = GeneratedCertificate.objects.filter(
        course_id=course_key,
        status=CertificateStatuses.unverified,
    )

    log.info(f'Regenerating {len(certs)} unverified certificates for {course_key}')

    return _regenerate_certs(certs, batch_size, sleep_seconds, count, False)
Example #9
0
def enrollment_mode_display(mode, verification_status, course_id):
    """ Select appropriate display strings and CSS classes.

        Uses mode and verification status to select appropriate display strings and CSS classes
        for certificate display.

        Args:
            mode (str): enrollment mode.
            verification_status (str) : verification status of student

        Returns:
            dictionary:
    """
    show_image = False
    image_alt = ''
    enrollment_title = ''
    enrollment_value = ''
    display_mode = _enrollment_mode_display(mode, verification_status, course_id)

    if display_mode == DISPLAY_VERIFIED:
        if is_integrity_signature_enabled(course_id) or verification_status == VERIFY_STATUS_APPROVED:
            enrollment_title = _("You're enrolled as a verified student")
            enrollment_value = _("Verified")
            show_image = True
            image_alt = _("ID Verified Ribbon/Badge")
        elif verification_status in [VERIFY_STATUS_NEED_TO_VERIFY, VERIFY_STATUS_SUBMITTED]:
            enrollment_title = _("Your verification is pending")
            enrollment_value = _("Verified: Pending Verification")
            show_image = True
            image_alt = _("ID verification pending")
    elif display_mode == DISPLAY_HONOR:
        enrollment_title = _("You're enrolled as an honor code student")
        enrollment_value = _("Honor Code")
    elif display_mode == DISPLAY_PROFESSIONAL:
        enrollment_title = _("You're enrolled as a professional education student")
        enrollment_value = _("Professional Ed")

    return {
        'enrollment_title': str(enrollment_title),
        'enrollment_value': str(enrollment_value),
        'show_image': show_image,
        'image_alt': str(image_alt),
        'display_mode': display_mode
    }
Example #10
0
    def post(self, request, course_id):
        """
        Create an integrity signature for the requesting user and course. If a signature
        already exists, returns the existing signature instead of creating a new one.

        /api/agreements/v1/integrity_signature/{course_id}

        Example response:
            {
                username: "******",
                course_id: "org.2/course_2/Run_2",
                created_at: "2021-04-23T18:25:43.511Z"
            }
        """
        # check that waffle flag is enabled
        if not is_integrity_signature_enabled():
            return Response(status=status.HTTP_404_NOT_FOUND, )

        username = request.user.username
        signature = create_integrity_signature(username, course_id)
        serializer = IntegritySignatureSerializer(signature)
        return Response(serializer.data)
Example #11
0
    def get(self, request, course_id):
        """
        In order to check whether the user has signed the integrity agreement for a given course.

        Should return the following:
            username (str)
            course_id (str)
            created_at (str)

        If a username is not given, it should default to the requesting user (or masqueraded user).
        Only staff should be able to access this endpoint for other users.
        """
        # check that waffle flag is enabled
        if not is_integrity_signature_enabled(
                CourseKey.from_string(course_id)):
            return Response(status=status.HTTP_404_NOT_FOUND, )

        # check that user can make request
        user = request.user.username
        requested_user = request.GET.get('username')
        is_staff = is_user_course_or_global_staff(request.user, course_id)

        if not is_staff and requested_user and (user != requested_user):
            return Response(
                status=status.HTTP_403_FORBIDDEN,
                data={
                    "message":
                    "User does not have permission to view integrity agreement."
                })

        username = requested_user if requested_user else user
        signature = get_integrity_signature(username, course_id)

        if signature is None:
            return Response(status=status.HTTP_404_NOT_FOUND, )

        serializer = IntegritySignatureSerializer(signature)
        return Response(serializer.data)
Example #12
0
def get_certificate_description(mode, certificate_type, platform_name,
                                course_key):
    """
    :return certificate_type_description on the basis of current mode
    """
    certificate_type_description = None
    if mode == 'honor':
        # Translators:  This text describes the 'Honor' course certificate type.
        certificate_type_description = _(
            "An {cert_type} certificate signifies that a "
            "learner has agreed to abide by the honor code established by "
            "{platform_name} and has completed all of the required tasks for this course "
            "under its guidelines.").format(cert_type=certificate_type,
                                            platform_name=platform_name)
    elif mode == 'verified':
        # Translators:  This text describes the 'ID Verified' course certificate type, which is a higher level of
        # verification offered by edX.  This type of verification is useful for professional education/certifications
        certificate_type_description = _(
            "A {cert_type} certificate signifies that a "
            "learner has agreed to abide by the honor code established by "
            "{platform_name} and has completed all of the required tasks for this course "
            "under its guidelines. ").format(cert_type=certificate_type,
                                             platform_name=platform_name)
        if not is_integrity_signature_enabled(course_key):
            certificate_type_description += _(
                "A {cert_type} certificate also indicates that the "
                "identity of the learner has been checked and "
                "is valid.").format(cert_type=certificate_type)

    elif mode == 'xseries':
        # Translators:  This text describes the 'XSeries' course certificate type.  An XSeries is a collection of
        # courses related to each other in a meaningful way, such as a specific topic or theme, or even an organization
        certificate_type_description = _(
            "An {cert_type} certificate demonstrates a high level of "
            "achievement in a program of study, and includes verification of "
            "the student's identity.").format(cert_type=certificate_type)
    return certificate_type_description
Example #13
0
    def _time_limited_student_view(self):
        """
        Delegated rendering of a student view when in a time
        limited view. This ultimately calls down into edx_proctoring
        pip installed djangoapp
        """

        # None = no overridden view rendering
        view_html = None

        proctoring_service = self.runtime.service(self, 'proctoring')
        credit_service = self.runtime.service(self, 'credit')
        verification_service = self.runtime.service(self, 'verification')

        # Is this sequence designated as a Timed Examination, which includes
        # Proctored Exams
        feature_enabled = (
            proctoring_service and
            credit_service and
            self.is_time_limited
        )
        if feature_enabled:
            user_id = self.runtime.user_id
            user_role_in_course = 'staff' if self.runtime.user_is_staff else 'student'
            course_id = self.runtime.course_id
            content_id = self.location

            context = {
                'display_name': self.display_name,
                'default_time_limit_mins': (
                    self.default_time_limit_minutes if
                    self.default_time_limit_minutes else 0
                ),
                'is_practice_exam': self.is_practice_exam,
                'allow_proctoring_opt_out': self.allow_proctoring_opt_out,
                'due_date': self.due,
                'grace_period': self.graceperiod,  # lint-amnesty, pylint: disable=no-member
                'is_integrity_signature_enabled': is_integrity_signature_enabled(course_id),
            }

            # inject the user's credit requirements and fulfillments
            if credit_service:
                credit_state = credit_service.get_credit_state(user_id, course_id)
                if credit_state:
                    context.update({
                        'credit_state': credit_state
                    })

            # inject verification status
            if verification_service:
                verification_status = verification_service.get_status(user_id)
                context.update({
                    'verification_status': verification_status['status'],
                    'reverify_url': verification_service.reverify_url(),
                })

            # See if the edx-proctoring subsystem wants to present
            # a special view to the student rather
            # than the actual sequence content
            #
            # This will return None if there is no
            # overridden view to display given the
            # current state of the user
            view_html = proctoring_service.get_student_view(
                user_id=user_id,
                course_id=course_id,
                content_id=content_id,
                context=context,
                user_role=user_role_in_course
            )

        return view_html
Example #14
0
def _required_verification_missing(course_key, user):
    """
    Return true if IDV is required for this course and the user does not have it
    """
    return not is_integrity_signature_enabled(
        course_key) and not IDVerificationService.user_is_verified(user)
Example #15
0
def check_verify_status_by_course(user, course_enrollments):
    """
    Determine the per-course verification statuses for a given user.

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

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

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

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

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

    """
    status_by_course = {}

    # Before retriving verification data, if the integrity_signature feature is enabled for the course
    # let's bypass all logic below. Filter down to those course with integrity_signature not enabled.
    enabled_course_enrollments = []
    for enrollment in course_enrollments:
        if not is_integrity_signature_enabled(enrollment.course_id):
            enabled_course_enrollments.append(enrollment)

    if len(enabled_course_enrollments) == 0:
        return status_by_course

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

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

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

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

    for enrollment in enabled_course_enrollments:

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

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

            relevant_verification = verification_for_datetime(
                deadline, verifications)

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

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

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

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

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

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

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

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

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

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

    return status_by_course
Example #16
0
def student_dashboard(request):  # lint-amnesty, pylint: disable=too-many-statements
    """
    Provides the LMS dashboard view

    TODO: This is lms specific and does not belong in common code.
    Note:
        To load the all courses set course_limit=None as parameter in GET. If its not None then default course
        limit will be used  that is set in configuration
    Arguments:
        request: The request object.

    Returns:
        The dashboard response.

    """
    user = request.user
    if not UserProfile.objects.filter(user=user).exists():
        return redirect(reverse('account_settings'))

    platform_name = configuration_helpers.get_value("platform_name", settings.PLATFORM_NAME)

    enable_verified_certificates = configuration_helpers.get_value(
        'ENABLE_VERIFIED_CERTIFICATES',
        settings.FEATURES.get('ENABLE_VERIFIED_CERTIFICATES')
    )
    display_course_modes_on_dashboard = configuration_helpers.get_value(
        'DISPLAY_COURSE_MODES_ON_DASHBOARD',
        settings.FEATURES.get('DISPLAY_COURSE_MODES_ON_DASHBOARD', True)
    )
    activation_email_support_link = configuration_helpers.get_value(
        'ACTIVATION_EMAIL_SUPPORT_LINK', settings.ACTIVATION_EMAIL_SUPPORT_LINK
    ) or settings.SUPPORT_SITE_LINK
    hide_dashboard_courses_until_activated = configuration_helpers.get_value(
        'HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED',
        settings.FEATURES.get('HIDE_DASHBOARD_COURSES_UNTIL_ACTIVATED', False)
    )
    empty_dashboard_message = configuration_helpers.get_value(
        'EMPTY_DASHBOARD_MESSAGE', None
    )

    disable_course_limit = request and 'course_limit' in request.GET
    course_limit = get_dashboard_course_limit() if not disable_course_limit else None

    # Get the org whitelist or the org blacklist for the current site
    site_org_whitelist, site_org_blacklist = get_org_black_and_whitelist_for_site()
    course_enrollments = list(get_course_enrollments(user, site_org_whitelist, site_org_blacklist, course_limit))

    # Get the entitlements for the user and a mapping to all available sessions for that entitlement
    # If an entitlement has no available sessions, pass through a mock course overview object
    (course_entitlements,
     course_entitlement_available_sessions,
     unfulfilled_entitlement_pseudo_sessions) = get_filtered_course_entitlements(
        user,
        site_org_whitelist,
        site_org_blacklist
    )

    # Record how many courses there are so that we can get a better
    # understanding of usage patterns on prod.
    monitoring_utils.accumulate('num_courses', len(course_enrollments))

    # Sort the enrollment pairs by the enrollment date
    course_enrollments.sort(key=lambda x: x.created, reverse=True)

    # Retrieve the course modes for each course
    enrolled_course_ids = [enrollment.course_id for enrollment in course_enrollments]
    __, unexpired_course_modes = CourseMode.all_and_unexpired_modes_for_courses(enrolled_course_ids)
    course_modes_by_course = {
        course_id: {
            mode.slug: mode
            for mode in modes
        }
        for course_id, modes in unexpired_course_modes.items()
    }

    # Check to see if the student has recently enrolled in a course.
    # If so, display a notification message confirming the enrollment.
    enrollment_message = _create_recent_enrollment_message(
        course_enrollments, course_modes_by_course
    )
    course_optouts = Optout.objects.filter(user=user).values_list('course_id', flat=True)

    # Display activation message
    activate_account_message = ''
    if not user.is_active:
        activate_account_message = Text(_(
            "Check your {email_start}{email}{email_end} inbox for an account activation link from {platform_name}. "
            "If you need help, contact {link_start}{platform_name} Support{link_end}."
        )).format(
            platform_name=platform_name,
            email_start=HTML("<strong>"),
            email_end=HTML("</strong>"),
            email=user.email,
            link_start=HTML("<a target='_blank' href='{activation_email_support_link}'>").format(
                activation_email_support_link=activation_email_support_link,
            ),
            link_end=HTML("</a>"),
        )

    enterprise_message = get_dashboard_consent_notification(request, user, course_enrollments)

    recovery_email_message = recovery_email_activation_message = None
    if is_secondary_email_feature_enabled():
        try:
            pending_email = PendingSecondaryEmailChange.objects.get(user=user)  # lint-amnesty, pylint: disable=unused-variable
        except PendingSecondaryEmailChange.DoesNotExist:
            try:
                account_recovery_obj = AccountRecovery.objects.get(user=user)  # lint-amnesty, pylint: disable=unused-variable
            except AccountRecovery.DoesNotExist:
                recovery_email_message = Text(
                    _(
                        "Add a recovery email to retain access when single-sign on is not available. "
                        "Go to {link_start}your Account Settings{link_end}.")
                ).format(
                    link_start=HTML("<a href='{account_setting_page}'>").format(
                        account_setting_page=reverse('account_settings'),
                    ),
                    link_end=HTML("</a>")
                )
        else:
            recovery_email_activation_message = Text(
                _(
                    "Recovery email is not activated yet. "
                    "Kindly visit your email and follow the instructions to activate it."
                )
            )

    # Disable lookup of Enterprise consent_required_course due to ENT-727
    # Will re-enable after fixing WL-1315
    consent_required_courses = set()

    # Account activation message
    account_activation_messages = [
        message for message in messages.get_messages(request) if 'account-activation' in message.tags
    ]

    # Global staff can see what courses encountered an error on their dashboard
    staff_access = False
    errored_courses = {}
    if has_access(user, 'staff', 'global'):
        # Show any courses that encountered an error on load
        staff_access = True
        errored_courses = modulestore().get_errored_courses()

    show_courseware_links_for = {
        enrollment.course_id: has_access(request.user, 'load', enrollment.course_overview)
        for enrollment in course_enrollments
    }

    # Find programs associated with course runs being displayed. This information
    # is passed in the template context to allow rendering of program-related
    # information on the dashboard.
    meter = ProgramProgressMeter(request.site, user, enrollments=course_enrollments)
    ecommerce_service = EcommerceService()
    inverted_programs = meter.invert_programs()

    urls, programs_data = {}, {}
    bundles_on_dashboard_flag = LegacyWaffleFlag(experiments_namespace, 'bundles_on_dashboard', __name__)  # lint-amnesty, pylint: disable=toggle-missing-annotation

    # TODO: Delete this code and the relevant HTML code after testing LEARNER-3072 is complete
    if bundles_on_dashboard_flag.is_enabled() and inverted_programs and list(inverted_programs.items()):
        if len(course_enrollments) < 4:
            for program in inverted_programs.values():
                try:
                    program_uuid = program[0]['uuid']
                    program_data = get_programs(uuid=program_uuid)
                    program_data = ProgramDataExtender(program_data, request.user).extend()
                    skus = program_data.get('skus')
                    checkout_page_url = ecommerce_service.get_checkout_page_url(*skus)
                    program_data['completeProgramURL'] = checkout_page_url + '&bundle=' + program_data.get('uuid')
                    programs_data[program_uuid] = program_data
                except:  # pylint: disable=bare-except
                    pass

    # Construct a dictionary of course mode information
    # used to render the course list.  We re-use the course modes dict
    # we loaded earlier to avoid hitting the database.
    course_mode_info = {
        enrollment.course_id: complete_course_mode_info(
            enrollment.course_id, enrollment,
            modes=course_modes_by_course[enrollment.course_id]
        )
        for enrollment in course_enrollments
    }

    # Determine the per-course verification status
    # This is a dictionary in which the keys are course locators
    # and the values are one of:
    #
    # VERIFY_STATUS_NEED_TO_VERIFY
    # VERIFY_STATUS_SUBMITTED
    # VERIFY_STATUS_APPROVED
    # VERIFY_STATUS_MISSED_DEADLINE
    #
    # Each of which correspond to a particular message to display
    # next to the course on the dashboard.
    #
    # If a course is not included in this dictionary,
    # there is no verification messaging to display.
    verify_status_by_course = check_verify_status_by_course(user, course_enrollments)
    cert_statuses = {
        enrollment.course_id: cert_info(request.user, enrollment)
        for enrollment in course_enrollments
    }

    # only show email settings for Mongo course and when bulk email is turned on
    show_email_settings_for = frozenset(
        enrollment.course_id for enrollment in course_enrollments if (
            is_bulk_email_feature_enabled(enrollment.course_id)
        )
    )

    # Verification Attempts
    # Used to generate the "you must reverify for course x" banner
    verification_status = IDVerificationService.user_status(user)
    verification_errors = get_verification_error_reasons_for_display(verification_status['error'])

    # Gets data for midcourse reverifications, if any are necessary or have failed
    statuses = ["approved", "denied", "pending", "must_reverify"]
    reverifications = reverification_info(statuses)

    enrolled_courses_either_paid = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.is_paid_course()
    )

    # Checks if a course enrollment redeemed using a voucher is refundable
    enrolled_courses_voucher_refundable = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.is_order_voucher_refundable()
    )

    # If there are *any* denied reverifications that have not been toggled off,
    # we'll display the banner
    denied_banner = any(item.display for item in reverifications["denied"])

    # get list of courses having pre-requisites yet to be completed
    courses_having_prerequisites = frozenset(
        enrollment.course_id for enrollment in course_enrollments
        if enrollment.course_overview.pre_requisite_courses
    )
    courses_requirements_not_met = get_pre_requisite_courses_not_completed(user, courses_having_prerequisites)

    if 'notlive' in request.GET:
        redirect_message = _("The course you are looking for does not start until {date}.").format(
            date=request.GET['notlive']
        )
    elif 'course_closed' in request.GET:
        redirect_message = _("The course you are looking for is closed for enrollment as of {date}.").format(
            date=request.GET['course_closed']
        )
    elif 'access_response_error' in request.GET:
        # This can be populated in a generalized way with fields from access response errors
        redirect_message = request.GET['access_response_error']
    else:
        redirect_message = ''

    all_integrity_enabled = True
    if not course_enrollments:
        all_integrity_enabled = is_integrity_signature_enabled(None)
    for enrollment in course_enrollments:
        if not is_integrity_signature_enabled(enrollment.course_id):
            all_integrity_enabled = False
            break

    valid_verification_statuses = ['approved', 'must_reverify', 'pending', 'expired']
    display_sidebar_on_dashboard = not all_integrity_enabled and \
        verification_status['status'] in valid_verification_statuses and \
        verification_status['should_display']

    # Filter out any course enrollment course cards that are associated with fulfilled entitlements
    for entitlement in [e for e in course_entitlements if e.enrollment_course_run is not None]:
        course_enrollments = [
            enr for enr in course_enrollments if entitlement.enrollment_course_run.course_id != enr.course_id
        ]

    show_account_activation_popup = request.COOKIES.get(settings.SHOW_ACTIVATE_CTA_POPUP_COOKIE_NAME, None)

    context = {
        'urls': urls,
        'programs_data': programs_data,
        'enterprise_message': enterprise_message,
        'consent_required_courses': consent_required_courses,
        'enrollment_message': enrollment_message,
        'redirect_message': Text(redirect_message),
        'account_activation_messages': account_activation_messages,
        'activate_account_message': activate_account_message,
        'course_enrollments': course_enrollments,
        'course_entitlements': course_entitlements,
        'course_entitlement_available_sessions': course_entitlement_available_sessions,
        'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions,
        'course_optouts': course_optouts,
        'staff_access': staff_access,
        'errored_courses': errored_courses,
        'show_courseware_links_for': show_courseware_links_for,
        'all_course_modes': course_mode_info,
        'cert_statuses': cert_statuses,
        'credit_statuses': _credit_statuses(user, course_enrollments),
        'show_email_settings_for': show_email_settings_for,
        'reverifications': reverifications,
        'verification_display': verification_status['should_display'],
        'verification_status': verification_status['status'],
        'verification_expiry': verification_status['verification_expiry'],
        'verification_status_by_course': verify_status_by_course,
        'verification_errors': verification_errors,
        'denied_banner': denied_banner,
        'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
        'show_account_activation_popup': show_account_activation_popup,
        'user': user,
        'logout_url': reverse('logout'),
        'platform_name': platform_name,
        'enrolled_courses_either_paid': enrolled_courses_either_paid,
        'enrolled_courses_voucher_refundable': enrolled_courses_voucher_refundable,
        'provider_states': [],
        'courses_requirements_not_met': courses_requirements_not_met,
        'nav_hidden': True,
        'inverted_programs': inverted_programs,
        'show_program_listing': ProgramsApiConfig.is_enabled(),
        'show_dashboard_tabs': True,
        'disable_courseware_js': True,
        'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard,
        'display_sidebar_on_dashboard': display_sidebar_on_dashboard,
        'display_sidebar_account_activation_message': not(user.is_active or hide_dashboard_courses_until_activated),
        'display_dashboard_courses': (user.is_active or not hide_dashboard_courses_until_activated),
        'empty_dashboard_message': empty_dashboard_message,
        'recovery_email_message': recovery_email_message,
        'recovery_email_activation_message': recovery_email_activation_message,
        'show_load_all_courses_link': show_load_all_courses_link(user, course_limit, course_enrollments),
        # TODO START: clean up as part of REVEM-199 (START)
        'course_info': get_dashboard_course_info(user, course_enrollments),
        # TODO START: clean up as part of REVEM-199 (END)
    }

    # Include enterprise learner portal metadata and messaging
    enterprise_learner_portal_context = get_enterprise_learner_portal_context(request)
    context.update(enterprise_learner_portal_context)

    context_from_plugins = get_plugins_view_context(
        ProjectType.LMS,
        COURSE_DASHBOARD_PLUGIN_VIEW_NAME,
        context
    )
    context.update(context_from_plugins)

    notice_url = check_for_unacknowledged_notices(context)
    if notice_url:
        return redirect(notice_url)

    course = None
    context.update(
        get_experiment_user_metadata_context(
            course,
            user,
        )
    )
    if ecommerce_service.is_enabled(request.user):
        context.update({
            'use_ecommerce_payment_flow': True,
            'ecommerce_payment_page': ecommerce_service.payment_page_url(),
        })

    # Gather urls for course card resume buttons.
    resume_button_urls = ['' for entitlement in course_entitlements]
    for url in get_resume_urls_for_enrollments(user, course_enrollments).values():
        resume_button_urls.append(url)
    # There must be enough urls for dashboard.html. Template creates course
    # cards for "enrollments + entitlements".
    context.update({
        'resume_button_urls': resume_button_urls
    })

    response = render_to_response('dashboard.html', context)
    if show_account_activation_popup:
        response.delete_cookie(
            settings.SHOW_ACTIVATE_CTA_POPUP_COOKIE_NAME,
            domain=settings.SESSION_COOKIE_DOMAIN,
            path='/',
        )

    return response