Exemple #1
0
 def test_name_validation(self):
     WaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
         "namespaced.name", module_name="module1"
     )
     with self.assertRaises(ValueError):
         WaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
             "non_namespaced", module_name="module1"
         )
Exemple #2
0
    def setUp(self):
        super().setUp()
        flag_name = "test_namespace.test_flag"
        self.waffle_flag = WaffleFlag(flag_name, __name__)

        request = RequestFactory().request()
        crum.set_current_request(request)
        RequestCache.clear_all_namespaces()

        self.addCleanup(crum.set_current_request, None)
        self.addCleanup(RequestCache.clear_all_namespaces)
Exemple #3
0
    def setUp(self):
        super().setUp()
        flag_name = "test_namespace.test_flag"
        self.waffle_flag = WaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
            flag_name, __name__
        )

        request = RequestFactory().request()
        crum.set_current_request(request)
        RequestCache.clear_all_namespaces()

        self.addCleanup(crum.set_current_request, None)
        self.addCleanup(RequestCache.clear_all_namespaces)
Exemple #4
0
 def _waffle_flag(flag_name):
     """
     Return a ``WaffleFlag`` object in WAFFLE_NAMESPACE
     with the given ``flag_name``.
     """
     WaffleFlag = import_waffle_flag()  # pylint: disable=invalid-name
     # pylint: disable=toggle-missing-annotation
     return WaffleFlag(f"{WAFFLE_NAMESPACE}.{flag_name}", module_name=__name__)
Exemple #5
0
def _add_waffle_flag_instances(flags_dict):
    """
    Add details from waffle flag instances, like code_owner.
    """
    waffle_flag_instances = WaffleFlag.get_instances()
    for flag_instance in waffle_flag_instances:
        flag = get_or_create_toggle_response(flags_dict, flag_instance.name)
        _add_toggle_instance_details(flag, flag_instance)
Exemple #6
0
 def _waffle_flag(flag_name):
     """
     Return a ``WaffleFlag`` object in WAFFLE_NAMESPACE
     with the given ``flag_name``.
     """
     WaffleFlag = import_waffle_flag()  # pylint: disable=invalid-name
     # pylint: disable=feature-toggle-needs-doc
     return WaffleFlag(f"{WAFFLE_NAMESPACE}.{flag_name}",
                       module_name=__name__)
Exemple #7
0
    def test_code_owners_without_module_information(self):
        # Create a waffle flag without any associated module_name
        waffle_flag = WaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
            "test.flag2",
            module_name="module1")
        report = ToggleStateReport().as_dict()

        result = [
            flag for flag in report["waffle_flags"]
            if flag["name"] == waffle_flag.name
        ][0]
        self.assertNotIn("code_owner", result)
Exemple #8
0
    def test_interlocked_overrides(self):
        waffle_flag1 = self.waffle_flag
        waffle_flag2 = WaffleFlag(waffle_flag1.name + "2", __name__)
        waffle_flag2.cached_flags()[waffle_flag2.name] = True

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())

        with override_waffle_flag(waffle_flag1, True):
            with override_waffle_flag(waffle_flag2, False):
                self.assertTrue(waffle_flag1.is_enabled())
                self.assertFalse(waffle_flag2.is_enabled())

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())
Exemple #9
0
def waffle_flags():
    """
    Returns the namespaced, cached, audited Waffle flags dictionary for Videos.
    """
    namespace = WaffleFlagNamespace(name=WAFFLE_NAMESPACE, log_prefix=u'Videos: ')
    return {
        DEPRECATE_YOUTUBE: CourseWaffleFlag(
            waffle_namespace=namespace,
            flag_name=DEPRECATE_YOUTUBE,
            module_name=__name__,
        ),
        ENABLE_DEVSTACK_VIDEO_UPLOADS: WaffleFlag(
            waffle_namespace=namespace,
            flag_name=ENABLE_DEVSTACK_VIDEO_UPLOADS,
            module_name=__name__,
        ),
        ENABLE_VEM_PIPELINE: CourseWaffleFlag(
            waffle_namespace=namespace,
            flag_name=ENABLE_VEM_PIPELINE,
            module_name=__name__,
        )
    }
Exemple #10
0
    def test_interlocked_overrides(self):
        waffle_flag1 = self.waffle_flag
        waffle_flag2 = WaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
            waffle_flag1.name + "2", __name__
        )
        waffle_flag2.cached_flags()[waffle_flag2.name] = True

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())

        with override_waffle_flag(waffle_flag1, True):
            with override_waffle_flag(waffle_flag2, False):
                self.assertTrue(waffle_flag1.is_enabled())
                self.assertFalse(waffle_flag2.is_enabled())

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())
Exemple #11
0
from django.urls import NoReverseMatch, reverse
from django.utils.translation import gettext as _
from edx_django_utils.cache import TieredCache, get_cache_key
from edx_toggles.toggles import WaffleFlag
from enterprise.api.v1.serializers import EnterpriseCustomerBrandingConfigurationSerializer
from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser
from social_django.models import UserSocialAuth

from common.djangoapps import third_party_auth
from common.djangoapps.student.helpers import get_next_url_for_login_page
from lms.djangoapps.branding.api import get_privacy_url
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_authn.cookies import standard_cookie_settings
from openedx.core.djangolib.markup import HTML, Text

ENTERPRISE_HEADER_LINKS = WaffleFlag('enterprise.enterprise_header_links',
                                     __name__)  # lint-amnesty, pylint: disable=toggle-missing-annotation


def get_data_consent_share_cache_key(user_id,
                                     course_id,
                                     enterprise_customer_uuid=None):
    """
        Returns cache key for data sharing consent needed against user_id, course_id and enterprise_customer_uuid
    """
    cache_key_params = dict(
        type='data_sharing_consent_needed',
        user_id=user_id,
        course_id=course_id,
    )

    if enterprise_customer_uuid:
Exemple #12
0
try:
    import newrelic.agent
except ImportError:
    newrelic = None  # pylint: disable=invalid-name

# HACK: This shouldn't be hard-coded to two types
# OBSOLETE: This obsoletes 'type'
class_priority = ['video', 'problem']

# Make '_' a no-op so we can scrape strings. Using lambda instead of
#  `django.utils.translation.ugettext_noop` because Django cannot be imported in this file
_ = lambda text: text

TIMED_EXAM_GATING_WAFFLE_FLAG = WaffleFlag(
    waffle_namespace="xmodule",
    flag_name=u'rev_1377_rollout',
    module_name=__name__,
)


class SequenceFields(object):
    has_children = True
    completion_mode = XBlockCompletionMode.AGGREGATOR

    # NOTE: Position is 1-indexed.  This is silly, but there are now student
    # positions saved on prod, so it's not easy to fix.
    position = Integer(help="Last tab viewed in this sequence",
                       scope=Scope.user_state)

    due = Date(
        display_name=_("Due Date"),
Exemple #13
0
# accommodates course api urls, excluding any course api routes that do not fall under v*/courses, such as v1/blocks.
COURSE_REGEX = re.compile(
    fr'^(.*?/course(s)?/)(?!v[0-9]+/[^/]+){settings.COURSE_ID_PATTERN}')

# .. toggle_name: request_utils.capture_cookie_sizes
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Enables more detailed capturing of cookie sizes for monitoring purposes. This can be useful for tracking
#       down large cookies if requests are nearing limits on the total size of cookies. See the
#       CookieMonitoringMiddleware docstring for details on the monitoring custom attributes that will be set.
# .. toggle_warnings: Enabling this flag will add a number of custom attributes, and could adversely affect other
#       monitoring. Only enable temporarily, or lower TOP_N_COOKIES_CAPTURED and TOP_N_COOKIE_GROUPS_CAPTURED django
#       settings to capture less data.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2019-02-22
CAPTURE_COOKIE_SIZES = WaffleFlag('request_utils.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).
    """
    request = crum.get_current_request()
from openedx.features.discounts.models import DiscountPercentageConfig, DiscountRestrictionConfig
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.track import segment

# .. toggle_name: discounts.enable_discounting
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Toggle discounts always being disabled
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2019-4-16
# .. toggle_target_removal_date: None
# .. toggle_tickets: REVEM-282
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
DISCOUNT_APPLICABILITY_FLAG = WaffleFlag(
    waffle_namespace=WaffleFlagNamespace(name=u'discounts'),
    flag_name=u'enable_discounting',
    module_name=__name__,
)

DISCOUNT_APPLICABILITY_HOLDBACK = 'first_purchase_discount_holdback'
REV1008_EXPERIMENT_ID = 16


def get_discount_expiration_date(user, course):
    """
    Returns the date when the discount expires for the user.
    Returns none if the user is not enrolled.
    """
    # anonymous users should never get the discount
    if user.is_anonymous:
        return None
Exemple #15
0
"""
This module contains various configuration settings via
waffle switches for the teams app.
"""

from edx_toggles.toggles import WaffleFlag

# .. toggle_name: teams.enable_teams_app
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag to enable teams app for a course
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2021-10-07
# .. toggle_target_removal_date: 2021-11-01
# .. toggle_warnings: When the flag is ON, the teams app will be visible in the new course authoring mfe.
# .. toggle_tickets: TNL-8816
ENABLE_TEAMS_APP = WaffleFlag(
    'teams.enable_teams_app',
    __name__,
)
Exemple #16
0
from crum import get_current_request
from edx_toggles.toggles import LegacyWaffleSwitch, LegacyWaffleFlagNamespace, LegacyWaffleSwitchNamespace, WaffleFlag

from lms.djangoapps.experiments.flags import ExperimentWaffleFlag
from lms.djangoapps.experiments.models import ExperimentData

WAFFLE_FLAG_NAMESPACE = LegacyWaffleFlagNamespace(name='schedules')
WAFFLE_SWITCH_NAMESPACE = LegacyWaffleSwitchNamespace(name='schedules')

# .. toggle_name: schedules.enable_debugging
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Enable debug level of logging for schedules messages.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2017-09-17
DEBUG_MESSAGE_WAFFLE_FLAG = WaffleFlag('schedules.enable_debugging', __name__)

COURSE_UPDATE_SHOW_UNSUBSCRIBE_WAFFLE_SWITCH = LegacyWaffleSwitch(  # lint-amnesty, pylint: disable=toggle-missing-annotation
    WAFFLE_SWITCH_NAMESPACE, 'course_update_show_unsubscribe', __name__)

# This experiment waffle is supporting an A/B test we are running on sending course updates from an external service,
# rather than through platform and ACE. See ticket AA-661 for more information.
# Don't use this flag directly, instead use the `set_up_external_updates_for_enrollment` and `query_external_updates`
# methods below. We save this flag decision at enrollment time and don't change it even if the flag changes. So you
# can't just directly look at flag result.
_EXTERNAL_COURSE_UPDATES_EXPERIMENT_ID = 18
_EXTERNAL_COURSE_UPDATES_FLAG = ExperimentWaffleFlag(
    WAFFLE_FLAG_NAMESPACE,
    'external_updates',
    __name__,  # lint-amnesty, pylint: disable=toggle-missing-annotation
    experiment_id=_EXTERNAL_COURSE_UPDATES_EXPERIMENT_ID,
# 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__)

# Waffle flag to let learners access a course before its start date.
COURSE_PRE_START_ACCESS_FLAG = WaffleFlag(WAFFLE_FLAG_NAMESPACE,
                                          'pre_start_access', __name__)

# Waffle flag to enable a review page link from the unified home page.
# .. toggle_name: course_experience.show_reviews_tool
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Used with our integration with CourseTalk to display reviews for a course.
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2017-06-19
# .. toggle_target_removal_date: None
# .. toggle_warnings: We are no longer integrating with CourseTalk, so this probably should be deprecated and the code
#   for reviews should be removed. This temporary feature toggle should have a target removal date.
# .. toggle_tickets: DEPR-48
SHOW_REVIEWS_TOOL_FLAG = CourseWaffleFlag(WAFFLE_FLAG_NAMESPACE,
                                          'show_reviews_tool', __name__)
Exemple #18
0
def student_dashboard(request):
    """
    Provides the LMS dashboard view

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

    Returns:
        The dashboard response.

    """
    user = request.user

    #Added by Mahendra
    if request.is_ajax():
        if request.method == 'GET' :
            if user.is_authenticated:
                if request.GET.get('disclaimer'):
                    log.info(u'disclaimer %s', request.GET.get('disclaimer'))
                    usr = request.user.id
                    cid = request.GET.get('cid')
                    disclaimer = disclaimer_agreement_status(course_id=cid, user_id=usr, status='1')
                    disclaimer.save()
                else:
                    usr = request.user.id
                    cid = request.GET.get('cid')
                    view_counter = user_view_counter.objects.filter(course_id=cid, user=usr)
                    if view_counter :
                        update_counter = user_view_counter.objects.filter(
                            course_id=cid,
                            user=usr
                        ).update(counter = F('counter')+1)
                    else:
                        countr = user_view_counter(user_id=usr, course_id=cid,counter=1)
                        countr.save()

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

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

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

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

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

    #Added by dev below code to display enrolled courses in course start date order
    from lms.djangoapps.course_extrainfo.models import course_extrainfo

    new_enrollments = []
    #log.info('course_enrollments--> %s', course_enrollments)
    for enrollment in course_enrollments:
        new_enrollments.append(enrollment.course_overview.id)

    get_type_courses = course_extrainfo.objects.filter(course_id__in=new_enrollments,course_type=1)

    course_ids = []
    for course in get_type_courses:
        course_ids.append(course.course_id)

    courseslist = CourseOverview.objects.filter(pk__in=course_ids).order_by('-start')

    get_type_lectures = course_extrainfo.objects.filter(course_id__in=new_enrollments,course_type=2)

    lecture_ids = []
    for lecture in get_type_lectures:
        lecture_ids.append(lecture.course_id)

    lectureslist = CourseOverview.objects.filter(pk__in=lecture_ids).order_by('-start')
    #code ends here

    # 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)

    # Display a message guiding the user to their Enterprise's Learner Portal if enabled
    enterprise_learner_portal_enabled_message = get_enterprise_learner_portal_enabled_message(request)

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


    # Disable lookup of Enterprise consent_required_course due to ENT-727
    # Will re-enable after fixing WL-1315
    consent_required_courses = set()
    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(experiments_namespace, u'bundles_on_dashboard', __name__)

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

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

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

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

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

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

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

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

    site_domain = request.site
    if 'notlive' in request.GET:
        if 'viatris' in str(site_domain):
            redirect_message = _("The webinar you are looking for does not start until {date}.").format(
                date=request.GET['notlive']
            )
        else:
            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 = 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': Text(redirect_message),
        'account_activation_messages': account_activation_messages,
        'activate_account_message': activate_account_message,
        'course_enrollments': course_enrollments,
        'course_entitlements': course_entitlements,
        'course_entitlement_available_sessions': course_entitlement_available_sessions,
        'unfulfilled_entitlement_pseudo_sessions': unfulfilled_entitlement_pseudo_sessions,
        'course_optouts': course_optouts,
        'staff_access': staff_access,
        'errored_courses': errored_courses,
        'show_courseware_links_for': show_courseware_links_for,
        'all_course_modes': course_mode_info,
        'cert_statuses': cert_statuses,
        'credit_statuses': _credit_statuses(user, course_enrollments),
        'show_email_settings_for': show_email_settings_for,
        'reverifications': reverifications,
        'verification_display': verification_status['should_display'],
        'verification_status': verification_status['status'],
        'verification_expiry': verification_status['verification_expiry'],
        'verification_status_by_course': verify_status_by_course,
        'verification_errors': verification_errors,
        'denied_banner': denied_banner,
        'billing_email': settings.PAYMENT_SUPPORT_EMAIL,
        'user': user,
        'logout_url': reverse('logout'),
        'platform_name': platform_name,
        'enrolled_courses_either_paid': enrolled_courses_either_paid,
        'provider_states': [],
        'courses_requirements_not_met': courses_requirements_not_met,
        'nav_hidden': True,
        'inverted_programs': inverted_programs,
        'show_program_listing': ProgramsApiConfig.is_enabled(),
        'show_dashboard_tabs': True,
        'disable_courseware_js': True,
        'display_course_modes_on_dashboard': enable_verified_certificates and display_course_modes_on_dashboard,
        'display_sidebar_on_dashboard': display_sidebar_on_dashboard,
        'display_sidebar_account_activation_message': not(user.is_active or hide_dashboard_courses_until_activated),
        'display_dashboard_courses': (user.is_active or not hide_dashboard_courses_until_activated),
        'empty_dashboard_message': empty_dashboard_message,
        'recovery_email_message': recovery_email_message,
        'recovery_email_activation_message': recovery_email_activation_message,
        'enterprise_learner_portal_enabled_message': enterprise_learner_portal_enabled_message,
        'show_load_all_courses_link': show_load_all_courses_link(user, course_limit, course_enrollments),
        # TODO START: clean up as part of REVEM-199 (START)
        'course_info': get_dashboard_course_info(user, course_enrollments),
        # TODO START: clean up as part of REVEM-199 (END)
        #added by dev 
        'courseslist':courseslist,
        'lectureslist':lectureslist,
    }

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

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

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

    return render_to_response('dashboard.html', context)
Exemple #19
0
"""
Tests for waffle utils views.
"""
from django.conf import settings
from django.test import TestCase
from django.test.utils import override_settings
from waffle.testutils import override_switch

from edx_toggles.toggles import SettingDictToggle, SettingToggle, WaffleFlag
from edx_toggles.toggles.state import ToggleStateReport
from edx_toggles.toggles.testutils import override_waffle_flag

TEST_WAFFLE_FLAG = WaffleFlag("test.flag", __name__)  # lint-amnesty, pylint: disable=toggle-missing-annotation


class ToggleStateTests(TestCase):
    """
    Unit tests for the toggle state report.
    """
    @override_waffle_flag(TEST_WAFFLE_FLAG, True)
    def test_response_with_waffle_flag(self):
        report = ToggleStateReport().as_dict()
        self.assertIn("waffle_flags", report)
        self.assertTrue(report["waffle_flags"])
        waffle_names = [waffle["name"] for waffle in report["waffle_flags"]]
        self.assertIn("test.flag", waffle_names)

    @override_switch("test.switch", True)
    def test_response_with_waffle_switch(self):
        report = ToggleStateReport().as_dict()
        self.assertIn("waffle_switches", report)
Exemple #20
0
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.util.db import outer_atomic
from xmodule.modulestore.django import modulestore

LOG = logging.getLogger(__name__)

# .. toggle_name: course_modes.use_new_track_selection
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: This flag enables the use of the new track selection template for testing purposes before full rollout
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2021-8-23
# .. toggle_target_removal_date: None
# .. toggle_tickets: REV-2133
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
VALUE_PROP_TRACK_SELECTION_FLAG = WaffleFlag(
    'course_modes.use_new_track_selection', __name__)


class ChooseModeView(View):
    """View used when the user is asked to pick a mode.

    When a get request is used, shows the selection page.

    When a post request is used, assumes that it is a form submission
    from the selection page, parses the response, and then sends user
    to the next step in the flow.

    """
    @method_decorator(transaction.non_atomic_requests)
    def dispatch(self, *args, **kwargs):
        """Disable atomicity for the view.
Exemple #21
0
from lms.djangoapps.experiments.models import ExperimentData
from lms.djangoapps.experiments.stable_bucketing import stable_bucketing_hash_group
from openedx.features.discounts.models import DiscountPercentageConfig, DiscountRestrictionConfig
from common.djangoapps.student.models import CourseEnrollment
from common.djangoapps.track import segment

# .. toggle_name: discounts.enable_discounting
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Toggle discounts always being disabled
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2019-4-16
# .. toggle_target_removal_date: None
# .. toggle_tickets: REVEM-282
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
DISCOUNT_APPLICABILITY_FLAG = WaffleFlag('discounts.enable_discounting',
                                         __name__)

DISCOUNT_APPLICABILITY_HOLDBACK = 'first_purchase_discount_holdback'
REV1008_EXPERIMENT_ID = 16


def get_discount_expiration_date(user, course):
    """
    Returns the date when the discount expires for the user.
    Returns none if the user is not enrolled.
    """
    # anonymous users should never get the discount
    if user.is_anonymous:
        return None

    course_enrollment = CourseEnrollment.objects.filter(
Exemple #22
0
"""
Toggles for Learner Profile page.
"""

from edx_toggles.toggles import WaffleFlag
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers

# Namespace for learner profile waffle flags.
WAFFLE_FLAG_NAMESPACE = '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_use_cases: temporary, open_edx
# .. toggle_creation_date: 2019-02-19
# .. toggle_target_removal_date: 2020-12-31
# .. toggle_warnings: Also set settings.PROFILE_MICROFRONTEND_URL and site's ENABLE_PROFILE_MICROFRONTEND.
# .. toggle_tickets: DEPR-17
REDIRECT_TO_PROFILE_MICROFRONTEND = WaffleFlag(
    f'{WAFFLE_FLAG_NAMESPACE}.redirect_to_microfrontend', __name__)


def should_redirect_to_profile_microfrontend():
    return (configuration_helpers.get_value('ENABLE_PROFILE_MICROFRONTEND')
            and REDIRECT_TO_PROFILE_MICROFRONTEND.is_enabled())
Exemple #23
0
class OverrideWaffleFlagTests(TestCase):
    """
    Tests for the override_waffle_flag decorator/context manager.
    """
    def setUp(self):
        super().setUp()
        flag_name = "test_namespace.test_flag"
        self.waffle_flag = WaffleFlag(flag_name, __name__)

        request = RequestFactory().request()
        crum.set_current_request(request)
        RequestCache.clear_all_namespaces()

        self.addCleanup(crum.set_current_request, None)
        self.addCleanup(RequestCache.clear_all_namespaces)

    def temporarily_enable_flag(self):
        """
        Temporarily override flag.
        """
        @override_waffle_flag(self.waffle_flag, True)
        def test_func():
            """
            Decorated test function.
            """
            self.assertTrue(self.waffle_flag.is_enabled())

        test_func()

    def test_override_waffle_flag_pre_cached(self):
        # checks and caches the is_enabled value
        self.assertFalse(self.waffle_flag.is_enabled())
        flag_cache = self.waffle_flag.cached_flags()
        self.assertIn(self.waffle_flag.name, flag_cache)

        self.temporarily_enable_flag()

        # test cached flag is restored
        self.assertIn(self.waffle_flag.name, flag_cache)
        self.assertFalse(self.waffle_flag.is_enabled())

    def test_override_waffle_flag_not_pre_cached(self):
        # check that the flag is not yet cached
        flag_cache = self.waffle_flag.cached_flags()
        self.assertNotIn(self.waffle_flag.name, flag_cache)

        self.temporarily_enable_flag()

        # test cache is removed when no longer using decorator/context manager
        self.assertNotIn(self.waffle_flag.name, flag_cache)

    def test_override_waffle_flag_as_context_manager(self):
        self.assertFalse(self.waffle_flag.is_enabled())

        with override_waffle_flag(self.waffle_flag, True):
            self.assertTrue(self.waffle_flag.is_enabled())

        self.assertFalse(self.waffle_flag.is_enabled())

    def test_interlocked_overrides(self):
        waffle_flag1 = self.waffle_flag
        waffle_flag2 = WaffleFlag(waffle_flag1.name + "2", __name__)
        waffle_flag2.cached_flags()[waffle_flag2.name] = True

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())

        with override_waffle_flag(waffle_flag1, True):
            with override_waffle_flag(waffle_flag2, False):
                self.assertTrue(waffle_flag1.is_enabled())
                self.assertFalse(waffle_flag2.is_enabled())

        self.assertFalse(waffle_flag1.is_enabled())
        self.assertTrue(waffle_flag2.is_enabled())
Exemple #24
0

# TODO: After removing this flag, add a migration to remove waffle flag in a follow-up deployment.
ENABLE_CHECKLISTS_QUALITY = CourseWaffleFlag(
    waffle_namespace=waffle_flags(),
    flag_name=u'enable_checklists_quality',
    module_name=__name__,
)

SHOW_REVIEW_RULES_FLAG = CourseWaffleFlag(
    waffle_namespace=waffle_flags(),
    flag_name=u'show_review_rules',
    module_name=__name__,
)

# Waffle flag to redirect to the library authoring MFE.
# .. toggle_name: contentstore.library_authoring_mfe
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Toggles the new micro-frontend-based implementation of the library authoring experience.
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2020-08-03
# .. toggle_target_removal_date: 2020-12-31
# .. toggle_warnings: Also set settings.LIBRARY_AUTHORING_MICROFRONTEND_URL and ENABLE_LIBRARY_AUTHORING_MICROFRONTEND.
# .. toggle_tickets: https://openedx.atlassian.net/wiki/spaces/COMM/pages/1545011241/BD-14+Blockstore+Powered+Content+Libraries+Taxonomies
REDIRECT_TO_LIBRARY_AUTHORING_MICROFRONTEND = WaffleFlag(
    waffle_namespace=waffle_flags(),
    flag_name='library_authoring_mfe',
    module_name=__name__,
)
Exemple #25
0

def is_require_third_party_auth_enabled():
    # TODO: Replace function with SettingToggle when it is available.
    return getattr(settings, "ENABLE_REQUIRE_THIRD_PARTY_AUTH", False)

# .. toggle_name: user_authn.redirect_to_microfrontend
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Supports staged rollout of a new micro-frontend-based implementation of the login and
#   registration pages
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2021-02-02
# .. toggle_target_removal_date: None
# .. toggle_warnings: Also set settings.AUTHN_MICROFRONTEND_URL and site's ENABLE_AUTHN_MICROFRONTEND
# .. toggle_tickets: VAN-308
REDIRECT_TO_AUTHN_MICROFRONTEND = WaffleFlag('user_authn.redirect_to_microfrontend', __name__)


def should_redirect_to_authn_microfrontend():
    """
    Checks if login/registration should be done via MFE.
    """
    request = get_current_request()
    if request and request.GET.get('skip_authn_mfe'):
        return False

    return configuration_helpers.get_value(
        'ENABLE_AUTHN_MICROFRONTEND', settings.FEATURES.get('ENABLE_AUTHN_MICROFRONTEND')
    ) and REDIRECT_TO_AUTHN_MICROFRONTEND.is_enabled()
Exemple #26
0
# TODO: clean up as part of REVEM-199 (START)
experiments_namespace = WaffleFlagNamespace(name=u'experiments')

# .. toggle_name: experiments.add_programs
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Toggle for adding the current course's program information to user metadata
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2019-2-25
# .. toggle_target_removal_date: None
# .. toggle_tickets: REVEM-63, REVEM-198
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
PROGRAM_INFO_FLAG = WaffleFlag(
    waffle_namespace=experiments_namespace,
    flag_name=u'add_programs',
    module_name=__name__,
)

# .. toggle_name: experiments.add_dashboard_info
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Toggle for adding info about each course to the dashboard metadata
# .. toggle_use_cases: temporary
# .. toggle_creation_date: 2019-3-28
# .. toggle_target_removal_date: None
# .. toggle_tickets: REVEM-118
# .. toggle_warnings: This temporary feature toggle does not have a target removal date.
DASHBOARD_INFO_FLAG = WaffleFlag(experiments_namespace, u'add_dashboard_info',
                                 __name__)
# TODO END: clean up as part of REVEM-199 (End)
"""
This module contains various configuration settings via
waffle switches for the learner_dashboard app.
"""

from edx_toggles.toggles import WaffleFlag

# .. toggle_name: learner_dashboard.enable_program_discussions
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag to enable new Program discussion experience for course.
# .. toggle_use_cases: temporary, open_edx
# .. toggle_creation_date: 2021-08-25
# .. toggle_target_removal_date: 2021-12-31
# .. toggle_warnings: When the flag is ON, the new experience for Program discussions will be enabled.
# .. toggle_tickets: TNL-8434
ENABLE_PROGRAM_DISCUSSIONS = WaffleFlag(
    'learner_dashboard.enable_program_discussions',
    __name__,
)
Exemple #28
0
# Videos Namespace
WAFFLE_NAMESPACE = 'videos'
LOG_PREFIX = 'Videos: '

# .. toggle_name: videos.deprecate_youtube
# .. toggle_implementation: CourseWaffleFlag
# .. toggle_default: False
# .. toggle_description: Waffle flag telling whether youtube is deprecated. When enabled, videos are no longer uploaded
#   to YouTube as part of the video pipeline.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2018-08-03
# .. toggle_tickets: https://github.com/edx/edx-platform/pull/18765
DEPRECATE_YOUTUBE = CourseWaffleFlag(f'{WAFFLE_NAMESPACE}.deprecate_youtube', __name__, LOG_PREFIX)

# .. toggle_name: videos.enable_devstack_video_uploads
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: When enabled, use Multi-Factor Authentication (MFA) for authenticating to AWS. These short-
#   lived access tokens are well suited for development (probably?). [At the time of annotation, the exact consequences
#   of enabling this feature toggle are uncertain.]
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2020-03-12
# .. toggle_warnings: Enabling this feature requires that the ROLE_ARN, MFA_SERIAL_NUMBER, MFA_TOKEN settings are
#   properly defined.
# .. toggle_tickets: https://github.com/edx/edx-platform/pull/23375
ENABLE_DEVSTACK_VIDEO_UPLOADS = WaffleFlag(f'{WAFFLE_NAMESPACE}.enable_devstack_video_uploads', __name__, LOG_PREFIX)

ENABLE_VEM_PIPELINE = CourseWaffleFlag(  # lint-amnesty, pylint: disable=toggle-missing-annotation
    f'{WAFFLE_NAMESPACE}.enable_vem_pipeline', __name__, LOG_PREFIX
)
Exemple #29
0
                               module_name=__name__)

# Namespace for studio dashboard waffle flags.
CONTENTSTORE_NAMESPACE = 'contentstore'
CONTENTSTORE_LOG_PREFIX = 'Contentstore: '

# .. toggle_name: contentstore.split_library_on_studio_dashboard
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Enables data new view for library on studio dashboard.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2020-07-8
# .. toggle_tickets: TNL-7536
SPLIT_LIBRARY_ON_DASHBOARD = WaffleFlag(
    f'{CONTENTSTORE_NAMESPACE}.split_library_on_studio_dashboard',
    __name__,
    CONTENTSTORE_LOG_PREFIX,
)

# .. toggle_name: contentstore.bypass_olx_failure
# .. toggle_implementation: WaffleFlag
# .. toggle_default: False
# .. toggle_description: Enables bypassing olx validation failures during course import.
# .. toggle_use_cases: open_edx
# .. toggle_creation_date: 2021-04-15
# .. toggle_target_removal_date: 2021-05-15
# .. toggle_tickets: TNL-8214
BYPASS_OLX_FAILURE = WaffleFlag(
    f'{CONTENTSTORE_NAMESPACE}.bypass_olx_failure',
    __name__,
    CONTENTSTORE_LOG_PREFIX,
Exemple #30
0
from django.urls import NoReverseMatch, reverse
from django.utils.translation import ugettext as _
from edx_django_utils.cache import TieredCache, get_cache_key
from edx_toggles.toggles import WaffleFlag
from enterprise.api.v1.serializers import EnterpriseCustomerBrandingConfigurationSerializer
from enterprise.models import EnterpriseCustomer, EnterpriseCustomerUser
from social_django.models import UserSocialAuth

from common.djangoapps import third_party_auth
from lms.djangoapps.branding.api import get_privacy_url
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.djangoapps.user_authn.cookies import standard_cookie_settings
from openedx.core.djangolib.markup import HTML, Text
from common.djangoapps.student.helpers import get_next_url_for_login_page

ENTERPRISE_HEADER_LINKS = WaffleFlag('enterprise', 'enterprise_header_links',
                                     __name__)


def get_data_consent_share_cache_key(user_id, course_id):
    """
        Returns cache key for data sharing consent needed against user_id and course_id
    """

    return get_cache_key(type='data_sharing_consent_needed',
                         user_id=user_id,
                         course_id=course_id)


def clear_data_consent_share_cache(user_id, course_id):
    """
        clears data_sharing_consent_needed cache