""" Toggles for verify_student app """ from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, WaffleFlag # Namespace for verify_students waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='verify_student') # Waffle flag to use new email templates for sending ID verification emails. # .. toggle_name: verify_student.use_new_email_templates # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Supports staged rollout to students for a new email templates implementation for ID verification. # .. toggle_category: verify student # .. toggle_use_cases: incremental_release, open_edx # .. toggle_creation_date: 2020-06-25 # .. toggle_expiration_date: n/a # .. toggle_warnings: n/a # .. toggle_tickets: PROD-1639 # .. toggle_status: supported USE_NEW_EMAIL_TEMPLATES = WaffleFlag( waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name='use_new_email_templates', ) def use_new_templates_for_id_verification_emails(): return USE_NEW_EMAIL_TEMPLATES.is_enabled()
""" Portfolio project helpers and settings """ from openedx.core.djangoapps.waffle_utils import (CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace) # Namespace for portfolio project waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='portfolio_project') # https://openedx.atlassian.net/browse/LEARNER-3926 INCLUDE_PORTFOLIO_UPSELL_MODAL = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'include_upsell_modal')
from django.utils import timezone from course_modes.models import CourseMode from courseware.models import DynamicUpgradeDeadlineConfiguration, CourseDynamicUpgradeDeadlineConfiguration from edx_ace.utils import date from openedx.core.djangoapps.signals.signals import COURSE_START_DATE_CHANGED from openedx.core.djangoapps.theming.helpers import get_current_site from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, CourseWaffleFlag from student.models import CourseEnrollment from .models import Schedule, ScheduleConfig from .tasks import update_course_schedules log = logging.getLogger(__name__) SCHEDULE_WAFFLE_FLAG = CourseWaffleFlag( waffle_namespace=WaffleFlagNamespace('schedules'), flag_name='create_schedules_for_course', flag_undefined_default=False) @receiver(post_save, sender=CourseEnrollment, dispatch_uid='create_schedule_for_enrollment') def create_schedule(sender, **kwargs): if not kwargs['created']: # only create schedules when enrollment records are created return current_site = get_current_site() if current_site is None: log.debug('Schedules: No current site')
def waffle_flags(): """ Returns the namespaced, cached, audited Waffle Flag class for Studio pages. """ return WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Studio: ')
def get(self, request, course_id, error=None): """Displays the course mode choice page. Args: request (`Request`): The Django Request object. course_id (unicode): The slash-separated course key. Keyword Args: error (unicode): If provided, display this error message on the page. Returns: Response """ course_key = CourseKey.from_string(course_id) # Check whether the user has access to this course # based on country access rules. embargo_redirect = embargo_api.redirect_if_blocked( course_key, user=request.user, ip_address=get_ip(request), url=request.path) if embargo_redirect: return redirect(embargo_redirect) enrollment_mode, is_active = CourseEnrollment.enrollment_mode_for_user( request.user, course_key) modes = CourseMode.modes_for_course_dict(course_key) ecommerce_service = EcommerceService() # We assume that, if 'professional' is one of the modes, it should be the *only* mode. # If there are both modes, default to non-id-professional. has_enrolled_professional = ( CourseMode.is_professional_slug(enrollment_mode) and is_active) if CourseMode.has_professional_mode( modes) and not has_enrolled_professional: purchase_workflow = request.GET.get("purchase_workflow", "single") verify_url = reverse('verify_student_start_flow', kwargs={'course_id': unicode(course_key)}) redirect_url = "{url}?purchase_workflow={workflow}".format( url=verify_url, workflow=purchase_workflow) if ecommerce_service.is_enabled(request.user): professional_mode = modes.get( CourseMode.NO_ID_PROFESSIONAL_MODE) or modes.get( CourseMode.PROFESSIONAL) if purchase_workflow == "single" and professional_mode.sku: redirect_url = ecommerce_service.get_checkout_page_url( professional_mode.sku) if purchase_workflow == "bulk" and professional_mode.bulk_sku: redirect_url = ecommerce_service.get_checkout_page_url( professional_mode.bulk_sku) return redirect(redirect_url) course = modulestore().get_course(course_key) # If there isn't a verified mode available, then there's nothing # to do on this page. Send the user to the dashboard. if not CourseMode.has_verified_mode(modes): return redirect(reverse('dashboard')) # If a user has already paid, redirect them to the dashboard. if is_active and (enrollment_mode in CourseMode.VERIFIED_MODES + [CourseMode.NO_ID_PROFESSIONAL_MODE]): # If the course has started redirect to course home instead if course.has_started(): return redirect( reverse('openedx.course_experience.course_home', kwargs={'course_id': course_key})) return redirect(reverse('dashboard')) donation_for_course = request.session.get("donation_for_course", {}) chosen_price = donation_for_course.get(unicode(course_key), None) if CourseEnrollment.is_enrollment_closed(request.user, course): locale = to_locale(get_language()) enrollment_end_date = format_datetime(course.enrollment_end, 'short', locale=locale) params = urllib.urlencode({'course_closed': enrollment_end_date}) return redirect('{0}?{1}'.format(reverse('dashboard'), params)) # When a credit mode is available, students will be given the option # to upgrade from a verified mode to a credit mode at the end of the course. # This allows students who have completed photo verification to be eligible # for univerity credit. # Since credit isn't one of the selectable options on the track selection page, # we need to check *all* available course modes in order to determine whether # a credit mode is available. If so, then we show slightly different messaging # for the verified track. has_credit_upsell = any( CourseMode.is_credit_mode(mode) for mode in CourseMode.modes_for_course(course_key, only_selectable=False)) course_id = text_type(course_key) bundle_data = {} bundles_on_track_selection = WaffleFlag( WaffleFlagNamespace(name=u'experiments'), u'bundles_on_track_selection') if bundles_on_track_selection.is_enabled(): # enrollment in the course on this page current_enrollment = list( CourseEnrollment.enrollments_for_user( request.user).filter(course_id=course_key)) if current_enrollment: meter = ProgramProgressMeter(request.site, request.user, enrollments=current_enrollment) meter_inverted_programs = meter.invert_programs() if len(meter_inverted_programs) > 0: # program for the course on this page programs_for_course = meter_inverted_programs.get( course_id) if programs_for_course: program_for_course = programs_for_course[0] program_uuid = program_for_course.get('uuid') if program_for_course: # program data with bundle info program_data = ProgramDataExtender( program_for_course, request.user, mobile_only=False).extend() skus = program_data.get('skus') ecommerce_service = EcommerceService() program_bundle_url = ecommerce_service.get_checkout_page_url( *skus, program_uuid=program_uuid) bundle_data = { 'program_marketing_site_url': program_data.get('marketing_url'), 'program_bundle_url': program_bundle_url, 'discount_data': program_data.get('discount_data'), 'program_type': program_data.get('type'), 'program_title': program_data.get('title'), 'program_price': program_data.get('full_program_price'), } context = { "bundle_data": bundle_data, "course_modes_choose_url": reverse("course_modes_choose", kwargs={'course_id': course_id}), "modes": modes, "has_credit_upsell": has_credit_upsell, "course_name": course.display_name_with_default, "course_org": course.display_org_with_default, "course_num": course.display_number_with_default, "chosen_price": chosen_price, "error": error, "responsive": True, "nav_hidden": True, "content_gating_enabled": ContentTypeGatingConfig.enabled_for_enrollment( user=request.user, course_key=course_key), } context.update( get_experiment_user_metadata_context( course, request.user, )) title_content = _( "Congratulations! You are now enrolled in {course_name}").format( course_name=course.display_name_with_default) context["title_content"] = title_content if "verified" in modes: verified_mode = modes["verified"] context["suggested_prices"] = [ decimal.Decimal(x.strip()) for x in verified_mode.suggested_prices.split(",") if x.strip() ] context["currency"] = verified_mode.currency.upper() context["min_price"] = verified_mode.min_price context["verified_name"] = verified_mode.name context["verified_description"] = verified_mode.description if verified_mode.sku: context[ "use_ecommerce_payment_flow"] = ecommerce_service.is_enabled( request.user) context[ "ecommerce_payment_page"] = ecommerce_service.payment_page_url( ) context["sku"] = verified_mode.sku context["bulk_sku"] = verified_mode.bulk_sku context['currency_data'] = [] if waffle.switch_is_active('local_currency'): if 'edx-price-l10n' not in request.COOKIES: currency_data = get_currency_data() try: context['currency_data'] = json.dumps(currency_data) except TypeError: pass return render_to_response("course_modes/choose.html", context)
""" Toggles for Learner Profile page. """ from __future__ import absolute_import from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace # Namespace for learner profile waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='learner_profile') # Waffle flag to redirect to another learner profile experience. # .. toggle_name: learner_profile.redirect_to_microfrontend # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the profile page. # .. toggle_category: micro-frontend # .. toggle_use_cases: incremental_release, open_edx # .. toggle_creation_date: 2019-02-19 # .. toggle_expiration_date: 2020-12-31 # .. toggle_warnings: Also set settings.PROFILE_MICROFRONTEND_URL and site's ENABLE_PROFILE_MICROFRONTEND. # .. toggle_tickets: DEPR-17 # .. toggle_status: supported REDIRECT_TO_PROFILE_MICROFRONTEND = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'redirect_to_microfrontend') def should_redirect_to_profile_microfrontend(): return (configuration_helpers.get_value('ENABLE_PROFILE_MICROFRONTEND') and REDIRECT_TO_PROFILE_MICROFRONTEND.is_enabled())
""" Learner analytics helpers and settings """ from openedx.core.djangoapps.waffle_utils import (CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace) # Namespace for learner analytics waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='learner_analytics') # Simple safety valve in case the modal breaks DOM. INCLUDE_UPSELL_MODAL = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'include_upsell_modal') # Enables the learner analytics page for different courses via waffle course overrides. ENABLE_DASHBOARD_TAB = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'enable_dashboard_tab')
""" Unified course experience settings and helper methods. """ import crum from django.utils.translation import ugettext as _ from edx_django_utils.monitoring import set_custom_attribute from waffle import flag_is_active from lms.djangoapps.experiments.flags import ExperimentWaffleFlag from openedx.core.djangoapps.util.user_messages import UserMessageCollection from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag # Namespace for course experience waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience') COURSE_EXPERIENCE_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience') # Waffle flag to disable the separate course outline page and full width content. DISABLE_COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag( COURSE_EXPERIENCE_WAFFLE_FLAG_NAMESPACE, 'disable_course_outline_page', __name__ ) # Waffle flag to enable a single unified "Course" tab. DISABLE_UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag( COURSE_EXPERIENCE_WAFFLE_FLAG_NAMESPACE, 'disable_unified_course_tab', __name__ ) # Waffle flag to enable the sock on the footer of the home and courseware pages. DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'display_course_sock', __name__)
"""Utilities to assist with commerce tasks.""" from urllib import urlencode from urlparse import urljoin import waffle from django.conf import settings from commerce.models import CommerceConfiguration from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace COMMERCE_API_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='commerce_api') def is_account_activation_requirement_disabled(): """ Checks to see if the django-waffle switch for disabling the account activation requirement is active Returns: Boolean value representing switch status """ switch_name = configuration_helpers.get_value( 'DISABLE_ACCOUNT_ACTIVATION_REQUIREMENT_SWITCH', settings.DISABLE_ACCOUNT_ACTIVATION_REQUIREMENT_SWITCH) return waffle.switch_is_active(switch_name) class EcommerceService(object): """ Helper class for ecommerce service integration. """ def __init__(self): self.config = CommerceConfiguration.current()
""" Discussion settings and flags. """ from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace # Namespace for course experience waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='edx_discussions') # Waffle flag to enable the use of Bootstrap USE_BOOTSTRAP_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'use_bootstrap')
from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, CourseWaffleFlag, WaffleFlag WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name=u'schedules') CREATE_SCHEDULE_WAFFLE_FLAG = CourseWaffleFlag( waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name=u'create_schedules_for_course', flag_undefined_default=False) COURSE_UPDATE_WAFFLE_FLAG = CourseWaffleFlag( waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name=u'send_updates_for_course', flag_undefined_default=False) DEBUG_MESSAGE_WAFFLE_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, u'enable_debugging')
""" This module contains various configuration settings via waffle switches for the instructor_task app. """ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace WAFFLE_NAMESPACE = 'instructor_task' INSTRUCTOR_TASK_WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace( name=WAFFLE_NAMESPACE) WAFFLE_SWITCHES = WaffleSwitchNamespace(name=WAFFLE_NAMESPACE) # Waffle switches OPTIMIZE_GET_LEARNERS_FOR_COURSE = 'optimize_get_learners_for_course' # Course override flags GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY = 'generate_problem_grade_report_verified_only' GENERATE_COURSE_GRADE_REPORT_VERIFIED_ONLY = 'generate_course_grade_report_verified_only' def waffle_flags(): """ Returns the namespaced, cached, audited Waffle flags dictionary for Grades. """ return { GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY: CourseWaffleFlag( waffle_namespace=INSTRUCTOR_TASK_WAFFLE_FLAG_NAMESPACE, flag_name=GENERATE_PROBLEM_GRADE_REPORT_VERIFIED_ONLY, module_name=__name__, ),
""" Enrollment API helpers and settings """ from openedx.core.djangoapps.waffle_utils import (WaffleFlag, WaffleFlagNamespace) WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='enrollment_api_rate_limit') REDUCE_RATE_LIMIT_FOR_STAFF_FOR_ENROLLMENT_API = WaffleFlag( WAFFLE_FLAG_NAMESPACE, 'reduce_staff_rate_limit') USE_UNIVERSAL_RATE_LIMIT_FOR_ENROLLMENT_API = WaffleFlag( WAFFLE_FLAG_NAMESPACE, 'use_universal_rate_limit')
from openedx.core.djangoapps.util.maintenance_banner import add_maintenance_banner from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace from openedx.core.djangolib.markup import HTML, Text from openedx.features.enterprise_support.api import get_dashboard_consent_notification from shoppingcart.models import DonationConfiguration from student.api import COURSE_DASHBOARD_PLUGIN_VIEW_NAME from student.helpers import cert_info, check_verify_status_by_course, get_resume_urls_for_enrollments from student.models import (AccountRecovery, CourseEnrollment, CourseEnrollmentAttribute, DashboardConfiguration, PendingSecondaryEmailChange, UserProfile) from util.milestones_helpers import get_pre_requisite_courses_not_completed from xmodule.modulestore.django import modulestore log = logging.getLogger("edx.student") experiments_namespace = WaffleFlagNamespace(name=u'student.experiments') def get_org_black_and_whitelist_for_site(): """ Returns the org blacklist and whitelist for the current site. Returns: (org_whitelist, org_blacklist): A tuple of lists of orgs that serve as either a blacklist or a whitelist of orgs for the current site. The whitelist takes precedence, and the blacklist is used if the whitelist is None. """ # Default blacklist is empty. org_blacklist = None # Whitelist the orgs configured for the current site. Each site outside
""" Content type gating waffle flag """ import random from django.dispatch import receiver from django.db import IntegrityError from experiments.models import ExperimentData, ExperimentKeyValue from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, WaffleFlag from student.models import EnrollStatusChange from student.signals import ENROLL_STATUS_CHANGE WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name=u'content_type_gating') CONTENT_TYPE_GATING_FLAG = WaffleFlag(waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name=u'debug', flag_undefined_default=False) FEATURE_BASED_ENROLLMENT_GLOBAL_KILL_FLAG = WaffleFlag( waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name=u'global_kill_switch', flag_undefined_default=False) EXPERIMENT_ID = 11 EXPERIMENT_DATA_HOLDBACK_KEY = 'holdback' @receiver(ENROLL_STATUS_CHANGE) def set_value_for_content_type_gating_holdback(sender, event=None,
def enable_anonymous_courseware_access(self): waffle_flag = CourseWaffleFlag(WaffleFlagNamespace(name='seo'), 'enable_anonymous_courseware_access') return waffle_flag.is_enabled(self.course_key)
""" This module contains various configuration settings via waffle switches for the completion app. """ from __future__ import absolute_import, division, print_function, unicode_literals from openedx.core.djangoapps.site_configuration.models import SiteConfiguration from openedx.core.djangoapps.theming.helpers import get_current_site from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace # Namespace WAFFLE_NAMESPACE = 'completion' WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='completion') # Switches # Full name: completion.enable_completion_tracking # Indicates whether or not to track completion of individual blocks. Keeping # this disabled will prevent creation of BlockCompletion objects in the # database, as well as preventing completion-related network access by certain # xblocks. ENABLE_COMPLETION_TRACKING = 'enable_completion_tracking' # Full name completion.enable_visual_progress # Overrides completion.enable_course_visual_progress # Acts as a global override -- enable visual progress indicators # sitewide. ENABLE_VISUAL_PROGRESS = 'enable_visual_progress' # Full name completion.enable_course_visual_progress # Acts as a course-by-course enabling of visual progress # indicators, e.g. updated 'resume button' functionality
""" Toggles for instructor app """ from openedx.core.djangoapps.waffle_utils import WaffleFlagNamespace, WaffleFlag # Namespace for instructor waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='instructor') # Waffle flag to use optimised is_small_course. # .. toggle_name: verify_student.optimised_is_small_course # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: Supports staged rollout to improved is_small_course method. # .. toggle_category: instructor # .. toggle_use_cases: incremental_release, open_edx # .. toggle_creation_date: 2020-07-02 # .. toggle_expiration_date: n/a # .. toggle_warnings: n/a # .. toggle_tickets: PROD-1740 # .. toggle_status: supported OPTIMISED_IS_SMALL_COURSE = WaffleFlag(waffle_namespace=WAFFLE_FLAG_NAMESPACE, flag_name='optimised_is_small_course', flag_undefined_default=False) def use_optimised_is_small_course(): return OPTIMISED_IS_SMALL_COURSE.is_enabled()
""" Toggles for Course API. """ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace COURSE_BLOCKS_API_NAMESPACE = WaffleFlagNamespace(name=u'course_blocks_api') # Waffle flag to hide access denial message. # .. toggle_name: course_blocks_api.hide_access_denials # .. toggle_implementation: WaffleFlag # .. toggle_default: False # .. toggle_description: ?? # .. toggle_category: course api # .. toggle_use_cases: incremental_release, open_edx # .. toggle_creation_date: 2019-04-10 # .. toggle_expiration_date: ?? # .. toggle_warnings: ?? # .. toggle_tickets: ?? # .. toggle_status: ?? HIDE_ACCESS_DENIALS_FLAG = WaffleFlag( waffle_namespace=COURSE_BLOCKS_API_NAMESPACE, flag_name=u'hide_access_denials', )
""" Unified course experience settings and helper methods. """ from django.utils.translation import ugettext as _ from lms.djangoapps.experiments.flags import ExperimentWaffleFlag from openedx.core.djangoapps.util.user_messages import UserMessageCollection from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlag, WaffleFlagNamespace # Namespace for course experience waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='course_experience') # Waffle flag to enable the separate course outline page and full width content. COURSE_OUTLINE_PAGE_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'course_outline_page', flag_undefined_default=True) # Waffle flag to enable a single unified "Course" tab. UNIFIED_COURSE_TAB_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'unified_course_tab', flag_undefined_default=True) # Waffle flag to enable the sock on the footer of the home and courseware pages. DISPLAY_COURSE_SOCK_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE, 'display_course_sock') # Waffle flag to let learners access a course before its start date. COURSE_PRE_START_ACCESS_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'pre_start_access')
# used to announce a registration REGISTER_USER = Signal(providing_args=["user", "registration"]) # .. feature_toggle_name: registration.enable_failure_logging # .. feature_toggle_type: flag # .. feature_toggle_default: False # .. feature_toggle_description: Enable verbose logging of registration failure messages # .. feature_toggle_category: registration # .. feature_toggle_use_cases: monitored_rollout # .. feature_toggle_creation_date: 2020-04-30 # .. feature_toggle_expiration_date: 2020-06-01 # .. feature_toggle_warnings: None # .. feature_toggle_tickets: None # .. feature_toggle_status: supported REGISTRATION_FAILURE_LOGGING_FLAG = WaffleFlag( waffle_namespace=WaffleFlagNamespace(name=u'registration'), flag_name=u'enable_failure_logging', ) @transaction.non_atomic_requests def create_account_with_params(request, params): """ Given a request and a dict of parameters (which may or may not have come from the request), create an account for the requesting user, including creating a comments service user object and sending an activation email. This also takes external/third-party auth into account, updates that as necessary, and authenticates the user for the request's session. Does not return anything.
def student_dashboard(request): """ Provides the LMS dashboard view TODO: This is lms specific and does not belong in common code. 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) # 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)) # 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 iteritems(unexpired_course_modes) } # 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) # Disable lookup of Enterprise consent_required_course due to ENT-727 # Will re-enable after fixing WL-1315 consent_required_courses = set() enterprise_customer_name = None # 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 = WaffleFlag( WaffleFlagNamespace(name=u'student.experiments'), u'bundles_on_dashboard') # 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 inverted_programs.items(): if len(course_enrollments) < 4: for program in inverted_programs.values(): try: program_uuid = program[0]['uuid'] program_data = get_programs(request.site, 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.course_overview) 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 (BulkEmailFlag.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) block_courses = frozenset( enrollment.course_id for enrollment in course_enrollments if is_course_blocked( request, CourseRegistrationCode.objects.filter( course_id=enrollment.course_id, registrationcoderedemption__redeemed_by=request.user), enrollment.course_id)) enrolled_courses_either_paid = frozenset( enrollment.course_id for enrollment in course_enrollments if enrollment.is_paid_course()) # 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"]) # Populate the Order History for the side-bar. order_history_list = order_history(user, course_org_filter=site_org_whitelist, org_filter_out_set=site_org_blacklist) # 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 = '' valid_verification_statuses = [ 'approved', 'must_reverify', 'pending', 'expired' ] display_sidebar_on_dashboard = ( len(order_history_list) or (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 ] context = { 'urls': urls, 'programs_data': programs_data, 'enterprise_message': enterprise_message, 'consent_required_courses': consent_required_courses, 'enterprise_customer_name': enterprise_customer_name, 'enrollment_message': enrollment_message, 'redirect_message': 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_status_by_course': verify_status_by_course, 'verification_errors': verification_errors, 'block_courses': block_courses, 'denied_banner': denied_banner, 'billing_email': settings.PAYMENT_SUPPORT_EMAIL, 'user': user, 'logout_url': reverse('logout'), 'platform_name': platform_name, 'enrolled_courses_either_paid': enrolled_courses_either_paid, 'provider_states': [], 'order_history_list': order_history_list, 'courses_requirements_not_met': courses_requirements_not_met, 'nav_hidden': True, 'inverted_programs': inverted_programs, 'show_program_listing': ProgramsApiConfig.is_enabled(), 'show_journal_listing': journals_enabled(), # TODO: Dashboard Plugin required '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, } 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_urls_for_resume_buttons(user, course_enrollments): 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) set_deprecated_user_info_cookie(response, request, user) # pylint: disable=protected-access return response
""" from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace # .. feature_toggle_name: discounts.enable_discounting # .. feature_toggle_type: flag # .. feature_toggle_default: False # .. feature_toggle_description: Toggle discounts always being disabled # .. feature_toggle_category: discounts # .. feature_toggle_use_cases: monitored_rollout # .. feature_toggle_creation_date: 2019-4-16 # .. feature_toggle_expiration_date: None # .. feature_toggle_warnings: None # .. feature_toggle_tickets: REVEM-282 # .. feature_toggle_status: supported DISCOUNT_APPLICABILITY_FLAG = WaffleFlag( waffle_namespace=WaffleFlagNamespace(name=u'discounts'), flag_name=u'enable_discounting', flag_undefined_default=False) def can_receive_discount(user, course_key_string): # pylint: disable=unused-argument """ Check all the business logic about whether this combination of user and course can receive a discount. """ # Always disable discounts until we are ready to enable this feature if not DISCOUNT_APPLICABILITY_FLAG.is_enabled(): return False # TODO: Add additional conditions to return False here
""" edX Platform support for credentials. This package will be used as a wrapper for interacting with the credentials service. """ from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='credentials') # Waffle flag to enable the experimental Student Records feature STUDENT_RECORDS_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'student_records')
from opaque_keys.edx.keys import CourseKey from six.moves.urllib.parse import urlparse from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace try: import newrelic.agent except ImportError: newrelic = None # pylint: disable=invalid-name # accommodates course api urls, excluding any course api routes that do not fall under v*/courses, such as v1/blocks. COURSE_REGEX = re.compile(r'^(.*?/courses/)(?!v[0-9]+/[^/]+){}'.format( settings.COURSE_ID_PATTERN)) WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='request_utils') CAPTURE_COOKIE_SIZES = WaffleFlag(WAFFLE_FLAG_NAMESPACE, 'capture_cookie_sizes', __name__) log = logging.getLogger(__name__) def get_request_or_stub(): """ Return the current request or a stub request. If called outside the context of a request, construct a fake request that can be used to build an absolute URI. This is useful in cases where we need to pass in a request object but don't have an active request (for example, in tests, celery tasks, and XBlocks). """
'video_encodings_download', 'video_images_handler', 'transcript_preferences_handler', ] LOGGER = logging.getLogger(__name__) # Waffle switches namespace for videos WAFFLE_NAMESPACE = 'videos' WAFFLE_SWITCHES = WaffleSwitchNamespace(name=WAFFLE_NAMESPACE) # Waffle switch for enabling/disabling video image upload feature VIDEO_IMAGE_UPLOAD_ENABLED = 'video_image_upload_enabled' # Waffle flag namespace for studio WAFFLE_STUDIO_FLAG_NAMESPACE = WaffleFlagNamespace(name=u'studio') ENABLE_VIDEO_UPLOAD_PAGINATION = CourseWaffleFlag( waffle_namespace=WAFFLE_STUDIO_FLAG_NAMESPACE, flag_name=u'enable_video_upload_pagination', flag_undefined_default=False) # Default expiration, in seconds, of one-time URLs used for uploading videos. KEY_EXPIRATION_IN_SECONDS = 86400 VIDEO_SUPPORTED_FILE_FORMATS = { '.mp4': 'video/mp4', '.mov': 'video/quicktime', } VIDEO_UPLOAD_MAX_FILE_SIZE_GB = 5
""" Toggles for courseware in-course experience. """ from django.conf import settings from lms.djangoapps.experiments.flags import ExperimentWaffleFlag from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace # Namespace for courseware waffle flags. WAFFLE_FLAG_NAMESPACE = WaffleFlagNamespace(name='courseware') # Waffle flag to redirect to another learner profile experience. # .. toggle_name: courseware.courseware_mfe # .. toggle_implementation: ExperimentWaffleFlag # .. toggle_default: False # .. toggle_description: Supports staged rollout to students for a new micro-frontend-based implementation of the courseware page. # .. toggle_category: micro-frontend # .. toggle_use_cases: incremental_release, open_edx # .. toggle_creation_date: 2020-01-29 # .. toggle_expiration_date: 2020-12-31 # .. toggle_warnings: Also set settings.LEARNING_MICROFRONTEND_URL and ENABLE_COURSEWARE_MICROFRONTEND. # .. toggle_tickets: TNL-7000 # .. toggle_status: supported REDIRECT_TO_COURSEWARE_MICROFRONTEND = ExperimentWaffleFlag( WAFFLE_FLAG_NAMESPACE, 'courseware_mfe') # Waffle flag to display a link for the new learner experience to course teams without redirecting students. # # .. toggle_name: courseware.microfrontend_course_team_preview # .. toggle_implementation: CourseWaffleFlag # .. toggle_default: False
from course_modes.models import get_cosmetic_verified_display_price, format_course_price from courseware.access import has_staff_access_to_preview_mode from courseware.date_summary import verified_upgrade_deadline_link, verified_upgrade_link_is_valid from xmodule.partitions.partitions_service import get_user_partition_groups, get_all_partitions_for_course from opaque_keys.edx.keys import CourseKey from opaque_keys import InvalidKeyError from openedx.core.djangoapps.catalog.utils import get_programs from openedx.core.djangoapps.django_comment_common.models import Role from openedx.core.djangoapps.waffle_utils import WaffleFlag, WaffleFlagNamespace from openedx.features.course_duration_limits.access import get_user_course_expiration_date from openedx.features.course_duration_limits.models import CourseDurationLimitConfig logger = logging.getLogger(__name__) # TODO: clean up as part of REVEM-199 (START) experiments_namespace = WaffleFlagNamespace(name=u'experiments') # .. toggle_name: experiments.add_programs # .. toggle_type: feature_flag # .. toggle_default: True # .. toggle_description: Toggle for adding the current course's program information to user metadata # .. toggle_category: experiments # .. toggle_use_cases: monitored_rollout # .. toggle_creation_date: 2019-2-25 # .. toggle_expiration_date: None # .. toggle_warnings: None # .. toggle_tickets: REVEM-63, REVEM-198 # .. toggle_status: supported PROGRAM_INFO_FLAG = WaffleFlag(waffle_namespace=experiments_namespace, flag_name=u'add_programs', flag_undefined_default=True)
""" This module contains various configuration settings via waffle switches for the Discussions app. """ from openedx.core.djangoapps.waffle_utils import CourseWaffleFlag, WaffleFlagNamespace, WaffleSwitchNamespace # Namespace WAFFLE_NAMESPACE = u'discussions' # Switches FORUM_RESPONSE_NOTIFICATIONS = u'forum_response_notifications' SEND_NOTIFICATIONS_FOR_COURSE = CourseWaffleFlag( waffle_namespace=WaffleFlagNamespace(name=WAFFLE_NAMESPACE), flag_name=u'send_notifications_for_course', flag_undefined_default=False) def waffle(): """ Returns the namespaced, cached, audited Waffle class for Discussions. """ return WaffleSwitchNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Discussions: ')