Django for E-commerce: Building a Simple Shopping Cart

Welcome to the exciting world of web development with Django! If you’ve ever dreamt of building your own online store, you know a crucial component is the shopping cart. It’s where customers collect items they wish to purchase before heading to checkout. In this guide, we’ll walk you through creating a simple, session-based shopping cart using Django, a powerful and popular Python web framework. Don’t worry if you’re new to this; we’ll explain everything step by step, using easy-to-understand language.

What is Django and Why Use It?

Django is a high-level Python web framework that encourages rapid development and clean, pragmatic design. Think of it as a toolkit that provides many pre-built components and structures, allowing you to focus on the unique parts of your application rather than reinventing the wheel. It’s known for being “batteries included,” meaning it comes with a lot of functionalities out of the box, like an object-relational mapper (ORM), an admin panel, and a templating system.

For e-commerce, Django is an excellent choice because:
* Robustness: It’s built to handle complex applications and large traffic.
* Security: Django helps protect your site from many common security vulnerabilities.
* Scalability: It can grow with your project, from a small shop to a massive online retailer.
* Admin Panel: Django provides an automatic administrative interface, which is super helpful for managing products, orders, and users without writing extra code.

Getting Started: Setting Up Your Django Project

Before we dive into the shopping cart, let’s make sure you have Django installed and a basic project set up.

Prerequisites

You’ll need:
* Python: Make sure Python is installed on your system. You can download it from python.org.
* Virtual Environment: It’s a good practice to use a virtual environment to manage your project’s dependencies separately from other Python projects.
* Virtual Environment (often called venv): An isolated environment for your Python projects. It ensures that the packages you install for one project don’t conflict with another.

Let’s create one and install Django:

mkdir my_shop_cart
cd my_shop_cart

python -m venv venv

source venv/bin/activate

pip install Django

Creating Your First Django Project and App

Now, let’s create a Django project and an “app” within it. In Django, a “project” is the entire website, and “apps” are smaller, self-contained modules that handle specific functionalities (like products, users, or, in our case, the cart).

django-admin startproject myshop .

python manage.py startapp cart

Your project structure should now look something like this:

my_shop_cart/
├── myshop/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── cart/
│   ├── migrations/
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── models.py
│   ├── tests.py
│   └── views.py
├── manage.py
└── venv/

Registering Your App

We need to tell Django about our new cart app. Open myshop/settings.py and add 'cart' to the INSTALLED_APPS list.

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'cart', # Add your new app here
]

Defining Your Product Model

Every e-commerce site needs products! Let’s define a simple Product model. A model in Django is a class that represents a table in your database. It defines the structure and fields for the data you want to store.

Open cart/models.py and add the following:

from django.db import models

class Product(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10, decimal_places=2)
    stock = models.IntegerField(default=0)
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)

    class Meta:
        ordering = ('name',) # Order products by name by default

    def __str__(self):
        return self.name
  • models.CharField: Stores short text strings (like names). max_length is required.
  • models.TextField: Stores longer text strings (like descriptions). blank=True means it’s not a mandatory field.
  • models.DecimalField: Stores numbers with decimal places (perfect for prices). max_digits is the total number of digits, and decimal_places is the number of digits after the decimal.
  • models.IntegerField: Stores whole numbers (like stock quantity).
  • models.BooleanField: Stores True or False values (like availability).
  • models.DateTimeField: Stores date and time information. auto_now_add=True automatically sets the creation time, and auto_now=True updates the time every time the object is saved.
  • __str__ method: This is a Python standard method that defines how an object is represented as a string. It’s very useful for displaying objects in the Django admin.

Database Migrations

After defining your model, you need to tell Django to create the corresponding table in your database. This is done using migrations.

  • Migrations: Django’s way of propagating changes you make to your models (like adding a field) into your database schema.
python manage.py makemigrations

python manage.py migrate

Accessing Products via Django Admin

Django’s admin panel is incredibly useful. Let’s register our Product model so we can easily add products.

Open cart/admin.py:

from django.contrib import admin
from .models import Product

@admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
    list_display = ('name', 'price', 'stock', 'available', 'created', 'updated')
    list_filter = ('available', 'created', 'updated')
    list_editable = ('price', 'stock', 'available')
    search_fields = ('name', 'description')

Now, create a superuser to access the admin panel:

python manage.py createsuperuser

Follow the prompts to create a username, email, and password. Then, run the development server:

python manage.py runserver

Visit http://127.0.0.1:8000/admin/ in your browser, log in with your superuser credentials, and you’ll see “Products” under the “CART” section. Click on “Add” to create a few sample products for your store!

Building the Shopping Cart Logic

Now for the core: the shopping cart! For simplicity, we’ll implement a session-based shopping cart. This means the cart’s contents are stored in the user’s browser session and are not permanently linked to a user account or database. If the user clears their browser data or the session expires, the cart will be empty. This is great for anonymous users.

  • Session: A way for a web server to store information about a user across multiple requests. In Django, request.session is a dictionary-like object where you can store temporary data specific to the current user’s visit.

Cart Structure in Session

We’ll store the cart as a dictionary in request.session. The keys of this dictionary will be product_id (as a string, because session keys are strings), and the values will be another dictionary containing quantity and price. This allows us to easily retrieve product details.

Example structure:

request.session['cart'] = {
    '1': {'quantity': 2, 'price': '10.50'}, # Product ID 1, 2 quantity
    '5': {'quantity': 1, 'price': '25.00'}, # Product ID 5, 1 quantity
}

The Cart Class

It’s good practice to create a Cart class to encapsulate all the cart logic. This makes your views cleaner and your code more organized. Create a new file cart/cart.py:

from decimal import Decimal
from django.conf import settings
from .models import Product

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 the 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)
        if product_id not in self.cart:
            self.cart[product_id] = {'quantity': 0,
                                     'price': str(product.price)}
        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)

        cart = self.cart.copy()
        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

    def __len__(self):
        """
        Count all items in the cart.
        """
        return sum(item['quantity'] for item in self.cart.values())

    def get_total_price(self):
        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()

We need to define CART_SESSION_ID in our settings. Open myshop/settings.py and add this at the bottom:

CART_SESSION_ID = 'cart'

Cart Views: Adding, Displaying, and Removing Items

Now, let’s create Django views to handle the cart interactions. A view is a Python function that takes a web request and returns a web response.

Open cart/views.py:

from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST
from .models import Product
from .cart import Cart


@require_POST # This decorator ensures only POST requests can access this view
def cart_add(request, product_id):
    cart = Cart(request)
    product = get_object_or_404(Product, id=product_id)

    # For a simple demo, we'll just add one quantity.
    # In a real app, you'd get quantity from a form.
    quantity = 1 

    # You could also get override_quantity from form data if needed.
    override_quantity = False 

    cart.add(product=product, quantity=quantity, override_quantity=override_quantity)
    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)
    return render(request, 'cart/detail.html', {'cart': cart})
  • require_POST: A decorator that restricts a view to only accept POST requests. This is good practice for actions that change data, like adding or removing items.
  • get_object_or_404: A shortcut function that retrieves an object based on the given parameters, or raises an Http404 exception if the object doesn’t exist.
  • render: A shortcut function that combines a given template with a given context dictionary and returns an HttpResponse object with that rendered text.
  • redirect: A shortcut function to redirect the user’s browser to another URL.

URL Patterns for Cart Views

We need to define URLs so users can access these views.

First, create a cart/urls.py file:

from django.urls import path
from . import views

app_name = 'cart' # This helps in namespacing 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, include these URLs in your main myshop/urls.py file:

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
    # You might want to add a path for product listing here later, e.g.,
    # path('', include('products.urls')),
]

Creating Templates for Your Cart

Finally, let’s create the HTML templates to display our products and the cart.

First, create a templates directory inside your cart app: cart/templates/cart/.

Product Listing (Example Snippet)

We’ll need a way to list products and add them to the cart. For this example, we’ll imagine you have a product_list.html template (perhaps in another app, or just a simple one here for demo).

Create a simple cart/templates/cart/product_list.html:

<!-- cart/templates/cart/product_list.html -->

<h1>Our Products</h1>

{% for product in products %}
    <div>
        <h2>{{ product.name }}</h2>
        <p>{{ product.description }}</p>
        <p>Price: ${{ product.price }}</p>
        <p>Stock: {{ product.stock }}</p>
        {% if product.available and product.stock > 0 %}
            <form action="{% url 'cart:cart_add' product.id %}" method="post">
                {% csrf_token %}
                <button type="submit">Add to cart</button>
            </form>
        {% else %}
            <p>Out of stock</p>
        {% endif %}
    </div>
    <hr>
{% empty %}
    <p>No products available yet.</p>
{% endfor %}

And a very basic view for it in cart/views.py:

from django.shortcuts import render, redirect, get_object_or_404
from .models import Product
from .cart import Cart


def product_list(request):
    products = Product.objects.filter(available=True)
    return render(request, 'cart/product_list.html', {'products': products})

And add its URL to cart/urls.py:

from django.urls import path
from . import views

app_name = 'cart'

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'),
    path('products/', views.product_list, name='product_list'), # New URL for product list
]

To test, you might want to change your root URL in myshop/urls.py to point to product_list or just navigate directly to /cart/products/.

from django.contrib import admin
from django.urls import path, include
from cart.views import product_list # Import product_list view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('cart/', include('cart.urls', namespace='cart')),
    path('', product_list, name='product_list'), # Set product list as root
]

Cart Detail Template

This template will display all items currently in the user’s cart.

Create cart/templates/cart/detail.html:

<!-- cart/templates/cart/detail.html -->

<h1>Your Shopping Cart</h1>

{% if cart %}
    <table>
        <thead>
            <tr>
                <th>Product</th>
                <th>Quantity</th>
                <th>Price</th>
                <th>Total</th>
                <th>Remove</th>
            </tr>
        </thead>
        <tbody>
            {% for item in cart %}
                <tr>
                    <td>{{ item.product.name }}</td>
                    <td>{{ item.quantity }}</td>
                    <td>${{ item.price }}</td>
                    <td>${{ item.total_price }}</td>
                    <td>
                        <form action="{% url 'cart:cart_remove' item.product.id %}" method="post">
                            {% csrf_token %}
                            <button type="submit">Remove</button>
                        </form>
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>
    <p><strong>Total: ${{ cart.get_total_price }}</strong></p>
    <p><a href="{% url 'product_list' %}">Continue shopping</a></p>
{% else %}
    <p>Your cart is empty.</p>
    <p><a href="{% url 'product_list' %}">Go shopping!</a></p>
{% endif %}
  • {% csrf_token %}: This is a security measure required by Django for all POST forms to protect against Cross-Site Request Forgery (CSRF) attacks.
  • {% url 'cart:cart_add' product.id %}: This is Django’s way of dynamically generating URLs. cart is the app’s namespace, cart_add is the URL pattern name, and product.id is the argument passed to the URL pattern.

Testing Your Shopping Cart

  1. Make sure your Product model has at least one product added through the Django admin (http://127.0.0.1:8000/admin/).
  2. Run the server: python manage.py runserver
  3. Go to http://127.0.0.1:8000/ (or /cart/products/ if you didn’t change the root URL). You should see your product list.
  4. Click “Add to cart” for a product. This will redirect you to the cart detail page (http://127.0.0.1:8000/cart/).
  5. You should see the product in your cart. You can click “Remove” to take it out.
  6. Navigate back to the product list and add more items to see your cart update.

Congratulations! You’ve successfully built a basic shopping cart using Django. This foundation can be expanded with features like updating quantities, user authentication, and integrating with a payment gateway to build a full-fledged e-commerce solution.

This simple example demonstrates the core principles of using Django’s models, views, templates, and sessions to create interactive web applications. Keep experimenting and building!


Comments

Leave a Reply