Chapter 8: Django Views
URLs wired up, and the server running. Now we’re diving into Django Views — the heart of what makes your site dynamic.
Think of views as the brain workers of your Django app:
- User visits a URL (e.g. /polls/)
- Django’s URL dispatcher says: “Hey, this path matches → call this view!”
- The view receives the request, does work (query DB, calculate something, check login…), and returns a response (HTML page, JSON, redirect, error, etc.)
Views are where the action happens — they’re Python callables (usually functions or classes) that take an HttpRequest and return an HttpResponse (or raise an exception like Http404).
In Django 6.0 (as of today), views work exactly like in 5.x for most cases — no massive breaking changes in views themselves. Async support is stronger (you can write async def views natively without wrappers), but we’ll start simple.
We’ll cover both styles step-by-step: Function-Based Views (FBVs) and Class-Based Views (CBVs) — with examples in your polls app.
1. Function-Based Views (FBVs) – The Simple & Beginner-Friendly Way
Most people (including the official tutorial) start here. It’s just a Python function.
Why FBVs first?
- Super readable — easy to see what happens from top to bottom
- No magic inheritance
- Perfect for learning logic flow
- Still used in 80%+ of real projects for custom/complex views
Example 1: Basic “Hello” View (what we already did)
In polls/views.py:
|
0 1 2 3 4 5 6 7 8 9 |
from django.http import HttpResponse def index(request): return HttpResponse("Hello, world. This is the polls index from a function view! 🚀") |
- request: The incoming HTTP request (contains GET/POST data, user info, headers…)
- Return HttpResponse: Sends HTML/text back
- URL mapping in polls/urls.py:
|
0 1 2 3 4 5 6 |
path('', views.index, name='index'), |
→ Visit /polls/ → see the message.
Example 2: Dynamic with Database (Real Power!)
Assume you have the Question model from earlier.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
from django.shortcuts import render from .models import Question def index(request): # Get latest 5 questions, newest first latest_questions = Question.objects.order_by('-pub_date')[:5] # Pass data to template context = { 'latest_questions': latest_questions, 'title': 'Latest Polls in Hyderabad!', } return render(request, 'polls/index.html', context) |
- render() shortcut: Loads template + adds context + handles request
- No manual HttpResponse(template.render(…)) — cleaner!
Create polls/templates/polls/index.html (namespaced folder!):
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
<!DOCTYPE html> <html> <head> <title>{{ title }}</title> </head> <body> <h1>{{ title }}</h1> {% if latest_questions %} <ul> {% for question in latest_questions %} <li>{{ question.question_text }}</li> {% endfor %} </ul> {% else %} <p>No polls yet. Create one in admin!</p> {% endif %} </body> </html> |
→ Refresh /polls/ → dynamic list from DB!
Example 3: Detail View with Error Handling
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
from django.shortcuts import get_object_or_404, render from .models import Question def detail(request, question_id): # Get question or raise 404 automatically question = get_object_or_404(Question, pk=question_id) return render(request, 'polls/detail.html', {'question': question}) |
In polls/urls.py:
|
0 1 2 3 4 5 6 |
path('<int:question_id>/', views.detail, name='detail'), |
- <int:question_id>: Captures number from URL (e.g. /polls/3/)
- get_object_or_404(): Clean — no try/except mess
polls/templates/polls/detail.html:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
<h1>{{ question.question_text }}</h1> <ul> {% for choice in question.choice_set.all %} <li>{{ choice.choice_text }}</li> {% endfor %} </ul> |
→ /polls/1/ → shows question + choices (if exists)
Example 4: Handling POST (Voting – Simple Version)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
from django.shortcuts import get_object_or_404, redirect, render from django.http import HttpResponseRedirect from django.urls import reverse from .models import Question, Choice def vote(request, question_id): question = get_object_or_404(Question, pk=question_id) try: selected_choice = question.choice_set.get(pk=request.POST['choice']) except (KeyError, Choice.DoesNotExist): # Redisplay form with error return render(request, 'polls/detail.html', { 'question': question, 'error_message': "You didn't select a choice.", }) # Increment vote (simple – no F() yet) selected_choice.votes += 1 selected_choice.save() # Redirect after POST (PRG pattern – prevents double submit) return HttpResponseRedirect(reverse('polls:results', args=(question.id,))) |
URL: path(‘<int:question_id>/vote/’, views.vote, name=’vote’),
- request.POST: Form data
- Always redirect after successful POST
2. Class-Based Views (CBVs) – When You Want Reusability
CBVs use classes + inheritance. Great for repeating patterns (list, detail, create…).
Why learn CBVs?
- Less code for common tasks (with generics)
- Mixins for custom behavior
- But: steeper learning curve at first
Simple CBV Example (TemplateView – No DB)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# polls/views.py from django.views.generic import TemplateView class AboutView(TemplateView): template_name = 'polls/about.html' # auto renders this def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) context['city'] = 'Hyderabad' context['year'] = 2026 return context |
URL: path(‘about/’, AboutView.as_view(), name=’about’),
Template: polls/templates/polls/about.html
|
0 1 2 3 4 5 6 7 |
<h1>About Us</h1> <p>Built in {{ city }}, {{ year }} with Django 6.0!</p> |
→ /polls/about/ → dynamic context!
Generic CBV Example (DetailView – Replaces our detail FBV)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 |
from django.views.generic import DetailView from .models import Question class QuestionDetailView(DetailView): model = Question template_name = 'polls/detail.html' # default would be polls/question_detail.html context_object_name = 'question' # default is 'object' – we rename pk_url_kwarg = 'question_id' # matches <int:question_id> |
URL: path(‘<int:question_id>/’, QuestionDetailView.as_view(), name=’detail’),
- Auto does get_object_or_404
- Much less code!
FBV vs CBV – Quick 2026 Comparison Table
| Aspect | Function-Based Views (FBV) | Class-Based Views (CBV) |
|---|---|---|
| Readability for beginners | ★★★★★ Very clear flow | ★★★☆☆ Need to know methods/parent classes |
| Custom logic | Easy – just write code | Override methods (get/post/get_context_data…) |
| Reusability | Copy-paste or decorators | Inheritance + mixins (very powerful) |
| Common tasks (CRUD) | Manual | Generic views (ListView, CreateView…) save tons |
| When to use in 2026 | Custom logic, APIs, simple pages | Admin-like interfaces, forms, lists/details |
| Async support | async def works great | async def get/post in custom classes |
My advice today: Start with FBVs (like the poll tutorial) → they teach you what is happening. Later switch to CBVs for DRY code.
Quick Recap & Homework
- View = request → logic → response
- FBV: def my_view(request): return render(…)
- CBV: class MyView(View): def get(self, request): …
- Always use shortcuts: render, get_object_or_404, redirect, reverse
Next?
- Want to add full voting + results views (FBV or CBV)?
- “Show me ListView + CreateView for questions”
- “How to make views login-required?”
- Or “I tried this code → error!”
Paste any code/error — we’ll debug together. You’re crushing it, boss! 🚀 Let’s build more!
