Implementing a Shopping Cart Module in Django

Frontend Request Analysis

The frontend sends two pieces of data to the backend: the product ID and the quantity. The request is made via POST method. ### Backend View Logic

The backend implements the following workflow: 1. Verify user authentication status 2. Extract incoming parameters from the request 3. Validate all required parameters are present 4. Query the database to confirm product existence 5. Validate that quantity is an integer 6. Check if requested quantity exceeds available stock 7. Persist cart data to Redis storage 8. Return appropriate JSON response to frontend ### Frontend POST Request Implementation

$.post("/cart/add", req_data, function (resp_data) {
    if (1 === resp_data.code) {
        window.location.href = "/users/login";
    } else if (0 === resp_data.code) {
        $(".add_jump").stop().animate({
            'left': $to_y+7,
            'top': $to_x+7
        }, "fast", function() {
            $(".add_jump").fadeOut('fast', function(){
                $('#show_count').html(resp_data.cart_num);
            });
        });
    } else {
        alert(resp_data.message);
    }
}, "json");

});


</div>### Backend View Handler

<div>```
class AddCartView(View):
    """Shopping cart addition handler"""
    def post(self, request):
        # Authentication check
        if not request.user.is_authenticated():
            return JsonResponse({"code": 1, "message": "User not authenticated"})

        # Extract product ID and quantity from request
        sku_id = request.POST.get("sku_id")
        quantity = request.POST.get("count")

        # Validate all required parameters
        if not all([sku_id, quantity]):
            return JsonResponse({"code": 2, "message": "Missing required parameters"})

        # Verify product exists in database
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            return JsonResponse({"code": 3, "message": "Product not found"})

        # Ensure quantity is a valid integer
        try:
            quantity = int(quantity)
        except Exception:
            return JsonResponse({"code": 4, "message": "Invalid quantity parameter"})

        # Stock availability check
        if quantity > sku.stock:
            return JsonResponse({"code": 5, "message": "Insufficient stock"})

        # Persist to Redis
        redis_conn = get_redis_connection("default")
        user_id = request.user.id
        existing_qty = redis_conn.hget("cart_%s" % user_id, sku_id)

        # Accumulate quantity if item already exists in cart
        if existing_qty is not None:
            quantity += int(existing_qty)
        
        # Store updated cart data
        redis_conn.hset("cart_%s" % user_id, sku_id, quantity)
        
        # Calculate total cart items
        cart_num = 0
        cart = redis_conn.hgetall('cart_%s' % user_id)
        for val in cart.values():
            cart_num += int(val)

        return JsonResponse({"code": 0, "message": "Item added to cart", "cart_num": cart_num})

Root URL configuration: ``` url(r'^cart/', include(cart.urls, namespace="cart")),


</div>Cart application URLs: <div>```
from cart import views

urlpatterns = [
    url(r"^add$", views.AddCartView.as_view(), name="add"),
]

2 Supporting Guest Users with Cookie-Based Storage

When users are not logged in, cart data is stored in browser cookies as a JSON string. The format is: "cart": '{"sku_1": 10, "sku_2": 11}'When users log in, cart data from cookies is merged with existing Redis data. ### JSON Module Usage

Converting Python dictionary to JSON string: ``` json.dumps(dictionary_data)


</div>Converting JSON string back to Python dictionary: <div>```
json.loads(json_string)

Accumulate quantities if product already in cart

if sku_id in cart_dict: previous_qty = cart_dict[sku_id] quantity += previous_qty

Update cart dictionary

cart_dict[sku_id] = quantity updated_cart_json = json.dumps(cart_dict)

Calculate total items

cart_total = sum(cart_dict.values())

Build response with updated cookie

response = JsonResponse({"code": 0, "message": "Item added to cart", "cart_total": cart_total}) response.set_cookie('cart', updated_cart_json) return response


</div>### Complete AddCartView Implementation

<div>```
class AddCartView(View):
    """Shopping cart handler supporting both authenticated and guest users"""
    def post(self, request):
        sku_id = request.POST.get("sku_id")
        quantity = request.POST.get("count")

        # Validate required parameters
        if not all([sku_id, quantity]):
            return JsonResponse({"code": 2, "message": "Missing required parameters"})

        # Verify product existence
        try:
            sku = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            return JsonResponse({"code": 3, "message": "Product not found"})

        # Validate quantity type
        try:
            quantity = int(quantity)
        except Exception:
            return JsonResponse({"code": 4, "message": "Invalid quantity"})

        # Stock availability check
        if quantity > sku.stock:
            return JsonResponse({"code": 5, "message": "Stock insufficient"})

        # Handle authenticated users
        if request.user.is_authenticated():
            redis_conn = get_redis_connection("default")
            user_id = request.user.id
            existing_qty = redis_conn.hget("cart_%s" % user_id, sku_id)

            if existing_qty is not None:
                quantity += int(existing_qty)

            redis_conn.hset("cart_%s" % user_id, sku_id, quantity)
            cart_total = sum(int(v) for v in redis_conn.hgetall('cart_%s' % user_id).values())
            return JsonResponse({"code": 0, "message": "Item added", "cart_num": cart_total})
        else:
            # Handle guest users with cookies
            cart_json = request.COOKIES.get("cart")
            cart_dict = json.loads(cart_json) if cart_json else {}

            if sku_id in cart_dict:
                cart_dict[sku_id] += quantity
            else:
                cart_dict[sku_id] = quantity

            updated_json = json.dumps(cart_dict)
            cart_total = sum(cart_dict.values())

            response = JsonResponse({"code": 0, "message": "Item added", "cart_num": cart_total})
            response.set_cookie('cart', updated_json)
            return response

3 Base Cart Mixin for Reusable Cart Count Logic

Create a base mixin class in the goods application to handle cart counting across different views. ``` class BaseCartMixin(View): """Mixin providing cart quantity calculation""" def calculate_cart_quantity(self, request): cart_count = 0

    if request.user.is_authenticated():
        # Retrieve from Redis for authenticated users
        redis_conn = get_redis_connection("default")
        cart_data = redis_conn.hgetall("cart_%s" % request.user.id)
        cart_count = sum(int(v) for v in cart_data.values())
    else:
        # Retrieve from cookies for guest users
        cart_json = request.COOKIES.get("cart")
        cart_data = json.loads(cart_json) if cart_json else {}
        cart_count = sum(cart_data.values())

    return cart_count

</div>Views can inherit this mixin to easily access cart quantities. ---

4 Displaying Cart Contents
--------------------------

### Backend View Logic

The cart display view retrieves all cart items, queries product details from the database, and calculates subtotals for each item along with the total order value. <div>```
class CartInfoView(View):
    """Display shopping cart contents"""
    def get(self, request):
        # Retrieve cart data based on authentication status
        if request.user.is_authenticated():
            user_id = request.user.id
            redis_conn = get_redis_connection('default')
            cart_data = redis_conn.hgetall('cart_%s' % user_id)
        else:
            cart_json = request.COOKIES.get('cart')
            cart_data = json.loads(cart_json) if cart_json else {}

        # Query product details and calculate totals
        cart_items = []
        total_quantity = 0
        total_price = 0

        for item_id, qty in cart_data.items():
            try:
                product = GoodsSKU.objects.get(id=item_id)
            except GoodsSKU.DoesNotExist:
                continue

            qty = int(qty)
            product.ordered_qty = qty
            cart_items.append(product)
            
            item_total = product.price * qty
            product.subtotal = item_total
            total_price += item_total
            total_quantity += qty

        context = {
            "items": cart_items,
            "total_qty": total_quantity,
            "total_price": total_price
        }

        return render(request, 'cart.html', context)

5 Merging Cart Data During Login

Simple Synchronization Approach

On successful login, copy cookie cart data to Redis and clear the cookie. ```

Merge cookie cart into Redis cart

redis_conn = get_redis_connection('default') redis_cart = redis_conn.hgetall('cart_%s' % user.id) redis_cart.update(cart_cookie) redis_conn.hmset('cart_%s' % user.id, redis_cart)

Clear browser cookie

response.delete_cookie('cart')


</div>### Accumulating Quantities on Login

This approach adds quantities together when the same product exists in both storage locations. <div>```
# Process each item from cookie
for sku_id, qty in cart_cookie.items():
    # Convert string key to bytes for Redis lookup
    sku_key = sku_id.encode()
    if sku_key in redis_cart:
        previous_qty = redis_cart[sku_key]
        qty += int(previous_qty)
    redis_cart[sku_key] = qty

redis_conn.hmset('cart_%s' % user.id, redis_cart)
def post(self, request):
    username = request.POST.get("username")
    password = request.POST.get("pwd")
    remember_me = request.POST.get('remembered')

    if not all([username, password]):
        return render(request, "login.html")

    # Authenticate user
    user = authenticate(username=username, password=password)
    if user is None:
        return render(request, "login.html", {"errmsg": "Invalid credentials"})

    if not user.is_active:
        return render(request, "login.html", {"errmsg": "Account not activated"})

    # Establish session
    login(request, user)
    request.session.set_expiry(0 if remember_me != 'on' else None)

    # Retrieve and merge cart data
    cart_json = request.COOKIES.get('cart')
    cookie_cart = json.loads(cart_json) if cart_json else {}

    redis_conn = get_redis_connection('default')
    redis_cart = redis_conn.hgetall('cart_%s' % user.id)

    for sku_id, qty in cookie_cart.items():
        sku_key = sku_id.encode()
        if sku_key in redis_cart:
            qty += int(redis_cart[sku_key])
        redis_cart[sku_key] = qty

    redis_conn.hmset('cart_%s' % user.id, redis_cart)

    # Redirect logic
    next_url = request.GET.get('next')
    response = redirect(next_url if next_url else reverse("goods:index"))
    response.delete_cookie('cart')
    return response

</div>---

6 Updating Cart Quantities
--------------------------

### Backend Update Logic

<div>```
class UpdateCartView(View):
    """Handle cart quantity modifications"""
    def post(self, request):
        sku_id = request.POST.get("sku_id")
        quantity = request.POST.get("count")

        # Validate product exists
        try:
            product = GoodsSKU.objects.get(id=sku_id)
        except GoodsSKU.DoesNotExist:
            return JsonResponse({"code": 1, "message": "Product not found"})

        # Validate quantity is integer
        try:
            quantity = int(quantity)
        except Exception:
            return JsonResponse({"code": 2, "message": "Invalid quantity"})

        # Check stock availability
        if quantity > product.stock:
            return JsonResponse({"code": 3, "message": "Stock insufficient"})

        # Persist update
        if not request.user.is_authenticated():
            # Store in cookie for guests
            cart_json = request.COOKIES.get("cart")
            cart_dict = json.loads(cart_json) if cart_json else {}
            cart_dict[sku_id] = quantity

            response = JsonResponse({"code": 0, "message": "Updated"})
            response.set_cookie("cart", json.dumps(cart_dict))
            return response
        else:
            # Store in Redis for authenticated users
            redis_conn = get_redis_connection("default")
            redis_conn.hset("cart_%s" % request.user.id, sku_id, quantity)
            return JsonResponse({"code": 0, "message": "Updated"})

7 Removing Items from Cart

    if not sku_id:
        return JsonResponse({'code': 1, 'message': 'Missing product ID'})

    if not request.user.is_authenticated():
        # Remove from cookie
        cart_json = request.COOKIES.get('cart')
        if cart_json:
            cart_dict = json.loads(cart_json)
            if sku_id in cart_dict:
                del cart_dict[sku_id]

            response = JsonResponse({'code': 0, 'message': 'Removed'})
            response.set_cookie('cart', json.dumps(cart_dict))
            return response
        return JsonResponse({'code': 0, 'message': 'Removed'})
    else:
        # Remove from Redis
        redis_conn = get_redis_connection('default')
        redis_conn.hdel('cart_%s' % request.user.id, sku_id)
        return JsonResponse({'code': 0, 'message': 'Removed'})

</div>---

8 Frontend Template Implementation
----------------------------------

<div>```
{% extends 'base.html' %}

{% block title %}Shopping Cart{% endblock %}

{% block body %}
    <div class="total_count">Total Items: <em>{{ total_quantity }}</em></div>
    <ul class="cart_list_th clearfix">
        <li class="col01">Product</li>
        <li class="col02">Unit</li>
        <li class="col03">Price</li>
        <li class="col04">Quantity</li>
        <li class="col05">Subtotal</li>
        <li class="col06">Action</li>
    </ul>
    <form action="#" method="post">
    {% csrf_token %}
    {% for item in items %}
    <ul class="cart_list_td clearfix" sku_id="{{ item.id }}">
        <li class="col01"><input checked="checked" name="selected" type="checkbox" value="{{ item.id }}"></input></li>
        <li class="col02"><img src="{{ item.default_image.url }}"></img></li>
        <li class="col03">{{ item.name }}<br></br><em>{{ item.price }}/{{ item.unit }}</em></li>
        <li class="col04">{{ item.unit }}</li>
        <li class="col05"><span>{{ item.price }}</span></li>
        <li class="col06">
            <div class="num_add">
                <a class="add fl" href="javascript:;">+</a>
                <input class="num_show fl" sku_id="{{ item.id }}" type="text" value="{{ item.ordered_qty }}"></input>
                <a class="minus fl" href="javascript:;">-</a>
            </div>
        </li>
        <li class="col07"><span>{{ item.subtotal }}</span></li>
        <li class="col08"><a class="del_btn" href="javascript:;">Delete</a></li>
    </ul>
    {% endfor %}
    <ul class="settlements">
        <li class="col01"><input checked="checked" type="checkbox"></input></li>
        <li class="col02">Select All</li>
        <li class="col03">Total: <span>$</span><em id="total_amount">{{ total_price }}</em></li>
        <li class="col04"><input id="checkout_btn" type="submit" value="Checkout"></input></li>
    </ul>
    </form>
{% endblock %}

{% block bottom_files %}
    <script>
        function refreshOrderSummary() {
            var amount = 0;
            var qty = 0;
            $('.cart_list_td :checked').parents('ul').each(function () {
                amount += parseFloat($(this).children('li.col07').text());
                qty += parseInt($(this).find('.num_show').val());
            });
            $("#total_amount").text(amount.toFixed(2));
            $("#total_count").text(qty);
        }

        function syncCartBackend(sku_id, new_qty, element) {
            $.post("/cart/update", {
                sku_id: sku_id,
                count: new_qty,
                csrfmiddlewaretoken: "{{ csrf_token }}"
            }, function(data) {
                if (0 === data.code) {
                    $(element).val(new_qty);
                    var price = $(".cart_list_td[sku_id="+sku_id+"]").children('li.col05').text();
                    $(".cart_list_td[sku_id="+sku_id+"]").children('li.col07').text((price * new_qty).toFixed(2));
                    refreshOrderSummary();
                }
            });
        }

        $(".add").click(function() {
            var sku_id = $(this).next().attr("sku_id");
            var qty = parseInt($(this).next().val()) + 1;
            syncCartBackend(sku_id, qty, $(this).next());
        });

        $(".minus").click(function() {
            var sku_id = $(this).prev().attr("sku_id");
            var qty = Math.max(1, parseInt($(this).prev().val()) - 1);
            syncCartBackend(sku_id, qty, $(this).prev());
        });

        $(".num_show").blur(function() {
            var sku_id = $(this).attr("sku_id");
            var qty = parseInt($(this).val());
            if (isNaN(qty) || qty < 1) qty = 1;
            syncCartBackend(sku_id, qty, this);
        });

        $(".del_btn").click(function() {
            var sku_id = $(this).parents("ul").attr("sku_id");
            $.post('/cart/delete', {
                sku_id: sku_id,
                csrfmiddlewaretoken: "{{ csrf_token }}"
            }, function() {
                location.reload();
            });
        });

        $('.settlements :checkbox').change(function () {
            $('.cart_list_td :checkbox').prop('checked', $(this).prop('checked'));
            refreshOrderSummary();
        });

        $('.cart_list_td :checkbox').change(function () {
            var all = $('.cart_list_td :checkbox').length;
            var checked = $('.cart_list_td :checked').length;
            $('.settlements :checkbox').prop('checked', all === checked);
            refreshOrderSummary();
        });
    </script>
{% endblock %}

Tags: Django python Redis shopping-cart Cookies

Posted on Tue, 22 Sep 2026 16:26:50 +0000 by elgoog