Turn a request into a response. Write function-based views, handle GET vs POST, then let Django’s class-based generic views do the repetitive work of listing and editing objects.
Why: a view is just a function that takes a request and returns a response. The simplest returns plain text with HttpResponse. Where: blog/views.py. Every view’s first argument is the request object.
# blog/views.py
from django.http import HttpResponse
def hello(request):
return HttpResponse("Hello from the blog!")Why: real views return HTML built from a template plus a context dict — the bridge between your data and the page. Note: render(request, template_name, context) is the shortcut you will use most. The Templates lesson covers the HTML side.
from django.shortcuts import render, get_object_or_404
from .models import Post
def post_list(request):
posts = Post.objects.filter(status="published")
return render(request, "blog/post_list.html", {"posts": posts})
def post_detail(request, slug):
# 404 instead of a crash when the slug doesn't exist
post = get_object_or_404(Post, slug=slug)
return render(request, "blog/post_detail.html", {"post": post})When GET: show the page. When POST: process a submitted form. Note: check request.method to branch, and always redirect after a successful POST so a browser refresh does not resubmit the form (the Post/Redirect/Get pattern).
from django.shortcuts import render, redirect
def post_create(request):
if request.method == "POST":
title = request.POST["title"]
post = Post.objects.create(title=title, slug=slugify(title))
return redirect("blog:post_detail", slug=post.slug)
# GET → show the empty form
return render(request, "blog/post_form.html")Why: a class-based view splits the HTTP methods into methods you override — get() and post() — which is cleaner than a long if/else once views grow. Note: .as_view() in the URLConf turns the class into a callable view.
# blog/views.py
from django.views import View
from django.shortcuts import render, redirect
class PostCreateView(View):
def get(self, request):
return render(request, "blog/post_form.html")
def post(self, request):
post = Post.objects.create(title=request.POST["title"])
return redirect(post.get_absolute_url())
# blog/urls.py
# path("new/", views.PostCreateView.as_view(), name="post_create")Why: listing all objects and showing one are so common Django ships ready-made views. Note: ListView passes the rows to the template as object_list (override with context_object_name); DetailView looks up one object by pk or slug from the URL.
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"
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(status="published")
class PostDetailView(DetailView):
model = Post # looks up by <slug> or <int:pk>
template_name = "blog/post_detail.html"Why: CreateView and UpdateView build a form from your model, validate it, and save — no manual form handling. DeleteView shows a confirm page then deletes. Note: fields lists the editable columns, and success_url is where the user lands afterwards.
from django.urls import reverse_lazy
from django.views.generic import CreateView, UpdateView, DeleteView
class PostCreateView(CreateView):
model = Post
fields = ["title", "slug", "body", "status"]
template_name = "blog/post_form.html"
class PostUpdateView(UpdateView):
model = Post
fields = ["title", "body", "status"]
template_name = "blog/post_form.html"
class PostDeleteView(DeleteView):
model = Post
success_url = reverse_lazy("blog:post_list")