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
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
803 additions
and
87 deletions
+803
-87
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
+33
-19
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):
"""
modes_by_course
=
defaultdict
(
list
)
for
mode
in
cls
.
objects
.
filter
(
course_id__in
=
course_id_list
):
modes_by_course
[
mode
.
course_id
]
.
append
(
Mode
(
mode
.
mode_slug
,
mode
.
mode_display_name
,
mode
.
min_price
,
mode
.
suggested_prices
,
mode
.
currency
,
mode
.
expiration_datetime
,
mode
.
description
)
)
modes_by_course
[
mode
.
course_id
]
.
append
(
mode
.
to_tuple
())
# Assign default modes if nothing available in the database
missing_courses
=
set
(
course_id_list
)
-
set
(
modes_by_course
.
keys
())
...
...
@@ -131,6 +121,31 @@ class CourseMode(models.Model):
return
(
all_modes
,
unexpired_modes
)
@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
):
"""
Returns a list of the non-expired modes for a given course id
...
...
@@ -141,15 +156,7 @@ class CourseMode(models.Model):
found_course_modes
=
cls
.
objects
.
filter
(
Q
(
course_id
=
course_id
)
&
(
Q
(
expiration_datetime__isnull
=
True
)
|
Q
(
expiration_datetime__gte
=
now
)))
modes
=
([
Mode
(
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
])
modes
=
([
mode
.
to_tuple
()
for
mode
in
found_course_modes
])
if
not
modes
:
modes
=
[
cls
.
DEFAULT_MODE
]
return
modes
...
...
@@ -359,6 +366,24 @@ class CourseMode(models.Model):
modes
=
cls
.
modes_for_course
(
course_id
)
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
):
return
u"{} : {}, min={}, prices={}"
.
format
(
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):
class
CourseFinanceAdminRole
(
CourseRole
):
"""A course
Instructor
"""
"""A course
staff member with privileges to review financial data.
"""
ROLE
=
'finance_admin'
def
__init__
(
self
,
*
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
):
"""A course Beta Tester"""
ROLE
=
'beta_testers'
...
...
common/djangoapps/student/tests/tests.py
View file @
158a0a0e
...
...
@@ -280,8 +280,9 @@ class DashboardTest(ModuleStoreTestCase):
recipient_name
=
'Testw_1'
,
recipient_email
=
'test2@test.com'
,
internal_reference
=
"A"
,
course_id
=
self
.
course
.
id
,
is_valid
=
False
)
course_reg_code
=
shoppingcart
.
models
.
CourseRegistrationCode
(
code
=
"abcde"
,
course_id
=
self
.
course
.
id
,
created_by
=
self
.
user
,
invoice
=
sale_invoice_1
)
course_reg_code
=
shoppingcart
.
models
.
CourseRegistrationCode
(
code
=
"abcde"
,
course_id
=
self
.
course
.
id
,
created_by
=
self
.
user
,
invoice
=
sale_invoice_1
,
mode_slug
=
'honor'
)
course_reg_code
.
save
()
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
import
requests
import
shutil
import
tempfile
from
unittest
import
TestCase
from
urllib
import
quote
from
django.conf
import
settings
...
...
@@ -45,8 +44,8 @@ from shoppingcart.models import (
from
student.models
import
(
CourseEnrollment
,
CourseEnrollmentAllowed
,
NonExistentCourseError
)
from
student.tests.factories
import
UserFactory
from
student.roles
import
CourseBetaTesterRole
from
student.tests.factories
import
UserFactory
,
CourseModeFactory
from
student.roles
import
CourseBetaTesterRole
,
CourseSalesAdminRole
,
CourseFinanceAdminRole
from
xmodule.modulestore
import
ModuleStoreEnum
from
xmodule.modulestore.django
import
modulestore
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
...
...
@@ -1722,7 +1721,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
2
):
course_registration_code
=
CourseRegistrationCode
(
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
()
...
...
@@ -1807,6 +1806,10 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
percentage_discount
=
'10'
,
created_by
=
self
.
instructor
,
is_active
=
True
)
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
)
# apply the coupon code to the item in the cart
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_code'
),
{
'code'
:
coupon
.
code
})
...
...
@@ -1838,7 +1841,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
2
):
course_registration_code
=
CourseRegistrationCode
(
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
()
...
...
@@ -1853,7 +1856,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
5
):
course_registration_code
=
CourseRegistrationCode
(
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
()
...
...
@@ -1872,7 +1875,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
5
):
course_registration_code
=
CourseRegistrationCode
(
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
()
...
...
@@ -1886,7 +1889,7 @@ class TestInstructorAPILevelsDataDump(ModuleStoreTestCase, LoginEnrollmentTestCa
for
i
in
range
(
5
):
course_registration_code
=
CourseRegistrationCode
(
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
()
...
...
@@ -2994,8 +2997,10 @@ class TestCourseRegistrationCodes(ModuleStoreTestCase):
Fixtures.
"""
self
.
course
=
CourseFactory
.
create
()
CourseModeFactory
.
create
(
course_id
=
self
.
course
.
id
,
min_price
=
50
)
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
'test'
)
CourseSalesAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
url
=
reverse
(
'generate_registration_codes'
,
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):
"""
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
.
assertTrue
(
'Coupons'
in
response
.
content
)
def
test_user_has_finance_admin_rights_in_e_commerce_tab
(
self
):
response
=
self
.
client
.
get
(
self
.
url
)
...
...
@@ -190,7 +192,8 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
self
.
assertTrue
(
'Please Enter the Integer Value for Coupon Discount'
in
response
.
content
)
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
()
...
...
@@ -288,3 +291,20 @@ class TestECommerceDashboardViews(ModuleStoreTestCase):
data
[
'coupon_id'
]
=
''
# Coupon id is not provided
response
=
self
.
client
.
post
(
update_coupon_url
,
data
=
data
)
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
import
random
import
unicodecsv
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
import
datetime
import
pytz
...
...
@@ -214,7 +216,7 @@ def require_level(level):
def
decorator
(
func
):
# pylint: disable=missing-docstring
def
wrapped
(
*
args
,
**
kwargs
):
# pylint: disable=missing-docstring
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
):
return
func
(
*
args
,
**
kwargs
)
...
...
@@ -224,6 +226,31 @@ def require_level(level):
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
USERNAME_INDEX
=
1
NAME_INDEX
=
2
...
...
@@ -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
)
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
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
()
...
...
@@ -1038,10 +1076,11 @@ def save_registration_code(user, course_id, invoice=None, order=None):
course_registration
=
CourseRegistrationCode
(
code
=
code
,
course_id
=
course_id
.
to_deprecated_string
(
),
course_id
=
unicode
(
course_id
),
created_by
=
user
,
invoice
=
invoice
,
order
=
order
order
=
order
,
mode_slug
=
mode_slug
)
try
:
course_registration
.
save
()
...
...
@@ -1101,13 +1140,13 @@ def get_registration_codes(request, course_id): # pylint: disable=unused-argume
@ensure_csrf_cookie
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
@require_
level
(
'staff'
)
@require_
sales_admin
@require_POST
def
generate_registration_codes
(
request
,
course_id
):
"""
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
# covert the course registration code number into integer
...
...
@@ -1155,15 +1194,32 @@ def generate_registration_codes(request, course_id):
internal_reference
=
internal_reference
,
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
=
[]
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
)
for
__
in
range
(
course_code_number
):
# pylint: disable=redefined-outer-name
generated_registration_code
=
save_registration_code
(
request
.
user
,
course_id
,
course_mode
.
slug
,
sale_invoice
,
order
=
None
)
registration_codes
.
append
(
generated_registration_code
)
site_name
=
microsite
.
get_value
(
'SITE_NAME'
,
settings
.
SITE_NAME
)
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
site_name
=
microsite
.
get_value
(
'SITE_NAME'
,
'localhost'
)
quantity
=
course_code_number
discount
=
(
float
(
quantity
*
course_price
)
-
float
(
sale_price
))
course_url
=
'{base_url}{course_about}'
.
format
(
...
...
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
158a0a0e
...
...
@@ -4,6 +4,8 @@ Instructor Dashboard Views
import
logging
import
datetime
from
opaque_keys
import
InvalidKeyError
from
opaque_keys.edx.keys
import
CourseKey
import
uuid
import
pytz
...
...
@@ -15,7 +17,7 @@ from django.views.decorators.cache import cache_control
from
edxmako.shortcuts
import
render_to_response
from
django.core.urlresolvers
import
reverse
from
django.utils.html
import
escape
from
django.http
import
Http404
from
django.http
import
Http404
,
HttpResponseServerError
from
django.conf
import
settings
from
util.json_request
import
JsonResponse
from
mock
import
patch
...
...
@@ -33,7 +35,7 @@ from django_comment_common.models import FORUM_ROLE_ADMINISTRATOR
from
student.models
import
CourseEnrollment
from
shoppingcart.models
import
Coupon
,
PaidCourseRegistration
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
.tools
import
get_units_with_due_date
,
title_or_url
,
bulk_email_is_enabled_for_course
...
...
@@ -47,13 +49,19 @@ log = logging.getLogger(__name__)
@cache_control
(
no_cache
=
True
,
no_store
=
True
,
must_revalidate
=
True
)
def
instructor_dashboard_2
(
request
,
course_id
):
""" Display the instructor dashboard for a course. """
course_key
=
SlashSeparatedCourseKey
.
from_deprecated_string
(
course_id
)
course
=
get_course_by_id
(
course_key
,
depth
=
None
)
try
:
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
=
{
'admin'
:
request
.
user
.
is_staff
,
'instructor'
:
has_access
(
request
.
user
,
'instructor'
,
course
),
'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
),
'forum_admin'
:
has_forum_access
(
request
.
user
,
course_key
,
FORUM_ROLE_ADMINISTRATOR
...
...
@@ -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
course_honor_mode
=
CourseMode
.
mode_for_course
(
course_key
,
'honor'
)
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
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'
]):
sections
.
insert
(
3
,
_section_extensions
(
course
))
...
...
@@ -89,8 +105,8 @@ def instructor_dashboard_2(request, course_id):
sections
.
append
(
_section_metrics
(
course
,
access
))
# Gate access to Ecommerce tab
if
course_mode_has_price
:
sections
.
append
(
_section_e_commerce
(
course
,
access
))
if
course_mode_has_price
and
(
access
[
'finance_admin'
]
or
access
[
'sales_admin'
])
:
sections
.
append
(
_section_e_commerce
(
course
,
access
,
paid_modes
[
0
],
is_white_label
))
disable_buttons
=
not
_is_small_course
(
course_key
)
...
...
@@ -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.
def
_section_e_commerce
(
course
,
access
):
def
_section_e_commerce
(
course
,
access
,
paid_mode
,
coupons_enabled
):
""" Provide data for the corresponding dashboard section """
course_key
=
course
.
id
coupons
=
Coupon
.
objects
.
filter
(
course_id
=
course_key
)
.
order_by
(
'-is_active'
)
course_price
=
None
course_price
=
paid_mode
.
min_price
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'
]:
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
course_key
)
...
...
@@ -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
)}),
'download_coupon_codes_url'
:
reverse
(
'get_coupon_codes'
,
kwargs
=
{
'course_id'
:
unicode
(
course_key
)}),
'coupons'
:
coupons
,
'sales_admin'
:
access
[
'sales_admin'
],
'coupons_enabled'
:
coupons_enabled
,
'course_price'
:
course_price
,
'total_amount'
:
total_amount
}
...
...
lms/djangoapps/instructor_analytics/tests/test_basic.py
View file @
158a0a0e
...
...
@@ -6,7 +6,8 @@ import json
from
student.models
import
CourseEnrollment
from
django.core.urlresolvers
import
reverse
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
shoppingcart.models
import
(
CourseRegistrationCode
,
RegistrationCodeRedemption
,
Order
,
...
...
@@ -149,7 +150,7 @@ class TestCourseSaleRecordsAnalyticsBasic(ModuleStoreTestCase):
for
i
in
range
(
5
):
course_code
=
CourseRegistrationCode
(
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
()
...
...
@@ -259,6 +260,13 @@ class TestCourseRegistrationCodeAnalyticsBasic(ModuleStoreTestCase):
self
.
course
=
CourseFactory
.
create
()
self
.
instructor
=
InstructorFactory
(
course_key
=
self
.
course
.
id
)
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'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
...
...
lms/djangoapps/shoppingcart/exceptions.py
View file @
158a0a0e
...
...
@@ -37,6 +37,11 @@ class MultipleCouponsNotAllowedException(InvalidCartItem):
pass
class
RedemptionCodeError
(
Exception
):
"""An error occurs while processing redemption codes. """
pass
class
ReportException
(
Exception
):
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):
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
order
=
models
.
ForeignKey
(
Order
,
db_index
=
True
,
null
=
True
,
related_name
=
"purchase_order"
)
invoice
=
models
.
ForeignKey
(
Invoice
,
null
=
True
)
mode_slug
=
models
.
CharField
(
max_length
=
100
,
null
=
True
)
class
RegistrationCodeRedemption
(
models
.
Model
):
...
...
@@ -1135,7 +1136,7 @@ class CourseRegCodeItem(OrderItem):
# is in another PR (for another feature)
from
instructor.views.api
import
save_registration_code
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}"
.
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 (
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
student.roles
import
CourseSalesAdminRole
from
util.date_utils
import
get_default_time_display
from
util.testing
import
UrlResetMixin
...
...
@@ -37,7 +38,7 @@ from shoppingcart.models import (
Coupon
,
CourseRegistrationCode
,
RegistrationCodeRedemption
,
DonationConfiguration
)
from
student.tests.factories
import
UserFactory
,
AdminFactory
from
student.tests.factories
import
UserFactory
,
AdminFactory
,
CourseModeFactory
from
courseware.tests.factories
import
InstructorFactory
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
...
...
@@ -104,6 +105,10 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
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
):
"""
This method simple return the discounted amount
...
...
@@ -119,13 +124,27 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
percentage_discount
=
self
.
percentage_discount
,
created_by
=
self
.
user
,
is_active
=
is_active
)
coupon
.
save
()
def
add_reg_code
(
self
,
course_key
):
def
add_reg_code
(
self
,
course_key
,
mode_slug
=
'honor'
):
"""
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
()
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
):
"""
adding course to user cart
...
...
@@ -392,6 +411,31 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertEqual
(
resp
.
status_code
,
404
)
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
):
self
.
add_coupon
(
self
.
course_key
,
True
,
self
.
coupon_code
)
...
...
@@ -1472,6 +1516,10 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
cache
.
clear
()
instructor
=
InstructorFactory
(
course_key
=
self
.
course_key
)
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'
,
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 (
HttpResponseBadRequest
,
HttpResponseForbidden
,
Http404
)
from
django.utils.translation
import
ugettext
as
_
from
course_modes.models
import
CourseMode
from
util.json_request
import
JsonResponse
from
django.views.decorators.http
import
require_POST
,
require_http_methods
from
django.core.urlresolvers
import
reverse
...
...
@@ -26,12 +27,13 @@ from courseware.courses import get_course_by_id
from
courseware.views
import
registered_for_course
from
config_models.decorators
import
require_config
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
student.models
import
CourseEnrollment
from
student.models
import
CourseEnrollment
,
EnrollmentClosedError
,
CourseFullError
,
\
AlreadyEnrolledError
from
.exceptions
import
(
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
MultipleCouponsNotAllowedException
,
InvalidCartItem
,
ItemNotFoundInCartException
ItemNotFoundInCartException
,
RedemptionCodeError
)
from
.models
import
(
Order
,
OrderTypes
,
...
...
@@ -307,7 +309,6 @@ def get_reg_code_validity(registration_code, request, limiter):
@require_http_methods
([
"GET"
,
"POST"
])
@login_required
@enforce_shopping_cart_enabled
def
register_code_redemption
(
request
,
registration_code
):
"""
This view allows the student to redeem the registration code
...
...
@@ -338,8 +339,14 @@ def register_code_redemption(request, registration_code):
elif
request
.
method
==
"POST"
:
reg_code_is_valid
,
reg_code_already_redeemed
,
course_registration
=
get_reg_code_validity
(
registration_code
,
request
,
limiter
)
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
:
# remove the course from the cart if it was added there.
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
...
...
@@ -355,23 +362,30 @@ def register_code_redemption(request, registration_code):
#now redeem the reg code.
redemption
=
RegistrationCodeRedemption
.
create_invoice_generated_registration_redemption
(
course_registration
,
request
.
user
)
redemption
.
course_enrollment
=
CourseEnrollment
.
enroll
(
request
.
user
,
course
.
id
)
try
:
kwargs
=
{}
if
course_registration
.
mode_slug
is
not
None
:
if
CourseMode
.
mode_for_course
(
course
.
id
,
course_registration
.
mode_slug
):
kwargs
[
'mode'
]
=
course_registration
.
mode_slug
else
:
raise
RedemptionCodeError
()
redemption
.
course_enrollment
=
CourseEnrollment
.
enroll
(
request
.
user
,
course
.
id
,
**
kwargs
)
redemption
.
save
()
context
=
{
'redemption_success'
:
True
,
'reg_code'
:
registration_code
,
'site_name'
:
site_name
,
'course'
:
course
,
}
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
:
context
=
{
'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
,
}
context
[
'redemption_success'
]
=
False
return
render_to_response
(
template_to_render
,
context
)
...
...
lms/static/sass/views/_shoppingcart.scss
View file @
158a0a0e
...
...
@@ -294,7 +294,7 @@
}
}
input
[
type
=
"submit"
]
{
input
[
type
=
"submit"
]
,
button
{
text-transform
:
none
;
width
:
450px
;
height
:
70px
;
...
...
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
View file @
158a0a0e
...
...
@@ -12,9 +12,11 @@
<div
class=
"wrap"
>
<h2>
${_('Registration Codes')}
</h2>
<div>
%if section_data['sales_admin']:
<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>
</span>
%endif
<p>
${_('Click to generate a CSV file of all Course Registrations Codes:')}
</p>
<p>
<form
action=
"${ section_data['get_registration_code_csv_url'] }"
id=
"download_registration_codes"
method=
"post"
>
...
...
@@ -43,6 +45,7 @@
</div>
</div>
<!-- end wrap -->
%if section_data['coupons_enabled']:
<div
class=
"wrap"
>
<h2>
${_("Course Price")}
</h2>
<div>
...
...
@@ -53,8 +56,9 @@
</span>
</div>
</div>
%endif
<!-- end wrap -->
%if section_data['access']['finance_admin']
is True
:
%if section_data['access']['finance_admin']:
<div
class=
"wrap"
>
<h2>
${_("Sales")}
</h2>
<div>
...
...
@@ -79,6 +83,7 @@
</div>
</div>
<!-- end wrap -->
%endif
%if section_data['coupons_enabled']:
<div
class=
"wrap"
>
<h2>
${_("Coupons List")}
</h2>
<div>
...
...
@@ -132,6 +137,7 @@
</div>
</div>
</div>
%endif
<!-- end wrap -->
</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
link_end='
</a>
',
)}
</p>
% elif redeem_code_error:
<p
class=
"enrollment-text"
>
${_( "There was an error processing your redeem code.")}
</p>
% else:
<p
class=
"enrollment-text"
>
${_(
...
...
@@ -90,12 +94,13 @@ from courseware.courses import course_image_url, get_course_about_section
</div>
% if not reg_code_already_redeemed:
%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:
<form
method=
"post"
>
<input
type=
"hidden"
name=
"csrfmiddlewaretoken"
value=
"${ csrf_token }"
>
<input
type=
"submit"
value=
"${_("
Activate
Course
Enrollment
")}
&#
x25b8
;"
id=
"id_active_course_enrollment"
name=
"active_course_enrollment"
>
<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
...
...
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