Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
E
edx-platform-release
Manage
Activity
Members
Labels
Plan
Issues
0
Issue boards
Milestones
Wiki
Code
Merge requests
1
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Releases
Package Registry
Model registry
Operate
Terraform modules
Monitor
Incidents
Service Desk
Analyze
Value stream analytics
Contributor analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Hsin-Yu Chien
edx-platform-release
Commits
b2432ac5
Commit
b2432ac5
authored
6 years ago
by
Christie Rice
Browse files
Options
Downloads
Patches
Plain Diff
REVEM-207 Add program price and purchase url to user metadata
parent
0c06bd3c
Loading
Loading
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
lms/djangoapps/experiments/tests/test_utils.py
+120
-1
120 additions, 1 deletion
lms/djangoapps/experiments/tests/test_utils.py
lms/djangoapps/experiments/utils.py
+130
-6
130 additions, 6 deletions
lms/djangoapps/experiments/utils.py
with
250 additions
and
7 deletions
lms/djangoapps/experiments/tests/test_utils.py
+
120
−
1
View file @
b2432ac5
"""
Tests of experiment functionality
"""
from
decimal
import
Decimal
from
unittest
import
TestCase
from
lms.djangoapps.experiments.utils
import
is_enrolled_in_course_run
from
lms.djangoapps.experiments.utils
import
get_course_entitlement_price_and_sku
,
get_program_price_and_skus
,
\
get_program_purchase_url
,
get_unenrolled_courses
,
is_enrolled_in_course_run
from
opaque_keys.edx.keys
import
CourseKey
...
...
@@ -10,6 +12,22 @@ class ExperimentUtilsTests(TestCase):
"""
Tests of experiment functionality
"""
def
setUp
(
self
):
super
(
ExperimentUtilsTests
,
self
).
setUp
()
# Create a course run
self
.
run_a_price
=
'
86.00
'
self
.
run_a_sku
=
'
B9B6D0B
'
seat_a
=
{
'
type
'
:
'
verified
'
,
'
price
'
:
self
.
run_a_price
,
'
sku
'
:
self
.
run_a_sku
}
seats
=
[
seat_a
]
self
.
course_run_a
=
{
'
status
'
:
'
published
'
,
'
seats
'
:
seats
}
# Create an entitlement
self
.
entitlement_a_price
=
'
199.23
'
self
.
entitlement_a_sku
=
'
B37EBA0
'
self
.
entitlement_a
=
{
'
mode
'
:
'
verified
'
,
'
price
'
:
self
.
entitlement_a_price
,
'
sku
'
:
self
.
entitlement_a_sku
}
def
test_valid_course_run_key_enrollment
(
self
):
course_run
=
{
'
key
'
:
'
course-v1:DelftX+NGIx+RA0
'
,
...
...
@@ -23,3 +41,104 @@ class ExperimentUtilsTests(TestCase):
}
enrollment_ids
=
{
CourseKey
.
from_string
(
'
course-v1:DelftX+NGIx+RA0
'
)}
self
.
assertFalse
(
is_enrolled_in_course_run
(
course_run
,
enrollment_ids
))
def
test_program_url_with_no_skus
(
self
):
url
=
get_program_purchase_url
(
None
)
self
.
assertEqual
(
None
,
url
)
def
test_program_url_with_no_skus_and_no_bundle
(
self
):
url
=
get_program_purchase_url
(
None
,
None
)
self
.
assertEqual
(
None
,
url
)
def
test_program_url_with_single_sku
(
self
):
skus
=
[
'
9FE0DE2
'
]
expected_url
=
'
https://ecommerce.edx.org/basket/add/?sku=9FE0DE2
'
url
=
get_program_purchase_url
(
skus
)
self
.
assertEqual
(
expected_url
,
url
)
def
test_program_url_with_single_sku_and_bundle
(
self
):
skus
=
[
'
9FE0DE2
'
]
program_id
=
'
bef7201a-6f97-40ad-ad17-d5ea8be1eec8
'
expected_url
=
'
https://ecommerce.edx.org/basket/add/?sku=9FE0DE2&bundle=
'
+
program_id
url
=
get_program_purchase_url
(
skus
,
program_id
)
self
.
assertEqual
(
expected_url
,
url
)
def
test_program_url_with_multiple_skus
(
self
):
skus
=
[
'
9FE0DE2
'
,
'
B37EBA0
'
,
'
FDCED11
'
]
expected_url
=
'
https://ecommerce.edx.org/basket/add/?sku=9FE0DE2&sku=B37EBA0&sku=FDCED11
'
url
=
get_program_purchase_url
(
skus
)
self
.
assertEqual
(
expected_url
,
url
)
def
test_program_url_with_multiple_skus_and_bundle
(
self
):
skus
=
[
'
9FE0DE2
'
,
'
B37EBA0
'
,
'
FDCED11
'
]
program_id
=
'
bef7201a-6f97-40ad-ad17-d5ea8be1eec8
'
expected_url
=
'
https://ecommerce.edx.org/basket/add/?sku=9FE0DE2&sku=B37EBA0&sku=FDCED11&bundle=
'
+
program_id
url
=
get_program_purchase_url
(
skus
,
program_id
)
self
.
assertEqual
(
expected_url
,
url
)
def
test_program_price_and_skus_for_empty_courses
(
self
):
price
,
skus
=
get_program_price_and_skus
([])
self
.
assertEqual
(
None
,
price
)
self
.
assertEqual
(
None
,
skus
)
def
test_unenrolled_courses_for_empty_courses
(
self
):
unenrolled_courses
=
get_unenrolled_courses
([],
[])
self
.
assertEqual
([],
unenrolled_courses
)
def
test_unenrolled_courses_for_single_course
(
self
):
course
=
{
'
key
'
:
'
UQx+ENGY1x
'
}
courses_in_program
=
[
course
]
user_enrollments
=
[]
unenrolled_courses
=
get_unenrolled_courses
(
courses_in_program
,
user_enrollments
)
expected_unenrolled_courses
=
[
course
]
self
.
assertEqual
(
expected_unenrolled_courses
,
unenrolled_courses
)
def
test_price_and_sku_from_empty_course
(
self
):
course
=
{}
price
,
sku
=
get_course_entitlement_price_and_sku
(
course
)
self
.
assertEqual
(
None
,
price
)
self
.
assertEqual
(
None
,
sku
)
def
test_price_and_sku_from_entitlement
(
self
):
entitlements
=
[
self
.
entitlement_a
]
course
=
{
'
key
'
:
'
UQx+ENGY1x
'
,
'
entitlements
'
:
entitlements
}
price
,
sku
=
get_course_entitlement_price_and_sku
(
course
)
self
.
assertEqual
(
self
.
entitlement_a_price
,
price
)
self
.
assertEqual
(
self
.
entitlement_a_sku
,
sku
)
def
test_price_and_sku_from_course_run
(
self
):
course_runs
=
[
self
.
course_run_a
]
course
=
{
'
key
'
:
'
UQx+ENGY1x
'
,
'
course_runs
'
:
course_runs
}
price
,
sku
=
get_course_entitlement_price_and_sku
(
course
)
expected_price
=
Decimal
(
self
.
run_a_price
)
self
.
assertEqual
(
expected_price
,
price
)
self
.
assertEqual
(
self
.
run_a_sku
,
sku
)
def
test_price_and_sku_from_course
(
self
):
entitlements
=
[
self
.
entitlement_a
]
course_a
=
{
'
key
'
:
'
UQx+ENGYCAPx
'
,
'
entitlements
'
:
entitlements
}
courses
=
[
course_a
]
price
,
skus
=
get_program_price_and_skus
(
courses
)
expected_price
=
u
'
$199.23
'
self
.
assertEqual
(
expected_price
,
price
)
self
.
assertEqual
(
1
,
len
(
skus
))
self
.
assertIn
(
self
.
entitlement_a_sku
,
skus
)
def
test_price_and_sku_from_multiple_courses
(
self
):
entitlements
=
[
self
.
entitlement_a
]
course_runs
=
[
self
.
course_run_a
]
course_a
=
{
'
key
'
:
'
UQx+ENGY1x
'
,
'
course_runs
'
:
course_runs
}
course_b
=
{
'
key
'
:
'
UQx+ENGYCAPx
'
,
'
entitlements
'
:
entitlements
}
courses
=
[
course_a
,
course_b
]
price
,
skus
=
get_program_price_and_skus
(
courses
)
expected_price
=
u
'
$285.23
'
self
.
assertEqual
(
expected_price
,
price
)
self
.
assertEqual
(
2
,
len
(
skus
))
self
.
assertIn
(
self
.
run_a_sku
,
skus
)
self
.
assertIn
(
self
.
entitlement_a_sku
,
skus
)
This diff is collapsed.
Click to expand it.
lms/djangoapps/experiments/utils.py
+
130
−
6
View file @
b2432ac5
...
...
@@ -5,9 +5,11 @@ Utilities to facilitate experimentation
import
hashlib
import
re
import
logging
from
decimal
import
Decimal
from
student.models
import
CourseEnrollment
from
django_comment_common.models
import
Role
from
course_modes.models
import
get_cosmetic_verified_display_price
from
django.utils.timezone
import
now
from
course_modes.models
import
get_cosmetic_verified_display_price
,
format_course_price
from
courseware.access
import
has_staff_access_to_preview_mode
from
courseware.date_summary
import
verified_upgrade_deadline_link
,
verified_upgrade_link_is_valid
from
xmodule.partitions.partitions_service
import
get_user_partition_groups
,
get_all_partitions_for_course
...
...
@@ -37,6 +39,24 @@ PROGRAM_INFO_FLAG = WaffleFlag(
flag_name
=
u
'
add_programs
'
,
flag_undefined_default
=
False
)
# .. feature_toggle_name: experiments.add_program_price
# .. feature_toggle_type: flag
# .. feature_toggle_default: False
# .. feature_toggle_description: Toggle for adding the current course's program price and sku information to user
# metadata
# .. feature_toggle_category: experiments
# .. feature_toggle_use_cases: monitored_rollout
# .. feature_toggle_creation_date: 2019-3-12
# .. feature_toggle_expiration_date: None
# .. feature_toggle_warnings: None
# .. feature_toggle_tickets: REVEM-118, REVEM-206
# .. feature_toggle_status: supported
PROGRAM_PRICE_FLAG
=
WaffleFlag
(
waffle_namespace
=
WaffleFlagNamespace
(
name
=
u
'
experiments
'
),
flag_name
=
u
'
add_program_price
'
,
flag_undefined_default
=
False
)
# TODO: clean up as part of REVEM-199 (END)
...
...
@@ -78,14 +98,96 @@ def check_and_get_upgrade_link_and_date(user, enrollment=None, course=None):
# TODO: clean up as part of REVEM-199 (START)
def
is_enrolled_in_all_courses_in_program
(
courses_in_program
,
user_enrollments
):
def
get_program_purchase_url
(
skus
,
bundle_id
=
None
):
"""
Return a url that will allow the purchase of the courses with these skus
"""
if
not
skus
:
return
None
url
=
'
https://ecommerce.edx.org/basket/add/?
'
for
sku
in
skus
:
url
+=
'
sku=
'
+
sku
+
'
&
'
if
bundle_id
:
url
+=
'
bundle=
'
+
bundle_id
else
:
# Remove trailing & from the skus
url
=
url
[:
-
1
]
return
url
def
get_program_price_and_skus
(
courses
):
"""
Get the total program price and purchase skus from these courses in the program
"""
program_price
=
0
skus
=
[]
for
course
in
courses
:
course_price
,
course_sku
=
get_course_entitlement_price_and_sku
(
course
)
if
course_price
is
not
None
and
course_sku
is
not
None
:
program_price
=
Decimal
(
program_price
)
+
Decimal
(
course_price
)
skus
.
append
(
course_sku
)
if
program_price
<=
0
:
program_price
=
None
skus
=
None
else
:
program_price
=
format_course_price
(
program_price
)
program_price
=
unicode
(
program_price
)
return
program_price
,
skus
def
get_course_entitlement_price_and_sku
(
course
):
"""
Get the entitlement price and sku from this course.
Try to get them from the first non-expired, verified entitlement that has a price and a sku. If that doesn
'
t work,
fall back to the first non-expired, verified course run that has a price and a sku.
"""
for
entitlement
in
course
.
get
(
'
entitlements
'
,
[]):
if
entitlement
.
get
(
'
mode
'
)
==
'
verified
'
and
entitlement
[
'
price
'
]
and
entitlement
[
'
sku
'
]:
expires
=
entitlement
.
get
(
'
expires
'
)
if
not
expires
or
expires
>
now
():
return
entitlement
[
'
price
'
],
entitlement
[
'
sku
'
]
course_runs
=
course
.
get
(
'
course_runs
'
,
[])
published_course_runs
=
[
run
for
run
in
course_runs
if
run
[
'
status
'
]
==
'
published
'
]
for
published_course_run
in
published_course_runs
:
for
seat
in
published_course_run
[
'
seats
'
]:
if
seat
.
get
(
'
type
'
)
==
'
verified
'
and
seat
[
'
price
'
]
and
seat
[
'
sku
'
]:
price
=
Decimal
(
seat
.
get
(
'
price
'
))
return
price
,
seat
.
get
(
'
sku
'
)
return
None
,
None
def
get_unenrolled_courses
(
courses
,
user_enrollments
):
"""
Given a list of courses and a list of user enrollments, return the courses in which the user is not enrolled.
Depending on the enrollments that are passed in, this method can be used to determine the courses in a program in
which the user has not yet enrolled or the courses in a program for which the user has not yet purchased a
certificate.
"""
# Get the enrollment course ids here, so we don't need to loop through them for every course run
enrollment_course_ids
=
{
enrollment
.
course_id
for
enrollment
in
user_enrollments
}
unenrolled_courses
=
[]
for
course
in
courses
:
if
not
is_enrolled_in_course
(
course
,
enrollment_course_ids
):
unenrolled_courses
.
append
(
course
)
return
unenrolled_courses
def
is_enrolled_in_all_courses
(
courses
,
user_enrollments
):
"""
Determine if the user is enrolled in all courses
in this program
Determine if the user is enrolled in all
of the
courses
"""
# Get the enrollment course ids here, so we don't need to loop through them for every course run
enrollment_course_ids
=
{
enrollment
.
course_id
for
enrollment
in
user_enrollments
}
for
course
in
courses
_in_program
:
for
course
in
courses
:
if
not
is_enrolled_in_course
(
course
,
enrollment_course_ids
):
# User is not enrolled in this course, meaning they are not enrolled in all courses in the program
return
False
...
...
@@ -148,18 +250,40 @@ def get_experiment_user_metadata_context(course, user):
# A course can be in multiple programs, but we're just grabbing the first one
program
=
programs
[
0
]
complete_enrollment
=
False
has_courses_left_to_purchase
=
False
total_courses
=
None
courses
=
program
.
get
(
'
courses
'
)
courses_left_to_purchase_price
=
None
courses_left_to_purchase_url
=
None
program_uuid
=
program
.
get
(
'
uuid
'
)
if
courses
is
not
None
:
total_courses
=
len
(
courses
)
complete_enrollment
=
is_enrolled_in_all_courses_in_program
(
courses
,
user_enrollments
)
complete_enrollment
=
is_enrolled_in_all_courses
(
courses
,
user_enrollments
)
if
PROGRAM_PRICE_FLAG
.
is_enabled
():
# Get the price and purchase URL of the program courses the user has yet to purchase. Say a
# program has 3 courses (A, B and C), and the user previously purchased a certificate for A.
# The user is enrolled in audit mode for B. The "left to purchase price" should be the price of
# B+C.
non_audit_enrollments
=
[
enrollment
for
enrollment
in
user_enrollments
if
enrollment
not
in
audit_enrollments
]
courses_left_to_purchase
=
get_unenrolled_courses
(
courses
,
non_audit_enrollments
)
if
courses_left_to_purchase
:
has_courses_left_to_purchase
=
True
courses_left_to_purchase_price
,
courses_left_to_purchase_skus
=
get_program_price_and_skus
(
courses_left_to_purchase
)
courses_left_to_purchase_url
=
get_program_purchase_url
(
courses_left_to_purchase_skus
,
program_uuid
)
program_key
=
{
'
uuid
'
:
program
.
get
(
'
uuid
'
)
,
'
uuid
'
:
program
_
uuid
,
'
title
'
:
program
.
get
(
'
title
'
),
'
marketing_url
'
:
program
.
get
(
'
marketing_url
'
),
'
total_courses
'
:
total_courses
,
'
complete_enrollment
'
:
complete_enrollment
,
'
has_courses_left_to_purchase
'
:
has_courses_left_to_purchase
,
'
courses_left_to_purchase_price
'
:
courses_left_to_purchase_price
,
'
courses_left_to_purchase_url
'
:
courses_left_to_purchase_url
,
}
# TODO: clean up as part of REVEM-199 (END)
enrollment
=
CourseEnrollment
.
objects
.
select_related
(
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment