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
58c45e26
Commit
58c45e26
authored
Aug 10, 2017
by
McKenzie Welter
Committed by
McKenzie Welter
Aug 18, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Track bundle purchases in Order Completed GA event
parent
6ddbbf41
Hide whitespace changes
Inline
Side-by-side
Showing
13 changed files
with
203 additions
and
60 deletions
+203
-60
ecommerce/coupons/tests/mixins.py
+1
-0
ecommerce/coupons/tests/test_utils.py
+1
-0
ecommerce/extensions/api/v2/tests/views/test_coupons.py
+1
-0
ecommerce/extensions/basket/tests/test_utils.py
+17
-0
ecommerce/extensions/basket/utils.py
+11
-0
ecommerce/extensions/checkout/signals.py
+29
-1
ecommerce/extensions/checkout/tests/test_signals.py
+58
-7
ecommerce/programs/conditions.py
+3
-20
ecommerce/programs/migrations/0002_add_basket_attribute_type.py
+30
-0
ecommerce/programs/tests/test_conditions.py
+2
-15
ecommerce/programs/tests/test_utils.py
+26
-0
ecommerce/programs/utils.py
+20
-0
ecommerce/programs/views.py
+4
-17
No files found.
ecommerce/coupons/tests/mixins.py
View file @
58c45e26
...
@@ -402,6 +402,7 @@ class CouponMixin(object):
...
@@ -402,6 +402,7 @@ class CouponMixin(object):
request
.
site
=
self
.
site
request
.
site
=
self
.
site
request
.
user
=
factories
.
UserFactory
()
request
.
user
=
factories
.
UserFactory
()
request
.
COOKIES
=
{}
request
.
COOKIES
=
{}
request
.
GET
=
{}
self
.
basket
=
prepare_basket
(
request
,
[
coupon
])
self
.
basket
=
prepare_basket
(
request
,
[
coupon
])
...
...
ecommerce/coupons/tests/test_utils.py
View file @
58c45e26
...
@@ -18,6 +18,7 @@ class CouponAppViewTests(TestCase):
...
@@ -18,6 +18,7 @@ class CouponAppViewTests(TestCase):
self
.
user
=
self
.
create_user
(
email
=
'test@tester.fake'
)
self
.
user
=
self
.
create_user
(
email
=
'test@tester.fake'
)
self
.
request
.
user
=
self
.
user
self
.
request
.
user
=
self
.
user
self
.
request
.
GET
=
{}
@ddt.data
(
@ddt.data
(
([
'verIfiEd'
,
'profeSSional'
],
'verified,professional'
),
([
'verIfiEd'
,
'profeSSional'
],
'verified,professional'
),
...
...
ecommerce/extensions/api/v2/tests/views/test_coupons.py
View file @
58c45e26
...
@@ -90,6 +90,7 @@ class CouponViewSetTest(CouponMixin, DiscoveryTestMixin, TestCase):
...
@@ -90,6 +90,7 @@ class CouponViewSetTest(CouponMixin, DiscoveryTestMixin, TestCase):
request
.
data
=
self
.
coupon_data
request
.
data
=
self
.
coupon_data
request
.
site
=
self
.
site
request
.
site
=
self
.
site
request
.
COOKIES
=
{}
request
.
COOKIES
=
{}
request
.
GET
=
{}
return
request
return
request
def
test_create
(
self
):
def
test_create
(
self
):
...
...
ecommerce/extensions/basket/tests/test_utils.py
View file @
58c45e26
...
@@ -25,6 +25,9 @@ from ecommerce.tests.testcases import TestCase, TransactionTestCase
...
@@ -25,6 +25,9 @@ from ecommerce.tests.testcases import TestCase, TransactionTestCase
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
Basket
=
get_model
(
'basket'
,
'Basket'
)
Basket
=
get_model
(
'basket'
,
'Basket'
)
BasketAttribute
=
get_model
(
'basket'
,
'BasketAttribute'
)
BasketAttributeType
=
get_model
(
'basket'
,
'BasketAttributeType'
)
BUNDLE
=
'bundle_identifier'
Product
=
get_model
(
'catalogue'
,
'Product'
)
Product
=
get_model
(
'catalogue'
,
'Product'
)
...
@@ -359,6 +362,20 @@ class BasketUtilsTests(DiscoveryTestMixin, TestCase):
...
@@ -359,6 +362,20 @@ class BasketUtilsTests(DiscoveryTestMixin, TestCase):
basket
=
prepare_basket
(
self
.
request
,
[
enrollment_code
])
basket
=
prepare_basket
(
self
.
request
,
[
enrollment_code
])
self
.
assertIsNotNone
(
basket
)
self
.
assertIsNotNone
(
basket
)
def
test_prepare_basket_with_bundle
(
self
):
"""
Test prepare_basket updates or creates a basket attribute for the associated bundle
"""
product
=
ProductFactory
()
request
=
self
.
request
basket
=
prepare_basket
(
request
,
[
product
])
with
self
.
assertRaises
(
BasketAttribute
.
DoesNotExist
):
BasketAttribute
.
objects
.
get
(
basket
=
basket
,
attribute_type__name
=
BUNDLE
)
request
.
GET
=
{
'bundle'
:
'test_bundle'
}
basket
=
prepare_basket
(
request
,
[
product
])
bundle_id
=
BasketAttribute
.
objects
.
get
(
basket
=
basket
,
attribute_type__name
=
BUNDLE
)
.
value_text
self
.
assertEqual
(
bundle_id
,
'test_bundle'
)
class
BasketUtilsTransactionTests
(
TransactionTestCase
):
class
BasketUtilsTransactionTests
(
TransactionTestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
...
...
ecommerce/extensions/basket/utils.py
View file @
58c45e26
...
@@ -18,6 +18,9 @@ from ecommerce.referrals.models import Referral
...
@@ -18,6 +18,9 @@ from ecommerce.referrals.models import Referral
Applicator
=
get_class
(
'offer.utils'
,
'Applicator'
)
Applicator
=
get_class
(
'offer.utils'
,
'Applicator'
)
Basket
=
get_model
(
'basket'
,
'Basket'
)
Basket
=
get_model
(
'basket'
,
'Basket'
)
BasketAttribute
=
get_model
(
'basket'
,
'BasketAttribute'
)
BasketAttributeType
=
get_model
(
'basket'
,
'BasketAttributeType'
)
BUNDLE
=
'bundle_identifier'
StockRecord
=
get_model
(
'partner'
,
'StockRecord'
)
StockRecord
=
get_model
(
'partner'
,
'StockRecord'
)
OrderLine
=
get_model
(
'order'
,
'Line'
)
OrderLine
=
get_model
(
'order'
,
'Line'
)
Refund
=
get_model
(
'refund'
,
'Refund'
)
Refund
=
get_model
(
'refund'
,
'Refund'
)
...
@@ -59,6 +62,14 @@ def prepare_basket(request, products, voucher=None):
...
@@ -59,6 +62,14 @@ def prepare_basket(request, products, voucher=None):
basket
.
save
()
basket
.
save
()
basket_addition
=
get_class
(
'basket.signals'
,
'basket_addition'
)
basket_addition
=
get_class
(
'basket.signals'
,
'basket_addition'
)
already_purchased_products
=
[]
already_purchased_products
=
[]
bundle
=
request
.
GET
.
get
(
'bundle'
)
if
bundle
:
BasketAttribute
.
objects
.
update_or_create
(
basket
=
basket
,
attribute_type
=
BasketAttributeType
.
objects
.
get
(
name
=
BUNDLE
),
value_text
=
bundle
)
if
request
.
site
.
siteconfiguration
.
enable_embargo_check
:
if
request
.
site
.
siteconfiguration
.
enable_embargo_check
:
if
not
embargo_check
(
request
.
user
,
request
.
site
,
products
):
if
not
embargo_check
(
request
.
user
,
request
.
site
,
products
):
...
...
ecommerce/extensions/checkout/signals.py
View file @
58c45e26
...
@@ -2,13 +2,17 @@ import logging
...
@@ -2,13 +2,17 @@ import logging
import
waffle
import
waffle
from
django.dispatch
import
receiver
from
django.dispatch
import
receiver
from
oscar.core.loading
import
get_class
from
oscar.core.loading
import
get_class
,
get_model
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.extensions.analytics.utils
import
silence_exceptions
,
track_segment_event
from
ecommerce.extensions.analytics.utils
import
silence_exceptions
,
track_segment_event
from
ecommerce.extensions.checkout.utils
import
get_credit_provider_details
,
get_receipt_page_url
from
ecommerce.extensions.checkout.utils
import
get_credit_provider_details
,
get_receipt_page_url
from
ecommerce.notifications.notifications
import
send_notification
from
ecommerce.notifications.notifications
import
send_notification
from
ecommerce.programs.utils
import
get_program
BasketAttribute
=
get_model
(
'basket'
,
'BasketAttribute'
)
BasketAttributeType
=
get_model
(
'basket'
,
'BasketAttributeType'
)
BUNDLE
=
'bundle_identifier'
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
post_checkout
=
get_class
(
'checkout.signals'
,
'post_checkout'
)
post_checkout
=
get_class
(
'checkout.signals'
,
'post_checkout'
)
...
@@ -29,6 +33,10 @@ def track_completed_order(sender, order=None, **kwargs): # pylint: disable=unus
...
@@ -29,6 +33,10 @@ def track_completed_order(sender, order=None, **kwargs): # pylint: disable=unus
voucher
=
order
.
basket_discounts
.
filter
(
voucher_id__isnull
=
False
)
.
first
()
voucher
=
order
.
basket_discounts
.
filter
(
voucher_id__isnull
=
False
)
.
first
()
coupon
=
voucher
.
voucher_code
if
voucher
else
None
coupon
=
voucher
.
voucher_code
if
voucher
else
None
try
:
bundle_id
=
BasketAttribute
.
objects
.
get
(
basket
=
order
.
basket
,
attribute_type__name
=
BUNDLE
)
.
value_text
except
BasketAttribute
.
DoesNotExist
:
bundle_id
=
None
properties
=
{
properties
=
{
'orderId'
:
order
.
number
,
'orderId'
:
order
.
number
,
...
@@ -51,6 +59,26 @@ def track_completed_order(sender, order=None, **kwargs): # pylint: disable=unus
...
@@ -51,6 +59,26 @@ def track_completed_order(sender, order=None, **kwargs): # pylint: disable=unus
}
for
line
in
order
.
lines
.
all
()
}
for
line
in
order
.
lines
.
all
()
],
],
}
}
try
:
bundle_id
=
BasketAttribute
.
objects
.
get
(
basket
=
order
.
basket
,
attribute_type__name
=
BUNDLE
)
.
value_text
program
=
get_program
(
bundle_id
,
order
.
basket
.
site
.
siteconfiguration
)
if
len
(
order
.
lines
.
all
())
<
len
(
program
[
'courses'
]):
variant
=
'partial'
else
:
variant
=
'full'
bundle_product
=
{
'id'
:
bundle_id
,
'price'
:
'0'
,
'quantity'
:
str
(
len
(
order
.
lines
.
all
())),
'category'
:
'bundle'
,
'variant'
:
variant
,
'name'
:
program
[
'name'
]
}
properties
[
'products'
]
.
append
(
bundle_product
)
except
BasketAttribute
.
DoesNotExist
:
logger
.
info
(
'There is no program or bundle associated with order number
%
s'
,
order
.
number
)
track_segment_event
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
track_segment_event
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
...
...
ecommerce/extensions/checkout/tests/test_signals.py
View file @
58c45e26
...
@@ -13,24 +13,26 @@ from ecommerce.core.tests import toggle_switch
...
@@ -13,24 +13,26 @@ from ecommerce.core.tests import toggle_switch
from
ecommerce.coupons.tests.mixins
import
CouponMixin
from
ecommerce.coupons.tests.mixins
import
CouponMixin
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.tests.factories
import
CourseFactory
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.extensions.catalogue.tests.mixins
import
DiscoveryTestMixin
from
ecommerce.extensions.checkout.signals
import
send_course_purchase_email
,
track_completed_order
from
ecommerce.extensions.checkout.signals
import
send_course_purchase_email
,
track_completed_order
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.checkout.utils
import
get_receipt_page_url
from
ecommerce.extensions.test.factories
import
create_order
,
prepare_voucher
from
ecommerce.extensions.test.factories
import
create_order
,
prepare_voucher
from
ecommerce.programs.tests.mixins
import
ProgramTestMixin
from
ecommerce.tests.factories
import
ProductFactory
from
ecommerce.tests.factories
import
ProductFactory
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.tests.testcases
import
TestCase
Applicator
=
get_class
(
'offer.utils'
,
'Applicator'
)
Applicator
=
get_class
(
'offer.utils'
,
'Applicator'
)
BasketAttribute
=
get_model
(
'basket'
,
'BasketAttribute'
)
BasketAttributeType
=
get_model
(
'basket'
,
'BasketAttributeType'
)
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
Condition
=
get_model
(
'offer'
,
'Condition'
)
Condition
=
get_model
(
'offer'
,
'Condition'
)
ConditionalOffer
=
get_model
(
'offer'
,
'ConditionalOffer'
)
ConditionalOffer
=
get_model
(
'offer'
,
'ConditionalOffer'
)
Product
=
get_model
(
'catalogue'
,
'Product'
)
BUNDLE
=
'bundle_identifier'
LOGGER_NAME
=
'ecommerce.extensions.checkout.signals'
LOGGER_NAME
=
'ecommerce.extensions.checkout.signals'
Product
=
get_model
(
'catalogue'
,
'Product'
)
class
SignalTests
(
Discovery
TestMixin
,
CouponMixin
,
TestCase
):
class
SignalTests
(
Program
TestMixin
,
CouponMixin
,
TestCase
):
def
setUp
(
self
):
def
setUp
(
self
):
super
(
SignalTests
,
self
)
.
setUp
()
super
(
SignalTests
,
self
)
.
setUp
()
self
.
user
=
self
.
create_user
()
self
.
user
=
self
.
create_user
()
...
@@ -55,6 +57,12 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
...
@@ -55,6 +57,12 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
order
=
create_order
(
basket
=
basket
,
user
=
self
.
user
)
order
=
create_order
(
basket
=
basket
,
user
=
self
.
user
)
return
order
return
order
def
mock_get_program_data
(
self
,
isFull
):
data
=
{
'name'
:
'test_program'
,
'courses'
:
[{}]}
if
isFull
:
data
[
'courses'
]
.
append
({})
return
data
@httpretty.activate
@httpretty.activate
def
test_post_checkout_callback
(
self
):
def
test_post_checkout_callback
(
self
):
"""
"""
...
@@ -122,9 +130,9 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
...
@@ -122,9 +130,9 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
)
)
)
)
def
_generate_event_properties
(
self
,
order
,
voucher
=
None
):
def
_generate_event_properties
(
self
,
order
,
voucher
=
None
,
bundle_id
=
None
,
fullBundle
=
False
):
coupon
=
voucher
.
code
if
voucher
else
None
coupon
=
voucher
.
code
if
voucher
else
None
return
{
properties
=
{
'orderId'
:
order
.
number
,
'orderId'
:
order
.
number
,
'total'
:
str
(
order
.
total_excl_tax
),
'total'
:
str
(
order
.
total_excl_tax
),
'currency'
:
order
.
currency
,
'currency'
:
order
.
currency
,
...
@@ -141,6 +149,24 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
...
@@ -141,6 +149,24 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
}
for
line
in
order
.
lines
.
all
()
}
for
line
in
order
.
lines
.
all
()
],
],
}
}
if
bundle_id
:
program
=
self
.
mock_get_program_data
(
fullBundle
)
if
len
(
order
.
lines
.
all
())
<
len
(
program
[
'courses'
]):
variant
=
'partial'
else
:
variant
=
'full'
bundle_product
=
{
'id'
:
bundle_id
,
'price'
:
'0'
,
'quantity'
:
str
(
len
(
order
.
lines
.
all
())),
'category'
:
'bundle'
,
'variant'
:
variant
,
'name'
:
program
[
'name'
]
}
properties
[
'products'
]
.
append
(
bundle_product
)
return
properties
def
test_track_completed_order
(
self
):
def
test_track_completed_order
(
self
):
""" An event should be sent to Segment. """
""" An event should be sent to Segment. """
...
@@ -151,13 +177,38 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
...
@@ -151,13 +177,38 @@ class SignalTests(DiscoveryTestMixin, CouponMixin, TestCase):
properties
=
self
.
_generate_event_properties
(
order
)
properties
=
self
.
_generate_event_properties
(
order
)
mock_track
.
assert_called_once_with
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
mock_track
.
assert_called_once_with
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
# We should be able to fire events even if the product is not rel
e
ated to a course.
# We should be able to fire events even if the product is not related to a course.
mock_track
.
reset_mock
()
mock_track
.
reset_mock
()
order
=
create_order
()
order
=
create_order
()
track_completed_order
(
None
,
order
)
track_completed_order
(
None
,
order
)
properties
=
self
.
_generate_event_properties
(
order
)
properties
=
self
.
_generate_event_properties
(
order
)
mock_track
.
assert_called_once_with
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
mock_track
.
assert_called_once_with
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
@mock.patch
(
'ecommerce.extensions.checkout.signals.track_segment_event'
)
def
test_track_bundle_order
(
self
,
mock_track
):
""" If the order is a bundle purchase, we should track the associated bundle in the properties """
order
=
self
.
prepare_order
(
'verified'
)
BasketAttribute
.
objects
.
update_or_create
(
basket
=
order
.
basket
,
attribute_type
=
BasketAttributeType
.
objects
.
get
(
name
=
BUNDLE
),
value_text
=
'test_bundle'
)
# Tracks a full bundle order
with
mock
.
patch
(
'ecommerce.extensions.checkout.signals.get_program'
,
mock
.
Mock
(
return_value
=
self
.
mock_get_program_data
(
True
))):
track_completed_order
(
None
,
order
)
properties
=
self
.
_generate_event_properties
(
order
,
bundle_id
=
'test_bundle'
,
fullBundle
=
True
)
mock_track
.
assert_called_once_with
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
# Tracks a partial bundle order
with
mock
.
patch
(
'ecommerce.extensions.checkout.signals.get_program'
,
mock
.
Mock
(
return_value
=
self
.
mock_get_program_data
(
False
))):
mock_track
.
reset_mock
()
track_completed_order
(
None
,
order
)
properties
=
self
.
_generate_event_properties
(
order
,
bundle_id
=
'test_bundle'
)
mock_track
.
assert_called_once_with
(
order
.
site
,
order
.
user
,
'Order Completed'
,
properties
)
def
test_track_completed_discounted_order_with_voucher
(
self
):
def
test_track_completed_discounted_order_with_voucher
(
self
):
""" An event including coupon information should be sent to Segment"""
""" An event including coupon information should be sent to Segment"""
with
mock
.
patch
(
'ecommerce.extensions.checkout.signals.track_segment_event'
)
as
mock_track
:
with
mock
.
patch
(
'ecommerce.extensions.checkout.signals.track_segment_event'
)
as
mock_track
:
...
...
ecommerce/programs/conditions.py
View file @
58c45e26
...
@@ -11,7 +11,7 @@ from oscar.core.loading import get_model
...
@@ -11,7 +11,7 @@ from oscar.core.loading import get_model
from
slumber.exceptions
import
HttpNotFoundError
,
SlumberBaseException
from
slumber.exceptions
import
HttpNotFoundError
,
SlumberBaseException
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.core.utils
import
get_cache_key
from
ecommerce.programs.
api
import
ProgramsApiClient
from
ecommerce.programs.
utils
import
get_program
Condition
=
get_model
(
'offer'
,
'Condition'
)
Condition
=
get_model
(
'offer'
,
'Condition'
)
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
...
@@ -26,27 +26,10 @@ class ProgramCourseRunSeatsCondition(Condition):
...
@@ -26,27 +26,10 @@ class ProgramCourseRunSeatsCondition(Condition):
def
name
(
self
):
def
name
(
self
):
return
'Basket contains a seat for every course in program {}'
.
format
(
self
.
program_uuid
)
return
'Basket contains a seat for every course in program {}'
.
format
(
self
.
program_uuid
)
def
get_program
(
self
,
site_configuration
):
"""
Returns details for the program associated with this condition.
Data is retrieved from the Discovery Service, and cached for ``settings.PROGRAM_CACHE_TIMEOUT`` seconds.
Args:
site_configuration (SiteConfiguration): Configuration containing the requisite parameters
to connect to the Discovery Service.
Returns:
dict
"""
program_uuid
=
str
(
self
.
program_uuid
)
client
=
ProgramsApiClient
(
site_configuration
.
discovery_api_client
,
site_configuration
.
site
.
domain
)
return
client
.
get_program
(
program_uuid
)
def
get_applicable_skus
(
self
,
site_configuration
):
def
get_applicable_skus
(
self
,
site_configuration
):
""" SKUs to which this condition applies. """
""" SKUs to which this condition applies. """
program_course_run_skus
=
set
()
program_course_run_skus
=
set
()
program
=
self
.
get_program
(
site_configuration
)
program
=
get_program
(
self
.
program_uuid
,
site_configuration
)
applicable_seat_types
=
program
[
'applicable_seat_types'
]
applicable_seat_types
=
program
[
'applicable_seat_types'
]
for
course
in
program
[
'courses'
]:
for
course
in
program
[
'courses'
]:
...
@@ -76,7 +59,7 @@ class ProgramCourseRunSeatsCondition(Condition):
...
@@ -76,7 +59,7 @@ class ProgramCourseRunSeatsCondition(Condition):
basket_skus
=
set
([
line
.
stockrecord
.
partner_sku
for
line
in
basket
.
all_lines
()])
basket_skus
=
set
([
line
.
stockrecord
.
partner_sku
for
line
in
basket
.
all_lines
()])
try
:
try
:
program
=
self
.
get_program
(
basket
.
site
.
siteconfiguration
)
program
=
get_program
(
self
.
program_uuid
,
basket
.
site
.
siteconfiguration
)
except
(
HttpNotFoundError
,
SlumberBaseException
,
requests
.
Timeout
):
except
(
HttpNotFoundError
,
SlumberBaseException
,
requests
.
Timeout
):
return
False
return
False
...
...
ecommerce/programs/migrations/0002_add_basket_attribute_type.py
0 → 100644
View file @
58c45e26
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
from
ecommerce.extensions.checkout.signals
import
BUNDLE
def
create_attribute
(
apps
,
schema_editor
):
BasketAttributeType
=
apps
.
get_model
(
'basket'
,
'BasketAttributeType'
)
BasketAttributeType
.
objects
.
create
(
name
=
BUNDLE
)
def
delete_attribute
(
apps
,
schema_editor
):
BasketAttributeType
=
apps
.
get_model
(
'basket'
,
'BasketAttributeType'
)
try
:
BasketAttributeType
.
objects
.
get
(
name
=
BUNDLE
)
.
delete
()
except
BasketAttributeType
.
DoesNotExist
:
pass
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'programs'
,
'0001_initial'
),
(
'basket'
,
'0007_auto_20160907_2040'
)
]
operations
=
[
migrations
.
RunPython
(
create_attribute
,
delete_attribute
)
]
ecommerce/programs/tests/test_conditions.py
View file @
58c45e26
...
@@ -31,19 +31,6 @@ class ProgramCourseRunSeatsConditionTests(ProgramTestMixin, TestCase):
...
@@ -31,19 +31,6 @@ class ProgramCourseRunSeatsConditionTests(ProgramTestMixin, TestCase):
self
.
assertEqual
(
condition
.
name
,
expected
)
self
.
assertEqual
(
condition
.
name
,
expected
)
@httpretty.activate
@httpretty.activate
def
test_get_program
(
self
):
"""
The method should return data from the Discovery Service API.
Data should be cached for subsequent calls.
"""
data
=
self
.
mock_program_detail_endpoint
(
self
.
condition
.
program_uuid
,
self
.
site_configuration
.
discovery_api_url
)
self
.
assertEqual
(
self
.
condition
.
get_program
(
self
.
site
.
siteconfiguration
),
data
)
# The program data should be cached
httpretty
.
disable
()
self
.
assertEqual
(
self
.
condition
.
get_program
(
self
.
site
.
siteconfiguration
),
data
)
@httpretty.activate
def
test_is_satisfied_no_enrollments
(
self
):
def
test_is_satisfied_no_enrollments
(
self
):
""" The method should return True if the basket contains one course run seat corresponding to each
""" The method should return True if the basket contains one course run seat corresponding to each
course in the program. """
course in the program. """
...
@@ -136,7 +123,7 @@ class ProgramCourseRunSeatsConditionTests(ProgramTestMixin, TestCase):
...
@@ -136,7 +123,7 @@ class ProgramCourseRunSeatsConditionTests(ProgramTestMixin, TestCase):
# Verify the user enrollments are cached
# Verify the user enrollments are cached
basket
.
site
.
siteconfiguration
.
enable_partial_program
=
True
basket
.
site
.
siteconfiguration
.
enable_partial_program
=
True
httpretty
.
disable
()
httpretty
.
disable
()
with
mock
.
patch
(
'ecommerce.programs.conditions.
ProgramCourseRunSeatsCondition.
get_program'
,
with
mock
.
patch
(
'ecommerce.programs.conditions.get_program'
,
return_value
=
program
):
return_value
=
program
):
self
.
assertTrue
(
self
.
condition
.
is_satisfied
(
offer
,
basket
))
self
.
assertTrue
(
self
.
condition
.
is_satisfied
(
offer
,
basket
))
...
@@ -147,7 +134,7 @@ class ProgramCourseRunSeatsConditionTests(ProgramTestMixin, TestCase):
...
@@ -147,7 +134,7 @@ class ProgramCourseRunSeatsConditionTests(ProgramTestMixin, TestCase):
basket
=
factories
.
BasketFactory
(
site
=
self
.
site
,
owner
=
factories
.
UserFactory
())
basket
=
factories
.
BasketFactory
(
site
=
self
.
site
,
owner
=
factories
.
UserFactory
())
basket
.
add_product
(
self
.
test_product
)
basket
.
add_product
(
self
.
test_product
)
with
mock
.
patch
(
'ecommerce.programs.conditions.
ProgramCourseRunSeatsCondition.
get_program'
,
with
mock
.
patch
(
'ecommerce.programs.conditions.get_program'
,
side_effect
=
value
):
side_effect
=
value
):
self
.
assertFalse
(
self
.
condition
.
is_satisfied
(
offer
,
basket
))
self
.
assertFalse
(
self
.
condition
.
is_satisfied
(
offer
,
basket
))
...
...
ecommerce/programs/tests/test_utils.py
0 → 100644
View file @
58c45e26
import
uuid
import
httpretty
from
ecommerce.programs.tests.mixins
import
ProgramTestMixin
from
ecommerce.programs.utils
import
get_program
from
ecommerce.tests.testcases
import
TestCase
class
UtilTests
(
ProgramTestMixin
,
TestCase
):
def
setUp
(
self
):
super
(
UtilTests
,
self
)
.
setUp
()
@httpretty.activate
def
test_get_program
(
self
):
"""
The method should return data from the Discovery Service API.
Data should be cached for subsequent calls.
"""
program_uuid
=
uuid
.
uuid4
()
data
=
self
.
mock_program_detail_endpoint
(
program_uuid
,
self
.
site
.
siteconfiguration
.
discovery_api_url
)
self
.
assertEqual
(
get_program
(
program_uuid
,
self
.
site
.
siteconfiguration
),
data
)
# The program data should be cached
httpretty
.
disable
()
self
.
assertEqual
(
get_program
(
program_uuid
,
self
.
site
.
siteconfiguration
),
data
)
ecommerce/programs/utils.py
0 → 100644
View file @
58c45e26
from
ecommerce.programs.api
import
ProgramsApiClient
def
get_program
(
program_uuid
,
siteconfiguration
):
"""
Returns details for the program identified by the program_uuid.
Data is retrieved from the Discovery Service, and cached for ``settings.PROGRAM_CACHE_TIMEOUT`` seconds.
Args:
siteconfiguration (SiteConfiguration): Configuration containing the requisite parameters
to connect to the Discovery Service.
program_uuid (uuid): id to query the specified program
Returns:
dict
"""
client
=
ProgramsApiClient
(
siteconfiguration
.
discovery_api_client
,
siteconfiguration
.
site
.
domain
)
return
client
.
get_program
(
str
(
program_uuid
))
ecommerce/programs/views.py
View file @
58c45e26
...
@@ -7,8 +7,8 @@ from django.views.generic import CreateView, ListView, UpdateView
...
@@ -7,8 +7,8 @@ from django.views.generic import CreateView, ListView, UpdateView
from
oscar.core.loading
import
get_model
from
oscar.core.loading
import
get_model
from
ecommerce.core.views
import
StaffOnlyMixin
from
ecommerce.core.views
import
StaffOnlyMixin
from
ecommerce.programs.api
import
ProgramsApiClient
from
ecommerce.programs.forms
import
ProgramOfferForm
from
ecommerce.programs.forms
import
ProgramOfferForm
from
ecommerce.programs.utils
import
get_program
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
Benefit
=
get_model
(
'offer'
,
'Benefit'
)
ConditionalOffer
=
get_model
(
'offer'
,
'ConditionalOffer'
)
ConditionalOffer
=
get_model
(
'offer'
,
'ConditionalOffer'
)
...
@@ -23,20 +23,6 @@ class ProgramOfferViewMixin(StaffOnlyMixin):
...
@@ -23,20 +23,6 @@ class ProgramOfferViewMixin(StaffOnlyMixin):
context
[
'admin'
]
=
'program_offers'
context
[
'admin'
]
=
'program_offers'
return
context
return
context
def
get_program_details
(
self
,
program_uuid
):
site
=
self
.
request
.
site
details
=
{
'title'
:
'(unknown)'
,
'uuid'
:
program_uuid
,
}
try
:
programs_api_client
=
ProgramsApiClient
(
site
.
siteconfiguration
.
discovery_api_client
,
site
.
domain
)
details
=
programs_api_client
.
get_program
(
program_uuid
)
except
:
# pylint: disable=bare-except
logger
.
exception
(
'Failed to retrieve program [
%
s] from the Programs API!'
,
program_uuid
)
return
details
def
get_queryset
(
self
):
def
get_queryset
(
self
):
return
super
(
ProgramOfferViewMixin
,
self
)
.
get_queryset
()
.
filter
(
return
super
(
ProgramOfferViewMixin
,
self
)
.
get_queryset
()
.
filter
(
condition__program_uuid__isnull
=
False
,
condition__program_uuid__isnull
=
False
,
...
@@ -80,7 +66,8 @@ class ProgramOfferUpdateView(ProgramOfferProcessFormViewMixin, UpdateView):
...
@@ -80,7 +66,8 @@ class ProgramOfferUpdateView(ProgramOfferProcessFormViewMixin, UpdateView):
context
=
super
(
ProgramOfferUpdateView
,
self
)
.
get_context_data
(
**
kwargs
)
context
=
super
(
ProgramOfferUpdateView
,
self
)
.
get_context_data
(
**
kwargs
)
context
.
update
({
context
.
update
({
'editing'
:
True
,
'editing'
:
True
,
'program'
:
self
.
get_program_details
(
self
.
object
.
condition
.
program_uuid
),
'program'
:
get_program
(
self
.
object
.
condition
.
program_uuid
,
self
.
request
.
site
.
siteconfiguration
),
})
})
return
context
return
context
...
@@ -94,7 +81,7 @@ class ProgramOfferListView(ProgramOfferViewMixin, ListView):
...
@@ -94,7 +81,7 @@ class ProgramOfferListView(ProgramOfferViewMixin, ListView):
# TODO: In the future, we should optimize our API calls, pulling the program data in as few calls as possible.
# TODO: In the future, we should optimize our API calls, pulling the program data in as few calls as possible.
offers
=
[]
offers
=
[]
for
offer
in
context
[
'object_list'
]:
for
offer
in
context
[
'object_list'
]:
offer
.
program
=
self
.
get_program_details
(
offer
.
condition
.
program_uuid
)
offer
.
program
=
get_program
(
offer
.
condition
.
program_uuid
,
self
.
request
.
site
.
siteconfiguration
)
offers
.
append
(
offer
)
offers
.
append
(
offer
)
return
context
return
context
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