ENT-251 Updated track selection UI for Enterprise context
This commit is contained in:
@@ -6,6 +6,7 @@ from datetime import datetime
|
||||
import unittest
|
||||
import decimal
|
||||
import ddt
|
||||
import httpretty
|
||||
import freezegun
|
||||
from mock import patch
|
||||
from nose.plugins.attrib import attr
|
||||
@@ -25,12 +26,14 @@ from student.models import CourseEnrollment
|
||||
from student.tests.factories import CourseEnrollmentFactory, UserFactory
|
||||
from util.testing import UrlResetMixin
|
||||
from openedx.core.djangoapps.theming.tests.test_util import with_comprehensive_theme
|
||||
from util.tests.mixins.enterprise import EnterpriseServiceMockMixin
|
||||
from util import organizations_helpers as organizations_api
|
||||
|
||||
|
||||
@attr(shard=3)
|
||||
@ddt.ddt
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseServiceMockMixin):
|
||||
"""
|
||||
Course Mode View tests
|
||||
"""
|
||||
@@ -44,6 +47,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
self.client.login(username=self.user.username, password="edx")
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
# is_active?, enrollment_mode, redirect?
|
||||
(True, 'verified', True),
|
||||
@@ -69,6 +73,14 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
user=self.user
|
||||
)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# Configure whether we're upgrading or not
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
@@ -118,17 +130,101 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
self.assertRedirects(response, 'http://testserver/test_basket/?sku=TEST', fetch_redirect_response=False)
|
||||
ecomm_test_utils.update_commerce_config(enabled=False)
|
||||
|
||||
@httpretty.activate
|
||||
def test_no_enrollment(self):
|
||||
# Create the course modes
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# User visits the track selection page directly without ever enrolling
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
@httpretty.activate
|
||||
def test_enterprise_learner_context(self):
|
||||
"""
|
||||
Test: Track selection page should show the enterprise context message if user belongs to the Enterprise.
|
||||
"""
|
||||
# Create the course modes
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# User visits the track selection page directly without ever enrolling
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertContains(
|
||||
response,
|
||||
'Welcome, {username}! You are about to enroll in {course_name}, from {partner_names}, '
|
||||
'sponsored by TestShib. Please select your enrollment information below.'.format(
|
||||
username=self.user.username,
|
||||
course_name=self.course.display_name_with_default_escaped,
|
||||
partner_names=self.course.org
|
||||
)
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
def test_enterprise_learner_context_with_multiple_organizations(self):
|
||||
"""
|
||||
Test: Track selection page should show the enterprise context message with multiple organization names
|
||||
if user belongs to the Enterprise.
|
||||
"""
|
||||
# Create the course modes
|
||||
for mode in ('audit', 'honor', 'verified'):
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# Creating organization
|
||||
for i in xrange(2):
|
||||
test_organization_data = {
|
||||
'name': 'test organization ' + str(i),
|
||||
'short_name': 'test_organization_' + str(i),
|
||||
'description': 'Test Organization Description',
|
||||
'active': True,
|
||||
'logo': '/logo_test1.png/'
|
||||
}
|
||||
test_org = organizations_api.add_organization(organization_data=test_organization_data)
|
||||
organizations_api.add_organization_course(organization_data=test_org, course_id=unicode(self.course.id))
|
||||
|
||||
# User visits the track selection page directly without ever enrolling
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
self.assertContains(
|
||||
response,
|
||||
'Welcome, {username}! You are about to enroll in {course_name}, from test organization 0 and '
|
||||
'test organization 1, sponsored by TestShib. Please select your enrollment information below.'.format(
|
||||
username=self.user.username,
|
||||
course_name=self.course.display_name_with_default_escaped
|
||||
)
|
||||
)
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
'',
|
||||
'1,,2',
|
||||
@@ -155,6 +251,14 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
user=self.user
|
||||
)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# Verify that the prices render correctly
|
||||
response = self.client.get(
|
||||
reverse('course_modes_choose', args=[unicode(self.course.id)]),
|
||||
@@ -165,6 +269,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
# TODO: Fix it so that response.templates works w/ mako templates, and then assert
|
||||
# that the right template rendered
|
||||
|
||||
@httpretty.activate
|
||||
@ddt.data(
|
||||
(['honor', 'verified', 'credit'], True),
|
||||
(['honor', 'verified'], False),
|
||||
@@ -175,6 +280,14 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
for mode in available_modes:
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# Check whether credit upsell is shown on the page
|
||||
# This should *only* be shown when a credit mode is available
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
@@ -375,11 +488,20 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
@with_comprehensive_theme("edx.org")
|
||||
@httpretty.activate
|
||||
def test_hide_nav(self):
|
||||
# Create the course modes
|
||||
for mode in ["honor", "verified"]:
|
||||
CourseModeFactory.create(mode_slug=mode, course_id=self.course.id)
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
|
||||
# Load the track selection page
|
||||
url = reverse('course_modes_choose', args=[unicode(self.course.id)])
|
||||
response = self.client.get(url)
|
||||
@@ -406,7 +528,7 @@ class CourseModeViewTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
|
||||
|
||||
@unittest.skipUnless(settings.ROOT_URLCONF == 'lms.urls', 'Test only valid in lms')
|
||||
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase, EnterpriseServiceMockMixin):
|
||||
"""Test embargo restrictions on the track selection page. """
|
||||
|
||||
URLCONF_MODULES = ['openedx.core.djangoapps.embargo']
|
||||
@@ -433,6 +555,15 @@ class TrackSelectionEmbargoTest(UrlResetMixin, ModuleStoreTestCase):
|
||||
response = self.client.get(self.url)
|
||||
self.assertRedirects(response, redirect_url)
|
||||
|
||||
@httpretty.activate
|
||||
def test_embargo_allow(self):
|
||||
|
||||
self.mock_enterprise_learner_api()
|
||||
# Create a service user and log in.
|
||||
UserFactory.create(
|
||||
username='enterprise_worker',
|
||||
email="bob@example.com",
|
||||
password="edx",
|
||||
)
|
||||
response = self.client.get(self.url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
@@ -26,6 +26,8 @@ from edxmako.shortcuts import render_to_response
|
||||
from openedx.core.djangoapps.embargo import api as embargo_api
|
||||
from student.models import CourseEnrollment
|
||||
from util.db import outer_atomic
|
||||
from util import enterprise_helpers as enterprise_api
|
||||
from util import organizations_helpers as organization_api
|
||||
|
||||
|
||||
class ChooseModeView(View):
|
||||
@@ -148,6 +150,20 @@ class ChooseModeView(View):
|
||||
"responsive": True,
|
||||
"nav_hidden": True,
|
||||
}
|
||||
|
||||
enterprise_learner_data = enterprise_api.get_enterprise_learner_data(site=request.site, user=request.user)
|
||||
if enterprise_learner_data:
|
||||
context["show_enterprise_context"] = True
|
||||
context["partner_names"] = partner_name = course.display_organization \
|
||||
if course.display_organization else course.org
|
||||
context["enterprise_name"] = enterprise_learner_data[0]['enterprise_customer']['name']
|
||||
context["username"] = request.user.username
|
||||
organizations = organization_api.get_course_organizations(course_id=course.id)
|
||||
if organizations:
|
||||
context["partner_names"] = ' and '.join([
|
||||
org.get('name', partner_name) for org in organizations
|
||||
])
|
||||
|
||||
if "verified" in modes:
|
||||
verified_mode = modes["verified"]
|
||||
context["suggested_prices"] = [
|
||||
|
||||
@@ -9,6 +9,7 @@ from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.shortcuts import redirect
|
||||
from django.utils.http import urlencode
|
||||
from django.core.cache import cache
|
||||
from edx_rest_api_client.client import EdxRestApiClient
|
||||
try:
|
||||
from enterprise import utils as enterprise_utils
|
||||
@@ -18,6 +19,8 @@ except ImportError:
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.lib.token_utils import JwtBuilder
|
||||
from slumber.exceptions import HttpClientError, HttpServerError
|
||||
import hashlib
|
||||
import six
|
||||
|
||||
|
||||
ENTERPRISE_CUSTOMER_BRANDING_OVERRIDE_DETAILS = 'enterprise_customer_branding_override_details'
|
||||
@@ -71,6 +74,108 @@ class EnterpriseApiClient(object):
|
||||
LOGGER.exception(message)
|
||||
raise EnterpriseApiException(message)
|
||||
|
||||
def fetch_enterprise_learner_data(self, site, user):
|
||||
"""
|
||||
Fetch information related to enterprise from the Enterprise Service.
|
||||
|
||||
Example:
|
||||
fetch_enterprise_learner_data(site, user)
|
||||
|
||||
Argument:
|
||||
site: (Site) site instance
|
||||
user: (User) django auth user
|
||||
|
||||
Returns:
|
||||
dict: {
|
||||
"enterprise_api_response_for_learner": {
|
||||
"count": 1,
|
||||
"num_pages": 1,
|
||||
"current_page": 1,
|
||||
"results": [
|
||||
{
|
||||
"enterprise_customer": {
|
||||
"uuid": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
|
||||
"name": "TestShib",
|
||||
"catalog": 2,
|
||||
"active": true,
|
||||
"site": {
|
||||
"domain": "example.com",
|
||||
"name": "example.com"
|
||||
},
|
||||
"enable_data_sharing_consent": true,
|
||||
"enforce_data_sharing_consent": "at_login",
|
||||
"enterprise_customer_users": [
|
||||
1
|
||||
],
|
||||
"branding_configuration": {
|
||||
"enterprise_customer": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
|
||||
"logo": "https://open.edx.org/sites/all/themes/edx_open/logo.png"
|
||||
},
|
||||
"enterprise_customer_entitlements": [
|
||||
{
|
||||
"enterprise_customer": "cf246b88-d5f6-4908-a522-fc307e0b0c59",
|
||||
"entitlement_id": 69
|
||||
}
|
||||
]
|
||||
},
|
||||
"user_id": 5,
|
||||
"user": {
|
||||
"username": "staff",
|
||||
"first_name": "",
|
||||
"last_name": "",
|
||||
"email": "staff@example.com",
|
||||
"is_staff": true,
|
||||
"is_active": true,
|
||||
"date_joined": "2016-09-01T19:18:26.026495Z"
|
||||
},
|
||||
"data_sharing_consent": [
|
||||
{
|
||||
"user": 1,
|
||||
"state": "enabled",
|
||||
"enabled": true
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"next": null,
|
||||
"start": 0,
|
||||
"previous": null
|
||||
}
|
||||
}
|
||||
|
||||
Raises:
|
||||
ConnectionError: requests exception "ConnectionError", raised if if ecommerce is unable to connect
|
||||
to enterprise api server.
|
||||
SlumberBaseException: base slumber exception "SlumberBaseException", raised if API response contains
|
||||
http error status like 4xx, 5xx etc.
|
||||
Timeout: requests exception "Timeout", raised if enterprise API is taking too long for returning
|
||||
a response. This exception is raised for both connection timeout and read timeout.
|
||||
|
||||
"""
|
||||
api_resource_name = 'enterprise-learner'
|
||||
|
||||
cache_key = get_cache_key(
|
||||
site_domain=site.domain,
|
||||
resource=api_resource_name,
|
||||
username=user.username
|
||||
)
|
||||
|
||||
response = cache.get(cache_key)
|
||||
if not response:
|
||||
try:
|
||||
endpoint = getattr(self.client, api_resource_name)
|
||||
querystring = {'username': user.username}
|
||||
response = endpoint().get(**querystring)
|
||||
cache.set(cache_key, response, settings.ENTERPRISE_API_CACHE_TIMEOUT)
|
||||
except (HttpClientError, HttpServerError):
|
||||
message = ("An error occurred while getting EnterpriseLearner data for user {username}".format(
|
||||
username=user.username
|
||||
))
|
||||
LOGGER.exception(message)
|
||||
return None
|
||||
|
||||
return response
|
||||
|
||||
|
||||
def data_sharing_consent_required(view_func):
|
||||
"""
|
||||
@@ -225,3 +330,39 @@ def get_enterprise_branding_filter_param(request):
|
||||
|
||||
"""
|
||||
return request.session.get(ENTERPRISE_CUSTOMER_BRANDING_OVERRIDE_DETAILS, None)
|
||||
|
||||
|
||||
def get_cache_key(**kwargs):
|
||||
"""
|
||||
Get MD5 encoded cache key for given arguments.
|
||||
|
||||
Here is the format of key before MD5 encryption.
|
||||
key1:value1__key2:value2 ...
|
||||
|
||||
Example:
|
||||
>>> get_cache_key(site_domain="example.com", resource="enterprise-learner")
|
||||
# Here is key format for above call
|
||||
# "site_domain:example.com__resource:enterprise-learner"
|
||||
a54349175618ff1659dee0978e3149ca
|
||||
|
||||
Arguments:
|
||||
**kwargs: Key word arguments that need to be present in cache key.
|
||||
|
||||
Returns:
|
||||
An MD5 encoded key uniquely identified by the key word arguments.
|
||||
"""
|
||||
key = '__'.join(['{}:{}'.format(item, value) for item, value in six.iteritems(kwargs)])
|
||||
|
||||
return hashlib.md5(key).hexdigest()
|
||||
|
||||
|
||||
def get_enterprise_learner_data(site, user):
|
||||
"""
|
||||
Client API operation adapter/wrapper
|
||||
"""
|
||||
if not enterprise_enabled():
|
||||
return None
|
||||
|
||||
enterprise_learner_data = EnterpriseApiClient().fetch_enterprise_learner_data(site=site, user=user)
|
||||
if enterprise_learner_data:
|
||||
return enterprise_learner_data['results']
|
||||
|
||||
@@ -57,6 +57,80 @@ class EnterpriseServiceMockMixin(object):
|
||||
status=500
|
||||
)
|
||||
|
||||
def mock_enterprise_learner_api(
|
||||
self,
|
||||
catalog_id=1,
|
||||
entitlement_id=1,
|
||||
learner_id=1,
|
||||
enterprise_customer_uuid='cf246b88-d5f6-4908-a522-fc307e0b0c59'
|
||||
):
|
||||
"""
|
||||
Helper function to register enterprise learner API endpoint.
|
||||
"""
|
||||
enterprise_learner_api_response = {
|
||||
'count': 1,
|
||||
'num_pages': 1,
|
||||
'current_page': 1,
|
||||
'results': [
|
||||
{
|
||||
'id': learner_id,
|
||||
'enterprise_customer': {
|
||||
'uuid': enterprise_customer_uuid,
|
||||
'name': 'TestShib',
|
||||
'catalog': catalog_id,
|
||||
'active': True,
|
||||
'site': {
|
||||
'domain': 'example.com',
|
||||
'name': 'example.com'
|
||||
},
|
||||
'enable_data_sharing_consent': True,
|
||||
'enforce_data_sharing_consent': 'at_login',
|
||||
'enterprise_customer_users': [
|
||||
1
|
||||
],
|
||||
'branding_configuration': {
|
||||
'enterprise_customer': enterprise_customer_uuid,
|
||||
'logo': 'https://open.edx.org/sites/all/themes/edx_open/logo.png'
|
||||
},
|
||||
'enterprise_customer_entitlements': [
|
||||
{
|
||||
'enterprise_customer': enterprise_customer_uuid,
|
||||
'entitlement_id': entitlement_id
|
||||
}
|
||||
]
|
||||
},
|
||||
'user_id': 5,
|
||||
'user': {
|
||||
'username': 'verified',
|
||||
'first_name': '',
|
||||
'last_name': '',
|
||||
'email': 'verified@example.com',
|
||||
'is_staff': True,
|
||||
'is_active': True,
|
||||
'date_joined': '2016-09-01T19:18:26.026495Z'
|
||||
},
|
||||
'data_sharing_consent': [
|
||||
{
|
||||
'user': 1,
|
||||
'state': 'enabled',
|
||||
'enabled': True
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
'next': None,
|
||||
'start': 0,
|
||||
'previous': None
|
||||
}
|
||||
enterprise_learner_api_response_json = json.dumps(enterprise_learner_api_response)
|
||||
|
||||
httpretty.register_uri(
|
||||
method=httpretty.GET,
|
||||
uri=self.get_enterprise_url('enterprise-learner'),
|
||||
body=enterprise_learner_api_response_json,
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
||||
class EnterpriseTestConsentRequired(object):
|
||||
"""
|
||||
|
||||
33
common/test/db_fixtures/enterprise.json
Normal file
33
common/test/db_fixtures/enterprise.json
Normal file
@@ -0,0 +1,33 @@
|
||||
[
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "auth.user",
|
||||
"fields": {
|
||||
"date_joined": "2015-06-12 11:02:13.007790+00:00",
|
||||
"username": "enterprise_worker",
|
||||
"first_name": "enterprise",
|
||||
"last_name": "worker",
|
||||
"email":"enterprise_worker@example.com",
|
||||
"password": "enterpriseworker",
|
||||
"is_staff": false,
|
||||
"is_active": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "student.userprofile",
|
||||
"fields": {
|
||||
"user": 2,
|
||||
"name": "enterprise worker",
|
||||
"courseware": "course.xml"
|
||||
}
|
||||
},
|
||||
{
|
||||
"pk": 2,
|
||||
"model": "student.registration",
|
||||
"fields": {
|
||||
"user": 2,
|
||||
"activation_key": "52bfac10384d49219385dcd4cc17177h"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -3063,3 +3063,4 @@ DOC_LINK_BASE_URL = None
|
||||
|
||||
ENTERPRISE_ENROLLMENT_API_URL = LMS_ROOT_URL + "/api/enrollment/v1/"
|
||||
ENTERPRISE_PUBLIC_ENROLLMENT_API_URL = ENTERPRISE_ENROLLMENT_API_URL
|
||||
ENTERPRISE_API_CACHE_TIMEOUT = 3600 # Value is in seconds
|
||||
|
||||
@@ -73,7 +73,13 @@ from openedx.core.djangolib.markup import HTML, Text
|
||||
<article class="register-choose content-main">
|
||||
<header class="page-header content-main">
|
||||
<h3 class="title">
|
||||
${_("Congratulations! You are now enrolled in {course_name}").format(course_name=course_name)}
|
||||
% if show_enterprise_context:
|
||||
${_("Welcome, {username}! You are about to enroll in {course_name}, from "
|
||||
"{partner_names}, sponsored by {enterprise_name}. Please select your enrollment"
|
||||
" information below.").format(username=username, course_name=course_name, partner_names=partner_names, enterprise_name=enterprise_name)}
|
||||
% else:
|
||||
${_("Congratulations! You are now enrolled in {course_name}").format(course_name=course_name)}
|
||||
% endif
|
||||
</h3>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user