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
7bf17ab3
Commit
7bf17ab3
authored
Sep 12, 2016
by
PaulWattenberger
Committed by
GitHub
Sep 12, 2016
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #933 from edx/pwattenberger/ecom-5486
ECOM-5486 pass sailthru campaign id to Sailthru
parents
59c9346b
9958ce21
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
303 additions
and
41 deletions
+303
-41
ecommerce/extensions/api/v2/views/baskets.py
+3
-3
ecommerce/extensions/basket/admin.py
+19
-1
ecommerce/extensions/basket/migrations/0007_auto_20160907_2040.py
+42
-0
ecommerce/extensions/basket/models.py
+24
-0
ecommerce/extensions/basket/utils.py
+1
-1
ecommerce/extensions/partner/admin.py
+1
-1
ecommerce/extensions/partner/migrations/0009_partner_enable_sailthru.py
+19
-0
ecommerce/extensions/partner/models.py
+3
-0
ecommerce/sailthru/migrations/0002_add_basket_attribute_type.py
+29
-0
ecommerce/sailthru/signals.py
+70
-29
ecommerce/sailthru/tests/sailthru_tests.py
+92
-6
No files found.
ecommerce/extensions/api/v2/views/baskets.py
View file @
7bf17ab3
...
@@ -187,7 +187,7 @@ class BasketCreateView(EdxOrderPlacementMixin, generics.CreateAPIView):
...
@@ -187,7 +187,7 @@ class BasketCreateView(EdxOrderPlacementMixin, generics.CreateAPIView):
payment_processor
=
get_default_processor_class
()
payment_processor
=
get_default_processor_class
()
try
:
try
:
response_data
=
self
.
_checkout
(
basket
,
payment_processor
())
response_data
=
self
.
_checkout
(
basket
,
payment_processor
()
,
request
)
except
Exception
as
ex
:
# pylint: disable=broad-except
except
Exception
as
ex
:
# pylint: disable=broad-except
basket
.
delete
()
basket
.
delete
()
logger
.
exception
(
'Failed to initiate checkout for Basket [
%
d]. The basket has been deleted.'
,
basket_id
)
logger
.
exception
(
'Failed to initiate checkout for Basket [
%
d]. The basket has been deleted.'
,
basket_id
)
...
@@ -198,7 +198,7 @@ class BasketCreateView(EdxOrderPlacementMixin, generics.CreateAPIView):
...
@@ -198,7 +198,7 @@ class BasketCreateView(EdxOrderPlacementMixin, generics.CreateAPIView):
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
return
Response
(
response_data
,
status
=
status
.
HTTP_200_OK
)
def
_checkout
(
self
,
basket
,
payment_processor
):
def
_checkout
(
self
,
basket
,
payment_processor
,
request
=
None
):
"""Perform checkout operations for the given basket.
"""Perform checkout operations for the given basket.
If the contents of the basket are free, places an order immediately. Otherwise,
If the contents of the basket are free, places an order immediately. Otherwise,
...
@@ -229,7 +229,7 @@ class BasketCreateView(EdxOrderPlacementMixin, generics.CreateAPIView):
...
@@ -229,7 +229,7 @@ class BasketCreateView(EdxOrderPlacementMixin, generics.CreateAPIView):
response_data
=
self
.
_generate_basic_response
(
basket
)
response_data
=
self
.
_generate_basic_response
(
basket
)
if
basket
.
total_excl_tax
==
0
:
if
basket
.
total_excl_tax
==
0
:
order
=
self
.
place_free_order
(
basket
)
order
=
self
.
place_free_order
(
basket
,
request
)
# Note: Our order serializer could be used here, but in an effort to pare down the information
# Note: Our order serializer could be used here, but in an effort to pare down the information
# returned by this endpoint, simply returning the order number will suffice for now.
# returned by this endpoint, simply returning the order number will suffice for now.
...
...
ecommerce/extensions/basket/admin.py
View file @
7bf17ab3
from
oscar.apps.basket.admin
import
*
# noqa pylint: disable=wildcard-import,unused-wildcard-import
from
oscar.apps.basket.admin
import
*
# noqa pylint: disable=wildcard-import,unused-wildcard-import
from
ecommerce.extensions.basket.models
import
BasketAttribute
,
BasketAttributeType
Basket
=
get_model
(
'basket'
,
'basket'
)
Basket
=
get_model
(
'basket'
,
'basket'
)
PaymentProcessorResponse
=
get_model
(
'payment'
,
'PaymentProcessorResponse'
)
PaymentProcessorResponse
=
get_model
(
'payment'
,
'PaymentProcessorResponse'
)
...
@@ -17,13 +19,29 @@ class PaymentProcessorResponseInline(admin.TabularInline):
...
@@ -17,13 +19,29 @@ class PaymentProcessorResponseInline(admin.TabularInline):
return
False
return
False
class
BasketAttributeInLine
(
admin
.
TabularInline
):
model
=
BasketAttribute
readonly_fields
=
(
'id'
,
'attribute_type'
,
'value_text'
,)
extra
=
0
def
has_add_permission
(
self
,
request
):
# Users are not allowed to add BasketAttribute objects
return
False
@admin.register
(
Basket
)
@admin.register
(
Basket
)
class
BasketAdminExtended
(
BasketAdmin
):
class
BasketAdminExtended
(
BasketAdmin
):
raw_id_fields
=
(
'vouchers'
,
)
raw_id_fields
=
(
'vouchers'
,
)
inlines
=
(
LineInline
,
PaymentProcessorResponseInline
,)
inlines
=
(
LineInline
,
PaymentProcessorResponseInline
,
BasketAttributeInLine
,
)
show_full_result_count
=
False
show_full_result_count
=
False
@admin.register
(
Line
)
@admin.register
(
Line
)
class
LineAdminExtended
(
LineAdmin
):
class
LineAdminExtended
(
LineAdmin
):
show_full_result_count
=
False
show_full_result_count
=
False
@admin.register
(
BasketAttributeType
)
class
BasketAttributeTypeAdmin
(
admin
.
ModelAdmin
):
list_display
=
(
'id'
,
'name'
,)
readonly_fields
=
(
'id'
,
'name'
,)
ecommerce/extensions/basket/migrations/0007_auto_20160907_2040.py
0 → 100644
View file @
7bf17ab3
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'basket'
,
'0006_basket_site'
),
]
operations
=
[
migrations
.
CreateModel
(
name
=
'BasketAttribute'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'value_text'
,
models
.
TextField
(
verbose_name
=
'Text Attribute'
)),
],
),
migrations
.
CreateModel
(
name
=
'BasketAttributeType'
,
fields
=
[
(
'id'
,
models
.
AutoField
(
verbose_name
=
'ID'
,
serialize
=
False
,
auto_created
=
True
,
primary_key
=
True
)),
(
'name'
,
models
.
CharField
(
unique
=
True
,
max_length
=
128
,
verbose_name
=
'Name'
)),
],
),
migrations
.
AddField
(
model_name
=
'basketattribute'
,
name
=
'attribute_type'
,
field
=
models
.
ForeignKey
(
verbose_name
=
'Attribute Type'
,
to
=
'basket.BasketAttributeType'
),
),
migrations
.
AddField
(
model_name
=
'basketattribute'
,
name
=
'basket'
,
field
=
models
.
ForeignKey
(
verbose_name
=
'Basket'
,
to
=
'basket.Basket'
),
),
migrations
.
AlterUniqueTogether
(
name
=
'basketattribute'
,
unique_together
=
set
([(
'basket'
,
'attribute_type'
)]),
),
]
ecommerce/extensions/basket/models.py
View file @
7bf17ab3
...
@@ -58,5 +58,29 @@ class Basket(AbstractBasket):
...
@@ -58,5 +58,29 @@ class Basket(AbstractBasket):
num_lines
=
self
.
num_lines
)
num_lines
=
self
.
num_lines
)
class
BasketAttributeType
(
models
.
Model
):
"""
Used to keep attribute types for BasketAttribute
"""
name
=
models
.
CharField
(
_
(
"Name"
),
max_length
=
128
,
unique
=
True
)
def
__unicode__
(
self
):
return
self
.
name
class
BasketAttribute
(
models
.
Model
):
"""
Used to add fields to basket without modifying basket directly. Fields
can be added by defining new types. Currently only supports text fields,
but could be extended
"""
basket
=
models
.
ForeignKey
(
'basket.Basket'
,
verbose_name
=
_
(
"Basket"
))
attribute_type
=
models
.
ForeignKey
(
'basket.BasketAttributeType'
,
verbose_name
=
_
(
"Attribute Type"
))
value_text
=
models
.
TextField
(
_
(
"Text Attribute"
))
class
Meta
(
object
):
unique_together
=
(
'basket'
,
'attribute_type'
)
# noinspection PyUnresolvedReferences
# noinspection PyUnresolvedReferences
from
oscar.apps.basket.models
import
*
# noqa pylint: disable=wildcard-import,unused-wildcard-import,wrong-import-position
from
oscar.apps.basket.models
import
*
# noqa pylint: disable=wildcard-import,unused-wildcard-import,wrong-import-position
ecommerce/extensions/basket/utils.py
View file @
7bf17ab3
...
@@ -53,7 +53,7 @@ def prepare_basket(request, product, voucher=None):
...
@@ -53,7 +53,7 @@ def prepare_basket(request, product, voucher=None):
# Call signal handler to notify listeners that something has been added to the basket
# Call signal handler to notify listeners that something has been added to the basket
basket_addition
=
get_class
(
'basket.signals'
,
'basket_addition'
)
basket_addition
=
get_class
(
'basket.signals'
,
'basket_addition'
)
basket_addition
.
send
(
sender
=
basket_addition
,
product
=
product
,
user
=
request
.
user
,
request
=
request
)
basket_addition
.
send
(
sender
=
basket_addition
,
product
=
product
,
user
=
request
.
user
,
request
=
request
,
basket
=
basket
)
return
basket
return
basket
...
...
ecommerce/extensions/partner/admin.py
View file @
7bf17ab3
...
@@ -56,4 +56,4 @@ class CatalogAdmin(admin.ModelAdmin):
...
@@ -56,4 +56,4 @@ class CatalogAdmin(admin.ModelAdmin):
class
PartnerAdmin
(
admin
.
ModelAdmin
):
class
PartnerAdmin
(
admin
.
ModelAdmin
):
# NOTE: Do not include the users field. The users table will grow so large
# NOTE: Do not include the users field. The users table will grow so large
# as to make the page timeout. Additionally, we don't actually make use of the field.
# as to make the page timeout. Additionally, we don't actually make use of the field.
fields
=
(
'name'
,
'short_code'
,)
fields
=
(
'name'
,
'short_code'
,
'enable_sailthru'
)
ecommerce/extensions/partner/migrations/0009_partner_enable_sailthru.py
0 → 100644
View file @
7bf17ab3
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
,
models
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'partner'
,
'0008_auto_20150914_1057'
),
]
operations
=
[
migrations
.
AddField
(
model_name
=
'partner'
,
name
=
'enable_sailthru'
,
field
=
models
.
BooleanField
(
default
=
True
,
help_text
=
'Report purchases/enrolls to Sailthru.'
,
verbose_name
=
'Enable Sailthru Reporting'
),
),
]
ecommerce/extensions/partner/models.py
View file @
7bf17ab3
...
@@ -12,6 +12,9 @@ class StockRecord(AbstractStockRecord):
...
@@ -12,6 +12,9 @@ class StockRecord(AbstractStockRecord):
class
Partner
(
AbstractPartner
):
class
Partner
(
AbstractPartner
):
# short_code is the unique identifier for the 'Partner'
# short_code is the unique identifier for the 'Partner'
short_code
=
models
.
CharField
(
max_length
=
8
,
unique
=
True
,
null
=
False
,
blank
=
False
)
short_code
=
models
.
CharField
(
max_length
=
8
,
unique
=
True
,
null
=
False
,
blank
=
False
)
enable_sailthru
=
models
.
BooleanField
(
default
=
True
,
verbose_name
=
_
(
'Enable Sailthru Reporting'
),
help_text
=
_
(
'Determines if purchases/enrolls should be reported to Sailthru.'
))
class
Meta
(
object
):
class
Meta
(
object
):
# Model name that will appear in the admin panel
# Model name that will appear in the admin panel
...
...
ecommerce/sailthru/migrations/0002_add_basket_attribute_type.py
0 → 100644
View file @
7bf17ab3
# -*- coding: utf-8 -*-
from
__future__
import
unicode_literals
from
django.db
import
migrations
from
ecommerce.sailthru.signals
import
SAILTHRU_CAMPAIGN
def
create_attribute
(
apps
,
schema_editor
):
BasketAttributeType
=
apps
.
get_model
(
'basket'
,
'BasketAttributeType'
)
BasketAttributeType
.
objects
.
create
(
name
=
SAILTHRU_CAMPAIGN
)
def
delete_attribute
(
apps
,
schema_editor
):
BasketAttributeType
=
apps
.
get_model
(
'basket'
,
'BasketAttributeType'
)
BasketAttributeType
.
objects
.
get
(
name
=
SAILTHRU_CAMPAIGN
)
.
delete
()
class
Migration
(
migrations
.
Migration
):
dependencies
=
[
(
'sailthru'
,
'0001_initial'
),
(
'basket'
,
'0007_auto_20160907_2040'
),
]
operations
=
[
migrations
.
RunPython
(
create_attribute
,
delete_attribute
)
]
\ No newline at end of file
ecommerce/sailthru/signals.py
View file @
7bf17ab3
import
logging
import
logging
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
import
waffle
import
waffle
from
ecommerce_worker.sailthru.v1.tasks
import
update_course_enrollment
from
ecommerce_worker.sailthru.v1.tasks
import
update_course_enrollment
from
ecommerce.
extensions.analytics.utils
import
silence_exceptions
from
ecommerce.
core.constants
import
SEAT_PRODUCT_CLASS_NAME
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.core.url_utils
import
get_lms_url
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.courses.utils
import
mode_for_seat
from
ecommerce.extensions.analytics.utils
import
silence_exceptions
logger
=
logging
.
getLogger
(
__name__
)
logger
=
logging
.
getLogger
(
__name__
)
post_checkout
=
get_class
(
'checkout.signals'
,
'post_checkout'
)
post_checkout
=
get_class
(
'checkout.signals'
,
'post_checkout'
)
basket_addition
=
get_class
(
'basket.signals'
,
'basket_addition'
)
basket_addition
=
get_class
(
'basket.signals'
,
'basket_addition'
)
BasketAttribute
=
get_model
(
'basket'
,
'BasketAttribute'
)
BasketAttributeType
=
get_model
(
'basket'
,
'BasketAttributeType'
)
SAILTHRU_CAMPAIGN
=
'sailthru_bid'
@receiver
(
post_checkout
)
@receiver
(
post_checkout
)
...
@@ -28,10 +32,23 @@ def process_checkout_complete(sender, order=None, user=None, request=None, # py
...
@@ -28,10 +32,23 @@ def process_checkout_complete(sender, order=None, user=None, request=None, # py
if
not
waffle
.
switch_is_active
(
'sailthru_enable'
):
if
not
waffle
.
switch_is_active
(
'sailthru_enable'
):
return
return
partner
=
order
.
site
.
siteconfiguration
.
partner
if
not
partner
.
enable_sailthru
:
return
# get campaign id from cookies, or saved value in basket
message_id
=
None
message_id
=
None
if
request
:
if
request
:
message_id
=
request
.
COOKIES
.
get
(
'sailthru_bid'
)
message_id
=
request
.
COOKIES
.
get
(
'sailthru_bid'
)
if
not
message_id
:
saved_id
=
BasketAttribute
.
objects
.
filter
(
basket
=
order
.
basket
,
attribute_type
=
_get_attribute_type
()
)
if
len
(
saved_id
)
>
0
:
message_id
=
saved_id
[
0
]
.
value_text
# loop through lines in order
# loop through lines in order
# If multi product orders become common it may be worthwhile to pass an array of
# If multi product orders become common it may be worthwhile to pass an array of
# orders to the worker in one call to save overhead, however, that would be difficult
# orders to the worker in one call to save overhead, however, that would be difficult
...
@@ -41,24 +58,26 @@ def process_checkout_complete(sender, order=None, user=None, request=None, # py
...
@@ -41,24 +58,26 @@ def process_checkout_complete(sender, order=None, user=None, request=None, # py
# get product
# get product
product
=
line
.
product
product
=
line
.
product
# get price
# ignore everything except course seats. no support for coupons as of yet
price
=
line
.
line_price_excl_tax
product_class_name
=
product
.
get_product_class
()
.
name
if
product_class_name
==
SEAT_PRODUCT_CLASS_NAME
:
course_id
=
product
.
course_id
price
=
line
.
line_price_excl_tax
# figure out course url
course_id
=
product
.
course_id
course_url
=
_build_course_url
(
course_id
)
# pass event to ecommerce_worker.sailthru.v1.tasks to handle asynchronously
# pass event to ecommerce_worker.sailthru.v1.tasks to handle asynchronously
update_course_enrollment
.
delay
(
order
.
user
.
email
,
course_url
,
False
,
mode_for_seat
(
product
),
update_course_enrollment
.
delay
(
order
.
user
.
email
,
_build_course_url
(
course_id
),
unit_cost
=
price
,
course_id
=
course_id
,
currency
=
order
.
currency
,
False
,
mode_for_seat
(
product
),
site_code
=
order
.
site
.
siteconfiguration
.
partner
.
short_code
,
unit_cost
=
price
,
course_id
=
course_id
,
currency
=
order
.
currency
,
message_id
=
message_id
)
site_code
=
partner
.
short_code
,
message_id
=
message_id
)
@receiver
(
basket_addition
)
@receiver
(
basket_addition
)
@silence_exceptions
(
"Failed to call Sailthru upon basket addition."
)
@silence_exceptions
(
"Failed to call Sailthru upon basket addition."
)
def
process_basket_addition
(
sender
,
product
=
None
,
user
=
None
,
request
=
None
,
**
kwargs
):
# pylint: disable=unused-argument
def
process_basket_addition
(
sender
,
product
=
None
,
user
=
None
,
request
=
None
,
basket
=
None
,
**
kwargs
):
# pylint: disable=unused-argument
"""Tell Sailthru when payment started.
"""Tell Sailthru when payment started.
Arguments:
Arguments:
...
@@ -68,28 +87,50 @@ def process_basket_addition(sender, product=None, user=None, request=None, **kwa
...
@@ -68,28 +87,50 @@ def process_basket_addition(sender, product=None, user=None, request=None, **kwa
if
not
waffle
.
switch_is_active
(
'sailthru_enable'
):
if
not
waffle
.
switch_is_active
(
'sailthru_enable'
):
return
return
course_id
=
product
.
course_id
partner
=
request
.
site
.
siteconfiguration
.
partner
if
not
partner
.
enable_sailthru
:
return
# ignore everything except course seats. no support for coupons as of yet
product_class_name
=
product
.
get_product_class
()
.
name
if
product_class_name
==
SEAT_PRODUCT_CLASS_NAME
:
# figure out course url
course_id
=
product
.
course_id
course_url
=
_build_course_url
(
course_id
)
# get price & currency
stock_record
=
product
.
stockrecords
.
first
()
stock_record
=
product
.
stockrecords
.
first
()
if
stock_record
:
if
stock_record
:
price
=
stock_record
.
price_excl_tax
price
=
stock_record
.
price_excl_tax
currency
=
stock_record
.
price_currency
currency
=
stock_record
.
price_currency
# return if no price, no need to add free items to shopping cart
# return if no price, no need to add free items to shopping cart
if
not
price
:
if
not
price
:
return
return
# pass event to ecommerce_worker.sailthru.v1.tasks to handle asynchronously
# save Sailthru campaign ID, if there is one
update_course_enrollment
.
delay
(
user
.
email
,
course_url
,
True
,
mode_for_seat
(
product
),
message_id
=
request
.
COOKIES
.
get
(
'sailthru_bid'
)
unit_cost
=
price
,
course_id
=
course_id
,
currency
=
currency
,
if
message_id
and
basket
:
site_code
=
request
.
site
.
siteconfiguration
.
partner
.
short_code
,
BasketAttribute
.
objects
.
update_or_create
(
message_id
=
request
.
COOKIES
.
get
(
'sailthru_bid'
))
basket
=
basket
,
attribute_type
=
_get_attribute_type
(),
value_text
=
message_id
)
# pass event to ecommerce_worker.sailthru.v1.tasks to handle asynchronously
update_course_enrollment
.
delay
(
user
.
email
,
_build_course_url
(
course_id
),
True
,
mode_for_seat
(
product
),
unit_cost
=
price
,
course_id
=
course_id
,
currency
=
currency
,
site_code
=
partner
.
short_code
,
message_id
=
message_id
)
def
_build_course_url
(
course_id
):
def
_build_course_url
(
course_id
):
"""Build a course url from a course id and the host"""
"""Build a course url from a course id and the host"""
return
get_lms_url
(
'courses/{}/info'
.
format
(
course_id
))
return
get_lms_url
(
'courses/{}/info'
.
format
(
course_id
))
def
_get_attribute_type
():
""" Read attribute type for Sailthru campaign id"""
try
:
attribute_type
=
BasketAttributeType
.
objects
.
get
(
name
=
SAILTHRU_CAMPAIGN
)
except
BasketAttributeType
.
DoesNotExist
:
attribute_type
=
BasketAttributeType
.
objects
.
create
(
name
=
SAILTHRU_CAMPAIGN
)
return
attribute_type
ecommerce/sailthru/tests/sailthru_tests.py
View file @
7bf17ab3
...
@@ -2,22 +2,26 @@
...
@@ -2,22 +2,26 @@
import
logging
import
logging
from
mock
import
patch
from
mock
import
patch
from
oscar.core.loading
import
get_model
from
oscar.test.factories
import
create_order
from
oscar.test.factories
import
create_order
from
oscar.test.newfactories
import
UserFactory
,
BasketFactory
from
oscar.test.newfactories
import
UserFactory
,
BasketFactory
from
django.test.client
import
RequestFactory
from
django.test.client
import
RequestFactory
from
ecommerce.tests.testcases
import
TestCase
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.core.tests
import
toggle_switch
from
ecommerce.
sailthru.signals
import
process_checkout_complete
,
process_basket_additio
n
from
ecommerce.
coupons.tests.mixins
import
CouponMixi
n
from
ecommerce.courses.models
import
Course
from
ecommerce.courses.models
import
Course
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.extensions.catalogue.tests.mixins
import
CourseCatalogTestMixin
from
ecommerce.sailthru.signals
import
process_checkout_complete
,
process_basket_addition
,
SAILTHRU_CAMPAIGN
from
ecommerce.tests.factories
import
SiteConfigurationFactory
from
ecommerce.tests.testcases
import
TestCase
log
=
logging
.
getLogger
(
__name__
)
log
=
logging
.
getLogger
(
__name__
)
TEST_EMAIL
=
"test@edx.org"
TEST_EMAIL
=
"test@edx.org"
CAMPAIGN_COOKIE
=
"cookie_bid"
class
SailthruTests
(
CourseCatalogTestMixin
,
TestCase
):
class
SailthruTests
(
Cou
ponMixin
,
Cou
rseCatalogTestMixin
,
TestCase
):
"""
"""
Tests for the Sailthru signals class.
Tests for the Sailthru signals class.
"""
"""
...
@@ -26,7 +30,7 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
...
@@ -26,7 +30,7 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
super
(
SailthruTests
,
self
)
.
setUp
()
super
(
SailthruTests
,
self
)
.
setUp
()
self
.
request_factory
=
RequestFactory
()
self
.
request_factory
=
RequestFactory
()
self
.
request
=
self
.
request_factory
.
get
(
"foo"
)
self
.
request
=
self
.
request_factory
.
get
(
"foo"
)
self
.
request
.
COOKIES
[
'sailthru_bid'
]
=
'cookie_bid'
self
.
request
.
COOKIES
[
'sailthru_bid'
]
=
CAMPAIGN_COOKIE
self
.
request
.
site
=
self
.
site
self
.
request
.
site
=
self
.
site
self
.
user
=
UserFactory
.
create
(
username
=
'test'
,
email
=
TEST_EMAIL
)
self
.
user
=
UserFactory
.
create
(
username
=
'test'
,
email
=
TEST_EMAIL
)
...
@@ -50,6 +54,45 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
...
@@ -50,6 +54,45 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
self
.
assertFalse
(
mock_log_error
.
called
)
self
.
assertFalse
(
mock_log_error
.
called
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.update_course_enrollment.delay'
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.update_course_enrollment.delay'
)
@patch
(
'ecommerce.sailthru.signals.logger.error'
)
def
test_just_return_if_partner_not_supported
(
self
,
mock_log_error
,
mock_update_course_enrollment
):
"""
Ensure that calls just return if enable_sailthru turned off for partner
"""
site_configuration
=
SiteConfigurationFactory
(
partner__name
=
'TestX'
)
site_configuration
.
partner
.
enable_sailthru
=
False
self
.
request
.
site
.
siteconfiguration
=
site_configuration
process_basket_addition
(
None
,
request
=
self
.
request
)
self
.
assertFalse
(
mock_update_course_enrollment
.
called
)
self
.
assertFalse
(
mock_log_error
.
called
)
__
,
order
=
self
.
_create_order
(
99
)
order
.
site
.
siteconfiguration
=
site_configuration
process_checkout_complete
(
None
,
order
=
order
)
self
.
assertFalse
(
mock_update_course_enrollment
.
called
)
self
.
assertFalse
(
mock_log_error
.
called
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.update_course_enrollment.delay'
)
@patch
(
'ecommerce.sailthru.signals.logger.error'
)
def
test_just_return_not_course
(
self
,
mock_log_error
,
mock_update_course_enrollment
):
"""
Verify data for coupon-related orders is not sent to Sailthru.
"""
coupon
=
self
.
create_coupon
()
basket
=
BasketFactory
()
basket
.
add_product
(
coupon
,
1
)
process_basket_addition
(
None
,
request
=
self
.
request
,
user
=
self
.
user
,
product
=
coupon
,
basket
=
basket
)
self
.
assertFalse
(
mock_update_course_enrollment
.
called
)
self
.
assertFalse
(
mock_log_error
.
called
)
order
=
create_order
(
number
=
1
,
basket
=
basket
,
user
=
self
.
user
)
process_checkout_complete
(
None
,
order
=
order
,
request
=
None
)
self
.
assertFalse
(
mock_update_course_enrollment
.
called
)
self
.
assertFalse
(
mock_log_error
.
called
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.update_course_enrollment.delay'
)
def
test_process_checkout_complete
(
self
,
mock_update_course_enrollment
):
def
test_process_checkout_complete
(
self
,
mock_update_course_enrollment
):
"""
"""
Test that the process_checkout signal handler properly calls the task routine
Test that the process_checkout signal handler properly calls the task routine
...
@@ -64,7 +107,7 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
...
@@ -64,7 +107,7 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
seat
.
attr
.
certificate_type
,
seat
.
attr
.
certificate_type
,
course_id
=
self
.
course_id
,
course_id
=
self
.
course_id
,
currency
=
order
.
currency
,
currency
=
order
.
currency
,
message_id
=
'cookie_bid'
,
message_id
=
CAMPAIGN_COOKIE
,
site_code
=
'edX'
,
site_code
=
'edX'
,
unit_cost
=
order
.
total_excl_tax
)
unit_cost
=
order
.
total_excl_tax
)
...
@@ -104,7 +147,7 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
...
@@ -104,7 +147,7 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
seat
.
attr
.
certificate_type
,
seat
.
attr
.
certificate_type
,
course_id
=
self
.
course_id
,
course_id
=
self
.
course_id
,
currency
=
order
.
currency
,
currency
=
order
.
currency
,
message_id
=
'cookie_bid'
,
message_id
=
CAMPAIGN_COOKIE
,
site_code
=
'edX'
,
site_code
=
'edX'
,
unit_cost
=
order
.
total_excl_tax
)
unit_cost
=
order
.
total_excl_tax
)
...
@@ -120,6 +163,49 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
...
@@ -120,6 +163,49 @@ class SailthruTests(CourseCatalogTestMixin, TestCase):
product
=
seat
)
product
=
seat
)
self
.
assertFalse
(
mock_update_course_enrollment
.
called
)
self
.
assertFalse
(
mock_update_course_enrollment
.
called
)
@patch
(
'ecommerce_worker.sailthru.v1.tasks.update_course_enrollment.delay'
)
def
test_save_campaign_id
(
self
,
mock_update_course_enrollment
):
"""
Verify the Sailthru campaign ID is saved as a basket attribute.
"""
# force exception in _get_attribute_type for coverage
BasketAttributeType
=
get_model
(
'basket'
,
'BasketAttributeType'
)
try
:
basket_attribute
=
BasketAttributeType
.
objects
.
get
(
name
=
SAILTHRU_CAMPAIGN
)
self
.
assertEqual
(
unicode
(
basket_attribute
),
SAILTHRU_CAMPAIGN
)
basket_attribute
.
delete
()
except
BasketAttributeType
.
DoesNotExist
:
pass
seat
,
order
=
self
.
_create_order
(
99
)
process_basket_addition
(
None
,
request
=
self
.
request
,
user
=
self
.
user
,
product
=
seat
,
basket
=
order
.
basket
)
self
.
assertTrue
(
mock_update_course_enrollment
.
called
)
mock_update_course_enrollment
.
assert_called_with
(
TEST_EMAIL
,
self
.
course_url
,
True
,
seat
.
attr
.
certificate_type
,
course_id
=
self
.
course_id
,
currency
=
order
.
currency
,
message_id
=
CAMPAIGN_COOKIE
,
site_code
=
'edX'
,
unit_cost
=
order
.
total_excl_tax
)
# now call checkout_complete with the same basket to see if campaign id saved and restored
process_checkout_complete
(
None
,
order
=
order
,
request
=
None
)
self
.
assertTrue
(
mock_update_course_enrollment
.
called
)
mock_update_course_enrollment
.
assert_called_with
(
TEST_EMAIL
,
self
.
course_url
,
False
,
seat
.
attr
.
certificate_type
,
course_id
=
self
.
course_id
,
currency
=
order
.
currency
,
message_id
=
CAMPAIGN_COOKIE
,
site_code
=
'edX'
,
unit_cost
=
order
.
total_excl_tax
)
def
_create_order
(
self
,
price
):
def
_create_order
(
self
,
price
):
seat
=
self
.
course
.
create_or_update_seat
(
'verified'
,
False
,
price
,
self
.
partner
,
None
)
seat
=
self
.
course
.
create_or_update_seat
(
'verified'
,
False
,
price
,
self
.
partner
,
None
)
...
...
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