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
665ccdb7
Commit
665ccdb7
authored
Jul 10, 2014
by
chrisndodge
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4172 from edx/cdodge/ecommerce-improvements
eCommerce enhancements
parents
256c6bb4
5734d2a8
Hide whitespace changes
Inline
Side-by-side
Showing
31 changed files
with
3015 additions
and
37 deletions
+3015
-37
AUTHORS
+2
-0
common/djangoapps/student/admin.py
+3
-1
common/djangoapps/student/migrations/0037_auto__add_courseregistrationcode.py
+180
-0
common/djangoapps/student/migrations/0038_auto__add_usersignupsource.py
+181
-0
common/djangoapps/student/migrations/0039_auto__del_courseregistrationcode.py
+175
-0
common/djangoapps/student/models.py
+12
-0
common/djangoapps/student/roles.py
+7
-0
common/djangoapps/student/tests/test_microsite.py
+51
-0
common/djangoapps/student/views.py
+19
-1
lms/djangoapps/instructor/tests/test_ecommerce.py
+193
-0
lms/djangoapps/instructor/views/coupons.py
+135
-0
lms/djangoapps/instructor/views/instructor_dashboard.py
+39
-0
lms/djangoapps/shoppingcart/exceptions.py
+12
-0
lms/djangoapps/shoppingcart/migrations/0008_auto__add_coupons__add_couponredemption__chg_field_certificateitem_cou.py
+190
-0
lms/djangoapps/shoppingcart/migrations/0009_auto__del_coupons__add_courseregistrationcode__add_coupon__chg_field_c.py
+217
-0
lms/djangoapps/shoppingcart/models.py
+87
-1
lms/djangoapps/shoppingcart/processors/CyberSource.py
+27
-7
lms/djangoapps/shoppingcart/processors/CyberSource2.py
+404
-0
lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
+29
-0
lms/djangoapps/shoppingcart/tests/test_views.py
+199
-2
lms/djangoapps/shoppingcart/urls.py
+1
-0
lms/djangoapps/shoppingcart/views.py
+57
-2
lms/static/sass/course/instructor/_instructor_2.scss
+267
-3
lms/static/sass/views/_shoppingcart.scss
+70
-9
lms/templates/instructor/instructor_dashboard_2/add_coupon_modal.html
+62
-0
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
+256
-0
lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html
+63
-0
lms/templates/shoppingcart/cybersource_form.html
+3
-1
lms/templates/shoppingcart/list.html
+55
-9
lms/templates/shoppingcart/receipt.html
+11
-1
lms/urls.py
+8
-0
No files found.
AUTHORS
View file @
665ccdb7
...
...
@@ -158,3 +158,5 @@ Tim Babych <tim.babych@gmail.com>
Brandon DeRosier <btd@cheesekeg.com>
Daniel Li <swli@edx.org>
Daniel Friedman <dfriedman@edx.org>
Asad Iqbal <aiqbal@edx.org>
Muhammad Shoaib <mshoaib@edx.org>
common/djangoapps/student/admin.py
View file @
665ccdb7
...
...
@@ -3,7 +3,7 @@ django admin pages for courseware model
'''
from
student.models
import
UserProfile
,
UserTestGroup
,
CourseEnrollmentAllowed
from
student.models
import
CourseEnrollment
,
Registration
,
PendingNameChange
from
student.models
import
CourseEnrollment
,
Registration
,
PendingNameChange
,
CourseAccessRole
from
ratelimitbackend
import
admin
admin
.
site
.
register
(
UserProfile
)
...
...
@@ -17,3 +17,5 @@ admin.site.register(CourseEnrollmentAllowed)
admin
.
site
.
register
(
Registration
)
admin
.
site
.
register
(
PendingNameChange
)
admin
.
site
.
register
(
CourseAccessRole
)
common/djangoapps/student/migrations/0037_auto__add_courseregistrationcode.py
0 → 100644
View file @
665ccdb7
# -*- 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 model 'CourseRegistrationCode'
db
.
create_table
(
'student_courseregistrationcode'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'code'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
db_index
=
True
)),
(
'transaction_group_name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
db_index
=
True
,
max_length
=
255
,
null
=
True
,
blank
=
True
)),
(
'created_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'created_by_user'
,
to
=
orm
[
'auth.User'
])),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
6
,
24
,
0
,
0
))),
(
'redeemed_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'redeemed_by_user'
,
null
=
True
,
to
=
orm
[
'auth.User'
])),
(
'redeemed_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
6
,
24
,
0
,
0
),
null
=
True
)),
))
db
.
send_create_signal
(
'student'
,
[
'CourseRegistrationCode'
])
def
backwards
(
self
,
orm
):
# Deleting model 'CourseRegistrationCode'
db
.
delete_table
(
'student_courseregistrationcode'
)
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.courseregistrationcode'
:
{
'Meta'
:
{
'object_name'
:
'CourseRegistrationCode'
},
'code'
:
(
'django.db.models.fields.CharField'
,
[],
{
'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(2014, 6, 24, 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'
}),
'redeemed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2014, 6, 24, 0, 0)'
,
'null'
:
'True'
}),
'redeemed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'redeemed_by_user'"
,
'null'
:
'True'
,
'to'
:
"orm['auth.User']"
}),
'transaction_group_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
'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.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'
]
\ No newline at end of file
common/djangoapps/student/migrations/0038_auto__add_usersignupsource.py
0 → 100644
View file @
665ccdb7
# -*- 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 model 'UserSignupSource'
db
.
create_table
(
'student_usersignupsource'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'user_id'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'auth.User'
])),
(
'site'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
,
db_index
=
True
)),
))
db
.
send_create_signal
(
'student'
,
[
'UserSignupSource'
])
def
backwards
(
self
,
orm
):
# Deleting model 'UserSignupSource'
db
.
delete_table
(
'student_usersignupsource'
)
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.courseregistrationcode'
:
{
'Meta'
:
{
'object_name'
:
'CourseRegistrationCode'
},
'code'
:
(
'django.db.models.fields.CharField'
,
[],
{
'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(2014, 6, 25, 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'
}),
'redeemed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2014, 6, 25, 0, 0)'
,
'null'
:
'True'
}),
'redeemed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'redeemed_by_user'"
,
'null'
:
'True'
,
'to'
:
"orm['auth.User']"
}),
'transaction_group_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'True'
})
},
'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_id'
:
(
'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'
]
\ No newline at end of file
common/djangoapps/student/migrations/0039_auto__del_courseregistrationcode.py
0 → 100644
View file @
665ccdb7
# -*- 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
):
# Deleting model 'CourseRegistrationCode'
db
.
delete_table
(
'student_courseregistrationcode'
)
def
backwards
(
self
,
orm
):
# Adding model 'CourseRegistrationCode'
db
.
create_table
(
'student_courseregistrationcode'
,
(
(
'code'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'transaction_group_name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
blank
=
True
,
max_length
=
255
,
null
=
True
,
db_index
=
True
)),
(
'redeemed_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'redeemed_by_user'
,
null
=
True
,
to
=
orm
[
'auth.User'
])),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
db_index
=
True
)),
(
'redeemed_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
6
,
25
,
0
,
0
),
null
=
True
)),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
6
,
25
,
0
,
0
))),
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'created_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'created_by_user'
,
to
=
orm
[
'auth.User'
])),
))
db
.
send_create_signal
(
'student'
,
[
'CourseRegistrationCode'
])
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.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_id'
:
(
'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'
]
\ No newline at end of file
common/djangoapps/student/models.py
View file @
665ccdb7
...
...
@@ -271,6 +271,15 @@ class UserProfile(models.Model):
self
.
save
()
class
UserSignupSource
(
models
.
Model
):
"""
This table contains information about users registering
via Micro-Sites
"""
user_id
=
models
.
ForeignKey
(
User
,
db_index
=
True
)
site
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
)
def
unique_id_for_user
(
user
,
save
=
True
):
"""
Return a unique id for a user, suitable for inserting into
...
...
@@ -1035,6 +1044,9 @@ class CourseAccessRole(models.Model):
"""
return
self
.
_key
<
other
.
_key
def
__unicode__
(
self
):
return
"[CourseAccessRole] user: {} role: {} org: {} course: {}"
.
format
(
self
.
user
.
username
,
self
.
role
,
self
.
org
,
self
.
course_id
)
#### Helper methods for use from python manage.py shell and other classes.
...
...
common/djangoapps/student/roles.py
View file @
665ccdb7
...
...
@@ -201,6 +201,13 @@ class CourseInstructorRole(CourseRole):
super
(
CourseInstructorRole
,
self
)
.
__init__
(
self
.
ROLE
,
*
args
,
**
kwargs
)
class
CourseFinanceAdminRole
(
CourseRole
):
"""A course Instructor"""
ROLE
=
'finance_admin'
def
__init__
(
self
,
*
args
,
**
kwargs
):
super
(
CourseFinanceAdminRole
,
self
)
.
__init__
(
self
.
ROLE
,
*
args
,
**
kwargs
)
class
CourseBetaTesterRole
(
CourseRole
):
"""A course Beta Tester"""
ROLE
=
'beta_testers'
...
...
common/djangoapps/student/tests/test_microsite.py
0 → 100644
View file @
665ccdb7
"""
Test for User Creation from Micro-Sites
"""
from
django.test
import
TestCase
from
student.models
import
UserSignupSource
import
mock
from
django.core.urlresolvers
import
reverse
def
fake_site_name
(
name
,
default
=
None
):
# pylint: disable=W0613
"""
create a fake microsite site name
"""
if
name
==
'SITE_NAME'
:
return
'openedx.localhost'
else
:
return
None
class
TestMicrosite
(
TestCase
):
"""Test for Account Creation from a white labeled Micro-Sites"""
def
setUp
(
self
):
self
.
username
=
"test_user"
self
.
url
=
reverse
(
"create_account"
)
self
.
params
=
{
"username"
:
self
.
username
,
"email"
:
"test@example.org"
,
"password"
:
"testpass"
,
"name"
:
"Test User"
,
"honor_code"
:
"true"
,
"terms_of_service"
:
"true"
,
}
@mock.patch
(
"microsite_configuration.microsite.get_value"
,
fake_site_name
)
def
test_user_signup_source
(
self
):
"""
test to create a user form the microsite and see that it record has been
saved in the UserSignupSource Table
"""
response
=
self
.
client
.
post
(
self
.
url
,
self
.
params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertGreater
(
len
(
UserSignupSource
.
objects
.
filter
(
site
=
'openedx.localhost'
)),
0
)
def
test_user_signup_from_non_micro_site
(
self
):
"""
test to create a user form the non-microsite. The record should not be saved
in the UserSignupSource Table
"""
response
=
self
.
client
.
post
(
self
.
url
,
self
.
params
)
self
.
assertEqual
(
response
.
status_code
,
200
)
self
.
assertEqual
(
len
(
UserSignupSource
.
objects
.
filter
(
site
=
'openedx.localhost'
)),
0
)
common/djangoapps/student/views.py
View file @
665ccdb7
...
...
@@ -30,6 +30,9 @@ from django.utils.translation import ugettext as _, get_language
from
django.views.decorators.cache
import
never_cache
from
django.views.decorators.http
import
require_POST
,
require_GET
from
django.db.models.signals
import
post_save
from
django.dispatch
import
receiver
from
django.template.response
import
TemplateResponse
from
ratelimitbackend.exceptions
import
RateLimitException
...
...
@@ -42,7 +45,7 @@ from student.models import (
Registration
,
UserProfile
,
PendingNameChange
,
PendingEmailChange
,
CourseEnrollment
,
unique_id_for_user
,
CourseEnrollmentAllowed
,
UserStanding
,
LoginFailures
,
create_comments_service_user
,
PasswordHistory
create_comments_service_user
,
PasswordHistory
,
UserSignupSource
)
from
student.forms
import
PasswordResetFormNoActive
...
...
@@ -1021,6 +1024,21 @@ class AccountValidationError(Exception):
super
(
AccountValidationError
,
self
)
.
__init__
(
message
)
self
.
field
=
field
@receiver
(
post_save
,
sender
=
User
)
def
user_signup_handler
(
sender
,
**
kwargs
):
# pylint: disable=W0613
"""
handler that saves the user Signup Source
when the user is created
"""
if
'created'
in
kwargs
and
kwargs
[
'created'
]:
site
=
microsite
.
get_value
(
'SITE_NAME'
)
if
site
:
user_signup_source
=
UserSignupSource
(
user_id
=
kwargs
[
'instance'
],
site
=
site
)
user_signup_source
.
save
()
log
.
info
(
u'user {} originated from a white labeled "Microsite"'
.
format
(
kwargs
[
'instance'
]
.
id
))
def
_do_create_account
(
post_vars
):
"""
Given cleaned post variables, create the User and UserProfile objects, as well as the
...
...
lms/djangoapps/instructor/tests/test_ecommerce.py
0 → 100644
View file @
665ccdb7
"""
Unit tests for Ecommerce feature flag in new instructor dashboard.
"""
from
django.test.utils
import
override_settings
from
django.core.urlresolvers
import
reverse
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
from
student.tests.factories
import
AdminFactory
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
course_modes.models
import
CourseMode
from
shoppingcart.models
import
Coupon
,
PaidCourseRegistration
from
mock
import
patch
from
student.roles
import
CourseFinanceAdminRole
# pylint: disable=E1101
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
class
TestECommerceDashboardViews
(
ModuleStoreTestCase
):
"""
Check for email view on the new instructor dashboard
for Mongo-backed courses
"""
def
setUp
(
self
):
self
.
course
=
CourseFactory
.
create
()
# Create instructor account
self
.
instructor
=
AdminFactory
.
create
()
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
"test"
)
mode
=
CourseMode
(
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
mode_slug
=
'honor'
,
mode_display_name
=
'honor'
,
min_price
=
10
,
currency
=
'usd'
)
mode
.
save
()
# URL for instructor dash
self
.
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
self
.
e_commerce_link
=
'<a href="" data-section="e-commerce">E-Commerce</a>'
CourseFinanceAdminRole
(
self
.
course
.
id
)
.
add_users
(
self
.
instructor
)
def
tearDown
(
self
):
"""
Undo all patches.
"""
patch
.
stopall
()
def
test_pass_e_commerce_tab_in_instructor_dashboard
(
self
):
"""
Test Pass E-commerce Tab is in the Instructor Dashboard
"""
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertTrue
(
self
.
e_commerce_link
in
response
.
content
)
def
test_user_has_finance_admin_rights_in_e_commerce_tab
(
self
):
response
=
self
.
client
.
get
(
self
.
url
)
self
.
assertTrue
(
self
.
e_commerce_link
in
response
.
content
)
# Total amount html should render in e-commerce page, total amount will be 0
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
self
.
course
.
id
)
self
.
assertTrue
(
'<span>Total Amount: <span>$'
+
str
(
total_amount
)
+
'</span></span>'
in
response
.
content
)
# removing the course finance_admin role of login user
CourseFinanceAdminRole
(
self
.
course
.
id
)
.
remove_users
(
self
.
instructor
)
# total amount should not be visible in e-commerce page if the user is not finance admin
url
=
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
url
)
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
self
.
course
.
id
)
self
.
assertFalse
(
'<span>Total Amount: <span>$'
+
str
(
total_amount
)
+
'</span></span>'
in
response
.
content
)
def
test_add_coupon
(
self
):
"""
Test Add Coupon Scenarios. Handle all the HttpResponses return by add_coupon view
"""
# URL for add_coupon
add_coupon_url
=
reverse
(
'add_coupon'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
data
=
{
'code'
:
'A2314'
,
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'description'
:
'ADSADASDSAD'
,
'created_by'
:
self
.
instructor
,
'discount'
:
5
}
response
=
self
.
client
.
post
(
add_coupon_url
,
data
)
self
.
assertTrue
(
"coupon with the coupon code ({code}) added successfully"
.
format
(
code
=
data
[
'code'
])
in
response
.
content
)
data
=
{
'code'
:
'A2314'
,
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
(),
'description'
:
'asdsasda'
,
'created_by'
:
self
.
instructor
,
'discount'
:
111
}
response
=
self
.
client
.
post
(
add_coupon_url
,
data
)
self
.
assertTrue
(
"coupon with the coupon code ({code}) already exist"
.
format
(
code
=
'A2314'
)
in
response
.
content
)
response
=
self
.
client
.
post
(
self
.
url
)
self
.
assertTrue
(
'<td>ADSADASDSAD</td>'
in
response
.
content
)
self
.
assertTrue
(
'<td>A2314</td>'
in
response
.
content
)
self
.
assertFalse
(
'<td>111</td>'
in
response
.
content
)
def
test_delete_coupon
(
self
):
"""
Test Delete Coupon Scenarios. Handle all the HttpResponses return by remove_coupon view
"""
coupon
=
Coupon
(
code
=
'AS452'
,
description
=
'asdsadsa'
,
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
percentage_discount
=
10
,
created_by
=
self
.
instructor
)
coupon
.
save
()
response
=
self
.
client
.
post
(
self
.
url
)
self
.
assertTrue
(
'<td>AS452</td>'
in
response
.
content
)
# URL for remove_coupon
delete_coupon_url
=
reverse
(
'remove_coupon'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
delete_coupon_url
,
{
'id'
:
coupon
.
id
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) updated successfully'
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
coupon
.
is_active
=
False
coupon
.
save
()
response
=
self
.
client
.
post
(
delete_coupon_url
,
{
'id'
:
coupon
.
id
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) is already inactive'
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
response
=
self
.
client
.
post
(
delete_coupon_url
,
{
'id'
:
24454
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) DoesNotExist'
.
format
(
coupon_id
=
24454
)
in
response
.
content
)
response
=
self
.
client
.
post
(
delete_coupon_url
,
{
'id'
:
''
})
self
.
assertTrue
(
'coupon id is None'
in
response
.
content
)
def
test_get_coupon_info
(
self
):
"""
Test Edit Coupon Info Scenarios. Handle all the HttpResponses return by edit_coupon_info view
"""
coupon
=
Coupon
(
code
=
'AS452'
,
description
=
'asdsadsa'
,
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
percentage_discount
=
10
,
created_by
=
self
.
instructor
)
coupon
.
save
()
# URL for edit_coupon_info
edit_url
=
reverse
(
'get_coupon_info'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
edit_url
,
{
'id'
:
coupon
.
id
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) updated successfully'
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
response
=
self
.
client
.
post
(
edit_url
,
{
'id'
:
444444
})
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) DoesNotExist'
.
format
(
coupon_id
=
444444
)
in
response
.
content
)
response
=
self
.
client
.
post
(
edit_url
,
{
'id'
:
''
})
self
.
assertTrue
(
'coupon id not found"'
in
response
.
content
)
coupon
.
is_active
=
False
coupon
.
save
()
response
=
self
.
client
.
post
(
edit_url
,
{
'id'
:
coupon
.
id
})
self
.
assertTrue
(
"coupon with the coupon id ({coupon_id}) is already inactive"
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
def
test_update_coupon
(
self
):
"""
Test Update Coupon Info Scenarios. Handle all the HttpResponses return by update_coupon view
"""
coupon
=
Coupon
(
code
=
'AS452'
,
description
=
'asdsadsa'
,
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
percentage_discount
=
10
,
created_by
=
self
.
instructor
)
coupon
.
save
()
response
=
self
.
client
.
post
(
self
.
url
)
self
.
assertTrue
(
'<td>AS452</td>'
in
response
.
content
)
data
=
{
'coupon_id'
:
coupon
.
id
,
'code'
:
'update_code'
,
'discount'
:
'12'
,
'course_id'
:
coupon
.
course_id
.
to_deprecated_string
()
}
# URL for update_coupon
update_coupon_url
=
reverse
(
'update_coupon'
,
kwargs
=
{
'course_id'
:
self
.
course
.
id
.
to_deprecated_string
()})
response
=
self
.
client
.
post
(
update_coupon_url
,
data
=
data
)
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) updated Successfully'
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
response
=
self
.
client
.
post
(
self
.
url
)
self
.
assertTrue
(
'<td>update_code</td>'
in
response
.
content
)
self
.
assertTrue
(
'<td>12</td>'
in
response
.
content
)
data
[
'coupon_id'
]
=
1000
# Coupon Not Exist with this ID
response
=
self
.
client
.
post
(
update_coupon_url
,
data
=
data
)
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) DoesNotExist'
.
format
(
coupon_id
=
1000
)
in
response
.
content
)
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
)
coupon1
=
Coupon
(
code
=
'11111'
,
description
=
'coupon'
,
course_id
=
self
.
course
.
id
.
to_deprecated_string
(),
percentage_discount
=
20
,
created_by
=
self
.
instructor
)
coupon1
.
save
()
data
=
{
'coupon_id'
:
coupon
.
id
,
'code'
:
'11111'
,
'discount'
:
'12'
}
response
=
self
.
client
.
post
(
update_coupon_url
,
data
=
data
)
self
.
assertTrue
(
'coupon with the coupon id ({coupon_id}) already exist'
.
format
(
coupon_id
=
coupon
.
id
)
in
response
.
content
)
lms/djangoapps/instructor/views/coupons.py
0 → 100644
View file @
665ccdb7
"""
E-commerce Tab Instructor Dashboard Coupons Operations views
"""
from
django.contrib.auth.decorators
import
login_required
from
django.core.exceptions
import
ObjectDoesNotExist
from
django.db.models
import
Q
from
django.views.decorators.http
import
require_POST
from
django.utils.translation
import
ugettext
as
_
from
util.json_request
import
JsonResponse
from
django.http
import
HttpResponse
,
HttpResponseNotFound
from
shoppingcart.models
import
Coupon
import
logging
log
=
logging
.
getLogger
(
__name__
)
@require_POST
@login_required
def
remove_coupon
(
request
,
course_id
):
# pylint: disable=W0613
"""
remove the coupon against the coupon id
set the coupon is_active flag to false
"""
coupon_id
=
request
.
POST
.
get
(
'id'
,
None
)
if
not
coupon_id
:
return
JsonResponse
({
'message'
:
_
(
'coupon id is None'
)
},
status
=
400
)
# status code 400: Bad Request
try
:
coupon
=
Coupon
.
objects
.
get
(
id
=
coupon_id
)
except
ObjectDoesNotExist
:
return
JsonResponse
({
'message'
:
_
(
'coupon with the coupon id ({coupon_id}) DoesNotExist'
)
.
format
(
coupon_id
=
coupon_id
)
},
status
=
400
)
# status code 400: Bad Request
if
not
coupon
.
is_active
:
return
JsonResponse
({
'message'
:
_
(
'coupon with the coupon id ({coupon_id}) is already inactive'
)
.
format
(
coupon_id
=
coupon_id
)
},
status
=
400
)
# status code 400: Bad Request
coupon
.
is_active
=
False
coupon
.
save
()
return
JsonResponse
({
'message'
:
_
(
'coupon with the coupon id ({coupon_id}) updated successfully'
)
.
format
(
coupon_id
=
coupon_id
)
})
# status code 200: OK by default
@require_POST
@login_required
def
add_coupon
(
request
,
course_id
):
# pylint: disable=W0613
"""
add coupon in the Coupons Table
"""
code
=
request
.
POST
.
get
(
'code'
)
# check if the code is already in the Coupons Table and active
coupon
=
Coupon
.
objects
.
filter
(
is_active
=
True
,
code
=
code
)
if
coupon
:
return
HttpResponseNotFound
(
_
(
"coupon with the coupon code ({code}) already exist"
)
.
format
(
code
=
code
))
description
=
request
.
POST
.
get
(
'description'
)
course_id
=
request
.
POST
.
get
(
'course_id'
)
discount
=
request
.
POST
.
get
(
'discount'
)
coupon
=
Coupon
(
code
=
code
,
description
=
description
,
course_id
=
course_id
,
percentage_discount
=
discount
,
created_by_id
=
request
.
user
.
id
)
coupon
.
save
()
return
HttpResponse
(
_
(
"coupon with the coupon code ({code}) added successfully"
)
.
format
(
code
=
code
))
@require_POST
@login_required
def
update_coupon
(
request
,
course_id
):
# pylint: disable=W0613
"""
update the coupon object in the database
"""
coupon_id
=
request
.
POST
.
get
(
'coupon_id'
,
None
)
if
not
coupon_id
:
return
HttpResponseNotFound
(
_
(
"coupon id not found"
))
try
:
coupon
=
Coupon
.
objects
.
get
(
pk
=
coupon_id
)
except
ObjectDoesNotExist
:
return
HttpResponseNotFound
(
_
(
"coupon with the coupon id ({coupon_id}) DoesNotExist"
)
.
format
(
coupon_id
=
coupon_id
))
code
=
request
.
POST
.
get
(
'code'
)
filtered_coupons
=
Coupon
.
objects
.
filter
(
~
Q
(
id
=
coupon_id
),
code
=
code
,
is_active
=
True
)
if
filtered_coupons
:
return
HttpResponseNotFound
(
_
(
"coupon with the coupon id ({coupon_id}) already exists"
)
.
format
(
coupon_id
=
coupon_id
))
description
=
request
.
POST
.
get
(
'description'
)
course_id
=
request
.
POST
.
get
(
'course_id'
)
discount
=
request
.
POST
.
get
(
'discount'
)
coupon
.
code
=
code
coupon
.
description
=
description
coupon
.
course_id
=
course_id
coupon
.
percentage_discount
=
discount
coupon
.
save
()
return
HttpResponse
(
_
(
"coupon with the coupon id ({coupon_id}) updated Successfully"
)
.
format
(
coupon_id
=
coupon_id
))
@require_POST
@login_required
def
get_coupon_info
(
request
,
course_id
):
# pylint: disable=W0613
"""
get the coupon information to display in the pop up form
"""
coupon_id
=
request
.
POST
.
get
(
'id'
,
None
)
if
not
coupon_id
:
return
JsonResponse
({
'message'
:
_
(
"coupon id not found"
)
},
status
=
400
)
# status code 400: Bad Request
try
:
coupon
=
Coupon
.
objects
.
get
(
id
=
coupon_id
)
except
ObjectDoesNotExist
:
return
JsonResponse
({
'message'
:
_
(
"coupon with the coupon id ({coupon_id}) DoesNotExist"
)
.
format
(
coupon_id
=
coupon_id
)
},
status
=
400
)
# status code 400: Bad Request
if
not
coupon
.
is_active
:
return
JsonResponse
({
'message'
:
_
(
"coupon with the coupon id ({coupon_id}) is already inactive"
)
.
format
(
coupon_id
=
coupon_id
)
},
status
=
400
)
# status code 400: Bad Request
return
JsonResponse
({
'coupon_code'
:
coupon
.
code
,
'coupon_description'
:
coupon
.
description
,
'coupon_course_id'
:
coupon
.
course_id
.
to_deprecated_string
(),
'coupon_discount'
:
coupon
.
percentage_discount
,
'message'
:
_
(
'coupon with the coupon id ({coupon_id}) updated successfully'
)
.
format
(
coupon_id
=
coupon_id
)
})
# status code 200: OK by default
lms/djangoapps/instructor/views/instructor_dashboard.py
View file @
665ccdb7
...
...
@@ -26,6 +26,10 @@ from courseware.courses import get_course_by_id, get_cms_course_link, get_course
from
django_comment_client.utils
import
has_forum_access
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
from
student.roles
import
CourseFinanceAdminRole
from
bulk_email.models
import
CourseAuthorization
from
class_dashboard.dashboard_data
import
get_section_display_name
,
get_array_section_has_problem
...
...
@@ -49,6 +53,7 @@ def instructor_dashboard_2(request, course_id):
access
=
{
'admin'
:
request
.
user
.
is_staff
,
'instructor'
:
has_access
(
request
.
user
,
'instructor'
,
course
),
'finance_admin'
:
CourseFinanceAdminRole
(
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
...
...
@@ -66,6 +71,12 @@ def instructor_dashboard_2(request, course_id):
_section_analytics
(
course_key
,
access
),
]
#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
:
course_mode_has_price
=
True
if
(
settings
.
FEATURES
.
get
(
'INDIVIDUAL_DUE_DATES'
)
and
access
[
'instructor'
]):
sections
.
insert
(
3
,
_section_extensions
(
course
))
...
...
@@ -77,6 +88,11 @@ def instructor_dashboard_2(request, course_id):
if
settings
.
FEATURES
[
'CLASS_DASHBOARD'
]
and
access
[
'staff'
]:
sections
.
append
(
_section_metrics
(
course_key
,
access
))
# Gate access to Ecommerce tab
if
course_mode_has_price
:
sections
.
append
(
_section_e_commerce
(
course_key
,
access
))
studio_url
=
None
if
is_studio_course
:
studio_url
=
get_cms_course_link
(
course
)
...
...
@@ -111,6 +127,29 @@ section_display_name will be used to generate link titles in the nav bar.
"""
# pylint: disable=W0105
def
_section_e_commerce
(
course_key
,
access
):
""" Provide data for the corresponding dashboard section """
coupons
=
Coupon
.
objects
.
filter
(
course_id
=
course_key
)
.
order_by
(
'-is_active'
)
total_amount
=
None
if
access
[
'finance_admin'
]:
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
course_key
)
section_data
=
{
'section_key'
:
'e-commerce'
,
'section_display_name'
:
_
(
'E-Commerce'
),
'access'
:
access
,
'course_id'
:
course_key
.
to_deprecated_string
(),
'ajax_remove_coupon_url'
:
reverse
(
'remove_coupon'
,
kwargs
=
{
'course_id'
:
course_key
.
to_deprecated_string
()}),
'ajax_get_coupon_info'
:
reverse
(
'get_coupon_info'
,
kwargs
=
{
'course_id'
:
course_key
.
to_deprecated_string
()}),
'ajax_update_coupon'
:
reverse
(
'update_coupon'
,
kwargs
=
{
'course_id'
:
course_key
.
to_deprecated_string
()}),
'ajax_add_coupon'
:
reverse
(
'add_coupon'
,
kwargs
=
{
'course_id'
:
course_key
.
to_deprecated_string
()}),
'instructor_url'
:
reverse
(
'instructor_dashboard'
,
kwargs
=
{
'course_id'
:
course_key
.
to_deprecated_string
()}),
'coupons'
:
coupons
,
'total_amount'
:
total_amount
,
}
return
section_data
def
_section_course_info
(
course_key
,
access
):
""" Provide data for the corresponding dashboard section """
course
=
get_course_by_id
(
course_key
,
depth
=
None
)
...
...
lms/djangoapps/shoppingcart/exceptions.py
View file @
665ccdb7
...
...
@@ -28,6 +28,18 @@ class CourseDoesNotExistException(InvalidCartItem):
pass
class
CouponDoesNotExistException
(
InvalidCartItem
):
pass
class
CouponAlreadyExistException
(
InvalidCartItem
):
pass
class
ItemDoesNotExistAgainstCouponException
(
InvalidCartItem
):
pass
class
ReportException
(
Exception
):
pass
...
...
lms/djangoapps/shoppingcart/migrations/0008_auto__add_coupons__add_couponredemption__chg_field_certificateitem_cou.py
0 → 100644
View file @
665ccdb7
# -*- 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 model 'Coupons'
db
.
create_table
(
'shoppingcart_coupons'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'code'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'description'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
,
null
=
True
,
blank
=
True
)),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
)),
(
'percentage_discount'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
)),
(
'created_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'auth.User'
])),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
6
,
24
,
0
,
0
))),
(
'is_active'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
True
)),
))
db
.
send_create_signal
(
'shoppingcart'
,
[
'Coupons'
])
# Adding model 'CouponRedemption'
db
.
create_table
(
'shoppingcart_couponredemption'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'order'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'shoppingcart.Order'
])),
(
'user'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'auth.User'
])),
(
'coupon'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'shoppingcart.Coupons'
])),
))
db
.
send_create_signal
(
'shoppingcart'
,
[
'CouponRedemption'
])
# Changing field 'CertificateItem.course_id'
db
.
alter_column
(
'shoppingcart_certificateitem'
,
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
128
))
# Changing field 'PaidCourseRegistrationAnnotation.course_id'
db
.
alter_column
(
'shoppingcart_paidcourseregistrationannotation'
,
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
unique
=
True
,
max_length
=
128
))
# Changing field 'PaidCourseRegistration.course_id'
db
.
alter_column
(
'shoppingcart_paidcourseregistration'
,
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
128
))
# Adding field 'OrderItem.discount_price'
db
.
add_column
(
'shoppingcart_orderitem'
,
'discount_price'
,
self
.
gf
(
'django.db.models.fields.DecimalField'
)(
null
=
True
,
max_digits
=
30
,
decimal_places
=
2
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Deleting model 'Coupons'
db
.
delete_table
(
'shoppingcart_coupons'
)
# Deleting model 'CouponRedemption'
db
.
delete_table
(
'shoppingcart_couponredemption'
)
# Changing field 'CertificateItem.course_id'
db
.
alter_column
(
'shoppingcart_certificateitem'
,
'course_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
128
))
# Changing field 'PaidCourseRegistrationAnnotation.course_id'
db
.
alter_column
(
'shoppingcart_paidcourseregistrationannotation'
,
'course_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
128
,
unique
=
True
))
# Changing field 'PaidCourseRegistration.course_id'
db
.
alter_column
(
'shoppingcart_paidcourseregistration'
,
'course_id'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
128
))
# Deleting field 'OrderItem.discount_price'
db
.
delete_column
(
'shoppingcart_orderitem'
,
'discount_price'
)
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.couponredemption'
:
{
'Meta'
:
{
'object_name'
:
'CouponRedemption'
},
'coupon'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Coupons']"
}),
'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.coupons'
:
{
'Meta'
:
{
'object_name'
:
'Coupons'
},
'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(2014, 6, 24, 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'
}),
'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.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'
}),
'currency'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'usd'"
,
'max_length'
:
'8'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'processor_reply_dump'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'purchase_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'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'
},
'currency'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'usd'"
,
'max_length'
:
'8'
}),
'discount_price'
:
(
'django.db.models.fields.DecimalField'
,
[],
{
'null'
:
'True'
,
'max_digits'
:
'30'
,
'decimal_places'
:
'2'
}),
'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'
}),
'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_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'
})
},
'student.courseenrollment'
:
{
'Meta'
:
{
'ordering'
:
"('user', 'course_id')"
,
'unique_together'
:
"(('user', 'course_id'),)"
,
'object_name'
:
'CourseEnrollment'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'honor'"
,
'max_length'
:
'100'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'shoppingcart'
]
\ No newline at end of file
lms/djangoapps/shoppingcart/migrations/0009_auto__del_coupons__add_courseregistrationcode__add_coupon__chg_field_c.py
0 → 100644
View file @
665ccdb7
# -*- 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
):
# Deleting model 'Coupons'
db
.
delete_table
(
'shoppingcart_coupons'
)
# Adding model 'CourseRegistrationCode'
db
.
create_table
(
'shoppingcart_courseregistrationcode'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'code'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
db_index
=
True
)),
(
'transaction_group_name'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
db_index
=
True
,
max_length
=
255
,
null
=
True
,
blank
=
True
)),
(
'created_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'created_by_user'
,
to
=
orm
[
'auth.User'
])),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
7
,
1
,
0
,
0
))),
(
'redeemed_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
related_name
=
'redeemed_by_user'
,
null
=
True
,
to
=
orm
[
'auth.User'
])),
(
'redeemed_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
7
,
1
,
0
,
0
),
null
=
True
)),
))
db
.
send_create_signal
(
'shoppingcart'
,
[
'CourseRegistrationCode'
])
# Adding model 'Coupon'
db
.
create_table
(
'shoppingcart_coupon'
,
(
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'code'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'description'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
,
null
=
True
,
blank
=
True
)),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
)),
(
'percentage_discount'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
)),
(
'created_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'auth.User'
])),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
7
,
1
,
0
,
0
))),
(
'is_active'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
True
)),
))
db
.
send_create_signal
(
'shoppingcart'
,
[
'Coupon'
])
# Changing field 'CouponRedemption.coupon'
db
.
alter_column
(
'shoppingcart_couponredemption'
,
'coupon_id'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'shoppingcart.Coupon'
]))
# Deleting field 'OrderItem.discount_price'
db
.
delete_column
(
'shoppingcart_orderitem'
,
'discount_price'
)
# Adding field 'OrderItem.list_price'
db
.
add_column
(
'shoppingcart_orderitem'
,
'list_price'
,
self
.
gf
(
'django.db.models.fields.DecimalField'
)(
null
=
True
,
max_digits
=
30
,
decimal_places
=
2
),
keep_default
=
False
)
def
backwards
(
self
,
orm
):
# Adding model 'Coupons'
db
.
create_table
(
'shoppingcart_coupons'
,
(
(
'code'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
32
,
db_index
=
True
)),
(
'percentage_discount'
,
self
.
gf
(
'django.db.models.fields.IntegerField'
)(
default
=
0
)),
(
'description'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
max_length
=
255
,
null
=
True
,
blank
=
True
)),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
)),
(
'created_at'
,
self
.
gf
(
'django.db.models.fields.DateTimeField'
)(
default
=
datetime
.
datetime
(
2014
,
6
,
24
,
0
,
0
))),
(
'is_active'
,
self
.
gf
(
'django.db.models.fields.BooleanField'
)(
default
=
True
)),
(
'id'
,
self
.
gf
(
'django.db.models.fields.AutoField'
)(
primary_key
=
True
)),
(
'created_by'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'auth.User'
])),
))
db
.
send_create_signal
(
'shoppingcart'
,
[
'Coupons'
])
# Deleting model 'CourseRegistrationCode'
db
.
delete_table
(
'shoppingcart_courseregistrationcode'
)
# Deleting model 'Coupon'
db
.
delete_table
(
'shoppingcart_coupon'
)
# Changing field 'CouponRedemption.coupon'
db
.
alter_column
(
'shoppingcart_couponredemption'
,
'coupon_id'
,
self
.
gf
(
'django.db.models.fields.related.ForeignKey'
)(
to
=
orm
[
'shoppingcart.Coupons'
]))
# Adding field 'OrderItem.discount_price'
db
.
add_column
(
'shoppingcart_orderitem'
,
'discount_price'
,
self
.
gf
(
'django.db.models.fields.DecimalField'
)(
null
=
True
,
max_digits
=
30
,
decimal_places
=
2
),
keep_default
=
False
)
# Deleting field 'OrderItem.list_price'
db
.
delete_column
(
'shoppingcart_orderitem'
,
'list_price'
)
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(2014, 7, 1, 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'
}),
'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.courseregistrationcode'
:
{
'Meta'
:
{
'object_name'
:
'CourseRegistrationCode'
},
'code'
:
(
'django.db.models.fields.CharField'
,
[],
{
'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(2014, 7, 1, 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'
}),
'redeemed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2014, 7, 1, 0, 0)'
,
'null'
:
'True'
}),
'redeemed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'redeemed_by_user'"
,
'null'
:
'True'
,
'to'
:
"orm['auth.User']"
}),
'transaction_group_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'db_index'
:
'True'
,
'max_length'
:
'255'
,
'null'
:
'True'
,
'blank'
:
'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'
}),
'currency'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'usd'"
,
'max_length'
:
'8'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'processor_reply_dump'
:
(
'django.db.models.fields.TextField'
,
[],
{
'blank'
:
'True'
}),
'purchase_time'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'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'
},
'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'
}),
'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_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'
})
},
'student.courseenrollment'
:
{
'Meta'
:
{
'ordering'
:
"('user', 'course_id')"
,
'unique_together'
:
"(('user', 'course_id'),)"
,
'object_name'
:
'CourseEnrollment'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'honor'"
,
'max_length'
:
'100'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'shoppingcart'
]
\ No newline at end of file
lms/djangoapps/shoppingcart/models.py
View file @
665ccdb7
...
...
@@ -31,7 +31,7 @@ from xmodule_django.models import CourseKeyField
from
verify_student.models
import
SoftwareSecurePhotoVerification
from
.exceptions
import
(
InvalidCartItem
,
PurchasedCallbackException
,
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
)
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
CouponAlreadyExistException
,
ItemDoesNotExistAgainstCouponException
)
from
microsite_configuration
import
microsite
...
...
@@ -217,6 +217,7 @@ class OrderItem(models.Model):
status
=
models
.
CharField
(
max_length
=
32
,
default
=
'cart'
,
choices
=
ORDER_STATUSES
,
db_index
=
True
)
qty
=
models
.
IntegerField
(
default
=
1
)
unit_cost
=
models
.
DecimalField
(
default
=
0.0
,
decimal_places
=
2
,
max_digits
=
30
)
list_price
=
models
.
DecimalField
(
decimal_places
=
2
,
max_digits
=
30
,
null
=
True
)
line_desc
=
models
.
CharField
(
default
=
"Misc. Item"
,
max_length
=
1024
)
currency
=
models
.
CharField
(
default
=
"usd"
,
max_length
=
8
)
# lower case ISO currency codes
fulfilled_time
=
models
.
DateTimeField
(
null
=
True
,
db_index
=
True
)
...
...
@@ -304,6 +305,78 @@ class OrderItem(models.Model):
return
''
class
CourseRegistrationCode
(
models
.
Model
):
"""
This table contains registration codes
With registration code, a user can register for a course for free
"""
code
=
models
.
CharField
(
max_length
=
32
,
db_index
=
True
)
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
transaction_group_name
=
models
.
CharField
(
max_length
=
255
,
db_index
=
True
,
null
=
True
,
blank
=
True
)
created_by
=
models
.
ForeignKey
(
User
,
related_name
=
'created_by_user'
)
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
redeemed_by
=
models
.
ForeignKey
(
User
,
null
=
True
,
related_name
=
'redeemed_by_user'
)
redeemed_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
),
null
=
True
)
class
Coupon
(
models
.
Model
):
"""
This table contains coupon codes
A user can get a discount offer on course if provide coupon code
"""
code
=
models
.
CharField
(
max_length
=
32
,
db_index
=
True
)
description
=
models
.
CharField
(
max_length
=
255
,
null
=
True
,
blank
=
True
)
course_id
=
CourseKeyField
(
max_length
=
255
)
percentage_discount
=
models
.
IntegerField
(
default
=
0
)
created_by
=
models
.
ForeignKey
(
User
)
created_at
=
models
.
DateTimeField
(
default
=
datetime
.
now
(
pytz
.
utc
))
is_active
=
models
.
BooleanField
(
default
=
True
)
class
CouponRedemption
(
models
.
Model
):
"""
This table contain coupon redemption info
"""
order
=
models
.
ForeignKey
(
Order
,
db_index
=
True
)
user
=
models
.
ForeignKey
(
User
,
db_index
=
True
)
coupon
=
models
.
ForeignKey
(
Coupon
,
db_index
=
True
)
@classmethod
def
get_discount_price
(
cls
,
percentage_discount
,
value
):
"""
return discounted price against coupon
"""
discount
=
Decimal
(
"{0:.2f}"
.
format
(
Decimal
(
percentage_discount
/
100.00
)
*
value
))
return
value
-
discount
@classmethod
def
add_coupon_redemption
(
cls
,
coupon
,
order
):
"""
add coupon info into coupon_redemption model
"""
cart_items
=
order
.
orderitem_set
.
all
()
.
select_subclasses
()
for
item
in
cart_items
:
if
getattr
(
item
,
'course_id'
):
if
item
.
course_id
==
coupon
.
course_id
:
coupon_redemption
,
created
=
cls
.
objects
.
get_or_create
(
order
=
order
,
user
=
order
.
user
,
coupon
=
coupon
)
if
not
created
:
log
.
exception
(
"Coupon '{0}' already exist for user '{1}' against order id '{2}'"
.
format
(
coupon
.
code
,
order
.
user
.
username
,
order
.
id
))
raise
CouponAlreadyExistException
discount_price
=
cls
.
get_discount_price
(
coupon
.
percentage_discount
,
item
.
unit_cost
)
item
.
list_price
=
item
.
unit_cost
item
.
unit_cost
=
discount_price
item
.
save
()
log
.
info
(
"Discount generated for user {0} against order id '{1}' "
.
format
(
order
.
user
.
username
,
order
.
id
))
return
coupon_redemption
log
.
warning
(
"Course item does not exist for coupon '{0}'"
.
format
(
coupon
.
code
))
raise
ItemDoesNotExistAgainstCouponException
class
PaidCourseRegistration
(
OrderItem
):
"""
This is an inventory item for paying for a course registration
...
...
@@ -320,6 +393,19 @@ class PaidCourseRegistration(OrderItem):
for
item
in
order
.
orderitem_set
.
all
()
.
select_subclasses
(
"paidcourseregistration"
)]
@classmethod
def
get_total_amount_of_purchased_item
(
cls
,
course_key
):
"""
This will return the total amount of money that a purchased course generated
"""
total_cost
=
0
result
=
cls
.
objects
.
filter
(
course_id
=
course_key
,
status
=
'purchased'
)
.
aggregate
(
total
=
Sum
(
'unit_cost'
,
field
=
'qty * unit_cost'
))
# pylint: disable=E1101
if
result
[
'total'
]
is
not
None
:
total_cost
=
result
[
'total'
]
return
total_cost
@classmethod
@transaction.commit_on_success
def
add_to_order
(
cls
,
order
,
course_id
,
mode_slug
=
CourseMode
.
DEFAULT_MODE_SLUG
,
cost
=
None
,
currency
=
None
):
"""
...
...
lms/djangoapps/shoppingcart/processors/CyberSource.py
View file @
665ccdb7
...
...
@@ -16,8 +16,25 @@ from django.utils.translation import ugettext as _
from
edxmako.shortcuts
import
render_to_string
from
shoppingcart.models
import
Order
from
shoppingcart.processors.exceptions
import
*
from
microsite_configuration
import
microsite
def
get_cybersource_config
():
"""
This method will return any microsite specific cybersource configuration, otherwise
we return the default configuration
"""
config_key
=
microsite
.
get_value
(
'cybersource_config_key'
)
config
=
{}
if
config_key
:
# The microsite CyberSource configuration will be subkeys inside of the normal default
# CyberSource configuration
config
=
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'microsites'
][
config_key
]
else
:
config
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
return
config
def
process_postpay_callback
(
params
):
"""
The top level call to this module, basically
...
...
@@ -53,7 +70,7 @@ def processor_hash(value):
"""
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
"""
shared_secret
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'SHARED_SECRET'
,
''
)
shared_secret
=
get_cybersource_config
()
.
get
(
'SHARED_SECRET'
,
''
)
hash_obj
=
hmac
.
new
(
shared_secret
.
encode
(
'utf-8'
),
value
.
encode
(
'utf-8'
),
sha1
)
return
binascii
.
b2a_base64
(
hash_obj
.
digest
())[:
-
1
]
# last character is a '\n', which we don't want
...
...
@@ -63,9 +80,9 @@ def sign(params, signed_fields_key='orderPage_signedFields', full_sig_key='order
params needs to be an ordered dict, b/c cybersource documentation states that order is important.
Reverse engineered from PHP version provided by cybersource
"""
merchant_id
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'MERCHANT_ID'
,
''
)
order_page_version
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'ORDERPAGE_VERSION'
,
'7'
)
serial_number
=
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'SERIAL_NUMBER'
,
''
)
merchant_id
=
get_cybersource_config
()
.
get
(
'MERCHANT_ID'
,
''
)
order_page_version
=
get_cybersource_config
()
.
get
(
'ORDERPAGE_VERSION'
,
'7'
)
serial_number
=
get_cybersource_config
()
.
get
(
'SERIAL_NUMBER'
,
''
)
params
[
'merchantID'
]
=
merchant_id
params
[
'orderPage_timestamp'
]
=
int
(
time
.
time
()
*
1000
)
...
...
@@ -123,7 +140,7 @@ def get_purchase_params(cart):
return
params
def
get_purchase_endpoint
():
return
settings
.
CC_PROCESSOR
[
'CyberSource'
]
.
get
(
'PURCHASE_ENDPOINT'
,
''
)
return
get_cybersource_config
()
.
get
(
'PURCHASE_ENDPOINT'
,
''
)
def
payment_accepted
(
params
):
"""
...
...
@@ -215,7 +232,9 @@ def record_purchase(params, order):
def
get_processor_decline_html
(
params
):
"""Have to parse through the error codes to return a helpful message"""
payment_support_email
=
settings
.
PAYMENT_SUPPORT_EMAIL
# see if we have an override in the microsites
payment_support_email
=
microsite
.
get_value
(
'payment_support_email'
,
settings
.
PAYMENT_SUPPORT_EMAIL
)
msg
=
dedent
(
_
(
"""
...
...
@@ -238,7 +257,8 @@ def get_processor_decline_html(params):
def
get_processor_exception_html
(
exception
):
"""Return error HTML associated with exception"""
payment_support_email
=
settings
.
PAYMENT_SUPPORT_EMAIL
# see if we have an override in the microsites
payment_support_email
=
microsite
.
get_value
(
'payment_support_email'
,
settings
.
PAYMENT_SUPPORT_EMAIL
)
if
isinstance
(
exception
,
CCProcessorDataException
):
msg
=
dedent
(
_
(
"""
...
...
lms/djangoapps/shoppingcart/processors/CyberSource2.py
0 → 100644
View file @
665ccdb7
### Implementation of support for the Cybersource Credit card processor using the new
### Secure Acceptance API. The previous Hosted Order Page API is being deprecated as of 9/14
### It is mostly the same as the CyberSource.py file, but we have a new file so that we can
### maintain some backwards-compatibility in case of a need to quickly roll back (i.e.
### configuration change rather than code rollback )
### The name of this file should be used as the key of the dict in the CC_PROCESSOR setting
### Implementes interface as specified by __init__.py
import
hmac
import
binascii
import
re
import
json
import
uuid
from
datetime
import
datetime
from
collections
import
OrderedDict
,
defaultdict
from
decimal
import
Decimal
,
InvalidOperation
from
hashlib
import
sha256
from
textwrap
import
dedent
from
django.conf
import
settings
from
django.utils.translation
import
ugettext
as
_
from
edxmako.shortcuts
import
render_to_string
from
shoppingcart.models
import
Order
from
shoppingcart.processors.exceptions
import
*
from
microsite_configuration
import
microsite
from
django.core.urlresolvers
import
reverse
def
get_cybersource_config
():
"""
This method will return any microsite specific cybersource configuration, otherwise
we return the default configuration
"""
config_key
=
microsite
.
get_value
(
'cybersource_config_key'
)
config
=
{}
if
config_key
:
# The microsite CyberSource configuration will be subkeys inside of the normal default
# CyberSource configuration
config
=
settings
.
CC_PROCESSOR
[
'CyberSource2'
][
'microsites'
][
config_key
]
else
:
config
=
settings
.
CC_PROCESSOR
[
'CyberSource2'
]
return
config
def
process_postpay_callback
(
params
):
"""
The top level call to this module, basically
This function is handed the callback request after the customer has entered the CC info and clicked "buy"
on the external Hosted Order Page.
It is expected to verify the callback and determine if the payment was successful.
It returns {'success':bool, 'order':Order, 'error_html':str}
If successful this function must have the side effect of marking the order purchased and calling the
purchased_callbacks of the cart items.
If unsuccessful this function should not have those side effects but should try to figure out why and
return a helpful-enough error message in error_html.
"""
try
:
result
=
payment_accepted
(
params
)
if
result
[
'accepted'
]:
# SUCCESS CASE first, rest are some sort of oddity
record_purchase
(
params
,
result
[
'order'
])
return
{
'success'
:
True
,
'order'
:
result
[
'order'
],
'error_html'
:
''
}
else
:
return
{
'success'
:
False
,
'order'
:
result
[
'order'
],
'error_html'
:
get_processor_decline_html
(
params
)}
except
CCProcessorException
as
error
:
return
{
'success'
:
False
,
'order'
:
None
,
# due to exception we may not have the order
'error_html'
:
get_processor_exception_html
(
error
)}
def
processor_hash
(
value
):
"""
Performs the base64(HMAC_SHA1(key, value)) used by CyberSource Hosted Order Page
"""
secret_key
=
get_cybersource_config
()
.
get
(
'SECRET_KEY'
,
''
)
hash_obj
=
hmac
.
new
(
secret_key
,
value
,
sha256
)
return
binascii
.
b2a_base64
(
hash_obj
.
digest
())[:
-
1
]
# last character is a '\n', which we don't want
def
sign
(
params
,
signed_fields_key
=
'signed_field_names'
,
full_sig_key
=
'signature'
):
"""
params needs to be an ordered dict, b/c cybersource documentation states that order is important.
Reverse engineered from PHP version provided by cybersource
"""
fields
=
u","
.
join
(
params
.
keys
())
params
[
signed_fields_key
]
=
fields
signed_fields
=
params
.
get
(
signed_fields_key
,
''
)
.
split
(
','
)
values
=
u","
.
join
([
u"{0}={1}"
.
format
(
i
,
params
.
get
(
i
,
''
))
for
i
in
signed_fields
])
params
[
full_sig_key
]
=
processor_hash
(
values
)
params
[
signed_fields_key
]
=
fields
return
params
def
render_purchase_form_html
(
cart
):
"""
Renders the HTML of the hidden POST form that must be used to initiate a purchase with CyberSource
"""
return
render_to_string
(
'shoppingcart/cybersource_form.html'
,
{
'action'
:
get_purchase_endpoint
(),
'params'
:
get_signed_purchase_params
(
cart
),
})
def
get_signed_purchase_params
(
cart
):
"""
This method will return a digitally signed set of CyberSource parameters
"""
return
sign
(
get_purchase_params
(
cart
))
def
get_purchase_params
(
cart
):
"""
This method will build out a dictionary of parameters needed by CyberSource to complete the transaction
"""
total_cost
=
cart
.
total_cost
amount
=
"{0:0.2f}"
.
format
(
total_cost
)
params
=
OrderedDict
()
params
[
'amount'
]
=
amount
params
[
'currency'
]
=
cart
.
currency
params
[
'orderNumber'
]
=
"OrderId: {0:d}"
.
format
(
cart
.
id
)
params
[
'access_key'
]
=
get_cybersource_config
()
.
get
(
'ACCESS_KEY'
,
''
)
params
[
'profile_id'
]
=
get_cybersource_config
()
.
get
(
'PROFILE_ID'
,
''
)
params
[
'reference_number'
]
=
cart
.
id
params
[
'transaction_type'
]
=
'sale'
params
[
'locale'
]
=
'en'
params
[
'signed_date_time'
]
=
datetime
.
utcnow
()
.
strftime
(
'
%
Y-
%
m-
%
dT
%
H:
%
M:
%
SZ'
)
params
[
'signed_field_names'
]
=
'access_key,profile_id,amount,currency,transaction_type,reference_number,signed_date_time,locale,transaction_uuid,signed_field_names,unsigned_field_names,orderNumber'
params
[
'unsigned_field_names'
]
=
''
params
[
'transaction_uuid'
]
=
uuid
.
uuid4
()
params
[
'payment_method'
]
=
'card'
if
hasattr
(
cart
,
'context'
)
and
'request_domain'
in
cart
.
context
:
params
[
'override_custom_receipt_page'
]
=
'{0}{1}'
.
format
(
cart
.
context
[
'request_domain'
],
reverse
(
'shoppingcart.views.postpay_callback'
)
)
return
params
def
get_purchase_endpoint
():
"""
Helper function to return the CyberSource endpoint configuration
"""
return
get_cybersource_config
()
.
get
(
'PURCHASE_ENDPOINT'
,
''
)
def
payment_accepted
(
params
):
"""
Check that cybersource has accepted the payment
params: a dictionary of POST parameters returned by CyberSource in their post-payment callback
returns: true if the payment was correctly accepted, for the right amount
false if the payment was not accepted
raises: CCProcessorDataException if the returned message did not provide required parameters
CCProcessorWrongAmountException if the amount charged is different than the order amount
"""
#make sure required keys are present and convert their values to the right type
valid_params
=
{}
for
key
,
key_type
in
[(
'req_reference_number'
,
int
),
(
'req_currency'
,
str
),
(
'decision'
,
str
)]:
if
key
not
in
params
:
raise
CCProcessorDataException
(
_
(
"The payment processor did not return a required parameter: {0}"
.
format
(
key
))
)
try
:
valid_params
[
key
]
=
key_type
(
params
[
key
])
except
ValueError
:
raise
CCProcessorDataException
(
_
(
"The payment processor returned a badly-typed value {0} for param {1}."
.
format
(
params
[
key
],
key
))
)
try
:
order
=
Order
.
objects
.
get
(
id
=
valid_params
[
'req_reference_number'
])
except
Order
.
DoesNotExist
:
raise
CCProcessorDataException
(
_
(
"The payment processor accepted an order whose number is not in our system."
))
if
valid_params
[
'decision'
]
==
'ACCEPT'
:
try
:
# Moved reading of charged_amount here from the valid_params loop above because
# only 'ACCEPT' messages have a 'ccAuthReply_amount' parameter
charged_amt
=
Decimal
(
params
[
'auth_amount'
])
except
InvalidOperation
:
raise
CCProcessorDataException
(
_
(
"The payment processor returned a badly-typed value {0} for param {1}."
.
format
(
params
[
'auth_amount'
],
'auth_amount'
))
)
if
charged_amt
==
order
.
total_cost
and
valid_params
[
'req_currency'
]
==
order
.
currency
:
return
{
'accepted'
:
True
,
'amt_charged'
:
charged_amt
,
'currency'
:
valid_params
[
'req_currency'
],
'order'
:
order
}
else
:
raise
CCProcessorWrongAmountException
(
_
(
"The amount charged by the processor {0} {1} is different than the total cost of the order {2} {3}."
.
format
(
charged_amt
,
valid_params
[
'req_currency'
],
order
.
total_cost
,
order
.
currency
))
)
else
:
return
{
'accepted'
:
False
,
'amt_charged'
:
0
,
'currency'
:
'usd'
,
'order'
:
order
}
def
record_purchase
(
params
,
order
):
"""
Record the purchase and run purchased_callbacks
"""
ccnum_str
=
params
.
get
(
'req_card_number'
,
''
)
mm
=
re
.
search
(
"
\
d"
,
ccnum_str
)
if
mm
:
ccnum
=
ccnum_str
[
mm
.
start
():]
else
:
ccnum
=
"####"
order
.
purchase
(
first
=
params
.
get
(
'req_bill_to_forename'
,
''
),
last
=
params
.
get
(
'req_bill_to_surname'
,
''
),
street1
=
params
.
get
(
'req_bill_to_address_line1'
,
''
),
street2
=
params
.
get
(
'req_bill_to_address_line2'
,
''
),
city
=
params
.
get
(
'req_bill_to_address_city'
,
''
),
state
=
params
.
get
(
'req_bill_to_address_state'
,
''
),
country
=
params
.
get
(
'req_bill_to_address_country'
,
''
),
postalcode
=
params
.
get
(
'req_bill_to_address_postal_code'
,
''
),
ccnum
=
ccnum
,
cardtype
=
CARDTYPE_MAP
[
params
.
get
(
'req_card_type'
,
''
)],
processor_reply_dump
=
json
.
dumps
(
params
)
)
def
get_processor_decline_html
(
params
):
"""Have to parse through the error codes to return a helpful message"""
payment_support_email
=
microsite
.
get_value
(
'payment_support_email'
,
settings
.
PAYMENT_SUPPORT_EMAIL
)
msg
=
dedent
(
_
(
"""
<p class="error_msg">
Sorry! Our payment processor did not accept your payment.
The decision they returned was <span class="decision">{decision}</span>,
and the reason was <span class="reason">{reason_code}:{reason_msg}</span>.
You were not charged. Please try a different form of payment.
Contact us with payment-related questions at {email}.
</p>
"""
))
return
msg
.
format
(
decision
=
params
[
'decision'
],
reason_code
=
params
[
'reason_code'
],
reason_msg
=
REASONCODE_MAP
[
params
[
'reason_code'
]],
email
=
payment_support_email
)
def
get_processor_exception_html
(
exception
):
"""Return error HTML associated with exception"""
payment_support_email
=
microsite
.
get_value
(
'payment_support_email'
,
settings
.
PAYMENT_SUPPORT_EMAIL
)
if
isinstance
(
exception
,
CCProcessorDataException
):
msg
=
dedent
(
_
(
"""
<p class="error_msg">
Sorry! Our payment processor sent us back a payment confirmation that had inconsistent data!
We apologize that we cannot verify whether the charge went through and take further action on your order.
The specific error message is: <span class="exception_msg">{msg}</span>.
Your credit card may possibly have been charged. Contact us with payment-specific questions at {email}.
</p>
"""
.
format
(
msg
=
exception
.
message
,
email
=
payment_support_email
)))
return
msg
elif
isinstance
(
exception
,
CCProcessorWrongAmountException
):
msg
=
dedent
(
_
(
"""
<p class="error_msg">
Sorry! Due to an error your purchase was charged for a different amount than the order total!
The specific error message is: <span class="exception_msg">{msg}</span>.
Your credit card has probably been charged. Contact us with payment-specific questions at {email}.
</p>
"""
.
format
(
msg
=
exception
.
message
,
email
=
payment_support_email
)))
return
msg
# fallthrough case, which basically never happens
return
'<p class="error_msg">EXCEPTION!</p>'
CARDTYPE_MAP
=
defaultdict
(
lambda
:
"UNKNOWN"
)
CARDTYPE_MAP
.
update
(
{
'001'
:
'Visa'
,
'002'
:
'MasterCard'
,
'003'
:
'American Express'
,
'004'
:
'Discover'
,
'005'
:
'Diners Club'
,
'006'
:
'Carte Blanche'
,
'007'
:
'JCB'
,
'014'
:
'EnRoute'
,
'021'
:
'JAL'
,
'024'
:
'Maestro'
,
'031'
:
'Delta'
,
'033'
:
'Visa Electron'
,
'034'
:
'Dankort'
,
'035'
:
'Laser'
,
'036'
:
'Carte Bleue'
,
'037'
:
'Carta Si'
,
'042'
:
'Maestro Int.'
,
'043'
:
'GE Money UK card'
}
)
REASONCODE_MAP
=
defaultdict
(
lambda
:
"UNKNOWN REASON"
)
REASONCODE_MAP
.
update
(
{
'100'
:
_
(
'Successful transaction.'
),
'102'
:
_
(
'One or more fields in the request contains invalid data.'
),
'104'
:
dedent
(
_
(
"""
The access_key and transaction_uuid fields for this authorization request matches the access_key and
transaction_uuid of another authorization request that you sent in the last 15 minutes.
Possible fix: retry the payment after 15 minutes.
"""
)),
'110'
:
_
(
'Only a partial amount was approved.'
),
'200'
:
dedent
(
_
(
"""
The authorization request was approved by the issuing bank but declined by CyberSource
becouse it did not pass the Address Verification System (AVS).
"""
)),
'201'
:
dedent
(
_
(
"""
The issuing bank has questions about the request. You do not receive an
authorization code programmatically, but you might receive one verbally by calling the processor.
Possible fix: retry with another form of payment
"""
)),
'202'
:
dedent
(
_
(
"""
Expired card. You might also receive this if the expiration date you
provided does not match the date the issuing bank has on file.
Possible fix: retry with another form of payment
"""
)),
'203'
:
dedent
(
_
(
"""
General decline of the card. No other information provided by the issuing bank.
Possible fix: retry with another form of payment
"""
)),
'204'
:
_
(
'Insufficient funds in the account. Possible fix: retry with another form of payment'
),
# 205 was Stolen or lost card. Might as well not show this message to the person using such a card.
'205'
:
_
(
'Stolen or lost card'
),
'207'
:
_
(
'Issuing bank unavailable. Possible fix: retry again after a few minutes'
),
'208'
:
dedent
(
_
(
"""
Inactive card or card not authorized for card-not-present transactions.
Possible fix: retry with another form of payment
"""
)),
'210'
:
_
(
'The card has reached the credit limit. Possible fix: retry with another form of payment'
),
'211'
:
_
(
'Invalid card verification number (CVN). Possible fix: retry with another form of payment'
),
# 221 was The customer matched an entry on the processor's negative file.
# Might as well not show this message to the person using such a card.
'221'
:
_
(
'The customer matched an entry on the processors negative file.'
),
'222'
:
_
(
'Account frozen. Possible fix: retry with another form of payment'
),
'230'
:
dedent
(
_
(
"""
The authorization request was approved by the issuing bank but declined by
CyberSource because it did not pass the CVN check.
Possible fix: retry with another form of payment
"""
)),
'231'
:
_
(
'Invalid account number. Possible fix: retry with another form of payment'
),
'232'
:
dedent
(
_
(
"""
The card type is not accepted by the payment processor.
Possible fix: retry with another form of payment
"""
)),
'233'
:
_
(
'General decline by the processor. Possible fix: retry with another form of payment'
),
'234'
:
dedent
(
_
(
"""
There is a problem with the information in your CyberSource account. Please let us know at {0}
"""
.
format
(
settings
.
PAYMENT_SUPPORT_EMAIL
))),
'236'
:
_
(
'Processor Failure. Possible fix: retry the payment'
),
'240'
:
dedent
(
_
(
"""
The card type sent is invalid or does not correlate with the credit card number.
Possible fix: retry with the same card or another form of payment
"""
)),
'475'
:
_
(
'The cardholder is enrolled for payer authentication'
),
'476'
:
_
(
'Payer authentication could not be authenticated'
),
'520'
:
dedent
(
_
(
"""
The authorization request was approved by the issuing bank but declined by CyberSource based
on your legacy Smart Authorization settings.
Possible fix: retry with a different form of payment.
"""
)),
}
)
lms/djangoapps/shoppingcart/processors/tests/test_CyberSource.py
View file @
665ccdb7
...
...
@@ -10,6 +10,8 @@ from shoppingcart.models import Order, OrderItem
from
shoppingcart.processors.CyberSource
import
*
from
shoppingcart.processors.exceptions
import
*
from
mock
import
patch
,
Mock
from
microsite_configuration
import
microsite
import
mock
TEST_CC_PROCESSOR
=
{
...
...
@@ -19,10 +21,28 @@ TEST_CC_PROCESSOR = {
'SERIAL_NUMBER'
:
'12345'
,
'ORDERPAGE_VERSION'
:
'7'
,
'PURCHASE_ENDPOINT'
:
''
,
'microsites'
:
{
'test_microsite'
:
{
'SHARED_SECRET'
:
'secret_override'
,
'MERCHANT_ID'
:
'edx_test_override'
,
'SERIAL_NUMBER'
:
'12345_override'
,
'ORDERPAGE_VERSION'
:
'7'
,
'PURCHASE_ENDPOINT'
:
''
,
}
}
}
}
def
fakemicrosite
(
name
,
default
=
None
):
"""
This is a test mocking function to return a microsite configuration
"""
if
name
==
'cybersource_config_key'
:
return
'test_microsite'
else
:
return
None
@override_settings
(
CC_PROCESSOR
=
TEST_CC_PROCESSOR
)
class
CyberSourceTests
(
TestCase
):
...
...
@@ -33,6 +53,15 @@ class CyberSourceTests(TestCase):
self
.
assertEqual
(
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'MERCHANT_ID'
],
'edx_test'
)
self
.
assertEqual
(
settings
.
CC_PROCESSOR
[
'CyberSource'
][
'SHARED_SECRET'
],
'secret'
)
def
test_microsite_no_override_settings
(
self
):
self
.
assertEqual
(
get_cybersource_config
()[
'MERCHANT_ID'
],
'edx_test'
)
self
.
assertEqual
(
get_cybersource_config
()[
'SHARED_SECRET'
],
'secret'
)
@mock.patch
(
"microsite_configuration.microsite.get_value"
,
fakemicrosite
)
def
test_microsite_override_settings
(
self
):
self
.
assertEqual
(
get_cybersource_config
()[
'MERCHANT_ID'
],
'edx_test_override'
)
self
.
assertEqual
(
get_cybersource_config
()[
'SHARED_SECRET'
],
'secret_override'
)
def
test_hash
(
self
):
"""
Tests the hash function. Basically just hardcodes the answer.
...
...
lms/djangoapps/shoppingcart/tests/test_views.py
View file @
665ccdb7
...
...
@@ -14,7 +14,7 @@ from xmodule.modulestore.tests.django_utils import ModuleStoreTestCase
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
from
shoppingcart.views
import
_can_download_report
,
_get_date_from_str
from
shoppingcart.models
import
Order
,
CertificateItem
,
PaidCourseRegistration
from
shoppingcart.models
import
Order
,
CertificateItem
,
PaidCourseRegistration
,
Coupon
from
student.tests.factories
import
UserFactory
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
...
...
@@ -22,7 +22,8 @@ from edxmako.shortcuts import render_to_response
from
shoppingcart.processors
import
render_purchase_form_html
from
mock
import
patch
,
Mock
from
shoppingcart.views
import
initialize_report
from
decimal
import
Decimal
from
student.tests.factories
import
AdminFactory
def
mock_render_purchase_form_html
(
*
args
,
**
kwargs
):
return
render_purchase_form_html
(
*
args
,
**
kwargs
)
...
...
@@ -45,7 +46,10 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
user
=
UserFactory
.
create
()
self
.
user
.
set_password
(
'password'
)
self
.
user
.
save
()
self
.
instructor
=
AdminFactory
.
create
()
self
.
cost
=
40
self
.
coupon_code
=
'abcde'
self
.
percentage_discount
=
10
self
.
course
=
CourseFactory
.
create
(
org
=
'MITx'
,
number
=
'999'
,
display_name
=
'Robot Super Course'
)
self
.
course_key
=
self
.
course
.
id
self
.
course_mode
=
CourseMode
(
course_id
=
self
.
course_key
,
...
...
@@ -58,6 +62,29 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
self
.
addCleanup
(
patcher
.
stop
)
def
get_discount
(
self
):
"""
This method simple return the discounted amount
"""
val
=
Decimal
(
"{0:.2f}"
.
format
(
Decimal
(
self
.
percentage_discount
/
100.00
)
*
self
.
cost
))
return
self
.
cost
-
val
def
add_coupon
(
self
,
course_key
,
is_active
):
"""
add dummy coupon into models
"""
coupon
=
Coupon
(
code
=
self
.
coupon_code
,
description
=
'testing code'
,
course_id
=
course_key
,
percentage_discount
=
self
.
percentage_discount
,
created_by
=
self
.
user
,
is_active
=
is_active
)
coupon
.
save
()
def
add_course_to_user_cart
(
self
):
"""
adding course to user cart
"""
self
.
login_user
()
reg_item
=
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course_key
)
return
reg_item
def
login_user
(
self
):
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
"password"
)
...
...
@@ -72,6 +99,141 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertEqual
(
resp
.
status_code
,
400
)
self
.
assertIn
(
'The course {0} is already in your cart.'
.
format
(
self
.
course_key
.
to_deprecated_string
()),
resp
.
content
)
def
test_course_discount_invalid_coupon
(
self
):
self
.
add_coupon
(
self
.
course_key
,
True
)
self
.
add_course_to_user_cart
()
non_existing_code
=
"non_existing_code"
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
non_existing_code
})
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Discount does not exist against coupon '{0}'."
.
format
(
non_existing_code
),
resp
.
content
)
def
test_course_discount_inactive_coupon
(
self
):
self
.
add_coupon
(
self
.
course_key
,
False
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
400
)
self
.
assertIn
(
"Coupon '{0}' is inactive."
.
format
(
self
.
coupon_code
),
resp
.
content
)
def
test_course_does_not_exist_in_cart_against_valid_coupon
(
self
):
course_key
=
self
.
course_key
.
to_deprecated_string
()
+
'testing'
self
.
add_coupon
(
course_key
,
True
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
404
)
self
.
assertIn
(
"Coupon '{0}' is not valid for any course in the shopping cart."
.
format
(
self
.
coupon_code
),
resp
.
content
)
def
test_course_discount_for_valid_active_coupon_code
(
self
):
self
.
add_coupon
(
self
.
course_key
,
True
)
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
# unit price should be updated for that course
item
=
self
.
cart
.
orderitem_set
.
all
()
.
select_subclasses
()[
0
]
self
.
assertEquals
(
item
.
unit_cost
,
self
.
get_discount
())
# after getting 10 percent discount
self
.
assertEqual
(
self
.
cart
.
total_cost
,
self
.
get_discount
())
# now testing coupon code already used scenario, reusing the same coupon code
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
400
)
self
.
assertIn
(
"Coupon '{0}' already used."
.
format
(
self
.
coupon_code
),
resp
.
content
)
@patch
(
'shoppingcart.views.log.debug'
)
def
test_non_existing_coupon_redemption_on_removing_item
(
self
,
debug_log
):
reg_item
=
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
reg_item
.
id
})
debug_log
.
assert_called_with
(
'Coupon redemption does not exist for order item id={0}.'
.
format
(
reg_item
.
id
))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
0
)
@patch
(
'shoppingcart.views.log.info'
)
def
test_existing_coupon_redemption_on_removing_item
(
self
,
info_log
):
self
.
add_coupon
(
self
.
course_key
,
True
)
reg_item
=
self
.
add_course_to_user_cart
()
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
reg_item
.
id
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
0
)
info_log
.
assert_called_with
(
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
self
.
coupon_code
,
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_coupon_discount_for_multiple_courses_in_cart
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
self
.
add_coupon
(
self
.
course_key
,
True
)
cert_item
=
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
# unit_cost should be updated for that particular course for which coupon code is registered
items
=
self
.
cart
.
orderitem_set
.
all
()
.
select_subclasses
()
for
item
in
items
:
if
item
.
id
==
reg_item
.
id
:
self
.
assertEquals
(
item
.
unit_cost
,
self
.
get_discount
())
elif
item
.
id
==
cert_item
.
id
:
self
.
assertEquals
(
item
.
list_price
,
None
)
# Delete the discounted item, corresponding coupon redemption should be removed for that particular discounted item
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
reg_item
.
id
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
1
)
info_log
.
assert_called_with
(
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
self
.
coupon_code
,
self
.
user
,
reg_item
.
id
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_delete_certificate_item
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
cert_item
=
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
# Delete the discounted item, corresponding coupon redemption should be removed for that particular discounted item
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.remove_item'
,
args
=
[]),
{
'id'
:
cert_item
.
id
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
1
)
info_log
.
assert_called_with
(
'order item {0} removed for user {1}'
.
format
(
cert_item
.
id
,
self
.
user
))
@patch
(
'shoppingcart.views.log.info'
)
def
test_remove_coupon_redemption_on_clear_cart
(
self
,
info_log
):
reg_item
=
self
.
add_course_to_user_cart
()
CertificateItem
.
add_to_order
(
self
.
cart
,
self
.
verified_course_key
,
self
.
cost
,
'honor'
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
2
)
self
.
add_coupon
(
self
.
course_key
,
True
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.clear_cart'
,
args
=
[]))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
0
)
info_log
.
assert_called_with
(
'Coupon redemption entry removed for user {0} for order {1}'
.
format
(
self
.
user
,
reg_item
.
id
))
def
test_add_course_to_cart_already_registered
(
self
):
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course_key
)
self
.
login_user
()
...
...
@@ -188,6 +350,41 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
resp2
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.show_receipt'
,
args
=
[
1000
]))
self
.
assertEqual
(
resp2
.
status_code
,
404
)
def
test_total_amount_of_purchased_course
(
self
):
self
.
add_course_to_user_cart
()
self
.
assertEquals
(
self
.
cart
.
orderitem_set
.
count
(),
1
)
self
.
add_coupon
(
self
.
course_key
,
True
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
# Total amount of a particular course that is purchased by different users
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
self
.
course_key
)
self
.
assertEqual
(
total_amount
,
36
)
self
.
client
.
login
(
username
=
self
.
instructor
.
username
,
password
=
"test"
)
cart
=
Order
.
get_cart_for_user
(
self
.
instructor
)
PaidCourseRegistration
.
add_to_order
(
cart
,
self
.
course_key
)
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
total_amount
=
PaidCourseRegistration
.
get_total_amount_of_purchased_item
(
self
.
course_key
)
self
.
assertEqual
(
total_amount
,
76
)
@patch
(
'shoppingcart.views.render_to_response'
,
render_mock
)
def
test_show_receipt_success_with_valid_coupon_code
(
self
):
self
.
add_course_to_user_cart
()
self
.
add_coupon
(
self
.
course_key
,
True
)
resp
=
self
.
client
.
post
(
reverse
(
'shoppingcart.views.use_coupon'
),
{
'coupon_code'
:
self
.
coupon_code
})
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
cart
.
purchase
(
first
=
'FirstNameTesting123'
,
street1
=
'StreetTesting123'
)
resp
=
self
.
client
.
get
(
reverse
(
'shoppingcart.views.show_receipt'
,
args
=
[
self
.
cart
.
id
]))
self
.
assertEqual
(
resp
.
status_code
,
200
)
self
.
assertIn
(
'FirstNameTesting123'
,
resp
.
content
)
self
.
assertIn
(
str
(
self
.
get_discount
()),
resp
.
content
)
@patch
(
'shoppingcart.views.render_to_response'
,
render_mock
)
def
test_show_receipt_success
(
self
):
reg_item
=
PaidCourseRegistration
.
add_to_order
(
self
.
cart
,
self
.
course_key
)
...
...
lms/djangoapps/shoppingcart/urls.py
View file @
665ccdb7
...
...
@@ -14,6 +14,7 @@ if settings.FEATURES['ENABLE_SHOPPING_CART']:
url
(
r'^clear/$'
,
'clear_cart'
),
url
(
r'^remove_item/$'
,
'remove_item'
),
url
(
r'^add/course/{}/$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'add_course_to_cart'
,
name
=
'add_course_to_cart'
),
url
(
r'^use_coupon/$'
,
'use_coupon'
),
)
if
settings
.
FEATURES
.
get
(
'ENABLE_PAYMENT_FAKE'
):
...
...
lms/djangoapps/shoppingcart/views.py
View file @
665ccdb7
...
...
@@ -14,9 +14,10 @@ from edxmako.shortcuts import render_to_response
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
from
shoppingcart.reports
import
RefundReport
,
ItemizedPurchaseReport
,
UniversityRevenueShareReport
,
CertificateStatusReport
from
student.models
import
CourseEnrollment
from
.exceptions
import
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
from
.models
import
Order
,
PaidCourseRegistration
,
OrderItem
from
.exceptions
import
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
ReportTypeDoesNotExistException
,
CouponAlreadyExistException
,
ItemDoesNotExistAgainstCouponException
from
.models
import
Order
,
PaidCourseRegistration
,
OrderItem
,
Coupon
,
CouponRedemption
from
.processors
import
process_postpay_callback
,
render_purchase_form_html
import
json
log
=
logging
.
getLogger
(
"shoppingcart"
)
...
...
@@ -69,6 +70,16 @@ def show_cart(request):
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
total_cost
=
cart
.
total_cost
cart_items
=
cart
.
orderitem_set
.
all
()
# add the request protocol, domain, and port to the cart object so that any specific
# CC_PROCESSOR implementation can construct callback URLs, if necessary
cart
.
context
=
{
'request_domain'
:
'{0}://{1}'
.
format
(
'https'
if
request
.
is_secure
()
else
'http'
,
request
.
get_host
()
)
}
form_html
=
render_purchase_form_html
(
cart
)
return
render_to_response
(
"shoppingcart/list.html"
,
{
'shoppingcart_items'
:
cart_items
,
...
...
@@ -81,6 +92,11 @@ def show_cart(request):
def
clear_cart
(
request
):
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
cart
.
clear
()
coupon_redemption
=
CouponRedemption
.
objects
.
filter
(
user
=
request
.
user
,
order
=
cart
.
id
)
if
coupon_redemption
:
coupon_redemption
.
delete
()
log
.
info
(
'Coupon redemption entry removed for user {0} for order {1}'
.
format
(
request
.
user
,
cart
.
id
))
return
HttpResponse
(
'Cleared'
)
...
...
@@ -90,12 +106,50 @@ def remove_item(request):
try
:
item
=
OrderItem
.
objects
.
get
(
id
=
item_id
,
status
=
'cart'
)
if
item
.
user
==
request
.
user
:
order_item_course_id
=
None
if
hasattr
(
item
,
'paidcourseregistration'
):
order_item_course_id
=
item
.
paidcourseregistration
.
course_id
item
.
delete
()
log
.
info
(
'order item {0} removed for user {1}'
.
format
(
item_id
,
request
.
user
))
try
:
coupon_redemption
=
CouponRedemption
.
objects
.
get
(
user
=
request
.
user
,
order
=
item
.
order_id
)
if
order_item_course_id
==
coupon_redemption
.
coupon
.
course_id
:
coupon_redemption
.
delete
()
log
.
info
(
'Coupon "{0}" redemption entry removed for user "{1}" for order item "{2}"'
.
format
(
coupon_redemption
.
coupon
.
code
,
request
.
user
,
item_id
))
except
CouponRedemption
.
DoesNotExist
:
log
.
debug
(
'Coupon redemption does not exist for order item id={0}.'
.
format
(
item_id
))
except
OrderItem
.
DoesNotExist
:
log
.
exception
(
'Cannot remove cart OrderItem id={0}. DoesNotExist or item is already purchased'
.
format
(
item_id
))
return
HttpResponse
(
'OK'
)
@login_required
def
use_coupon
(
request
):
"""
This method generate discount against valid coupon code and save its entry into coupon redemption table
"""
coupon_code
=
request
.
POST
[
"coupon_code"
]
try
:
coupon
=
Coupon
.
objects
.
get
(
code
=
coupon_code
)
except
Coupon
.
DoesNotExist
:
return
HttpResponseNotFound
(
_
(
"Discount does not exist against coupon '{0}'."
.
format
(
coupon_code
)))
if
coupon
.
is_active
:
try
:
cart
=
Order
.
get_cart_for_user
(
request
.
user
)
CouponRedemption
.
add_coupon_redemption
(
coupon
,
cart
)
except
CouponAlreadyExistException
:
return
HttpResponseBadRequest
(
_
(
"Coupon '{0}' already used."
.
format
(
coupon_code
)))
except
ItemDoesNotExistAgainstCouponException
:
return
HttpResponseNotFound
(
_
(
"Coupon '{0}' is not valid for any course in the shopping cart."
.
format
(
coupon_code
)))
response
=
HttpResponse
(
json
.
dumps
({
'response'
:
'success'
}),
content_type
=
"application/json"
)
return
response
else
:
return
HttpResponseBadRequest
(
_
(
"Coupon '{0}' is inactive."
.
format
(
coupon_code
)))
@csrf_exempt
@require_POST
def
postpay_callback
(
request
):
...
...
@@ -122,6 +176,7 @@ def show_receipt(request, ordernum):
Displays a receipt for a particular order.
404 if order is not yet purchased or request.user != order.user
"""
try
:
order
=
Order
.
objects
.
get
(
id
=
ordernum
)
except
Order
.
DoesNotExist
:
...
...
lms/static/sass/course/instructor/_instructor_2.scss
View file @
665ccdb7
...
...
@@ -597,7 +597,7 @@ section.instructor-dashboard-content-2 {
float
:
left
;
clear
:
both
;
margin-top
:
25px
;
.metrics-left
,
.metrics-left-header
{
position
:
relative
;
width
:
30%
;
...
...
@@ -611,7 +611,7 @@ section.instructor-dashboard-content-2 {
.metrics-section.metrics-left
{
height
:
640px
;
}
.metrics-right
,
.metrics-right-header
{
position
:
relative
;
width
:
65%
;
...
...
@@ -627,7 +627,7 @@ section.instructor-dashboard-content-2 {
.metrics-section.metrics-right
{
height
:
295px
;
}
svg
{
.stacked-bar
{
cursor
:
pointer
;
...
...
@@ -775,3 +775,267 @@ input[name="subject"] {
font-weight
:
bold
;
}
}
.ecommerce-wrapper
{
h2
{
height
:
26px
;
line-height
:
26px
;
span
{
float
:
right
;
font-size
:
16px
;
font-weight
:
bold
;
span
{
background
:
#ddd
;
padding
:
2px
9px
;
border-radius
:
2px
;
float
:
none
;
font-weight
:
400
;
}
}
}
span
.tip
{
padding
:
10px
15px
;
display
:
block
;
border-top
:
1px
solid
#ddd
;
border-bottom
:
1px
solid
#ddd
;
background
:
#f8f4ec
;
color
:
#3c3c3c
;
line-height
:
30px
;
.add
{
@include
button
(
simple
,
$blue
);
@extend
.button-reset
;
font-size
:
em
(
13
);
float
:
right
;
}
}
}
#e-commerce
{
.coupon-errors
{
background
:
#FFEEF5
;
color
:
#B72667
;
text-align
:
center
;
padding
:
10px
0px
;
font-family
:
"Open Sans"
,
Verdana
,
Geneva
,
sans-serif
,
sans-serif
;
font-size
:
15px
;
border-bottom
:
1px
solid
#B72667
;
margin-bottom
:
20px
;
display
:
none
;
}
.content
{
padding
:
0
!
important
;
}
.coupons-table
{
width
:
100%
;
tr
:nth-child
(
even
)
{
background-color
:
#f8f8f8
;
border-bottom
:
1px
solid
#f3f3f3
;
}
tr
.always-gray
{
background
:
#eee
!
important
;
border-top
:
2px
solid
#FFFFFF
;
}
tr
.always-white
{
background
:
#fff
!
important
;
td
{
padding
:
30px
0px
10px
;
}
}
.coupons-headings
{
height
:
40px
;
border-bottom
:
1px
solid
#BEBEBE
;
th
:nth-child
(
5
)
{
text-align
:
center
;
width
:
120px
;
}
th
:first-child
{
padding-left
:
20px
;
}
th
{
text-align
:
left
;
border-bottom
:
1px
solid
$border-color-1
;
&
.c_code
{
width
:
170px
;
}
&
.c_count
{
width
:
85px
;
}
&
.c_course_id
{
width
:
320px
;
word-wrap
:
break-word
;
}
&
.c_discount
{
width
:
90px
;
}
&
.c_action
{
width
:
89px
;
}
&
.c_dsc
{
width
:
260px
;
word-wrap
:
break-word
;
}
}
}
// in_active coupon rows style
.inactive_coupon
{
background
:
#FFF0F0
!
important
;
text-decoration
:
line-through
;
color
:
rgba
(
51
,
51
,
51
,
0
.2
);
border-bottom
:
1px
solid
#fff
;
td
{
a
{
color
:
rgba
(
51
,
51
,
51
,
0
.2
);
}
}
}
// coupon items style
.coupons-items
{
td
{
padding
:
10px
0px
;
position
:
relative
;
line-height
:
normal
;
span
.old-price
{
left
:
-75px
;
position
:
relative
;
text-decoration
:
line-through
;
color
:
red
;
font-size
:
12px
;
top
:
-1px
;
}
}
td
:nth-child
(
5
),
td
:first-child
{
padding-left
:
20px
;
}
td
:nth-child
(
2
)
{
line-height
:
22px
;
padding-right
:
0px
;
word-wrap
:
break-word
;
}
td
:nth-child
(
5
)
{
padding-left
:
0
;
text-align
:
center
;
}
td
{
a
.edit-right
{
margin-left
:
15px
;
}
}
}
}
// coupon edit and add modals
#add-coupon-modal
,
#edit-coupon-modal
{
.inner-wrapper
{
background
:
#fff
;
}
top
:
-95px
!
important
;
width
:
650px
;
margin-left
:
-325px
;
border-radius
:
2px
;
input
[
type
=
"submit"
]
#update_coupon_button
{
@include
button
(
simple
,
$blue
);
@extend
.button-reset
;
}
input
[
type
=
"submit"
]
#add_coupon_button
{
@include
button
(
simple
,
$blue
);
@extend
.button-reset
;
}
.modal-form-error
{
box-shadow
:
inset
0
-1px
2px
0
#f3d9db
;
-webkit-box-sizing
:
border-box
;
-moz-box-sizing
:
border-box
;
box-sizing
:
border-box
;
margin
:
20px
0
10px
0
!
important
;
padding
:
20px
;
border
:
none
;
border-bottom
:
3px
solid
#a0050e
;
background
:
#fbf2f3
;
}
ol
.list-input
{
li
{
width
:
278px
;
float
:
left
;
label
.required
:after
{
content
:
"*"
;
margin-left
:
5px
;
}
}
li
:nth-child
(
even
)
{
margin-left
:
30px
!
important
;
}
li
:nth-child
(
3
),
li
:nth-child
(
4
)
{
margin-left
:
0px
!
important
;
width
:
100%
;
}
li
:nth-child
(
3
)
{
margin-bottom
:
0px
!
important
;
textarea
{
min-height
:
100px
;
}
}
li
:last-child
{
margin-bottom
:
0px
!
important
;
}
}
#coupon-content
{
padding
:
20px
;
header
{
margin
:
0
;
padding
:
0
;
h2
{
font-size
:
24px
;
font-weight
:
100
;
color
:
#1580b0
;
text-align
:
left
;
}
}
.instructions
p
{
margin-bottom
:
5px
;
}
form
{
border-radius
:
0
;
box-shadow
:
none
;
margin
:
0
;
border
:
none
;
padding
:
0
;
.group-form
{
margin
:
0
;
padding-top
:
0
;
padding-bottom
:
20px
;
}
.list-input
{
margin
:
0
;
padding
:
0
;
list-style
:
none
;
}
.readonly
{
background-color
:
#eee
!
important
;
color
:
#aaa
;
}
.field
{
margin
:
0
0
20px
0
;
}
.field.required
label
{
font-weight
:
600
;
}
.field
label
{
-webkit-transition
:
color
0
.15s
ease-in-out
0s
;
-moz-transition
:
color
0
.15s
ease-in-out
0s
;
transition
:
color
0
.15s
ease-in-out
0s
;
margin
:
0
0
5px
0
;
color
:
#333
;
}
.field.text
input
{
background
:
#fff
;
margin-bottom
:
0
;
}
.field
input
{
width
:
100%
;
margin
:
0
;
padding
:
10px
15px
;
}
}
}
}
}
lms/static/sass/views/_shoppingcart.scss
View file @
665ccdb7
...
...
@@ -12,14 +12,20 @@
border
:
1px
solid
$red
;
}
.cart-errors
{
background
:
#FFEEF5
;
color
:
#B72667
;
text-align
:
center
;
padding
:
10px
0px
;
font-family
:
"Open Sans"
,
Verdana
,
Geneva
,
sans-serif
,
sans-serif
;
font-size
:
15px
;
border-bottom
:
1px
solid
#B72667
;
margin-bottom
:
20px
;
display
:
none
;
}
.cart-list
{
padding
:
30px
;
margin-top
:
40px
;
border-radius
:
3px
;
border
:
1px
solid
$border-color-1
;
background-color
:
$action-primary-fg
;
>
h2
{
font-size
:
1
.5em
;
color
:
$base-font-color
;
...
...
@@ -27,13 +33,42 @@
.cart-table
{
width
:
100%
;
tr
:nth-child
(
even
)
{
background-color
:
#f8f8f8
;
border-bottom
:
1px
solid
#f3f3f3
;
}
tr
.always-gray
{
background
:
#eee
!
important
;
border-top
:
2px
solid
#FFFFFF
;
}
tr
.always-white
{
background
:
#fff
!
important
;
td
{
padding
:
30px
0px
10px
;
}
}
tr
{
td
.cart-total
{
padding
:
10px
0
;
span
{
display
:
inline-block
;
margin-right
:
15px
;
margin-left
:
15px
;
font-weight
:
bold
;
}
}
}
.cart-headings
{
height
:
35px
;
border-bottom
:
1px
solid
#BEBEBE
;
th
:nth-child
(
5
),
th
:first-child
{
text-align
:
center
;
width
:
120px
;
}
th
{
text-align
:
left
;
padding-left
:
5px
;
border-bottom
:
1px
solid
$border-color-1
;
&
.qty
{
...
...
@@ -48,12 +83,35 @@
&
.cur
{
width
:
100px
;
}
&
.dsc
{
width
:
640px
;
padding-right
:
50px
;
}
}
}
.cart-items
{
td
{
padding
:
10px
25px
;
padding
:
10px
0px
;
position
:
relative
;
line-height
:
normal
;
span
.old-price
{
left
:
-75px
;
position
:
relative
;
text-decoration
:
line-through
;
color
:
red
;
font-size
:
12px
;
top
:
-1px
;
}
}
td
:nth-child
(
5
),
td
:first-child
{
text-align
:
center
;
}
td
:nth-child
(
2
)
{
line-height
:
22px
;
padding-right
:
50px
;
}
}
...
...
@@ -64,6 +122,7 @@
font-weight
:
bold
;
padding
:
10px
25px
;
}
}
}
}
...
...
@@ -80,11 +139,10 @@
.items-ordered
{
padding-top
:
50px
;
}
tr
{
}
th
{
text-align
:
left
;
padding
:
25px
0
15px
0
;
...
...
@@ -105,6 +163,9 @@
tr
.order-item
{
td
{
padding-bottom
:
10px
;
span
.old-price
{
text-decoration
:
line-through
!
important
;
}
}
}
}
...
...
lms/templates/instructor/instructor_dashboard_2/add_coupon_modal.html
0 → 100644
View file @
665ccdb7
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%
page
args=
"section_data"
/>
<section
id=
"add-coupon-modal"
class=
"modal"
role=
"dialog"
tabindex=
"-1"
aria-label=
"${_('Password Reset')}"
>
<div
class=
"inner-wrapper"
>
<button
class=
"close-modal"
>
<i
class=
"icon-remove"
></i>
<span
class=
"sr"
>
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close')}
</span>
</button>
<div
id=
"coupon-content"
>
<header>
<h2>
${_("Add Coupon")}
</h2>
</header>
<div
class=
"instructions"
>
<p>
${_("Please enter Coupon detail below")}
</p>
</div>
<form
id=
"add_coupon_form"
action=
"${section_data['ajax_add_coupon']}"
method=
"post"
data-remote=
"true"
>
<div
id=
"coupon_form_error"
class=
"modal-form-error"
></div>
<fieldset
class=
"group group-form group-form-requiredinformation"
>
<legend
class=
"is-hidden"
>
${_("Required Information")}
</legend>
<ol
class=
"list-input"
>
<li
class=
"field required text"
id=
"add-coupon-modal-field-code"
>
<label
for=
"coupon_code"
class=
"required"
>
${_("Code")}
</label>
<input
class=
""
id=
"coupon_code"
type=
"text"
name=
"code"
maxlength=
"16"
value=
""
placeholder=
"example: A123DS"
aria-required=
"true"
/>
</li>
<li
class=
"field required text"
id=
"add-coupon-modal-field-discount"
>
<label
for=
"coupon_discount"
class=
"required text"
>
${_("Percentage Discount")}
</label>
<input
class=
"field required"
id=
"coupon_discount"
type=
"text"
name=
"discount"
value=
""
maxlength=
"5"
aria-required=
"true"
/>
</li>
<li
class=
"field"
id=
"add-coupon-modal-field-description"
>
<label
for=
"coupon_description"
>
${_("Description")}
</label>
<textarea
class=
"field"
id=
"coupon_description"
type=
"text"
name=
"description"
value=
""
aria-describedby=
"pwd_reset_email-tip"
aria-required=
"true"
>
</textarea>
</li>
<li
class=
"field"
id=
"add-coupon-modal-field-course_id"
>
<label
for=
"coupon_course_id"
>
${_("Course ID")}
</label>
<input
class=
"field readonly"
id=
"coupon_course_id"
type=
"text"
name=
"course_id"
value=
"${section_data['course_id']}"
readonly
aria-required=
"true"
/>
</li>
</ol>
</fieldset>
<div
class=
"submit"
>
<input
name=
"submit"
type=
"submit"
id=
"add_coupon_button"
value=
"${_('Add Coupon')}"
/>
</div>
</form>
</div>
</div>
</section>
lms/templates/instructor/instructor_dashboard_2/e-commerce.html
0 → 100644
View file @
665ccdb7
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%
page
args=
"section_data"
/>
<
%
include
file=
"add_coupon_modal.html"
args=
"section_data=section_data"
/>
<
%
include
file=
"edit_coupon_modal.html"
args=
"section_data=section_data"
/>
<div
class=
"ecommerce-wrapper"
>
<h2>
${_("Coupons List")}
%if section_data['total_amount'] is not None:
<span>
${_("Total Amount: ")}
<span>
$${section_data['total_amount']}
</span></span>
%endif
</h2>
<h3
class=
"coupon-errors"
id=
"coupon-error"
></h3>
<span
class=
"tip"
>
${_("Coupons Information")}
<a
id=
"add_coupon_link"
href=
"#add-coupon-modal"
rel=
"leanModal"
class=
"add blue-button"
>
${_("+ Add Coupon")}
</a></span>
</div>
<div
class=
"wrapper-content wrapper"
>
<section
class=
"content"
>
%if len(section_data['coupons']):
<table
class=
"coupons-table"
>
<thead>
<tr
class=
"coupons-headings"
>
<th
class=
"c_code"
>
${_("Code")}
</th>
<th
class=
"c_dsc"
>
${_("Description")}
</th>
<th
class=
"c_course_id"
>
${_("Course_id")}
</th>
<th
class=
"c_discount"
>
${_("Discount(%)")}
</th>
<th
class=
"c_count"
>
${_("Count")}
</th>
<th
class=
"c_action"
>
${_("Actions")}
</th>
</tr>
</thead>
<tbody>
%for coupon in section_data['coupons']:
%if coupon.is_active == False:
<tr
class=
"coupons-items inactive_coupon"
>
%else:
<tr
class=
"coupons-items"
>
%endif
<td>
${coupon.code}
</td>
<td>
${coupon.description}
</td>
<td>
${coupon.course_id.to_deprecated_string()}
</td>
<td>
${coupon.percentage_discount}
</td>
<td>
${ coupon.couponredemption_set.all().count() }
</td>
<!--<td>${coupon.is_active}</td>-->
<td><a
data-item-id=
"${coupon.id}"
class=
'remove_coupon'
href=
'#'
>
[x]
</a><a
href=
"#edit-modal"
data-item-id=
"${coupon.id}"
class=
"edit-right"
>
Edit
</a></td>
</tr>
%endfor
</tbody>
</table>
<a
id=
"edit-modal-trigger"
href=
"#edit-coupon-modal"
rel=
"leanModal"
></a>
%endif
</section>
</div>
<script>
$
(
function
()
{
$
(
'a[rel*=leanModal]'
).
leanModal
();
$
.
each
(
$
(
"a.edit-right"
),
function
()
{
if
(
$
(
this
).
parent
().
parent
(
'tr'
).
hasClass
(
'inactive_coupon'
))
{
$
(
this
).
removeAttr
(
'href'
)
}
});
$
.
each
(
$
(
"a.remove_coupon"
),
function
()
{
if
(
$
(
this
).
parent
().
parent
(
'tr'
).
hasClass
(
'inactive_coupon'
))
{
$
(
this
).
removeAttr
(
'href'
)
}
});
$
(
'a.edit-right'
).
click
(
function
(
event
)
{
$
(
'#edit_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: none'
);
$
(
'#edit_coupon_form #coupon_form_error'
).
text
();
event
.
preventDefault
();
event
.
stopPropagation
();
var
coupon_id
=
$
(
this
).
data
(
'item-id'
);
$
(
'#coupon_id'
).
val
(
coupon_id
);
if
(
$
(
this
).
parent
().
parent
(
'tr'
).
hasClass
(
'inactive_coupon'
))
{
return
false
;
}
$
.
ajax
({
type
:
"POST"
,
data
:
{
id
:
coupon_id
},
url
:
"${section_data['ajax_get_coupon_info']}"
,
success
:
function
(
data
)
{
$
(
'#coupon-error'
).
val
(
''
);
$
(
'#coupon-error'
).
attr
(
'style'
,
'display: none'
);
$
(
'input#edit_coupon_code'
).
val
(
data
.
coupon_code
);
$
(
'input#edit_coupon_discount'
).
val
(
data
.
coupon_discount
);
$
(
'textarea#edit_coupon_description'
).
val
(
data
.
coupon_description
);
$
(
'input#edit_coupon_course_id'
).
val
(
data
.
coupon_course_id
);
$
(
'#edit-modal-trigger'
).
click
();
},
error
:
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
var
data
=
$
.
parseJSON
(
jqXHR
.
responseText
);
$
(
'#coupon-error'
).
html
(
data
.
message
).
show
();
}
});
});
$
(
'a.remove_coupon'
).
click
(
function
(
event
)
{
var
anchor
=
$
(
this
);
if
(
anchor
.
data
(
"disabled"
))
{
return
false
;
}
anchor
.
data
(
"disabled"
,
"disabled"
);
event
.
preventDefault
();
if
(
$
(
this
).
parent
().
parent
(
'tr'
).
hasClass
(
'inactive_coupon'
))
{
return
false
;
}
$
.
ajax
({
type
:
"POST"
,
data
:
{
id
:
$
(
this
).
data
(
'item-id'
)},
url
:
"${section_data['ajax_remove_coupon_url']}"
,
success
:
function
(
data
)
{
anchor
.
removeData
(
"disabled"
);
location
.
reload
(
true
);
},
error
:
function
(
jqXHR
,
textStatus
,
errorThrown
)
{
var
data
=
$
.
parseJSON
(
jqXHR
.
responseText
);
$
(
'#coupon-error'
).
html
(
data
.
message
).
show
();
anchor
.
removeData
(
"disabled"
);
}
});
});
$
(
'#edit_coupon_form'
).
submit
(
function
()
{
$
(
"#update_coupon_button"
).
attr
(
'disabled'
,
true
);
// Get the Code and Discount value and trim it
var
code
=
$
.
trim
(
$
(
'#edit_coupon_code'
).
val
());
var
coupon_discount
=
$
.
trim
(
$
(
'#edit_coupon_discount'
).
val
());
// Check if empty of not
if
(
code
===
''
)
{
$
(
'#edit_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#edit_coupon_form #coupon_form_error'
).
text
(
"${_('Please Enter the Coupon Code')}"
);
$
(
"#update_coupon_button"
).
removeAttr
(
'disabled'
);
return
false
;
}
if
(
coupon_discount
==
'0'
)
{
$
(
'#edit_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#edit_coupon_form #coupon_form_error'
).
text
(
"${_('Please Enter the Value Greater than 0')}"
);
$
(
"#update_coupon_button"
).
removeAttr
(
'disabled'
);
return
false
;
}
if
(
!
$
.
isNumeric
(
coupon_discount
))
{
$
(
'#edit_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#edit_coupon_form #coupon_form_error'
).
text
(
"${_('Please Enter the Coupon Discount Value Greater than 0')}"
);
$
(
"#update_coupon_button"
).
removeAttr
(
'disabled'
);
return
false
;
}
});
$
(
'#add_coupon_link'
).
click
(
function
()
{
reset_input_fields
();
});
$
(
'#add_coupon_form'
).
submit
(
function
()
{
$
(
"#add_coupon_button"
).
attr
(
'disabled'
,
true
);
// Get the Code and Discount value and trim it
var
code
=
$
.
trim
(
$
(
'#coupon_code'
).
val
());
var
coupon_discount
=
$
.
trim
(
$
(
'#coupon_discount'
).
val
());
// Check if empty of not
if
(
code
===
''
)
{
$
(
"#add_coupon_button"
).
removeAttr
(
'disabled'
);
$
(
'#add_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#add_coupon_form #coupon_form_error'
).
text
(
"${_('Please Enter the Coupon Code')}"
);
return
false
;
}
if
(
coupon_discount
==
'0'
)
{
$
(
'#add_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#add_coupon_form #coupon_form_error'
).
text
(
"${_('Please Enter the Coupon Discount Value Greater than 0')}"
);
$
(
"#add_coupon_button"
).
removeAttr
(
'disabled'
);
return
false
;
}
if
(
!
$
.
isNumeric
(
coupon_discount
))
{
$
(
"#add_coupon_button"
).
removeAttr
(
'disabled'
);
$
(
'#add_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#add_coupon_form #coupon_form_error'
).
text
(
"${_('Please Enter the Numeric value for Discount')}"
);
return
false
;
}
});
$
(
'#add_coupon_form'
).
on
(
'ajax:complete'
,
function
(
event
,
xhr
)
{
if
(
xhr
.
status
==
200
)
{
location
.
reload
(
true
);
}
else
{
$
(
"#add_coupon_button"
).
removeAttr
(
'disabled'
);
$
(
'#add_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#add_coupon_form #coupon_form_error'
).
text
(
xhr
.
responseText
);
}
});
$
(
'#edit_coupon_form'
).
on
(
'ajax:complete'
,
function
(
event
,
xhr
)
{
if
(
xhr
.
status
==
200
)
{
location
.
reload
(
true
);
}
else
{
$
(
"#update_coupon_button"
).
removeAttr
(
'disabled'
);
$
(
'#edit_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: block !important'
);
$
(
'#edit_coupon_form #coupon_form_error'
).
text
(
xhr
.
responseText
);
}
});
// removing close link's default behavior
$
(
'.close-modal'
).
click
(
function
(
e
)
{
$
(
"#update_coupon_button"
).
removeAttr
(
'disabled'
);
$
(
"#add_coupon_button"
).
removeAttr
(
'disabled'
);
reset_input_fields
();
e
.
preventDefault
();
});
var
onModalClose
=
function
()
{
$
(
"#add-coupon-modal"
).
attr
(
"aria-hidden"
,
"true"
);
$
(
".remove_coupon"
).
focus
();
$
(
"#edit-coupon-modal"
).
attr
(
"aria-hidden"
,
"true"
);
$
(
".edit-right"
).
focus
();
$
(
"#add_coupon_button"
).
removeAttr
(
'disabled'
);
$
(
"#update_coupon_button"
).
removeAttr
(
'disabled'
);
reset_input_fields
();
};
var
cycle_modal_tab
=
function
(
from_element_name
,
to_element_name
)
{
$
(
from_element_name
).
on
(
'keydown'
,
function
(
e
)
{
var
keyCode
=
e
.
keyCode
||
e
.
which
;
var
TAB_KEY
=
9
;
// 9 corresponds to the tab key
if
(
keyCode
===
TAB_KEY
)
{
e
.
preventDefault
();
$
(
to_element_name
).
focus
();
}
});
};
$
(
"#add-coupon-modal .close-modal"
).
click
(
onModalClose
);
$
(
"#edit-coupon-modal .close-modal"
).
click
(
onModalClose
);
$
(
"#add-coupon-modal .close-modal"
).
click
(
reset_input_fields
);
// Hitting the ESC key will exit the modal
$
(
"#add-coupon-modal, #edit-coupon-modal"
).
on
(
"keydown"
,
function
(
e
)
{
var
keyCode
=
e
.
keyCode
||
e
.
which
;
// 27 is the ESC key
if
(
keyCode
===
27
)
{
e
.
preventDefault
();
$
(
"#add-coupon-modal .close-modal"
).
click
();
$
(
"#edit-coupon-modal .close-modal"
).
click
();
}
});
});
var
reset_input_fields
=
function
()
{
$
(
'#coupon-error'
).
val
(
''
);
$
(
'#coupon-error'
).
attr
(
'style'
,
'display: none'
);
$
(
'#add_coupon_form #coupon_form_error'
).
attr
(
'style'
,
'display: none'
);
$
(
'#add_coupon_form #coupon_form_error'
).
text
();
$
(
'input#coupon_code'
).
val
(
''
);
$
(
'input#coupon_discount'
).
val
(
''
);
$
(
'textarea#coupon_description'
).
val
(
''
);
}
</script>
lms/templates/instructor/instructor_dashboard_2/edit_coupon_modal.html
0 → 100644
View file @
665ccdb7
<
%!
from
django
.
utils
.
translation
import
ugettext
as
_
%
>
<
%!
from
django
.
core
.
urlresolvers
import
reverse
%
>
<
%
page
args=
"section_data"
/>
<section
id=
"edit-coupon-modal"
class=
"modal"
role=
"dialog"
tabindex=
"-1"
aria-label=
"${_('Edit Coupon')}"
>
<div
class=
"inner-wrapper"
>
<button
class=
"close-modal"
>
<i
class=
"icon-remove"
></i>
<span
class=
"sr"
>
## Translators: this is a control to allow users to exit out of this modal interface (a menu or piece of UI that takes the full focus of the screen)
${_('Close')}
</span>
</button>
<div
id=
"coupon-content"
>
<header>
<h2>
${_("Update Coupon")}
</h2>
</header>
<div
class=
"instructions"
>
<p>
${_("Update Coupon Information")}
</p>
</div>
<form
id=
"edit_coupon_form"
action=
"${section_data['ajax_update_coupon']}"
method=
"post"
data-remote=
"true"
>
<div
id=
"coupon_form_error"
class=
"modal-form-error"
></div>
<fieldset
class=
"group group-form group-form-requiredinformation"
>
<legend
class=
"is-hidden"
>
${_("Required Information")}
</legend>
<ol
class=
"list-input"
>
<li
class=
"field required text"
id=
"edit-coupon-modal-field-code"
>
<label
for=
"edit_coupon_code"
class=
"required"
>
${_("Code")}
</label>
<input
class=
"field"
id=
"edit_coupon_code"
type=
"text"
name=
"code"
maxlength=
"16"
value=
""
placeholder=
"example: A123DS"
aria-required=
"true"
/>
</li>
<li
class=
"field required text"
id=
"edit-coupon-modal-field-discount"
>
<label
for=
"edit_coupon_discount"
class=
"required"
>
${_("Percentage Discount")}
</label>
<input
class=
"field"
id=
"edit_coupon_discount"
type=
"text"
name=
"discount"
value=
""
maxlength=
"5"
aria-required=
"true"
/>
</li>
<li
class=
"field"
id=
"edit-coupon-modal-field-description"
>
<label
for=
"edit_coupon_description"
>
${_("Description")}
</label>
<textarea
class=
"field"
id=
"edit_coupon_description"
type=
"text"
name=
"description"
value=
""
aria-required=
"true"
></textarea>
</li>
<li
class=
"field"
id=
"edit-coupon-modal-field-course_id"
>
<label
for=
"edit_coupon_course_id"
>
${_("Course ID")}
</label>
<input
class=
"field readonly"
id=
"edit_coupon_course_id"
type=
"text"
name=
"course_id"
value=
""
readonly
aria-required=
"true"
/>
</li>
</ol>
</fieldset>
<div
class=
"submit"
>
<input
type=
"hidden"
name=
"coupon_id"
id=
"coupon_id"
/>
<input
name=
"submit"
type=
"submit"
id=
"update_coupon_button"
value=
"${_('Update Coupon')}"
/>
</div>
</form>
</div>
</div>
</section>
lms/templates/shoppingcart/cybersource_form.html
View file @
665ccdb7
...
...
@@ -2,5 +2,6 @@
% for pk, pv in params.iteritems():
<input
type=
"hidden"
name=
"${pk}"
value=
"${pv}"
/>
% endfor
<input
type=
"submit"
value=
"Check Out"
/>
</form>
</form>
\ No newline at end of file
lms/templates/shoppingcart/list.html
View file @
665ccdb7
...
...
@@ -8,6 +8,7 @@
<section
class=
"container cart-list"
>
<h2>
${_("Your selected items:")}
</h2>
<h3
class=
"cart-errors"
id=
"cart-error"
>
Error goes here.
</h3>
% if shoppingcart_items:
<table
class=
"cart-table"
>
<thead>
...
...
@@ -24,24 +25,39 @@
<tr
class=
"cart-items"
>
<td>
${item.qty}
</td>
<td>
${item.line_desc}
</td>
<td>
${"{0:0.2f}".format(item.unit_cost)}
</td>
<td>
${"{0:0.2f}".format(item.unit_cost)}
% if item.list_price != None:
<span
class=
"old-price"
>
${"{0:0.2f}".format(item.list_price)}
</span>
% endif
</td>
<td>
${"{0:0.2f}".format(item.line_cost)}
</td>
<td>
${item.currency.upper()}
</td>
<td><a
data-item-id=
"${item.id}"
class=
'remove_line_item'
href=
'#'
>
[x]
</a></td>
</tr>
% endfor
<tr
class=
"cart-headings"
>
<td
colspan=
"4"
></td>
<th>
${_("Total Amount")}
</th>
</tr>
<tr
class=
"cart-totals"
>
<td
colspan=
"4"
></td>
<td
class=
"cart-total-cost"
>
${"{0:0.2f}".format(amount)}
</td>
<tr
class=
"always-gray"
>
<td
colspan=
"3"
></td>
<td
colspan=
"3"
valign=
"middle"
class=
"cart-total"
align=
"right"
>
<b>
${_("Total Amount")}:
<span>
${"{0:0.2f}".format(amount)}
</span>
</b>
</td>
</tr>
</tbody>
<tfoot>
<tr
class=
"always-white"
>
<td
colspan=
"2"
>
<input
type=
"text"
placeholder=
"Enter coupon code here"
name=
"coupon_code"
id=
"couponCode"
>
<input
type=
"button"
value=
"Use Coupon"
id=
"cart-coupon"
>
</td>
<td
colspan=
"4"
align=
"right"
>
${form_html}
</td>
</tr>
</tfoot>
</table>
<!-- <input id="back_input" type="submit" value="Return" /> -->
${form_html}
% else:
<p>
${_("You have selected no items for purchase.")}
</p>
% endif
...
...
@@ -60,9 +76,39 @@
});
});
$
(
'#cart-coupon'
).
click
(
function
(
event
){
event
.
preventDefault
();
var
post_url
=
"${reverse('shoppingcart.views.use_coupon')}"
;
$
.
post
(
post_url
,{
"coupon_code"
:
$
(
'#couponCode'
).
val
(),
beforeSend
:
function
(
xhr
,
options
){
if
(
$
(
'#couponCode'
).
val
()
==
""
)
{
showErrorMsgs
(
'Must contain a valid coupon code'
)
xhr
.
abort
();
}
}
}
)
.
success
(
function
(
data
)
{
location
.
reload
(
true
);
})
.
error
(
function
(
data
,
status
)
{
if
(
status
==
"parsererror"
){
location
.
reload
(
true
);
}
else
{
showErrorMsgs
(
data
.
responseText
)
}
})
});
$
(
'#back_input'
).
click
(
function
(){
history
.
back
();
});
function
showErrorMsgs
(
msg
){
$
(
".cart-errors"
).
css
(
'display'
,
'block'
);
$
(
"#cart-error"
).
html
(
msg
);
}
});
</script>
lms/templates/shoppingcart/receipt.html
View file @
665ccdb7
...
...
@@ -3,6 +3,7 @@
<
%!
from
django
.
conf
import
settings
%
>
<
%
inherit
file=
"../main.html"
/>
<
%
block
name=
"bodyclass"
>
purchase-receipt
</
%
block>
<
%
block
name=
"pagetitle"
>
${_("Register for [Course Name] | Receipt (Order")} ${order.id})
</
%
block>
...
...
@@ -37,16 +38,25 @@
<tr>
<th
class=
"qty"
>
${_("Qty")}
</th>
<th
class=
"desc"
>
${_("Description")}
</th>
<th
class=
"url"
>
${_("URL")}
</th>
<th
class=
"u-pr"
>
${_("Unit Price")}
</th>
<th
class=
"pri"
>
${_("Price")}
</th>
<th
class=
"curr"
>
${_("Currency")}
</th>
</tr>
% for item in order_items:
<
%
course_id =
reverse('info',
args=
[item.course_id.to_deprecated_string()])
%
>
<tr
class=
"order-item"
>
% if item.status == "purchased":
<td>
${item.qty}
</td>
<td>
${item.line_desc}
</td>
<td>
${"{0:0.2f}".format(item.unit_cost)}
</td>
<td><a
href=
"${course_id}"
class=
"enter-course"
>
${_('View Course')}
</a></td>
<td>
${"{0:0.2f}".format(item.unit_cost)}
% if item.list_price != None:
<span
class=
"old-price"
>
${"{0:0.2f}".format(item.list_price)}
</span>
% endif
</td>
<td>
${"{0:0.2f}".format(item.line_cost)}
</td>
<td>
${item.currency.upper()}
</td></tr>
% elif item.status == "refunded":
...
...
lms/urls.py
View file @
665ccdb7
...
...
@@ -283,6 +283,14 @@ if settings.COURSEWARE_ENABLED:
'instructor.views.instructor_dashboard.instructor_dashboard_2'
,
name
=
"instructor_dashboard"
),
url
(
r'^courses/{}/instructor/api/'
.
format
(
settings
.
COURSE_ID_PATTERN
),
include
(
'instructor.views.api_urls'
)),
url
(
r'^courses/{}/remove_coupon$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'instructor.views.coupons.remove_coupon'
,
name
=
"remove_coupon"
),
url
(
r'^courses/{}/add_coupon$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'instructor.views.coupons.add_coupon'
,
name
=
"add_coupon"
),
url
(
r'^courses/{}/update_coupon$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'instructor.views.coupons.update_coupon'
,
name
=
"update_coupon"
),
url
(
r'^courses/{}/get_coupon_info$'
.
format
(
settings
.
COURSE_ID_PATTERN
),
'instructor.views.coupons.get_coupon_info'
,
name
=
"get_coupon_info"
),
# see ENABLE_INSTRUCTOR_LEGACY_DASHBOARD section for legacy dash urls
...
...
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