Commerce Framework Tutorial

This is designed as a framework, not an app, so you need to write your own models. Once you have done this, define a Summary class, which describes how your cart/order/invoice will operate.

Concept

To maximise flexibility, I’ve tried to identify the core features of carts, orders, invoices or any financial summary. These are:

  • items (a many-to-many collection of things to sum up, eg products, work sessions, discounts, vouchers)
  • extras (an global added cost or discount, eg tax, delivery, discount)
  • totals (a sum of some or all of the above, eg pretax total, total)

This is done to keep things as flexible as possible and divide a cart into parts which operate similarly. For example, a discount could be a single “extra” that is applied to a cart, or you could allow a user to apply several discounts to their cart (several discount “items”). The totals are generated by the framework, based on what you define should be included.

Basic shopping cart

To begin you need to create your own cart app with an appropriate model. Just as in the Django tutorial, create a new app and edit your models.py file, adding the following three models:

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=100)
    price = models.DecimalField(max_digits=10, decimal_places=2)

class Cart(models.Model):
    items        = models.ManyToManyField(Product, through="CartItem")

class CartItem(models.Model):
    product  = models.ForeignKey(Product)
    cart     = models.ForeignKey(Cart)
    quantity = models.PositiveIntegerField(default=1)

    def get_item_amount(self, instance):
        return self.product.price * self.quantity

    def __unicode__(self):
        return "%dx %s" % (self.quantity, self.product.name)

As you can see, this is a pretty basic shopping cart. Each product has a price and a name, and can be linked to a number of carts with a varying quantity. You are free to modify these models as you wish later, the commerce framework makes no assumptions as to how things are organised.

Let’s create some data for our cart:

>>> from myapp.models import Cart
>>> guitar = Product.objects.create(name="Guitar", price="329.42")
>>> saxophone = Product.objects.create(name="Saxophone", price="672.23")
>>> triangle = Product.objects.create(name="Triangle", price="4.48")
>>> my_cart = Cart.objects.create()
>>> CartItem.objects.create(product=guitar, cart=my_cart)
>>> CartItem.objects.create(product=triangle, cart=my_cart)
>>> CartItem.objects.create(product=saxophone, cart=my_cart, quantity=3)

With your models in hand, you can now create a Summary of your cart.

Cart Summary

This is where the commerce framework comes into the picture. Open up a new file, for example commerce.py and enter the following:

from rollyourown import commerce

class CartSummary(commerce.Summary):
    items    = commerce.Items(attribute="items", item_amount_from="get_item_amount")
    delivery = commerce.Extra()
    total    = commerce.Total()

    def get_amount_delivery(self, instance):
        return "10.00"

This summary will describe our cart. It defines a cart as having a number of items (items), an extra cost (delivery) and a total (total). The summary knows where to find the items (by default it looks for an attribute in the model with the same name, items). The amount of this extra cost (delivery) is found by default by looking for a method call get_amount_X where X is the name of the extra. Conveniently, we defined a method get_amount_delivery which provides this extra cost (fixed at 10.00).

Our total is then generated automatically, by adding everything together in the CartSummary. We can try this out using the shell.

Lets see what the summary can tell us about the cart we filled earlier:

>>> from myapp.commerce import CartSummary
>>> cart_summary = CartSummary(my_cart)
>>> cart_summary.total
Decimal('2360.59')
>>> print cart
1x Guitar      329.42
1x Triangle      4.48
3x Saxophone  2016.69

Delivery        10.00

       Total  2360.59

Great! But it’s not especially impressive; The framework merely added up the cost of the products and added a 10.00 delivery fee. Let’s make things a little more interesting.

New delivery pricing

Revising our CartSummary definition, let’s make the delivery calculation more sophisticated:

from rollyourown import commerce
from decimal import Decimal

class CartSummary(commerce.Summary):
    items    = commerce.Items(attribute="items", item_amount_from="get_item_amount")
    delivery  = commerce.Extra()
    subtotal  = commerce.Total('items')
    total     = commerce.Total()

    def get_amount_delivery(self, instance):
        " Delivery is 10% of the subtotal "
        return (self.subtotal / 10).quantize(Decimal("0.01"))

Now our delivery is calculated as 10% of the cost of the items. We’ve also added a new total (subtotal), which sums only the cost of the items. Let’s see what information our summary provides:

>>> from myapp.commerce import CartSummary
>>> cart_summary = CartSummary(my_cart)
>>> cart_summary.subtotal
Decimal('2350.59')
>>> cart_summary.total
Decimal('2585.65')
>>> cart_summary.delivery.amount
Decimal('235.06')
>>> print cart_summary
1x Guitar      329.42
1x Triangle      4.48
3x Saxophone  2016.69

Delivery       235.06

       Total  2360.59

Now things are getting interesting, we’ve changed our delivery pricing structure without touching our data model.

What else is possible?

This is just a simple demonstration of how everything fits together. The commerce framework has a number of other features, which you can read about in the Summary class syntax reference and the summary class usage. These include:

  • automatic and configurable locale-aware currency formatting for amounts
  • already-included values, which are removed from a total (eg TAX)
  • optional protection against negative values
  • sophisticated total calculation
  • denormalisation (calculated value caching using model instance)
  • utility functions for tax calculation, unique IDs etc