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
f72cf800
Commit
f72cf800
authored
Nov 17, 2017
by
Anthony Mangano
Committed by
McKenzie Welter
Nov 30, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Consider user entitlements and use entitlement products in bundle one-click purchase
parent
c25e4ba0
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
99 additions
and
40 deletions
+99
-40
common/djangoapps/course_modes/models.py
+4
-4
common/djangoapps/entitlements/models.py
+7
-0
openedx/core/djangoapps/catalog/tests/factories.py
+12
-2
openedx/core/djangoapps/programs/tests/test_utils.py
+0
-0
openedx/core/djangoapps/programs/utils.py
+76
-34
No files found.
common/djangoapps/course_modes/models.py
View file @
f72cf800
...
...
@@ -136,10 +136,10 @@ class CourseMode(models.Model):
HONOR
=
'honor'
PROFESSIONAL
=
'professional'
VERIFIED
=
"verified"
AUDIT
=
"audit"
NO_ID_PROFESSIONAL_MODE
=
"no-id-professional"
CREDIT_MODE
=
"credit"
VERIFIED
=
'verified'
AUDIT
=
'audit'
NO_ID_PROFESSIONAL_MODE
=
'no-id-professional'
CREDIT_MODE
=
'credit'
DEFAULT_MODE
=
Mode
(
settings
.
COURSE_MODE_DEFAULTS
[
'slug'
],
...
...
common/djangoapps/entitlements/models.py
View file @
f72cf800
...
...
@@ -24,3 +24,10 @@ class CourseEntitlement(TimeStampedModel):
help_text
=
'The current Course enrollment for this entitlement. If NULL the Learner has not enrolled.'
)
order_number
=
models
.
CharField
(
max_length
=
128
,
null
=
True
)
@property
def
expired_at_datetime
(
self
):
"""
Getter to be used instead of expired_at because of the conditional check and update
"""
return
self
.
expired_at
openedx/core/djangoapps/catalog/tests/factories.py
View file @
f72cf800
...
...
@@ -8,6 +8,7 @@ from faker import Faker
fake
=
Faker
()
VERIFIED_MODE
=
'verified'
def
generate_instances
(
factory_class
,
count
=
3
):
...
...
@@ -103,10 +104,18 @@ class SeatFactory(DictFactoryBase):
currency
=
'USD'
price
=
factory
.
Faker
(
'random_int'
)
sku
=
factory
.
LazyFunction
(
generate_seat_sku
)
type
=
'verified'
type
=
VERIFIED_MODE
upgrade_deadline
=
factory
.
LazyFunction
(
generate_zulu_datetime
)
class
EntitlementFactory
(
DictFactoryBase
):
currency
=
'USD'
price
=
factory
.
Faker
(
'random_int'
)
sku
=
factory
.
LazyFunction
(
generate_seat_sku
)
mode
=
VERIFIED_MODE
expires
=
None
class
CourseRunFactory
(
DictFactoryBase
):
eligible_for_financial_aid
=
True
end
=
factory
.
LazyFunction
(
generate_zulu_datetime
)
...
...
@@ -121,7 +130,7 @@ class CourseRunFactory(DictFactoryBase):
start
=
factory
.
LazyFunction
(
generate_zulu_datetime
)
status
=
'published'
title
=
factory
.
Faker
(
'catch_phrase'
)
type
=
'verified'
type
=
VERIFIED_MODE
uuid
=
factory
.
Faker
(
'uuid4'
)
content_language
=
'en'
max_effort
=
4
...
...
@@ -130,6 +139,7 @@ class CourseRunFactory(DictFactoryBase):
class
CourseFactory
(
DictFactoryBase
):
course_runs
=
factory
.
LazyFunction
(
partial
(
generate_instances
,
CourseRunFactory
))
entitlements
=
factory
.
LazyFunction
(
partial
(
generate_instances
,
EntitlementFactory
))
image
=
ImageFactory
()
key
=
factory
.
LazyFunction
(
generate_course_key
)
owners
=
factory
.
LazyFunction
(
partial
(
generate_instances
,
OrganizationFactory
,
count
=
1
))
...
...
openedx/core/djangoapps/programs/tests/test_utils.py
View file @
f72cf800
This diff is collapsed.
Click to expand it.
openedx/core/djangoapps/programs/utils.py
View file @
f72cf800
...
...
@@ -460,57 +460,99 @@ class ProgramDataExtender(object):
def
_attach_course_run_may_certify
(
self
,
run_mode
):
run_mode
[
'may_certify'
]
=
self
.
course_overview
.
may_certify
()
def
_check_enrollment_for_user
(
self
,
course_run
):
applicable_seat_types
=
self
.
data
[
'applicable_seat_types'
]
def
_filter_out_courses_with_entitlements
(
self
,
courses
):
"""
Removes courses for which the current user already holds an applicable entitlement.
TODO:
Add a NULL value of enrollment_course_run to filter, as courses with entitlements spent on applicable
enrollments will already have been filtered out by _filter_out_courses_with_enrollments.
(
enrollment_mode
,
active
)
=
CourseEnrollment
.
enrollment_mode_for_user
(
self
.
user
,
CourseKey
.
from_string
(
course_run
[
'key'
])
Arguments:
courses (list): Containing dicts representing courses in a program
Returns:
A subset of the given list of course dicts
"""
course_uuids
=
set
(
course
[
'uuid'
]
for
course
in
courses
)
# Filter the entitlements' modes with a case-insensitive match against applicable seat_types
entitlements
=
self
.
user
.
courseentitlement_set
.
filter
(
mode__in
=
self
.
data
[
'applicable_seat_types'
],
course_uuid__in
=
course_uuids
,
)
# Here we check the entitlements' expired_at_datetime property rather than filter by the expired_at attribute
# to ensure that the expiration status is as up to date as possible
entitlements
=
[
e
for
e
in
entitlements
if
not
e
.
expired_at_datetime
]
courses_with_entitlements
=
set
(
unicode
(
entitlement
.
course_uuid
)
for
entitlement
in
entitlements
)
return
[
course
for
course
in
courses
if
course
[
'uuid'
]
not
in
courses_with_entitlements
]
is_paid_seat
=
False
if
enrollment_mode
is
not
None
and
active
is
not
None
and
active
is
True
:
# Check all the applicable seat types
# this will also check for no-id-professional as professional
is_paid_seat
=
any
(
seat_type
in
enrollment_mode
for
seat_type
in
applicable_seat_types
)
def
_filter_out_courses_with_enrollments
(
self
,
courses
):
"""
Removes courses for which the current user already holds an active and applicable enrollment
for one of that course's runs.
return
is_paid_seat
Arguments:
courses (list): Containing dicts representing courses in a program
Returns:
A subset of the given list of course dicts
"""
enrollments
=
self
.
user
.
courseenrollment_set
.
filter
(
is_active
=
True
,
mode__in
=
self
.
data
[
'applicable_seat_types'
]
)
course_runs_with_enrollments
=
set
(
unicode
(
enrollment
.
course_id
)
for
enrollment
in
enrollments
)
courses_without_enrollments
=
[]
for
course
in
courses
:
if
all
(
unicode
(
run
[
'key'
])
not
in
course_runs_with_enrollments
for
run
in
course
[
'course_runs'
]):
courses_without_enrollments
.
append
(
course
)
return
courses_without_enrollments
def
_collect_one_click_purchase_eligibility_data
(
self
):
"""
Extend the program data with data about learner's eligibility for one click purchase,
discount data of the program and SKUs of seats that should be added to basket.
"""
applicable_seat_types
=
self
.
data
[
'applicable_seat_types'
]
if
'professional'
in
self
.
data
[
'applicable_seat_types'
]:
self
.
data
[
'applicable_seat_types'
]
.
append
(
'no-id-professional'
)
applicable_seat_types
=
set
(
seat
for
seat
in
self
.
data
[
'applicable_seat_types'
]
if
seat
!=
'credit'
)
is_learner_eligible_for_one_click_purchase
=
self
.
data
[
'is_program_eligible_for_one_click_purchase'
]
skus
=
[]
bundle_variant
=
'full'
if
is_learner_eligible_for_one_click_purchase
:
for
course
in
self
.
data
[
'courses'
]:
add_course_sku
=
True
course_runs
=
course
.
get
(
'course_runs'
,
[])
published_course_runs
=
filter
(
lambda
run
:
run
[
'status'
]
==
'published'
,
course_runs
)
if
len
(
published_course_runs
)
==
1
:
for
course_run
in
course_runs
:
is_paid_seat
=
self
.
_check_enrollment_for_user
(
course_run
)
if
is_paid_seat
:
add_course_sku
=
False
break
if
add_course_sku
:
if
is_learner_eligible_for_one_click_purchase
:
courses
=
self
.
data
[
'courses'
]
if
not
self
.
user
.
is_anonymous
():
courses
=
self
.
_filter_out_courses_with_enrollments
(
courses
)
courses
=
self
.
_filter_out_courses_with_entitlements
(
courses
)
if
len
(
courses
)
<
len
(
self
.
data
[
'courses'
]):
bundle_variant
=
'partial'
for
course
in
courses
:
entitlement_product
=
False
for
entitlement
in
course
.
get
(
'entitlements'
,
[]):
# We add the first entitlement product found with an applicable seat type because, at this time,
# we are assuming that, for any given course, there is at most one paid entitlement available.
if
entitlement
[
'mode'
]
in
applicable_seat_types
:
skus
.
append
(
entitlement
[
'sku'
])
entitlement_product
=
True
break
if
not
entitlement_product
:
course_runs
=
course
.
get
(
'course_runs'
,
[])
published_course_runs
=
[
run
for
run
in
course_runs
if
run
[
'status'
]
==
'published'
]
if
len
(
published_course_runs
)
==
1
:
for
seat
in
published_course_runs
[
0
][
'seats'
]:
if
seat
[
'type'
]
in
applicable_seat_types
and
seat
[
'sku'
]:
skus
.
append
(
seat
[
'sku'
])
break
else
:
bundle_variant
=
'partial'
else
:
# If a course in the program has more than 1 published course run
# learner won't be eligible for a one click purchase.
is_learner_eligible_for_one_click_purchase
=
False
skus
=
[]
break
# If a course in the program has more than 1 published course run
# learner won't be eligible for a one click purchase.
skus
=
[]
break
if
skus
:
try
:
...
...
@@ -604,7 +646,7 @@ class ProgramMarketingDataExtender(ProgramDataExtender):
def
__init__
(
self
,
program_data
,
user
):
super
(
ProgramMarketingDataExtender
,
self
)
.
__init__
(
program_data
,
user
)
# Aggregate list of instructors for the program
# Aggregate list of instructors for the program
keyed by name
self
.
instructors
=
[]
# Values for programs' price calculation.
...
...
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