Views হলো যেখানে সবচেয়ে বেশি optimization করা যায়। এখানে database queries, caching, এবং response generation সব হয়।
🔴 Normal Views (Slow)
shop/views.py – Normal Version:
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView
from .models import Product, Category, Brand, Review, Order
# ❌ Function-based view (No optimization)
def product_list(request):
"""সব products দেখান - Slow!"""
products = Product.objects.all() # ❌ No select_related
# ❌ Template এ loop করলে N+1 problem
# {% for product in products %}
# {{ product.category.name }} <- Extra query!
# {% endfor %}
context = {'products': products}
return render(request, 'shop/product_list.html', context)
def product_detail(request, pk):
"""একটি product এর details - Slow!"""
product = get_object_or_404(Product, pk=pk) # ❌ No select_related
# ❌ প্রতিটি access এ extra query
category_name = product.category.name # Extra query!
brand_name = product.brand.name # Extra query!
# ❌ Reviews fetch করতে extra query
reviews = product.review_set.all() # Extra query!
# ❌ Average rating calculate করতে query
avg_rating = reviews.aggregate(Avg('rating')) # Extra query!
context = {
'product': product,
'reviews': reviews,
'avg_rating': avg_rating,
}
return render(request, 'shop/product_detail.html', context)
# ❌ Class-based view (No optimization)
class ProductListView(ListView):
model = Product
template_name = 'shop/product_list.html'
context_object_name = 'products'
paginate_by = 20
# ❌ queryset optimize নেই
# N+1 problem হবে template এ
class ProductDetailView(DetailView):
model = Product
template_name = 'shop/product_detail.html'
context_object_name = 'product'
# ❌ Related data fetch optimize নেই
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# ❌ Extra queries
context['reviews'] = self.object.review_set.all()
context['related_products'] = Product.objects.filter(
category=self.object.category
).exclude(pk=self.object.pk)[:4]
return context
def category_products(request, category_id):
"""একটি category এর সব products - Slow!"""
category = get_object_or_404(Category, pk=category_id)
products = Product.objects.filter(category=category) # ❌ No optimization
context = {
'category': category,
'products': products,
}
return render(request, 'shop/category_products.html', context)
def user_orders(request):
"""User এর সব orders - Slow!"""
if not request.user.is_authenticated:
return redirect('login')
# ❌ No prefetch_related for items
orders = Order.objects.filter(user=request.user)
# ❌ Template এ loop করলে N+1 problem
# {% for order in orders %}
# {% for item in order.items.all %} <- Extra query per order!
# {% endfor %}
# {% endfor %}
context = {'orders': orders}
return render(request, 'shop/user_orders.html', context)❌ Normal Views এর সমস্যা:
- N+1 Query Problem – ForeignKey access এ extra queries
- No Caching – Same data বারবার fetch
- Inefficient Filtering – Index ব্যবহার না করা
- No Pagination – সব data একসাথে load
- Template এ Heavy Logic – Database queries template এ
✅ Optimized Views (Fast)
shop/views.py – Optimized Version:
from django.shortcuts import render, get_object_or_404, redirect
from django.views.generic import ListView, DetailView
from django.core.cache import cache
from django.db.models import Prefetch, Q, Count, Avg, F
from django.core.paginator import Paginator
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from .models import Product, Category, Brand, Review, Order, OrderItem
# ✅ Function-based view (Optimized)
def product_list_optimized(request):
"""সব products দেখান - Fast!"""
# ✅ Cache check করুন
cache_key = 'product_list_active'
products = cache.get(cache_key)
if products is None:
# ✅ Optimized queryset
products = Product.objects.filter(is_active=True)\
.select_related('category', 'brand')\
.only(
'id', 'name', 'slug', 'price', 'stock',
'average_rating', 'review_count',
'category__name', 'brand__name'
)\
.order_by('-created_at')
# ✅ Cache করুন (5 মিনিট)
cache.set(cache_key, products, 300)
# ✅ Pagination
paginator = Paginator(products, 20)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context = {'page_obj': page_obj}
return render(request, 'shop/product_list.html', context)
def product_detail_optimized(request, slug):
"""একটি product এর details - Fast!"""
# ✅ Cache check
cache_key = f'product_detail_{slug}'
cached_data = cache.get(cache_key)
if cached_data:
return render(request, 'shop/product_detail.html', cached_data)
# ✅ Optimized query with all relations
product = get_object_or_404(
Product.objects.select_related('category', 'brand')
.prefetch_related(
Prefetch(
'reviews',
queryset=Review.objects.select_related('user')
.order_by('-created_at')[:10]
)
),
slug=slug,
is_active=True
)
# ✅ Related products (optimized)
related_products = Product.objects.filter(
category=product.category,
is_active=True
).exclude(pk=product.pk)\
.select_related('category', 'brand')\
.only('id', 'name', 'slug', 'price', 'average_rating')[:4]
context = {
'product': product,
'related_products': related_products,
}
# ✅ Cache করুন (10 মিনিট)
cache.set(cache_key, context, 600)
return render(request, 'shop/product_detail.html', context)
# ✅ Class-based view (Fully Optimized)
class ProductListViewOptimized(ListView):
model = Product
template_name = 'shop/product_list.html'
context_object_name = 'products'
paginate_by = 20
def get_queryset(self):
"""✅ Optimized queryset"""
queryset = Product.objects.filter(is_active=True)
# ✅ select_related for ForeignKeys
queryset = queryset.select_related('category', 'brand')
# ✅ only() - শুধু প্রয়োজনীয় fields
queryset = queryset.only(
'id', 'name', 'slug', 'price', 'stock',
'is_featured', 'average_rating', 'review_count',
'category__id', 'category__name', 'category__slug',
'brand__id', 'brand__name', 'brand__slug'
)
# ✅ Filtering (indexed fields)
category_slug = self.request.GET.get('category')
if category_slug:
queryset = queryset.filter(category__slug=category_slug)
brand_slug = self.request.GET.get('brand')
if brand_slug:
queryset = queryset.filter(brand__slug=brand_slug)
# ✅ Price range filter
min_price = self.request.GET.get('min_price')
if min_price:
queryset = queryset.filter(price__gte=min_price)
max_price = self.request.GET.get('max_price')
if max_price:
queryset = queryset.filter(price__lte=max_price)
# ✅ Search (indexed fields)
search = self.request.GET.get('q')
if search:
queryset = queryset.filter(
Q(name__icontains=search) |
Q(description__icontains=search)
)
# ✅ Sorting
sort = self.request.GET.get('sort', '-created_at')
allowed_sorts = [
'price', '-price',
'name', '-name',
'-created_at', 'created_at',
'-average_rating', 'average_rating'
]
if sort in allowed_sorts:
queryset = queryset.order_by(sort)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# ✅ Categories for filter (cached)
cache_key = 'categories_with_count'
categories = cache.get(cache_key)
if categories is None:
categories = Category.objects.annotate(
product_count=Count('products', filter=Q(products__is_active=True))
).filter(product_count__gt=0)
cache.set(cache_key, categories, 600)
context['categories'] = categories
# ✅ Brands for filter (cached)
cache_key = 'brands_with_count'
brands = cache.get(cache_key)
if brands is None:
brands = Brand.objects.filter(is_active=True).annotate(
product_count=Count('products', filter=Q(products__is_active=True))
).filter(product_count__gt=0)
cache.set(cache_key, brands, 600)
context['brands'] = brands
return context
class ProductDetailViewOptimized(DetailView):
model = Product
template_name = 'shop/product_detail.html'
context_object_name = 'product'
slug_field = 'slug'
slug_url_kwarg = 'slug'
def get_queryset(self):
"""✅ Optimized queryset with all relations"""
return Product.objects.filter(is_active=True)\
.select_related('category', 'brand')\
.prefetch_related(
Prefetch(
'reviews',
queryset=Review.objects.select_related('user')
.order_by('-created_at')
),
'reviews__user'
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
product = self.object
# ✅ Related products (cached per category)
cache_key = f'related_products_{product.category_id}_{product.id}'
related_products = cache.get(cache_key)
if related_products is None:
related_products = Product.objects.filter(
category=product.category,
is_active=True
).exclude(pk=product.pk)\
.select_related('category', 'brand')\
.only('id', 'name', 'slug', 'price', 'average_rating', 'stock')[:4]
cache.set(cache_key, list(related_products), 600)
context['related_products'] = related_products
# ✅ Reviews already prefetched, no extra query
context['reviews'] = product.reviews.all()[:10]
return context
def category_products_optimized(request, category_slug):
"""একটি category এর সব products - Fast!"""
# ✅ Cache check
cache_key = f'category_products_{category_slug}'
cached_data = cache.get(cache_key)
if cached_data:
return render(request, 'shop/category_products.html', cached_data)
# ✅ Optimized category fetch
category = get_object_or_404(
Category.objects.annotate(
product_count=Count('products', filter=Q(products__is_active=True))
),
slug=category_slug
)
# ✅ Optimized products fetch
products = Product.objects.filter(
category=category,
is_active=True
).select_related('brand')\
.only('id', 'name', 'slug', 'price', 'stock', 'average_rating', 'brand__name')\
.order_by('-created_at')
# ✅ Pagination
paginator = Paginator(products, 20)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context = {
'category': category,
'page_obj': page_obj,
}
# ✅ Cache করুন (5 মিনিট)
cache.set(cache_key, context, 300)
return render(request, 'shop/category_products.html', context)
def user_orders_optimized(request):
"""User এর সব orders - Fast!"""
if not request.user.is_authenticated:
return redirect('login')
# ✅ Optimized orders fetch
orders = Order.objects.filter(user=request.user)\
.prefetch_related(
Prefetch(
'items',
queryset=OrderItem.objects.select_related('product')
.only('id', 'quantity', 'price', 'product__name')
)
)\
.annotate(
item_count=Count('items')
)\
.order_by('-created_at')
# ✅ Pagination
paginator = Paginator(orders, 10)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context = {'page_obj': page_obj}
return render(request, 'shop/user_orders.html', context)
# ✅ Cache decorator দিয়ে পুরো view cache করুন
@cache_page(60 * 5) # 5 মিনিট cache
def featured_products(request):
"""Featured products - Cached view"""
products = Product.objects.filter(
is_active=True,
is_featured=True
).select_related('category', 'brand')\
.only('id', 'name', 'slug', 'price', 'average_rating')[:10]
context = {'products': products}
return render(request, 'shop/featured_products.html', context)
# ✅ Advanced: Multiple prefetch with filtering
def brand_detail_optimized(request, brand_slug):
"""Brand details with products - Fully optimized"""
# ✅ Brand with annotated data
brand = get_object_or_404(
Brand.objects.annotate(
product_count=Count('products', filter=Q(products__is_active=True)),
avg_product_price=Avg('products__price', filter=Q(products__is_active=True))
),
slug=brand_slug,
is_active=True
)
# ✅ Products with custom prefetch
products = Product.objects.filter(
brand=brand,
is_active=True
).select_related('category')\
.prefetch_related(
Prefetch(
'reviews',
queryset=Review.objects.filter(rating__gte=4)
.select_related('user')[:3]
)
)\
.annotate(
order_count=Count('order_items')
)\
.order_by('-order_count', '-average_rating')
# ✅ Pagination
paginator = Paginator(products, 20)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context = {
'brand': brand,
'page_obj': page_obj,
}
return render(request, 'shop/brand_detail.html', context)
# ✅ Search view with optimization
def search_products(request):
"""Product search - Optimized"""
query = request.GET.get('q', '').strip()
if not query:
return render(request, 'shop/search.html', {'products': []})
# ✅ Cache search results
cache_key = f'search_{query}'
products = cache.get(cache_key)
if products is None:
# ✅ Optimized search query
products = Product.objects.filter(
Q(name__icontains=query) |
Q(description__icontains=query) |
Q(category__name__icontains=query) |
Q(brand__name__icontains=query),
is_active=True
).select_related('category', 'brand')\
.only('id', 'name', 'slug', 'price', 'average_rating')\
.distinct()[:50] # Limit results
# ✅ Cache করুন (2 মিনিট)
cache.set(cache_key, list(products), 120)
# ✅ Pagination
paginator = Paginator(products, 20)
page_number = request.GET.get('page', 1)
page_obj = paginator.get_page(page_number)
context = {
'query': query,
'page_obj': page_obj,
}
return render(request, 'shop/search.html', context)📊 Views Optimization Techniques বিস্তারিত
1️⃣ select_related() – ForeignKey Optimization
# ❌ Without select_related (N+1 Problem)
products = Product.objects.all()
for product in products:
print(product.category.name) # Extra query!
print(product.brand.name) # Extra query!
# 100 products = 1 + 100 + 100 = 201 queries
# ✅ With select_related (JOIN)
products = Product.objects.select_related('category', 'brand')
for product in products:
print(product.category.name) # No extra query!
print(product.brand.name) # No extra query!
# 100 products = 1 query (with JOINs)
# SQL Query:
# SELECT product.*, category.*, brand.*
# FROM product
# INNER JOIN category ON product.category_id = category.id
# INNER JOIN brand ON product.brand_id = brand.idকখন ব্যবহার করবেন:
- ✅ ForeignKey (Many-to-One)
- ✅ OneToOneField
- ✅ যখন relation সবসময় access করা হয়
কখন ব্যবহার করবেন না:
- ❌ ManyToManyField (prefetch_related ব্যবহার করুন)
- ❌ Reverse ForeignKey (prefetch_related ব্যবহার করুন)
2️⃣ prefetch_related() – Reverse Relations & ManyToMany
# ❌ Without prefetch_related
orders = Order.objects.all()
for order in orders:
for item in order.items.all(): # Extra query per order!
print(item.product.name)
# 100 orders with 5 items each = 1 + 100 = 101 queries
# ✅ With prefetch_related
orders = Order.objects.prefetch_related('items')
for order in orders:
for item in order.items.all(): # No extra query!
print(item.product.name)
# 100 orders = 2 queries (1 main + 1 prefetch)
# SQL Queries:
# Query 1: SELECT * FROM order
# Query 2: SELECT * FROM orderitem WHERE order_id IN (1,2,3,...,100)কখন ব্যবহার করবেন:
- ✅ Reverse ForeignKey (One-to-Many)
- ✅ ManyToManyField
- ✅ GenericForeignKey
3️⃣ Prefetch() – Custom Prefetch with Filtering
# ✅ Advanced: Prefetch with custom queryset
products = Product.objects.prefetch_related(
Prefetch(
'reviews',
queryset=Review.objects.filter(rating__gte=4)
.select_related('user')
.order_by('-created_at')[:5]
)
)
# এখন product.reviews.all() শুধু 4+ rating এর 5টি review দেবে
# এবং user relation already loaded থাকবে!4️⃣ only() – Load Specific Fields Only
# ❌ Without only() - সব fields load হয়
products = Product.objects.all()
# SELECT * FROM product (সব columns)
# ✅ With only() - শুধু প্রয়োজনীয় fields
products = Product.objects.only('id', 'name', 'price')
# SELECT id, name, price FROM product
# ⚠️ সতর্কতা: অন্য field access করলে extra query হবে
for product in products:
print(product.name) # OK, no query
print(product.description) # Extra query!5️⃣ defer() – Exclude Specific Fields
# ✅ defer() - নির্দিষ্ট fields বাদ দিন
products = Product.objects.defer('description')
# SELECT id, name, price, ... (description ছাড়া)
# Large text fields বাদ দিতে useful
products = Product.objects.defer('description', 'long_text_field')6️⃣ annotate() – Add Calculated Fields
# ❌ Without annotate - প্রতিবার query
for product in products:
review_count = product.reviews.count() # Extra query!
# ✅ With annotate - একবারে calculate
products = Product.objects.annotate(
review_count=Count('reviews'),
avg_rating=Avg('reviews__rating'),
total_orders=Count('order_items')
)
for product in products:
print(product.review_count) # No extra query!
print(product.avg_rating) # No extra query!7️⃣ F() – Database-level Operations
# ❌ Python-level operation (slow)
product = Product.objects.get(pk=1)
product.stock = product.stock - 1
product.save()
# 2 queries: SELECT + UPDATE
# ✅ Database-level operation (fast)
Product.objects.filter(pk=1).update(stock=F('stock') - 1)
# 1 query: UPDATE product SET stock = stock - 1
# ✅ Atomic operation, race condition safe!8️⃣ Q() – Complex Queries
# ✅ OR conditions
products = Product.objects.filter(
Q(name__icontains='phone') | Q(description__icontains='phone')
)
# ✅ Complex conditions
products = Product.objects.filter(
Q(price__gte=100) & Q(price__lte=500) |
Q(is_featured=True)
)
# ✅ NOT condition
products = Product.objects.filter(
~Q(category__name='Electronics')
)9️⃣ Caching Strategies
# ✅ Strategy 1: Cache queryset results
cache_key = 'featured_products'
products = cache.get(cache_key)
if products is None:
products = list(Product.objects.filter(is_featured=True)[:10])
cache.set(cache_key, products, 300) # 5 minutes
# ✅ Strategy 2: Cache view with decorator
@cache_page(60 * 5) # 5 minutes
def my_view(request):
...
# ✅ Strategy 3: Cache template fragment
{% load cache %}
{% cache 300 sidebar %}
... expensive template code ...
{% endcache %}
# ✅ Strategy 4: Low-level cache
from django.core.cache import cache
def get_product_stats(product_id):
cache_key = f'product_stats_{product_id}'
stats = cache.get(cache_key)
if stats is None:
stats = calculate_stats(product_id)
cache.set(cache_key, stats, 600)
return stats🔟 Pagination
# ✅ Always paginate large querysets
from django.core.paginator import Paginator
products = Product.objects.all()
paginator = Paginator(products, 20) # 20 per page
page_obj = paginator.get_page(page_number)
# Template এ:
# {% for product in page_obj %}
# ...
# {% endfor %}🧪 Views Performance Test
test_views_performance.py:
import os
import django
import time
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'optimization_project.settings')
django.setup()
from django.test.utils import override_settings
from django.db import connection, reset_queries
from shop.models import Product
@override_settings(DEBUG=True)
def test_views_queries():
print("=" * 60)
print("🔴 Testing NORMAL View Queries")
print("=" * 60)
reset_queries()
start = time.time()
# Normal query (N+1 problem)
products = Product.objects.all()[:100]
for product in products:
_ = product.category.name
_ = product.brand.name
end = time.time()
print(f"Queries: {len(connection.queries)}")
print(f"Time: {end - start:.3f}s")
print("\n" + "=" * 60)
print("✅ Testing OPTIMIZED View Queries")
print("=" * 60)
reset_queries()
start = time.time()
# Optimized query
products = Product.objects.select_related('category', 'brand')[:100]
for product in products:
_ = product.category.name
_ = product.brand.name
end = time.time()
print(f"Queries: {len(connection.queries)}")
print(f"Time: {end - start:.3f}s")
if __name__ == '__main__':
test_views_queries()এটি Part 3 শেষ। পরবর্তী Part এ API Optimization ও Django Bolt দেখব।
পরবর্তী Part এ থাকবে:
- Normal API vs Optimized API
- Django Bolt integration
- API caching strategies
- Serializer optimization
চালিয়ে যাব?