Exemple #1
0
"""
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()
Exemple #2
0
"""
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')
Exemple #3
0
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')
Exemple #4
0
def waffle_flags():
    """
    Returns the namespaced, cached, audited Waffle Flag class for Studio pages.
    """
    return WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Studio: ')
Exemple #5
0
    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)
Exemple #6
0
"""
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())
Exemple #7
0
"""
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__)
Exemple #9
0
"""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()
Exemple #10
0
"""
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')
Exemple #11
0
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')
Exemple #12
0
"""
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__,
        ),
Exemple #13
0
"""
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')
Exemple #14
0
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
Exemple #15
0
"""
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,
Exemple #16
0
 def enable_anonymous_courseware_access(self):
     waffle_flag = CourseWaffleFlag(WaffleFlagNamespace(name='seo'), 'enable_anonymous_courseware_access')
     return waffle_flag.is_enabled(self.course_key)
Exemple #17
0
"""
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
Exemple #18
0
"""
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()
Exemple #19
0
"""
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')
Exemple #21
0
# 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
Exemple #23
0
"""
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
Exemple #24
0
"""
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')
Exemple #25
0
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).
    """
Exemple #26
0
    '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
Exemple #27
0
"""
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
Exemple #28
0
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)
Exemple #29
0
"""
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: ')