FBV vs CBV — Function and Class Based Views
Django supports both Function-Based Views (FBV) and Class-Based Views (CBV). Choose based on the situation.
Function-Based Views (FBV)
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
from django.contrib.auth.decorators import login_required
import json
from .models import Product
def product_list(request):
if request.method == "GET":
products = Product.objects.filter(is_active=True).values(
"id", "name", "price", "stock"
)
return JsonResponse(list(products), safe=False)
elif request.method == "POST":
data = json.loads(request.body)
product = Product.objects.create(owner=request.user, **data)
return JsonResponse({"id": product.id, "name": product.name}, status=201)
@require_http_methods(["GET", "POST"])
@login_required
def create_product(request):
data = json.loads(request.body)
product = Product.objects.create(owner=request.user, **data)
return JsonResponse({"id": product.id}, status=201)
# Custom decorator
from functools import wraps
def admin_required(view_func):
@wraps(view_func)
def wrapper(request, *args, **kwargs):
if not request.user.is_staff:
return JsonResponse({"error": "Admin access required"}, status=403)
return view_func(request, *args, **kwargs)
return wrapper
@admin_required
def admin_stats(request):
count = Product.objects.count()
return JsonResponse({"total_products": count})
Class-Based Views (CBV)
from django.views import View
from django.http import JsonResponse, HttpResponse
import json
from .models import Product
class ProductListView(View):
"""GET /products/ — list, POST /products/ — create"""
def get(self, request):
products = list(
Product.objects.filter(is_active=True).values("id", "name", "price")
)
return JsonResponse(products, safe=False)
def post(self, request):
data = json.loads(request.body)
product = Product.objects.create(owner=request.user, **data)
return JsonResponse({"id": product.id}, status=201)
class ProductDetailView(View):
"""GET/PUT/DELETE /products/{pk}/"""
def get_object(self, pk):
try:
return Product.objects.get(pk=pk)
except Product.DoesNotExist:
return None
def get(self, request, pk):
product = self.get_object(pk)
if not product:
return JsonResponse({"error": "Not found"}, status=404)
return JsonResponse({"id": product.id, "name": product.name, "price": float(product.price)})
def put(self, request, pk):
product = self.get_object(pk)
if not product:
return JsonResponse({"error": "Not found"}, status=404)
data = json.loads(request.body)
for key, value in data.items():
setattr(product, key, value)
product.save()
return JsonResponse({"id": product.id, "name": product.name})
def delete(self, request, pk):
product = self.get_object(pk)
if not product:
return JsonResponse({"error": "Not found"}, status=404)
product.delete()
return HttpResponse(status=204)
Generic CBV — Code Reuse
from django.views.generic import ListView, DetailView, CreateView, UpdateView, DeleteView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.urls import reverse_lazy
from .models import Product
from .forms import ProductForm
class ProductListView(ListView):
model = Product
template_name = "products/list.html"
context_object_name = "products"
paginate_by = 20
def get_queryset(self):
qs = super().get_queryset().filter(is_active=True)
q = self.request.GET.get("q")
if q:
qs = qs.filter(name__icontains=q)
return qs
class ProductCreateView(LoginRequiredMixin, CreateView):
model = Product
form_class = ProductForm
template_name = "products/form.html"
success_url = reverse_lazy("product-list")
def form_valid(self, form):
form.instance.owner = self.request.user
return super().form_valid(form)
class ProductUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Product
form_class = ProductForm
template_name = "products/form.html"
def test_func(self):
return self.get_object().owner == self.request.user
class ProductDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Product
template_name = "products/confirm_delete.html"
success_url = reverse_lazy("product-list")
def test_func(self):
return self.get_object().owner == self.request.user
Mixin Pattern
from django.views import View
from django.http import JsonResponse
class JSONResponseMixin:
def render_json(self, data, status=200):
return JsonResponse(data, status=status)
def render_error(self, message, status=400):
return JsonResponse({"error": message}, status=status)
class LoginRequiredMixin:
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
return JsonResponse({"error": "Login required"}, status=401)
return super().dispatch(request, *args, **kwargs)
class OwnerRequiredMixin:
def dispatch(self, request, *args, **kwargs):
obj = self.get_object()
if obj.owner != request.user:
return JsonResponse({"error": "Permission denied"}, status=403)
return super().dispatch(request, *args, **kwargs)
class ProductAPIView(JSONResponseMixin, LoginRequiredMixin, View):
def get(self, request, pk):
try:
product = Product.objects.get(pk=pk)
return self.render_json({"id": product.id, "name": product.name})
except Product.DoesNotExist:
return self.render_error("Product not found", status=404)
FBV vs CBV Decision Guide
Choose FBV (Function-Based)
✅ Simple single-purpose logic (webhooks, callbacks)
✅ Complex conditional branching
✅ Team is comfortable with FBV
✅ Prefer FBV APIView with DRF
Choose CBV (Class-Based)
✅ Repeating CRUD patterns (use generic views)
✅ Reuse common behavior via Mixins
✅ Consistency in large projects
Summary
| View Type | Best For |
|---|---|
| FBV + decorators | Simple logic, fast implementation |
CBV (View) | Clear HTTP method separation |
| Generic CBV | CRUD patterns (ListView, CreateView, etc.) |
| Mixin | Reusable features (auth, permissions, logging) |
In practice, DRF's APIView and ViewSet are the most commonly used patterns.