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
8d60a8b4
Commit
8d60a8b4
authored
Oct 07, 2014
by
Will Daly
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #5482 from edx/will/cybersource-donations
CyberSource donations (back-end)
parents
324a1da6
26bcfe58
Show whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
503 additions
and
20 deletions
+503
-20
lms/djangoapps/shoppingcart/migrations/0018_auto__add_donation.py
+192
-0
lms/djangoapps/shoppingcart/models.py
+142
-2
lms/djangoapps/shoppingcart/tests/test_models.py
+96
-9
lms/djangoapps/shoppingcart/tests/test_views.py
+67
-7
lms/templates/shoppingcart/receipt.html
+6
-2
No files found.
lms/djangoapps/shoppingcart/migrations/0018_auto__add_donation.py
0 → 100644
View file @
8d60a8b4
# -*- 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 'Donation'
db
.
create_table
(
'shoppingcart_donation'
,
(
(
'orderitem_ptr'
,
self
.
gf
(
'django.db.models.fields.related.OneToOneField'
)(
to
=
orm
[
'shoppingcart.OrderItem'
],
unique
=
True
,
primary_key
=
True
)),
(
'donation_type'
,
self
.
gf
(
'django.db.models.fields.CharField'
)(
default
=
'general'
,
max_length
=
32
)),
(
'course_id'
,
self
.
gf
(
'xmodule_django.models.CourseKeyField'
)(
max_length
=
255
,
db_index
=
True
)),
))
db
.
send_create_signal
(
'shoppingcart'
,
[
'Donation'
])
def
backwards
(
self
,
orm
):
# Deleting model 'Donation'
db
.
delete_table
(
'shoppingcart_donation'
)
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, 10, 2, 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'
,
[],
{
'unique'
:
'True'
,
'max_length'
:
'32'
,
'db_index'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2014, 10, 2, 0, 0)'
}),
'created_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'created_by_user'"
,
'to'
:
"orm['auth.User']"
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'invoice'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Invoice']"
,
'null'
:
'True'
}),
'order'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'related_name'
:
"'purchase_order'"
,
'null'
:
'True'
,
'to'
:
"orm['shoppingcart.Order']"
})
},
'shoppingcart.donation'
:
{
'Meta'
:
{
'object_name'
:
'Donation'
,
'_ormbases'
:
[
'shoppingcart.OrderItem'
]},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'donation_type'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'general'"
,
'max_length'
:
'32'
}),
'orderitem_ptr'
:
(
'django.db.models.fields.related.OneToOneField'
,
[],
{
'to'
:
"orm['shoppingcart.OrderItem']"
,
'unique'
:
'True'
,
'primary_key'
:
'True'
})
},
'shoppingcart.invoice'
:
{
'Meta'
:
{
'object_name'
:
'Invoice'
},
'address_line_1'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'address_line_2'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'address_line_3'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'city'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'company_contact_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'company_contact_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'company_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'country'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'null'
:
'True'
}),
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'customer_reference_number'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'63'
,
'null'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'internal_reference'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'is_valid'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'recipient_email'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'recipient_name'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
}),
'state'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'255'
,
'null'
:
'True'
}),
'total_amount'
:
(
'django.db.models.fields.FloatField'
,
[],
{}),
'zip'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'15'
,
'null'
:
'True'
})
},
'shoppingcart.order'
:
{
'Meta'
:
{
'object_name'
:
'Order'
},
'bill_to_cardtype'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'32'
,
'blank'
:
'True'
}),
'bill_to_ccnum'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'8'
,
'blank'
:
'True'
}),
'bill_to_city'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_country'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_first'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_last'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'64'
,
'blank'
:
'True'
}),
'bill_to_postalcode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'16'
,
'blank'
:
'True'
}),
'bill_to_state'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'8'
,
'blank'
:
'True'
}),
'bill_to_street1'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'bill_to_street2'
:
(
'django.db.models.fields.CharField'
,
[],
{
'max_length'
:
'128'
,
'blank'
:
'True'
}),
'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'
})
},
'shoppingcart.registrationcoderedemption'
:
{
'Meta'
:
{
'object_name'
:
'RegistrationCodeRedemption'
},
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'order'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.Order']"
,
'null'
:
'True'
}),
'redeemed_at'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'default'
:
'datetime.datetime(2014, 10, 2, 0, 0)'
,
'null'
:
'True'
}),
'redeemed_by'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
}),
'registration_code'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['shoppingcart.CourseRegistrationCode']"
})
},
'student.courseenrollment'
:
{
'Meta'
:
{
'ordering'
:
"('user', 'course_id')"
,
'unique_together'
:
"(('user', 'course_id'),)"
,
'object_name'
:
'CourseEnrollment'
},
'course_id'
:
(
'xmodule_django.models.CourseKeyField'
,
[],
{
'max_length'
:
'255'
,
'db_index'
:
'True'
}),
'created'
:
(
'django.db.models.fields.DateTimeField'
,
[],
{
'auto_now_add'
:
'True'
,
'null'
:
'True'
,
'db_index'
:
'True'
,
'blank'
:
'True'
}),
'id'
:
(
'django.db.models.fields.AutoField'
,
[],
{
'primary_key'
:
'True'
}),
'is_active'
:
(
'django.db.models.fields.BooleanField'
,
[],
{
'default'
:
'True'
}),
'mode'
:
(
'django.db.models.fields.CharField'
,
[],
{
'default'
:
"'honor'"
,
'max_length'
:
'100'
}),
'user'
:
(
'django.db.models.fields.related.ForeignKey'
,
[],
{
'to'
:
"orm['auth.User']"
})
}
}
complete_apps
=
[
'shoppingcart'
]
\ No newline at end of file
lms/djangoapps/shoppingcart/models.py
View file @
8d60a8b4
...
...
@@ -30,9 +30,12 @@ from xmodule_django.models import CourseKeyField
from
verify_student.models
import
SoftwareSecurePhotoVerification
from
.exceptions
import
(
InvalidCartItem
,
PurchasedCallbackException
,
ItemAlreadyInCartException
,
from
.exceptions
import
(
InvalidCartItem
,
PurchasedCallbackException
,
ItemAlreadyInCartException
,
AlreadyEnrolledInCourseException
,
CourseDoesNotExistException
,
MultipleCouponsNotAllowedException
,
RegCodeAlreadyExistException
,
ItemDoesNotExistAgainstRegCodeException
)
MultipleCouponsNotAllowedException
,
RegCodeAlreadyExistException
,
ItemDoesNotExistAgainstRegCodeException
)
from
microsite_configuration
import
microsite
...
...
@@ -865,3 +868,140 @@ class CertificateItem(OrderItem):
mode
=
'verified'
,
status
=
'purchased'
,
unit_cost__gt
=
(
CourseMode
.
min_course_price_for_verified_for_currency
(
course_id
,
'usd'
))))
.
count
()
class
Donation
(
OrderItem
):
"""A donation made by a user.
Donations can be made for a specific course or to the organization as a whole.
Users can choose the donation amount.
"""
# Types of donations
DONATION_TYPES
=
(
(
"general"
,
"A general donation"
),
(
"course"
,
"A donation to a particular course"
)
)
# The type of donation
donation_type
=
models
.
CharField
(
max_length
=
32
,
default
=
"general"
,
choices
=
DONATION_TYPES
)
# If a donation is made for a specific course, then store the course ID here.
# If the donation is made to the organization as a whole,
# set this field to CourseKeyField.Empty
course_id
=
CourseKeyField
(
max_length
=
255
,
db_index
=
True
)
@classmethod
@transaction.commit_on_success
def
add_to_order
(
cls
,
order
,
donation_amount
,
course_id
=
None
,
currency
=
'usd'
):
"""Add a donation to an order.
Args:
order (Order): The order to add this donation to.
donation_amount (Decimal): The amount the user is donating.
Keyword Args:
course_id (CourseKey): If provided, associate this donation with a particular course.
currency (str): The currency used for the the donation.
Raises:
InvalidCartItem: The provided course ID is not valid.
Returns:
Donation
"""
# This will validate the currency but won't actually add the item to the order.
super
(
Donation
,
cls
)
.
add_to_order
(
order
,
currency
=
currency
)
# Create a line item description, including the name of the course
# if this is a per-course donation.
# This will raise an exception if the course can't be found.
description
=
cls
.
_line_item_description
(
course_id
=
course_id
)
params
=
{
"order"
:
order
,
"user"
:
order
.
user
,
"status"
:
order
.
status
,
"qty"
:
1
,
"unit_cost"
:
donation_amount
,
"currency"
:
currency
,
"line_desc"
:
description
}
if
course_id
is
not
None
:
params
[
"course_id"
]
=
course_id
params
[
"donation_type"
]
=
"course"
else
:
params
[
"donation_type"
]
=
"general"
return
cls
.
objects
.
create
(
**
params
)
def
purchased_callback
(
self
):
"""Donations do not need to be fulfilled, so this method does nothing."""
pass
def
generate_receipt_instructions
(
self
):
"""Provide information about tax-deductible donations in the receipt.
Returns:
tuple of (Donation, unicode)
"""
return
self
.
pk_with_subclass
,
set
([
self
.
_tax_deduction_msg
()])
@property
def
additional_instruction_text
(
self
):
"""Provide information about tax-deductible donations in the confirmation email.
Returns:
unicode
"""
return
self
.
_tax_deduction_msg
()
def
_tax_deduction_msg
(
self
):
"""Return the translated version of the tax deduction message.
Returns:
unicode
"""
return
_
(
u"This receipt was prepared to support charitable contributions for tax purposes. "
u"Gifts are tax deductible as permitted by law. "
u"We confirm that neither goods nor services were provided in exchange for this gift."
)
@classmethod
def
_line_item_description
(
self
,
course_id
=
None
):
"""Create a line-item description for the donation.
Includes the course display name if provided.
Keyword Arguments:
course_id (CourseKey)
Raises:
InvalidCartItem: The course ID is not valid.
Returns:
unicode
"""
# If a course ID is provided, include the display name of the course
# in the line item description.
if
course_id
is
not
None
:
course
=
modulestore
()
.
get_course
(
course_id
)
if
course
is
None
:
err
=
_
(
u"Could not find a course with the ID '{course_id}'"
)
.
format
(
course_id
=
course_id
)
raise
InvalidCartItem
(
err
)
return
_
(
u"Donation for {course}"
)
.
format
(
course
=
course
.
display_name
)
# The donation is for the organization as a whole, not a specific course
else
:
return
_
(
u"Donation"
)
lms/djangoapps/shoppingcart/tests/test_models.py
View file @
8d60a8b4
"""
Tests for the Shopping Cart Models
"""
from
decimal
import
Decimal
import
datetime
import
smtplib
from
boto.exception
import
BotoServerError
# this is a super-class of SESError and catches connection errors
from
mock
import
patch
,
MagicMock
import
pytz
from
django.core
import
mail
from
django.conf
import
settings
from
django.db
import
DatabaseError
from
django.test
import
TestCase
from
django.test.utils
import
override_settings
from
django.contrib.auth.models
import
AnonymousUser
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
(
ModuleStoreTestCase
,
mixed_store_config
)
from
xmodule.modulestore.tests.factories
import
CourseFactory
from
courseware.tests.tests
import
TEST_DATA_MONGO_MODULESTORE
from
shoppingcart.models
import
(
Order
,
OrderItem
,
CertificateItem
,
InvalidCartItem
,
PaidCourseRegistration
,
OrderItemSubclassPK
)
from
shoppingcart.models
import
(
Order
,
OrderItem
,
CertificateItem
,
InvalidCartItem
,
PaidCourseRegistration
,
Donation
,
OrderItemSubclassPK
)
from
student.tests.factories
import
UserFactory
from
student.models
import
CourseEnrollment
from
course_modes.models
import
CourseMode
from
shoppingcart.exceptions
import
PurchasedCallbackException
import
pytz
import
datetime
from
opaque_keys.edx.locations
import
SlashSeparatedCourseKey
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG
=
mixed_store_config
(
settings
.
COMMON_TEST_DATA_ROOT
,
{},
include_xml
=
False
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
OrderTest
(
ModuleStoreTestCase
):
def
setUp
(
self
):
self
.
user
=
UserFactory
.
create
()
...
...
@@ -286,7 +296,7 @@ class OrderItemTest(TestCase):
self
.
assertEquals
(
set
([]),
inst_set
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
PaidCourseRegistrationTest
(
ModuleStoreTestCase
):
def
setUp
(
self
):
self
.
user
=
UserFactory
.
create
()
...
...
@@ -383,7 +393,7 @@ class PaidCourseRegistrationTest(ModuleStoreTestCase):
self
.
assertTrue
(
PaidCourseRegistration
.
contained_in_order
(
cart
,
self
.
course_key
))
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
CertificateItemTest
(
ModuleStoreTestCase
):
"""
Tests for verifying specific CertificateItem functionality
...
...
@@ -547,3 +557,80 @@ class CertificateItemTest(ModuleStoreTestCase):
CourseEnrollment
.
enroll
(
self
.
user
,
self
.
course_key
,
'verified'
)
ret_val
=
CourseEnrollment
.
unenroll
(
self
.
user
,
self
.
course_key
)
self
.
assertFalse
(
ret_val
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
DonationTest
(
ModuleStoreTestCase
):
"""Tests for the donation order item type. """
COST
=
Decimal
(
'23.45'
)
def
setUp
(
self
):
"""Create a test user and order. """
super
(
DonationTest
,
self
)
.
setUp
()
self
.
user
=
UserFactory
.
create
()
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
def
test_donate_to_org
(
self
):
# No course ID provided, so this is a donation to the entire organization
donation
=
Donation
.
add_to_order
(
self
.
cart
,
self
.
COST
)
self
.
_assert_donation
(
donation
,
donation_type
=
"general"
,
unit_cost
=
self
.
COST
,
line_desc
=
"Donation"
)
def
test_donate_to_course
(
self
):
# Create a test course
course
=
CourseFactory
.
create
(
display_name
=
"Test Course"
)
# Donate to the course
donation
=
Donation
.
add_to_order
(
self
.
cart
,
self
.
COST
,
course_id
=
course
.
id
)
self
.
_assert_donation
(
donation
,
donation_type
=
"course"
,
course_id
=
course
.
id
,
unit_cost
=
self
.
COST
,
line_desc
=
u"Donation for Test Course"
)
def
test_donate_no_such_course
(
self
):
fake_course_id
=
SlashSeparatedCourseKey
(
"edx"
,
"fake"
,
"course"
)
with
self
.
assertRaises
(
InvalidCartItem
):
Donation
.
add_to_order
(
self
.
cart
,
self
.
COST
,
course_id
=
fake_course_id
)
def
test_confirmation_email
(
self
):
# Pay for a donation
Donation
.
add_to_order
(
self
.
cart
,
self
.
COST
)
self
.
cart
.
start_purchase
()
self
.
cart
.
purchase
()
# Check that the tax-deduction information appears in the confirmation email
self
.
assertEqual
(
len
(
mail
.
outbox
),
1
)
email
=
mail
.
outbox
[
0
]
self
.
assertEquals
(
'Order Payment Confirmation'
,
email
.
subject
)
self
.
assertIn
(
"tax deductible"
,
email
.
body
)
def
_assert_donation
(
self
,
donation
,
donation_type
=
None
,
course_id
=
None
,
unit_cost
=
None
,
line_desc
=
None
):
"""Verify the donation fields and that the donation can be purchased. """
self
.
assertEqual
(
donation
.
order
,
self
.
cart
)
self
.
assertEqual
(
donation
.
user
,
self
.
user
)
self
.
assertEqual
(
donation
.
donation_type
,
donation_type
)
self
.
assertEqual
(
donation
.
course_id
,
course_id
)
self
.
assertEqual
(
donation
.
qty
,
1
)
self
.
assertEqual
(
donation
.
unit_cost
,
unit_cost
)
self
.
assertEqual
(
donation
.
currency
,
"usd"
)
self
.
assertEqual
(
donation
.
line_desc
,
line_desc
)
# Verify that the donation is in the cart
self
.
assertTrue
(
self
.
cart
.
has_items
(
item_type
=
Donation
))
self
.
assertEqual
(
self
.
cart
.
total_cost
,
unit_cost
)
# Purchase the item
self
.
cart
.
start_purchase
()
self
.
cart
.
purchase
()
# Verify that the donation is marked as purchased
donation
=
Donation
.
objects
.
get
(
pk
=
donation
.
id
)
self
.
assertEqual
(
donation
.
status
,
"purchased"
)
lms/djangoapps/shoppingcart/tests/test_views.py
View file @
8d60a8b4
...
...
@@ -18,11 +18,16 @@ from pytz import UTC
from
freezegun
import
freeze_time
from
datetime
import
datetime
,
timedelta
from
xmodule.modulestore.tests.django_utils
import
ModuleStoreTestCase
from
xmodule.modulestore.tests.django_utils
import
(
ModuleStoreTestCase
,
mixed_store_config
)
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
,
Coupon
,
CourseRegistrationCode
,
RegistrationCodeRedemption
from
shoppingcart.models
import
(
Order
,
CertificateItem
,
PaidCourseRegistration
,
Coupon
,
CourseRegistrationCode
,
RegistrationCodeRedemption
,
Donation
)
from
student.tests.factories
import
UserFactory
,
AdminFactory
from
courseware.tests.factories
import
InstructorFactory
from
student.models
import
CourseEnrollment
...
...
@@ -33,7 +38,7 @@ from shoppingcart.admin import SoftDeleteCouponAdmin
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
)
...
...
@@ -48,7 +53,12 @@ render_mock = Mock(side_effect=mock_render_to_response)
postpay_mock
=
Mock
()
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
# Since we don't need any XML course fixtures, use a modulestore configuration
# that disables the XML modulestore.
MODULESTORE_CONFIG
=
mixed_store_config
(
settings
.
COMMON_TEST_DATA_ROOT
,
{},
include_xml
=
False
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
ShoppingCartViewsTests
(
ModuleStoreTestCase
):
def
setUp
(
self
):
patcher
=
patch
(
'student.models.tracker'
)
...
...
@@ -739,7 +749,7 @@ class ShoppingCartViewsTests(ModuleStoreTestCase):
self
.
assertEqual
(
template
,
cert_item
.
single_item_receipt_template
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
RegistrationCodeRedemptionCourseEnrollment
(
ModuleStoreTestCase
):
"""
Test suite for RegistrationCodeRedemption Course Enrollments
...
...
@@ -857,7 +867,57 @@ class RegistrationCodeRedemptionCourseEnrollment(ModuleStoreTestCase):
self
.
assertTrue
(
"You've clicked a link for an enrollment code that has already been used."
in
response
.
content
)
@override_settings
(
MODULESTORE
=
TEST_DATA_MONGO_MODULESTORE
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
DonationReceiptViewTest
(
ModuleStoreTestCase
):
"""Tests for the receipt page when the user pays for a donation. """
COST
=
Decimal
(
'23.45'
)
PASSWORD
=
"password"
def
setUp
(
self
):
"""Create a test user and order. """
super
(
DonationReceiptViewTest
,
self
)
.
setUp
()
# Create and login a user
self
.
user
=
UserFactory
.
create
()
self
.
user
.
set_password
(
self
.
PASSWORD
)
self
.
user
.
save
()
result
=
self
.
client
.
login
(
username
=
self
.
user
.
username
,
password
=
self
.
PASSWORD
)
self
.
assertTrue
(
result
)
# Create an order for the user
self
.
cart
=
Order
.
get_cart_for_user
(
self
.
user
)
def
test_donation_for_org_receipt
(
self
):
# Purchase the donation
Donation
.
add_to_order
(
self
.
cart
,
self
.
COST
)
self
.
cart
.
start_purchase
()
self
.
cart
.
purchase
()
# Verify the receipt page
self
.
_assert_receipt_contains
(
"tax deductible"
)
def
test_donation_for_course_receipt
(
self
):
# Create a test course
self
.
course
=
CourseFactory
.
create
(
display_name
=
"Test Course"
)
# Purchase the donation for the course
Donation
.
add_to_order
(
self
.
cart
,
self
.
COST
,
course_id
=
self
.
course
.
id
)
self
.
cart
.
start_purchase
()
self
.
cart
.
purchase
()
# Verify the receipt page
self
.
_assert_receipt_contains
(
"tax deductible"
)
self
.
_assert_receipt_contains
(
self
.
course
.
display_name
)
def
_assert_receipt_contains
(
self
,
expected_text
):
"""Load the receipt page and verify that it contains the expected text."""
url
=
reverse
(
"shoppingcart.views.show_receipt"
,
kwargs
=
{
"ordernum"
:
self
.
cart
.
id
})
resp
=
self
.
client
.
get
(
url
)
self
.
assertContains
(
resp
,
expected_text
)
@override_settings
(
MODULESTORE
=
MODULESTORE_CONFIG
)
class
CSVReportViewsTest
(
ModuleStoreTestCase
):
"""
Test suite for CSV Purchase Reporting
...
...
lms/templates/shoppingcart/receipt.html
View file @
8d60a8b4
...
...
@@ -46,13 +46,17 @@
</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><a
href=
"${course_id | h}"
class=
"enter-course"
>
${_('View Course')}
</a></td>
<td>
% if item.course_id:
<
%
course_id =
reverse('info',
args=
[item.course_id.to_deprecated_string()])
%
>
<a
href=
"${course_id | h}"
class=
"enter-course"
>
${_('View Course')}
</a></td>
% endif
</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>
...
...
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