Skip to main content
Advertisement

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 TypeBest For
FBV + decoratorsSimple logic, fast implementation
CBV (View)Clear HTTP method separation
Generic CBVCRUD patterns (ListView, CreateView, etc.)
MixinReusable features (auth, permissions, logging)

In practice, DRF's APIView and ViewSet are the most commonly used patterns.

Advertisement