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
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)
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)
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) )
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
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(), }
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
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)
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 }
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)
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)
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
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
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)
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
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