Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
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
ecommerce
Commits
e208386e
Commit
e208386e
authored
Feb 02, 2017
by
zubair-arbi
Committed by
Zubair Afzal
Feb 17, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Update offer's Range model to use course catalog query for applying enterprise coupon
ENT-169
parent
ca89f929
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
306 additions
and
85 deletions
+306
-85
ecommerce/coupons/tests/mixins.py
+45
-2
ecommerce/courses/tests/mixins.py
+14
-7
ecommerce/courses/tests/test_utils.py
+4
-2
ecommerce/enterprise/entitlements.py
+101
-64
ecommerce/enterprise/tests/mixins.py
+44
-1
ecommerce/enterprise/tests/test_entitlements.py
+0
-0
ecommerce/extensions/api/serializers.py
+5
-0
ecommerce/extensions/api/v2/tests/views/test_catalog.py
+1
-1
ecommerce/extensions/offer/models.py
+32
-6
ecommerce/extensions/offer/tests/test_models.py
+60
-2
No files found.
ecommerce/coupons/tests/mixins.py
View file @
e208386e
...
@@ -89,8 +89,8 @@ class CourseCatalogMockMixin(object):
...
@@ -89,8 +89,8 @@ class CourseCatalogMockMixin(object):
course_run_url_with_query_and_partner_code
=
'{}course_runs/?q={}&partner={}'
.
format
(
course_run_url_with_query_and_partner_code
=
'{}course_runs/?q={}&partner={}'
.
format
(
settings
.
COURSE_CATALOG_API_URL
,
settings
.
COURSE_CATALOG_API_URL
,
partner_code
if
partner_code
else
'edx
'
,
query
if
query
else
'id:course*
'
,
query
if
query
else
'id:course*
'
partner_code
if
partner_code
else
'edx
'
)
)
httpretty
.
register_uri
(
httpretty
.
register_uri
(
httpretty
.
GET
,
httpretty
.
GET
,
...
@@ -129,6 +129,49 @@ class CourseCatalogMockMixin(object):
...
@@ -129,6 +129,49 @@ class CourseCatalogMockMixin(object):
content_type
=
'application/json'
content_type
=
'application/json'
)
)
def
mock_get_catalog_contains_api_for_failure
(
self
,
partner_code
,
course_run_ids
,
query
,
error
):
"""
Helper function to register a course catalog API endpoint with failure
for getting course runs information.
"""
def
callback
(
request
,
uri
,
headers
):
# pylint: disable=unused-argument
raise
error
catalog_contains_course_run_url
=
'{}course_runs/contains/?course_run_ids={}&query={}&partner={}'
.
format
(
settings
.
COURSE_CATALOG_API_URL
,
(
course_run_id
for
course_run_id
in
course_run_ids
),
query
,
partner_code
,
)
httpretty
.
register_uri
(
method
=
httpretty
.
GET
,
uri
=
catalog_contains_course_run_url
,
responses
=
[
httpretty
.
Response
(
body
=
callback
,
content_type
=
'application/json'
,
status_code
=
500
)
]
)
def
mock_get_catalog_course_runs_for_failure
(
self
,
partner_code
,
query
,
error
):
"""
Helper function to register a course catalog API endpoint with failure
for getting course runs information.
"""
def
callback
(
request
,
uri
,
headers
):
# pylint: disable=unused-argument
raise
error
course_run_url_with_query_and_partner_code
=
'{}course_runs/?q={}&partner={}'
.
format
(
settings
.
COURSE_CATALOG_API_URL
,
query
,
partner_code
,
)
httpretty
.
register_uri
(
method
=
httpretty
.
GET
,
uri
=
course_run_url_with_query_and_partner_code
,
responses
=
[
httpretty
.
Response
(
body
=
callback
,
content_type
=
'application/json'
,
status_code
=
500
)
]
)
class
CouponMixin
(
object
):
class
CouponMixin
(
object
):
""" Mixin for preparing data for coupons and creating coupons. """
""" Mixin for preparing data for coupons and creating coupons. """
...
...
ecommerce/courses/tests/mixins.py
View file @
e208386e
...
@@ -17,12 +17,11 @@ class CourseCatalogServiceMockMixin(object):
...
@@ -17,12 +17,11 @@ class CourseCatalogServiceMockMixin(object):
super
(
CourseCatalogServiceMockMixin
,
self
)
.
setUp
()
super
(
CourseCatalogServiceMockMixin
,
self
)
.
setUp
()
cache
.
clear
()
cache
.
clear
()
def
mock_course_discovery_api_for_catalog_by_resource_id
(
self
,
catalog_query
=
'title: *'
):
def
mock_course_discovery_api_for_catalog_by_resource_id
(
self
,
catalog_
id
=
1
,
catalog_
query
=
'title: *'
):
"""
"""
Helper function to register course catalog API endpoint for a
Helper function to register course catalog API endpoint for a
single catalog with its resource id.
single catalog with its resource id.
"""
"""
catalog_id
=
1
course_discovery_api_response
=
{
course_discovery_api_response
=
{
'id'
:
catalog_id
,
'id'
:
catalog_id
,
'name'
:
'Catalog {}'
.
format
(
catalog_id
),
'name'
:
'Catalog {}'
.
format
(
catalog_id
),
...
@@ -122,15 +121,23 @@ class CourseCatalogServiceMockMixin(object):
...
@@ -122,15 +121,23 @@ class CourseCatalogServiceMockMixin(object):
responses
=
mocked_api_responses
responses
=
mocked_api_responses
)
)
def
mock_course_discovery_api_for_
failure
(
self
):
def
mock_course_discovery_api_for_
catalogs_with_failure
(
self
,
error
,
catalog_id
=
None
):
"""
"""
Helper function to register course catalog API endpoint for
a
Helper function to register course catalog API endpoint for
catalogs
failure.
with
failure.
"""
"""
def
callback
(
request
,
uri
,
headers
):
# pylint: disable=unused-argument
raise
error
if
catalog_id
:
course_catalog_uri
=
'{}{}/'
.
format
(
self
.
COURSE_DISCOVERY_CATALOGS_URL
,
catalog_id
)
else
:
course_catalog_uri
=
self
.
COURSE_DISCOVERY_CATALOGS_URL
httpretty
.
register_uri
(
httpretty
.
register_uri
(
method
=
httpretty
.
GET
,
method
=
httpretty
.
GET
,
uri
=
self
.
COURSE_DISCOVERY_CATALOGS_URL
,
uri
=
course_catalog_uri
,
responses
=
[
responses
=
[
httpretty
.
Response
(
body
=
'Clunk'
,
content_type
=
'application/json'
,
status_code
=
500
)
httpretty
.
Response
(
body
=
callback
,
content_type
=
'application/json'
,
status_code
=
500
)
]
]
)
)
ecommerce/courses/tests/test_utils.py
View file @
e208386e
...
@@ -3,6 +3,7 @@ import hashlib
...
@@ -3,6 +3,7 @@ import hashlib
import
ddt
import
ddt
from
django.core.cache
import
cache
from
django.core.cache
import
cache
import
httpretty
import
httpretty
from
requests.exceptions
import
ConnectionError
from
ecommerce.core.constants
import
ENROLLMENT_CODE_SWITCH
from
ecommerce.core.constants
import
ENROLLMENT_CODE_SWITCH
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
...
@@ -175,7 +176,8 @@ class GetCourseCatalogUtilTests(CourseCatalogServiceMockMixin, TestCase):
...
@@ -175,7 +176,8 @@ class GetCourseCatalogUtilTests(CourseCatalogServiceMockMixin, TestCase):
Verify that method "get_course_catalogs" raises exception in case
Verify that method "get_course_catalogs" raises exception in case
the Course Discovery API fails to return data.
the Course Discovery API fails to return data.
"""
"""
self
.
mock_course_discovery_api_for_failure
()
exception
=
ConnectionError
self
.
mock_course_discovery_api_for_catalogs_with_failure
(
exception
)
with
self
.
assertRaises
(
E
xception
):
with
self
.
assertRaises
(
e
xception
):
get_course_catalogs
(
self
.
request
.
site
)
get_course_catalogs
(
self
.
request
.
site
)
ecommerce/enterprise/entitlements.py
View file @
e208386e
...
@@ -16,11 +16,10 @@ from oscar.core.loading import get_model
...
@@ -16,11 +16,10 @@ from oscar.core.loading import get_model
from
requests.exceptions
import
ConnectionError
,
Timeout
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberBaseException
from
slumber.exceptions
import
SlumberBaseException
from
ecommerce.coupons.utils
import
get_catalog_course_runs
from
ecommerce.coupons.views
import
voucher_is_valid
from
ecommerce.coupons.views
import
voucher_is_valid
from
ecommerce.courses.utils
import
get_course_catalogs
from
ecommerce.courses.utils
import
get_course_catalogs
from
ecommerce.enterprise.utils
import
is_enterprise_feature_enabled
from
ecommerce.enterprise.utils
import
is_enterprise_feature_enabled
from
ecommerce.extensions.api.serializers
import
retrieve_
voucher
from
ecommerce.extensions.api.serializers
import
retrieve_
all_vouchers
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -34,28 +33,29 @@ def get_entitlement_voucher(request, product):
...
@@ -34,28 +33,29 @@ def get_entitlement_voucher(request, product):
learner.
learner.
Arguments:
Arguments:
request (HttpRequest): request with voucher data
product (Product): A product that has course_id as attribute (seat or
product (Product): A product that has course_key as attribute (seat or
bulk enrollment coupon)
bulk enrollment coupon)
request (HttpRequest): request with voucher data
"""
"""
if
not
is_enterprise_feature_enabled
():
if
not
is_enterprise_feature_enabled
():
return
None
return
None
vouchers
=
get_vouchers_for_learner
(
request
.
site
,
request
.
user
)
vouchers
=
get_course_vouchers_for_learner
(
request
.
site
,
request
.
user
,
product
.
course_id
)
if
vouchers
:
if
not
vouchers
:
entitlement_voucher
=
get_available_voucher_for_product
(
request
,
product
,
vouchers
)
return
None
return
entitlement_voucher
return
None
entitlement_voucher
=
get_available_voucher_for_product
(
request
,
product
,
vouchers
)
return
entitlement_voucher
def
get_
vouchers_for_learner
(
site
,
user
):
def
get_
course_vouchers_for_learner
(
site
,
user
,
course_id
):
"""
"""
Get vouchers against the list of all enterprise entitlements for the
Get vouchers against the list of all enterprise entitlements for the
provided learner.
provided learner
and course id
.
Arguments:
Arguments:
course_id (str): The course ID.
site: (django.contrib.sites.Site) site instance
site: (django.contrib.sites.Site) site instance
user: (django.contrib.auth.User) django auth user
user: (django.contrib.auth.User) django auth user
...
@@ -63,7 +63,7 @@ def get_vouchers_for_learner(site, user):
...
@@ -63,7 +63,7 @@ def get_vouchers_for_learner(site, user):
list of Voucher class objects
list of Voucher class objects
"""
"""
entitlements
=
get_
entitlements_for_learner
(
site
,
user
)
entitlements
=
get_
course_entitlements_for_learner
(
site
,
user
,
course_id
)
if
not
entitlements
:
if
not
entitlements
:
return
None
return
None
...
@@ -78,18 +78,19 @@ def get_vouchers_for_learner(site, user):
...
@@ -78,18 +78,19 @@ def get_vouchers_for_learner(site, user):
)
)
return
None
return
None
entitlement_voucher
=
retrieve_
voucher
(
coupon_product
)
entitlement_voucher
=
retrieve_
all_vouchers
(
coupon_product
)
vouchers
.
app
end
(
entitlement_voucher
)
vouchers
.
ext
end
(
entitlement_voucher
)
return
vouchers
return
vouchers
def
get_
entitlements_for_learner
(
site
,
user
):
def
get_
course_entitlements_for_learner
(
site
,
user
,
course_id
):
"""
"""
Get entitlements for the provided learner
if the provided learner is
Get entitlements for the provided learner
against the provided course id
affiliated with an enterprise.
if the provided learner is
affiliated with an enterprise.
Arguments:
Arguments:
course_id (str): The course ID.
site: (django.contrib.sites.Site) site instance
site: (django.contrib.sites.Site) site instance
user: (django.contrib.auth.User) django auth user
user: (django.contrib.auth.User) django auth user
...
@@ -108,14 +109,90 @@ def get_entitlements_for_learner(site, user):
...
@@ -108,14 +109,90 @@ def get_entitlements_for_learner(site, user):
return
None
return
None
try
:
try
:
enterprise_catalog_id
=
enterprise_learner_data
[
0
][
'enterprise_customer'
][
'catalog'
]
entitlements
=
enterprise_learner_data
[
0
][
'enterprise_customer'
][
'enterprise_customer_entitlements'
]
entitlements
=
enterprise_learner_data
[
0
][
'enterprise_customer'
][
'enterprise_customer_entitlements'
]
except
KeyError
:
except
KeyError
:
logger
.
error
(
'Invalid structure for enterprise learner API response for the learner [
%
s]'
,
user
.
username
)
logger
.
exception
(
'Invalid structure for enterprise learner API response for the learner [
%
s]'
,
user
.
username
)
return
None
# Before returning entitlements verify that the provided course exists in
# the enterprise course catalog
if
not
is_course_in_enterprise_catalog
(
site
,
course_id
,
enterprise_catalog_id
):
return
None
return
None
return
entitlements
return
entitlements
def
is_course_in_enterprise_catalog
(
site
,
course_id
,
enterprise_catalog_id
):
"""
Verify that the provided course id exists in the site base list of course
run keys from the provided enterprise course catalog.
Arguments:
course_id (str): The course ID.
site: (django.contrib.sites.Site) site instance
enterprise_catalog_id (Int): Course catalog id of enterprise
Returns:
Boolean
"""
try
:
enterprise_course_catalog
=
get_course_catalogs
(
site
=
site
,
resource_id
=
enterprise_catalog_id
)
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
logger
.
exception
(
'Unable to connect to Course Catalog service for course catalogs.'
)
return
None
if
is_course_in_catalog_query
(
site
,
course_id
,
enterprise_course_catalog
.
get
(
'query'
)):
return
True
return
False
def
is_course_in_catalog_query
(
site
,
course_id
,
enterprise_catalog_query
):
"""
Find out if the provided course exists in list of courses against the
enterprise course catalog query.
Arguments:
site: (django.contrib.sites.Site) site instance
course_id (Int): Course catalog id of enterprise
enterprise_catalog_query (Str): Enterprise course catalog query
Returns:
Boolean
"""
partner_code
=
site
.
siteconfiguration
.
partner
.
short_code
cache_key
=
hashlib
.
md5
(
'{site_domain}_{partner_code}_catalog_query_contains_{course_id}_{query}'
.
format
(
site_domain
=
site
.
domain
,
partner_code
=
partner_code
,
course_id
=
course_id
,
query
=
enterprise_catalog_query
)
)
.
hexdigest
()
response
=
cache
.
get
(
cache_key
)
if
not
response
:
try
:
response
=
site
.
siteconfiguration
.
course_catalog_api_client
.
course_runs
.
contains
.
get
(
query
=
enterprise_catalog_query
,
course_run_ids
=
course_id
,
partner
=
partner_code
)
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
logger
.
exception
(
'Unable to connect to Course Catalog service for course runs.'
)
return
False
try
:
is_course_in_course_runs
=
response
[
'course_runs'
][
course_id
]
except
KeyError
:
return
False
return
is_course_in_course_runs
def
get_enterprise_learner_data
(
site
,
user
):
def
get_enterprise_learner_data
(
site
,
user
):
"""
"""
Fetch information related to enterprise and its entitlements according to
Fetch information related to enterprise and its entitlements according to
...
@@ -220,7 +297,7 @@ def get_available_voucher_for_product(request, product, vouchers):
...
@@ -220,7 +297,7 @@ def get_available_voucher_for_product(request, product, vouchers):
product.
product.
Arguments:
Arguments:
product (Product): A product that has course_
key
as attribute (seat or
product (Product): A product that has course_
id
as attribute (seat or
bulk enrollment coupon)
bulk enrollment coupon)
request (HttpRequest): request with voucher data
request (HttpRequest): request with voucher data
vouchers: (List) List of voucher class objects for an enterprise
vouchers: (List) List of voucher class objects for an enterprise
...
@@ -229,50 +306,10 @@ def get_available_voucher_for_product(request, product, vouchers):
...
@@ -229,50 +306,10 @@ def get_available_voucher_for_product(request, product, vouchers):
for
voucher
in
vouchers
:
for
voucher
in
vouchers
:
is_valid_voucher
,
__
=
voucher_is_valid
(
voucher
,
[
product
],
request
)
is_valid_voucher
,
__
=
voucher_is_valid
(
voucher
,
[
product
],
request
)
if
is_valid_voucher
:
if
is_valid_voucher
:
voucher_course_ids
=
get_course_ids_from_voucher
(
request
.
site
,
voucher
)
voucher_offer
=
voucher
.
offers
.
first
()
if
product
.
course_id
in
voucher_course_ids
:
offer_range
=
voucher_offer
.
condition
.
range
if
offer_range
.
contains_product
(
product
):
return
voucher
return
voucher
# Explicitly return None in case product has no valid voucher
def
get_course_ids_from_voucher
(
site
,
voucher
):
return
None
"""
Get site base list of course run keys from the provided voucher object.
Arguments:
site: (django.contrib.sites.Site) site instance
voucher (Voucher): voucher class object
Returns:
list of course ids
"""
voucher_offer
=
voucher
.
offers
.
first
()
offer_range
=
voucher_offer
.
condition
.
range
if
offer_range
.
course_catalog
:
try
:
course_catalog
=
get_course_catalogs
(
site
=
site
,
resource_id
=
offer_range
.
course_catalog
)
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
logger
.
error
(
'Unable to connect to Course Catalog service for course catalogs.'
)
return
None
try
:
course_runs
=
get_catalog_course_runs
(
site
,
course_catalog
.
get
(
'query'
))[
'results'
]
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
,
KeyError
):
logger
.
error
(
'Unable to get course runs from Course Catalog service.'
)
return
None
voucher_course_ids
=
[
course_run
.
get
(
'key'
)
for
course_run
in
course_runs
if
course_run
.
get
(
'key'
)]
elif
offer_range
.
catalog_query
:
try
:
course_runs
=
get_catalog_course_runs
(
site
,
offer_range
.
catalog_query
)[
'results'
]
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
,
KeyError
):
logger
.
error
(
'Unable to get course runs from Course Catalog service.'
)
return
None
voucher_course_ids
=
[
course_run
.
get
(
'key'
)
for
course_run
in
course_runs
if
course_run
.
get
(
'key'
)]
else
:
stock_records
=
offer_range
.
catalog
.
stock_records
.
all
()
seats
=
Product
.
objects
.
filter
(
id__in
=
[
sr
.
product
.
id
for
sr
in
stock_records
])
voucher_course_ids
=
[
seat
.
course_id
for
seat
in
seats
]
return
voucher_course_ids
ecommerce/enterprise/tests/mixins.py
View file @
e208386e
...
@@ -110,7 +110,7 @@ class EnterpriseServiceMockMixin(object):
...
@@ -110,7 +110,7 @@ class EnterpriseServiceMockMixin(object):
def
mock_enterprise_learner_api_for_learner_with_invalid_response
(
self
):
def
mock_enterprise_learner_api_for_learner_with_invalid_response
(
self
):
"""
"""
Helper function to register enterprise learner API endpoint for a
Helper function to register enterprise learner API endpoint for a
learner with invalid API reponse structure.
learner with invalid API re
s
ponse structure.
"""
"""
enterprise_learner_api_response
=
{
enterprise_learner_api_response
=
{
'count'
:
0
,
'count'
:
0
,
...
@@ -151,6 +151,49 @@ class EnterpriseServiceMockMixin(object):
...
@@ -151,6 +151,49 @@ class EnterpriseServiceMockMixin(object):
content_type
=
'application/json'
content_type
=
'application/json'
)
)
def
mock_enterprise_learner_api_for_learner_with_invalid_entitlements_response
(
self
):
"""
Helper function to register enterprise learner API endpoint for a
learner with partial invalid API response structure for the enterprise
customer entitlements.
"""
enterprise_learner_api_response
=
{
'count'
:
0
,
'num_pages'
:
1
,
'current_page'
:
1
,
'results'
:
[
{
'enterprise_customer'
:
{
'uuid'
:
'cf246b88-d5f6-4908-a522-fc307e0b0c59'
,
'name'
:
'TestShib'
,
'catalog'
:
1
,
'active'
:
True
,
'site'
:
{
'domain'
:
'example.com'
,
'name'
:
'example.com'
},
'invalid-unexpected-enterprise_customer_entitlements-key'
:
[
{
'enterprise_customer'
:
'cf246b88-d5f6-4908-a522-fc307e0b0c59'
,
'entitlement_id'
:
1
}
]
}
}
],
'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
.
ENTERPRISE_LEARNER_URL
,
body
=
enterprise_learner_api_response_json
,
content_type
=
'application/json'
)
def
mock_enterprise_learner_api_for_failure
(
self
):
def
mock_enterprise_learner_api_for_failure
(
self
):
"""
"""
Helper function to register enterprise learner API endpoint for a
Helper function to register enterprise learner API endpoint for a
...
...
ecommerce/enterprise/tests/test_entitlements.py
View file @
e208386e
This diff is collapsed.
Click to expand it.
ecommerce/extensions/api/serializers.py
View file @
e208386e
...
@@ -83,6 +83,11 @@ def retrieve_voucher(obj):
...
@@ -83,6 +83,11 @@ def retrieve_voucher(obj):
return
obj
.
attr
.
coupon_vouchers
.
vouchers
.
first
()
return
obj
.
attr
.
coupon_vouchers
.
vouchers
.
first
()
def
retrieve_all_vouchers
(
obj
):
"""Helper method to retrieve all vouchers from coupon. """
return
obj
.
attr
.
coupon_vouchers
.
vouchers
.
all
()
def
retrieve_voucher_usage
(
obj
):
def
retrieve_voucher_usage
(
obj
):
"""Helper method to retrieve usage from voucher. """
"""Helper method to retrieve usage from voucher. """
return
retrieve_voucher
(
obj
)
.
usage
return
retrieve_voucher
(
obj
)
.
usage
...
...
ecommerce/extensions/api/v2/tests/views/test_catalog.py
View file @
e208386e
...
@@ -179,7 +179,7 @@ class CatalogViewSetTest(CatalogMixin, CourseCatalogMockMixin, CourseCatalogServ
...
@@ -179,7 +179,7 @@ class CatalogViewSetTest(CatalogMixin, CourseCatalogMockMixin, CourseCatalogServ
empty results list in case the Course Discovery API fails to return
empty results list in case the Course Discovery API fails to return
data.
data.
"""
"""
self
.
mock_course_discovery_api_for_
failure
(
)
self
.
mock_course_discovery_api_for_
catalogs_with_failure
(
ConnectionError
)
request
=
self
.
prepare_request
(
'/api/v2/coupons/course_catalogs/'
)
request
=
self
.
prepare_request
(
'/api/v2/coupons/course_catalogs/'
)
response
=
CatalogViewSet
()
.
course_catalogs
(
request
)
response
=
CatalogViewSet
()
.
course_catalogs
(
request
)
...
...
ecommerce/extensions/offer/models.py
View file @
e208386e
...
@@ -7,9 +7,12 @@ from django.core.cache import cache
...
@@ -7,9 +7,12 @@ from django.core.cache import cache
from
django.db
import
models
from
django.db
import
models
from
django.utils.translation
import
ugettext_lazy
as
_
from
django.utils.translation
import
ugettext_lazy
as
_
from
oscar.apps.offer.abstract_models
import
AbstractBenefit
,
AbstractConditionalOffer
,
AbstractRange
from
oscar.apps.offer.abstract_models
import
AbstractBenefit
,
AbstractConditionalOffer
,
AbstractRange
from
requests.exceptions
import
ConnectionError
,
Timeout
from
slumber.exceptions
import
SlumberBaseException
from
threadlocals.threadlocals
import
get_current_request
from
threadlocals.threadlocals
import
get_current_request
from
ecommerce.core.utils
import
log_message_and_raise_validation_error
from
ecommerce.core.utils
import
log_message_and_raise_validation_error
from
ecommerce.courses.utils
import
get_course_catalogs
class
Benefit
(
AbstractBenefit
):
class
Benefit
(
AbstractBenefit
):
...
@@ -220,21 +223,30 @@ class Range(AbstractRange):
...
@@ -220,21 +223,30 @@ class Range(AbstractRange):
if
self
.
course_seat_types
:
if
self
.
course_seat_types
:
validate_credit_seat_type
(
self
.
course_seat_types
)
validate_credit_seat_type
(
self
.
course_seat_types
)
def
run_catalog_query
(
self
,
product
):
def
run_catalog_query
(
self
,
product
,
query
=
None
):
"""
"""
Retrieve the results from running the query contained in catalog_query field.
Retrieve the results from running the query contained in catalog_query field.
"""
"""
if
not
query
:
query
=
self
.
catalog_query
request
=
get_current_request
()
partner_code
=
request
.
site
.
siteconfiguration
.
partner
.
short_code
cache_key
=
hashlib
.
md5
(
cache_key
=
hashlib
.
md5
(
'catalog_query_contains [{}] [{}]'
.
format
(
self
.
catalog_query
,
product
.
course_id
)
'{site_domain}_{partner_code}_catalog_query_contains_{course_id}_{query}'
.
format
(
site_domain
=
request
.
site
.
domain
,
partner_code
=
partner_code
,
course_id
=
product
.
course_id
,
query
=
query
)
)
.
hexdigest
()
)
.
hexdigest
()
response
=
cache
.
get
(
cache_key
)
response
=
cache
.
get
(
cache_key
)
if
not
response
:
# pragma: no cover
if
not
response
:
# pragma: no cover
request
=
get_current_request
()
try
:
try
:
response
=
request
.
site
.
siteconfiguration
.
course_catalog_api_client
.
course_runs
.
contains
.
get
(
response
=
request
.
site
.
siteconfiguration
.
course_catalog_api_client
.
course_runs
.
contains
.
get
(
query
=
self
.
catalog_
query
,
query
=
query
,
course_run_ids
=
product
.
course_id
,
course_run_ids
=
product
.
course_id
,
partner
=
request
.
site
.
siteconfiguration
.
partner
.
short
_code
partner
=
partner
_code
)
)
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
cache
.
set
(
cache_key
,
response
,
settings
.
COURSES_API_CACHE_TIMEOUT
)
except
:
# pylint: disable=bare-except
except
:
# pylint: disable=bare-except
...
@@ -246,7 +258,21 @@ class Range(AbstractRange):
...
@@ -246,7 +258,21 @@ class Range(AbstractRange):
"""
"""
Assert if the range contains the product.
Assert if the range contains the product.
"""
"""
if
self
.
catalog_query
and
self
.
course_seat_types
:
if
self
.
course_catalog
:
request
=
get_current_request
()
try
:
course_catalog
=
get_course_catalogs
(
site
=
request
.
site
,
resource_id
=
self
.
course_catalog
)
except
(
ConnectionError
,
SlumberBaseException
,
Timeout
):
raise
Exception
(
'Unable to connect to Course Catalog service for catalog with id [
%
s].'
%
self
.
course_catalog
)
response
=
self
.
run_catalog_query
(
product
,
course_catalog
.
get
(
'query'
))
# Range can have a catalog query and 'regular' products in it,
# therefor an OR is used to check for both possibilities.
return
((
response
[
'course_runs'
][
product
.
course_id
])
or
super
(
Range
,
self
)
.
contains_product
(
product
))
# pylint: disable=bad-super-call
elif
self
.
catalog_query
and
self
.
course_seat_types
:
if
product
.
attr
.
certificate_type
.
lower
()
in
self
.
course_seat_types
:
# pylint: disable=unsupported-membership-test
if
product
.
attr
.
certificate_type
.
lower
()
in
self
.
course_seat_types
:
# pylint: disable=unsupported-membership-test
response
=
self
.
run_catalog_query
(
product
)
response
=
self
.
run_catalog_query
(
product
)
# Range can have a catalog query and 'regular' products in it,
# Range can have a catalog query and 'regular' products in it,
...
...
ecommerce/extensions/offer/tests/test_models.py
View file @
e208386e
...
@@ -13,16 +13,18 @@ from oscar.test import factories
...
@@ -13,16 +13,18 @@ from oscar.test import factories
from
ecommerce.core.tests.decorators
import
mock_course_catalog_api_client
from
ecommerce.core.tests.decorators
import
mock_course_catalog_api_client
from
ecommerce.coupons.tests.mixins
import
CourseCatalogMockMixin
,
CouponMixin
from
ecommerce.coupons.tests.mixins
import
CourseCatalogMockMixin
,
CouponMixin
from
ecommerce.courses.tests.mixins
import
CourseCatalogServiceMockMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
Catalog
=
get_model
(
'catalogue'
,
'Catalog'
)
Catalog
=
get_model
(
'catalogue'
,
'Catalog'
)
ConditionalOffer
=
get_model
(
'offer'
,
'ConditionalOffer'
)
ConditionalOffer
=
get_model
(
'offer'
,
'ConditionalOffer'
)
Range
=
get_model
(
'offer'
,
'Range'
)
Range
=
get_model
(
'offer'
,
'Range'
)
@ddt.ddt
@ddt.ddt
class
RangeTests
(
CouponMixin
,
CourseCatalogTestMixin
,
CourseCatalogMockMixin
,
TestCase
):
class
RangeTests
(
CouponMixin
,
CourseCatalog
ServiceMockMixin
,
CourseCatalog
TestMixin
,
CourseCatalogMockMixin
,
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
super
(
RangeTests
,
self
)
.
setUp
()
super
(
RangeTests
,
self
)
.
setUp
()
...
@@ -100,7 +102,15 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
...
@@ -100,7 +102,15 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
request
.
site
=
self
.
site
request
.
site
=
self
.
site
self
.
range
.
catalog_query
=
'key:*'
self
.
range
.
catalog_query
=
'key:*'
cache_key
=
hashlib
.
md5
(
'catalog_query_contains [{}] [{}]'
.
format
(
'key:*'
,
seat
.
course_id
))
.
hexdigest
()
partner_code
=
request
.
site
.
siteconfiguration
.
partner
.
short_code
cache_key
=
hashlib
.
md5
(
'{site_domain}_{partner_code}_catalog_query_contains_{course_id}_{query}'
.
format
(
site_domain
=
request
.
site
.
domain
,
partner_code
=
partner_code
,
course_id
=
seat
.
course_id
,
query
=
self
.
range
.
catalog_query
)
)
.
hexdigest
()
cached_response
=
cache
.
get
(
cache_key
)
cached_response
=
cache
.
get
(
cache_key
)
self
.
assertIsNone
(
cached_response
)
self
.
assertIsNone
(
cached_response
)
...
@@ -129,6 +139,54 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
...
@@ -129,6 +139,54 @@ class RangeTests(CouponMixin, CourseCatalogTestMixin, CourseCatalogMockMixin, Te
@httpretty.activate
@httpretty.activate
@mock_course_catalog_api_client
@mock_course_catalog_api_client
def
test_course_catalog_query_range_contains_product
(
self
):
"""
Verify that the method "contains_product" returns True (boolean) if a
product is in it's range for a course catalog Range.
"""
catalog_query
=
'key:*'
course
,
seat
=
self
.
create_course_and_seat
()
self
.
mock_dynamic_catalog_contains_api
(
query
=
catalog_query
,
course_run_ids
=
[
course
.
id
])
false_response
=
self
.
range
.
contains_product
(
seat
)
self
.
assertFalse
(
false_response
)
course_catalog_id
=
1
self
.
mock_course_discovery_api_for_catalog_by_resource_id
(
catalog_id
=
course_catalog_id
,
catalog_query
=
catalog_query
)
self
.
range
.
catalog_query
=
None
self
.
range
.
course_seat_types
=
None
self
.
range
.
course_catalog
=
course_catalog_id
self
.
range
.
save
()
response
=
self
.
range
.
contains_product
(
seat
)
self
.
assertTrue
(
response
)
@httpretty.activate
@mock_course_catalog_api_client
def
test_course_catalog_query_range_contains_product_for_failure
(
self
):
"""
Verify that the method "contains_product" raises exception if the
method "get_course_catalogs" is unable to get the catalog from course
catalog service for a course catalog Range.
"""
__
,
seat
=
self
.
create_course_and_seat
()
course_catalog_id
=
1
self
.
range
.
catalog_query
=
None
self
.
range
.
course_seat_types
=
None
self
.
range
.
course_catalog
=
course_catalog_id
self
.
range
.
save
()
with
self
.
assertRaises
(
Exception
)
as
error
:
self
.
range
.
contains_product
(
seat
)
expected_exception_message
=
'Unable to connect to Course Catalog service for catalog with id [
%
s].'
%
\
self
.
range
.
course_catalog
self
.
assertEqual
(
error
.
exception
.
message
,
expected_exception_message
)
@httpretty.activate
@mock_course_catalog_api_client
def
test_query_range_all_products
(
self
):
def
test_query_range_all_products
(
self
):
"""
"""
all_products() should return seats from the query.
all_products() should return seats from the query.
...
...
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