Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building Shopping Cart and Order Management APIs with Django REST Framework

Tech 1

Shopping Cart Implementation

Serializer Design for Cart Operations

To manage shopping cart data, we define a serializer that handles the addition of items. Since we need custom logic for merging quantities if an item already exists, inheriting from serializers.Serializer is appropriate rather than ModelSerializer.

from rest_framework import serializers
from .models import Cart
from goods.models import Product

class CartItemSerializer(serializers.Serializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    quantity = serializers.IntegerField(
        required=True, 
        label="Quantity", 
        min_value=1,
        error_messages={"min_value": "Quantity cannot be less than 1.", "required": "Please select a quantity."}
    )
    product = serializers.PrimaryKeyRelatedField(
        required=True, 
        queryset=Product.objects.all()
    )

    def create(self, validated_data):
        current_user = self.context["request"].user
        quantity = validated_data["quantity"]
        product = validated_data["product"]

        existing_item = Cart.objects.filter(user=current_user, product=product).first()
        
        if existing_item:
            existing_item.quantity += quantity
            existing_item.save()
            return existing_item
        
        return Cart.objects.create(**validated_data)

    def update(self, instance, validated_data):
        instance.quantity = validated_data.get("quantity", instance.quantity)
        instance.save()
        return instance

ViewSet Configuration

The view handles CRUD operations for the cart. We restrict access to authenticated users and ensure they can only modify their own items. We also configure the lookup field to use the product ID for updates.

from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.authentication import SessionAuthentication
from .serializers import CartItemSerializer, CartDetailSerializer
from .models import Cart
from utils.permissions import IsOwnerOrReadOnly

class CartViewSet(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = CartItemSerializer
    lookup_field = "product_id"

    def get_queryset(self):
        return Cart.objects.filter(user=self.request.user)

    def get_serializer_class(self):
        if self.action == 'list':
            return CartDetailSerializer
        return CartItemSerializer

Dynamic Serializer for Product Details

When listing cart items, clients often need detailed product information (like name or image) rather than just the product ID. We use a dynamic serializer to handle this.

class CartDetailSerializer(serializers.ModelSerializer):
    product = ProductSerializer(read_only=True)

    class Meta:
        model = Cart
        fields = ("product", "quantity")

Inventory Management Signals

It is crucial to maintain accurate inventory levels when items are added to or removed from the cart. We override the perform methods in the ViewSet to adjust the product stock accordingly.

    def perform_create(self, serializer):
        cart_item = serializer.save()
        product = cart_item.product
        product.stock -= cart_item.quantity
        product.save()

    def perform_destroy(self, instance):
        instance.product.stock += instance.quantity
        instance.product.save()
        instance.delete()

    def perform_update(self, serializer):
        original_quantity = serializer.instance.quantity
        updated_item = serializer.save()
        quantity_delta = updated_item.quantity - original_quantity
        updated_item.product.stock -= quantity_delta
        updated_item.product.save()

Order Management System

Order Serializer Logic

When a user creates an order, the system must generate a unique order number and populate read-only fields such as payment status. The validate method is used to inject the generated order number before saving.

import time
import random
from .models import OrderInfo, OrderGoods

class OrderSerializer(serializers.ModelSerializer):
    user = serializers.HiddenField(default=serializers.CurrentUserDefault())
    
    # Read-only fields generated by the system
    order_number = serializers.CharField(read_only=True)
    trade_no = serializers.CharField(read_only=True)
    pay_status = serializers.CharField(read_only=True)
    pay_time = serializers.DateTimeField(read_only=True)

    def generate_order_sn(self):
        # Format: Timestamp + User ID + Random integer
        time_str = time.strftime("%Y%m%d%H%M%S")
        user_id = self.context["request"].user.id
        random_suffix = random.randint(10, 99)
        return f"{time_str}{user_id}{random_suffix}"

    def validate(self, attrs):
        attrs["order_number"] = self.generate_order_sn()
        return attrs

    class Meta:
        model = OrderInfo
        fields = "__all__"

Order Detail Nested Serializer

To display order details, we nest the goods information within the order serializer.

class OrderGoodsSerializer(serializers.ModelSerializer):
    product = ProductSerializer(read_only=True)

    class Meta:
        model = OrderGoods
        fields = "__all__"

class OrderDetailSerializer(serializers.ModelSerializer):
    items = OrderGoodsSerializer(many=True, read_only=True)

    class Meta:
        model = OrderInfo
        fields = "__all__"

Order ViewSet and Cart Clearance

The order creation process involves validating the data, saving the order, transferring items from the shopping cart to the order record, and finally clearing the cart.

from rest_framework import mixins

class OrderViewSet(mixins.ListModelMixin, mixins.RetrieveModelMixin, 
                  mixins.CreateModelMixin, mixins.DestroyModelMixin, 
                  viewsets.GenericViewSet):
    permission_classes = [IsAuthenticated, IsOwnerOrReadOnly]
    authentication_classes = [JSONWebTokenAuthentication, SessionAuthentication]
    serializer_class = OrderSerializer

    def get_serializer_class(self):
        if self.action == "retrieve":
            return OrderDetailSerializer
        return OrderSerializer

    def get_queryset(self):
        return OrderInfo.objects.filter(user=self.request.user)

    def perform_create(self, serializer):
        new_order = serializer.save()
        cart_items = Cart.objects.filter(user=self.request.user)
        
        for item in cart_items:
            OrderGoods.objects.create(
                order=new_order,
                product=item.product,
                quantity=item.quantity
            )
            item.delete()
        return new_order

URL Configuration

Finally, register the ViewSets with the router.

from django.conf.urls import url, include
from rest_framework.routers import DefaultRouter
from trade.views import CartViewSet, OrderViewSet

router = DefaultRouter()
router.register(r'carts', CartViewSet, base_name="carts")
router.register(r'orders', OrderViewSet, base_name="orders")

urlpatterns = [
    url(r'^', include(router.urls)),
]

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.