Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
bfde6d63
Commit
bfde6d63
authored
Mar 17, 2017
by
asadiqbal
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ENT-251 Updated track selection UI for Enterprise context
parent
b10083cb
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
405 additions
and
3 deletions
+405
-3
common/djangoapps/course_modes/tests/test_views.py
+133
-2
common/djangoapps/course_modes/views.py
+16
-0
common/djangoapps/util/enterprise_helpers.py
+141
-0
common/djangoapps/util/tests/mixins/enterprise.py
+74
-0
common/test/db_fixtures/enterprise.json
+33
-0
lms/envs/common.py
+1
-0
lms/templates/course_modes/choose.html
+7
-1
No files found.
common/djangoapps/course_modes/tests/test_views.py
View file @
bfde6d63
...
...
@@ -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
)
common/djangoapps/course_modes/views.py
View file @
bfde6d63
...
...
@@ -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"
]
=
[
...
...
common/djangoapps/util/enterprise_helpers.py
View file @
bfde6d63
...
...
@@ -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'
]
common/djangoapps/util/tests/mixins/enterprise.py
View file @
bfde6d63
...
...
@@ -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
):
"""
...
...
common/test/db_fixtures/enterprise.json
0 → 100644
View file @
bfde6d63
[
{
"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"
}
}
]
lms/envs/common.py
View file @
bfde6d63
...
...
@@ -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
lms/templates/course_modes/choose.html
View file @
bfde6d63
...
...
@@ -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>
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment