Cookbook¶
Adapting to multiple item models¶
If you use multi-table inheritance in your item models, then you will likely
want that cart items were associated with instances of their respective child
models. This can be achieved by overriding the
process_object()
method of the BaseCart
class.
Let’s assume we have the following models:
# catalog/models.py
from django.db import models
class Item(models.Model):
name = models.CharField(max_length=40)
price = models.PositiveIntegerField()
class Book(Item):
author = models.CharField(max_length=40)
class Magazine(Item):
issue = models.CharField(max_length=40)
Instances of Item
can access their respective child model through
attributes book and magazine. The problem is, we don’t know in advance
which one to use. The easiest way to circumvent it is to use a try‑except block
to access each attribute one by one:
from django.core.exceptions import ObjectDoesNotExist
from easycart import BaseCart
CATEGORIES = ('book', 'magazine')
class Cart(BaseCart):
def get_queryset(self, pks):
return Item.objects.filter(pk__in=pks).select_related(*CATEGORIES)
def process_object(self, obj):
for category in CATEGORIES:
try:
return getattr(obj, category)
except ObjectDoesNotExist:
pass
Alternatively, just store the name of the right attribute in a separate field
on Item
:
class Item(models.Model):
name = models.CharField(max_length=40)
price = models.PositiveIntegerField()
category = models.CharField(max_length=50, editable=False)
def save(self, *args, **kwargs):
if not self.category:
self.category = self.__class__.__name__.lower()
super().save(*args, **kwargs)
In this case, your cart class may look something like this:
class Cart(BaseCart):
def get_queryset(self, pks):
return Item.objects.filter(pk__in=pks).select_related(*CATEGORIES)
def process_object(self, obj):
return getattr(obj, obj.category)
Attention
Whatever technique you choose, be sure to use select_related() to avoid redundant queries to the database.
Associating arbitrary data with cart items¶
You can associate arbitrary data with items by passing extra keyword arguments
to the cart’s method add()
.
As an example, we will save the date and time the item is added to the cart. Having a timestamp may be handy in quite a few scenarios. For example, many e-commerce sites have a widget displaying a list of items recently added to the cart.
To implement such functionality, create a cart class similar to the one below:
import time
from easycart import BaseCart
class Cart(BaseCart):
def add(self, pk, quantity=1):
super(Cart, self).add(pk, quantity, timestamp=time.time())
def list_items_by_timestamp(self):
return self.list_items(sort_key=lambda item: item.timestamp, reverse=True)
Now, in your templates, do something like:
{% for item in cart.list_items_by_timestamp|slice:":6" %}
{{ item.name }}
{{ item.price }}
{% endfor %}
Adding per item discounts and taxes¶
To change the way the individual item prices are calculated, you need to
override the total()
method of the BaseItem
class.
Assume we have the following models.py:
from django.db import models
class Item(models.Model):
price = models.DecimalField(decimal_places=2, max_digits=8)
# Suppose discounts and taxes are stored as percentages
discount = models.IntegerField(default=0)
tax = models.IntegerField(default=0)
In this case, your item class may look like this:
class CartItem(BaseItem):
@property
def total(self):
discount_mod = 1 - self.obj.discount/100
tax_mod = 1 + self.obj.tax/100
return self.price * discount_mod * tax_mod
class Cart(BaseCart):
# Point the cart to the new item class
item_class = CartItem
Limiting the maximum quantity allowed per item¶
You may want to limit the maximum quantity allowed per item, for example, to ensure that the user can’t put more items in his cart than you have in stock.
See the max_quantity
attribute of the
BaseCart
class.