Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 37 additions & 13 deletions lms/djangoapps/learner_home/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from lms.djangoapps.bulk_email.models import Optout
from lms.djangoapps.learner_home.test_utils import create_test_enrollment, random_string, random_url
from lms.djangoapps.learner_home.views import (
EnterpriseCustomerData,
get_course_overviews_for_pseudo_sessions,
get_course_programs,
get_course_share_urls,
Expand Down Expand Up @@ -412,26 +413,49 @@ def test_no_suggested_courses(self):
self.assertDictEqual(return_data, self.EMPTY_SUGGESTED_COURSES) # noqa: PT009


FAKE_ENTERPRISE_CUSTOMER_DATA: EnterpriseCustomerData = {
"name": "Fake Enterprise",
"uuid": str(uuid4()),
"slug": "fake-enterprise",
"auth_org_id": None,
"enable_learner_portal": True,
}


def _fake_enterprise_customer_override(prev_fn, user, request, is_masquerading):
"""Test double target for OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER"""
_fake_enterprise_customer_override.last_call = (prev_fn, user, request, is_masquerading)
return FAKE_ENTERPRISE_CUSTOMER_DATA


@ddt.ddt
class TestGetEnterpriseCustomer(TestCase):
"""Test for get_enterprise_customer"""

@ddt.data(True, False)
@patch("lms.djangoapps.learner_home.views.get_enterprise_learner_data_from_db")
@patch(
"lms.djangoapps.learner_home.views.enterprise_customer_from_session_or_learner_data"
)
def test_get_enterprise_customer(
self, is_masquerading, mock_get_from_session, mock_get_from_db
):
"""Don't load the user from session if we're masquerading, load directly from db"""
def test_get_enterprise_customer_default(self, is_masquerading):
"""With no plugin override configured, the base implementation returns None"""
user, request = Mock(), Mock()
result = get_enterprise_customer(user, request, is_masquerading)
if is_masquerading:
assert not mock_get_from_session.called
assert result is mock_get_from_db.return_value[0]["enterprise_customer"]
else:
assert result is mock_get_from_session.return_value
assert result is None

@override_settings(
OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER=(
f"{__name__}._fake_enterprise_customer_override"
)
)
def test_get_enterprise_customer_uses_plugin_override(self):
"""When OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER is configured, it is used instead"""
user, request = Mock(), Mock()
result = get_enterprise_customer(user, request, is_masquerading=True)
assert result == FAKE_ENTERPRISE_CUSTOMER_DATA

_, received_user, received_request, received_is_masquerading = (
_fake_enterprise_customer_override.last_call
)
assert received_user is user
assert received_request is request
assert received_is_masquerading is True


class TestGetSocialShareSettings(TestCase):
Expand Down
40 changes: 28 additions & 12 deletions lms/djangoapps/learner_home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,25 @@
Views for Learner Home
"""

from __future__ import annotations

import logging
from collections import OrderedDict
from typing import TYPE_CHECKING, TypedDict

from completion.exceptions import UnavailableCompletionData
from completion.utilities import get_key_to_last_completed_block
from django.conf import settings
from django.urls import reverse
from edx_django_utils import monitoring as monitoring_utils
from edx_django_utils.monitoring import function_trace
from edx_django_utils.plugins import pluggable_override
from edx_rest_framework_extensions.auth.jwt.authentication import JwtAuthentication
from edx_rest_framework_extensions.auth.session.authentication import SessionAuthenticationAllowInactiveUser
from edx_rest_framework_extensions.permissions import NotJwtRestrictedApplication
from opaque_keys.edx.keys import CourseKey
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

Expand Down Expand Up @@ -44,10 +49,9 @@
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from openedx.core.lib.api.authentication import BearerAuthenticationAllowInactiveUser
from openedx.features.course_duration_limits.access import get_user_course_expiration_date
from openedx.features.enterprise_support.api import (
enterprise_customer_from_session_or_learner_data,
get_enterprise_learner_data_from_db,
)

if TYPE_CHECKING:
from django.contrib.auth.models import User # lint-amnesty, pylint: disable=imported-auth-user

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -192,17 +196,29 @@ def get_email_settings_info(user, course_enrollments):
return show_email_settings_for, course_optouts


class EnterpriseCustomerData(TypedDict):
"""Contract for the OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER return value."""
name: str
uuid: str
slug: str
auth_org_id: str | None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As much as I prefer | None, the original uses Optional[]

@marlonkeating marlonkeating Jul 22, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently there's a ruff rule being used here that wasn't under release-ulmo, that was causing CI quality check to fail

enable_learner_portal: bool


@function_trace("get_enterprise_customer")
def get_enterprise_customer(user, request, is_masquerading):
@pluggable_override('OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER')
def get_enterprise_customer(
user: User,
request: Request,
is_masquerading: bool,
) -> EnterpriseCustomerData | None:
"""
If we are not masquerading, try to load the enterprise learner from session data, falling back to the db.
If we are masquerading, don't read or write to/from session data, go directly to db.
Return the enterprise customer dict for the given user, or None.

This function can be overridden by an installed plugin via the
OVERRIDE_LEARNER_HOME_GET_ENTERPRISE_CUSTOMER setting.
"""
if is_masquerading:
learner_data = get_enterprise_learner_data_from_db(user)
return learner_data[0]["enterprise_customer"] if learner_data else None
else:
return enterprise_customer_from_session_or_learner_data(request)
return None


@function_trace("get_ecommerce_payment_page")
Expand Down
Loading