Views

Django Fundamentals — Views
Django Fundamentals
Course 1 · Chapter 3 · Views

👁️ Views

A view is the Python callable that urls.py hands a request to — the same role a FastAPI path operation function plays. The shape is different in two ways worth learning early: Django's request and response are their own classes rather than framework-injected parameters and auto-serialized return values, and Django gives you a choice between plain functions and classes for the view itself.

Function-Based Views (FBVs)

The simplest view is just a function that takes a request and returns a response:

# catalog/views.py from django.http import HttpResponse def book_list(request): return HttpResponse("Here are all the books.")

Compare that to the FastAPI shape you already know:

FastAPI Path Operation vs Django FBV

FastAPI
@app.get("/books") def book_list(): return {"books": []} # A plain dict is auto-serialized to a JSON response.
Django
from django.http import JsonResponse def book_list(request): return JsonResponse({"books": []}) # A dict must be explicitly wrapped in JsonResponse.

📨 The Request Object

Every view's first parameter is Django's HttpRequest — where FastAPI hands you individually typed parameters (path params, query params, a Pydantic body model), Django hands you one object and you pull what you need off it:

def search_books(request): query = request.GET.get("q", "") # query string: ?q=dune method = request.method # "GET", "POST", etc. if request.method == "POST": title = request.POST.get("title") # form-encoded POST data return HttpResponse(f"Searching for: {query}")

request.GET

A dict-like object of query string parameters — always present, empty on a request with no query string.

request.POST

Form-encoded POST body data — for JSON request bodies, you'd instead parse request.body yourself (Chapter 7 covers Forms properly).

request.method

One function often handles GET and POST for the same URL by branching on this — versus FastAPI's separate @app.get/@app.post decorators for the same path.

request.user

Once authentication middleware runs (Course 2), the logged-in user is attached here automatically — no manual dependency injection needed.

The Response Object

Every view must return something that is (or behaves like) an HttpResponse — Django provides several shortcuts for common cases:

from django.http import HttpResponse, JsonResponse from django.shortcuts import render, redirect def plain_text(request): return HttpResponse("Just some text", content_type="text/plain") def as_json(request): return JsonResponse({"status": "ok"}) def as_html(request): # render() combines a template + context dict + HttpResponse into one call — # the templating side of this is fully covered in Chapter 4. return render(request, "catalog/book_list.html", {"books": []}) def after_save(request): return redirect("book-list") # by URL name, per Chapter 2

🏗️ Class-Based Views (CBVs)

Django also lets a view be a class — instead of branching on request.method inside one function, each HTTP method gets its own class method:

Function-Based vs Class-Based, Same Route

Function-Based View
def book_form(request): if request.method == "POST": # handle form submission return redirect("book-list") # request.method == "GET" return render(request, "catalog/book_form.html")
Class-Based View
from django.views import View class BookFormView(View): def get(self, request): return render(request, "catalog/book_form.html") def post(self, request): # handle form submission return redirect("book-list")

Wiring a CBV into urls.py needs one extra step — .as_view() converts the class into something path() can call:

from . import views urlpatterns = [ path("new/", views.BookFormView.as_view(), name="book-create"), ]

Dispatch by Method Name

View's base class reads request.method internally and calls the matching method (get, post, put, ...) — no manual if/elif chain needed.

Generic CBVs (Preview)

Django ships ready-made CBVs like ListView and DetailView that implement common patterns almost entirely for you — a deep dive is coming in Course 2.

💻 Coding Challenges

Challenge 1: Write a GET/POST Function-Based View

Write a subscribe(request) FBV that returns an HTML form on GET, and on POST, reads an email field from request.POST and returns a plain-text confirmation via HttpResponse.

Goal: Practice branching on request.method and reading from request.POST.

→ Solution

Challenge 2: Convert It to a Class-Based View

Rewrite the subscribe view from Challenge 1 as a SubscribeView(View) class with separate get() and post() methods, and update the corresponding urls.py entry to use .as_view().

Goal: Practice the FBV-to-CBV conversion and the .as_view() wiring step.

→ Solution

Challenge 3: Return the Right Response Type

Write three tiny views: one returning plain text, one returning JSON, and one returning a redirect to a named URL — using HttpResponse, JsonResponse, and redirect() respectively.

Goal: Build muscle memory for Django's explicit response types, instead of assuming a return value gets auto-converted the way FastAPI would.

→ Solution

⚠️ Gotcha: Forgetting to Return an HttpResponse

In FastAPI, returning a dict, a list, or even None from a path operation function is handled gracefully — FastAPI serializes whatever you give it. Django is stricter: a view must return an HttpResponse (or a subclass like JsonResponse, or the result of render()/redirect(), which are HttpResponse under the hood). A view that falls through without an explicit return — often because an if branch handles POST but there's no matching return for GET — raises a very literal ValueError: The view didn't return an HttpResponse object. It returned None instead.

🎯 What's Next

Views now return real responses — the next chapter covers where the HTML in render() actually comes from: Templates, the Django Template Language, template inheritance, and how its auto-escaping compares to what you've seen already.