Commits

Federico Hlawaczek committed e6d1c74

Simple cart

Comments (0)

Files changed (11)

simple_cart/__init__.py

+import os
+
+APP_NAME = os.path.split(os.path.dirname(__file__))[1]
+# coding: utf-8
+
+from django.conf import settings
+from django.contrib.admin.util import unquote
+from django.contrib.admin import ModelAdmin, TabularInline
+from django.utils.translation import ugettext as _
+
+from models import SimpleCartItem
+
+class SimpleCartItemInline(TabularInline):
+    model = SimpleCartItem
+    extra = 0
+    can_delete = False
+    readonly_fields = ('quantity', 'product', 'notes', )
+
+class SimpleCartAdmin(ModelAdmin):
+    # Change list settings #############
+    actions = None
+    list_display = ('get_id_display', 'ts', 'customer_name', 'status', )
+    list_filter = ('status', )    
+    search_fields = ('id', 'customer_name', 'customer_email', )
+    list_editable = ()
+    # Change Form settings #############
+    fieldsets = (
+        (_(u"Seguimiento del pedido"), {
+            'fields': (
+                ('get_id_display', 'ts', ), 
+                'status', 
+                'internal_notes', 
+            ), 
+        }), 
+        (_(u"Datos del pedido"), {
+            'fields': (
+                'customer_name', 
+                'customer_email', 
+                'customer_phone', 
+                'customer_address_line1', 
+                'customer_address_line2', 
+                'customer_address_city', 
+                'customer_address_state', 
+                'customer_address_code', 
+                'message', 
+            )
+        }), 
+    )
+    readonly_fields = ('get_id_display', 'ts', 'customer_name', 'customer_email', 
+                       'customer_phone', 'customer_address_line1', 'customer_address_line2', 
+                       'customer_address_city', 'customer_address_state', 'customer_address_code', 
+                       'message', 
+                       )
+    inlines = [SimpleCartItemInline, ]
+# coding: utf8
+
+from django import forms
+
+from models import SimpleCart
+
+class SimpleCartForm(forms.ModelForm):
+    class Meta:
+        model = SimpleCart
+        exclude = ('status', 'internal_notes', )

simple_cart/helpers.py

+# coding: utf8
+
+class CartItem(object):
+    def __init__(self, itemid, product, quantity=1, notes=""):
+        self.itemid = itemid
+        self.product = product
+        self.quantity = quantity
+        self.notes = notes
+
+class Cart(object):
+    def __init__(self):
+        self._items = dict()
+        self._last_itemid = 0
+    
+    def add_item(self, product, quantity=1):
+        key = unicode(product.pk)
+        if key in self._items:
+            item = self._items[key]
+            item.quantity += quantity
+        else:
+            self._items[key] = CartItem(key, product, quantity)
+    
+    def remove_item(self, product):
+        key = unicode(product.pk)
+        try:
+            del self._items[key]
+        except KeyError:
+            pass
+    
+    def change_quantity(self, product, quantity):
+        if quantity > 0:
+            key = unicode(product.pk)
+            try:
+                item = self._items[key]
+            except KeyError:
+                pass
+            else:
+                item.quantity = quantity
+        else:
+            self.remove_item(product)
+    
+    def change_notes(self, product, notes):
+        key = unicode(product.pk)
+        try:
+            item = self._items[key]
+        except KeyError:
+            pass
+        else:
+            item.notes = notes
+    
+    def has_items(self):
+        return len(self._items) != 0
+    
+    def __iter__(self):
+        for key, item in self._items.items():
+            yield item
+    
+    def __unicode__(self):
+        if self._items:
+            return u"Cart: " + u"; ".join([u"{0} x {1}".format(item.quantity, item.product) for key, item in self._items.items()])
+        else:
+            return u"Cart: Empty"
+        

simple_cart/models.py

+# coding: utf8
+
+from django.conf import settings
+from django.db import models
+from django.core.mail import send_mail
+from django.template import loader, Context
+from django.utils.encoding import force_unicode
+from django.utils.translation import ugettext as _
+
+from simple_catalog.models import Product
+
+STATUS_CHOICES = (
+    ('new', _(u"Nueva")), 
+    ('prep', _(u"En preparación")), 
+    ('sent', _(u"Enviado")), 
+    ('comp', _(u"Completado")), 
+)
+# TODO: Make this configurable through settings
+
+class SimpleCartManager(models.Manager):
+    pass
+
+class SimpleCart(models.Model):
+    # Model data #######################
+    ts = models.DateTimeField(_("Fecha y hora"), auto_now=True, editable=False)
+    # Customer data
+    customer_name = models.CharField(_("Nombre"), max_length=64)
+    customer_email = models.EmailField(_("Email"))
+    customer_phone = models.CharField(_(u"Teléfono"), max_length=64, blank=True)
+    customer_address_line1 = models.CharField(_(u"Dirección"), max_length=64, blank=True)
+    customer_address_line2 = models.CharField(_(u"Dirección (2da línea)"), max_length=64, blank=True)
+    customer_address_city = models.CharField(_(u"Ciudad"), max_length=32, blank=True)
+    customer_address_state = models.CharField(_(u"Provincia"), max_length=32, blank=True)
+    customer_address_code = models.CharField(_(u"Código Postal"), max_length=16, blank=True)
+    message = models.TextField(_(u"Mensaje"), blank=True)
+    # Pipeline
+    status = models.CharField(_(u"Estado"), max_length=8, 
+                              choices=STATUS_CHOICES, default=STATUS_CHOICES[0][0], 
+                              )
+    internal_notes = models.TextField(_(u"Notas internas"), blank=True)
+    
+    class Meta:
+        ordering = ("-ts", )
+        verbose_name = _(u"Pedido")
+        verbose_name_plural = _(u"Pedidos")
+    
+    # Class methods ####################
+    def load_items(self, cart):
+        for item in cart:
+            self.items.create(product=item.product, quantity=item.quantity, notes=unicode(item.notes)[:64])
+    
+    def get_id_display(self):
+        return u"{0:0>8}".format(self.id)
+    get_id_display.short_description = _(u"Número de pedido")
+    
+    # Method overrides #################
+    def __unicode__(self):
+        return u"{0} - {1}".format(self.get_id_display(), self.customer_name)
+
+class SimpleCartItem(models.Model):
+    # Model data #######################
+    cart = models.ForeignKey(SimpleCart, related_name="items")
+    product = models.ForeignKey(Product, verbose_name=_(u"Producto"))
+    quantity = models.PositiveIntegerField(_(u"Cantidad"))
+    notes = models.CharField(_(u"Notas"), max_length=64, blank=True)
+    
+    class Meta:
+        verbose_name = _(u"Item del carrito")
+        verbose_name_plural = _(u"Items del carrito")
+    
+    # Method overrides #################
+    def __unicode__(self):
+        return u"{0} x {1}".format(self.quantity, self.product)

simple_cart/templates/simple_cart/tag/cart_change_submit.html

+<input name="{{ action_field_name }}" value="{{ action_value }}" type="hidden">
+<input type="submit" value="{{ submit_value }}">

simple_cart/templates/simple_cart/tag/item_notes_field.html

+<input name="{{ field_name }}" value="{{ value }}" size="16" type="text">

simple_cart/templates/simple_cart/tag/item_quantity_field.html

+<input name="{{ field_name }}" value="{{ value }}" size="2" type="text">

simple_cart/templatetags/__init__.py

Empty file added.

simple_cart/templatetags/simple_cart.py

+# coding: utf8
+
+from django import template
+from django.utils.safestring import mark_safe
+from django.utils.http import urlquote, urlencode
+from django.core.urlresolvers import reverse
+from django.utils.translation import ugettext as _
+
+from .. import APP_NAME
+from ..views import ACTION_ADD, ACTION_REMOVE, ACTION_VAR, ACTION_CHANGE, \
+     PRODUCT_ID_VAR, QUANTITY_VAR, \
+     get_item_quantity_field_name, get_item_notes_field_name
+
+register = template.Library()
+
+@register.filter
+def get_add_to_cart_url(product):
+    url = reverse("{0}:index".format(APP_NAME))
+    parameters = {ACTION_VAR: ACTION_ADD, PRODUCT_ID_VAR: product.pk, QUANTITY_VAR: 1}
+    return mark_safe("{0}?{1}".format(url, urlencode(parameters)))
+
+@register.filter
+def get_remove_from_cart_url(product):
+    url = reverse("{0}:index".format(APP_NAME))
+    parameters = {ACTION_VAR: ACTION_REMOVE, PRODUCT_ID_VAR: product.pk}
+    return mark_safe("{0}?{1}".format(url, urlencode(parameters)))
+
+def item_quantity_field(cartitem):
+    return {
+        'field_name': get_item_quantity_field_name(cartitem), 
+        'value': cartitem.quantity, 
+    }
+item_quantity_field = register.inclusion_tag("simple_cart/tag/item_quantity_field.html")(item_quantity_field)
+
+def item_notes_field(cartitem):
+    return {
+        'field_name': get_item_notes_field_name(cartitem), 
+        'value': cartitem.notes, 
+    }
+item_notes_field = register.inclusion_tag("simple_cart/tag/item_notes_field.html")(item_notes_field)
+
+def cart_change_submit(cart):
+    return {
+        'action_field_name': ACTION_VAR, 
+        'action_value': ACTION_CHANGE, 
+        'submit_value': _(u"Guardar cambios")
+    }
+cart_change_submit = register.inclusion_tag("simple_cart/tag/cart_change_submit.html")(cart_change_submit)
+# coding: utf8
+from urllib import urlencode
+
+from django import template
+from django import forms
+from django.conf import settings
+from django.core.exceptions import ObjectDoesNotExist, ValidationError
+from django.core.paginator import Paginator, InvalidPage, EmptyPage
+from django.shortcuts import render_to_response
+from django.utils.translation import ugettext as _
+from django.utils.safestring import mark_safe
+from django.utils.encoding import force_unicode
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect, Http404
+
+from simple_catalog.models import Product
+
+from . import APP_NAME
+from helpers import Cart
+from forms import SimpleCartForm
+
+PRODUCT_ID_VAR = 'p'
+QUANTITY_VAR = 'q'
+ACTION_VAR = 'a'
+ACTION_ADD = 'add'
+ACTION_REMOVE = 'del'
+ACTION_CHANGE = 'change'
+CART_SESSION_KEY = APP_NAME + "_CART"
+SENTCART_SESSION_KEY = APP_NAME + "_SENTCART"
+
+def get_item_quantity_field_name(cartitem):
+    return "{0}-{1}".format("quantity", cartitem.itemid)
+
+def get_item_notes_field_name(cartitem):
+    return "{0}-{1}".format("notes", cartitem.itemid)
+
+class SimpleCartViews(object):
+    def __init__(self, name=None, cart_template=None, checkout_template=None, thanks_template=None):
+        if name:
+            self.name = name
+        else:
+            self.name = APP_NAME
+        self.cart_template = cart_template or APP_NAME + "/index.html"
+        self.checkout_template = cart_template or APP_NAME + "/checkout.html"
+        self.thanks_template = thanks_template or APP_NAME + "/thanks.html"
+        
+    def index(self, request, extra_context=None):
+        # Get current user's cart
+        if not CART_SESSION_KEY in request.session:
+            request.session[CART_SESSION_KEY] = Cart()
+        cart = request.session[CART_SESSION_KEY]
+        # Do actions and refresh cart
+        if request.REQUEST.has_key(ACTION_VAR):
+            try:
+                p = Product.objects_published.get(pk=Product._meta.pk.to_python(request.REQUEST[PRODUCT_ID_VAR]))
+            except (KeyError, ValueError, ValidationError, ObjectDoesNotExist):
+                p = None
+            try:
+                q = int(request.REQUEST[QUANTITY_VAR])
+            except (KeyError, ValueError):
+                q = 1
+            if request.REQUEST[ACTION_VAR] == ACTION_ADD and p:
+                cart.add_item(p, q)
+            elif request.REQUEST[ACTION_VAR] == ACTION_REMOVE and p:
+                cart.remove_item(p)
+            elif request.REQUEST[ACTION_VAR] == ACTION_CHANGE:
+                for cartitem in cart:
+                    # Quantity
+                    q_key = get_item_quantity_field_name(cartitem)
+                    try:
+                        item_q = int(request.REQUEST[q_key])
+                    except (KeyError, ValueError):
+                        continue
+                    cart.change_quantity(cartitem.product, item_q)
+                    # Notes
+                    n_key = get_item_notes_field_name(cartitem)
+                    try:
+                        item_n = unicode(request.REQUEST[n_key])
+                    except (KeyError, ValueError):
+                        continue
+                    cart.change_notes(cartitem.product, item_n)
+                    
+            request.session[CART_SESSION_KEY] = cart
+            return HttpResponseRedirect("./")
+        
+        context = {
+            'cart': cart, 
+        }
+        context.update(extra_context or {})
+        context_instance = template.RequestContext(request, current_app=APP_NAME)
+        return render_to_response(self.cart_template, context, context_instance=context_instance)
+    
+    def checkout(self, request, extra_context=None):
+        # Get current user's cart
+        if not CART_SESSION_KEY in request.session:
+            request.session[CART_SESSION_KEY] = Cart()
+        cart = request.session[CART_SESSION_KEY]
+        if request.method == 'POST' and cart.has_items():
+            form = SimpleCartForm(request.POST)
+            if form.is_valid() :
+                form.save()
+                form.instance.load_items(cart)
+                request.session[SENTCART_SESSION_KEY] = form.instance
+                del request.session[CART_SESSION_KEY]
+                return HttpResponseRedirect(reverse("{0}:thanks".format(APP_NAME)))
+        else:
+            form = SimpleCartForm()
+        context = {
+            'cart': cart, 
+            'form': form, 
+        }
+        context.update(extra_context or {})
+        context_instance = template.RequestContext(request, current_app=APP_NAME)
+        return render_to_response(self.checkout_template, context, context_instance=context_instance)
+    
+    def thanks(self, request, extra_context=None):
+        if not SENTCART_SESSION_KEY in request.session:
+            raise Http404(_(u"No envió ningún pedido"))
+        context = {
+            'cart': request.session[SENTCART_SESSION_KEY], 
+        }
+        context.update(extra_context or {})
+        context_instance = template.RequestContext(request, current_app=APP_NAME)
+        return render_to_response(self.thanks_template, context, context_instance=context_instance)
+    
+    @property
+    def urls(self):
+        from django.conf.urls.defaults import patterns, url, include
+        urlpatterns = patterns('',
+            url(r'^$', self.index, name="index"), 
+            url(r'^checkout/$', self.checkout, name="checkout"), 
+            url(r'^thanks/$', self.thanks, name="thanks"), 
+            )
+        return urlpatterns, APP_NAME, self.name