59.4 Views: Function-Based and Class-Based Views
Right, let’s talk about views. This is where your application stops being a collection of models and templates and starts actually doing something. It’s the waiter who takes your order (the request), runs back to the kitchen (the models), gets your food, and brings it back to you (the response). And just like in a restaurant, you can have a single, overworked waiter doing everything (a function-based view, or FBV), or you can have a whole team with a system, where one person takes the drink order and another brings the food (a class-based view, or CBV).
I’m going to let you in on a secret that most tutorials bury in the third act: you can use both. The Django police will not kick your door in. The key is knowing when to use which. Let’s meet them.
The Humble Function-Based View
This is where everyone starts. It’s straightforward, explicit, and you can see the entire request-response cycle laid out in one function. It’s my go-to for one-off, simple views or when the logic is just too weird to fit into a class structure.
# views.py
from django.shortcuts import render, get_object_or_404
from django.http import HttpResponse
from .models import BlogPost
def blog_post_detail(request, slug):
# 1. Get the object (or show a 404 if it doesn't exist)
post = get_object_or_404(BlogPost, slug=slug, status='published')
# 2. Any custom logic you can dream up
if request.user != post.author:
post.view_count += 1
post.save()
# 3. Render a template with the object
return render(request, 'blog/detail.html', {'post': post})
See? No magic. A request comes in, you write some Python, you return a response. The pitfall here is the “any custom logic you can dream up” part. It’s a siren song. This function can quickly turn into a 200-line monstrosity handling GET, POST, AJAX requests, and what feels like nuclear launch codes. When that happens, it’s time to consider…
The Powerful, Sometimes-Magical Class-Based View
CBVs are designed to handle common patterns. Instead of writing a long function with a bunch of if request.method == 'GET' statements, you structure your code by placing it into specific methods of a class. Django provides a rich hierarchy of classes to inherit from, each handling more of the boilerplate for you.
The most common one is ListView. Want to display a list of objects? Don’t write the loop yourself.
# views.py
from django.views.generic import ListView
from .models import BlogPost
class BlogPostListView(ListView):
model = BlogPost
context_object_name = 'post_list' # defaults to 'object_list'. This is better.
template_name = 'blog/list.html' # defaults to 'blogpost_list.html'. Lame.
paginate_by = 10
def get_queryset(self):
# Override the default queryset to only show published posts
return BlogPost.objects.filter(status='published').order_by('-publish_date')
Boom. Pagination built-in. The template gets a page_obj for navigation. You just defined the queryset and the template. The rest—the part that’s identical in every list view—is handled for you. It’s less code and more structured.
For detail views, there’s DetailView. It handles the get_object_or_404 call for you.
# views.py
from django.views.generic import DetailView
from .models import BlogPost
class BlogPostDetailView(DetailView):
model = BlogPost
context_object_name = 'post' # defaults to 'object'. Seriously, change this.
def get_object(self, queryset=None):
# Hook into the object retrieval to count the view
obj = super().get_object(queryset)
if self.request.user != obj.author:
obj.view_count += 1
obj.save()
return obj
Now the view-counting logic is neatly contained in the get_object method, which is the method responsible for, you guessed it, getting the object.
The Gotcha: When to Use What
Here’s my rule of thumb, forged in the fires of debugging overly complex CBVs:
- Use FBVs when the view is simple, unique, or does something that doesn’t cleanly map to a CRUD operation (like a complex data processing endpoint). The explicit nature wins.
- Use CBVs for the standard stuff: lists of things, details of a thing, creating/editing forms. They reduce boilerplate and enforce good structure.
The biggest pitfall with CBVs is not understanding the inheritance chain. Methods like get_context_data, get_queryset, and get_form are your hooks for customization. Don’t just copy-paste a CBV and hope it works; open up the Django source code (django/views/generic/) and look at it. It’s just Python. It’s not that scary. Well, django/views/generic/base.py is a little scary, but in a “wow, that’s clever” way.
The designers made a questionable choice by making the default context names object and object_list. They are terrible. Always set context_object_name to something meaningful. Your front-end developer (which might also be you) will thank you.
Ultimately, mastering views is about understanding the request-response cycle and then picking the tool—FBV or CBV—that makes your implementation of that cycle the cleanest and most maintainable. Now go wire one to a URL; we’ve got work to do.