ENT-3367 | Show at most 1 Enterprise Learner Portal dashboard link in the header user-menu. Also, guard against no branding config when getting learner portal data.
This commit is contained in:
committed by
Alex Dusenbery
parent
ba9ee4e151
commit
4bb1914ec6
@@ -8,15 +8,14 @@ from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from lms.djangoapps.ccx.overrides import get_current_ccx
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name, get_enterprise_learner_portals
|
||||
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name, get_enterprise_learner_portal
|
||||
|
||||
# App that handles subdomain specific branding
|
||||
from branding import api as branding_api
|
||||
%>
|
||||
|
||||
<%
|
||||
enterprise_customer_links = get_enterprise_learner_portals(request)
|
||||
enterprise_customer_link = enterprise_customer_links[0] if enterprise_customer_links else None # Only want to show the first link
|
||||
enterprise_customer_link = get_enterprise_learner_portal(request)
|
||||
%>
|
||||
|
||||
<h1 class="header-logo">
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.utils.translation import ugettext as _
|
||||
from openedx.core.djangoapps.user_api.accounts.image_helpers import get_profile_image_urls_for_user
|
||||
from openedx.core.djangoapps.user_api.accounts.toggles import should_redirect_to_order_history_microfrontend
|
||||
from openedx.core.djangoapps.user_api.accounts.utils import retrieve_last_sitewide_block_completed
|
||||
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name, get_enterprise_learner_portals
|
||||
from openedx.features.enterprise_support.utils import get_enterprise_learner_generic_name, get_enterprise_learner_portal
|
||||
%>
|
||||
|
||||
<%
|
||||
@@ -22,10 +22,10 @@ profile_image_url = get_profile_image_urls_for_user(self.real_user)['medium']
|
||||
username = self.real_user.username
|
||||
resume_block = retrieve_last_sitewide_block_completed(self.real_user)
|
||||
displayname = get_enterprise_learner_generic_name(request) or username
|
||||
enterprise_customer_portals = get_enterprise_learner_portals(request)
|
||||
enterprise_customer_portal = get_enterprise_learner_portal(request)
|
||||
## Enterprises with the learner portal enabled should not show order history, as it does
|
||||
## not apply to the learner's method of purchasing content.
|
||||
should_show_order_history = should_redirect_to_order_history_microfrontend() and not enterprise_customer_portals
|
||||
should_show_order_history = should_redirect_to_order_history_microfrontend() and not enterprise_customer_portal
|
||||
%>
|
||||
|
||||
<div class="nav-item hidden-mobile">
|
||||
@@ -43,12 +43,10 @@ should_show_order_history = should_redirect_to_order_history_microfrontend() and
|
||||
% if resume_block:
|
||||
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${resume_block}" role="menuitem">${_("Resume your last course")}</a></div>
|
||||
% endif
|
||||
% if not enterprise_customer_portals:
|
||||
% if not enterprise_customer_portal:
|
||||
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${reverse('dashboard')}" role="menuitem">${_("Dashboard")}</a></div>
|
||||
% else:
|
||||
% for portal in enterprise_customer_portals:
|
||||
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL}/${portal.get('slug')}" role="menuitem">${_("{name} Dashboard").format(name=portal.get('name'))}</a></div>
|
||||
% endfor
|
||||
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${settings.ENTERPRISE_LEARNER_PORTAL_BASE_URL}/${enterprise_customer_portal.get('slug')}" role="menuitem">${_("Dashboard")}</a></div>
|
||||
% endif
|
||||
|
||||
<div class="mobile-nav-item dropdown-item dropdown-nav-item"><a href="${reverse('learner_profile', kwargs={'username': username})}" role="menuitem">${_("Profile")}</a></div>
|
||||
|
||||
@@ -43,6 +43,10 @@ except ImportError:
|
||||
|
||||
CONSENT_FAILED_PARAMETER = 'consent_failed'
|
||||
LOGGER = logging.getLogger("edx.enterprise_helpers")
|
||||
ENTERPRISE_CUSTOMER_KEY_NAME = 'enterprise_customer'
|
||||
|
||||
# See https://open-edx-proposals.readthedocs.io/en/latest/oep-0022-bp-django-caches.html#common-caching-defect-and-fix
|
||||
_CACHE_MISS = '__CACHE_MISS__'
|
||||
|
||||
|
||||
class EnterpriseApiException(Exception):
|
||||
@@ -283,7 +287,7 @@ class EnterpriseApiServiceClient(EnterpriseServiceClientMixin, EnterpriseApiClie
|
||||
API response`.
|
||||
"""
|
||||
enterprise_customer = enterprise_customer_from_cache(uuid=uuid)
|
||||
if not enterprise_customer:
|
||||
if enterprise_customer is _CACHE_MISS:
|
||||
endpoint = getattr(self.client, 'enterprise-customer')
|
||||
enterprise_customer = endpoint(uuid).get()
|
||||
if enterprise_customer:
|
||||
@@ -348,7 +352,6 @@ def enterprise_is_enabled(otherwise=None):
|
||||
return decorator
|
||||
|
||||
|
||||
@enterprise_is_enabled()
|
||||
def get_enterprise_customer_cache_key(uuid, username=settings.ENTERPRISE_SERVICE_WORKER_USERNAME):
|
||||
"""The cache key used to get cached Enterprise Customer data."""
|
||||
return get_cache_key(
|
||||
@@ -358,28 +361,63 @@ def get_enterprise_customer_cache_key(uuid, username=settings.ENTERPRISE_SERVICE
|
||||
)
|
||||
|
||||
|
||||
@enterprise_is_enabled()
|
||||
def cache_enterprise(enterprise_customer):
|
||||
"""Cache this customer's data."""
|
||||
"""Add this customer's data to the Django cache."""
|
||||
cache_key = get_enterprise_customer_cache_key(enterprise_customer['uuid'])
|
||||
cache.set(cache_key, enterprise_customer, settings.ENTERPRISE_API_CACHE_TIMEOUT)
|
||||
|
||||
|
||||
@enterprise_is_enabled()
|
||||
def enterprise_customer_from_cache(request=None, uuid=None):
|
||||
"""Check all available caches for Enterprise Customer data."""
|
||||
enterprise_customer = None
|
||||
def enterprise_customer_from_cache(uuid):
|
||||
"""
|
||||
Retrieve enterprise customer data associated with the given ``uuid`` from the Django cache,
|
||||
returning a ``__CACHE_MISS__`` if absent.
|
||||
"""
|
||||
cache_key = get_enterprise_customer_cache_key(uuid)
|
||||
return cache.get(cache_key, _CACHE_MISS)
|
||||
|
||||
# Check if it's cached in the general cache storage.
|
||||
if uuid:
|
||||
cache_key = get_enterprise_customer_cache_key(uuid)
|
||||
enterprise_customer = cache.get(cache_key)
|
||||
|
||||
# Check if it's cached in the session.
|
||||
if not enterprise_customer and request:
|
||||
enterprise_customer = request.session.get('enterprise_customer')
|
||||
def add_enterprise_customer_to_session(request, enterprise_customer):
|
||||
""" Add the given enterprise_customer data to the request's session. """
|
||||
request.session[ENTERPRISE_CUSTOMER_KEY_NAME] = enterprise_customer
|
||||
|
||||
return enterprise_customer
|
||||
|
||||
def enterprise_customer_from_session(request):
|
||||
"""
|
||||
Retrieve enterprise_customer data from the request's session,
|
||||
returning a ``__CACHE_MISS__`` if absent.
|
||||
"""
|
||||
return request.session.get(ENTERPRISE_CUSTOMER_KEY_NAME, _CACHE_MISS)
|
||||
|
||||
|
||||
def enterprise_customer_uuid_from_session(request):
|
||||
"""
|
||||
Retrieve an enterprise customer UUID from the request's session,
|
||||
returning a ``__CACHE_MISS__`` if absent. Note that this may
|
||||
return ``None``, which indicates that we've previously looked
|
||||
for an associated customer for this request's user, and
|
||||
none was present.
|
||||
"""
|
||||
customer_data = enterprise_customer_from_session(request)
|
||||
if customer_data is not _CACHE_MISS:
|
||||
customer_data = customer_data or {}
|
||||
return customer_data.get('uuid')
|
||||
return _CACHE_MISS
|
||||
|
||||
|
||||
def enterprise_customer_uuid_from_query_param(request):
|
||||
"""
|
||||
Returns an enterprise customer UUID from the given request's GET data,
|
||||
or ``__CACHE_MISS__`` if not present.
|
||||
"""
|
||||
return request.GET.get(ENTERPRISE_CUSTOMER_KEY_NAME, _CACHE_MISS)
|
||||
|
||||
|
||||
def enterprise_customer_uuid_from_cookie(request):
|
||||
"""
|
||||
Returns an enterprise customer UUID from the given request's cookies,
|
||||
or ``__CACHE_MISS__`` if not present.
|
||||
"""
|
||||
return request.COOKIES.get(settings.ENTERPRISE_CUSTOMER_COOKIE_NAME, _CACHE_MISS)
|
||||
|
||||
|
||||
@enterprise_is_enabled()
|
||||
@@ -430,34 +468,56 @@ def enterprise_customer_uuid_for_request(request):
|
||||
except EnterpriseCustomer.DoesNotExist:
|
||||
enterprise_customer_uuid = None
|
||||
else:
|
||||
# Check if we got an Enterprise UUID passed directly as either a query
|
||||
# parameter, or as a value in the Enterprise cookie.
|
||||
enterprise_customer_uuid = request.GET.get('enterprise_customer') or request.COOKIES.get(
|
||||
settings.ENTERPRISE_CUSTOMER_COOKIE_NAME
|
||||
)
|
||||
enterprise_customer_uuid = _customer_uuid_from_query_param_cookies_or_session(request)
|
||||
|
||||
if not enterprise_customer_uuid and request.user.is_authenticated:
|
||||
if enterprise_customer_uuid is _CACHE_MISS and request.user.is_authenticated:
|
||||
# If there's no way to get an Enterprise UUID for the request, check to see
|
||||
# if there's already an Enterprise attached to the requesting user on the backend.
|
||||
learner_data = get_enterprise_learner_data_from_db(request.user)
|
||||
if learner_data:
|
||||
enterprise_customer_uuid = learner_data[0]['enterprise_customer']['uuid']
|
||||
enterprise_customer = learner_data[0]['enterprise_customer']
|
||||
enterprise_customer_uuid = enterprise_customer['uuid']
|
||||
cache_enterprise(enterprise_customer)
|
||||
else:
|
||||
enterprise_customer_uuid = None
|
||||
|
||||
# Now that we've asked the database for this users's enterprise customer data,
|
||||
# add it to their session (even if it's null/empty, which indicates the user
|
||||
# has no associated enterprise customer).
|
||||
add_enterprise_customer_to_session(request, enterprise_customer)
|
||||
|
||||
return enterprise_customer_uuid
|
||||
|
||||
|
||||
def _customer_uuid_from_query_param_cookies_or_session(request):
|
||||
"""
|
||||
Helper function that plucks a customer UUID out of the given requests's
|
||||
query params, cookie, or session data.
|
||||
Returns ``__CACHE_MISS__`` if none of those keys are present in the request.
|
||||
"""
|
||||
for function in (
|
||||
enterprise_customer_uuid_from_query_param,
|
||||
enterprise_customer_uuid_from_cookie,
|
||||
enterprise_customer_uuid_from_session,
|
||||
):
|
||||
enterprise_customer_uuid = function(request)
|
||||
if enterprise_customer_uuid is not _CACHE_MISS:
|
||||
return enterprise_customer_uuid
|
||||
|
||||
return _CACHE_MISS
|
||||
|
||||
|
||||
@enterprise_is_enabled()
|
||||
def enterprise_customer_for_request(request):
|
||||
"""
|
||||
Check all the context clues of the request to determine if
|
||||
the request being made is tied to a particular EnterpriseCustomer.
|
||||
"""
|
||||
if 'enterprise_customer' in request.session and request.session['enterprise_customer']:
|
||||
return enterprise_customer_from_cache(request=request)
|
||||
else:
|
||||
enterprise_customer = enterprise_customer_from_session(request)
|
||||
if enterprise_customer is _CACHE_MISS:
|
||||
enterprise_customer = enterprise_customer_from_api(request)
|
||||
request.session['enterprise_customer'] = enterprise_customer
|
||||
return enterprise_customer
|
||||
add_enterprise_customer_to_session(request, enterprise_customer)
|
||||
return enterprise_customer
|
||||
|
||||
|
||||
@enterprise_is_enabled(otherwise=False)
|
||||
@@ -587,19 +647,21 @@ def get_enterprise_learner_portal_enabled_message(request):
|
||||
"""
|
||||
Returns message to be displayed in dashboard if the user is linked to an Enterprise with the Learner Portal enabled.
|
||||
|
||||
Note: request.session['enterprise_customer'] will be used in case the user is linked to
|
||||
Note: request.session[ENTERPRISE_CUSTOMER_KEY_NAME] will be used in case the user is linked to
|
||||
multiple Enterprises. Otherwise, it won't exist and the Enterprise Learner data
|
||||
will be used. If that doesn't exist return None.
|
||||
|
||||
Args:
|
||||
request: request made to the LMS dashboard
|
||||
"""
|
||||
if 'enterprise_customer' in request.session and request.session['enterprise_customer']:
|
||||
enterprise_customer = request.session['enterprise_customer']
|
||||
else:
|
||||
enterprise_customer = enterprise_customer_from_session(request)
|
||||
if enterprise_customer is _CACHE_MISS:
|
||||
learner_data = get_enterprise_learner_data_from_db(request.user)
|
||||
if learner_data:
|
||||
enterprise_customer = learner_data[0]['enterprise_customer']
|
||||
enterprise_customer = learner_data[0]['enterprise_customer'] if learner_data else None
|
||||
# Add to session cache regardless of whether it is null
|
||||
add_enterprise_customer_to_session(request, enterprise_customer)
|
||||
if enterprise_customer:
|
||||
cache_enterprise(enterprise_customer)
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
@@ -172,7 +172,13 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
|
||||
@mock.patch('openedx.features.enterprise_support.api.get_enterprise_learner_data_from_db')
|
||||
def test_consent_needed_for_course(self, mock_get_enterprise_learner_data):
|
||||
user = UserFactory(username='janedoe')
|
||||
request = mock.MagicMock(session={}, user=user, site=SiteFactory(domain="example.com"))
|
||||
request = mock.MagicMock(
|
||||
user=user,
|
||||
site=SiteFactory(domain="example.com"),
|
||||
session={},
|
||||
COOKIES={},
|
||||
GET={},
|
||||
)
|
||||
ec_uuid = 'cf246b88-d5f6-4908-a522-fc307e0b0c59'
|
||||
course_id = 'fake-course'
|
||||
mock_get_enterprise_learner_data.return_value = self.get_mock_enterprise_learner_results()
|
||||
@@ -266,28 +272,55 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
|
||||
# the third-party auth pipeline has no `provider_id`.
|
||||
mock_registry.get_from_pipeline.return_value.provider_id = None
|
||||
self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200)
|
||||
enterprise_customer = enterprise_customer_for_request(
|
||||
mock.MagicMock(GET={'enterprise_customer': 'real-ent-uuid'}, user=self.user)
|
||||
mock_request = mock.MagicMock(
|
||||
GET={'enterprise_customer': 'real-ent-uuid'},
|
||||
COOKIES={},
|
||||
session={},
|
||||
user=self.user
|
||||
)
|
||||
enterprise_customer = enterprise_customer_for_request(mock_request)
|
||||
self.assertEqual(enterprise_customer, {'real': 'enterprisecustomer'})
|
||||
|
||||
# Verify that the method `enterprise_customer_for_request` returns
|
||||
# expected enterprise customer against the requesting user even if
|
||||
# the third-party auth pipeline has no `provider_id` but there is
|
||||
# enterprise customer UUID in the cookie.
|
||||
enterprise_customer = enterprise_customer_for_request(
|
||||
mock.MagicMock(GET={}, COOKIES={settings.ENTERPRISE_CUSTOMER_COOKIE_NAME: 'real-ent-uuid'}, user=self.user)
|
||||
mock_request = mock.MagicMock(
|
||||
GET={},
|
||||
COOKIES={settings.ENTERPRISE_CUSTOMER_COOKIE_NAME: 'real-ent-uuid'},
|
||||
session={},
|
||||
user=self.user
|
||||
)
|
||||
enterprise_customer = enterprise_customer_for_request(mock_request)
|
||||
self.assertEqual(enterprise_customer, {'real': 'enterprisecustomer'})
|
||||
|
||||
# Verify that the method `enterprise_customer_for_request` returns
|
||||
# expected enterprise customer against the requesting user if
|
||||
# data is cached only in the request session
|
||||
mock_registry.get_from_pipeline.return_value.provider_id = None
|
||||
self.mock_get_enterprise_customer('real-ent-uuid', {'real': 'enterprisecustomer'}, 200)
|
||||
mock_request = mock.MagicMock(
|
||||
GET={},
|
||||
COOKIES={},
|
||||
session={'enterprise_customer': {'real': 'enterprisecustomer'}},
|
||||
user=self.user
|
||||
)
|
||||
enterprise_customer = enterprise_customer_for_request(mock_request)
|
||||
self.assertEqual(enterprise_customer, {'real': 'enterprisecustomer'})
|
||||
|
||||
# Verify that we can still get enterprise customer from enterprise
|
||||
# learner API even if we are unable to get it from preferred sources,
|
||||
# e.g. url query parameters, third-party auth pipeline, enterprise
|
||||
# cookie.
|
||||
# cookie, or session.
|
||||
mock_get_enterprise_learner_data.return_value = [{'enterprise_customer': {'uuid': 'real-ent-uuid'}}]
|
||||
enterprise_customer = enterprise_customer_for_request(
|
||||
mock.MagicMock(GET={}, COOKIES={}, user=self.user, site=1)
|
||||
mock_request = mock.MagicMock(
|
||||
GET={},
|
||||
COOKIES={},
|
||||
session={},
|
||||
user=self.user,
|
||||
site=1
|
||||
)
|
||||
enterprise_customer = enterprise_customer_for_request(mock_request)
|
||||
self.assertEqual(enterprise_customer, {'real': 'enterprisecustomer'})
|
||||
|
||||
def test_enterprise_customer_for_request_with_session(self):
|
||||
@@ -312,13 +345,13 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_from_api',
|
||||
return_value=enterprise_data
|
||||
) as mock_enterprise_customer_from_api, mock.patch(
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_from_cache',
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_from_session',
|
||||
return_value=enterprise_data
|
||||
) as mock_enterprise_customer_from_cache:
|
||||
) as mock_enterprise_customer_from_session:
|
||||
enterprise_customer = enterprise_customer_for_request(dummy_request)
|
||||
self.assertEqual(enterprise_customer, enterprise_data)
|
||||
self.assertEqual(mock_enterprise_customer_from_api.called, False)
|
||||
self.assertEqual(mock_enterprise_customer_from_cache.called, True)
|
||||
self.assertEqual(mock_enterprise_customer_from_session.called, True)
|
||||
|
||||
# Verify enterprise customer data fetched from session for subsequent calls
|
||||
# with unauthenticated user in SAML case
|
||||
@@ -328,13 +361,13 @@ class TestEnterpriseApi(EnterpriseServiceMockMixin, CacheIsolationTestCase):
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_from_api',
|
||||
return_value=enterprise_data
|
||||
) as mock_enterprise_customer_from_api, mock.patch(
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_from_cache',
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_from_session',
|
||||
return_value=enterprise_data
|
||||
) as mock_enterprise_customer_from_cache:
|
||||
) as mock_enterprise_customer_from_session:
|
||||
enterprise_customer = enterprise_customer_for_request(dummy_request)
|
||||
self.assertEqual(enterprise_customer, enterprise_data)
|
||||
self.assertEqual(mock_enterprise_customer_from_api.called, False)
|
||||
self.assertEqual(mock_enterprise_customer_from_cache.called, True)
|
||||
self.assertEqual(mock_enterprise_customer_from_session.called, True)
|
||||
|
||||
def check_data_sharing_consent(self, consent_required=False, consent_url=None):
|
||||
"""
|
||||
|
||||
@@ -11,7 +11,7 @@ from django.test.utils import override_settings
|
||||
|
||||
from openedx.core.djangolib.testing.utils import skip_unless_lms
|
||||
from openedx.core.djangoapps.waffle_utils.testutils import override_waffle_flag
|
||||
from openedx.features.enterprise_support.utils import ENTERPRISE_HEADER_LINKS, get_enterprise_learner_portals
|
||||
from openedx.features.enterprise_support.utils import ENTERPRISE_HEADER_LINKS, get_enterprise_learner_portal
|
||||
from openedx.features.enterprise_support.tests import FEATURES_WITH_ENTERPRISE_ENABLED
|
||||
from openedx.features.enterprise_support.tests.factories import (
|
||||
EnterpriseCustomerBrandingConfigurationFactory, EnterpriseCustomerUserFactory,
|
||||
@@ -49,9 +49,10 @@ class TestEnterpriseUtils(TestCase):
|
||||
self.assertEqual(mock_customer_request.call_count, expected_calls)
|
||||
|
||||
@override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
|
||||
def test_get_enterprise_learner_portals_uncached(self):
|
||||
def test_get_enterprise_learner_portal_uncached(self):
|
||||
"""
|
||||
Test that only enabled enterprise portals are returned
|
||||
Test that only an enabled enterprise portal is returned,
|
||||
and that it matches the customer UUID provided in the request.
|
||||
"""
|
||||
enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
|
||||
EnterpriseCustomerBrandingConfigurationFactory(
|
||||
@@ -61,24 +62,88 @@ class TestEnterpriseUtils(TestCase):
|
||||
enterprise_customer_user.enterprise_customer.save()
|
||||
|
||||
request = mock.MagicMock(session={}, user=self.user)
|
||||
portals = get_enterprise_learner_portals(request)
|
||||
self.assertEqual(len(portals), 1)
|
||||
self.assertDictEqual(portals[0], {
|
||||
# Indicate the "preferred" customer in the request
|
||||
request.GET = {'enterprise_customer': enterprise_customer_user.enterprise_customer.uuid}
|
||||
|
||||
# Create another enterprise customer association for the same user.
|
||||
# There should be no data returned for this customer's portal,
|
||||
# because we filter for only the enterprise customer uuid found in the request.
|
||||
other_enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
|
||||
other_enterprise_customer_user.enable_learner_portal = True
|
||||
other_enterprise_customer_user.save()
|
||||
|
||||
portal = get_enterprise_learner_portal(request)
|
||||
self.assertDictEqual(portal, {
|
||||
'name': enterprise_customer_user.enterprise_customer.name,
|
||||
'slug': enterprise_customer_user.enterprise_customer.slug,
|
||||
'logo': enterprise_customer_user.enterprise_customer.branding_configuration.logo.url,
|
||||
})
|
||||
|
||||
@override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
|
||||
def test_get_enterprise_learner_portals_cached(self):
|
||||
def test_get_enterprise_learner_portal_no_branding_config(self):
|
||||
"""
|
||||
Test that only an enabled enterprise portal is returned,
|
||||
and that it matches the customer UUID provided in the request,
|
||||
even if no branding config is associated with the customer.
|
||||
"""
|
||||
enterprise_customer_user = EnterpriseCustomerUserFactory.create(active=True, user_id=self.user.id)
|
||||
enterprise_customer_user.enterprise_customer.enable_learner_portal = True
|
||||
enterprise_customer_user.enterprise_customer.save()
|
||||
|
||||
request = mock.MagicMock(session={}, user=self.user)
|
||||
# Indicate the "preferred" customer in the request
|
||||
request.GET = {'enterprise_customer': enterprise_customer_user.enterprise_customer.uuid}
|
||||
|
||||
portal = get_enterprise_learner_portal(request)
|
||||
self.assertDictEqual(portal, {
|
||||
'name': enterprise_customer_user.enterprise_customer.name,
|
||||
'slug': enterprise_customer_user.enterprise_customer.slug,
|
||||
'logo': None,
|
||||
})
|
||||
|
||||
@override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
|
||||
def test_get_enterprise_learner_portal_no_customer_from_request(self):
|
||||
"""
|
||||
Test that only one enabled enterprise portal is returned,
|
||||
even if enterprise_customer_uuid_from_request() returns None.
|
||||
"""
|
||||
# Create another enterprise customer association for the same user.
|
||||
# There should be no data returned for this customer's portal,
|
||||
# because another customer is later created with a more recent active/modified time.
|
||||
other_enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
|
||||
other_enterprise_customer_user.enable_learner_portal = True
|
||||
other_enterprise_customer_user.save()
|
||||
|
||||
enterprise_customer_user = EnterpriseCustomerUserFactory(active=True, user_id=self.user.id)
|
||||
EnterpriseCustomerBrandingConfigurationFactory(
|
||||
enterprise_customer=enterprise_customer_user.enterprise_customer,
|
||||
)
|
||||
enterprise_customer_user.enterprise_customer.enable_learner_portal = True
|
||||
enterprise_customer_user.enterprise_customer.save()
|
||||
|
||||
request = mock.MagicMock(session={}, user=self.user)
|
||||
|
||||
with mock.patch(
|
||||
'openedx.features.enterprise_support.api.enterprise_customer_uuid_for_request',
|
||||
return_value=None,
|
||||
):
|
||||
portal = get_enterprise_learner_portal(request)
|
||||
|
||||
self.assertDictEqual(portal, {
|
||||
'name': enterprise_customer_user.enterprise_customer.name,
|
||||
'slug': enterprise_customer_user.enterprise_customer.slug,
|
||||
'logo': enterprise_customer_user.enterprise_customer.branding_configuration.logo.url,
|
||||
})
|
||||
|
||||
@override_waffle_flag(ENTERPRISE_HEADER_LINKS, True)
|
||||
def test_get_enterprise_learner_portal_cached(self):
|
||||
enterprise_customer_data = {
|
||||
'name': 'Enabled Customer',
|
||||
'slug': 'enabled_customer',
|
||||
'logo': 'https://logo.url',
|
||||
}
|
||||
request = mock.MagicMock(session={
|
||||
'enterprise_learner_portals': json.dumps([enterprise_customer_data])
|
||||
'enterprise_learner_portal': json.dumps(enterprise_customer_data)
|
||||
}, user=self.user)
|
||||
portals = get_enterprise_learner_portals(request)
|
||||
self.assertEqual(len(portals), 1)
|
||||
self.assertDictEqual(portals[0], enterprise_customer_data)
|
||||
portal = get_enterprise_learner_portal(request)
|
||||
self.assertDictEqual(portal, enterprise_customer_data)
|
||||
|
||||
@@ -10,6 +10,7 @@ from django.conf import settings
|
||||
from django.urls import NoReverseMatch, reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from edx_django_utils.cache import TieredCache, get_cache_key
|
||||
from enterprise.api.v1.serializers import EnterpriseCustomerBrandingConfigurationSerializer
|
||||
from enterprise.models import EnterpriseCustomerUser, EnterpriseCustomer
|
||||
from social_django.models import UserSocialAuth
|
||||
|
||||
@@ -293,42 +294,77 @@ def _get_sync_learner_profile_data(enterprise_customer):
|
||||
return False
|
||||
|
||||
|
||||
def get_enterprise_learner_portals(request):
|
||||
def get_enterprise_learner_portal(request):
|
||||
"""
|
||||
Gets the formatted portal names and slugs that can be used
|
||||
to generate links for enabled enterprise Learner Portals.
|
||||
Gets the formatted portal name and slug that can be used
|
||||
to generate a link for an enabled enterprise Learner Portal.
|
||||
|
||||
Caches and returns results in/from the user's request session if provided.
|
||||
Caches and returns result in/from the user's request session if provided.
|
||||
"""
|
||||
# Prevent a circular import.
|
||||
from openedx.features.enterprise_support.api import enterprise_enabled
|
||||
from openedx.features.enterprise_support.api import enterprise_enabled, enterprise_customer_uuid_for_request
|
||||
|
||||
user = request.user
|
||||
# Only cache this if a learner is authenticated (AnonymousUser exists and should not be tracked)
|
||||
|
||||
learner_portal_session_key = 'enterprise_learner_portal'
|
||||
|
||||
if enterprise_enabled() and ENTERPRISE_HEADER_LINKS.is_enabled() and user and user.id:
|
||||
# If the key exists return that value
|
||||
if 'enterprise_learner_portals' in request.session:
|
||||
return json.loads(request.session['enterprise_learner_portals'])
|
||||
# Ordering is important, this is consistent with how we decide on which
|
||||
# enterprise_customer is the selected one for an enterprise_customer
|
||||
enterprise_learner_portals = [{
|
||||
'name': enterprise_customer_user.enterprise_customer.name,
|
||||
'slug': enterprise_customer_user.enterprise_customer.slug,
|
||||
'logo': enterprise_customer_user.enterprise_customer.branding_configuration.logo.url if
|
||||
enterprise_customer_user.enterprise_customer.branding_configuration else None,
|
||||
} for enterprise_customer_user in EnterpriseCustomerUser.objects.filter(
|
||||
user_id=user.id, enterprise_customer__enable_learner_portal=True
|
||||
).prefetch_related(
|
||||
'enterprise_customer', 'enterprise_customer__branding_configuration'
|
||||
).order_by('-enterprise_customer__active', '-modified')]
|
||||
if learner_portal_session_key in request.session:
|
||||
return json.loads(request.session[learner_portal_session_key])
|
||||
|
||||
kwargs = {
|
||||
'user_id': user.id,
|
||||
'enterprise_customer__enable_learner_portal': True,
|
||||
}
|
||||
enterprise_customer_uuid = enterprise_customer_uuid_for_request(request)
|
||||
if enterprise_customer_uuid:
|
||||
kwargs['enterprise_customer__uuid'] = enterprise_customer_uuid
|
||||
|
||||
queryset = EnterpriseCustomerUser.objects.filter(**kwargs).prefetch_related(
|
||||
'enterprise_customer',
|
||||
'enterprise_customer__branding_configuration',
|
||||
)
|
||||
|
||||
if not enterprise_customer_uuid:
|
||||
# If the request doesn't help us know which Enterprise Customer UUID to select with,
|
||||
# order by the most recently activated/modified customers,
|
||||
# so that when we select the first result of the query as the preferred
|
||||
# customer, it's the most recently active one.
|
||||
queryset = queryset.order_by('-enterprise_customer__active', '-modified')
|
||||
|
||||
preferred_enterprise_customer_user = queryset.first()
|
||||
if not preferred_enterprise_customer_user:
|
||||
return None
|
||||
|
||||
enterprise_customer = preferred_enterprise_customer_user.enterprise_customer
|
||||
learner_portal_data = {
|
||||
'name': enterprise_customer.name,
|
||||
'slug': enterprise_customer.slug,
|
||||
'logo': enterprise_branding_configuration(enterprise_customer).get('logo'),
|
||||
}
|
||||
|
||||
# Cache the result in the user's request session
|
||||
request.session['enterprise_learner_portals'] = json.dumps(enterprise_learner_portals)
|
||||
|
||||
return enterprise_learner_portals
|
||||
request.session[learner_portal_session_key] = json.dumps(learner_portal_data)
|
||||
return learner_portal_data
|
||||
return None
|
||||
|
||||
|
||||
def enterprise_branding_configuration(enterprise_customer_obj):
|
||||
"""
|
||||
Given an instance of ``EnterpriseCustomer``, returns a related
|
||||
branding_configuration serialized dictionary if it exists, otherwise an empty dictionary.
|
||||
"""
|
||||
# We can use hasattr() on one-to-one relationships to avoid exception-catching:
|
||||
# https://docs.djangoproject.com/en/2.2/topics/db/examples/one_to_one/
|
||||
if not hasattr(enterprise_customer_obj, 'branding_configuration'):
|
||||
return {}
|
||||
|
||||
branding_config = enterprise_customer_obj.branding_configuration
|
||||
return EnterpriseCustomerBrandingConfigurationSerializer(branding_config).data
|
||||
|
||||
|
||||
def get_enterprise_learner_generic_name(request):
|
||||
"""
|
||||
Get a generic name concatenating the Enterprise Customer name and 'Learner'.
|
||||
|
||||
Reference in New Issue
Block a user