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):

Python
# 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
]
Python
# 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:

Python
# 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:

Python
# 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"
Python
# 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()

Python
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 manual try/except Post.DoesNotExist block in every view that looks up a single object by ID.
  • Mounting an app's URLs directly in the project's urls.py instead of using include() — 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/DetailView by 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.