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
158a0a0e
Commit
158a0a0e
authored
Jan 13, 2015
by
Stephen Sanchez
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #6374 from edx/sanchez/enable_redeem_codes
Enable redeem codes for Verified Cert Courses
parents
6d184f7b
b1884306
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
804 additions
and
88 deletions
+804
-88
common/djangoapps/course_modes/models.py
+45
-20
common/djangoapps/student/migrations/0042_grant_sales_admin_roles.py
+184
-0
common/djangoapps/student/roles.py
+9
-1
common/djangoapps/student/tests/tests.py
+3
-2
lms/djangoapps/instructor/tests/test_api.py
+13
-8
lms/djangoapps/instructor/tests/test_ecommerce.py
+21
-1
lms/djangoapps/instructor/views/api.py
+68
-12
lms/djangoapps/instructor/views/instructor_dashboard.py
+29
-13
lms/djangoapps/instructor_analytics/tests/test_basic.py
+10
-2
lms/djangoapps/shoppingcart/exceptions.py
+5
-0
lms/djangoapps/shoppingcart/migrations/0024_auto__add_field_courseregistrationcode_mode_slug.py
+218
-0
lms/djangoapps/shoppingcart/models.py
+2
-1
lms/djangoapps/shoppingcart/tests/test_views.py
+51
-3
lms/djangoapps/shoppingcart/views.py
+34
-20
lms/static/sass/views/_shoppingcart.scss
+1
-1
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
+7
-1
lms/templates/shoppingcart/registration_code_receipt.html
+96
-0
lms/templates/shoppingcart/registration_code_redemption.html
+8
-3
No files found.
common/djangoapps/course_modes/models.py
View file @
158a0a0e
...
@@ -81,17 +81,7 @@ class CourseMode(models.Model):
...
@@ -81,17 +81,7 @@ class CourseMode(models.Model):
"""
"""
modes_by_course
=
defaultdict
(
list
)
modes_by_course
=
defaultdict
(
list
)
for
mode
in
cls
.
objects
.
filter
(
course_id__in
=
course_id_list
):
for
mode
in
cls
.
objects
.
filter
(
course_id__in
=
course_id_list
):
modes_by_course
[
mode
.
course_id
]
.
append
(
modes_by_course
[
mode
.
course_id
]
.
append
(
mode
.
to_tuple
())
Mode
(
mode
.
mode_slug
,
mode
.
mode_display_name
,
mode
.
min_price
,
mode
.
suggested_prices
,
mode
.
currency
,
mode
.
expiration_datetime
,
mode
.
description
)
)
# Assign default modes if nothing available in the database
# Assign default modes if nothing available in the database
missing_courses
=
set
(
course_id_list
)
-
set
(
modes_by_course
.
keys
())
missing_courses
=
set
(
course_id_list
)
-
set
(
modes_by_course
.
keys
())
...
@@ -131,6 +121,31 @@ class CourseMode(models.Model):
...
@@ -131,6 +121,31 @@ class CourseMode(models.Model):
return
(
all_modes
,
unexpired_modes
)
return
(
all_modes
,
unexpired_modes
)
@classmethod
@classmethod
def
paid_modes_for_course
(
cls
,
course_id
):
"""
Returns a list of non-expired modes for a course ID that have a set minimum price.
If no modes have been set, returns an empty list.
Args:
course_id (CourseKey): The course to find paid modes for.
Returns:
A list of CourseModes with a minimum price.
"""
now
=
datetime
.
now
(
pytz
.
UTC
)
found_course_modes
=
cls
.
objects
.
filter
(
Q
(
course_id
=
course_id
)
&
Q
(
min_price__gt
=
0
)
&
(
Q
(
expiration_datetime__isnull
=
True
)
|
Q
(
expiration_datetime__gte
=
now
)
)
)
return
[
mode
.
to_tuple
()
for
mode
in
found_course_modes
]
@classmethod
def
modes_for_course
(
cls
,
course_id
):
def
modes_for_course
(
cls
,
course_id
):
"""
"""
Returns a list of the non-expired modes for a given course id
Returns a list of the non-expired modes for a given course id
...
@@ -141,15 +156,7 @@ class CourseMode(models.Model):
...
@@ -141,15 +156,7 @@ class CourseMode(models.Model):
found_course_modes
=
cls
.
objects
.
filter
(
Q
(
course_id
=
course_id
)
&
found_course_modes
=
cls
.
objects
.
filter
(
Q
(
course_id
=
course_id
)
&
(
Q
(
expiration_datetime__isnull
=
True
)
|
(
Q
(
expiration_datetime__isnull
=
True
)
|
Q
(
expiration_datetime__gte
=
now
)))
Q
(
expiration_datetime__gte
=
now
)))
modes
=
([
Mode
(
modes
=
([
mode
.
to_tuple
()
for
mode
in
found_course_modes
])
mode
.
mode_slug
,
mode
.
mode_display_name
,
mode
.
min_price
,
mode
.
suggested_prices
,
mode
.
currency
,
mode
.
expiration_datetime
,
mode
.
description
)
for
mode
in
found_course_modes
])
if
not
modes
:
if
not
modes
:
modes
=
[
cls
.
DEFAULT_MODE
]
modes
=
[
cls
.
DEFAULT_MODE
]
return
modes
return
modes
...
@@ -359,6 +366,24 @@ class CourseMode(models.Model):
...
@@ -359,6 +366,24 @@ class CourseMode(models.Model):
modes
=
cls
.
modes_for_course
(
course_id
)
modes
=
cls
.
modes_for_course
(
course_id
)
return
min
(
mode
.
min_price
for
mode
in
modes
if
mode
.
currency
==
currency
)
return
min
(
mode
.
min_price
for
mode
in
modes
if
mode
.
currency
==
currency
)
def
to_tuple
(
self
):
"""
Takes a mode model and turns it into a model named tuple.
Returns:
A 'Model' namedtuple with all the same attributes as the model.
"""
return
Mode
(
self
.
mode_slug
,
self
.
mode_display_name
,
self
.
min_price
,
self
.
suggested_prices
,
self
.
currency
,
self
.
expiration_datetime
,
self
.
description
)
def
__unicode__
(
self
):
def
__unicode__
(
self
):
return
u"{} : {}, min={}, prices={}"
.
format
(
return
u"{} : {}, min={}, prices={}"
.
format
(
self
.
course_id
.
to_deprecated_string
(),
self
.
mode_slug
,
self
.
min_price
,
self
.
suggested_prices
self
.
course_id
.
to_deprecated_string
(),
self
.
mode_slug
,
self
.
min_price
,
self
.
suggested_prices
...
...
common/djangoapps/student/migrations/0042_grant_sales_admin_roles.py
0 → 100644
View file @
158a0a0e
# -*- coding: utf-8 -*-
import
datetime
from
south.db
import
db
from
south.v2
import
DataMigration
from
django.db
import
models
,
IntegrityError
class
Migration
(
DataMigration
):
def
forwards
(
self
,
orm
):
"""Map all Finance Admins to Sales Admins."""
finance_admins
=
orm
[
'student.courseaccessrole'
]
.
objects
.
filter
(
role
=
'finance_admin'
)
for
finance_admin
in
finance_admins
:
sales_admin
=
orm
[
'student.courseaccessrole'
](
role
=
'sales_admin'
,
user
=
finance_admin
.
user
,
org
=
finance_admin
.
org
,
course_id
=
finance_admin
.
course_id
,
)
try
:
sales_admin
.
save
()
except
IntegrityError
:
pass
# If sales admin roles exist, continue.
def
backwards
(
self
,
orm
):
"""Remove all sales administrators, as they did not exist before this migration. """
sales_admins
=
orm
[
'student.courseaccessrole'
]
.
objects
.
filter
(
role
=
'sales_admin'
)
sales_admins
.
delete
()
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'
})
},
'student.anonymoususerid'
:
{
'Meta'
:
{
'object_name'
:
'AnonymousUserId'
},
'anonymous_user_id'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'32'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.courseaccessrole'
:
{
'Meta'
:
{
'unique_together'
:
"(('user', 'org', 'course_id', 'role'),)"
,
'object_name'
:
'CourseAccessRole'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'org'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'role'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'db_index'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'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']"
})
},
'student.courseenrollmentallowed'
:
{
'Meta'
:
{
'unique_together'
:
"(('email', 'course_id'),)"
,
'object_name'
:
'CourseEnrollmentAllowed'
},
'auto_enroll'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'False'
}),
'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'
}),
'email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
})
},
'student.dashboardconfiguration'
:
{
'Meta'
:
{
'object_name'
:
'DashboardConfiguration'
},
'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'
}),
'recent_enrollment_time_delta'
:
(
'django.db.models.fields.PositiveIntegerField'
,
[],
{
'default'
:
'0'
})
},
'student.loginfailures'
:
{
'Meta'
:
{
'object_name'
:
'LoginFailures'
},
'failure_count'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'default'
:
'0'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'lockout_until'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'null'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.passwordhistory'
:
{
'Meta'
:
{
'object_name'
:
'PasswordHistory'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'password'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
}),
'time_set'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime.now'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.pendingemailchange'
:
{
'Meta'
:
{
'object_name'
:
'PendingEmailChange'
},
'activation_key'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'new_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['auth.User']"
,
'unique'
:
'True'
})
},
'student.pendingnamechange'
:
{
'Meta'
:
{
'object_name'
:
'PendingNameChange'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'new_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'rationale'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'1024'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['auth.User']"
,
'unique'
:
'True'
})
},
'student.registration'
:
{
'Meta'
:
{
'object_name'
:
'Registration'
,
'db_table'
:
"'auth_registration'"
},
'activation_key'
:
(
'django.db.models.fields.CharField'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'unique'
:
'True'
})
},
'student.userprofile'
:
{
'Meta'
:
{
'object_name'
:
'UserProfile'
,
'db_table'
:
"'auth_userprofile'"
},
'allow_certificate'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'city'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'country'
:
(
'django_countries.fields.CountryField'
,
[],
{
'max_length'
:
'2'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'courseware'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'course.xml'"
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'gender'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'6'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'goals'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'language'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'level_of_education'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'6'
,
'null'
:
'True'
,
'blank'
:
'True'
}),
'location'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'mailing_address'
:
(
'django.db.models.fields.TextField'
,
[],
{
'null'
:
'True'
,
'blank'
:
'True'
}),
'meta'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'related_name'
:
"'profile'"
,
'unique'
:
'True'
,
'to'
:
"orm['auth.User']"
}),
'year_of_birth'
:
(
'django.db.models.fields.IntegerField'
,
[],
{
'db_index'
:
'True'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
'student.usersignupsource'
:
{
'Meta'
:
{
'object_name'
:
'UserSignupSource'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'site'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
},
'student.userstanding'
:
{
'Meta'
:
{
'object_name'
:
'UserStanding'
},
'account_status'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'31'
,
'blank'
:
'True'
}),
'changed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'standing_last_changed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now'
:
'True'
,
'blank'
:
'True'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'standing'"
,
'unique'
:
'True'
,
'to'
:
"orm['auth.User']"
})
},
'student.usertestgroup'
:
{
'Meta'
:
{
'object_name'
:
'UserTestGroup'
},
'description'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'users'
:
(
'django.db.models.fields.related.ManyToManyField'
,
[],
{
'to'
:
"orm['auth.User']"
,
'db_index'
:
'True'
,
'symmetrical'
:
'False'
})
}
}
complete_apps
=
[
'student'
]
symmetrical
=
True
common/djangoapps/student/roles.py
View file @
158a0a0e
...
@@ -204,13 +204,21 @@ class CourseInstructorRole(CourseRole):
...
@@ -204,13 +204,21 @@ class CourseInstructorRole(CourseRole):
class
CourseFinanceAdminRole
(
CourseRole
):
class
CourseFinanceAdminRole
(
CourseRole
):
"""A course
Instructor
"""
"""A course
staff member with privileges to review financial data.
"""
ROLE
=
'finance_admin'
ROLE
=
'finance_admin'
def
__init__
(
self
,
*
args
,
**
kwargs
):
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
CourseFinanceAdminRole
,
self
)
.
__init__
(
self
.
ROLE
,
*
args
,
**
kwargs
)
super
(
CourseFinanceAdminRole
,
self
)
.
__init__
(
self
.
ROLE
,
*
args
,
**
kwargs
)
class
CourseSalesAdminRole
(
CourseRole
):
"""A course staff member with privileges to perform sales operations. """
ROLE
=
'sales_admin'
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
CourseSalesAdminRole
,
self
)
.
__init__
(
self
.
ROLE
,
*
args
,
**
kwargs
)
class
CourseBetaTesterRole
(
CourseRole
):
class
CourseBetaTesterRole
(
CourseRole
):
"""A course Beta Tester"""
"""A course Beta Tester"""
ROLE
=
'beta_testers'
ROLE
=
'beta_testers'
...
...
common/djangoapps/student/tests/tests.py
View file @
158a0a0e
...
@@ -280,8 +280,9 @@ class DashboardTest(ModuleStoreTestCase):
...
@@ -280,8 +280,9 @@ class DashboardTest(ModuleStoreTestCase):
recipient_name
=
'Testw_1'
,
recipient_email
=
'test2@test.com'
,
internal_reference
=
"A"
,
recipient_name
=
'Testw_1'
,
recipient_email
=
'test2@test.com'
,
internal_reference
=
"A"
,
course_id
=
self
.
course
.
id
,
is_valid
=
False
course_id
=
self
.
course
.
id
,
is_valid
=
False
)
)
course_reg_code
=
shoppingcart
.
models
.
CourseRegistrationCode
(
code
=
"abcde"
,
course_id
=
self
.
course
.
id
,
course_reg_code
=
shoppingcart
.
models
.
CourseRegistrationCode
(
created_by
=
self
.
user
,
invoice
=
sale_invoice_1
)
code
=
"abcde"
,
course_id
=
self
.
course
.
id
,
created_by
=
self
.
user
,
invoice
=
sale_invoice_1
,
mode_slug
=
'honor'
)
course_reg_code
.
save
()
course_reg_code
.
save
()
cart
=
shoppingcart
.
models
.
Order
.
get_cart_for_user
(
self
.
user
)
cart
=
shoppingcart
.
models
.
Order
.
get_cart_for_user
(
self
.
user
)
...
...
lms/djangoapps/instructor/tests/test_api.py
View file @
158a0a0e
...
@@ -13,7 +13,6 @@ import random
...
@@ -13,7 +13,6 @@ import random
import
requests
import
requests
import
shutil
import
shutil
import
tempfile
import
tempfile
from
unittest
import
TestCase
from
urllib
import
quote
from
urllib
import
quote
from
django.conf
import
settings
from
django.conf
import
settings
...
@@ -45,8 +44,8 @@ from shoppingcart.models import (
...
@@ -45,8 +44,8 @@ from shoppingcart.models import (
from
student.models
import
(
from
student.models
import
(
CourseEnrollment
,
CourseEnrollmentAllowed
,
NonExistentCourseError
CourseEnrollment
,
CourseEnrollmentAllowed
,
NonExistentCourseError
)
)
from
student.tests.factories
import
UserFactory
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.roles
import
CourseBetaTesterRole
from
student.roles
import
CourseBetaTesterRole
,
CourseSalesAdminRole
,
CourseFinanceAdminRole
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
@@ -1722,7 +1721,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -1722,7 +1721,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
2
):
for
i
in
range
(
2
):
course_registration_code
=
CourseRegistrationCode
(
course_registration_code
=
CourseRegistrationCode
(
code
=
'sale_invoice{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
code
=
'sale_invoice{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
,
mode_slug
=
'honor'
)
)
course_registration_code
.
save
()
course_registration_code
.
save
()
...
@@ -1807,6 +1806,10 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -1807,6 +1806,10 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
percentage_discount
=
'10'
,
created_by
=
self
.
instructor
,
is_active
=
True
percentage_discount
=
'10'
,
created_by
=
self
.
instructor
,
is_active
=
True
)
)
coupon
.
save
()
coupon
.
save
()
# Coupon Redeem Count only visible for Financial Admins.
CourseFinanceAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course
.
id
)
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course
.
id
)
# apply the coupon code to the item in the cart
# apply the coupon code to the item in the cart
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
coupon
.
code
})
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
coupon
.
code
})
...
@@ -1838,7 +1841,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -1838,7 +1841,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
2
):
for
i
in
range
(
2
):
course_registration_code
=
CourseRegistrationCode
(
course_registration_code
=
CourseRegistrationCode
(
code
=
'sale_invoice{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
code
=
'sale_invoice{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
,
mode_slug
=
'honor'
)
)
course_registration_code
.
save
()
course_registration_code
.
save
()
...
@@ -1853,7 +1856,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -1853,7 +1856,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
5
):
for
i
in
range
(
5
):
course_registration_code
=
CourseRegistrationCode
(
course_registration_code
=
CourseRegistrationCode
(
code
=
'sale_invoice{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
code
=
'sale_invoice{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
,
mode_slug
=
'honor'
)
)
course_registration_code
.
save
()
course_registration_code
.
save
()
...
@@ -1872,7 +1875,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -1872,7 +1875,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
5
):
for
i
in
range
(
5
):
course_registration_code
=
CourseRegistrationCode
(
course_registration_code
=
CourseRegistrationCode
(
code
=
'qwerty{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
code
=
'qwerty{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
created_by
=
self
.
instructor
,
invoice
=
self
.
sale_invoice_1
,
mode_slug
=
'honor'
)
)
course_registration_code
.
save
()
course_registration_code
.
save
()
...
@@ -1886,7 +1889,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
...
@@ -1886,7 +1889,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
5
):
for
i
in
range
(
5
):
course_registration_code
=
CourseRegistrationCode
(
course_registration_code
=
CourseRegistrationCode
(
code
=
'xyzmn{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
code
=
'xyzmn{}'
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
,
invoice
=
sale_invoice_2
created_by
=
self
.
instructor
,
invoice
=
sale_invoice_2
,
mode_slug
=
'honor'
)
)
course_registration_code
.
save
()
course_registration_code
.
save
()
...
@@ -2994,8 +2997,10 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
...
@@ -2994,8 +2997,10 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
Fixtures.
Fixtures.
"""
"""
self
.
course
=
CourseFactory
.
create
()
self
.
course
=
CourseFactory
.
create
()
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
min_price
=
50
)
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
CourseSalesAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
url
=
reverse
(
'generate_registration_codes'
,
url
=
reverse
(
'generate_registration_codes'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
...
...
lms/djangoapps/instructor/tests/test_ecommerce.py
View file @
158a0a0e
...
@@ -50,6 +50,8 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
...
@@ -50,6 +50,8 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
"""
"""
response
=
self
.
client
.
get
(
self
.
url
)
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertTrue
(
self
.
e_commerce_link
in
response
.
content
)
self
.
assertTrue
(
self
.
e_commerce_link
in
response
.
content
)
# Coupons should show up for White Label sites with priced honor modes.
self
.
assertTrue
(
'Coupons'
in
response
.
content
)
def
test_user_has_finance_admin_rights_in_e_commerce_tab
(
self
):
def
test_user_has_finance_admin_rights_in_e_commerce_tab
(
self
):
response
=
self
.
client
.
get
(
self
.
url
)
response
=
self
.
client
.
get
(
self
.
url
)
...
@@ -190,7 +192,8 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
...
@@ -190,7 +192,8 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
self
.
assertTrue
(
'Please Enter the Integer Value for Coupon Discount'
in
response
.
content
)
self
.
assertTrue
(
'Please Enter the Integer Value for Coupon Discount'
in
response
.
content
)
course_registration
=
CourseRegistrationCode
(
course_registration
=
CourseRegistrationCode
(
code
=
'Vs23Ws4j'
,
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
code
=
'Vs23Ws4j'
,
course_id
=
unicode
(
self
.
course
.
id
),
created_by
=
self
.
instructor
,
mode_slug
=
'honor'
)
)
course_registration
.
save
()
course_registration
.
save
()
...
@@ -288,3 +291,20 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
...
@@ -288,3 +291,20 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
data
[
'coupon_id'
]
=
''
# Coupon id is not provided
data
[
'coupon_id'
]
=
''
# Coupon id is not provided
response
=
self
.
client
.
post
(
update_coupon_url
,
data
=
data
)
response
=
self
.
client
.
post
(
update_coupon_url
,
data
=
data
)
self
.
assertTrue
(
'coupon id not found'
in
response
.
content
)
self
.
assertTrue
(
'coupon id not found'
in
response
.
content
)
def
test_verified_course
(
self
):
"""Verify the e-commerce panel shows up for verified courses as well, without Coupons """
# Change honor mode to verified.
original_mode
=
CourseMode
.
objects
.
get
(
course_id
=
self
.
course
.
id
,
mode_slug
=
'honor'
)
original_mode
.
delete
()
new_mode
=
CourseMode
(
course_id
=
unicode
(
self
.
course
.
id
),
mode_slug
=
'verified'
,
mode_display_name
=
'verified'
,
min_price
=
10
,
currency
=
'usd'
)
new_mode
.
save
()
# Get the response value, ensure the Coupon section is not included.
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertTrue
(
self
.
e_commerce_link
in
response
.
content
)
# Coupons should show up for White Label sites with priced honor modes.
self
.
assertFalse
(
'Coupons List'
in
response
.
content
)
lms/djangoapps/instructor/views/api.py
View file @
158a0a0e
...
@@ -28,6 +28,8 @@ import string # pylint: disable=deprecated-module
...
@@ -28,6 +28,8 @@ import string # pylint: disable=deprecated-module
import
random
import
random
import
unicodecsv
import
unicodecsv
import
urllib
import
urllib
from
student
import
auth
from
student.roles
import
CourseSalesAdminRole
from
util.file
import
store_uploaded_file
,
course_and_time_based_filename_generator
,
FileValidationException
,
UniversalNewlineIterator
from
util.file
import
store_uploaded_file
,
course_and_time_based_filename_generator
,
FileValidationException
,
UniversalNewlineIterator
import
datetime
import
datetime
import
pytz
import
pytz
...
@@ -214,7 +216,7 @@ def require_level(level):
...
@@ -214,7 +216,7 @@ def require_level(level):
def
decorator
(
func
):
# pylint: disable=missing-docstring
def
decorator
(
func
):
# pylint: disable=missing-docstring
def
wrapped
(
*
args
,
**
kwargs
):
# pylint: disable=missing-docstring
def
wrapped
(
*
args
,
**
kwargs
):
# pylint: disable=missing-docstring
request
=
args
[
0
]
request
=
args
[
0
]
course
=
get_course_by_id
(
SlashSeparatedCourseKey
.
from_deprecated
_string
(
kwargs
[
'course_id'
]))
course
=
get_course_by_id
(
CourseKey
.
from
_string
(
kwargs
[
'course_id'
]))
if
has_access
(
request
.
user
,
level
,
course
):
if
has_access
(
request
.
user
,
level
,
course
):
return
func
(
*
args
,
**
kwargs
)
return
func
(
*
args
,
**
kwargs
)
...
@@ -224,6 +226,31 @@ def require_level(level):
...
@@ -224,6 +226,31 @@ def require_level(level):
return
decorator
return
decorator
def
require_sales_admin
(
func
):
"""
Decorator for checking sales administrator access before executing an HTTP endpoint. This decorator
is designed to be used for a request based action on a course. It assumes that there will be a
request object as well as a course_id attribute to leverage to check course level privileges.
If the user does not have privileges for this operation, this will return HttpResponseForbidden (403).
"""
def
wrapped
(
request
,
course_id
):
# pylint: disable=missing-docstring
try
:
course_key
=
CourseKey
.
from_string
(
course_id
)
except
InvalidKeyError
:
log
.
error
(
u"Unable to find course with course key
%
s"
,
course_id
)
return
HttpResponseNotFound
()
access
=
auth
.
has_access
(
request
.
user
,
CourseSalesAdminRole
(
course_key
))
if
access
:
return
func
(
request
,
course_id
)
else
:
return
HttpResponseForbidden
()
return
wrapped
EMAIL_INDEX
=
0
EMAIL_INDEX
=
0
USERNAME_INDEX
=
1
USERNAME_INDEX
=
1
NAME_INDEX
=
2
NAME_INDEX
=
2
...
@@ -1024,10 +1051,21 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
...
@@ -1024,10 +1051,21 @@ def get_coupon_codes(request, course_id): # pylint: disable=unused-argument
return
instructor_analytics
.
csvs
.
create_csv_response
(
'Coupons.csv'
,
header
,
data_rows
)
return
instructor_analytics
.
csvs
.
create_csv_response
(
'Coupons.csv'
,
header
,
data_rows
)
def
save_registration_code
(
user
,
course_id
,
invoice
=
None
,
order
=
None
):
def
save_registration_code
(
user
,
course_id
,
mode_slug
,
invoice
=
None
,
order
=
None
):
"""
"""
recursive function that generate a new code every time and saves in the Course Registration Table
recursive function that generate a new code every time and saves in the Course Registration Table
if validation check passes
if validation check passes
Args:
user (User): The user creating the course registration codes.
course_id (str): The string representation of the course ID.
mode_slug (str): The Course Mode Slug associated with any enrollment made by these codes.
invoice (Invoice): (Optional) The associated invoice for this code.
order (Order): (Optional) The associated order for this code.
Returns:
The newly created CourseRegistrationCode.
"""
"""
code
=
random_code_generator
()
code
=
random_code_generator
()
...
@@ -1038,10 +1076,11 @@ def save_registration_code(user, course_id, invoice=None, order=None):
...
@@ -1038,10 +1076,11 @@ def save_registration_code(user, course_id, invoice=None, order=None):
course_registration
=
CourseRegistrationCode
(
course_registration
=
CourseRegistrationCode
(
code
=
code
,
code
=
code
,
course_id
=
course_id
.
to_deprecated_string
(
),
course_id
=
unicode
(
course_id
),
created_by
=
user
,
created_by
=
user
,
invoice
=
invoice
,
invoice
=
invoice
,
order
=
order
order
=
order
,
mode_slug
=
mode_slug
)
)
try
:
try
:
course_registration
.
save
()
course_registration
.
save
()
...
@@ -1101,13 +1140,13 @@ def get_registration_codes(request, course_id): # pylint: disable=unused-argume
...
@@ -1101,13 +1140,13 @@ def get_registration_codes(request, course_id): # pylint: disable=unused-argume
@ensure_csrf_cookie
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_
level
(
'staff'
)
@require_
sales_admin
@require_POST
@require_POST
def
generate_registration_codes
(
request
,
course_id
):
def
generate_registration_codes
(
request
,
course_id
):
"""
"""
Respond with csv which contains a summary of all Generated Codes.
Respond with csv which contains a summary of all Generated Codes.
"""
"""
course_id
=
SlashSeparatedCourseKey
.
from_deprecated
_string
(
course_id
)
course_id
=
CourseKey
.
from
_string
(
course_id
)
invoice_copy
=
False
invoice_copy
=
False
# covert the course registration code number into integer
# covert the course registration code number into integer
...
@@ -1155,15 +1194,32 @@ def generate_registration_codes(request, course_id):
...
@@ -1155,15 +1194,32 @@ def generate_registration_codes(request, course_id):
internal_reference
=
internal_reference
,
internal_reference
=
internal_reference
,
customer_reference_number
=
customer_reference_number
customer_reference_number
=
customer_reference_number
)
)
course
=
get_course_by_id
(
course_id
,
depth
=
0
)
paid_modes
=
CourseMode
.
paid_modes_for_course
(
course_id
)
if
len
(
paid_modes
)
!=
1
:
msg
=
(
u"Generating Code Redeem Codes for Course '{course_id}', which must have a single paid course mode. "
u"This is a configuration issue. Current course modes with payment options: {paid_modes}"
)
.
format
(
course_id
=
course_id
,
paid_modes
=
paid_modes
)
log
.
error
(
msg
)
return
HttpResponse
(
status
=
500
,
content
=
_
(
u"Unable to generate redeem codes because of course misconfiguration."
)
)
course_mode
=
paid_modes
[
0
]
course_price
=
course_mode
.
min_price
registration_codes
=
[]
registration_codes
=
[]
for
_
in
range
(
course_code_number
):
# pylint: disable=redefined-outer-name
for
__
in
range
(
course_code_number
):
# pylint: disable=redefined-outer-name
generated_registration_code
=
save_registration_code
(
request
.
user
,
course_id
,
sale_invoice
,
order
=
None
)
generated_registration_code
=
save_registration_code
(
request
.
user
,
course_id
,
course_mode
.
slug
,
sale_invoice
,
order
=
None
)
registration_codes
.
append
(
generated_registration_code
)
registration_codes
.
append
(
generated_registration_code
)
site_name
=
microsite
.
get_value
(
'SITE_NAME'
,
settings
.
SITE_NAME
)
site_name
=
microsite
.
get_value
(
'SITE_NAME'
,
'localhost'
)
course
=
get_course_by_id
(
course_id
,
depth
=
None
)
course_honor_mode
=
CourseMode
.
mode_for_course
(
course_id
,
'honor'
)
course_price
=
course_honor_mode
.
min_price
quantity
=
course_code_number
quantity
=
course_code_number
discount
=
(
float
(
quantity
*
course_price
)
-
float
(
sale_price
))
discount
=
(
float
(
quantity
*
course_price
)
-
float
(
sale_price
))
course_url
=
'{base_url}{course_about}'
.
format
(
course_url
=
'{base_url}{course_about}'
.
format
(
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
158a0a0e
...
@@ -4,6 +4,8 @@ Instructor Dashboard Views
...
@@ -4,6 +4,8 @@ Instructor Dashboard Views
import
logging
import
logging
import
datetime
import
datetime
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
import
uuid
import
uuid
import
pytz
import
pytz
...
@@ -15,7 +17,7 @@ from django.views.decorators.cache import cache_control
...
@@ -15,7 +17,7 @@ from django.views.decorators.cache import cache_control
from
edxmako.shortcuts
import
render_to_response
from
edxmako.shortcuts
import
render_to_response
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
django.utils.html
import
escape
from
django.utils.html
import
escape
from
django.http
import
Http404
from
django.http
import
Http404
,
HttpResponseServerError
from
django.conf
import
settings
from
django.conf
import
settings
from
util.json_request
import
JsonResponse
from
util.json_request
import
JsonResponse
from
mock
import
patch
from
mock
import
patch
...
@@ -33,7 +35,7 @@ from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
...
@@ -33,7 +35,7 @@ from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
shoppingcart.models
import
Coupon
,
PaidCourseRegistration
from
shoppingcart.models
import
Coupon
,
PaidCourseRegistration
from
course_modes.models
import
CourseMode
,
CourseModesArchive
from
course_modes.models
import
CourseMode
,
CourseModesArchive
from
student.roles
import
CourseFinanceAdminRole
from
student.roles
import
CourseFinanceAdminRole
,
CourseSalesAdminRole
from
class_dashboard.dashboard_data
import
get_section_display_name
,
get_array_section_has_problem
from
class_dashboard.dashboard_data
import
get_section_display_name
,
get_array_section_has_problem
from
.tools
import
get_units_with_due_date
,
title_or_url
,
bulk_email_is_enabled_for_course
from
.tools
import
get_units_with_due_date
,
title_or_url
,
bulk_email_is_enabled_for_course
...
@@ -47,13 +49,19 @@ log = logging.getLogger(__name__)
...
@@ -47,13 +49,19 @@ log = logging.getLogger(__name__)
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
def
instructor_dashboard_2
(
request
,
course_id
):
def
instructor_dashboard_2
(
request
,
course_id
):
""" Display the instructor dashboard for a course. """
""" Display the instructor dashboard for a course. """
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
try
:
course
=
get_course_by_id
(
course_key
,
depth
=
None
)
course_key
=
CourseKey
.
from_string
(
course_id
)
except
InvalidKeyError
:
log
.
error
(
u"Unable to find course with course key
%
s while loading the Instructor Dashboard."
,
course_id
)
return
HttpResponseServerError
()
course
=
get_course_by_id
(
course_key
,
depth
=
0
)
access
=
{
access
=
{
'admin'
:
request
.
user
.
is_staff
,
'admin'
:
request
.
user
.
is_staff
,
'instructor'
:
has_access
(
request
.
user
,
'instructor'
,
course
),
'instructor'
:
has_access
(
request
.
user
,
'instructor'
,
course
),
'finance_admin'
:
CourseFinanceAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'finance_admin'
:
CourseFinanceAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'sales_admin'
:
CourseSalesAdminRole
(
course_key
)
.
has_user
(
request
.
user
),
'staff'
:
has_access
(
request
.
user
,
'staff'
,
course
),
'staff'
:
has_access
(
request
.
user
,
'staff'
,
course
),
'forum_admin'
:
has_forum_access
(
'forum_admin'
:
has_forum_access
(
request
.
user
,
course_key
,
FORUM_ROLE_ADMINISTRATOR
request
.
user
,
course_key
,
FORUM_ROLE_ADMINISTRATOR
...
@@ -72,10 +80,18 @@ def instructor_dashboard_2(request, course_id):
...
@@ -72,10 +80,18 @@ def instructor_dashboard_2(request, course_id):
]
]
#check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
#check if there is corresponding entry in the CourseMode Table related to the Instructor Dashboard course
course_honor_mode
=
CourseMode
.
mode_for_course
(
course_key
,
'honor'
)
course_mode_has_price
=
False
course_mode_has_price
=
False
if
course_honor_mode
and
course_honor_mode
.
min_price
>
0
:
paid_modes
=
CourseMode
.
paid_modes_for_course
(
course_key
)
if
len
(
paid_modes
)
==
1
:
course_mode_has_price
=
True
course_mode_has_price
=
True
elif
len
(
paid_modes
)
>
1
:
log
.
error
(
u"Course
%
s has
%
s course modes with payment options. Course must only have "
u"one paid course mode to enable eCommerce options."
,
unicode
(
course_key
),
len
(
paid_modes
)
)
is_white_label
=
CourseMode
.
is_white_label
(
course_key
)
if
(
settings
.
FEATURES
.
get
(
'INDIVIDUAL_DUE_DATES'
)
and
access
[
'instructor'
]):
if
(
settings
.
FEATURES
.
get
(
'INDIVIDUAL_DUE_DATES'
)
and
access
[
'instructor'
]):
sections
.
insert
(
3
,
_section_extensions
(
course
))
sections
.
insert
(
3
,
_section_extensions
(
course
))
...
@@ -89,8 +105,8 @@ def instructor_dashboard_2(request, course_id):
...
@@ -89,8 +105,8 @@ def instructor_dashboard_2(request, course_id):
sections
.
append
(
_section_metrics
(
course
,
access
))
sections
.
append
(
_section_metrics
(
course
,
access
))
# Gate access to Ecommerce tab
# Gate access to Ecommerce tab
if
course_mode_has_price
:
if
course_mode_has_price
and
(
access
[
'finance_admin'
]
or
access
[
'sales_admin'
])
:
sections
.
append
(
_section_e_commerce
(
course
,
access
))
sections
.
append
(
_section_e_commerce
(
course
,
access
,
paid_modes
[
0
],
is_white_label
))
disable_buttons
=
not
_is_small_course
(
course_key
)
disable_buttons
=
not
_is_small_course
(
course_key
)
...
@@ -126,15 +142,13 @@ def instructor_dashboard_2(request, course_id):
...
@@ -126,15 +142,13 @@ def instructor_dashboard_2(request, course_id):
## section_display_name will be used to generate link titles in the nav bar.
## section_display_name will be used to generate link titles in the nav bar.
def
_section_e_commerce
(
course
,
access
):
def
_section_e_commerce
(
course
,
access
,
paid_mode
,
coupons_enabled
):
""" Provide data for the corresponding dashboard section """
""" Provide data for the corresponding dashboard section """
course_key
=
course
.
id
course_key
=
course
.
id
coupons
=
Coupon
.
objects
.
filter
(
course_id
=
course_key
)
.
order_by
(
'-is_active'
)
coupons
=
Coupon
.
objects
.
filter
(
course_id
=
course_key
)
.
order_by
(
'-is_active'
)
course_price
=
None
course_price
=
paid_mode
.
min_price
total_amount
=
None
total_amount
=
None
course_honor_mode
=
CourseMode
.
mode_for_course
(
course_key
,
'honor'
)
if
course_honor_mode
and
course_honor_mode
.
min_price
>
0
:
course_price
=
course_honor_mode
.
min_price
if
access
[
'finance_admin'
]:
if
access
[
'finance_admin'
]:
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
course_key
)
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
course_key
)
...
@@ -160,6 +174,8 @@ def _section_e_commerce(course, access):
...
@@ -160,6 +174,8 @@ def _section_e_commerce(course, access):
'set_course_mode_url'
:
reverse
(
'set_course_mode_price'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'set_course_mode_url'
:
reverse
(
'set_course_mode_price'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'download_coupon_codes_url'
:
reverse
(
'get_coupon_codes'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'download_coupon_codes_url'
:
reverse
(
'get_coupon_codes'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'coupons'
:
coupons
,
'coupons'
:
coupons
,
'sales_admin'
:
access
[
'sales_admin'
],
'coupons_enabled'
:
coupons_enabled
,
'course_price'
:
course_price
,
'course_price'
:
course_price
,
'total_amount'
:
total_amount
'total_amount'
:
total_amount
}
}
...
...
lms/djangoapps/instructor_analytics/tests/test_basic.py
View file @
158a0a0e
...
@@ -6,7 +6,8 @@ import json
...
@@ -6,7 +6,8 @@ import json
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
from
mock
import
patch
from
mock
import
patch
from
student.tests.factories
import
UserFactory
from
student.roles
import
CourseSalesAdminRole
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
shoppingcart.models
import
(
from
shoppingcart.models
import
(
CourseRegistrationCode
,
RegistrationCodeRedemption
,
Order
,
CourseRegistrationCode
,
RegistrationCodeRedemption
,
Order
,
...
@@ -149,7 +150,7 @@ class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase):
...
@@ -149,7 +150,7 @@ class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase):
for
i
in
range
(
5
):
for
i
in
range
(
5
):
course_code
=
CourseRegistrationCode
(
course_code
=
CourseRegistrationCode
(
code
=
"test_code{}"
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
code
=
"test_code{}"
.
format
(
i
),
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
created_by
=
self
.
instructor
,
invoice
=
sale_invoice
created_by
=
self
.
instructor
,
invoice
=
sale_invoice
,
mode_slug
=
'honor'
)
)
course_code
.
save
()
course_code
.
save
()
...
@@ -259,6 +260,13 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
...
@@ -259,6 +260,13 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
self
.
course
=
CourseFactory
.
create
()
self
.
course
=
CourseFactory
.
create
()
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
CourseSalesAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
# Create a paid course mode.
mode
=
CourseModeFactory
.
create
()
mode
.
course_id
=
self
.
course
.
id
mode
.
min_price
=
1
mode
.
save
()
url
=
reverse
(
'generate_registration_codes'
,
url
=
reverse
(
'generate_registration_codes'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
...
...
lms/djangoapps/shoppingcart/exceptions.py
View file @
158a0a0e
...
@@ -37,6 +37,11 @@ class MultipleCouponsNotAllowedException(InvalidCartItem):
...
@@ -37,6 +37,11 @@ class MultipleCouponsNotAllowedException(InvalidCartItem):
pass
pass
class
RedemptionCodeError
(
Exception
):
"""An error occurs while processing redemption codes. """
pass
class
ReportException
(
Exception
):
class
ReportException
(
Exception
):
pass
pass
...
...
lms/djangoapps/shoppingcart/migrations/0024_auto__add_field_courseregistrationcode_mode_slug.py
0 → 100644
View file @
158a0a0e
# -*- 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 'CourseRegistrationCode.mode_slug'
db
.
add_column
(
'shoppingcart_courseregistrationcode'
,
'mode_slug'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
100
,
null
=
True
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting field 'CourseRegistrationCode.mode_slug'
db
.
delete_column
(
'shoppingcart_courseregistrationcode'
,
'mode_slug'
)
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, 12, 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, 12, 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'
}),
'mode_slug'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'100'
,
'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, 12, 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'
]
lms/djangoapps/shoppingcart/models.py
View file @
158a0a0e
...
@@ -745,6 +745,7 @@ class CourseRegistrationCode(models.Model):
...
@@ -745,6 +745,7 @@ class CourseRegistrationCode(models.Model):
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
order
=
models
.
ForeignKey
(
Order
,
db_index
=
True
,
null
=
True
,
related_name
=
"purchase_order"
)
order
=
models
.
ForeignKey
(
Order
,
db_index
=
True
,
null
=
True
,
related_name
=
"purchase_order"
)
invoice
=
models
.
ForeignKey
(
Invoice
,
null
=
True
)
invoice
=
models
.
ForeignKey
(
Invoice
,
null
=
True
)
mode_slug
=
models
.
CharField
(
max_length
=
100
,
null
=
True
)
class
RegistrationCodeRedemption
(
models
.
Model
):
class
RegistrationCodeRedemption
(
models
.
Model
):
...
@@ -1135,7 +1136,7 @@ class CourseRegCodeItem(OrderItem):
...
@@ -1135,7 +1136,7 @@ class CourseRegCodeItem(OrderItem):
# is in another PR (for another feature)
# is in another PR (for another feature)
from
instructor.views.api
import
save_registration_code
from
instructor.views.api
import
save_registration_code
for
i
in
range
(
total_registration_codes
):
# pylint: disable=unused-variable
for
i
in
range
(
total_registration_codes
):
# pylint: disable=unused-variable
save_registration_code
(
self
.
user
,
self
.
course_id
,
invoice
=
None
,
order
=
self
.
order
)
save_registration_code
(
self
.
user
,
self
.
course_id
,
self
.
mode
,
invoice
=
None
,
order
=
self
.
order
)
log
.
info
(
"Enrolled {0} in paid course {1}, paid ${2}"
log
.
info
(
"Enrolled {0} in paid course {1}, paid ${2}"
.
format
(
self
.
user
.
email
,
self
.
course_id
,
self
.
line_cost
))
# pylint: disable=no-member
.
format
(
self
.
user
.
email
,
self
.
course_id
,
self
.
line_cost
))
# pylint: disable=no-member
...
...
lms/djangoapps/shoppingcart/tests/test_views.py
View file @
158a0a0e
...
@@ -28,6 +28,7 @@ from xmodule.modulestore.tests.django_utils import (
...
@@ -28,6 +28,7 @@ from xmodule.modulestore.tests.django_utils import (
ModuleStoreTestCase
,
mixed_store_config
ModuleStoreTestCase
,
mixed_store_config
)
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
student.roles
import
CourseSalesAdminRole
from
util.date_utils
import
get_default_time_display
from
util.date_utils
import
get_default_time_display
from
util.testing
import
UrlResetMixin
from
util.testing
import
UrlResetMixin
...
@@ -37,7 +38,7 @@ from shoppingcart.models import (
...
@@ -37,7 +38,7 @@ from shoppingcart.models import (
Coupon
,
CourseRegistrationCode
,
RegistrationCodeRedemption
,
Coupon
,
CourseRegistrationCode
,
RegistrationCodeRedemption
,
DonationConfiguration
DonationConfiguration
)
)
from
student.tests.factories
import
UserFactory
,
AdminFactory
from
student.tests.factories
import
UserFactory
,
AdminFactory
,
CourseModeFactory
from
courseware.tests.factories
import
InstructorFactory
from
courseware.tests.factories
import
InstructorFactory
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
from
course_modes.models
import
CourseMode
...
@@ -104,6 +105,10 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
...
@@ -104,6 +105,10 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
self
.
addCleanup
(
patcher
.
stop
)
self
.
addCleanup
(
patcher
.
stop
)
self
.
now
=
datetime
.
now
(
pytz
.
UTC
)
self
.
yesterday
=
self
.
now
-
timedelta
(
days
=
1
)
self
.
tomorrow
=
self
.
now
+
timedelta
(
days
=
1
)
def
get_discount
(
self
,
cost
):
def
get_discount
(
self
,
cost
):
"""
"""
This method simple return the discounted amount
This method simple return the discounted amount
...
@@ -119,13 +124,27 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
...
@@ -119,13 +124,27 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
percentage_discount
=
self
.
percentage_discount
,
created_by
=
self
.
user
,
is_active
=
is_active
)
percentage_discount
=
self
.
percentage_discount
,
created_by
=
self
.
user
,
is_active
=
is_active
)
coupon
.
save
()
coupon
.
save
()
def
add_reg_code
(
self
,
course_key
):
def
add_reg_code
(
self
,
course_key
,
mode_slug
=
'honor'
):
"""
"""
add dummy registration code into models
add dummy registration code into models
"""
"""
course_reg_code
=
CourseRegistrationCode
(
code
=
self
.
reg_code
,
course_id
=
course_key
,
created_by
=
self
.
user
)
course_reg_code
=
CourseRegistrationCode
(
code
=
self
.
reg_code
,
course_id
=
course_key
,
created_by
=
self
.
user
,
mode_slug
=
mode_slug
)
course_reg_code
.
save
()
course_reg_code
.
save
()
def
_add_course_mode
(
self
,
min_price
=
50
,
mode_slug
=
'honor'
,
expiration_date
=
None
):
"""
Adds a course mode to the test course.
"""
mode
=
CourseModeFactory
.
create
()
mode
.
course_id
=
self
.
course
.
id
mode
.
min_price
=
min_price
mode
.
mode_slug
=
mode_slug
mode
.
expiration_date
=
expiration_date
mode
.
save
()
return
mode
def
add_course_to_user_cart
(
self
,
course_key
):
def
add_course_to_user_cart
(
self
,
course_key
):
"""
"""
adding course to user cart
adding course to user cart
...
@@ -392,6 +411,31 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
...
@@ -392,6 +411,31 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Cart item quantity should not be greater than 1 when applying activation code"
,
resp
.
content
)
self
.
assertIn
(
"Cart item quantity should not be greater than 1 when applying activation code"
,
resp
.
content
)
@ddt.data
(
True
,
False
)
def
test_reg_code_uses_associated_mode
(
self
,
expired_mode
):
"""Tests the use of reg codes on verified courses, expired or active. """
course_key
=
self
.
course_key
.
to_deprecated_string
()
expiration_date
=
self
.
yesterday
if
expired_mode
else
self
.
tomorrow
self
.
_add_course_mode
(
mode_slug
=
'verified'
,
expiration_date
=
expiration_date
)
self
.
add_reg_code
(
course_key
,
mode_slug
=
'verified'
)
self
.
add_course_to_user_cart
(
self
.
course_key
)
resp
=
self
.
client
.
post
(
reverse
(
'register_code_redemption'
,
args
=
[
self
.
reg_code
]),
HTTP_HOST
=
'localhost'
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertIn
(
self
.
course
.
display_name
,
resp
.
content
)
@ddt.data
(
True
,
False
)
def
test_reg_code_uses_unknown_mode
(
self
,
expired_mode
):
"""Tests the use of reg codes on verified courses, expired or active. """
course_key
=
self
.
course_key
.
to_deprecated_string
()
expiration_date
=
self
.
yesterday
if
expired_mode
else
self
.
tomorrow
self
.
_add_course_mode
(
mode_slug
=
'verified'
,
expiration_date
=
expiration_date
)
self
.
add_reg_code
(
course_key
,
mode_slug
=
'bananas'
)
self
.
add_course_to_user_cart
(
self
.
course_key
)
resp
=
self
.
client
.
post
(
reverse
(
'register_code_redemption'
,
args
=
[
self
.
reg_code
]),
HTTP_HOST
=
'localhost'
)
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertIn
(
self
.
course
.
display_name
,
resp
.
content
)
self
.
assertIn
(
"error processing your redeem code"
,
resp
.
content
)
def
test_course_discount_for_valid_active_coupon_code
(
self
):
def
test_course_discount_for_valid_active_coupon_code
(
self
):
self
.
add_coupon
(
self
.
course_key
,
True
,
self
.
coupon_code
)
self
.
add_coupon
(
self
.
course_key
,
True
,
self
.
coupon_code
)
...
@@ -1472,6 +1516,10 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
...
@@ -1472,6 +1516,10 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
cache
.
clear
()
cache
.
clear
()
instructor
=
InstructorFactory
(
course_key
=
self
.
course_key
)
instructor
=
InstructorFactory
(
course_key
=
self
.
course_key
)
self
.
client
.
login
(
username
=
instructor
.
username
,
password
=
'test'
)
self
.
client
.
login
(
username
=
instructor
.
username
,
password
=
'test'
)
# Registration Code Generation only available to Sales Admins.
CourseSalesAdminRole
(
self
.
course
.
id
)
.
add_users
(
instructor
)
url
=
reverse
(
'generate_registration_codes'
,
url
=
reverse
(
'generate_registration_codes'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
...
...
lms/djangoapps/shoppingcart/views.py
View file @
158a0a0e
...
@@ -10,6 +10,7 @@ from django.http import (
...
@@ -10,6 +10,7 @@ from django.http import (
HttpResponseBadRequest
,
HttpResponseForbidden
,
Http404
HttpResponseBadRequest
,
HttpResponseForbidden
,
Http404
)
)
from
django.utils.translation
import
ugettext
as
_
from
django.utils.translation
import
ugettext
as
_
from
course_modes.models
import
CourseMode
from
util.json_request
import
JsonResponse
from
util.json_request
import
JsonResponse
from
django.views.decorators.http
import
require_POST
,
require_http_methods
from
django.views.decorators.http
import
require_POST
,
require_http_methods
from
django.core.urlresolvers
import
reverse
from
django.core.urlresolvers
import
reverse
...
@@ -26,12 +27,13 @@ from courseware.courses import get_course_by_id
...
@@ -26,12 +27,13 @@ from courseware.courses import get_course_by_id
from
courseware.views
import
registered_for_course
from
courseware.views
import
registered_for_course
from
config_models.decorators
import
require_config
from
config_models.decorators
import
require_config
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
,
EnrollmentClosedError
,
CourseFullError
,
\
AlreadyEnrolledError
from
.exceptions
import
(
from
.exceptions
import
(
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
MultipleCouponsNotAllowedException
,
InvalidCartItem
,
MultipleCouponsNotAllowedException
,
InvalidCartItem
,
ItemNotFoundInCartException
ItemNotFoundInCartException
,
RedemptionCodeError
)
)
from
.models
import
(
from
.models
import
(
Order
,
OrderTypes
,
Order
,
OrderTypes
,
...
@@ -307,7 +309,6 @@ def get_reg_code_validity(registration_code, request, limiter):
...
@@ -307,7 +309,6 @@ def get_reg_code_validity(registration_code, request, limiter):
@require_http_methods
([
"GET"
,
"POST"
])
@require_http_methods
([
"GET"
,
"POST"
])
@login_required
@login_required
@enforce_shopping_cart_enabled
def
register_code_redemption
(
request
,
registration_code
):
def
register_code_redemption
(
request
,
registration_code
):
"""
"""
This view allows the student to redeem the registration code
This view allows the student to redeem the registration code
...
@@ -338,8 +339,14 @@ def register_code_redemption(request, registration_code):
...
@@ -338,8 +339,14 @@ def register_code_redemption(request, registration_code):
elif
request
.
method
==
"POST"
:
elif
request
.
method
==
"POST"
:
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
request
,
limiter
)
request
,
limiter
)
course
=
get_course_by_id
(
getattr
(
course_registration
,
'course_id'
),
depth
=
0
)
course
=
get_course_by_id
(
getattr
(
course_registration
,
'course_id'
),
depth
=
0
)
context
=
{
'reg_code'
:
registration_code
,
'site_name'
:
site_name
,
'course'
:
course
,
'reg_code_is_valid'
:
reg_code_is_valid
,
'reg_code_already_redeemed'
:
reg_code_already_redeemed
,
}
if
reg_code_is_valid
and
not
reg_code_already_redeemed
:
if
reg_code_is_valid
and
not
reg_code_already_redeemed
:
# remove the course from the cart if it was added there.
# remove the course from the cart if it was added there.
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
...
@@ -355,23 +362,30 @@ def register_code_redemption(request, registration_code):
...
@@ -355,23 +362,30 @@ def register_code_redemption(request, registration_code):
#now redeem the reg code.
#now redeem the reg code.
redemption
=
RegistrationCodeRedemption
.
create_invoice_generated_registration_redemption
(
course_registration
,
request
.
user
)
redemption
=
RegistrationCodeRedemption
.
create_invoice_generated_registration_redemption
(
course_registration
,
request
.
user
)
redemption
.
course_enrollment
=
CourseEnrollment
.
enroll
(
request
.
user
,
course
.
id
)
try
:
redemption
.
save
()
kwargs
=
{}
context
=
{
if
course_registration
.
mode_slug
is
not
None
:
'redemption_success'
:
True
,
if
CourseMode
.
mode_for_course
(
course
.
id
,
course_registration
.
mode_slug
):
'reg_code'
:
registration_code
,
kwargs
[
'mode'
]
=
course_registration
.
mode_slug
'site_name'
:
site_name
,
else
:
'course'
:
course
,
raise
RedemptionCodeError
()
}
redemption
.
course_enrollment
=
CourseEnrollment
.
enroll
(
request
.
user
,
course
.
id
,
**
kwargs
)
redemption
.
save
()
context
[
'redemption_success'
]
=
True
except
RedemptionCodeError
:
context
[
'redeem_code_error'
]
=
True
context
[
'redemption_success'
]
=
False
except
EnrollmentClosedError
:
context
[
'enrollment_closed'
]
=
True
context
[
'redemption_success'
]
=
False
except
CourseFullError
:
context
[
'course_full'
]
=
True
context
[
'redemption_success'
]
=
False
except
AlreadyEnrolledError
:
context
[
'registered_for_course'
]
=
True
context
[
'redemption_success'
]
=
False
else
:
else
:
context
=
{
context
[
'redemption_success'
]
=
False
'reg_code_is_valid'
:
reg_code_is_valid
,
'reg_code_already_redeemed'
:
reg_code_already_redeemed
,
'redemption_success'
:
False
,
'reg_code'
:
registration_code
,
'site_name'
:
site_name
,
'course'
:
course
,
}
return
render_to_response
(
template_to_render
,
context
)
return
render_to_response
(
template_to_render
,
context
)
...
...
lms/static/sass/views/_shoppingcart.scss
View file @
158a0a0e
...
@@ -294,7 +294,7 @@
...
@@ -294,7 +294,7 @@
}
}
}
}
input
[
type
=
"submit"
]
{
input
[
type
=
"submit"
]
,
button
{
text-transform
:
none
;
text-transform
:
none
;
width
:
450px
;
width
:
450px
;
height
:
70px
;
height
:
70px
;
...
...
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
View file @
158a0a0e
...
@@ -12,9 +12,11 @@
...
@@ -12,9 +12,11 @@
<div
class=
"wrap"
>
<div
class=
"wrap"
>
<h2>
${_('Registration Codes')}
</h2>
<h2>
${_('Registration Codes')}
</h2>
<div>
<div>
%if section_data['sales_admin']:
<span
class=
"code_tip"
>
${_('Click to generate Registration Codes')}
<span
class=
"code_tip"
>
${_('Click to generate Registration Codes')}
<a
id=
"registration_code_generation_link"
href=
"#reg_code_generation_modal"
class=
"add blue-button"
>
${_('Generate Registration Codes')}
</a>
<a
id=
"registration_code_generation_link"
href=
"#reg_code_generation_modal"
class=
"add blue-button"
>
${_('Generate Registration Codes')}
</a>
</span>
</span>
%endif
<p>
${_('Click to generate a CSV file of all Course Registrations Codes:')}
</p>
<p>
${_('Click to generate a CSV file of all Course Registrations Codes:')}
</p>
<p>
<p>
<form
action=
"${ section_data['get_registration_code_csv_url'] }"
id=
"download_registration_codes"
method=
"post"
>
<form
action=
"${ section_data['get_registration_code_csv_url'] }"
id=
"download_registration_codes"
method=
"post"
>
...
@@ -43,6 +45,7 @@
...
@@ -43,6 +45,7 @@
</div>
</div>
</div>
</div>
<!-- end wrap -->
<!-- end wrap -->
%if section_data['coupons_enabled']:
<div
class=
"wrap"
>
<div
class=
"wrap"
>
<h2>
${_("Course Price")}
</h2>
<h2>
${_("Course Price")}
</h2>
<div>
<div>
...
@@ -53,8 +56,9 @@
...
@@ -53,8 +56,9 @@
</span>
</span>
</div>
</div>
</div>
</div>
%endif
<!-- end wrap -->
<!-- end wrap -->
%if section_data['access']['finance_admin']
is True
:
%if section_data['access']['finance_admin']:
<div
class=
"wrap"
>
<div
class=
"wrap"
>
<h2>
${_("Sales")}
</h2>
<h2>
${_("Sales")}
</h2>
<div>
<div>
...
@@ -79,6 +83,7 @@
...
@@ -79,6 +83,7 @@
</div>
</div>
</div>
<!-- end wrap -->
</div>
<!-- end wrap -->
%endif
%endif
%if section_data['coupons_enabled']:
<div
class=
"wrap"
>
<div
class=
"wrap"
>
<h2>
${_("Coupons List")}
</h2>
<h2>
${_("Coupons List")}
</h2>
<div>
<div>
...
@@ -132,6 +137,7 @@
...
@@ -132,6 +137,7 @@
</div>
</div>
</div>
</div>
</div>
</div>
%endif
<!-- end wrap -->
<!-- end wrap -->
</div>
</div>
</div>
</div>
...
...
lms/templates/shoppingcart/registration_code_receipt.html
0 → 100644
View file @
158a0a0e
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
from
django
.
core
.
urlresolvers
import
reverse
from
courseware
.
courses
import
course_image_url
,
get_course_about_section
%
>
<
%
inherit
file=
"../main.html"
/>
<
%
namespace
name=
'static'
file=
'/static_content.html'
/>
<
%
block
name=
"pagetitle"
>
${_("Confirm Enrollment")}
</
%
block>
<
%
block
name=
"content"
>
<div
class=
"container"
>
<section
class=
"wrapper confirm-enrollment"
>
<header
class=
"page-header"
>
<h1
class=
"title"
>
${_("{site_name} - Confirm Enrollment").format(site_name=site_name)}
</h1>
</header>
<section>
<div
class=
"course-image"
>
<img
style=
"width: 100%; height: auto;"
src=
"${course_image_url(course)}"
alt=
"${course.display_number_with_default | h} ${get_course_about_section(course, 'title')} Cover Image"
/>
</div>
<div
class=
"enrollment-details"
>
<div
class=
"sub-title"
>
${_("Confirm your enrollment for:")}
<span
class=
"course-date-label"
>
${_("course dates")}
</span>
<div
class=
"clearfix"
></div>
</div>
<div
class=
"course-title"
>
<h1>
${_("{course_name}").format(course_name=course.display_name)}
<span
class=
"course-dates"
>
${_("{start_date}").format(start_date=course.start_datetime_text())} - ${_("{end_date}").format(end_date=course.end_datetime_text())}
</span>
</h1>
</div>
<hr>
<div>
% if reg_code_already_redeemed:
<
%
dashboard_url =
reverse('dashboard')%
>
<p
class=
"enrollment-text"
>
${_("You've clicked a link for an enrollment code that has already been used."
" Check your
<a
href=
{dashboard_url}
>
course dashboard
</a>
to see if you're enrolled in the course,"
" or contact your company's administrator.").format(dashboard_url=dashboard_url)}
</p>
% elif redemption_success:
<p
class=
"enrollment-text"
>
${_("You have successfully enrolled in {course_name}."
" This course has now been added to your dashboard.").format(course_name=course.display_name)}
</p>
% elif registered_for_course:
<
%
dashboard_url =
reverse('dashboard')%
>
<p
class=
"enrollment-text"
>
${_("You're already registered for this course."
" Visit your
<a
href=
{dashboard_url}
>
dashboard
</a>
to see the course.").format(dashboard_url=dashboard_url)}
</p>
% elif course_full:
<
%
dashboard_url =
reverse('dashboard')%
>
<p
class=
"enrollment-text"
>
${_("The course you are registering for is full.")}
</p>
% elif enrollment_closed:
<
%
dashboard_url =
reverse('dashboard')%
>
<p
class=
"enrollment-text"
>
${_("The course you are registering for is closed.")}
</p>
% elif redeem_code_error:
<
%
dashboard_url =
reverse('dashboard')%
>
<p
class=
"enrollment-text"
>
${_("There was an error processing your redeem code.")}
</p>
% else:
<p
class=
"enrollment-text"
>
${_("You're about to activate an enrollment code for {course_name} by {site_name}. "
"This code can only be used one time, so you should only activate this code if you're its intended"
" recipient.").format(course_name=course.display_name, site_name=site_name)}
</p>
% endif
</div>
</div>
% if not reg_code_already_redeemed:
%if redemption_success:
<
%
course_url =
reverse('info',
args=
[course.id.to_deprecated_string()])
%
>
<a
href=
"${course_url}"
class=
"link-button course-link-bg-color"
>
${_("View Course")}
<i
class=
"icon fa fa-caret-right"
></i></a>
%elif not registered_for_course:
<form
method=
"post"
>
<input
type=
"hidden"
name=
"csrfmiddlewaretoken"
value=
"${ csrf_token }"
>
<button
type=
"submit"
id=
"id_active_course_enrollment"
name=
"active_course_enrollment"
>
${_("Activate Course Enrollment")}
<i
class=
"icon fa fa-caret-right"
></i></button>
</form>
%endif
%endif
</section>
</section>
</div>
</
%
block>
lms/templates/shoppingcart/registration_code_redemption.html
View file @
158a0a0e
...
@@ -73,6 +73,10 @@ from courseware.courses import course_image_url, get_course_about_section
...
@@ -73,6 +73,10 @@ from courseware.courses import course_image_url, get_course_about_section
link_end='
</a>
',
link_end='
</a>
',
)}
)}
</p>
</p>
% elif redeem_code_error:
<p
class=
"enrollment-text"
>
${_( "There was an error processing your redeem code.")}
</p>
% else:
% else:
<p
class=
"enrollment-text"
>
<p
class=
"enrollment-text"
>
${_(
${_(
...
@@ -90,12 +94,13 @@ from courseware.courses import course_image_url, get_course_about_section
...
@@ -90,12 +94,13 @@ from courseware.courses import course_image_url, get_course_about_section
</div>
</div>
% if not reg_code_already_redeemed:
% if not reg_code_already_redeemed:
%if redemption_success:
%if redemption_success:
<a
href=
"${reverse('dashboard')}"
class=
"link-button course-link-bg-color"
>
${_("View Dashboard
▸
")}
</a>
<a
href=
"${reverse('dashboard')}"
class=
"link-button course-link-bg-color"
>
${_("View Dashboard
")}
<i
class=
"icon fa fa-caret-right"
></i>
</a>
%elif not registered_for_course:
%elif not registered_for_course:
<form
method=
"post"
>
<form
method=
"post"
>
<input
type=
"hidden"
name=
"csrfmiddlewaretoken"
value=
"${ csrf_token }"
>
<input
type=
"hidden"
name=
"csrfmiddlewaretoken"
value=
"${ csrf_token }"
>
<input
type=
"submit"
value=
"${_("
Activate
Course
Enrollment
")}
&#
x25b8
;"
<button
type=
"submit"
id=
"id_active_course_enrollment"
name=
"active_course_enrollment"
>
id=
"id_active_course_enrollment"
name=
"active_course_enrollment"
>
${_("Activate Course Enrollment")}
<i
class=
"icon fa fa-caret-right"
></i></button>
</form>
</form>
%endif
%endif
%endif
%endif
...
...
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