Django Views and URLs
URL routing, function-based vs class-based views, and HttpResponse vs render().
URL routing with urls.py
Django routes incoming requests through a urls.py module — a list of path() entries mapping a URL pattern to a view function (or class):
# mysite/urls.py (the project's root URL configuration)
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("blog/", include("blog.urls")), # delegate everything under /blog/ to the app's own urls.py
]
# blog/urls.py (the app's own URL configuration)
from django.urls import path
from . import views
urlpatterns = [
path("", views.post_list, name="post_list"),
path("<int:post_id>/", views.post_detail, name="post_detail"),
]
include() keeps each app's routes self-contained — the blog app owns its own URL structure, and the project just mounts it under a prefix. <int:post_id> works exactly like a Flask path converter: it captures that URL segment as an int and passes it to the view as an argument. The name="..." on each path lets you reference the URL elsewhere (in a template, or from another view) as {% url 'post_detail' post.id %} rather than hardcoding the literal path string.
Function-based views
A function-based view (FBV) is the simplest kind of Django view — a plain Python function that takes a request and returns a response:
# blog/views.py
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, render
from .models import Post
def post_list(request):
posts = Post.objects.filter(published=True)
return render(request, "blog/post_list.html", {"posts": posts})
def post_detail(request, post_id):
post = get_object_or_404(Post, id=post_id)
return render(request, "blog/post_detail.html", {"post": post})
Class-based views
A class-based view (CBV) expresses the same idea using a class, with Django's generic views providing a lot of common behavior (listing objects, showing a single object, form handling) for free:
# blog/views.py
from django.views.generic import ListView, DetailView
from .models import Post
class PostListView(ListView):
model = Post
template_name = "blog/post_list.html"
context_object_name = "posts"
queryset = Post.objects.filter(published=True)
class PostDetailView(DetailView):
model = Post
template_name = "blog/post_detail.html"
context_object_name = "post"
# blog/urls.py
from django.urls import path
from .views import PostListView, PostDetailView
urlpatterns = [
path("", PostListView.as_view(), name="post_list"),
path("<int:pk>/", PostDetailView.as_view(), name="post_detail"),
]
.as_view() is what actually turns the class into something urls.py can route to — it's a class method that returns a plain view function under the hood, so from Django's routing perspective, an FBV and a CBV look identical.
Function-based vs class-based: the same case, two ways
| Function-based view | Class-based view | |
|---|---|---|
| Shape | A plain function | A class, usually extending a Django generic view |
| Boilerplate for common patterns (list, detail, create) | You write it explicitly every time | Provided by ListView, DetailView, CreateView, etc. |
| Customizing one small piece of behavior | Easy — it's just code in a function | Override a specific method (e.g., get_queryset()) — very little code |
| Readability for simple, one-off logic | Often clearer — the whole flow is linear in one place | Can feel like "spooky action" until you know which method to override |
| Reuse across views | Requires manually extracting helper functions | Inherit and extend an existing class |
Neither is strictly better — many real Django codebases use FBVs for unusual, one-off logic and CBVs (or Django REST Framework's own class-based views) for standard CRUD-shaped endpoints where the generic views save real boilerplate.
HttpResponse vs render()
from django.http import HttpResponse
from django.shortcuts import render
def raw_response(request):
return HttpResponse("<h1>Hello, World!</h1>") # you build the string yourself
def templated_response(request):
return render(request, "blog/hello.html", {"name": "Ada"}) # Django renders a template file for you
HttpResponse is the lowest-level building block — every Django view ultimately returns some kind of HttpResponse (or a subclass, like JsonResponse or HttpResponseRedirect). render() is a shortcut that loads a template file, renders it with the given context dictionary, and wraps the result in an HttpResponse for you — it's what you reach for almost every time you're returning an HTML page.
Common mistakes
- Forgetting
get_object_or_404()and instead writing a manualtry/except Post.DoesNotExistblock in every view that looks up a single object by ID. - Mounting an app's URLs directly in the project's
urls.pyinstead of usinginclude()— this couples the project tightly to each app's internal URL structure instead of keeping apps self-contained. - Reaching for a class-based generic view when the logic doesn't actually match its shape — forcing unusual one-off logic into
ListView/DetailViewby overriding several methods is often more code than a plain function would have been.
Interview questions
Q: When would you choose a function-based view over a class-based view, or vice versa?
Function-based views tend to read more clearly for unusual, one-off logic where the whole request-handling flow benefits from being linear and explicit in one place. Class-based views (especially Django's generic views like ListView and DetailView) pay off for standard, repetitive CRUD-shaped patterns, where inheriting from a generic view and overriding one method (like get_queryset()) replaces boilerplate you'd otherwise write by hand in every similar view.
Q: What's the difference between HttpResponse and render()?
HttpResponse is the base response object every Django view ultimately returns — you construct its body yourself, whether that's a raw string, JSON, or anything else. render() is a convenience shortcut built on top of it: it loads a named template file, renders it against a context dictionary using Django's template engine, and returns the result already wrapped in an HttpResponse, which is why nearly every view that renders an HTML page uses render() rather than building an HttpResponse by hand.