Django

⌘K
  1. Home
  2. Django
  3. Speed Optimization
  4. Part 4: API Optimization

Part 4: API Optimization

🔴 Normal API (Slow)

shop/api/serializers.py – Normal Version:

from rest_framework import serializers
from shop.models import Product, Category, Brand, Review

# ❌ Basic serializer (No optimization)
class CategorySerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name', 'slug']


class BrandSerializer(serializers.ModelSerializer):
    class Meta:
        model = Brand
        fields = ['id', 'name', 'slug', 'country']


class ReviewSerializer(serializers.ModelSerializer):
    # ❌ প্রতিটি review এর জন্য user fetch করতে extra query
    user_name = serializers.CharField(source='user.username')

    class Meta:
        model = Review
        fields = ['id', 'user_name', 'rating', 'comment', 'created_at']


class ProductSerializer(serializers.ModelSerializer):
    # ❌ Nested serializers - N+1 problem
    category = CategorySerializer()
    brand = BrandSerializer()
    reviews = ReviewSerializer(many=True, source='review_set')

    class Meta:
        model = Product
        fields = [
            'id', 'name', 'slug', 'description', 'price', 'stock',
            'category', 'brand', 'reviews', 'created_at'
        ]

shop/api/views.py – Normal Version:

from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from shop.models import Product, Category
from .serializers import ProductSerializer, CategorySerializer

# ❌ Basic ViewSet (No optimization)
class ProductViewSet(viewsets.ModelViewSet):
    queryset = Product.objects.all()  # ❌ No select_related
    serializer_class = ProductSerializer

    # ❌ N+1 problem হবে serialization এ
    # প্রতিটি product এর জন্য:
    # - category fetch করতে 1 query
    # - brand fetch করতে 1 query
    # - reviews fetch করতে 1 query
    # 100 products = 1 + 100 + 100 + 100 = 301 queries!


class CategoryViewSet(viewsets.ModelViewSet):
    queryset = Category.objects.all()
    serializer_class = CategorySerializer

✅ Optimized API (Fast)

shop/api/serializers.py – Optimized Version:

from rest_framework import serializers
from shop.models import Product, Category, Brand, Review
from django.core.cache import cache

# ✅ Lightweight serializers
class CategoryLightSerializer(serializers.ModelSerializer):
    class Meta:
        model = Category
        fields = ['id', 'name', 'slug']


class BrandLightSerializer(serializers.ModelSerializer):
    class Meta:
        model = Brand
        fields = ['id', 'name', 'slug']


class ReviewLightSerializer(serializers.ModelSerializer):
    # ✅ user already prefetched থাকলে no extra query
    user_name = serializers.CharField(source='user.username', read_only=True)

    class Meta:
        model = Review
        fields = ['id', 'user_name', 'rating', 'comment', 'created_at']
        read_only_fields = ['created_at']


# ✅ List এর জন্য lightweight serializer
class ProductListSerializer(serializers.ModelSerializer):
    category_name = serializers.CharField(source='category.name', read_only=True)
    brand_name = serializers.CharField(source='brand.name', read_only=True)

    class Meta:
        model = Product
        fields = [
            'id', 'name', 'slug', 'price', 'stock',
            'category_name', 'brand_name',
            'average_rating', 'review_count', 'is_featured'
        ]
        read_only_fields = ['average_rating', 'review_count']


# ✅ Detail এর জন্য full serializer
class ProductDetailSerializer(serializers.ModelSerializer):
    category = CategoryLightSerializer(read_only=True)
    brand = BrandLightSerializer(read_only=True)
    reviews = ReviewLightSerializer(many=True, read_only=True)

    class Meta:
        model = Product
        fields = [
            'id', 'name', 'slug', 'description', 'price', 'stock',
            'category', 'brand', 'reviews',
            'average_rating', 'review_count',
            'is_active', 'is_featured', 'created_at', 'updated_at'
        ]
        read_only_fields = ['average_rating', 'review_count', 'created_at', 'updated_at']


# ✅ Category with product count
class CategoryDetailSerializer(serializers.ModelSerializer):
    product_count = serializers.IntegerField(read_only=True)

    class Meta:
        model = Category
        fields = ['id', 'name', 'slug', 'description', 'product_count', 'created_at']

shop/api/views.py – Optimized Version:

from rest_framework import viewsets, filters, status
from rest_framework.decorators import action
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from django.core.cache import cache
from django.db.models import Prefetch, Q, Count, Avg
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from shop.models import Product, Category, Brand, Review
from .serializers import (
    ProductListSerializer, ProductDetailSerializer,
    CategoryDetailSerializer, ReviewLightSerializer
)

# ✅ Fully Optimized Product API
class ProductViewSetOptimized(viewsets.ModelViewSet):
    permission_classes = [IsAuthenticatedOrReadOnly]
    filter_backends = [filters.SearchFilter, filters.OrderingFilter]
    search_fields = ['name', 'description']
    ordering_fields = ['price', 'created_at', 'average_rating']

    def get_queryset(self):
        """✅ Optimized queryset based on action"""
        queryset = Product.objects.filter(is_active=True)

        # ✅ List action - lightweight
        if self.action == 'list':
            queryset = queryset.select_related('category', 'brand')\
                              .only(
                                  'id', 'name', 'slug', 'price', 'stock',
                                  'average_rating', 'review_count', 'is_featured',
                                  'category__name', 'brand__name'
                              )

        # ✅ Detail action - full data
        elif self.action == 'retrieve':
            queryset = queryset.select_related('category', 'brand')\
                              .prefetch_related(
                                  Prefetch(
                                      'reviews',
                                      queryset=Review.objects.select_related('user')
                                                            .order_by('-created_at')[:10]
                                  )
                              )

        # ✅ Filtering
        category = self.request.query_params.get('category')
        if category:
            queryset = queryset.filter(category__slug=category)

        brand = self.request.query_params.get('brand')
        if brand:
            queryset = queryset.filter(brand__slug=brand)

        min_price = self.request.query_params.get('min_price')
        if min_price:
            queryset = queryset.filter(price__gte=min_price)

        max_price = self.request.query_params.get('max_price')
        if max_price:
            queryset = queryset.filter(price__lte=max_price)

        return queryset

    def get_serializer_class(self):
        """✅ Different serializers for different actions"""
        if self.action == 'list':
            return ProductListSerializer
        return ProductDetailSerializer

    @action(detail=False, methods=['get'])
    @method_decorator(cache_page(60 * 5))  # ✅ Cache 5 minutes
    def featured(self, request):
        """Featured products - Cached"""
        products = self.get_queryset().filter(is_featured=True)[:10]
        serializer = ProductListSerializer(products, many=True)
        return Response(serializer.data)

    @action(detail=False, methods=['get'])
    def popular(self, request):
        """Popular products by order count"""
        cache_key = 'popular_products'
        data = cache.get(cache_key)

        if data is None:
            products = self.get_queryset().annotate(
                order_count=Count('order_items')
            ).filter(order_count__gt=0)\
             .order_by('-order_count')[:10]

            serializer = ProductListSerializer(products, many=True)
            data = serializer.data
            cache.set(cache_key, data, 300)  # 5 minutes

        return Response(data)

    @action(detail=True, methods=['get'])
    def reviews(self, request, pk=None):
        """Get all reviews for a product"""
        product = self.get_object()
        reviews = product.reviews.select_related('user').order_by('-created_at')
        serializer = ReviewLightSerializer(reviews, many=True)
        return Response(serializer.data)


# ✅ Optimized Category API
class CategoryViewSetOptimized(viewsets.ReadOnlyModelViewSet):
    serializer_class = CategoryDetailSerializer
    lookup_field = 'slug'

    def get_queryset(self):
        """✅ Annotate with product count"""
        return Category.objects.annotate(
            product_count=Count('products', filter=Q(products__is_active=True))
        ).filter(product_count__gt=0)

    @action(detail=True, methods=['get'])
    def products(self, request, slug=None):
        """Get all products in a category"""
        category = self.get_object()

        products = Product.objects.filter(
            category=category,
            is_active=True
        ).select_related('brand')\
         .only('id', 'name', 'slug', 'price', 'average_rating', 'brand__name')

        serializer = ProductListSerializer(products, many=True)
        return Response(serializer.data)

🚀 Django Bolt Integration

Django Bolt হলো একটি performance library যা API response কে 10-100x দ্রুত করে।

Installation

pip install django-bolt redis django-redis

Settings Configuration

# settings.py

INSTALLED_APPS = [
    # ...
    'django_bolt',
]

# Redis Cache
CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': 'redis://127.0.0.1:6379/1',
        'OPTIONS': {
            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
        }
    }
}

# Django Bolt Configuration
DJANGO_BOLT = {
    'ENABLED': True,
    'CACHE_TIMEOUT': 300,
}

Bolt-Optimized Views

from django_bolt.views import BoltAPIView, BoltListAPIView
from rest_framework.permissions import IsAuthenticatedOrReadOnly

class BoltProductListView(BoltListAPIView):
    """✅ 10-100x faster than normal DRF"""
    queryset = Product.objects.filter(is_active=True)\
                             .select_related('category', 'brand')
    serializer_class = ProductListSerializer
    permission_classes = [IsAuthenticatedOrReadOnly]

    # Bolt-specific settings
    bolt_cache_enabled = True
    bolt_cache_timeout = 300  # 5 minutes


class BoltProductDetailView(BoltAPIView):
    """✅ Single product - Bolt optimized"""
    permission_classes = [IsAuthenticatedOrReadOnly]
    bolt_cache_enabled = True

    def get(self, request, pk):
        try:
            product = Product.objects.select_related('category', 'brand')\
                                    .prefetch_related('reviews__user')\
                                    .get(pk=pk, is_active=True)
            serializer = ProductDetailSerializer(product)
            return Response(serializer.data)
        except Product.DoesNotExist:
            return Response({'error': 'Not found'}, status=404)

URLs

# shop/api/urls.py
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views import ProductViewSetOptimized, CategoryViewSetOptimized
from .bolt_views import BoltProductListView, BoltProductDetailView

router = DefaultRouter()
router.register('products', ProductViewSetOptimized, basename='product')
router.register('categories', CategoryViewSetOptimized, basename='category')

urlpatterns = [
    path('', include(router.urls)),

    # Bolt-optimized endpoints
    path('bolt/products/', BoltProductListView.as_view(), name='bolt-products'),
    path('bolt/products/<int:pk>/', BoltProductDetailView.as_view(), name='bolt-product-detail'),
]

How can we help?