Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
edx-platform
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
edx-platform
Commits
dc46170f
Commit
dc46170f
authored
Jul 23, 2014
by
asadiqbal08
Committed by
Chris Dodge
Aug 05, 2014
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
registration code checkout flow
Redirecting to dashboard after free user enrollment.
parent
878eaa9f
Show whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
384 additions
and
64 deletions
+384
-64
lms/djangoapps/instructor/tests/test_api.py
+1
-1
lms/djangoapps/shoppingcart/exceptions.py
+8
-0
lms/djangoapps/shoppingcart/models.py
+51
-1
lms/djangoapps/shoppingcart/tests/test_views.py
+188
-13
lms/djangoapps/shoppingcart/urls.py
+2
-1
lms/djangoapps/shoppingcart/views.py
+88
-21
lms/static/sass/views/_shoppingcart.scss
+12
-11
lms/templates/shoppingcart/list.html
+31
-15
lms/templates/shoppingcart/receipt.html
+3
-1
No files found.
lms/djangoapps/instructor/tests/test_api.py
View file @
dc46170f
...
...
@@ -1368,7 +1368,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
url
=
reverse
(
'get_purchase_transaction'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
# using coupon code
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
response
=
self
.
client
.
get
(
url
,
{})
...
...
lms/djangoapps/shoppingcart/exceptions.py
View file @
dc46170f
...
...
@@ -40,6 +40,14 @@ class ItemDoesNotExistAgainstCouponException(InvalidCartItem):
pass
class
RegCodeAlreadyExistException
(
InvalidCartItem
):
pass
class
ItemDoesNotExistAgainstRegCodeException
(
InvalidCartItem
):
pass
class
ReportException
(
Exception
):
pass
...
...
lms/djangoapps/shoppingcart/models.py
View file @
dc46170f
...
...
@@ -31,7 +31,9 @@ from xmodule_django.models import CourseKeyField
from
verify_student.models
import
SoftwareSecurePhotoVerification
from
.exceptions
import
(
InvalidCartItem
,
PurchasedCallbackException
,
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
CouponAlreadyExistException
,
ItemDoesNotExistAgainstCouponException
)
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
CouponAlreadyExistException
,
ItemDoesNotExistAgainstCouponException
,
RegCodeAlreadyExistException
,
ItemDoesNotExistAgainstRegCodeException
)
from
microsite_configuration
import
microsite
...
...
@@ -326,6 +328,25 @@ class CourseRegistrationCode(models.Model):
created_by
=
models
.
ForeignKey
(
User
,
related_name
=
'created_by_user'
)
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
@classmethod
@transaction.commit_on_success
def
free_user_enrollment
(
cls
,
cart
):
"""
Here we enroll the user free for all courses available in shopping cart
"""
cart_items
=
cart
.
orderitem_set
.
all
()
.
select_subclasses
()
if
cart_items
:
for
item
in
cart_items
:
CourseEnrollment
.
enroll
(
cart
.
user
,
item
.
course_id
)
log
.
info
(
"Enrolled '{0}' in free course '{1}'"
.
format
(
cart
.
user
.
email
,
item
.
course_id
))
# pylint: disable=E1101
item
.
status
=
'purchased'
item
.
save
()
cart
.
status
=
'purchased'
cart
.
purchase_time
=
datetime
.
now
(
pytz
.
utc
)
cart
.
save
()
class
RegistrationCodeRedemption
(
models
.
Model
):
"""
...
...
@@ -336,6 +357,35 @@ class RegistrationCodeRedemption(models.Model):
redeemed_by
=
models
.
ForeignKey
(
User
,
db_index
=
True
)
redeemed_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
),
null
=
True
)
@classmethod
def
add_reg_code_redemption
(
cls
,
course_reg_code
,
order
):
"""
add course registration code info into RegistrationCodeRedemption model
"""
cart_items
=
order
.
orderitem_set
.
all
()
.
select_subclasses
()
for
item
in
cart_items
:
if
getattr
(
item
,
'course_id'
):
if
item
.
course_id
==
course_reg_code
.
course_id
:
# If another account tries to use a existing registration code before the student checks out, an
# error message will appear.The reg code is un-reusable.
code_redemption
=
cls
.
objects
.
filter
(
registration_code
=
course_reg_code
)
if
code_redemption
:
log
.
exception
(
"Registration code '{0}' already used"
.
format
(
course_reg_code
.
code
))
raise
RegCodeAlreadyExistException
code_redemption
=
RegistrationCodeRedemption
(
registration_code
=
course_reg_code
,
order
=
order
,
redeemed_by
=
order
.
user
)
code_redemption
.
save
()
item
.
list_price
=
item
.
unit_cost
item
.
unit_cost
=
0
item
.
save
()
log
.
info
(
"Code '{0}' is used by user {1} against order id '{2}' "
.
format
(
course_reg_code
.
code
,
order
.
user
.
username
,
order
.
id
))
return
course_reg_code
log
.
warning
(
"Course item does not exist against registration code '{0}'"
.
format
(
course_reg_code
.
code
))
raise
ItemDoesNotExistAgainstRegCodeException
class
SoftDeleteCouponManager
(
models
.
Manager
):
""" Use this manager to get objects that have a is_active=True """
...
...
lms/djangoapps/shoppingcart/tests/test_views.py
View file @
dc46170f
...
...
@@ -17,7 +17,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
from
shoppingcart.views
import
_can_download_report
,
_get_date_from_str
from
shoppingcart.models
import
Order
,
CertificateItem
,
PaidCourseRegistration
,
Coupon
from
shoppingcart.models
import
Order
,
CertificateItem
,
PaidCourseRegistration
,
Coupon
,
CourseRegistrationCode
from
student.tests.factories
import
UserFactory
,
AdminFactory
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
...
...
@@ -53,6 +53,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
instructor
=
AdminFactory
.
create
()
self
.
cost
=
40
self
.
coupon_code
=
'abcde'
self
.
reg_code
=
'qwerty'
self
.
percentage_discount
=
10
self
.
course
=
CourseFactory
.
create
(
org
=
'MITx'
,
number
=
'999'
,
display_name
=
'Robot Super Course'
)
self
.
course_key
=
self
.
course
.
id
...
...
@@ -61,6 +62,16 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
mode_display_name
=
"honor cert"
,
min_price
=
self
.
cost
)
self
.
course_mode
.
save
()
#Saving another testing course mode
self
.
testing_cost
=
20
self
.
testing_course
=
CourseFactory
.
create
(
org
=
'edX'
,
number
=
'888'
,
display_name
=
'Testing Super Course'
)
self
.
testing_course_mode
=
CourseMode
(
course_id
=
self
.
testing_course
.
id
,
mode_slug
=
"honor"
,
mode_display_name
=
"testing honor cert"
,
min_price
=
self
.
testing_cost
)
self
.
testing_course_mode
.
save
()
verified_course
=
CourseFactory
.
create
(
org
=
'org'
,
number
=
'test'
,
display_name
=
'Test Course'
)
self
.
verified_course_key
=
verified_course
.
id
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
...
...
@@ -81,6 +92,14 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
percentage_discount
=
self
.
percentage_discount
,
created_by
=
self
.
user
,
is_active
=
is_active
)
coupon
.
save
()
def
add_reg_code
(
self
,
course_key
):
"""
add dummy registration code into models
"""
course_reg_code
=
CourseRegistrationCode
(
code
=
self
.
reg_code
,
course_id
=
course_key
,
transaction_group_name
=
'A'
,
created_by
=
self
.
user
)
course_reg_code
.
save
()
def
add_course_to_user_cart
(
self
):
"""
adding course to user cart
...
...
@@ -107,14 +126,22 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
add_coupon
(
self
.
course_key
,
True
)
self
.
add_course_to_user_cart
()
non_existing_code
=
"non_existing_code"
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
non_existing_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
non_existing_code
})
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Discount does not exist against code '{0}'."
.
format
(
non_existing_code
),
resp
.
content
)
def
test_course_discount_invalid_reg_code
(
self
):
self
.
add_reg_code
(
self
.
course_key
)
self
.
add_course_to_user_cart
()
non_existing_code
=
"non_existing_code"
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
non_existing_code
})
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Discount does not exist against co
upon
'{0}'."
.
format
(
non_existing_code
),
resp
.
content
)
self
.
assertIn
(
"Discount does not exist against co
de
'{0}'."
.
format
(
non_existing_code
),
resp
.
content
)
def
test_course_discount_inactive_coupon
(
self
):
self
.
add_coupon
(
self
.
course_key
,
False
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
400
)
self
.
assertIn
(
"Coupon '{0}' is inactive."
.
format
(
self
.
coupon_code
),
resp
.
content
)
...
...
@@ -123,16 +150,25 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
add_coupon
(
course_key
,
True
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Coupon '{0}' is not valid for any course in the shopping cart."
.
format
(
self
.
coupon_code
),
resp
.
content
)
def
test_course_does_not_exist_in_cart_against_valid_reg_code
(
self
):
course_key
=
self
.
course_key
.
to_deprecated_string
()
+
'testing'
self
.
add_reg_code
(
course_key
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Code '{0}' is not valid for any course in the shopping cart."
.
format
(
self
.
reg_code
),
resp
.
content
)
def
test_course_discount_for_valid_active_coupon_code
(
self
):
self
.
add_coupon
(
self
.
course_key
,
True
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
# unit price should be updated for that course
...
...
@@ -143,7 +179,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertEqual
(
self
.
cart
.
total_cost
,
self
.
get_discount
())
# now testing coupon code already used scenario, reusing the same coupon code
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
400
)
self
.
assertIn
(
"Coupon '{0}' already used."
.
format
(
self
.
coupon_code
),
resp
.
content
)
...
...
@@ -180,6 +216,24 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
for
coupon
in
test_query_set
:
self
.
assertEqual
(
coupon
.
is_active
,
False
)
def
test_course_free_discount_for_valid_active_reg_code
(
self
):
self
.
add_reg_code
(
self
.
course_key
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
# unit price should be updated to 0 for that course
item
=
self
.
cart
.
orderitem_set
.
all
()
.
select_subclasses
()[
0
]
self
.
assertEquals
(
item
.
unit_cost
,
0
)
self
.
assertEqual
(
self
.
cart
.
total_cost
,
0
)
# now testing registration code already used scenario, reusing the same code
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
400
)
self
.
assertIn
(
"Oops! The code '{0}' you entered is either invalid or expired"
.
format
(
self
.
reg_code
),
resp
.
content
)
@patch
(
'shoppingcart.views.log.debug'
)
def
test_non_existing_coupon_redemption_on_removing_item
(
self
,
debug_log
):
...
...
@@ -187,7 +241,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
reg_item
.
id
})
debug_log
.
assert_called_with
(
'Co
upon
redemption does not exist for order item id={0}.'
.
format
(
reg_item
.
id
))
'Co
de
redemption does not exist for order item id={0}.'
.
format
(
reg_item
.
id
))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
0
)
...
...
@@ -198,7 +252,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
add_coupon
(
self
.
course_key
,
True
)
reg_item
=
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
...
...
@@ -210,6 +264,23 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
self
.
coupon_code
,
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_existing_reg_code_redemption_on_removing_item
(
self
,
info_log
):
self
.
add_reg_code
(
self
.
course_key
)
reg_item
=
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
reg_item
.
id
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
0
)
info_log
.
assert_called_with
(
'Registration code "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
self
.
reg_code
,
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_coupon_discount_for_multiple_courses_in_cart
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
...
...
@@ -217,7 +288,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
cert_item
=
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
# unit_cost should be updated for that particular course for which coupon code is registered
...
...
@@ -238,6 +309,34 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
self
.
coupon_code
,
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_reg_code_free_discount_with_multiple_courses_in_cart
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
self
.
add_reg_code
(
self
.
course_key
)
cert_item
=
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
# unit_cost should be 0 for that particular course for which registration code is registered
items
=
self
.
cart
.
orderitem_set
.
all
()
.
select_subclasses
()
for
item
in
items
:
if
item
.
id
==
reg_item
.
id
:
self
.
assertEquals
(
item
.
unit_cost
,
0
)
elif
item
.
id
==
cert_item
.
id
:
self
.
assertEquals
(
item
.
list_price
,
None
)
# Delete the discounted item, corresponding reg code redemption should be removed for that particular item
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
reg_item
.
id
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
1
)
info_log
.
assert_called_with
(
'Registration code "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
self
.
reg_code
,
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_delete_certificate_item
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
...
...
@@ -261,7 +360,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
self
.
add_coupon
(
self
.
course_key
,
True
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.clear_cart'
,
args
=
[]))
...
...
@@ -271,6 +370,24 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
info_log
.
assert_called_with
(
'Coupon redemption entry removed for user {0} for order {1}'
.
format
(
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_remove_registration_code_redemption_on_clear_cart
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
self
.
add_reg_code
(
self
.
course_key
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.clear_cart'
,
args
=
[]))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
0
)
info_log
.
assert_called_with
(
'Registration code redemption entry removed for user {0} for order {1}'
.
format
(
self
.
user
,
reg_item
.
id
))
def
test_add_course_to_cart_already_registered
(
self
):
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course_key
)
self
.
login_user
()
...
...
@@ -391,7 +508,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
add_course_to_user_cart
()
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
1
)
self
.
add_coupon
(
self
.
course_key
,
True
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
...
...
@@ -413,7 +530,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
add_course_to_user_cart
()
self
.
add_coupon
(
self
.
course_key
,
True
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
upon'
),
{
'coupon_
code'
:
self
.
coupon_code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_co
de'
),
{
'
code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
...
...
@@ -423,6 +540,64 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertIn
(
str
(
self
.
get_discount
()),
resp
.
content
)
@patch
(
'shoppingcart.views.render_to_response'
,
render_mock
)
def
test_reg_code_and_course_registration_scenario
(
self
):
self
.
add_reg_code
(
self
.
course_key
)
# One courses in user shopping cart
self
.
add_course_to_user_cart
()
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
1
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.show_cart'
,
args
=
[]))
self
.
assertIn
(
'Register'
,
resp
.
content
)
# freely enroll the user into course
resp
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.register_courses'
))
self
.
assertIn
(
'success'
,
resp
.
content
)
@patch
(
'shoppingcart.views.render_to_response'
,
render_mock
)
def
test_reg_code_with_multiple_courses_and_checkout_scenario
(
self
):
self
.
add_reg_code
(
self
.
course_key
)
# Two courses in user shopping cart
self
.
login_user
()
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course_key
)
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
testing_course
.
id
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.show_cart'
,
args
=
[]))
self
.
assertIn
(
'Check Out'
,
resp
.
content
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
resp
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.show_receipt'
,
args
=
[
self
.
cart
.
id
]))
self
.
assertEqual
(
resp
.
status_code
,
200
)
((
template
,
context
),
_
)
=
render_mock
.
call_args
# pylint: disable=W0621
self
.
assertEqual
(
template
,
'shoppingcart/receipt.html'
)
self
.
assertEqual
(
context
[
'order'
],
self
.
cart
)
self
.
assertEqual
(
context
[
'order'
]
.
total_cost
,
self
.
testing_cost
)
course_enrollment
=
CourseEnrollment
.
objects
.
filter
(
user
=
self
.
user
)
self
.
assertEqual
(
course_enrollment
.
count
(),
2
)
@patch
(
'shoppingcart.views.render_to_response'
,
render_mock
)
def
test_show_receipt_success_with_valid_reg_code
(
self
):
self
.
add_course_to_user_cart
()
self
.
add_reg_code
(
self
.
course_key
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
self
.
reg_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
resp
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.show_receipt'
,
args
=
[
self
.
cart
.
id
]))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertIn
(
'0.00'
,
resp
.
content
)
@patch
(
'shoppingcart.views.render_to_response'
,
render_mock
)
def
test_show_receipt_success
(
self
):
reg_item
=
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course_key
)
cert_item
=
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
...
...
lms/djangoapps/shoppingcart/urls.py
View file @
dc46170f
...
...
@@ -14,7 +14,8 @@ if settings.FEATURES['ENABLE_SHOPPING_CART']:
url
(
r'^clear/$'
,
'clear_cart'
),
url
(
r'^remove_item/$'
,
'remove_item'
),
url
(
r'^add/course/{}/$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'add_course_to_cart'
,
name
=
'add_course_to_cart'
),
url
(
r'^use_coupon/$'
,
'use_coupon'
),
url
(
r'^use_code/$'
,
'use_code'
),
url
(
r'^register_courses/$'
,
'register_courses'
),
)
if
settings
.
FEATURES
.
get
(
'ENABLE_PAYMENT_FAKE'
):
...
...
lms/djangoapps/shoppingcart/views.py
View file @
dc46170f
...
...
@@ -14,8 +14,9 @@ from edxmako.shortcuts import render_to_response
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
student.models
import
CourseEnrollment
from
.exceptions
import
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
CouponAlreadyExistException
,
ItemDoesNotExistAgainstCouponException
from
.models
import
Order
,
PaidCourseRegistration
,
OrderItem
,
Coupon
,
CouponRedemption
from
.exceptions
import
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
\
CouponAlreadyExistException
,
ItemDoesNotExistAgainstCouponException
,
RegCodeAlreadyExistException
,
ItemDoesNotExistAgainstRegCodeException
from
.models
import
Order
,
PaidCourseRegistration
,
OrderItem
,
Coupon
,
CouponRedemption
,
CourseRegistrationCode
,
RegistrationCodeRedemption
from
.processors
import
process_postpay_callback
,
render_purchase_form_html
import
json
...
...
@@ -97,6 +98,11 @@ def clear_cart(request):
coupon_redemption
.
delete
()
log
.
info
(
'Coupon redemption entry removed for user {0} for order {1}'
.
format
(
request
.
user
,
cart
.
id
))
reg_code_redemption
=
RegistrationCodeRedemption
.
objects
.
filter
(
redeemed_by
=
request
.
user
,
order
=
cart
.
id
)
if
reg_code_redemption
:
reg_code_redemption
.
delete
()
log
.
info
(
'Registration code redemption entry removed for user {0} for order {1}'
.
format
(
request
.
user
,
cart
.
id
))
return
HttpResponse
(
'Cleared'
)
...
...
@@ -111,43 +117,104 @@ def remove_item(request):
order_item_course_id
=
item
.
paidcourseregistration
.
course_id
item
.
delete
()
log
.
info
(
'order item {0} removed for user {1}'
.
format
(
item_id
,
request
.
user
))
try
:
coupon_redemption
=
CouponRedemption
.
objects
.
get
(
user
=
request
.
user
,
order
=
item
.
order_id
)
if
order_item_course_id
==
coupon_redemption
.
coupon
.
course_id
:
coupon_redemption
.
delete
()
log
.
info
(
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
coupon_redemption
.
coupon
.
code
,
request
.
user
,
item_id
))
except
CouponRedemption
.
DoesNotExist
:
log
.
debug
(
'Coupon redemption does not exist for order item id={0}.'
.
format
(
item_id
))
remove_code_redemption
(
order_item_course_id
,
item_id
,
item
,
request
.
user
)
except
OrderItem
.
DoesNotExist
:
log
.
exception
(
'Cannot remove cart OrderItem id={0}. DoesNotExist or item is already purchased'
.
format
(
item_id
))
return
HttpResponse
(
'OK'
)
def
remove_code_redemption
(
order_item_course_id
,
item_id
,
item
,
user
):
"""
If an item removed from shopping cart then we will remove
the corresponding redemption info of coupon/registration code.
"""
try
:
# Try to remove redemption information of coupon code, If exist.
coupon_redemption
=
CouponRedemption
.
objects
.
get
(
user
=
user
,
order
=
item
.
order_id
)
except
CouponRedemption
.
DoesNotExist
:
try
:
# Try to remove redemption information of registration code, If exist.
reg_code_redemption
=
RegistrationCodeRedemption
.
objects
.
get
(
redeemed_by
=
user
,
order
=
item
.
order_id
)
except
RegistrationCodeRedemption
.
DoesNotExist
:
log
.
debug
(
'Code redemption does not exist for order item id={0}.'
.
format
(
item_id
))
else
:
if
order_item_course_id
==
reg_code_redemption
.
registration_code
.
course_id
:
reg_code_redemption
.
delete
()
log
.
info
(
'Registration code "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
reg_code_redemption
.
registration_code
.
code
,
user
,
item_id
))
else
:
if
order_item_course_id
==
coupon_redemption
.
coupon
.
course_id
:
coupon_redemption
.
delete
()
log
.
info
(
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
coupon_redemption
.
coupon
.
code
,
user
,
item_id
))
@login_required
def
use_co
upon
(
request
):
def
use_co
de
(
request
):
"""
This method generate discount against valid coupon code and save its entry into coupon redemption table
This method may generate the discount against valid coupon code
and save its entry into coupon redemption table
OR
Make the cart item free of cost against valid registration code.
Valid Code can be either coupon or registration code.
"""
co
upon_code
=
request
.
POST
[
"coupon_
code"
]
co
de
=
request
.
POST
[
"
code"
]
try
:
coupon
=
Coupon
.
objects
.
get
(
code
=
co
upon_co
de
)
coupon
=
Coupon
.
objects
.
get
(
code
=
code
)
except
Coupon
.
DoesNotExist
:
return
HttpResponseNotFound
(
_
(
"Discount does not exist against coupon '{0}'."
.
format
(
coupon_code
)))
# If not coupon code then we check that code against course registration code
try
:
course_reg
=
CourseRegistrationCode
.
objects
.
get
(
code
=
code
)
except
CourseRegistrationCode
.
DoesNotExist
:
return
HttpResponseNotFound
(
_
(
"Discount does not exist against code '{0}'."
.
format
(
code
)))
return
use_registration_code
(
course_reg
,
request
.
user
)
return
use_coupon_code
(
coupon
,
request
.
user
)
def
use_registration_code
(
course_reg
,
user
):
"""
This method utilize course registration code
"""
try
:
cart
=
Order
.
get_cart_for_user
(
user
)
RegistrationCodeRedemption
.
add_reg_code_redemption
(
course_reg
,
cart
)
except
RegCodeAlreadyExistException
:
return
HttpResponseBadRequest
(
_
(
"Oops! The code '{0}' you entered is either invalid or expired"
.
format
(
course_reg
.
code
)))
except
ItemDoesNotExistAgainstRegCodeException
:
return
HttpResponseNotFound
(
_
(
"Code '{0}' is not valid for any course in the shopping cart."
.
format
(
course_reg
.
code
)))
return
HttpResponse
(
json
.
dumps
({
'response'
:
'success'
}),
content_type
=
"application/json"
)
def
use_coupon_code
(
coupon
,
user
):
"""
This method utilize course coupon code
"""
if
coupon
.
is_active
:
try
:
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
cart
=
Order
.
get_cart_for_user
(
user
)
CouponRedemption
.
add_coupon_redemption
(
coupon
,
cart
)
except
CouponAlreadyExistException
:
return
HttpResponseBadRequest
(
_
(
"Coupon '{0}' already used."
.
format
(
coupon
_
code
)))
return
HttpResponseBadRequest
(
_
(
"Coupon '{0}' already used."
.
format
(
coupon
.
code
)))
except
ItemDoesNotExistAgainstCouponException
:
return
HttpResponseNotFound
(
_
(
"Coupon '{0}' is not valid for any course in the shopping cart."
.
format
(
coupon
_
code
)))
return
HttpResponseNotFound
(
_
(
"Coupon '{0}' is not valid for any course in the shopping cart."
.
format
(
coupon
.
code
)))
response
=
HttpResponse
(
json
.
dumps
({
'response'
:
'success'
}),
content_type
=
"application/json"
)
return
response
return
HttpResponse
(
json
.
dumps
({
'response'
:
'success'
}),
content_type
=
"application/json"
)
else
:
return
HttpResponseBadRequest
(
_
(
"Coupon '{0}' is inactive."
.
format
(
coupon_code
)))
return
HttpResponseBadRequest
(
_
(
"Coupon '{0}' is inactive."
.
format
(
coupon
.
code
)))
@login_required
def
register_courses
(
request
):
"""
This method enroll the user for available course(s)
in cart on which valid registration code is applied
"""
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
CourseRegistrationCode
.
free_user_enrollment
(
cart
)
return
HttpResponse
(
json
.
dumps
({
'response'
:
'success'
}),
content_type
=
"application/json"
)
@csrf_exempt
...
...
lms/static/sass/views/_shoppingcart.scss
View file @
dc46170f
...
...
@@ -63,10 +63,6 @@
height
:
35px
;
border-bottom
:
1px
solid
#BEBEBE
;
th
:nth-child
(
5
),
th
:first-child
{
text-align
:
center
;
width
:
120px
;
}
th
{
text-align
:
left
;
border-bottom
:
1px
solid
$border-color-1
;
...
...
@@ -75,17 +71,19 @@
width
:
100px
;
}
&
.u-pr
{
width
:
10
0px
;
width
:
7
0px
;
}
&
.prc
{
width
:
150px
;
}
&
.cur
{
width
:
100px
;
text-align
:
center
;
}
&
.dsc
{
width
:
640px
;
padding-right
:
50px
;
text-align
:
left
;
}
}
}
...
...
@@ -96,22 +94,25 @@
position
:
relative
;
line-height
:
normal
;
span
.old-price
{
left
:
-75px
;
position
:
relative
;
text-decoration
:
line-through
;
color
:
red
;
font-size
:
12px
;
top
:
-1px
;
margin-left
:
3px
;
}
}
td
:nth-child
(
5
),
td
:first-child
{
td
:nth-child
(
3
)
{
text-align
:
center
;
}
td
:last-child
{
width
:
50px
;
text-align
:
center
;
}
td
:nth-child
(
2
)
{
td
:nth-child
(
1
)
{
line-height
:
22px
;
padding-right
:
50px
;
text-align
:
left
;
padding-right
:
20px
;
}
}
...
...
lms/templates/shoppingcart/list.html
View file @
dc46170f
...
...
@@ -13,17 +13,15 @@
<table
class=
"cart-table"
>
<thead>
<tr
class=
"cart-headings"
>
<th
class=
"qty"
>
${_("Quantity")}
</th>
<th
class=
"dsc"
>
${_("Description")}
</th>
<th
class=
"u-pr"
>
${_("Unit Price")}
</th>
<th
class=
"prc"
>
${_("Price")}
</th>
<th
class=
"u-pr"
>
${_("Price")}
</th>
<th
class=
"cur"
>
${_("Currency")}
</th>
<th>
</th>
</tr>
</thead>
<tbody>
% for item in shoppingcart_items:
<tr
class=
"cart-items"
>
<td>
${item.qty}
</td>
<td>
${item.line_desc}
</td>
<td>
${"{0:0.2f}".format(item.unit_cost)}
...
...
@@ -31,14 +29,12 @@
<span
class=
"old-price"
>
${"{0:0.2f}".format(item.list_price)}
</span>
% endif
</td>
<td>
${"{0:0.2f}".format(item.line_cost)}
</td>
<td>
${item.currency.upper()}
</td>
<td><a
data-item-id=
"${item.id}"
class=
'remove_line_item'
href=
'#'
>
[x]
</a></td>
</tr>
% endfor
<tr
class=
"always-gray"
>
<td
colspan=
"3"
></td>
<td
colspan=
"3"
valign=
"middle"
class=
"cart-total"
align=
"right"
>
<td
colspan=
"4"
valign=
"middle"
class=
"cart-total"
align=
"right"
>
<b>
${_("Total Amount")}:
<span>
${"{0:0.2f}".format(amount)}
</span>
</b>
</td>
</tr>
...
...
@@ -47,11 +43,15 @@
<tfoot>
<tr
class=
"always-white"
>
<td
colspan=
"2"
>
<input
type=
"text"
placeholder=
"Enter co
upon code here"
name=
"coupon_code"
id=
"couponC
ode"
>
<input
type=
"button"
value=
"
Use Coupon"
id=
"cart-coupon
"
>
<input
type=
"text"
placeholder=
"Enter co
de here"
name=
"cart_code"
id=
"c
ode"
>
<input
type=
"button"
value=
"
Apply Code"
id=
"cart-code
"
>
</td>
<td
colspan=
"4"
align=
"right"
>
% if amount == 0:
<input
type=
"button"
value =
"Register"
id=
"register"
>
% else:
${form_html}
%endif
</td>
</tr>
...
...
@@ -76,14 +76,14 @@
});
});
$
(
'#cart-co
upon
'
).
click
(
function
(
event
){
$
(
'#cart-co
de
'
).
click
(
function
(
event
){
event
.
preventDefault
();
var
post_url
=
"${reverse('shoppingcart.views.use_co
upon
')}"
;
var
post_url
=
"${reverse('shoppingcart.views.use_co
de
')}"
;
$
.
post
(
post_url
,{
"co
upon_code"
:
$
(
'#couponC
ode'
).
val
(),
"co
de"
:
$
(
'#c
ode'
).
val
(),
beforeSend
:
function
(
xhr
,
options
){
if
(
$
(
'#co
uponCo
de'
).
val
()
==
""
)
{
showErrorMsgs
(
'Must
contain a valid coupon
code'
)
if
(
$
(
'#code'
).
val
()
==
""
)
{
showErrorMsgs
(
'Must
enter a valid
code'
)
xhr
.
abort
();
}
}
...
...
@@ -101,6 +101,22 @@
})
});
$
(
'#register'
).
click
(
function
(
event
){
event
.
preventDefault
();
var
post_url
=
"${reverse('shoppingcart.views.register_courses')}"
;
$
.
post
(
post_url
)
.
success
(
function
(
data
)
{
window
.
location
.
href
=
"${reverse('dashboard')}"
;
})
.
error
(
function
(
data
,
status
)
{
if
(
status
==
"parsererror"
){
location
.
reload
(
true
);
}
else
{
showErrorMsgs
(
data
.
responseText
)
}
})
});
$
(
'#back_input'
).
click
(
function
(){
history
.
back
();
});
...
...
@@ -111,4 +127,3 @@
}
});
</script>
\ No newline at end of file
lms/templates/shoppingcart/receipt.html
View file @
dc46170f
...
...
@@ -86,7 +86,7 @@
${_("Note: items with strikethough like
<del>
this
</del>
have been refunded.")}
</p>
% endif
% if order.total_cost > 0:
<h2>
${_("Billed To:")}
</h2>
<p>
${order.bill_to_cardtype} ${_("#:")} ${order.bill_to_ccnum}
<br
/>
...
...
@@ -96,6 +96,8 @@
${order.bill_to_city}, ${order.bill_to_state} ${order.bill_to_postalcode}
<br
/>
${order.bill_to_country.upper()}
<br
/>
</p>
% endif
</article>
</div>
</section>
</div>
...
...
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