Move shopping cart from session into model/db
This commit is contained in:
@@ -386,7 +386,7 @@ def change_enrollment(request):
|
||||
CourseEnrollment.unenroll(user, course_id)
|
||||
|
||||
org, course_num, run = course_id.split("/")
|
||||
log.increment("common.student.unenrollment",
|
||||
statsd.increment("common.student.unenrollment",
|
||||
tags=["org:{0}".format(org),
|
||||
"course:{0}".format(course_num),
|
||||
"run:{0}".format(run)])
|
||||
|
||||
@@ -1,68 +0,0 @@
|
||||
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)])
|
||||
116
lms/djangoapps/shoppingcart/migrations/0001_initial.py
Normal file
116
lms/djangoapps/shoppingcart/migrations/0001_initial.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# -*- 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 'Order'
|
||||
db.create_table('shoppingcart_order', (
|
||||
('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
|
||||
('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])),
|
||||
('status', self.gf('django.db.models.fields.CharField')(default='cart', max_length=32)),
|
||||
('nonce', self.gf('django.db.models.fields.CharField')(max_length=128)),
|
||||
('purchase_time', self.gf('django.db.models.fields.DateTimeField')(null=True, blank=True)),
|
||||
))
|
||||
db.send_create_signal('shoppingcart', ['Order'])
|
||||
|
||||
# Adding model 'OrderItem'
|
||||
db.create_table('shoppingcart_orderitem', (
|
||||
('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'])),
|
||||
('status', self.gf('django.db.models.fields.CharField')(default='cart', max_length=32)),
|
||||
('qty', self.gf('django.db.models.fields.IntegerField')(default=1)),
|
||||
('unit_cost', self.gf('django.db.models.fields.FloatField')(default=0.0)),
|
||||
('line_cost', self.gf('django.db.models.fields.FloatField')(default=0.0)),
|
||||
('line_desc', self.gf('django.db.models.fields.CharField')(default='Misc. Item', max_length=1024)),
|
||||
))
|
||||
db.send_create_signal('shoppingcart', ['OrderItem'])
|
||||
|
||||
# Adding model 'PaidCourseRegistration'
|
||||
db.create_table('shoppingcart_paidcourseregistration', (
|
||||
('orderitem_ptr', self.gf('django.db.models.fields.related.OneToOneField')(to=orm['shoppingcart.OrderItem'], unique=True, primary_key=True)),
|
||||
('course_id', self.gf('django.db.models.fields.CharField')(max_length=128, db_index=True)),
|
||||
))
|
||||
db.send_create_signal('shoppingcart', ['PaidCourseRegistration'])
|
||||
|
||||
|
||||
def backwards(self, orm):
|
||||
# Deleting model 'Order'
|
||||
db.delete_table('shoppingcart_order')
|
||||
|
||||
# Deleting model 'OrderItem'
|
||||
db.delete_table('shoppingcart_orderitem')
|
||||
|
||||
# Deleting model 'PaidCourseRegistration'
|
||||
db.delete_table('shoppingcart_paidcourseregistration')
|
||||
|
||||
|
||||
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'},
|
||||
'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
|
||||
'nonce': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
|
||||
'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'},
|
||||
'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']
|
||||
0
lms/djangoapps/shoppingcart/migrations/__init__.py
Normal file
0
lms/djangoapps/shoppingcart/migrations/__init__.py
Normal file
@@ -1,3 +1,161 @@
|
||||
import pytz
|
||||
import logging
|
||||
from datetime import datetime
|
||||
from django.db import models
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
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")
|
||||
|
||||
# Create your models here.
|
||||
ORDER_STATUSES = (
|
||||
('cart', 'cart'),
|
||||
('purchased', 'purchased'),
|
||||
('refunded', 'refunded'), # Not used for now
|
||||
)
|
||||
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
|
||||
@classmethod
|
||||
def get_cart_for_user(cls, user):
|
||||
"""
|
||||
Use this to enforce 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
|
||||
|
||||
@property
|
||||
def total_cost(self):
|
||||
return sum([i.line_cost for i in self.orderitem_set.all()])
|
||||
|
||||
def purchase(self):
|
||||
"""
|
||||
Call to mark this order as purchased. Iterates through its OrderItems and calls
|
||||
their purchased_callback
|
||||
"""
|
||||
self.status = 'purchased'
|
||||
self.purchase_time = datetime.now(pytz.utc)
|
||||
self.save()
|
||||
for item in self.orderitem_set.all():
|
||||
item.status = 'purchased'
|
||||
item.purchased_callback()
|
||||
item.save()
|
||||
|
||||
|
||||
class OrderItem(models.Model):
|
||||
"""
|
||||
This is the basic interface for order items.
|
||||
Order items are line items that fill up the shopping carts and orders.
|
||||
|
||||
Each implementation of OrderItem should provide its own purchased_callback as
|
||||
a method.
|
||||
"""
|
||||
order = models.ForeignKey(Order, db_index=True)
|
||||
# this is denormalized, but convenient for SQL queries for reports, etc. user should always be = order.user
|
||||
user = models.ForeignKey(User, db_index=True)
|
||||
# this is denormalized, but convenient for SQL queries for reports, etc. status should always be = order.status
|
||||
status = models.CharField(max_length=32, default='cart', choices=ORDER_STATUSES)
|
||||
qty = models.IntegerField(default=1)
|
||||
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)
|
||||
|
||||
def add_to_order(self, *args, **kwargs):
|
||||
"""
|
||||
A suggested convenience function for subclasses.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def purchased_callback(self):
|
||||
"""
|
||||
This is called on each inventory item in the shopping cart when the
|
||||
purchase goes through.
|
||||
|
||||
NOTE: We want to provide facilities for doing something like
|
||||
for item in OrderItem.objects.filter(order_id=order_id):
|
||||
item.purchased_callback()
|
||||
|
||||
Unfortunately the QuerySet used determines the class to be OrderItem, and not its most specific
|
||||
subclasses. That means this parent class implementation of purchased_callback needs to act as
|
||||
a dispatcher to call the callback the proper subclasses, and as such it needs to know about all subclasses.
|
||||
So please add
|
||||
"""
|
||||
for classname, lc_classname in ORDER_ITEM_SUBTYPES:
|
||||
try:
|
||||
sub_instance = getattr(self,lc_classname)
|
||||
sub_instance.purchased_callback()
|
||||
except (ObjectDoesNotExist, AttributeError):
|
||||
log.exception('Cannot call purchase_callback on non-existent subclass attribute {0} of OrderItem'\
|
||||
.format(lc_classname))
|
||||
pass
|
||||
|
||||
# Each entry is a tuple of ('ModelName', 'lower_case_model_name')
|
||||
# See https://docs.djangoproject.com/en/1.4/topics/db/models/#multi-table-inheritance for
|
||||
# PLEASE KEEP THIS LIST UP_TO_DATE WITH THE SUBCLASSES OF OrderItem
|
||||
ORDER_ITEM_SUBTYPES = [
|
||||
('PaidCourseRegistration', 'paidcourseregistration')
|
||||
]
|
||||
|
||||
|
||||
|
||||
class PaidCourseRegistration(OrderItem):
|
||||
"""
|
||||
This is an inventory item for paying for a course registration
|
||||
"""
|
||||
course_id = models.CharField(max_length=128, db_index=True)
|
||||
|
||||
@classmethod
|
||||
def add_to_order(cls, order, course_id, cost):
|
||||
"""
|
||||
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.
|
||||
|
||||
Returns the order item
|
||||
"""
|
||||
# TODO: Possibly add checking for whether student is already enrolled in course
|
||||
course = course_from_id(course_id) # actually fetch the course to make sure it exists, use this to
|
||||
# throw errors if it doesn't
|
||||
item, created = cls.objects.get_or_create(order=order, user=order.user, course_id=course_id)
|
||||
item.status = order.status
|
||||
item.qty = 1
|
||||
item.unit_cost = cost
|
||||
item.line_cost = cost
|
||||
item.line_desc = "Registration for Course {0}".format(course_id)
|
||||
item.save()
|
||||
return item
|
||||
|
||||
def purchased_callback(self):
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
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=self.user.email, course_id=self.course_id, auto_enroll=True)
|
||||
CourseEnrollment.objects.get_or_create(user=self.user, course_id=self.course_id)
|
||||
|
||||
log.info("Enrolled {0} in paid course {1}, paid ${2}".format(self.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)])
|
||||
|
||||
@@ -3,7 +3,8 @@ 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'^add/course/(?P<course_id>[^/]+/[^/]+/[^/]+)/$','add_course_to_cart'),
|
||||
url(r'^clear/$','clear_cart'),
|
||||
url(r'^remove_item/$', 'remove_item'),
|
||||
url(r'^purchased/$', 'purchased'),
|
||||
)
|
||||
@@ -7,10 +7,10 @@ from hashlib import sha1
|
||||
|
||||
from django.conf import settings
|
||||
from collections import OrderedDict
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponse, HttpResponseRedirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from mitxmako.shortcuts import render_to_response
|
||||
from .inventory_types import *
|
||||
from .models import *
|
||||
|
||||
log = logging.getLogger("shoppingcart")
|
||||
|
||||
@@ -20,50 +20,62 @@ def test(request, course_id):
|
||||
item1.purchased_callback(request.user.id)
|
||||
return HttpResponse('OK')
|
||||
|
||||
@login_required
|
||||
def purchased(request):
|
||||
#verify() -- signatures, total cost match up, etc. Need error handling code (
|
||||
# If verify fails probaly need to display a contact email/number)
|
||||
cart = Order.get_cart_for_user(request.user)
|
||||
cart.purchase()
|
||||
return HttpResponseRedirect('/')
|
||||
|
||||
@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")
|
||||
cart = Order.get_cart_for_user(request.user)
|
||||
# TODO: Catch 500 here for course that does not exist, period
|
||||
PaidCourseRegistration.add_to_order(cart, course_id, 200)
|
||||
return HttpResponse("Added")
|
||||
|
||||
@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]))
|
||||
cart = Order.get_cart_for_user(request.user)
|
||||
total_cost = cart.total_cost
|
||||
cart_items = cart.orderitem_set.all()
|
||||
params = OrderedDict()
|
||||
params['amount'] = total_cost
|
||||
params['currency'] = 'usd'
|
||||
params['orderPage_transactionType'] = 'sale'
|
||||
params['orderNumber'] = "{0:d}".format(random.randint(1, 10000))
|
||||
params['orderNumber'] = "{0:d}".format(cart.id)
|
||||
params['billTo_email'] = request.user.email
|
||||
idx=1
|
||||
for item in cart_items:
|
||||
prefix = "item_{0:d}_".format(idx)
|
||||
params[prefix+'productSKU'] = "{0:d}".format(item.id)
|
||||
params[prefix+'quantity'] = item.qty
|
||||
params[prefix+'productName'] = item.line_desc
|
||||
params[prefix+'unitPrice'] = item.unit_cost
|
||||
params[prefix+'taxAmount'] = "0.00"
|
||||
signed_param_dict = cybersource_sign(params)
|
||||
return render_to_response("shoppingcart/list.html",
|
||||
{'shoppingcart_items': cart,
|
||||
{'shoppingcart_items': cart_items,
|
||||
'total_cost': total_cost,
|
||||
'params': signed_param_dict,
|
||||
})
|
||||
|
||||
@login_required
|
||||
def clear_cart(request):
|
||||
request.session['shopping_cart'] = []
|
||||
cart = Order.get_cart_for_user(request.user)
|
||||
cart.orderitem_set.all().delete()
|
||||
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')
|
||||
item_id = request.REQUEST.get('id', '-1')
|
||||
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))
|
||||
item = OrderItem.objects.get(id=item_id, status='cart')
|
||||
if item.user == request.user:
|
||||
item.delete()
|
||||
except OrderItem.DoesNotExist:
|
||||
log.exception('Cannot remove cart OrderItem id={0}. DoesNotExist or item is already purchased'.format(item_id))
|
||||
return HttpResponse('OK')
|
||||
|
||||
|
||||
|
||||
@@ -778,6 +778,9 @@ INSTALLED_APPS = (
|
||||
'rest_framework',
|
||||
'user_api',
|
||||
|
||||
# shopping cart
|
||||
'shoppingcart',
|
||||
|
||||
# Notification preferences setting
|
||||
'notification_prefs',
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
<tr><td>Qty</td><td>Description</td><td>Unit Price</td><td>Price</td></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
% for idx,item in enumerate(shoppingcart_items):
|
||||
% for item in 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>
|
||||
<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 Cost</td></tr>
|
||||
<tr><td></td><td></td><td></td><td>${total_cost}</td></tr>
|
||||
@@ -41,7 +41,7 @@
|
||||
$('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')})
|
||||
$.post(post_url, {id:$(this).data('item-id')})
|
||||
.always(function(data){
|
||||
location.reload(true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user