Skip to content
Snippets Groups Projects
Commit ab1452cb authored by Jason Bau's avatar Jason Bau Committed by Diana Huang
Browse files

add Order model fields for receipt generation

parent 1f6bdca6
No related merge requests found
# -*- 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 field 'Order.nonce'
db.delete_column('shoppingcart_order', 'nonce')
# Adding field 'Order.bill_to_first'
db.add_column('shoppingcart_order', 'bill_to_first',
self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_last'
db.add_column('shoppingcart_order', 'bill_to_last',
self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_street1'
db.add_column('shoppingcart_order', 'bill_to_street1',
self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_street2'
db.add_column('shoppingcart_order', 'bill_to_street2',
self.gf('django.db.models.fields.CharField')(max_length=128, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_city'
db.add_column('shoppingcart_order', 'bill_to_city',
self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_postalcode'
db.add_column('shoppingcart_order', 'bill_to_postalcode',
self.gf('django.db.models.fields.CharField')(max_length=16, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_country'
db.add_column('shoppingcart_order', 'bill_to_country',
self.gf('django.db.models.fields.CharField')(max_length=64, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_ccnum'
db.add_column('shoppingcart_order', 'bill_to_ccnum',
self.gf('django.db.models.fields.CharField')(max_length=8, null=True, blank=True),
keep_default=False)
# Adding field 'Order.bill_to_cardtype'
db.add_column('shoppingcart_order', 'bill_to_cardtype',
self.gf('django.db.models.fields.CharField')(max_length=32, null=True, blank=True),
keep_default=False)
# Adding field 'Order.processor_reply_dump'
db.add_column('shoppingcart_order', 'processor_reply_dump',
self.gf('django.db.models.fields.TextField')(null=True, blank=True),
keep_default=False)
# Adding field 'OrderItem.currency'
db.add_column('shoppingcart_orderitem', 'currency',
self.gf('django.db.models.fields.CharField')(default='usd', max_length=8),
keep_default=False)
def backwards(self, orm):
# Adding field 'Order.nonce'
db.add_column('shoppingcart_order', 'nonce',
self.gf('django.db.models.fields.CharField')(default='defaultNonce', max_length=128),
keep_default=False)
# Deleting field 'Order.bill_to_first'
db.delete_column('shoppingcart_order', 'bill_to_first')
# Deleting field 'Order.bill_to_last'
db.delete_column('shoppingcart_order', 'bill_to_last')
# Deleting field 'Order.bill_to_street1'
db.delete_column('shoppingcart_order', 'bill_to_street1')
# Deleting field 'Order.bill_to_street2'
db.delete_column('shoppingcart_order', 'bill_to_street2')
# Deleting field 'Order.bill_to_city'
db.delete_column('shoppingcart_order', 'bill_to_city')
# Deleting field 'Order.bill_to_postalcode'
db.delete_column('shoppingcart_order', 'bill_to_postalcode')
# Deleting field 'Order.bill_to_country'
db.delete_column('shoppingcart_order', 'bill_to_country')
# Deleting field 'Order.bill_to_ccnum'
db.delete_column('shoppingcart_order', 'bill_to_ccnum')
# Deleting field 'Order.bill_to_cardtype'
db.delete_column('shoppingcart_order', 'bill_to_cardtype')
# Deleting field 'Order.processor_reply_dump'
db.delete_column('shoppingcart_order', 'processor_reply_dump')
# Deleting field 'OrderItem.currency'
db.delete_column('shoppingcart_orderitem', 'currency')
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.order': {
'Meta': {'object_name': 'Order'},
'bill_to_cardtype': ('django.db.models.fields.CharField', [], {'max_length': '32', 'null': 'True', 'blank': 'True'}),
'bill_to_ccnum': ('django.db.models.fields.CharField', [], {'max_length': '8', 'null': 'True', 'blank': 'True'}),
'bill_to_city': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'bill_to_country': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'bill_to_first': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'bill_to_last': ('django.db.models.fields.CharField', [], {'max_length': '64', 'null': 'True', 'blank': 'True'}),
'bill_to_postalcode': ('django.db.models.fields.CharField', [], {'max_length': '16', 'null': 'True', 'blank': 'True'}),
'bill_to_street1': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
'bill_to_street2': ('django.db.models.fields.CharField', [], {'max_length': '128', 'null': 'True', 'blank': 'True'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'processor_reply_dump': ('django.db.models.fields.TextField', [], {'null': 'True', 'blank': 'True'}),
'purchase_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'}),
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
'line_cost': ('django.db.models.fields.FloatField', [], {'default': '0.0'}),
'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'}),
'status': ('django.db.models.fields.CharField', [], {'default': "'cart'", 'max_length': '32'}),
'unit_cost': ('django.db.models.fields.FloatField', [], {'default': '0.0'}),
'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"})
},
'shoppingcart.paidcourseregistration': {
'Meta': {'object_name': 'PaidCourseRegistration', '_ormbases': ['shoppingcart.OrderItem']},
'course_id': ('django.db.models.fields.CharField', [], {'max_length': '128', 'db_index': 'True'}),
'orderitem_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['shoppingcart.OrderItem']", 'unique': 'True', 'primary_key': 'True'})
}
}
complete_apps = ['shoppingcart']
\ No newline at end of file
......@@ -19,20 +19,29 @@ class Order(models.Model):
"""
This is the model for an order. Before purchase, an Order and its related OrderItems are used
as the shopping cart.
THERE SHOULD ONLY EVER BE ZERO OR ONE ORDER WITH STATUS='cart' PER USER.
FOR ANY USER, THERE SHOULD ONLY EVER BE ZERO OR ONE ORDER WITH STATUS='cart'.
"""
user = models.ForeignKey(User, db_index=True)
status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES)
# Because we allow an external service to tell us when something is purchased, and our order numbers
# are their pk and therefore predicatble, let's protect against
# forged/replayed replies with a nonce.
nonce = models.CharField(max_length=128)
purchase_time = models.DateTimeField(null=True, blank=True)
# Now we store data needed to generate a reasonable receipt
# These fields only make sense after the purchase
bill_to_first = models.CharField(max_length=64, null=True, blank=True)
bill_to_last = models.CharField(max_length=64, null=True, blank=True)
bill_to_street1 = models.CharField(max_length=128, null=True, blank=True)
bill_to_street2 = models.CharField(max_length=128, null=True, blank=True)
bill_to_city = models.CharField(max_length=64, null=True, blank=True)
bill_to_postalcode = models.CharField(max_length=16, null=True, blank=True)
bill_to_country = models.CharField(max_length=64, null=True, blank=True)
bill_to_ccnum = models.CharField(max_length=8, null=True, blank=True) # last 4 digits
bill_to_cardtype = models.CharField(max_length=32, null=True, blank=True)
# a JSON dump of the CC processor response, for completeness
processor_reply_dump = models.TextField(null=True, blank=True)
@classmethod
def get_cart_for_user(cls, user):
"""
Use this to enforce the property that at most 1 order per user has status = 'cart'
Always use this to preserve the property that at most 1 order per user has status = 'cart'
"""
order, created = cls.objects.get_or_create(user=user, status='cart')
return order
......@@ -41,6 +50,15 @@ class Order(models.Model):
def total_cost(self):
return sum([i.line_cost for i in self.orderitem_set.all()])
@property
def currency(self):
"""Assumes that all cart items are in the same currency"""
items = self.orderitem_set.all()
if not items:
return 'usd'
else:
return items[0].currency
def purchase(self):
"""
Call to mark this order as purchased. Iterates through its OrderItems and calls
......@@ -72,6 +90,7 @@ class OrderItem(models.Model):
unit_cost = models.FloatField(default=0.0)
line_cost = models.FloatField(default=0.0) # qty * unit_cost
line_desc = models.CharField(default="Misc. Item", max_length=1024)
currency = models.CharField(default="usd", max_length=8) # lower case ISO currency codes
def add_to_order(self, *args, **kwargs):
"""
......@@ -118,7 +137,7 @@ class PaidCourseRegistration(OrderItem):
course_id = models.CharField(max_length=128, db_index=True)
@classmethod
def add_to_order(cls, order, course_id, cost):
def add_to_order(cls, order, course_id, cost, currency='usd'):
"""
A standardized way to create these objects, with sensible defaults filled in.
Will update the cost if called on an order that already carries the course.
......@@ -134,6 +153,7 @@ class PaidCourseRegistration(OrderItem):
item.unit_cost = cost
item.line_cost = cost
item.line_desc = "Registration for Course {0}".format(course_id)
item.currency = currency
item.save()
return item
......
......@@ -50,7 +50,7 @@ def show_cart(request):
params = OrderedDict()
params['comment'] = 'Stanford OpenEdX Purchase'
params['amount'] = amount
params['currency'] = 'usd'
params['currency'] = cart.currency
params['orderPage_transactionType'] = 'sale'
params['orderNumber'] = "{0:d}".format(cart.id)
params['billTo_email'] = request.user.email
......
......@@ -10,12 +10,13 @@
% if shoppingcart_items:
<table>
<thead>
<tr><td>Qty</td><td>Description</td><td>Unit Price</td><td>Price</td></tr>
<tr><td>Qty</td><td>Description</td><td>Unit Price</td><td>Price</td><td>Currency</td></tr>
</thead>
<tbody>
% for item in shoppingcart_items:
<tr><td>${item.qty}</td><td>${item.line_desc}</td>
<td>${"{0:0.2f}".format(item.unit_cost)}</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><td></td><td></td><td></td><td>Total Amount</td></tr>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment