Welcome, future e-commerce developer! Have you ever wondered how online stores keep track of the items you want to buy before you actually check out? That magical container is called a shopping cart. In this guide, we’re going to dive into Django, a powerful and popular web framework, and learn how to build a basic shopping cart for your e-commerce project.
Don’t worry if you’re new to Django or web development; we’ll explain everything in simple terms. By the end of this post, you’ll have a foundational understanding of how to manage a user’s shopping cart using Django’s powerful features, especially its session management system.
What is a Shopping Cart and Why Do We Need It?
Imagine walking into a physical store. You pick up items you like and put them in your physical shopping cart or basket. The online equivalent is exactly that: a temporary storage space for items a user intends to purchase.
A shopping cart is essential for any e-commerce website because it:
* Allows users to collect multiple products before making a purchase.
* Provides a clear overview of selected items, quantities, and often, the total price.
* Enhances the user experience by making the shopping process intuitive and flexible.
* Acts as an intermediary step before the checkout and payment process.
For our simple cart, we’ll focus on adding products, changing quantities, and removing items.
Prerequisites
Before we start coding, please make sure you have:
* Python installed: Django is built with Python.
* Django installed: You can install it using pip install django.
* A basic Django project set up: If you haven’t, you can create one with django-admin startproject my_ecommerce_project and then an app with python manage.py startapp shop.
* Basic familiarity with Django concepts: Like models, views, URLs, and templates. If these terms are new, don’t fret too much; we’ll briefly explain them as we go.
For this tutorial, let’s assume you have a Django project named my_ecommerce_project and an app named shop.
Step 1: Defining Our Product Model
First, we need something to sell! Let’s define a simple Product model in our shop/models.py file. A model in Django is like a blueprint for a table in your database. It defines the structure of the data your application will store.
from django.db import models
from django.urls import reverse # We'll use reverse for product URLs later
class Product(models.Model):
name = models.CharField(max_length=200, db_index=True)
slug = models.SlugField(max_length=200, db_index=True, unique=True)
image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
description = models.TextField(blank=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
available = models.BooleanField(default=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
class Meta:
ordering = ('name',) # Sort products by name by default
index_together = (('id', 'slug'),) # For efficient querying
def __str__(self):
return self.name
def get_absolute_url(self):
# This method is useful for linking to individual product pages
return reverse('shop:product_detail', args=[self.id, self.slug])
After defining your model, remember to:
1. Make migrations: python manage.py makemigrations shop
2. Apply migrations: python manage.py migrate
This will create the necessary table in your database for storing product information.
Step 2: Understanding Django Sessions for Our Cart
How do we keep track of what items a user has put in their cart as they browse different pages? This is where Django’s session framework comes in handy!
A session is a way for your web application to remember information about a specific user across multiple web requests. When a user visits your site, Django can create a unique session for them. This session can store small pieces of data, like a user’s login status or, in our case, the contents of their shopping cart. The data is typically stored on the server side, and the user’s browser only receives a small, unique session ID.
We’ll store our cart data as a dictionary within the session, where product IDs will be keys and their quantities will be values.
Step 3: Creating the Cart Logic (A Cart Class)
It’s good practice to encapsulate our cart’s functionality within a dedicated Python class. This keeps our code organized and reusable. Create a new file cart/cart.py (you might need to create a cart app first with python manage.py startapp cart and add it to INSTALLED_APPS in settings.py).
from decimal import Decimal
from django.conf import settings
from shop.models import Product # Import our Product model
class Cart(object):
def __init__(self, request):
"""
Initialize the cart.
"""
self.session = request.session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
# save an empty cart in session
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, product, quantity=1, override_quantity=False):
"""
Add a product to the cart or update its quantity.
"""
product_id = str(product.id) # Convert product ID to string as session keys are strings
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0,
'price': str(product.price)} # Store price as string
if override_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def save(self):
# mark the session as "modified" to make sure it gets saved
self.session.modified = True
def remove(self, product):
"""
Remove a product from the cart.
"""
product_id = str(product.id)
if product_id in self.cart:
del self.cart[product_id]
self.save()
def __iter__(self):
"""
Iterate over the items in the cart and get the products from the database.
"""
product_ids = self.cart.keys()
# get the product objects and add them to the cart
products = Product.objects.filter(id__in=product_ids) # Queryset for products
cart = self.cart.copy() # Make a copy to avoid modifying while iterating
for product in products:
cart[str(product.id)]['product'] = product
for item in cart.values():
item['price'] = Decimal(item['price'])
item['total_price'] = item['price'] * item['quantity']
yield item # yield makes this function a generator, returning one item at a time
def __len__(self):
"""
Count all items in the cart.
"""
return sum(item['quantity'] for item in self.cart.values())
def get_total_price(self):
"""
Calculate the total price of all items in the cart.
"""
return sum(Decimal(item['price']) * item['quantity'] for item in self.cart.values())
def clear(self):
"""
Remove cart from session.
"""
del self.session[settings.CART_SESSION_ID]
self.save()
In settings.py, add CART_SESSION_ID = 'cart' to define the key for your cart in the session.
Step 4: Cart Views
Now let’s create views to handle adding products to the cart, removing them, and displaying the cart. A view is a function or class that takes a web request and returns a web response, typically rendering a template.
from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST # Ensures only POST requests are processed
from shop.models import Product
from .cart import Cart # Import our Cart class
from .forms import CartAddProductForm # We'll create this form next
@require_POST
def cart_add(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
form = CartAddProductForm(request.POST) # Get data from the submitted form
if form.is_valid():
cd = form.cleaned_data # Cleaned data from the form
cart.add(product=product,
quantity=cd['quantity'],
override_quantity=cd['override'])
return redirect('cart:cart_detail')
@require_POST
def cart_remove(request, product_id):
cart = Cart(request)
product = get_object_or_404(Product, id=product_id)
cart.remove(product)
return redirect('cart:cart_detail')
def cart_detail(request):
cart = Cart(request)
for item in cart: # Iterate through cart items to prepare for display
item['update_quantity_form'] = CartAddProductForm(initial={
'quantity': item['quantity'],
'override': True # Set override to True for updating existing item quantities
})
return render(request, 'cart/detail.html', {'cart': cart})
Step 5: Cart Forms
For adding products to the cart, we’ll use a simple form. This form will allow users to specify the quantity and whether to add to the existing quantity or replace it. Create cart/forms.py.
from django import forms
PRODUCT_QUANTITY_CHOICES = [(i, str(i)) for i in range(1, 21)] # Choices from 1 to 20
class CartAddProductForm(forms.Form):
quantity = forms.TypedChoiceField(
choices=PRODUCT_QUANTITY_CHOICES,
coerce=int) # Ensures quantity is an integer
override = forms.BooleanField(required=False,
initial=False,
widget=forms.HiddenInput) # Hidden field for override option
Step 6: URL Patterns
We need to map our views to specific URLs. A URL pattern tells Django which view to call when a certain URL is requested. Create cart/urls.py and link it in your project’s urls.py.
First, create cart/urls.py:
from django.urls import path
from . import views
app_name = 'cart' # Namespace for our cart URLs
urlpatterns = [
path('', views.cart_detail, name='cart_detail'),
path('add/<int:product_id>/', views.cart_add, name='cart_add'),
path('remove/<int:product_id>/', views.cart_remove, name='cart_remove'),
]
Then, add this to your my_ecommerce_project/urls.py:
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('cart/', include('cart.urls', namespace='cart')), # Include cart URLs
path('', include('shop.urls', namespace='shop')), # Assuming you have shop URLs too
]
Don’t forget to create shop/urls.py for displaying products. For simplicity, here’s a basic one:
from django.urls import path
from . import views
app_name = 'shop'
urlpatterns = [
path('', views.product_list, name='product_list'),
path('<int:id>/<slug:slug>/', views.product_detail, name='product_detail'),
]
And a corresponding shop/views.py:
from django.shortcuts import render, get_object_or_404
from .models import Product
from cart.forms import CartAddProductForm # Import the cart form
def product_list(request):
products = Product.objects.filter(available=True)
return render(request, 'shop/product/list.html', {'products': products})
def product_detail(request, id, slug):
product = get_object_or_404(Product, id=id, slug=slug, available=True)
cart_product_form = CartAddProductForm() # Form to add product to cart
return render(request, 'shop/product/detail.html', {'product': product,
'cart_product_form': cart_product_form})
Step 7: Cart Templates
Finally, let’s create the HTML templates to display our products and the shopping cart. A template is an HTML file that can include special Django syntax to display dynamic content.
Create shop/product/list.html to display a list of products:
<!-- shop/product/list.html -->
{% extends 'base.html' %} {% load static %}
{% block title %}Products{% endblock %}
{% block content %}
<h1>Our Products</h1>
<div id="product-list">
{% for product in products %}
<div class="item">
<a href="{{ product.get_absolute_url }}">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static 'img/no_image.png' %}{% endif %}" alt="{{ product.name }}">
</a>
<a href="{{ product.get_absolute_url }}">{{ product.name }}</a><br>
${{ product.price }}
</div>
{% endfor %}
</div>
{% endblock %}
Create shop/product/detail.html for individual product pages, including an “Add to Cart” button:
<!-- shop/product/detail.html -->
{% extends 'base.html' %} {% load static %}
{% block title %}{{ product.name }}{% endblock %}
{% block content %}
<div class="product-detail">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static 'img/no_image.png' %}{% endif %}" alt="{{ product.name }}">
<h1>{{ product.name }}</h1>
<h2><a href="#"></a></h2>
<p class="price">${{ product.price }}</p>
<form action="{% url 'cart:cart_add' product.id %}" method="post">
{{ cart_product_form }}
{% csrf_token %}
<input type="submit" value="Add to cart">
</form>
{{ product.description|linebreaks }}
</div>
{% endblock %}
And finally, cart/detail.html to display the contents of the cart:
<!-- cart/detail.html -->
{% extends 'base.html' %}
{% load static %}
{% block title %}
Your shopping cart
{% endblock %}
{% block content %}
<h1>Your shopping cart</h1>
<table class="cart">
<thead>
<tr>
<th>Image</th>
<th>Product</th>
<th>Quantity</th>
<th>Remove</th>
<th>Unit price</th>
<th>Price</th>
</tr>
</thead>
<tbody>
{% for item in cart %}
{% with product=item.product %}
<tr>
<td>
<a href="{{ product.get_absolute_url }}">
<img src="{% if product.image %}{{ product.image.url }}{% else %}{% static 'img/no_image.png' %}{% endif %}" alt="{{ product.name }}">
</a>
</td>
<td>{{ product.name }}</td>
<td>
<form action="{% url 'cart:cart_add' product.id %}" method="post">
{{ item.update_quantity_form.quantity }}
{{ item.update_quantity_form.override }}
<input type="submit" value="Update">
{% csrf_token %}
</form>
</td>
<td>
<form action="{% url 'cart:cart_remove' product.id %}" method="post">
<input type="submit" value="Remove">
{% csrf_token %}
</form>
</td>
<td class="num">${{ item.price }}</td>
<td class="num">${{ item.total_price }}</td>
</tr>
{% endwith %}
{% endfor %}
<tr class="total">
<td colspan="4">Total</td>
<td colspan="2">${{ cart.get_total_price }}</td>
</tr>
</tbody>
</table>
<p class="text-right">
<a href="{% url 'shop:product_list' %}" class="button light">Continue shopping</a>
<a href="#" class="button">Checkout</a>
</p>
{% endblock %}
Note: You’ll need a base.html template that defines basic HTML structure and includes the static files. Also, provide a static/img/no_image.png or adjust the image paths.
Here’s a very basic base.html for completeness:
<!-- templates/base.html -->
{% load static %}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{% block title %}My Shop{% endblock %}</title>
<link href="{% static 'css/base.css' %}" rel="stylesheet">
</head>
<body>
<div id="header">
<a href="{% url 'shop:product_list' %}" class="logo">My Shop</a>
<div id="subheader">
<div class="cart">
{% with total_items=cart|length %}
{% if total_items > 0 %}
Your cart:
<a href="{% url 'cart:cart_detail' %}">
{{ total_items }} item{{ total_items|pluralize }}, ${{ cart.get_total_price }}
</a>
{% else %}
Your cart is empty.
{% endif %}
{% endwith %}
</div>
</div>
</div>
<div id="content">
{% block content %}
{% endblock %}
</div>
</body>
</html>
You would also need a static/css/base.css and a static/img/no_image.png for the styling and default image to work correctly. Don’t forget to run python manage.py collectstatic if you deploy.
To make the cart available in base.html, you’ll need to use a context processor. Add 'cart.context_processors.cart' to TEMPLATES['OPTIONS']['context_processors'] in settings.py.
Create cart/context_processors.py:
from .cart import Cart
def cart(request):
return {'cart': Cart(request)}
Wrapping Up
Congratulations! You’ve just laid the groundwork for a simple but functional shopping cart in Django. We’ve covered:
* Defining a product model.
* Using Django sessions to store cart data.
* Building a Cart class to manage adding, removing, and iterating over items.
* Creating views to handle cart logic.
* Mapping URLs to our cart views.
* Designing basic templates to display products and the cart contents.
This is a fundamental step in building any e-commerce platform. From here, you can expand your cart with features like saving carts for logged-in users, handling inventory, and integrating with a checkout process. Keep experimenting and building!
Leave a Reply
You must be logged in to post a comment.