Commit 9e560280 by Jason Bau Committed by Diana Huang

added shopping cart list template, embedded form

parent b7d73933
import logging
from django.contrib.auth.models import User
from student.views import course_from_id
from student.models import CourseEnrollmentAllowed, CourseEnrollment
from statsd import statsd
log = logging.getLogger("shoppingcart")
class InventoryItem(object):
"""
This is the abstract interface for inventory items.
Inventory items are things that fill up the shopping cart.
Each implementation of InventoryItem should have purchased_callback as
a method and data attributes as defined in __init__ below
"""
def __init__(self):
# Set up default data attribute values
self.qty = 1
self.unit_cost = 0 # in dollars
self.line_cost = 0 # qty * unit_cost
self.line_desc = "Misc Item"
def purchased_callback(self, user_id):
"""
This is called on each inventory item in the shopping cart when the
purchase goes through. The parameter provided is the id of the user who
made the purchase.
"""
raise NotImplementedError
class PaidCourseRegistration(InventoryItem):
"""
This is an inventory item for paying for a course registration
"""
def __init__(self, course_id, unit_cost):
course = course_from_id(course_id) # actually fetch the course to make sure it exists, use this to
# throw errors if it doesn't
self.qty = 1
self.unit_cost = unit_cost
self.line_cost = unit_cost
self.course_id = course_id
self.line_desc = "Registration for Course {0}".format(course_id)
def purchased_callback(self, user_id):
"""
When purchased, this should enroll the user in the course. We are assuming that
course settings for enrollment date are configured such that only if the (user.email, course_id) pair is found in
CourseEnrollmentAllowed will the user be allowed to enroll. Otherwise requiring payment
would in fact be quite silly since there's a clear back door.
"""
user = User.objects.get(id=user_id)
course = course_from_id(self.course_id) # actually fetch the course to make sure it exists, use this to
# throw errors if it doesn't
# use get_or_create here to gracefully handle case where the user is already enrolled in the course, for
# whatever reason.
# Don't really need to create CourseEnrollmentAllowed object, but doing it for bookkeeping and consistency
# with rest of codebase.
CourseEnrollmentAllowed.objects.get_or_create(email=user.email, course_id=self.course_id, auto_enroll=True)
CourseEnrollment.objects.get_or_create(user=user, course_id=self.course_id)
log.info("Enrolled {0} in paid course {1}, paid ${2}".format(user.email, self.course_id, self.line_cost))
org, course_num, run = self.course_id.split("/")
statsd.increment("shoppingcart.PaidCourseRegistration.purchased_callback.enrollment",
tags=["org:{0}".format(org),
"course:{0}".format(course_num),
"run:{0}".format(run)])
from django.db import models
# Create your models here.
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
Replace this with more appropriate tests for your application.
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""
Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
from django.conf.urls import patterns, include, url
urlpatterns = patterns('shoppingcart.views', # nopep8
url(r'^$','show_cart'),
url(r'^(?P<course_id>[^/]+/[^/]+/[^/]+)/$','test'),
url(r'^add/(?P<course_id>[^/]+/[^/]+/[^/]+)/$','add_course_to_cart'),
url(r'^clear/$','clear_cart'),
url(r'^remove_item/$', 'remove_item'),
)
\ No newline at end of file
import logging
import random
import time
import hmac
import binascii
from hashlib import sha1
from collections import OrderedDict
from django.http import HttpResponse
from django.contrib.auth.decorators import login_required
from mitxmako.shortcuts import render_to_response
from .inventory_types import *
log = logging.getLogger("shoppingcart")
def test(request, course_id):
item1 = PaidCourseRegistration(course_id, 200)
item1.purchased_callback(request.user.id)
return HttpResponse('OK')
@login_required
def add_course_to_cart(request, course_id):
cart = request.session.get('shopping_cart', [])
course_ids_in_cart = [i.course_id for i in cart if isinstance(i, PaidCourseRegistration)]
if course_id not in course_ids_in_cart:
# TODO: Catch 500 here for course that does not exist, period
item = PaidCourseRegistration(course_id, 200)
cart.append(item)
request.session['shopping_cart'] = cart
return HttpResponse('Added')
else:
return HttpResponse("Item exists, not adding")
@login_required
def show_cart(request):
cart = request.session.get('shopping_cart', [])
total_cost = "{0:0.2f}".format(sum([i.line_cost for i in cart]))
params = OrderedDict()
params['amount'] = total_cost
params['currency'] = 'usd'
params['orderPage_transactionType'] = 'sale'
params['orderNumber'] = "{0:d}".format(random.randint(1, 10000))
signed_param_dict = cybersource_sign(params)
return render_to_response("shoppingcart/list.html",
{'shoppingcart_items': cart,
'total_cost': total_cost,
'params': signed_param_dict,
})
@login_required
def clear_cart(request):
request.session['shopping_cart'] = []
return HttpResponse('Cleared')
@login_required
def remove_item(request):
# doing this with indexes to replicate the function that generated the list on the HTML page
item_idx = request.REQUEST.get('idx', 'blank')
try:
cart = request.session.get('shopping_cart', [])
cart.pop(int(item_idx))
request.session['shopping_cart'] = cart
except IndexError, ValueError:
log.exception('Cannot remove element at index {0} from cart'.format(item_idx))
return HttpResponse('OK')
def cybersource_sign(params):
"""
params needs to be an ordered dict, b/c cybersource documentation states that order is important.
Reverse engineered from PHP version provided by cybersource
"""
shared_secret = "ELIDED"
merchant_id = "ELIDED"
serial_number = "ELIDED"
orderPage_version = "7"
params['merchantID'] = merchant_id
params['orderPage_timestamp'] = int(time.time()*1000)
params['orderPage_version'] = orderPage_version
params['orderPage_serialNumber'] = serial_number
fields = ",".join(params.keys())
values = ",".join(["{0}={1}".format(i,params[i]) for i in params.keys()])
fields_hash_obj = hmac.new(shared_secret, fields, sha1)
fields_sig = binascii.b2a_base64(fields_hash_obj.digest())[:-1] # last character is a '\n', which we don't want
values += ",signedFieldsPublicSignature=" + fields_sig
values_hash_obj = hmac.new(shared_secret, values, sha1)
params['orderPage_signaturePublic'] = binascii.b2a_base64(values_hash_obj.digest())[:-1]
params['orderPage_signedFields'] = fields
return params
\ No newline at end of file
<%! from django.utils.translation import ugettext as _ %>
<%! from django.core.urlresolvers import reverse %>
<%inherit file="../main.html" />
<%block name="title"><title>${_("Your Shopping Cart")}</title></%block>
<section class="container cart-list">
<table>
<thead>
<tr><td>Qty</td><td>Description</td><td>Unit Price</td><td>Price</td></tr>
</thead>
<tbody>
% for idx,item in enumerate(shoppingcart_items):
<tr><td>${item.qty}</td><td>${item.line_desc}</td><td>${item.unit_cost}</td><td>${item.line_cost}</td>
<td><a data-item-idx="${idx}" class='remove_line_item' href='#'>[x]</a></td></tr>
% endfor
<tr><td></td><td></td><td></td><td>Total Cost</td></tr>
<tr><td></td><td></td><td></td><td>${total_cost}</td></tr>
</tbody>
</table>
<form action="https://orderpagetest.ic3.com/hop/orderform.jsp" method="post">
% for pk, pv in params.iteritems():
<input type="hidden" name="${pk}" value="${pv}" />
% endfor
<input type="submit" value="Check Out" />
</form>
</section>
<script>
$(function() {
$('a.remove_line_item').click(function(event) {
event.preventDefault();
var post_url = "${reverse('shoppingcart.views.remove_item')}";
$.post(post_url, {idx:$(this).data('item-idx')})
.always(function(data){
location.reload(true);
});
});
});
</script>
......@@ -379,7 +379,7 @@ if settings.MITX_FEATURES.get('RESTRICT_ENROLL_BY_REG_METHOD'):
# Shopping cart
urlpatterns += (
url(r'^shoppingcarttest/(?P<course_id>[^/]+/[^/]+/[^/]+)/$','shoppingcart.views.test'),
url(r'^shoppingcart/', include('shoppingcart.urls')),
)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment