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
5e58b69d
Commit
5e58b69d
authored
Apr 21, 2016
by
Marko Jevtic
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[SOL-1589] Create Coupon for Multiple Courses UI
parent
8bd2ab94
Hide whitespace changes
Inline
Side-by-side
Showing
25 changed files
with
698 additions
and
129 deletions
+698
-129
ecommerce/coupons/utils.py
+13
-0
ecommerce/extensions/api/constants.py
+0
-2
ecommerce/extensions/api/serializers.py
+2
-1
ecommerce/extensions/api/v2/tests/views/test_coupons.py
+3
-11
ecommerce/extensions/api/v2/views/coupons.py
+37
-14
ecommerce/extensions/voucher/tests/test_utils.py
+1
-1
ecommerce/extensions/voucher/utils.py
+3
-2
ecommerce/static/js/models/coupon_model.js
+53
-13
ecommerce/static/js/pages/coupon_detail_page.js
+1
-1
ecommerce/static/js/pages/page.js
+5
-0
ecommerce/static/js/routers/page_router.js
+3
-2
ecommerce/static/js/test/specs/models/coupon_model_spec.js
+57
-4
ecommerce/static/js/test/specs/pages/page_spec.js
+26
-0
ecommerce/static/js/test/specs/views/coupon_detail_view_spec.js
+34
-42
ecommerce/static/js/test/specs/views/coupon_edit_view_spec.js
+2
-2
ecommerce/static/js/test/specs/views/coupon_form_view_spec.js
+15
-1
ecommerce/static/js/test/specs/views/dynamic_catalog_view_spec.js
+118
-0
ecommerce/static/js/views/coupon_detail_view.js
+38
-21
ecommerce/static/js/views/coupon_form_view.js
+57
-3
ecommerce/static/js/views/dynamic_catalog_view.js
+93
-0
ecommerce/static/js/views/form_view.js
+3
-3
ecommerce/static/sass/partials/views/_coupon_admin.scss
+81
-2
ecommerce/static/templates/coupon_detail.html
+10
-4
ecommerce/static/templates/coupon_form.html
+25
-0
ecommerce/static/templates/dynamic_catalog_buttons.html
+18
-0
No files found.
ecommerce/coupons/utils.py
View file @
5e58b69d
...
...
@@ -29,3 +29,16 @@ def get_seats_from_query(site, query, seat_types):
except
Product
.
DoesNotExist
:
pass
return
query_products
def
prepare_course_seat_types
(
course_seat_types
):
"""
Convert list of course seat types into comma-separated string.
Arguments:
course_seat_types (list): List of course seat types
Returns:
str: Comma-separated list of course seat types if course_seat_types is not empty
"""
return
','
.
join
(
seat_type
.
lower
()
for
seat_type
in
course_seat_types
)
ecommerce/extensions/api/constants.py
View file @
5e58b69d
...
...
@@ -6,13 +6,11 @@ class APIDictionaryKeys(object):
BASKET_ID
=
u'id'
BENEFIT_TYPE
=
u'benefit_type'
BENEFIT_VALUE
=
u'benefit_value'
CATALOG_QUERY
=
u'catalog_query'
CATEGORY_IDS
=
u'category_ids'
CHECKOUT
=
u'checkout'
CLIENT_USERNAME
=
u'client_username'
CODE
=
u'code'
COUPON_ID
=
u'coupon_id'
COURSE_SEAT_TYPES
=
u'course_seat_types'
END_DATE
=
u'end_date'
NOTE
=
u'note'
ORDER
=
u'order'
...
...
ecommerce/extensions/api/serializers.py
View file @
5e58b69d
...
...
@@ -493,7 +493,8 @@ class CouponSerializer(ProductPaymentInfoMixin, serializers.ModelSerializer):
def
get_course_seat_types
(
self
,
obj
):
offer
=
self
.
retrieve_offer
(
obj
)
return
offer
.
condition
.
range
.
course_seat_types
course_seat_types
=
offer
.
condition
.
range
.
course_seat_types
return
course_seat_types
.
split
(
','
)
if
course_seat_types
else
course_seat_types
class
Meta
(
object
):
model
=
Product
...
...
ecommerce/extensions/api/v2/tests/views/test_coupons.py
View file @
5e58b69d
...
...
@@ -444,7 +444,8 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CatalogPr
vouchers
=
coupon
.
attr
.
coupon_vouchers
.
vouchers
.
all
()
CouponViewSet
()
.
update_coupon_benefit_value
(
benefit_value
=
Decimal
(
54
),
vouchers
=
vouchers
vouchers
=
vouchers
,
coupon
=
coupon
)
for
voucher
in
vouchers
:
...
...
@@ -484,15 +485,6 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CatalogPr
baskets
=
Basket
.
objects
.
filter
(
lines__product_id
=
coupon
.
id
)
self
.
assertEqual
(
baskets
.
first
()
.
owner
.
username
,
'Test Client Username'
)
def
test_exception_for_multi_use_voucher_type
(
self
):
"""Test that an exception is raised for multi-use voucher types."""
self
.
data
.
update
({
'voucher_type'
:
Voucher
.
MULTI_USE
,
})
with
self
.
assertRaises
(
NotImplementedError
):
self
.
client
.
post
(
COUPONS_LINK
,
data
=
self
.
data
,
format
=
'json'
)
@ddt.data
(
'audit'
,
'honor'
)
def
test_restricted_course_mode
(
self
,
mode
):
"""Test that an exception is raised when a black-listed course mode is used."""
...
...
@@ -525,7 +517,7 @@ class CouponViewSetFunctionalTest(CouponMixin, CourseCatalogTestMixin, CatalogPr
details_response
=
self
.
client
.
get
(
reverse
(
'api:v2:coupons-detail'
,
args
=
[
coupon_id
]))
detail
=
json
.
loads
(
details_response
.
content
)
self
.
assertEqual
(
detail
[
'catalog_query'
],
catalog_query
)
self
.
assertEqual
(
detail
[
'course_seat_types'
],
course_seat_types
[
0
]
)
self
.
assertEqual
(
detail
[
'course_seat_types'
],
course_seat_types
)
self
.
assertEqual
(
detail
[
'seats'
][
0
][
'id'
],
seat
.
id
)
...
...
ecommerce/extensions/api/v2/views/coupons.py
View file @
5e58b69d
...
...
@@ -15,6 +15,7 @@ from rest_framework.permissions import IsAdminUser, IsAuthenticated
from
rest_framework.response
import
Response
from
ecommerce.core.models
import
BusinessClient
,
User
from
ecommerce.coupons.utils
import
prepare_course_seat_types
from
ecommerce.extensions.api
import
data
as
data_api
from
ecommerce.extensions.api.constants
import
APIConstants
as
AC
from
ecommerce.extensions.api.filters
import
ProductFilter
...
...
@@ -35,9 +36,18 @@ Order = get_model('order', 'Order')
Product
=
get_model
(
'catalogue'
,
'Product'
)
ProductCategory
=
get_model
(
'catalogue'
,
'ProductCategory'
)
ProductClass
=
get_model
(
'catalogue'
,
'ProductClass'
)
Range
=
Range
=
get_model
(
'offer'
,
'Range'
)
StockRecord
=
get_model
(
'partner'
,
'StockRecord'
)
Voucher
=
get_model
(
'voucher'
,
'Voucher'
)
CATALOG_QUERY
=
'catalog_query'
COURSE_SEAT_TYPES
=
'course_seat_types'
UPDATABLE_RANGE_FIELDS
=
[
CATALOG_QUERY
,
COURSE_SEAT_TYPES
,
]
class
CouponViewSet
(
EdxOrderPlacementMixin
,
viewsets
.
ModelViewSet
):
""" Coupon resource. """
...
...
@@ -73,7 +83,7 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
with
transaction
.
atomic
():
title
=
request
.
data
[
AC
.
KEYS
.
TITLE
]
client_username
=
request
.
data
[
AC
.
KEYS
.
CLIENT_USERNAME
]
stock_record_ids
=
request
.
data
.
get
(
AC
.
KEYS
.
STOCK_RECORD_IDS
,
None
)
stock_record_ids
=
request
.
data
.
get
(
AC
.
KEYS
.
STOCK_RECORD_IDS
)
start_date
=
dateutil
.
parser
.
parse
(
request
.
data
[
AC
.
KEYS
.
START_DATE
])
end_date
=
dateutil
.
parser
.
parse
(
request
.
data
[
AC
.
KEYS
.
END_DATE
])
code
=
request
.
data
[
AC
.
KEYS
.
CODE
]
...
...
@@ -85,13 +95,13 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
partner
=
request
.
site
.
siteconfiguration
.
partner
categories
=
Category
.
objects
.
filter
(
id__in
=
request
.
data
[
AC
.
KEYS
.
CATEGORY_IDS
])
client
,
__
=
BusinessClient
.
objects
.
get_or_create
(
name
=
client_username
)
note
=
request
.
data
.
get
(
'note'
,
None
)
max_uses
=
request
.
data
.
get
(
'max_uses'
,
None
)
catalog_query
=
request
.
data
.
get
(
AC
.
KEYS
.
CATALOG_QUERY
,
None
)
course_seat_types
=
request
.
data
.
get
(
AC
.
KEYS
.
COURSE_SEAT_TYPES
,
None
)
note
=
request
.
data
.
get
(
'note'
)
max_uses
=
request
.
data
.
get
(
'max_uses'
)
catalog_query
=
request
.
data
.
get
(
CATALOG_QUERY
)
course_seat_types
=
request
.
data
.
get
(
COURSE_SEAT_TYPES
)
if
course_seat_types
:
course_seat_types
=
','
.
join
(
seat_type
.
lower
()
for
seat_type
in
course_seat_types
)
course_seat_types
=
prepare_course_seat_types
(
course_seat_types
)
# Maximum number of uses can be set for each voucher type and disturb
# the predefined behaviours of the different voucher types. Therefor
...
...
@@ -102,10 +112,6 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
else
:
max_uses
=
None
# We currently do not support multi-use voucher types.
if
voucher_type
==
Voucher
.
MULTI_USE
:
raise
NotImplementedError
(
'Multi-use voucher types are not supported'
)
# When a black-listed course mode is received raise an exception.
# Audit modes do not have a certificate type and therefore will raise
# an AttributeError exception.
...
...
@@ -301,9 +307,23 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
if
data
:
vouchers
.
all
()
.
update
(
**
data
)
range_data
=
{}
for
field
in
UPDATABLE_RANGE_FIELDS
:
self
.
create_update_data_dict
(
request_data
=
request
.
data
,
request_data_key
=
field
,
update_dict
=
range_data
,
update_dict_key
=
field
)
if
range_data
:
voucher_range
=
vouchers
.
first
()
.
offers
.
first
()
.
benefit
.
range
Range
.
objects
.
filter
(
id
=
voucher_range
.
id
)
.
update
(
**
range_data
)
benefit_value
=
request
.
data
.
get
(
AC
.
KEYS
.
BENEFIT_VALUE
,
''
)
if
benefit_value
:
self
.
update_coupon_benefit_value
(
benefit_value
=
benefit_value
,
vouchers
=
vouchers
)
self
.
update_coupon_benefit_value
(
benefit_value
=
benefit_value
,
vouchers
=
vouchers
,
coupon
=
coupon
)
category_ids
=
request
.
data
.
get
(
AC
.
KEYS
.
CATEGORY_IDS
,
''
)
if
category_ids
:
...
...
@@ -336,14 +356,16 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
"""
value
=
request_data
.
get
(
request_data_key
,
''
)
if
value
:
update_dict
[
update_dict_key
]
=
value
update_dict
[
update_dict_key
]
=
prepare_course_seat_types
(
value
)
\
if
update_dict_key
==
COURSE_SEAT_TYPES
else
value
def
update_coupon_benefit_value
(
self
,
benefit_value
,
vouchers
):
def
update_coupon_benefit_value
(
self
,
benefit_value
,
vouchers
,
coupon
):
"""
Remove all offers from the vouchers and add a new offer
Arguments:
benefit_value (Decimal): Benefit value associated with a new offer
vouchers (ManyRelatedManager): Vouchers associated with the coupon to be updated
coupon (Product): Coupon product associated with vouchers
"""
voucher_offers
=
vouchers
.
first
()
.
offers
voucher_offer
=
voucher_offers
.
first
()
...
...
@@ -351,7 +373,8 @@ class CouponViewSet(EdxOrderPlacementMixin, viewsets.ModelViewSet):
new_offer
=
update_voucher_offer
(
offer
=
voucher_offer
,
benefit_value
=
benefit_value
,
benefit_type
=
voucher_offer
.
benefit
.
type
benefit_type
=
voucher_offer
.
benefit
.
type
,
coupon
=
coupon
)
for
voucher
in
vouchers
.
all
():
voucher
.
offers
.
clear
()
...
...
ecommerce/extensions/voucher/tests/test_utils.py
View file @
5e58b69d
...
...
@@ -449,7 +449,7 @@ class UtilTests(CouponMixin, CourseCatalogTestMixin, LmsApiMockMixin, TestCase):
self
.
assertEqual
(
voucher_offer
.
benefit
.
value
,
100.00
)
self
.
assertEqual
(
voucher_offer
.
benefit
.
range
.
catalog
,
self
.
catalog
)
new_offer
=
update_voucher_offer
(
voucher_offer
,
50.00
,
Benefit
.
PERCENTAGE
)
new_offer
=
update_voucher_offer
(
voucher_offer
,
50.00
,
Benefit
.
PERCENTAGE
,
self
.
coupon
)
self
.
assertEqual
(
new_offer
.
benefit
.
type
,
Benefit
.
PERCENTAGE
)
self
.
assertEqual
(
new_offer
.
benefit
.
value
,
50.00
)
self
.
assertEqual
(
new_offer
.
benefit
.
range
.
catalog
,
self
.
catalog
)
ecommerce/extensions/voucher/utils.py
View file @
5e58b69d
...
...
@@ -439,7 +439,7 @@ def get_voucher_discount_info(benefit, price):
}
def
update_voucher_offer
(
offer
,
benefit_value
,
benefit_type
):
def
update_voucher_offer
(
offer
,
benefit_value
,
benefit_type
,
coupon
):
"""
Update voucher offer with new benefit value.
...
...
@@ -454,5 +454,6 @@ def update_voucher_offer(offer, benefit_value, benefit_type):
return
_get_or_create_offer
(
product_range
=
offer
.
benefit
.
range
,
benefit_value
=
benefit_value
,
benefit_type
=
benefit_type
benefit_type
=
benefit_type
,
coupon_id
=
coupon
.
id
)
ecommerce/static/js/models/coupon_model.js
View file @
5e58b69d
...
...
@@ -21,9 +21,10 @@ define([
'use strict'
;
_
.
extend
(
Backbone
.
Validation
.
messages
,
{
required
:
gettext
(
'This field is required'
),
number
:
gettext
(
'This value must be a number'
),
date
:
gettext
(
'This value must be a date'
)
required
:
gettext
(
'This field is required.'
),
number
:
gettext
(
'This value must be a number.'
),
date
:
gettext
(
'This value must be a date.'
),
seat_types
:
gettext
(
'At least one seat type must be selected.'
),
});
_
.
extend
(
Backbone
.
Model
.
prototype
,
Backbone
.
Validation
.
mixin
);
...
...
@@ -37,19 +38,29 @@ define([
code
:
''
,
price
:
0
,
total_value
:
0
,
max_uses
:
1
max_uses
:
1
,
seats
:
[],
course_seats
:
[],
course_seat_types
:
[]
},
validation
:
{
category
:
{
required
:
true
},
course_id
:
{
pattern
:
'courseId'
,
msg
:
gettext
(
'A valid course ID is required'
)
msg
:
gettext
(
'A valid course ID is required'
),
required
:
function
()
{
return
this
.
get
(
'catalog_type'
)
===
'Single course'
;
}
},
title
:
{
required
:
true
},
client
:
{
required
:
true
},
// seat_type is for validation only, stock_record_ids holds the values
seat_type
:
{
required
:
true
},
seat_type
:
{
required
:
function
()
{
return
this
.
get
(
'catalog_type'
)
===
'Single course'
;
}
},
quantity
:
{
pattern
:
'number'
},
price
:
{
pattern
:
'number'
},
benefit_value
:
{
...
...
@@ -63,6 +74,16 @@ define([
rangeLength
:
[
8
,
16
],
msg
:
gettext
(
'Code field must be empty or between 8 and 16 characters'
)
},
catalog_query
:
{
required
:
function
()
{
return
this
.
get
(
'catalog_type'
)
===
'Multiple courses'
;
}
},
course_seat_types
:
function
(
val
)
{
if
(
this
.
get
(
'catalog_type'
)
===
'Multiple courses'
&&
val
.
length
===
0
)
{
return
Backbone
.
Validation
.
messages
.
seat_types
;
}
},
start_date
:
function
(
val
)
{
var
startDate
,
endDate
;
...
...
@@ -112,20 +133,39 @@ define([
},
getSeatPrice
:
function
()
{
return
this
.
get
(
'seats'
)[
0
].
price
;
var
seats
=
this
.
get
(
'seats'
);
return
seats
[
0
]
?
seats
[
0
].
price
:
''
;
},
updateTotalValue
:
function
(
seat_price
)
{
this
.
set
(
'total_value'
,
this
.
get
(
'quantity'
)
*
seat_price
);
},
getCertificateType
:
function
(
seat_data
)
{
var
seat_type
=
_
.
findWhere
(
seat_data
,
{
'name'
:
'certificate_type'
});
return
seat_type
?
seat_type
.
value
:
''
;
},
getCourseID
:
function
(
seat_data
)
{
var
course_id
=
_
.
findWhere
(
seat_data
,
{
'name'
:
'course_key'
});
return
course_id
?
course_id
.
value
:
''
;
},
updateSeatData
:
function
()
{
var
seat_data
=
this
.
get
(
'seats'
)[
0
].
attribute_values
,
seat_type
=
_
.
findWhere
(
seat_data
,
{
'name'
:
'certificate_type'
}),
course_id
=
_
.
findWhere
(
seat_data
,
{
'name'
:
'course_key'
});
this
.
set
(
'seat_type'
,
seat_type
?
seat_type
.
value
:
''
);
this
.
set
(
'course_id'
,
course_id
?
course_id
.
value
:
''
);
this
.
updateTotalValue
(
this
.
getSeatPrice
());
var
seat_data
,
seats
=
this
.
get
(
'seats'
);
this
.
set
(
'catalog_type'
,
this
.
has
(
'catalog_query'
)
?
'Multiple courses'
:
'Single course'
);
if
(
this
.
get
(
'catalog_type'
)
===
'Single course'
)
{
if
(
seats
[
0
])
{
seat_data
=
seats
[
0
].
attribute_values
;
this
.
set
(
'seat_type'
,
this
.
getCertificateType
(
seat_data
));
this
.
set
(
'course_id'
,
this
.
getCourseID
(
seat_data
));
this
.
updateTotalValue
(
this
.
getSeatPrice
());
}
}
},
updateVoucherData
:
function
()
{
...
...
ecommerce/static/js/pages/coupon_detail_page.js
View file @
5e58b69d
...
...
@@ -16,7 +16,7 @@ define([
initialize
:
function
(
options
)
{
this
.
model
=
Coupon
.
findOrCreate
({
id
:
options
.
id
});
this
.
view
=
new
CouponDetailView
({
model
:
this
.
model
});
this
.
listenTo
(
this
.
model
,
'sync'
,
this
.
re
nder
);
this
.
listenTo
(
this
.
model
,
'sync'
,
this
.
re
fresh
);
this
.
model
.
fetch
();
}
});
...
...
ecommerce/static/js/pages/page.js
View file @
5e58b69d
...
...
@@ -63,6 +63,11 @@ define(['backbone',
this
.
renderTitle
();
this
.
renderNestedView
();
return
this
;
},
refresh
:
function
()
{
this
.
view
.
remove
();
this
.
render
();
}
});
...
...
ecommerce/static/js/routers/page_router.js
View file @
5e58b69d
define
([
'backbone'
'backbone'
,
'backbone.route-filter'
,
'backbone.super'
],
function
(
Backbone
)
{
'use strict'
;
...
...
@@ -8,7 +10,6 @@ define([
* Base Router class.
*/
return
Backbone
.
Router
.
extend
({
// Keeps track of the page/view currently on display
currentView
:
null
,
...
...
ecommerce/static/js/test/specs/models/coupon_model_spec.js
View file @
5e58b69d
...
...
@@ -17,9 +17,14 @@ define([
describe
(
'Coupon model'
,
function
()
{
describe
(
'validation'
,
function
()
{
it
(
'should validate dates'
,
function
()
{
var
model
;
beforeEach
(
function
()
{
spyOn
(
$
,
'ajax'
);
var
model
=
Coupon
.
findOrCreate
(
discountCodeData
,
{
parse
:
true
});
model
=
Coupon
.
findOrCreate
(
discountCodeData
,
{
parse
:
true
});
});
it
(
'should validate dates'
,
function
()
{
model
.
validate
();
expect
(
model
.
isValid
()).
toBeTruthy
();
...
...
@@ -35,13 +40,61 @@ define([
});
it
(
'should validate discount code has discount type and value'
,
function
()
{
spyOn
(
$
,
'ajax'
);
var
model
=
Coupon
.
findOrCreate
(
discountCodeData
,
{
parse
:
true
});
model
.
set
(
'benefit_value'
,
''
);
model
.
set
(
'benefit_type'
,
''
);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeFalsy
();
});
it
(
'should validate course ID if the catalog is a Single Course Catalog'
,
function
()
{
model
.
set
(
'catalog_type'
,
'Single course'
);
model
.
set
(
'course_id'
,
''
);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeFalsy
();
model
.
set
(
'course_id'
,
'a/b/c'
);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeTruthy
();
});
it
(
'should validate seat type if the catalog is a Single Course Catalog'
,
function
()
{
model
.
set
(
'catalog_type'
,
'Single course'
);
model
.
set
(
'seat_type'
,
''
);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeFalsy
();
model
.
set
(
'seat_type'
,
'Verified'
);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeTruthy
();
});
it
(
'should validate catalog query and course seat types for Multiple Courses Catalog'
,
function
()
{
model
.
set
(
'catalog_type'
,
'Multiple courses'
);
model
.
set
(
'catalog_query'
,
''
);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeFalsy
();
model
.
set
(
'catalog_query'
,
'*:*'
);
model
.
set
(
'course_seat_types'
,
[]);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeFalsy
();
model
.
set
(
'catalog_query'
,
'*:*'
);
model
.
set
(
'course_seat_types'
,
[
'verified'
]);
model
.
validate
();
expect
(
model
.
isValid
()).
toBeTruthy
();
});
});
describe
(
'test model methods'
,
function
()
{
it
(
'should return seat price if a coupon has a seat'
,
function
()
{
var
model
=
new
Coupon
();
expect
(
model
.
getSeatPrice
()).
toEqual
(
''
);
model
.
set
(
'seats'
,
[{
'price'
:
100
}]);
expect
(
model
.
getSeatPrice
()).
toEqual
(
100
);
});
});
describe
(
'save'
,
function
()
{
...
...
ecommerce/static/js/test/specs/pages/page_spec.js
0 → 100644
View file @
5e58b69d
define
([
'backbone'
,
'jquery'
,
'pages/page'
],
function
(
Backbone
,
$
,
Page
)
{
'use strict'
;
describe
(
'Base page'
,
function
()
{
var
page
;
it
(
'should remove and render the view when refresh is called'
,
function
()
{
page
=
new
Page
();
page
.
view
=
new
Backbone
.
View
();
spyOn
(
page
.
view
,
'remove'
);
spyOn
(
page
,
'render'
);
page
.
refresh
();
expect
(
page
.
view
.
remove
).
toHaveBeenCalled
();
expect
(
page
.
render
).
toHaveBeenCalled
();
});
});
});
ecommerce/static/js/test/specs/views/coupon_detail_view_spec.js
View file @
5e58b69d
...
...
@@ -35,11 +35,6 @@ define([
verifiedSeat
=
Mock_Coupons
.
verifiedSeat
;
});
it
(
'should capitalize string value'
,
function
()
{
expect
(
view
.
capitalize
(
'abc'
)).
toBe
(
'Abc'
);
expect
(
view
.
capitalize
(
'aBC'
)).
toBe
(
'Abc'
);
});
it
(
'should get code status from voucher data'
,
function
()
{
expect
(
view
.
codeStatus
(
enrollmentCodeVoucher
)).
toBe
(
'ACTIVE'
);
...
...
@@ -56,38 +51,6 @@ define([
expect
(
view
.
couponType
(
enrollmentCodeVoucher
)).
toBe
(
'Enrollment Code'
);
});
it
(
'should get course ID from seat data'
,
function
()
{
expect
(
view
.
courseID
(
verifiedSeat
.
attribute_values
)).
toBe
(
'course-v1:edX+DemoX+Demo_Course'
);
verifiedSeat
.
attribute_values
=
[
{
name
:
'certificate_type'
,
value
:
'verified'
},
{
name
:
'id_verification_required'
,
value
:
true
}
];
expect
(
view
.
courseID
(
verifiedSeat
.
attribute_values
)).
toBe
(
''
);
});
it
(
'should get certificate type from seat data'
,
function
()
{
expect
(
view
.
certificateType
(
verifiedSeat
.
attribute_values
)).
toBe
(
'Verified'
);
verifiedSeat
.
attribute_values
=
[
{
name
:
'course_key'
,
value
:
'course-v1:edX+DemoX+Demo_Course'
},
{
name
:
'id_verification_required'
,
value
:
true
}
];
expect
(
view
.
certificateType
(
verifiedSeat
.
attribute_values
)).
toBe
(
''
);
});
it
(
'should get discount value from voucher data'
,
function
()
{
expect
(
view
.
discountValue
(
percentageDiscountCodeVoucher
)).
toBe
(
'50%'
);
expect
(
view
.
discountValue
(
valueDiscountCodeVoucher
)).
toBe
(
'$12'
);
...
...
@@ -106,13 +69,17 @@ define([
expect
(
view
.
usageLimitation
(
enrollmentCodeVoucher
)).
toBe
(
'Can be used once by one customer'
);
expect
(
view
.
usageLimitation
(
valueDiscountCodeVoucher
)).
toBe
(
'Can be used once by multiple customers'
);
valueDiscountCodeVoucher
.
usage
=
'Multi-use'
;
expect
(
view
.
usageLimitation
(
valueDiscountCodeVoucher
)).
toBe
(
'Can be used multiple times by multiple customers'
);
valueDiscountCodeVoucher
.
usage
=
''
;
expect
(
view
.
usageLimitation
(
valueDiscountCodeVoucher
)).
toBe
(
''
);
});
it
(
'should display correct data upon rendering'
,
function
()
{
var
course_data
=
model
.
get
(
'seats'
)[
0
].
attribute_values
,
voucher
=
model
.
get
(
'vouchers'
)[
0
],
var
voucher
=
model
.
get
(
'vouchers'
)[
0
],
category
=
model
.
get
(
'categories'
)[
0
].
name
;
spyOn
(
view
,
'renderVoucherTable'
);
...
...
@@ -125,10 +92,10 @@ define([
);
expect
(
view
.
$el
.
find
(
'.category > .value'
).
text
()).
toEqual
(
category
);
expect
(
view
.
$el
.
find
(
'.discount-value > .value'
).
text
()).
toEqual
(
view
.
discountValue
(
voucher
));
expect
(
$
.
trim
(
view
.
$el
.
find
(
'.course-info > .value'
).
text
())).
toEqual
(
view
.
courseID
(
course_data
));
expect
(
view
.
$el
.
find
(
'.course-info > .value > .pull-right'
).
text
()).
toEqual
(
view
.
certificateType
(
course_data
)
expect
(
view
.
$el
.
find
(
'.course-info > .value'
).
contents
().
get
(
0
).
nodeValue
).
toEqual
(
'course-v1:edX+DemoX+Demo_Course'
);
expect
(
view
.
$el
.
find
(
'.course-info > .value > .pull-right'
).
text
()).
toEqual
(
'verified'
);
expect
(
view
.
$el
.
find
(
'.start-date-info > .value'
).
text
()).
toEqual
(
view
.
formatDateTime
(
voucher
.
start_datetime
)
);
...
...
@@ -143,6 +110,31 @@ define([
expect
(
view
.
renderVoucherTable
).
toHaveBeenCalled
();
});
it
(
'should render course data'
,
function
()
{
view
.
model
.
set
({
'catalog_type'
:
'Single course'
,
'course_id'
:
'a/b/c'
,
'seat_type'
:
'Verified'
});
view
.
render
();
var
course_info
=
view
.
$el
.
find
(
'.course-info .value'
);
expect
(
course_info
.
length
).
toEqual
(
1
);
expect
(
course_info
.
text
()).
toEqual
(
'a/b/cVerified'
);
view
.
model
.
set
({
'catalog_type'
:
'Multiple courses'
,
'catalog_query'
:
'id:*'
,
'course_seat_types'
:
[
'verified'
,
'professional'
]
});
view
.
render
();
expect
(
view
.
$el
.
find
(
'.catalog-query .value'
).
text
()).
toEqual
(
'id:*'
);
expect
(
view
.
$el
.
find
(
'.seat-types .value'
).
text
()).
toEqual
(
'verified,professional'
);
});
it
(
'should display data table'
,
function
()
{
view
.
renderVoucherTable
();
expect
(
view
.
$el
.
find
(
'#vouchersTable'
).
DataTable
().
autowidth
).
toBeFalsy
();
...
...
ecommerce/static/js/test/specs/views/coupon_edit_view_spec.js
View file @
5e58b69d
...
...
@@ -36,7 +36,7 @@ define([
expect
(
view
.
$el
.
find
(
'[name=code_type]'
).
val
()).
toEqual
(
'Enrollment code'
);
expect
(
view
.
$el
.
find
(
'[name=start_date]'
).
val
()).
toEqual
(
startDate
);
expect
(
view
.
$el
.
find
(
'[name=end_date]'
).
val
()).
toEqual
(
endDate
);
expect
(
voucherType
.
children
().
length
).
toBe
(
2
);
expect
(
voucherType
.
children
().
length
).
toBe
(
3
);
expect
(
voucherType
.
val
()).
toEqual
(
model
.
get
(
'voucher_type'
));
expect
(
view
.
$el
.
find
(
'[name=quantity]'
).
val
()).
toEqual
(
model
.
get
(
'quantity'
).
toString
());
expect
(
view
.
$el
.
find
(
'[name=client]'
).
val
()).
toEqual
(
model
.
get
(
'client'
));
...
...
@@ -61,7 +61,7 @@ define([
expect
(
view
.
$el
.
find
(
'[name=code_type]'
).
val
()).
toEqual
(
'Discount code'
);
expect
(
view
.
$el
.
find
(
'[name=start_date]'
).
val
()).
toEqual
(
startDate
);
expect
(
view
.
$el
.
find
(
'[name=end_date]'
).
val
()).
toEqual
(
endDate
);
expect
(
voucherType
.
children
().
length
).
toBe
(
2
);
expect
(
voucherType
.
children
().
length
).
toBe
(
3
);
expect
(
voucherType
.
val
()).
toEqual
(
model
.
get
(
'voucher_type'
));
expect
(
view
.
$el
.
find
(
'[name=quantity]'
).
val
()).
toEqual
(
model
.
get
(
'quantity'
).
toString
());
expect
(
view
.
$el
.
find
(
'[name=client]'
).
val
()).
toEqual
(
model
.
get
(
'client'
));
...
...
ecommerce/static/js/test/specs/views/coupon_form_view_spec.js
View file @
5e58b69d
...
...
@@ -119,7 +119,7 @@ define([
});
});
describe
(
'discount'
,
function
()
{
describe
(
'discount
code
'
,
function
()
{
beforeEach
(
function
()
{
view
.
$el
.
find
(
'[name=code_type]'
).
val
(
'Discount code'
).
trigger
(
'change'
);
});
...
...
@@ -179,6 +179,20 @@ define([
expect
(
visible
(
'[name=code]'
)).
toBe
(
true
);
});
});
describe
(
'dynamic catalog coupon'
,
function
()
{
it
(
'should update dynamic catalog view query with coupon catalog query'
,
function
()
{
model
.
set
(
'catalog_query'
,
'*:*'
);
view
.
updateCatalogQuery
();
expect
(
view
.
dynamic_catalog_view
.
query
).
toEqual
(
model
.
get
(
'catalog_query'
));
});
it
(
'should update dynamic catalog view course seat types with coupon seat types'
,
function
()
{
model
.
set
(
'course_seat_types'
,
[
'verified'
]);
view
.
updateCourseSeatTypes
();
expect
(
view
.
dynamic_catalog_view
.
seat_types
).
toEqual
(
model
.
get
(
'course_seat_types'
));
});
});
});
}
);
ecommerce/static/js/test/specs/views/dynamic_catalog_view_spec.js
0 → 100644
View file @
5e58b69d
define
([
'jquery'
,
'underscore'
,
'collections/course_collection'
,
'models/course_model'
,
'views/dynamic_catalog_view'
],
function
(
$
,
_
,
Courses
,
Course
,
DynamicCatalogView
)
{
'use strict'
;
describe
(
'dynamic catalog view'
,
function
()
{
var
view
;
beforeEach
(
function
()
{
view
=
new
DynamicCatalogView
({
creating_editing
:
true
,
query
:
'*:*'
,
seat_types
:
'verified,professional'
});
view
.
render
();
});
it
(
'should call preview catalog if preview button was clicked'
,
function
()
{
spyOn
(
view
,
'previewCatalog'
);
view
.
delegateEvents
();
view
.
$el
.
find
(
'[name=preview_catalog]'
).
trigger
(
'click'
);
expect
(
view
.
previewCatalog
).
toHaveBeenCalled
();
});
it
(
'should format row data for dynamic catalog preview'
,
function
()
{
var
course
=
Course
.
findOrCreate
({
'id'
:
'a/b/c'
,
'name'
:
'ABC Course'
,
'type'
:
'verified'
},
{
parse
:
true
}),
row_data
=
view
.
getRowData
(
course
);
expect
(
row_data
).
toEqual
({
'id'
:
course
.
get
(
'id'
),
'name'
:
course
.
get
(
'name'
),
'type'
:
'Verified'
});
});
it
(
'should call Course Catalog API if previewCatalog was called'
,
function
()
{
var
args
,
calls
,
e
=
$
.
Event
(
'click'
);
spyOn
(
e
,
'preventDefault'
);
spyOn
(
Backbone
,
'ajax'
);
view
.
previewCatalog
(
e
);
expect
(
e
.
preventDefault
).
toHaveBeenCalled
();
expect
(
Backbone
.
ajax
).
toHaveBeenCalled
();
calls
=
Backbone
.
ajax
.
calls
;
args
=
calls
.
argsFor
(
calls
.
count
()
-
1
)[
0
];
expect
(
args
.
type
).
toEqual
(
'GET'
);
expect
(
args
.
url
).
toEqual
(
window
.
location
.
origin
+
'/api/v2/catalogs/preview/'
);
expect
(
args
.
data
).
toEqual
({
query
:
view
.
query
});
expect
(
args
.
success
).
toEqual
(
view
.
onSuccess
);
});
it
(
'should fill datatable on successful AJAX call to Course Catalog API'
,
function
()
{
var
API_data
=
{
results
:
[{
key
:
'a/b/c'
},
{
key
:
'd/e/f'
}]
},
args
;
spyOn
(
_
,
'pluck'
);
spyOn
(
$
.
prototype
,
'DataTable'
);
view
.
onSuccess
(
API_data
);
expect
(
_
.
pluck
).
toHaveBeenCalledWith
(
API_data
.
results
,
'key'
);
expect
(
$
.
prototype
.
DataTable
).
toHaveBeenCalled
();
args
=
$
.
prototype
.
DataTable
.
calls
.
argsFor
(
0
)[
0
];
expect
(
args
.
autoWidth
).
toBeFalsy
();
expect
(
args
.
destroy
).
toBeTruthy
();
expect
(
args
.
info
).
toBeTruthy
();
expect
(
args
.
paging
).
toBeTruthy
();
expect
(
args
.
ordering
).
toBeFalsy
();
expect
(
args
.
searching
).
toBeFalsy
();
expect
(
args
.
columns
).
toEqual
([
{
title
:
'Course ID'
,
data
:
'id'
},
{
title
:
'Course name'
,
data
:
'name'
},
{
title
:
'Seat type'
,
data
:
'type'
}
]);
});
it
(
'should filter courses by calling filterCourses function'
,
function
()
{
var
course_keys
=
[
'test1/test1/test1'
,
'test3/test3/test3'
],
filtered_courses
,
seat_types
=
[
'verified'
];
view
.
courses
=
new
Courses
([
Course
.
findOrCreate
({
id
:
'test1/test1/test1'
,
type
:
'verified'
}),
Course
.
findOrCreate
({
id
:
'test2/test2/test2'
,
type
:
'verified'
}),
Course
.
findOrCreate
({
id
:
'test3/test3/test3'
,
type
:
'professional'
})
]);
filtered_courses
=
view
.
filterCourses
(
course_keys
,
seat_types
);
expect
(
filtered_courses
.
length
).
toEqual
(
1
);
expect
(
filtered_courses
[
0
].
get
(
'id'
)).
toEqual
(
'test1/test1/test1'
);
expect
(
filtered_courses
[
0
].
get
(
'type'
)).
toEqual
(
'verified'
);
});
});
}
);
ecommerce/static/js/views/coupon_detail_view.js
View file @
5e58b69d
...
...
@@ -4,14 +4,16 @@ define([
'underscore'
,
'underscore.string'
,
'moment'
,
'text!templates/coupon_detail.html'
'text!templates/coupon_detail.html'
,
'views/dynamic_catalog_view'
,
],
function
(
$
,
Backbone
,
_
,
_s
,
moment
,
CouponDetailTemplate
)
{
CouponDetailTemplate
,
DynamicCatalogView
)
{
'use strict'
;
return
Backbone
.
View
.
extend
({
...
...
@@ -23,10 +25,6 @@ define([
template
:
_
.
template
(
CouponDetailTemplate
),
capitalize
:
function
(
string
)
{
return
string
.
charAt
(
0
).
toUpperCase
()
+
string
.
substring
(
1
).
toLowerCase
();
},
codeStatus
:
function
(
voucher
)
{
var
startDate
=
moment
(
new
Date
(
voucher
.
start_datetime
)),
endDate
=
moment
(
new
Date
(
voucher
.
end_datetime
)),
...
...
@@ -42,16 +40,6 @@ define([
);
},
courseID
:
function
(
course_data
)
{
var
course_id
=
_
.
findWhere
(
course_data
,
{
'name'
:
'course_key'
});
return
course_id
?
course_id
.
value
:
''
;
},
certificateType
:
function
(
course_data
)
{
var
certificate_type
=
_
.
findWhere
(
course_data
,
{
'name'
:
'certificate_type'
});
return
certificate_type
?
gettext
(
this
.
capitalize
(
certificate_type
.
value
))
:
''
;
},
discountValue
:
function
(
voucher
)
{
var
benefitType
=
voucher
.
benefit
[
0
],
benefitValue
=
voucher
.
benefit
[
1
],
...
...
@@ -70,6 +58,8 @@ define([
usageLimitation
:
function
(
voucher
)
{
if
(
voucher
.
usage
===
'Single use'
)
{
return
gettext
(
'Can be used once by one customer'
);
}
else
if
(
voucher
.
usage
===
'Multi-use'
)
{
return
gettext
(
'Can be used multiple times by multiple customers'
);
}
else
if
(
voucher
.
usage
===
'Once per customer'
)
{
return
gettext
(
'Can be used once by multiple customers'
);
}
...
...
@@ -77,16 +67,13 @@ define([
},
render
:
function
()
{
var
course_data
=
this
.
model
.
get
(
'seats'
)[
0
].
attribute_values
,
html
,
var
html
,
voucher
=
this
.
model
.
get
(
'vouchers'
)[
0
],
category
=
this
.
model
.
get
(
'categories'
)[
0
].
name
,
note
=
this
.
model
.
get
(
'note'
);
html
=
this
.
template
({
course_id
:
this
.
courseID
(
course_data
),
certificate_type
:
this
.
certificateType
(
course_data
),
coupon
:
this
.
model
.
attributes
,
coupon
:
this
.
model
.
toJSON
(),
couponType
:
this
.
couponType
(
voucher
),
codeStatus
:
this
.
codeStatus
(
voucher
),
discountValue
:
this
.
discountValue
(
voucher
),
...
...
@@ -101,7 +88,17 @@ define([
this
.
$el
.
html
(
html
);
this
.
renderVoucherTable
();
this
.
renderCourseData
();
this
.
delegateEvents
();
this
.
dynamic_catalog_view
=
new
DynamicCatalogView
({
'query'
:
this
.
model
.
get
(
'catalog_query'
),
'seat_types'
:
this
.
model
.
get
(
'course_seat_types'
)
});
this
.
dynamic_catalog_view
.
$el
=
this
.
$
(
'.catalog_buttons'
);
this
.
dynamic_catalog_view
.
render
();
this
.
dynamic_catalog_view
.
delegateEvents
();
return
this
;
},
...
...
@@ -127,6 +124,26 @@ define([
return
this
;
},
renderCourseData
:
function
()
{
if
(
this
.
model
.
get
(
'catalog_type'
)
===
'Single course'
)
{
this
.
$el
.
find
(
'.course-info'
).
append
(
_s
.
sprintf
(
'<div class="value">%s<span class="pull-right">%s</span></div>'
,
this
.
model
.
get
(
'course_id'
),
this
.
model
.
get
(
'seat_type'
))
);
this
.
$el
.
find
(
'.catalog-query'
).
addClass
(
'hidden'
);
this
.
$el
.
find
(
'.seat-types'
).
addClass
(
'hidden'
);
this
.
$el
.
find
(
'.course-info'
).
removeClass
(
'hidden'
);
}
else
if
(
this
.
model
.
get
(
'catalog_type'
)
===
'Multiple courses'
)
{
this
.
$el
.
find
(
'.course-info'
).
addClass
(
'hidden'
);
this
.
$el
.
find
(
'.catalog-query'
).
removeClass
(
'hidden'
);
this
.
$el
.
find
(
'.seat-types'
).
removeClass
(
'hidden'
);
}
return
this
;
},
downloadCouponReport
:
function
(
event
)
{
var
url
=
_s
.
sprintf
(
'/api/v2/coupons/coupon_reports/%d'
,
this
.
model
.
id
);
...
...
ecommerce/static/js/views/coupon_form_view.js
View file @
5e58b69d
...
...
@@ -12,7 +12,9 @@ define([
'utils/utils'
,
'text!templates/coupon_form.html'
,
'models/course_model'
,
'views/form_view'
'collections/course_collection'
,
'views/form_view'
,
'views/dynamic_catalog_view'
,
],
function
(
$
,
Backbone
,
...
...
@@ -25,7 +27,9 @@ define([
Utils
,
CouponFormTemplate
,
Course
,
FormView
)
{
Courses
,
FormView
,
DynamicCatalogView
)
{
'use strict'
;
return
FormView
.
extend
({
...
...
@@ -56,6 +60,10 @@ define([
{
value
:
'Once per customer'
,
label
:
gettext
(
'Can be used once by multiple customers'
)
},
{
value
:
'Multi-use'
,
label
:
gettext
(
'Can be used multiple times by multiple customers'
),
}
],
...
...
@@ -152,7 +160,16 @@ define([
},
'input[name=max_uses]'
:
{
observe
:
'max_uses'
}
},
'input[name=catalog_type]'
:
{
observe
:
'catalog_type'
},
'textarea[name=catalog_query]'
:
{
observe
:
'catalog_query'
},
'input[name=course_seat_types]'
:
{
observe
:
'course_seat_types'
},
},
events
:
{
...
...
@@ -170,10 +187,18 @@ define([
this
.
editing
=
options
.
editing
||
false
;
this
.
hiddenClass
=
'hidden'
;
this
.
dynamic_catalog_view
=
new
DynamicCatalogView
({
'query'
:
this
.
model
.
get
(
'catalog_query'
),
'seat_types'
:
this
.
model
.
get
(
'course_seat_types'
)
});
this
.
listenTo
(
this
.
model
,
'change:coupon_type'
,
this
.
toggleCouponTypeField
);
this
.
listenTo
(
this
.
model
,
'change:voucher_type'
,
this
.
toggleVoucherTypeField
);
this
.
listenTo
(
this
.
model
,
'change:code'
,
this
.
toggleCodeField
);
this
.
listenTo
(
this
.
model
,
'change:quantity'
,
this
.
toggleQuantityField
);
this
.
listenTo
(
this
.
model
,
'change:catalog_type'
,
this
.
toggleCatalogTypeField
);
this
.
listenTo
(
this
.
model
,
'change:catalog_query'
,
this
.
updateCatalogQuery
);
this
.
listenTo
(
this
.
model
,
'change:course_seat_types'
,
this
.
updateCourseSeatTypes
);
this
.
_super
();
},
...
...
@@ -216,6 +241,20 @@ define([
}
},
toggleCatalogTypeField
:
function
()
{
if
(
this
.
model
.
get
(
'catalog_type'
)
===
'Single course'
)
{
this
.
formGroup
(
'[name=catalog_query]'
).
addClass
(
this
.
hiddenClass
);
this
.
formGroup
(
'[name=course_seat_types]'
).
addClass
(
this
.
hiddenClass
);
this
.
formGroup
(
'[name=course_id]'
).
removeClass
(
this
.
hiddenClass
);
this
.
formGroup
(
'[name=seat_type]'
).
removeClass
(
this
.
hiddenClass
);
}
else
{
this
.
formGroup
(
'[name=catalog_query]'
).
removeClass
(
this
.
hiddenClass
);
this
.
formGroup
(
'[name=course_seat_types]'
).
removeClass
(
this
.
hiddenClass
);
this
.
formGroup
(
'[name=course_id]'
).
addClass
(
this
.
hiddenClass
);
this
.
formGroup
(
'[name=seat_type]'
).
addClass
(
this
.
hiddenClass
);
}
},
toggleVoucherTypeField
:
function
()
{
var
voucherType
=
this
.
model
.
get
(
'voucher_type'
);
if
(
!
this
.
editing
)
{
...
...
@@ -343,6 +382,7 @@ define([
this
.
$el
.
find
(
'input[name=benefit_type]'
).
attr
(
'disabled'
,
true
);
this
.
$el
.
find
(
'select[name=seat_type]'
).
attr
(
'disabled'
,
true
);
this
.
$el
.
find
(
'input[name=max_uses]'
).
attr
(
'disabled'
,
true
);
this
.
$el
.
find
(
'input[name=catalog_type]'
).
attr
(
'disabled'
,
true
);
},
getSeatData
:
function
()
{
...
...
@@ -353,11 +393,22 @@ define([
return
this
.
$el
.
find
(
'[name=seat_type]'
).
val
();
},
updateCatalogQuery
:
function
()
{
this
.
dynamic_catalog_view
.
query
=
this
.
model
.
get
(
'catalog_query'
);
},
updateCourseSeatTypes
:
function
()
{
this
.
dynamic_catalog_view
.
seat_types
=
this
.
model
.
get
(
'course_seat_types'
);
},
render
:
function
()
{
// Render the parent form/template
this
.
$el
.
html
(
this
.
template
(
this
.
model
.
attributes
));
this
.
stickit
();
this
.
toggleCatalogTypeField
();
this
.
dynamic_catalog_view
.
setElement
(
this
.
$el
.
find
(
'.catalog_buttons'
)).
render
();
// Avoid the need to create this jQuery object every time an alert has to be rendered.
this
.
$alerts
=
this
.
$el
.
find
(
'.alerts'
);
...
...
@@ -368,6 +419,7 @@ define([
this
.
toggleVoucherTypeField
();
this
.
toggleCodeField
();
this
.
toggleQuantityField
();
this
.
$el
.
find
(
'.catalog-query'
).
addClass
(
'editing'
);
this
.
$el
.
find
(
'button[type=submit]'
).
html
(
gettext
(
'Save Changes'
));
this
.
fillFromCourse
();
}
else
{
...
...
@@ -377,8 +429,10 @@ define([
this
.
model
.
set
(
'voucher_type'
,
this
.
voucherTypes
[
0
].
value
);
this
.
model
.
set
(
'category'
,
defaultCategory
[
0
].
id
);
this
.
model
.
set
(
'benefit_type'
,
'Percentage'
);
this
.
model
.
set
(
'catalog_type'
,
'Single course'
);
this
.
$el
.
find
(
'[name=benefit_value]'
).
attr
(
'max'
,
100
);
this
.
$el
.
find
(
'button[type=submit]'
).
html
(
gettext
(
'Create Coupon'
));
this
.
$el
.
find
(
'.catalog-query'
).
removeClass
(
'editing'
);
}
// Add date picker
...
...
ecommerce/static/js/views/dynamic_catalog_view.js
0 → 100644
View file @
5e58b69d
define
([
'jquery'
,
'backbone'
,
'underscore.string'
,
'collections/course_collection'
,
'text!templates/dynamic_catalog_buttons.html'
],
function
(
$
,
Backbone
,
_s
,
Courses
,
DynamicCatalogButtons
)
{
'use strict'
;
return
Backbone
.
View
.
extend
({
template
:
_
.
template
(
DynamicCatalogButtons
),
events
:
{
'click [name=preview_catalog]'
:
'previewCatalog'
},
initialize
:
function
(
options
)
{
this
.
query
=
options
.
query
;
this
.
seat_types
=
options
.
seat_types
;
this
.
courses
=
new
Courses
();
this
.
_super
();
},
getRowData
:
function
(
course
)
{
return
{
id
:
course
.
get
(
'id'
),
name
:
course
.
get
(
'name'
),
type
:
_s
(
course
.
get
(
'type'
)).
capitalize
().
value
()
};
},
previewCatalog
:
function
(
event
)
{
event
.
preventDefault
();
this
.
courses
.
fetch
();
Backbone
.
ajax
({
context
:
this
,
type
:
'GET'
,
url
:
window
.
location
.
origin
+
'/api/v2/catalogs/preview/'
,
data
:
{
query
:
this
.
query
},
success
:
this
.
onSuccess
});
},
filterCourses
:
function
(
course_keys
,
seat_types
)
{
return
_
.
filter
(
this
.
courses
.
models
,
function
(
course
)
{
return
(
_
.
contains
(
course_keys
,
course
.
get
(
'id'
))
&&
_
.
contains
(
seat_types
,
course
.
get
(
'type'
)));
});
},
onSuccess
:
function
(
data
)
{
var
course_keys
=
_
.
pluck
(
data
.
results
,
'key'
),
course_data
=
this
.
filterCourses
(
course_keys
,
this
.
seat_types
);
this
.
$el
.
find
(
'#coursesTable'
).
DataTable
({
autoWidth
:
false
,
destroy
:
true
,
info
:
true
,
paging
:
true
,
ordering
:
false
,
searching
:
false
,
columns
:
[
{
title
:
gettext
(
'Course ID'
),
data
:
'id'
},
{
title
:
gettext
(
'Course name'
),
data
:
'name'
},
{
title
:
gettext
(
'Seat type'
),
data
:
'type'
}
],
data
:
course_data
.
map
(
this
.
getRowData
,
this
)
},
this
);
},
render
:
function
()
{
this
.
$el
.
html
(
this
.
template
({}));
return
this
;
}
});
});
ecommerce/static/js/views/form_view.js
View file @
5e58b69d
...
...
@@ -210,9 +210,9 @@ define([
* Override Backbone.View.extend so that the child view inherits events.
*/
FormView
.
extend
=
function
(
child
)
{
var
view
=
Backbone
.
View
.
extend
.
apply
(
this
,
arguments
);
view
.
prototype
.
events
=
_
.
extend
({},
this
.
prototype
.
events
,
child
.
events
);
return
view
;
var
view
=
Backbone
.
View
.
extend
.
apply
(
this
,
arguments
);
view
.
prototype
.
events
=
_
.
extend
({},
this
.
prototype
.
events
,
child
.
events
);
return
view
;
};
return
FormView
;
...
...
ecommerce/static/sass/partials/views/_coupon_admin.scss
View file @
5e58b69d
...
...
@@ -12,6 +12,54 @@
.help-block
{
margin
:
0
;
}
.catalog-type
{
padding-top
:
30px
;
height
:
61px
;
label
{
padding
:
0
10px
;
}
}
.catalog-query
{
margin-bottom
:
10px
;
&
.editing
{
margin-bottom
:
30px
;
}
p
{
margin-bottom
:
20px
;
}
button
{
margin-top
:
10px
;
margin-right
:
10px
;
}
textarea
{
height
:
216px
;
}
}
.course-seat-types
{
margin-bottom
:
27px
;
height
:
154px
;
.form-inline
label
{
margin-bottom
:
10px
;
padding-right
:
10px
;
}
.checkboxes
{
margin-bottom
:
25px
;
}
.catalog-buttons
{
margin-bottom
:
34px
;
}
}
}
.coupon-detail-view
{
...
...
@@ -59,7 +107,9 @@
.date-info
,
.usage-limitations
,
.client-info
,
.total-paid
{
.total-paid
,
.catalog-query
,
.seat-types
{
@include
float
(
left
);
@include
margin-right
(
2%
);
...
...
@@ -68,12 +118,15 @@
height
:
75px
;
}
.date-info
,
.usage-limitations
,
.total-paid
{
@include
margin-right
(
50%
);
}
.date-info.single-course
{
margin-right
:
50%
;
}
.start-date-info
,
.end-date-info
{
@include
float
(
left
);
...
...
@@ -86,9 +139,35 @@
}
.codes
{
@include
float
(
left
);
width
:
100%
;
.heading
{
margin-bottom
:
10px
;
}
}
}
}
.catalog_buttons
{
margin-top
:
10px
;
margin-bottom
:
34px
;
}
#catalogModal
{
.modal-dialog
{
margin-top
:
100px
;
width
:
75%
;
}
.modal-header
{
border
:
none
;
.modal-title
{
text-align
:
center
;
font-weight
:
800
;
}
}
}
ecommerce/static/templates/coupon_detail.html
View file @
5e58b69d
...
...
@@ -38,10 +38,11 @@
<div
class=
"value"
><
%=
discountValue
%
></div>
</div>
<div
class=
"info-item course-info"
>
<div
class=
"heading"
><
%=
gettext
('
Valid
for
course:
')
%
></div>
<div
class=
"value"
><
%=
course_id
%
>
<span
class=
"pull-right"
><
%=
certificate_type
%
></span>
</div>
<div
class=
"heading"
><
%=
gettext
('
Valid
for
courses:
')
%
></div>
</div>
<div
class=
"info-item catalog-query"
>
<div
class=
"heading"
><
%=
gettext
('
Catalog
query:
')
%
></div>
<div
class=
"value"
><
%=
coupon
['
catalog_query
']
%
></div>
</div>
<div
class=
"info-item date-info"
>
<div
class=
"start-date-info"
>
...
...
@@ -53,6 +54,11 @@
<div
class=
"value"
><
%=
endDateTime
%
></div>
</div>
</div>
<div
class=
"info-item seat-types"
>
<div
class=
"heading"
><
%=
gettext
('
Seat
types:
')
%
></div>
<div
class=
"value"
><
%=
coupon
['
course_seat_types
']
%
></div>
<div
class=
"catalog_buttons"
></div>
</div>
<div
class=
"info-item usage-limitations"
>
<div
class=
"heading"
><
%=
gettext
('
Usage
Limitations:
')
%
></div>
<div
class=
"value"
><
%=
usage
%
></div>
...
...
ecommerce/static/templates/coupon_form.html
View file @
5e58b69d
...
...
@@ -102,6 +102,15 @@
<div
class=
"fields col-md-6"
>
<div
class=
"form-group"
>
<div
class=
"form-inline catalog-type"
>
<input
id=
"single_course"
type=
"radio"
name=
"catalog_type"
value=
"Single course"
>
<label
for=
"single_course"
><
%=
gettext
('
Single
course
')
%
></label>
<input
id=
"multiple_courses"
type=
"radio"
name=
"catalog_type"
value=
"Multiple courses"
>
<label
for=
"multiple_courses"
><
%=
gettext
('
Multiple
courses
')
%
></label>
</div>
<p
class=
"help-block"
></p>
</div>
<div
class=
"form-group"
>
<label
for=
"course_id"
><
%=
gettext
('
Course
ID
')
%
>
*
</label>
<input
type=
"text"
class=
"form-control"
name=
"course_id"
>
<p
class=
"help-block"
></p>
...
...
@@ -111,6 +120,22 @@
<select
class=
"form-control"
name=
"seat_type"
></select>
<p
class=
"help-block"
></p>
</div>
<div
class=
"form-group catalog-query"
>
<label
for=
"catalog_query"
><
%=
gettext
('
Valid
for:
')
%
>
*
</label>
<textarea
class=
"form-control"
name=
"catalog_query"
rows=
"10"
></textarea>
<p
class=
"help-block"
></p>
</div>
<div
class=
"form-group course-seat-types"
>
<label
for=
"course_seat_types"
><
%=
gettext
('
Seat
Types:
')
%
></label>
<div
class=
"checkboxes"
>
<input
id=
"verified"
type=
"checkbox"
name=
"course_seat_types"
value=
"verified"
>
<label
for=
"verified"
><
%=
gettext
('
Verified
')
%
></label>
<input
id=
"professional"
type=
"checkbox"
name=
"course_seat_types"
value=
"professional"
>
<label
for=
"professional"
><
%=
gettext
('
Professional
')
%
></label>
<p
class=
"help-block"
></p>
</div>
<div
class=
"catalog_buttons"
></div>
</div>
<div
class=
"form-group"
>
<label
for=
"note"
><
%=
gettext
('
Note
')
%
></label>
<input
type=
"text"
class=
"form-control"
name=
"note"
maxlength=
"100"
>
...
...
ecommerce/static/templates/dynamic_catalog_buttons.html
0 → 100644
View file @
5e58b69d
<button
name=
"preview_catalog"
data-toggle=
"modal"
data-target=
"#catalogModal"
>
<
%=
gettext
('
Preview
')
%
>
</button>
<div
class=
"modal fade"
id=
"catalogModal"
tabindex=
"-1"
role=
"dialog"
aria-labelledby=
"catalogModalLabel"
>
<div
class=
"modal-dialog"
role=
"document"
>
<div
class=
"modal-content"
>
<div
class=
"modal-header"
>
<button
type=
"button"
class=
"close"
data-dismiss=
"modal"
aria-label=
"Close"
><span
aria-hidden=
"true"
>
×
</span></button>
<h4
class=
"modal-title"
id=
"catalogModalLabel"
><
%=
gettext
('
Catalog
Details
')
%
></h4>
</div>
<div
class=
"modal-body"
>
<table
id=
"coursesTable"
class=
"copy copy-base table table-striped table-bordered"
cellspacing=
"0"
>
</table>
</div>
</div>
</div>
</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