Class-Based Views vs Function-Based Views in 2022
django python
The CBV vs FBV debate never dies in Django circles. Years into Django development, here’s my updated perspective on when to use each.
The Basics
Function-Based View
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from .models import Article
from .forms import ArticleForm
@login_required
def article_detail(request, pk):
article = get_object_or_404(Article, pk=pk)
return render(request, 'articles/detail.html', {'article': article})
@login_required
def article_create(request):
if request.method == 'POST':
form = ArticleForm(request.POST)
if form.is_valid():
article = form.save(commit=False)
article.author = request.user
article.save()
return redirect('article_detail', pk=article.pk)
else:
form = ArticleForm()
return render(request, 'articles/form.html', {'form': form})
Class-Based View
from django.views.generic import DetailView, CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy
from .models import Article
from .forms import ArticleForm
class ArticleDetailView(LoginRequiredMixin, DetailView):
model = Article
template_name = 'articles/detail.html'
context_object_name = 'article'
class ArticleCreateView(LoginRequiredMixin, CreateView):
model = Article
form_class = ArticleForm
template_name = 'articles/form.html'
def form_valid(self, form):
form.instance.author = self.request.user
return super().form_valid(form)
def get_success_url(self):
return reverse_lazy('article_detail', kwargs={'pk': self.object.pk})
When to Use FBVs
Simple, Unique Logic
@login_required
def dashboard(request):
"""Complex dashboard with multiple data sources."""
user = request.user
context = {
'recent_articles': Article.objects.filter(author=user)[:5],
'pending_reviews': Review.objects.filter(reviewer=user, status='pending'),
'notifications': Notification.objects.filter(user=user, read=False),
'stats': calculate_user_stats(user),
}
return render(request, 'dashboard.html', context)
This doesn’t fit any generic pattern. FBV is clearer.
API Endpoints
from django.http import JsonResponse
from django.views.decorators.http import require_GET
@require_GET
def api_search(request):
query = request.GET.get('q', '')
if len(query) < 3:
return JsonResponse({'error': 'Query too short'}, status=400)
results = Article.objects.filter(title__icontains=query)[:10]
return JsonResponse({
'results': [
{'id': a.id, 'title': a.title}
for a in results
]
})
For simple API responses, FBVs are direct.
When You Need Explicit Control Flow
def checkout(request):
"""Multi-step checkout with complex flow."""
cart = get_cart(request)
if not cart.items.exists():
messages.warning(request, "Your cart is empty")
return redirect('cart')
if request.method == 'POST':
form = CheckoutForm(request.POST)
if form.is_valid():
# Payment processing
payment_result = process_payment(form.cleaned_data, cart)
if payment_result.success:
order = create_order(cart, form.cleaned_data)
clear_cart(request)
send_confirmation_email(order)
return redirect('order_confirmation', order_id=order.id)
else:
messages.error(request, payment_result.error_message)
else:
form = CheckoutForm(initial=get_user_defaults(request.user))
return render(request, 'checkout.html', {
'form': form,
'cart': cart,
})
The control flow is explicit and readable.
When to Use CBVs
CRUD Operations
class ArticleListView(ListView):
model = Article
paginate_by = 20
ordering = ['-created_at']
class ArticleDetailView(DetailView):
model = Article
class ArticleCreateView(LoginRequiredMixin, CreateView):
model = Article
fields = ['title', 'content']
class ArticleUpdateView(LoginRequiredMixin, UserPassesTestMixin, UpdateView):
model = Article
fields = ['title', 'content']
def test_func(self):
return self.get_object().author == self.request.user
class ArticleDeleteView(LoginRequiredMixin, UserPassesTestMixin, DeleteView):
model = Article
success_url = reverse_lazy('article_list')
def test_func(self):
return self.get_object().author == self.request.user
Standard CRUD? CBVs handle boilerplate.
Shared Behavior via Mixins
class TenantMixin:
"""Filter queryset by current tenant."""
def get_queryset(self):
return super().get_queryset().filter(tenant=self.request.tenant)
class AuditMixin:
"""Log all modifications."""
def form_valid(self, form):
response = super().form_valid(form)
log_action(self.request.user, self.object, 'modified')
return response
class ArticleUpdateView(LoginRequiredMixin, TenantMixin, AuditMixin, UpdateView):
model = Article
fields = ['title', 'content']
Mixins compose behavior elegantly.
Reusable Patterns
class FilteredListView(ListView):
"""Base for filterable lists."""
filter_class = None
def get_queryset(self):
qs = super().get_queryset()
if self.filter_class:
self.filter = self.filter_class(self.request.GET, queryset=qs)
return self.filter.qs
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = getattr(self, 'filter', None)
return context
# Usage
class ArticleListView(FilteredListView):
model = Article
filter_class = ArticleFilter
paginate_by = 20
Define once, reuse everywhere.
The 2022 Reality
DRF Changed the Conversation
With Django REST Framework, ViewSets are standard:
class ArticleViewSet(viewsets.ModelViewSet):
queryset = Article.objects.all()
serializer_class = ArticleSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
def perform_create(self, serializer):
serializer.save(author=self.request.user)
For APIs, this is the way.
Async Views Work with Both
# Async FBV
async def async_dashboard(request):
articles = await sync_to_async(list)(Article.objects.filter(author=request.user)[:5])
return render(request, 'dashboard.html', {'articles': articles})
# Async CBV
class AsyncArticleView(View):
async def get(self, request, pk):
article = await sync_to_async(get_object_or_404)(Article, pk=pk)
return render(request, 'article.html', {'article': article})
Both work. Choose based on other factors.
My Rules
| Situation | Choice | Reason |
|---|---|---|
| CRUD for a model | CBV | Built-in behavior |
| Unique complex logic | FBV | Explicit control |
| Shared behavior needed | CBV + Mixins | Composition |
| Quick API endpoint | FBV | Simplicity |
| REST API | DRF ViewSet | Standard for APIs |
| Learning Django | FBV first | Understand the basics |
Final Thoughts
The debate is less heated than it was. Both have their place:
- FBVs: When you want explicit, readable control flow
- CBVs: When you want reusable, composable behavior
Use both. Let the use case decide.
The best view is the one that’s easy to understand.