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
c5746e6d
Commit
c5746e6d
authored
Jan 09, 2015
by
chrisndodge
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6071 from edx/muhhshoaib/WL-135-add-expiration-dates-to-Coupon-codes
Add expiration dates to Coupon Codes
parents
17c2af23
10f8d8c0
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
478 additions
and
27 deletions
+478
-27
lms/djangoapps/instructor/tests/test_api.py
+22
-1
lms/djangoapps/instructor/tests/test_ecommerce.py
+19
-2
lms/djangoapps/instructor/views/api.py
+10
-2
lms/djangoapps/instructor/views/coupons.py
+19
-3
lms/djangoapps/instructor_analytics/basic.py
+2
-1
lms/djangoapps/instructor_analytics/tests/test_basic.py
+22
-2
lms/djangoapps/shoppingcart/migrations/0023_auto__add_field_coupon_expiration_date.py
+220
-0
lms/djangoapps/shoppingcart/models.py
+9
-0
lms/djangoapps/shoppingcart/tests/test_views.py
+1
-1
lms/djangoapps/shoppingcart/views.py
+9
-4
lms/static/js/instructor_dashboard/ecommerce.js
+36
-0
lms/static/js/spec/instructor_dashboard/ecommerce_spec.js
+36
-0
lms/static/js/spec/main.js
+5
-0
lms/static/sass/course/instructor/_instructor_2.scss
+31
-5
lms/templates/instructor/instructor_dashboard_2/add_coupon_modal.html
+6
-0
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
+24
-6
lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html
+6
-0
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
+1
-0
No files found.
lms/djangoapps/instructor/tests/test_api.py
View file @
c5746e6d
...
...
@@ -4,6 +4,8 @@ Unit tests for instructor.api methods.
"""
import
datetime
import
ddt
import
random
import
pytz
import
io
import
json
import
os
...
...
@@ -61,7 +63,7 @@ from .test_tools import msk_from_problem_urlname
from
..views.tools
import
get_extended_due
EXPECTED_CSV_HEADER
=
'"code","course_id","company_name","created_by","redeemed_by","invoice_id","purchaser","customer_reference_number","internal_reference"'
EXPECTED_COUPON_CSV_HEADER
=
'"course_id","percentage_discount","code_redeemed_count","description"'
EXPECTED_COUPON_CSV_HEADER
=
'"co
de","co
urse_id","percentage_discount","code_redeemed_count","description"'
# ddt data for test cases involving reports
REPORTS_DATA
=
(
...
...
@@ -3331,8 +3333,27 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
)
coupon
.
save
()
#now create coupons with the expiration dates
for
i
in
range
(
5
):
coupon
=
Coupon
(
code
=
'coupon{0}'
.
format
(
i
),
description
=
'test_description'
,
course_id
=
self
.
course
.
id
,
percentage_discount
=
'{0}'
.
format
(
i
),
created_by
=
self
.
instructor
,
is_active
=
True
,
expiration_date
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
)
coupon
.
save
()
response
=
self
.
client
.
get
(
get_coupon_code_url
)
self
.
assertEqual
(
response
.
status_code
,
200
,
response
.
content
)
# filter all the coupons
for
coupon
in
Coupon
.
objects
.
all
():
self
.
assertIn
(
'"{code}","{course_id}","{discount}","0","{description}","{expiration_date}"'
.
format
(
code
=
coupon
.
code
,
course_id
=
coupon
.
course_id
,
discount
=
coupon
.
percentage_discount
,
description
=
coupon
.
description
,
expiration_date
=
coupon
.
display_expiry_date
),
response
.
content
)
self
.
assertEqual
(
response
[
'Content-Type'
],
'text/csv'
)
body
=
response
.
content
.
replace
(
'
\r
'
,
''
)
self
.
assertTrue
(
body
.
startswith
(
EXPECTED_COUPON_CSV_HEADER
))
...
...
lms/djangoapps/instructor/tests/test_ecommerce.py
View file @
c5746e6d
...
...
@@ -3,6 +3,8 @@ Unit tests for Ecommerce feature flag in new instructor dashboard.
"""
from
django.core.urlresolvers
import
reverse
import
datetime
import
pytz
from
django.test.utils
import
override_settings
from
mock
import
patch
...
...
@@ -144,13 +146,26 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
"""
# URL for add_coupon
add_coupon_url
=
reverse
(
'add_coupon'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
expiration_date
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
data
=
{
'code'
:
'A2314'
,
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'description'
:
'ADSADASDSAD'
,
'created_by'
:
self
.
instructor
,
'discount'
:
5
'description'
:
'ADSADASDSAD'
,
'created_by'
:
self
.
instructor
,
'discount'
:
5
,
'expiration_date'
:
'{month}/{day}/{year}'
.
format
(
month
=
expiration_date
.
month
,
day
=
expiration_date
.
day
,
year
=
expiration_date
.
year
)
}
response
=
self
.
client
.
post
(
add_coupon_url
,
data
)
self
.
assertTrue
(
"coupon with the coupon code ({code}) added successfully"
.
format
(
code
=
data
[
'code'
])
in
response
.
content
)
#now add the coupon with the wrong value in the expiration_date
# server will through the ValueError Exception in the expiration_date field
data
=
{
'code'
:
'213454'
,
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'description'
:
'ADSADASDSAD'
,
'created_by'
:
self
.
instructor
,
'discount'
:
5
,
'expiration_date'
:
expiration_date
.
strftime
(
'"
%
d/
%
m/
%
Y'
)
}
response
=
self
.
client
.
post
(
add_coupon_url
,
data
)
self
.
assertTrue
(
"Please enter the date in this format i-e month/day/year"
in
response
.
content
)
data
=
{
'code'
:
'A2314'
,
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'description'
:
'asdsasda'
,
'created_by'
:
self
.
instructor
,
'discount'
:
99
...
...
@@ -221,13 +236,15 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
"""
coupon
=
Coupon
(
code
=
'AS452'
,
description
=
'asdsadsa'
,
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
percentage_discount
=
10
,
created_by
=
self
.
instructor
percentage_discount
=
10
,
created_by
=
self
.
instructor
,
expiration_date
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
)
coupon
.
save
()
# URL for edit_coupon_info
edit_url
=
reverse
(
'get_coupon_info'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
edit_url
,
{
'id'
:
coupon
.
id
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) updated successfully'
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
self
.
assertIn
(
coupon
.
display_expiry_date
,
response
.
content
)
response
=
self
.
client
.
post
(
edit_url
,
{
'id'
:
444444
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) DoesNotExist'
.
format
(
coupon_id
=
444444
)
in
response
.
content
)
...
...
lms/djangoapps/instructor/views/api.py
View file @
c5746e6d
...
...
@@ -18,6 +18,7 @@ from django.views.decorators.cache import cache_control
from
django.core.exceptions
import
ValidationError
,
PermissionDenied
from
django.core.mail.message
import
EmailMessage
from
django.db
import
IntegrityError
from
django.db.models
import
Q
from
django.core.urlresolvers
import
reverse
from
django.core.validators
import
validate_email
from
django.utils.translation
import
ugettext
as
_
...
...
@@ -28,6 +29,8 @@ import random
import
unicodecsv
import
urllib
from
util.file
import
store_uploaded_file
,
course_and_time_based_filename_generator
,
FileValidationException
,
UniversalNewlineIterator
import
datetime
import
pytz
from
util.json_request
import
JsonResponse
from
instructor.views.instructor_task_helpers
import
extract_email_features
,
extract_task_features
...
...
@@ -1007,9 +1010,14 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
Respond with csv which contains a summary of all Active Coupons.
"""
course_id
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
active_coupons
=
Coupon
.
objects
.
filter
(
course_id
=
course_id
,
is_active
=
True
)
active_coupons
=
Coupon
.
objects
.
filter
(
Q
(
course_id
=
course_id
),
Q
(
is_active
=
True
),
Q
(
expiration_date__gt
=
datetime
.
datetime
.
now
(
pytz
.
UTC
))
|
Q
(
expiration_date__isnull
=
True
)
)
query_features
=
[
'co
urse_id'
,
'percentage_discount'
,
'code_redeemed_count'
,
'description
'
'co
de'
,
'course_id'
,
'percentage_discount'
,
'code_redeemed_count'
,
'description'
,
'expiration_date
'
]
coupons_list
=
instructor_analytics
.
basic
.
coupon_codes_features
(
query_features
,
active_coupons
)
header
,
data_rows
=
instructor_analytics
.
csvs
.
format_dictlist
(
coupons_list
,
query_features
)
...
...
lms/djangoapps/instructor/views/coupons.py
View file @
c5746e6d
...
...
@@ -10,7 +10,8 @@ from util.json_request import JsonResponse
from
django.http
import
HttpResponse
,
HttpResponseNotFound
from
shoppingcart.models
import
Coupon
,
CourseRegistrationCode
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
import
datetime
import
pytz
import
logging
log
=
logging
.
getLogger
(
__name__
)
...
...
@@ -79,9 +80,22 @@ def add_coupon(request, course_id): # pylint: disable=unused-argument
return
JsonResponse
({
'message'
:
_
(
"Please Enter the Coupon Discount Value Less than or Equal to 100"
)
},
status
=
400
)
# status code 400: Bad Request
expiration_date
=
None
if
request
.
POST
.
get
(
'expiration_date'
):
expiration_date
=
request
.
POST
.
get
(
'expiration_date'
)
try
:
expiration_date
=
datetime
.
datetime
.
strptime
(
expiration_date
,
"
%
m/
%
d/
%
Y"
)
.
replace
(
tzinfo
=
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
1
)
except
ValueError
:
return
JsonResponse
({
'message'
:
_
(
"Please enter the date in this format i-e month/day/year"
)
},
status
=
400
)
# status code 400: Bad Request
coupon
=
Coupon
(
code
=
code
,
description
=
description
,
course_id
=
course_id
,
percentage_discount
=
discount
,
created_by_id
=
request
.
user
.
id
code
=
code
,
description
=
description
,
course_id
=
course_id
,
percentage_discount
=
discount
,
created_by_id
=
request
.
user
.
id
,
expiration_date
=
expiration_date
)
coupon
.
save
()
return
JsonResponse
(
...
...
@@ -143,10 +157,12 @@ def get_coupon_info(request, course_id): # pylint: disable=unused-argument
'message'
:
_
(
"coupon with the coupon id ({coupon_id}) is already inactive"
)
.
format
(
coupon_id
=
coupon_id
)
},
status
=
400
)
# status code 400: Bad Request
expiry_date
=
coupon
.
display_expiry_date
return
JsonResponse
({
'coupon_code'
:
coupon
.
code
,
'coupon_description'
:
coupon
.
description
,
'coupon_course_id'
:
coupon
.
course_id
.
to_deprecated_string
(),
'coupon_discount'
:
coupon
.
percentage_discount
,
'expiry_date'
:
expiry_date
,
'message'
:
_
(
'coupon with the coupon id ({coupon_id}) updated successfully'
)
.
format
(
coupon_id
=
coupon_id
)
})
# status code 200: OK by default
lms/djangoapps/instructor_analytics/basic.py
View file @
c5746e6d
...
...
@@ -30,7 +30,7 @@ SALE_ORDER_FEATURES = ('id', 'company_name', 'company_contact_name', 'company_co
AVAILABLE_FEATURES
=
STUDENT_FEATURES
+
PROFILE_FEATURES
COURSE_REGISTRATION_FEATURES
=
(
'code'
,
'course_id'
,
'created_by'
,
'created_at'
)
COUPON_FEATURES
=
(
'co
urse_id'
,
'percentage_discount'
,
'description
'
)
COUPON_FEATURES
=
(
'co
de'
,
'course_id'
,
'percentage_discount'
,
'description'
,
'expiration_date
'
)
def
sale_order_record_features
(
course_id
,
features
):
...
...
@@ -228,6 +228,7 @@ def coupon_codes_features(features, coupons_list):
# codes csv. In the case of active and generated registration codes the redeemed_by value will be None.
# They have not been redeemed yet
coupon_dict
[
'expiration_date'
]
=
coupon
.
display_expiry_date
coupon_dict
[
'course_id'
]
=
coupon_dict
[
'course_id'
]
.
to_deprecated_string
()
return
coupon_dict
return
[
extract_coupon
(
coupon
,
features
)
for
coupon
in
coupons_list
]
...
...
lms/djangoapps/instructor_analytics/tests/test_basic.py
View file @
c5746e6d
...
...
@@ -22,6 +22,10 @@ from courseware.tests.factories import InstructorFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
import
datetime
from
django.db.models
import
Q
import
pytz
class
TestAnalyticsBasic
(
ModuleStoreTestCase
):
""" Test basic analytics functions. """
...
...
@@ -303,7 +307,7 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
def
test_coupon_codes_features
(
self
):
query_features
=
[
'course_id'
,
'percentage_discount'
,
'code_redeemed_count'
,
'description'
'course_id'
,
'percentage_discount'
,
'code_redeemed_count'
,
'description'
,
'expiration_date'
]
for
i
in
range
(
10
):
coupon
=
Coupon
(
...
...
@@ -314,13 +318,29 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
is_active
=
True
)
coupon
.
save
()
active_coupons
=
Coupon
.
objects
.
filter
(
course_id
=
self
.
course
.
id
,
is_active
=
True
)
#now create coupons with the expiration dates
for
i
in
range
(
5
):
coupon
=
Coupon
(
code
=
'coupon{0}'
.
format
(
i
),
description
=
'test_description'
,
course_id
=
self
.
course
.
id
,
percentage_discount
=
'{0}'
.
format
(
i
),
created_by
=
self
.
instructor
,
is_active
=
True
,
expiration_date
=
datetime
.
datetime
.
now
(
pytz
.
UTC
)
+
datetime
.
timedelta
(
days
=
2
)
)
coupon
.
save
()
active_coupons
=
Coupon
.
objects
.
filter
(
Q
(
course_id
=
self
.
course
.
id
),
Q
(
is_active
=
True
),
Q
(
expiration_date__gt
=
datetime
.
datetime
.
now
(
pytz
.
UTC
))
|
Q
(
expiration_date__isnull
=
True
)
)
active_coupons_list
=
coupon_codes_features
(
query_features
,
active_coupons
)
self
.
assertEqual
(
len
(
active_coupons_list
),
len
(
active_coupons
))
for
active_coupon
in
active_coupons_list
:
self
.
assertEqual
(
set
(
active_coupon
.
keys
()),
set
(
query_features
))
self
.
assertIn
(
active_coupon
[
'percentage_discount'
],
[
coupon
.
percentage_discount
for
coupon
in
active_coupons
])
self
.
assertIn
(
active_coupon
[
'description'
],
[
coupon
.
description
for
coupon
in
active_coupons
])
if
active_coupon
[
'expiration_date'
]:
self
.
assertIn
(
active_coupon
[
'expiration_date'
],
[
coupon
.
display_expiry_date
for
coupon
in
active_coupons
])
self
.
assertIn
(
active_coupon
[
'course_id'
],
[
coupon
.
course_id
.
to_deprecated_string
()
for
coupon
in
active_coupons
]
...
...
lms/djangoapps/shoppingcart/migrations/0023_auto__add_field_coupon_expiration_date.py
0 → 100644
View file @
c5746e6d
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
SchemaMigration
from
django.db
import
models
class
Migration
(
SchemaMigration
):
def
forwards
(
self
,
orm
):
# Adding field 'Coupon.expiration_date'
db
.
add_column
(
'shoppingcart_coupon'
,
'expiration_date'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
null
=
True
,
blank
=
True
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'Coupon.expiration_date'
db
.
delete_column
(
'shoppingcart_coupon'
,
'expiration_date'
)
models
=
{
'auth.group'
:
{
'Meta'
:
{
'object_name'
:
'Group'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'80'
}),
'permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
})
},
'auth.permission'
:
{
'Meta'
:
{
'ordering'
:
"('content_type__app_label', 'content_type__model', 'codename')"
,
'unique_together'
:
"(('content_type', 'codename'),)"
,
'object_name'
:
'Permission'
},
'codename'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'content_type'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['contenttypes.ContentType']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'50'
})
},
'auth.user'
:
{
'Meta'
:
{
'object_name'
:
'User'
},
'date_joined'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'email'
:
(
'django.db.models.fields.EmailField'
,
[],
{
'max_length'
:
'75'
,
'blank'
:
'True'
}),
'first_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'groups'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Group']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'is_staff'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'is_superuser'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'last_login'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'last_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'30'
,
'blank'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'user_permissions'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.Permission']"
,
'symmetrical'
:
'False'
,
'blank'
:
'True'
}),
'username'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'30'
})
},
'contenttypes.contenttype'
:
{
'Meta'
:
{
'ordering'
:
"('name',)"
,
'unique_together'
:
"(('app_label', 'model'),)"
,
'object_name'
:
'ContentType'
,
'db_table'
:
"'django_content_type'"
},
'app_label'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'model'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
})
},
'shoppingcart.certificateitem'
:
{
'Meta'
:
{
'object_name'
:
'CertificateItem'
,
'_ormbases'
:
[
'shoppingcart.OrderItem'
]},
'course_enrollment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['student.CourseEnrollment']"
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.SlugField'
,
[],
{
'max_length'
:
'50'
}),
'orderitem_ptr'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['shoppingcart.OrderItem']"
,
'unique'
:
'True'
,
'primary_key'
:
'True'
})
},
'shoppingcart.coupon'
:
{
'Meta'
:
{
'object_name'
:
'Coupon'
},
'code'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2015, 1, 6, 0, 0)'
}),
'created_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
}),
'description'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'expiration_date'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'percentage_discount'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
})
},
'shoppingcart.couponredemption'
:
{
'Meta'
:
{
'object_name'
:
'CouponRedemption'
},
'coupon'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Coupon']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'order'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Order']"
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'shoppingcart.courseregcodeitem'
:
{
'Meta'
:
{
'object_name'
:
'CourseRegCodeItem'
,
'_ormbases'
:
[
'shoppingcart.OrderItem'
]},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.SlugField'
,
[],
{
'default'
:
"'honor'"
,
'max_length'
:
'50'
}),
'orderitem_ptr'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['shoppingcart.OrderItem']"
,
'unique'
:
'True'
,
'primary_key'
:
'True'
})
},
'shoppingcart.courseregcodeitemannotation'
:
{
'Meta'
:
{
'object_name'
:
'CourseRegCodeItemAnnotation'
},
'annotation'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'shoppingcart.courseregistrationcode'
:
{
'Meta'
:
{
'object_name'
:
'CourseRegistrationCode'
},
'code'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2015, 1, 6, 0, 0)'
}),
'created_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'created_by_user'"
,
'to'
:
"orm['auth.User']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'invoice'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Invoice']"
,
'null'
:
'True'
}),
'order'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'purchase_order'"
,
'null'
:
'True'
,
'to'
:
"orm['shoppingcart.Order']"
})
},
'shoppingcart.donation'
:
{
'Meta'
:
{
'object_name'
:
'Donation'
,
'_ormbases'
:
[
'shoppingcart.OrderItem'
]},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'donation_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'general'"
,
'max_length'
:
'32'
}),
'orderitem_ptr'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['shoppingcart.OrderItem']"
,
'unique'
:
'True'
,
'primary_key'
:
'True'
})
},
'shoppingcart.donationconfiguration'
:
{
'Meta'
:
{
'object_name'
:
'DonationConfiguration'
},
'change_date'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'blank'
:
'True'
}),
'changed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'null'
:
'True'
,
'on_delete'
:
'models.PROTECT'
}),
'enabled'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'shoppingcart.invoice'
:
{
'Meta'
:
{
'object_name'
:
'Invoice'
},
'address_line_1'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'address_line_2'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'address_line_3'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'city'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'company_contact_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'company_contact_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'company_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'country'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'null'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'customer_reference_number'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'63'
,
'null'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'internal_reference'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'is_valid'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'recipient_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'recipient_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'state'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'total_amount'
:
(
'django.db.models.fields.FloatField'
,
[],
{}),
'zip'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'15'
,
'null'
:
'True'
})
},
'shoppingcart.order'
:
{
'Meta'
:
{
'object_name'
:
'Order'
},
'bill_to_cardtype'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'blank'
:
'True'
}),
'bill_to_ccnum'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'8'
,
'blank'
:
'True'
}),
'bill_to_city'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_country'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_first'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_last'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_postalcode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'16'
,
'blank'
:
'True'
}),
'bill_to_state'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'8'
,
'blank'
:
'True'
}),
'bill_to_street1'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'bill_to_street2'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'company_contact_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'company_contact_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'company_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'currency'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'usd'"
,
'max_length'
:
'8'
}),
'customer_reference_number'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'63'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'order_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'personal'"
,
'max_length'
:
'32'
}),
'processor_reply_dump'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'purchase_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'recipient_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'recipient_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'refunded_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'status'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'cart'"
,
'max_length'
:
'32'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'shoppingcart.orderitem'
:
{
'Meta'
:
{
'object_name'
:
'OrderItem'
},
'created'
:
(
'model_utils.fields.AutoCreatedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'currency'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'usd'"
,
'max_length'
:
'8'
}),
'fulfilled_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'line_desc'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'Misc. Item'"
,
'max_length'
:
'1024'
}),
'list_price'
:
(
'django.db.models.fields.DecimalField'
,
[],
{
'null'
:
'True'
,
'max_digits'
:
'30'
,
'decimal_places'
:
'2'
}),
'modified'
:
(
'model_utils.fields.AutoLastModifiedField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'order'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Order']"
}),
'qty'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'1'
}),
'refund_requested_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
,
'db_index'
:
'True'
}),
'report_comments'
:
(
'django.db.models.fields.TextField'
,
[],
{
'default'
:
"''"
}),
'service_fee'
:
(
'django.db.models.fields.DecimalField'
,
[],
{
'default'
:
'0.0'
,
'max_digits'
:
'30'
,
'decimal_places'
:
'2'
}),
'status'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'cart'"
,
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'unit_cost'
:
(
'django.db.models.fields.DecimalField'
,
[],
{
'default'
:
'0.0'
,
'max_digits'
:
'30'
,
'decimal_places'
:
'2'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'shoppingcart.paidcourseregistration'
:
{
'Meta'
:
{
'object_name'
:
'PaidCourseRegistration'
,
'_ormbases'
:
[
'shoppingcart.OrderItem'
]},
'course_enrollment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['student.CourseEnrollment']"
,
'null'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.SlugField'
,
[],
{
'default'
:
"'honor'"
,
'max_length'
:
'50'
}),
'orderitem_ptr'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['shoppingcart.OrderItem']"
,
'unique'
:
'True'
,
'primary_key'
:
'True'
})
},
'shoppingcart.paidcourseregistrationannotation'
:
{
'Meta'
:
{
'object_name'
:
'PaidCourseRegistrationAnnotation'
},
'annotation'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'128'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'shoppingcart.registrationcoderedemption'
:
{
'Meta'
:
{
'object_name'
:
'RegistrationCodeRedemption'
},
'course_enrollment'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['student.CourseEnrollment']"
,
'null'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'order'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Order']"
,
'null'
:
'True'
}),
'redeemed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2015, 1, 6, 0, 0)'
,
'null'
:
'True'
}),
'redeemed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
}),
'registration_code'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.CourseRegistrationCode']"
})
},
'student.courseenrollment'
:
{
'Meta'
:
{
'ordering'
:
"('user', 'course_id')"
,
'unique_together'
:
"(('user', 'course_id'),)"
,
'object_name'
:
'CourseEnrollment'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'honor'"
,
'max_length'
:
'100'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'shoppingcart'
]
\ No newline at end of file
lms/djangoapps/shoppingcart/models.py
View file @
c5746e6d
...
...
@@ -2,6 +2,7 @@
from
collections
import
namedtuple
from
datetime
import
datetime
from
datetime
import
timedelta
from
decimal
import
Decimal
import
analytics
import
pytz
...
...
@@ -803,12 +804,20 @@ class Coupon(models.Model):
created_by
=
models
.
ForeignKey
(
User
)
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
is_active
=
models
.
BooleanField
(
default
=
True
)
expiration_date
=
models
.
DateTimeField
(
null
=
True
,
blank
=
True
)
def
__unicode__
(
self
):
return
"[Coupon] code: {} course: {}"
.
format
(
self
.
code
,
self
.
course_id
)
objects
=
SoftDeleteCouponManager
()
@property
def
display_expiry_date
(
self
):
"""
return the coupon expiration date in the readable format
"""
return
(
self
.
expiration_date
-
timedelta
(
days
=
1
))
.
strftime
(
"
%
B
%
d,
%
Y"
)
if
self
.
expiration_date
else
None
class
CouponRedemption
(
models
.
Model
):
"""
...
...
lms/djangoapps/shoppingcart/tests/test_views.py
View file @
c5746e6d
...
...
@@ -369,7 +369,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'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
)
self
.
assertIn
(
"
Discount does not exist against code '{0}'
."
.
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'
...
...
lms/djangoapps/shoppingcart/views.py
View file @
c5746e6d
...
...
@@ -2,6 +2,7 @@ import logging
import
datetime
import
decimal
import
pytz
from
django.db.models
import
Q
from
django.conf
import
settings
from
django.contrib.auth.models
import
Group
from
django.http
import
(
...
...
@@ -258,7 +259,12 @@ def use_code(request):
Registration Code Redemption page.
"""
code
=
request
.
POST
[
"code"
]
coupons
=
Coupon
.
objects
.
filter
(
code
=
code
,
is_active
=
True
)
coupons
=
Coupon
.
objects
.
filter
(
Q
(
code
=
code
),
Q
(
is_active
=
True
),
Q
(
expiration_date__gt
=
datetime
.
datetime
.
now
(
pytz
.
UTC
))
|
Q
(
expiration_date__isnull
=
True
)
)
if
not
coupons
:
# If no coupons then we check that code against course registration code
try
:
...
...
@@ -423,9 +429,8 @@ def use_coupon_code(coupons, user):
return
HttpResponseBadRequest
(
_
(
"Only one coupon redemption is allowed against an order"
))
if
not
is_redemption_applied
:
log
.
warning
(
"Course item does not exist for coupon '{code}'"
.
format
(
code
=
coupons
[
0
]
.
code
))
return
HttpResponseNotFound
(
_
(
"Coupon '{code}' is not valid for any course in the shopping cart."
.
format
(
code
=
coupons
[
0
]
.
code
)))
log
.
warning
(
"Discount does not exist against code '{code}'."
.
format
(
code
=
coupons
[
0
]
.
code
))
return
HttpResponseNotFound
(
_
(
"Discount does not exist against code '{code}'."
.
format
(
code
=
coupons
[
0
]
.
code
)))
return
HttpResponse
(
json
.
dumps
({
'response'
:
'success'
,
'coupon_code_applied'
:
True
}),
...
...
lms/static/js/instructor_dashboard/ecommerce.js
0 → 100644
View file @
c5746e6d
var
edx
=
edx
||
{};
(
function
(
Backbone
,
$
,
_
)
{
'use strict'
;
edx
.
instructor_dashboard
=
edx
.
instructor_dashboard
||
{};
edx
.
instructor_dashboard
.
ecommerce
=
{};
edx
.
instructor_dashboard
.
ecommerce
.
ExpiryCouponView
=
Backbone
.
View
.
extend
({
el
:
'li#add-coupon-modal-field-expiry'
,
events
:
{
'click input[type="checkbox"]'
:
'clicked'
},
initialize
:
function
()
{
$
(
'li#add-coupon-modal-field-expiry input[name="expiration_date"]'
).
hide
();
_
.
bindAll
(
this
,
'clicked'
);
},
clicked
:
function
(
event
)
{
if
(
event
.
currentTarget
.
checked
)
{
this
.
$el
.
find
(
'#coupon_expiration_date'
).
show
();
this
.
$el
.
find
(
'#coupon_expiration_date'
).
focus
();
}
else
{
this
.
$el
.
find
(
'#coupon_expiration_date'
).
hide
();
}
}
});
$
(
function
()
{
$
(
"#coupon_expiration_date"
).
datepicker
({
minDate
:
0
});
var
view
=
new
edx
.
instructor_dashboard
.
ecommerce
.
ExpiryCouponView
();
});
}).
call
(
this
,
Backbone
,
$
,
_
);
\ No newline at end of file
lms/static/js/spec/instructor_dashboard/ecommerce_spec.js
0 → 100644
View file @
c5746e6d
define
([
'backbone'
,
'jquery'
,
'js/instructor_dashboard/ecommerce'
,
'js/common_helpers/template_helpers'
],
function
(
Backbone
,
$
,
ExpiryCouponView
,
TemplateHelpers
)
{
'use strict'
;
var
expiryCouponView
,
createExpiryCoupon
;
describe
(
"edx.instructor_dashboard.ecommerce.ExpiryCouponView"
,
function
()
{
beforeEach
(
function
()
{
setFixtures
(
'<li class="field full-width" id="add-coupon-modal-field-expiry"><input id="expiry-check" type="checkbox"/><label for="expiry-check"></label><input type="text" id="coupon_expiration_date" class="field" name="expiration_date" aria-required="true"/></li>'
)
expiryCouponView
=
new
ExpiryCouponView
();
});
it
(
"is defined"
,
function
()
{
expect
(
expiryCouponView
).
toBeDefined
();
});
it
(
"triggers the callback when the checkbox is clicked"
,
function
()
{
var
target
=
expiryCouponView
.
$el
.
find
(
'input[type="checkbox"]'
);
spyOn
(
expiryCouponView
,
'clicked'
);
expiryCouponView
.
delegateEvents
();
target
.
click
();
expect
(
expiryCouponView
.
clicked
).
toHaveBeenCalled
();
});
it
(
"shows the input field when the checkbox is checked"
,
function
()
{
var
target
=
expiryCouponView
.
$el
.
find
(
'input[type="checkbox"]'
);
target
.
attr
(
"checked"
,
"checked"
);
target
.
click
();
expect
(
expiryCouponView
.
$el
.
find
(
'#coupon_expiration_date'
)).
toHaveAttr
(
'style'
,
'display: inline;'
);
});
it
(
"hides the input field when the checkbox is unchecked"
,
function
()
{
var
target
=
expiryCouponView
.
$el
.
find
(
'input[type="checkbox"]'
);
expect
(
expiryCouponView
.
$el
.
find
(
'#coupon_expiration_date'
)).
toHaveAttr
(
'style'
,
'display: none;'
);
});
});
});
lms/static/js/spec/main.js
View file @
c5746e6d
...
...
@@ -270,6 +270,10 @@
},
// Backbone classes loaded explicitly until they are converted to use RequireJS
'js/instructor_dashboard/ecommerce'
:
{
exports
:
'edx.instructor_dashboard.ecommerce.ExpiryCouponView'
,
deps
:
[
'backbone'
,
'jquery'
,
'underscore'
]
},
'js/models/cohort'
:
{
exports
:
'CohortModel'
,
deps
:
[
'backbone'
]
...
...
@@ -497,6 +501,7 @@
'lms/include/js/spec/views/file_uploader_spec.js'
,
'lms/include/js/spec/dashboard/donation.js'
,
'lms/include/js/spec/shoppingcart/shoppingcart_spec.js'
,
'lms/include/js/spec/instructor_dashboard/ecommerce_spec.js'
,
'lms/include/js/spec/student_account/account_spec.js'
,
'lms/include/js/spec/student_account/access_spec.js'
,
'lms/include/js/spec/student_account/login_spec.js'
,
...
...
lms/static/sass/course/instructor/_instructor_2.scss
View file @
c5746e6d
...
...
@@ -12,6 +12,9 @@
}
}
#ui-datepicker-div
{
z-index
:
12000
!
important
;
width
:
16
.5em
!
important
}
.instructor-dashboard-wrapper-2
{
position
:
relative
;
// display: table;
...
...
@@ -1325,12 +1328,13 @@ input[name="subject"] {
th
{
text-align
:
left
;
border-bottom
:
1px
solid
$border-color-1
;
font-size
:
16px
;
&
.c_code
{
width
:
1
7
0px
;
width
:
1
1
0px
;
}
&
.c_count
{
width
:
85
px
;
width
:
60
px
;
}
&
.c_course_id
{
width
:
320px
;
...
...
@@ -1339,11 +1343,14 @@ input[name="subject"] {
&
.c_discount
{
width
:
90px
;
}
&
.c_expiry
{
width
:
150px
;
}
&
.c_action
{
width
:
89
px
;
width
:
60
px
;
}
&
.c_dsc
{
width
:
2
6
0px
;
width
:
2
0
0px
;
word-wrap
:
break-word
;
}
}
...
...
@@ -1361,6 +1368,16 @@ input[name="subject"] {
}
}
}
// in_active coupon rows style
.expired_coupon
{
background
:
#FEEFB3
!
important
;
color
:
rgba
(
51
,
51
,
51
,
0
.2
);
border-bottom
:
1px
solid
#fff
;
td
:nth-child
(
3
)
{
text-decoration
:
line-through
;
}
}
// coupon items style
.coupons-items
{
...
...
@@ -1368,6 +1385,7 @@ input[name="subject"] {
padding
:
(
$baseline
/
2
)
0
;
position
:
relative
;
line-height
:
normal
;
font-size
:
14px
;
span
.old-price
{
left
:
-75px
;
position
:
relative
;
...
...
@@ -1613,7 +1631,15 @@ input[name="subject"] {
}
}
}
#add-coupon-modal
{
ol
.list-input
{
li
{
input
[
type
=
"checkbox"
]
#expiry-check
,
input
[
type
=
"checkbox"
]
#expiry-check
+
label
{
display
:
inline-block
;
width
:
auto
;
margin-top
:
10px
;}
&
.full-width
{
width
:
100%
;}
input
#coupon_expiration_date
{
width
:
278px
;
display
:
inline-block
;
float
:
right
;}
}
}
}
}
.profile-distribution-widget
{
...
...
lms/templates/instructor/instructor_dashboard_2/add_coupon_modal.html
View file @
c5746e6d
...
...
@@ -49,6 +49,12 @@
<input
class=
"field readonly"
id=
"coupon_course_id"
type=
"text"
name=
"course_id"
value=
"${section_data['course_id'] | h}"
readonly
aria-required=
"true"
/>
</li>
<li
class=
"field full-width"
id=
"add-coupon-modal-field-expiry"
>
<input
id=
"expiry-check"
type=
"checkbox"
value=
"true"
/>
<label
for=
"expiry-check"
>
${_('Add expiration date')}
</label>
<input
type=
"text"
id=
"coupon_expiration_date"
value=
""
class=
"field"
name=
"expiration_date"
aria-required=
"true"
/>
</li>
</ol>
</fieldset>
...
...
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
View file @
c5746e6d
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
datetime
import
datetime
,
timedelta
%
>
<
%!
import
pytz
%
>
<
%
page
args=
"section_data"
/>
<
%
include
file=
"add_coupon_modal.html"
args=
"section_data=section_data"
/>
<
%
include
file=
"edit_coupon_modal.html"
args=
"section_data=section_data"
/>
...
...
@@ -95,6 +97,7 @@
<tr
class=
"coupons-headings"
>
<th
class=
"c_code"
>
${_("Code")}
</th>
<th
class=
"c_dsc"
>
${_("Description")}
</th>
<th
class=
"c_expiry"
>
${_("Expiry Date")}
</th>
<th
class=
"c_discount"
>
${_("Discount (%)")}
</th>
<th
class=
"c_count"
>
${_("Redeem Count")}
</th>
<th
class=
"c_action"
>
${_("Actions")}
</th>
...
...
@@ -102,14 +105,21 @@
</thead>
<tbody>
%for coupon in section_data['coupons']:
<
%
current_date =
datetime.now(pytz.UTC)
%
>
<
%
coupon_expiry_date =
coupon.expiration_date
%
>
%if coupon.is_active == False:
<tr
class=
"coupons-items inactive_coupon"
>
%else:
%elif coupon_expiry_date is not None and current_date >= coupon_expiry_date:
<tr
class=
"coupons-items expired_coupon"
>
%else:
<tr
class=
"coupons-items"
>
%endif
<td>
${coupon.code}
</td>
<td>
${coupon.description}
</td>
<td>
${coupon.percentage_discount}
</td>
<td>
${_('{code}').format(code=coupon.code)}
</td>
<td>
${_('{description}').format(description=coupon.description)}
</td>
<td>
${coupon.display_expiry_date}
</td>
<td>
${_('{discount}').format(discount=coupon.percentage_discount)}
</td>
<td>
${ coupon.couponredemption_set.filter(order__status='purchased').count() }
</td>
<td><a
data-item-id=
"${coupon.id}"
class=
'remove_coupon'
href=
'#'
>
[x]
</a><a
href=
"#edit-modal"
data-item-id=
"${coupon.id}"
class=
"edit-right"
>
${_('Edit')}
</a></td>
</tr>
...
...
@@ -184,7 +194,7 @@
}
if
(
$
(
'#invoice_number'
).
val
()
==
""
)
{
$
(
'#error-msg'
).
attr
(
'class'
,
'error-msgs'
)
$
(
'#error-msg'
).
html
(
"${_(
"
Invoice
number
should
not
be
empty
.
"
)}"
).
show
();
$
(
'#error-msg'
).
html
(
"${_(
'Invoice number should not be empty.'
)}"
).
show
();
return
}
$
.
ajax
({
...
...
@@ -224,6 +234,12 @@
$
(
'input#edit_coupon_discount'
).
val
(
data
.
coupon_discount
);
$
(
'textarea#edit_coupon_description'
).
val
(
data
.
coupon_description
);
$
(
'input#edit_coupon_course_id'
).
val
(
data
.
coupon_course_id
);
if
(
data
.
expiry_date
)
{
$
(
'input#edit_coupon_expiration_date'
).
val
(
data
.
expiry_date
);
}
else
{
$
(
'input#edit_coupon_expiration_date'
).
val
(
"${_('Never Expires')}"
);
}
$
(
'#edit-modal-trigger'
).
click
();
},
error
:
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
...
...
@@ -459,6 +475,7 @@
var
coupon_discount
=
$
.
trim
(
$
(
'#coupon_discount'
).
val
());
var
course_id
=
$
.
trim
(
$
(
'#coupon_course_id'
).
val
());
var
description
=
$
.
trim
(
$
(
'#coupon_description'
).
val
());
var
expiration_date
=
$
.
trim
(
$
(
'#coupon_expiration_date'
).
val
());
// Check if empty of not
if
(
code
===
''
)
{
...
...
@@ -485,7 +502,8 @@
"code"
:
code
,
"discount"
:
coupon_discount
,
"course_id"
:
course_id
,
"description"
:
description
"description"
:
description
,
"expiration_date"
:
expiration_date
},
url
:
"${section_data['ajax_add_coupon']}"
,
success
:
function
(
data
)
{
...
...
lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html
View file @
c5746e6d
...
...
@@ -49,6 +49,12 @@
<input
class=
"field readonly"
id=
"edit_coupon_course_id"
type=
"text"
name=
"course_id"
value=
""
readonly
aria-required=
"true"
/>
</li>
<li
class=
"field"
id=
"edit-coupon-modal-field-expiration-date"
>
<label
for=
"edit_coupon_expiration_date"
>
${_("Expiration Date")}
</label>
<input
class=
"field readonly"
id=
"edit_coupon_expiration_date"
type=
"text"
name=
"expiration_date"
value=
""
readonly
aria-required=
"true"
/>
</li>
</ol>
</fieldset>
...
...
lms/templates/instructor/instructor_dashboard_2/instructor_dashboard_2.html
View file @
c5746e6d
...
...
@@ -53,6 +53,7 @@
<
%
static:js
group=
'application'
/>
## Backbone classes declared explicitly until RequireJS is supported
<script
type=
"text/javascript"
src=
"${static.url('js/instructor_dashboard/ecommerce.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/models/notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/views/notification.js')}"
></script>
<script
type=
"text/javascript"
src=
"${static.url('js/views/file_uploader.js')}"
></script>
...
...
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