Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
course-discovery
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
course-discovery
Commits
d7be3278
Commit
d7be3278
authored
Nov 16, 2017
by
McKenzie Welter
Committed by
McKenzie Welter
Nov 20, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
consider entitlements in program one click purchase eligibility
parent
d872147a
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
121 additions
and
3 deletions
+121
-3
course_discovery/apps/api/serializers.py
+2
-1
course_discovery/apps/course_metadata/data_loaders/api.py
+2
-1
course_discovery/apps/course_metadata/data_loaders/tests/test_api.py
+2
-0
course_discovery/apps/course_metadata/migrations/0069_courseentitlement_expires.py
+20
-0
course_discovery/apps/course_metadata/models.py
+6
-0
course_discovery/apps/course_metadata/tests/factories.py
+1
-0
course_discovery/apps/course_metadata/tests/test_admin.py
+1
-1
course_discovery/apps/course_metadata/tests/test_models.py
+87
-0
No files found.
course_discovery/apps/api/serializers.py
View file @
d7be3278
...
...
@@ -401,6 +401,7 @@ class CourseEntitlementSerializer(serializers.ModelSerializer):
currency
=
serializers
.
SlugRelatedField
(
read_only
=
True
,
slug_field
=
'code'
)
sku
=
serializers
.
CharField
()
mode
=
serializers
.
SlugRelatedField
(
slug_field
=
'name'
,
queryset
=
SeatType
.
objects
.
all
())
expires
=
serializers
.
DateTimeField
()
@classmethod
def
prefetch_queryset
(
cls
):
...
...
@@ -408,7 +409,7 @@ class CourseEntitlementSerializer(serializers.ModelSerializer):
class
Meta
(
object
):
model
=
CourseEntitlement
fields
=
(
'mode'
,
'price'
,
'currency'
,
'sku'
,)
fields
=
(
'mode'
,
'price'
,
'currency'
,
'sku'
,
'expires'
)
class
MinimalOrganizationSerializer
(
serializers
.
ModelSerializer
):
...
...
course_discovery/apps/course_metadata/data_loaders/api.py
View file @
d7be3278
...
...
@@ -394,7 +394,8 @@ class EcommerceApiDataLoader(AbstractDataLoader):
defaults
=
{
'price'
:
price
,
'currency'
:
currency
,
'sku'
:
sku
'sku'
:
sku
,
'expires'
:
self
.
parse_date
(
body
[
'expires'
])
}
course
.
entitlements
.
update_or_create
(
mode
=
mode
,
defaults
=
defaults
)
return
sku
...
...
course_discovery/apps/course_metadata/data_loaders/tests/test_api.py
View file @
d7be3278
...
...
@@ -438,6 +438,7 @@ class EcommerceApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestC
""" Assert a Course Entitlement was loaded into the database for each entry in the specified data body. """
self
.
assertEqual
(
CourseEntitlement
.
objects
.
count
(),
len
(
body
))
for
datum
in
body
:
expires
=
datum
[
'expires'
]
attributes
=
{
attribute
[
'name'
]:
attribute
[
'value'
]
for
attribute
in
datum
[
'attribute_values'
]}
course
=
Course
.
objects
.
get
(
uuid
=
attributes
[
'UUID'
])
stock_record
=
datum
[
'stockrecords'
][
0
]
...
...
@@ -450,6 +451,7 @@ class EcommerceApiDataLoaderTests(ApiClientTestMixin, DataLoaderTestMixin, TestC
entitlement
=
course
.
entitlements
.
get
(
mode
=
mode
)
self
.
assertEqual
(
entitlement
.
expires
,
expires
)
self
.
assertEqual
(
entitlement
.
course
,
course
)
self
.
assertEqual
(
entitlement
.
mode
,
mode
)
self
.
assertEqual
(
entitlement
.
price
,
price
)
...
...
course_discovery/apps/course_metadata/migrations/0069_courseentitlement_expires.py
0 → 100644
View file @
d7be3278
# -*- coding: utf-8 -*-
# Generated by Django 1.11.3 on 2017-11-16 17:08
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'course_metadata'
,
'0068_auto_20171108_1614'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'courseentitlement'
,
name
=
'expires'
,
field
=
models
.
DateTimeField
(
blank
=
True
,
null
=
True
),
),
]
course_discovery/apps/course_metadata/models.py
View file @
d7be3278
...
...
@@ -779,6 +779,7 @@ class CourseEntitlement(TimeStampedModel):
price
=
models
.
DecimalField
(
**
PRICE_FIELD_CONFIG
)
currency
=
models
.
ForeignKey
(
Currency
)
sku
=
models
.
CharField
(
max_length
=
128
,
null
=
True
,
blank
=
True
)
expires
=
models
.
DateTimeField
(
null
=
True
,
blank
=
True
)
class
Meta
(
object
):
unique_together
=
(
...
...
@@ -934,6 +935,11 @@ class Program(TimeStampedModel):
applicable_seat_types
=
[
seat_type
.
name
.
lower
()
for
seat_type
in
self
.
type
.
applicable_seat_types
.
all
()]
for
course
in
self
.
courses
.
all
():
entitlement_products
=
set
(
course
.
entitlements
.
filter
(
mode__name__in
=
applicable_seat_types
)
.
exclude
(
expires__lte
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)))
if
len
(
entitlement_products
)
==
1
:
continue
course_runs
=
set
(
course
.
course_runs
.
filter
(
status
=
CourseRunStatus
.
Published
))
-
excluded_course_runs
if
len
(
course_runs
)
!=
1
:
...
...
course_discovery/apps/course_metadata/tests/factories.py
View file @
d7be3278
...
...
@@ -355,6 +355,7 @@ class CourseEntitlementFactory(factory.DjangoModelFactory):
price
=
FuzzyDecimal
(
0.0
,
650.0
)
currency
=
factory
.
Iterator
(
Currency
.
objects
.
all
())
sku
=
FuzzyText
(
length
=
8
)
expires
=
FuzzyDateTime
(
datetime
.
datetime
(
2014
,
1
,
1
,
tzinfo
=
UTC
))
course
=
factory
.
SubFactory
(
CourseFactory
)
class
Meta
:
...
...
course_discovery/apps/course_metadata/tests/test_admin.py
View file @
d7be3278
...
...
@@ -386,7 +386,7 @@ class ProgramEligibilityFilterTests(SiteMixin, TestCase):
courses
=
[
course_run
.
course
],
one_click_purchase_enabled
=
True
,
)
with
self
.
assertNumQueries
(
1
1
):
with
self
.
assertNumQueries
(
1
2
):
self
.
assertEqual
(
list
(
program_filter
.
queryset
({},
Program
.
objects
.
all
())),
[
one_click_purchase_eligible_program
]
...
...
course_discovery/apps/course_metadata/tests/test_models.py
View file @
d7be3278
...
...
@@ -510,6 +510,30 @@ class ProgramTests(TestCase):
return
factories
.
ProgramFactory
(
type
=
program_type
,
courses
=
[
course_run
.
course
])
def
create_program_with_entitlements_and_seats
(
self
):
verified_seat_type
,
__
=
SeatType
.
objects
.
get_or_create
(
name
=
Seat
.
VERIFIED
)
program_type
=
factories
.
ProgramTypeFactory
(
applicable_seat_types
=
[
verified_seat_type
])
courses
=
[]
for
__
in
range
(
3
):
entitlement
=
factories
.
CourseEntitlementFactory
(
mode
=
verified_seat_type
,
expires
=
None
)
for
__
in
range
(
3
):
factories
.
SeatFactory
(
course_run
=
factories
.
CourseRunFactory
(
end
=
None
,
enrollment_end
=
None
,
course
=
entitlement
.
course
),
type
=
Seat
.
VERIFIED
,
upgrade_deadline
=
None
)
courses
.
append
(
entitlement
.
course
)
program
=
factories
.
ProgramFactory
(
courses
=
courses
,
one_click_purchase_enabled
=
True
,
type
=
program_type
,
)
return
program
,
courses
def
assert_one_click_purchase_ineligible_program
(
self
,
end
=
None
,
enrollment_start
=
None
,
enrollment_end
=
None
,
seat_type
=
Seat
.
VERIFIED
,
upgrade_deadline
=
None
,
one_click_purchase_enabled
=
True
,
excluded_course_runs
=
None
,
program_type
=
None
...
...
@@ -570,6 +594,69 @@ class ProgramTests(TestCase):
)
self
.
assertTrue
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_eligible_with_entitlements
(
self
):
""" Verify that program is one click purchase eligible when its courses have unexpired entitlement products. """
# Program has one_click_purchase_enabled set to True,
# all courses have a verified mode entitlement product and multiple course runs.
program
,
__
=
self
.
create_program_with_entitlements_and_seats
()
self
.
assertTrue
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_ineligible_expired_entitlement
(
self
):
""" Verify that program is not one click purchase eligible if course entitlement product is expired. """
program
,
courses
=
self
.
create_program_with_entitlements_and_seats
()
expired_entitlement
=
courses
[
2
]
.
entitlements
.
first
()
expired_entitlement
.
expires
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
-
datetime
.
timedelta
(
days
=
7
)
expired_entitlement
.
save
()
self
.
assertFalse
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_eligible_expired_entitlement_one_run
(
self
):
"""
Verify that program is one click purchase eligible if there is only one
published course run for the course whose entitlement product is expired.
"""
program
,
courses
=
self
.
create_program_with_entitlements_and_seats
()
expired_entitlement
=
courses
[
2
]
.
entitlements
.
first
()
expired_entitlement
.
expires
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
-
datetime
.
timedelta
(
days
=
7
)
expired_entitlement
.
save
()
CourseRun
.
objects
.
filter
(
course
=
courses
[
2
])
.
delete
()
factories
.
SeatFactory
(
course_run
=
factories
.
CourseRunFactory
(
end
=
None
,
enrollment_end
=
None
,
course
=
courses
[
2
]
),
type
=
Seat
.
VERIFIED
,
upgrade_deadline
=
None
)
self
.
assertTrue
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_eligible_future_expires
(
self
):
""" Verify that program is one click purchase eligible if course entitlement product expires in the future. """
program
,
courses
=
self
.
create_program_with_entitlements_and_seats
()
future_expiring_entitlement
=
courses
[
1
]
.
entitlements
.
first
()
future_expiring_entitlement
.
expires
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
7
)
future_expiring_entitlement
.
save
()
self
.
assertTrue
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_ineligible_wrong_mode
(
self
):
""" Verify that program is not one click purchase eligible if course entitlement product has the wrong mode. """
program
,
courses
=
self
.
create_program_with_entitlements_and_seats
()
honor_seat_type
,
__
=
SeatType
.
objects
.
get_or_create
(
name
=
Seat
.
HONOR
)
honor_mode_entitlement
=
courses
[
0
]
.
entitlements
.
first
()
honor_mode_entitlement
.
mode
=
honor_seat_type
honor_mode_entitlement
.
save
()
self
.
assertFalse
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_ineligible_multiple_entitlements
(
self
):
"""
Verify that program is not one click purchase eligible if course has
multiple entitlement products with correct modes.
"""
program
,
courses
=
self
.
create_program_with_entitlements_and_seats
()
credit_seat_type
,
__
=
SeatType
.
objects
.
get_or_create
(
name
=
Seat
.
CREDIT
)
program
.
type
.
applicable_seat_types
.
add
(
credit_seat_type
)
factories
.
CourseEntitlementFactory
(
mode
=
credit_seat_type
,
expires
=
None
,
course
=
courses
[
0
])
self
.
assertFalse
(
program
.
is_program_eligible_for_one_click_purchase
)
def
test_one_click_purchase_eligible_with_unpublished_runs
(
self
):
""" Verify that program with unpublished course runs is one click purchase eligible. """
...
...
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