Views and URLs

URL routing with converters and namespaces, function and class-based views, and returning JSON from the same project.

URL configuration

# blog/urls.py
from django.urls import path
from . import views

app_name = "blog"          # namespace, used as blog:detail

urlpatterns = [
    path("", views.post_list, name="list"),
    path("<int:pk>/", views.post_detail, name="detail"),
    path("<slug:slug>/", views.post_by_slug, name="by-slug"),
    path("category/<slug:slug>/", views.category, name="category"),
]
# config/urls.py
from django.contrib import admin
from django.urls import include, path

urlpatterns = [
    path("admin/", admin.site.urls),
    path("blog/", include("blog.urls")),
    path("api/", include("api.urls")),
]
ConverterMatchesExample
strAny text except / (default)hello-world
intNon-negative integer42
slugLetters, digits, hyphens, underscoresmy-post
uuidFormatted UUIDa1b2c3d4-...
pathAny text including /a/b/c.txt

Function and class-based views

# blog/views.py
from django.http import Http404, JsonResponse
from django.shortcuts import get_object_or_404, render
from django.views.generic import DetailView, ListView
from .models import Post

def post_list(request):
    posts = Post.objects.select_related("author").filter(status="published")
    return render(request, "blog/post_list.html", {"posts": posts})

def post_detail(request, pk):
    post = get_object_or_404(Post, pk=pk)      # 404 instead of DoesNotExist
    return render(request, "blog/post_detail.html", {"post": post})

def post_json(request, pk):
    post = get_object_or_404(Post, pk=pk)
    return JsonResponse({"id": post.pk, "title": post.title})

class PostDetailView(DetailView):
    model = Post
    template_name = "blog/post_detail.html"
    context_object_name = "post"

    def get_queryset(self):
        return Post.objects.select_related("author").filter(status="published")

class PublishedListView(ListView):
    queryset = Post.objects.filter(status="published")
    paginate_by = 20
    context_object_name = "posts"
# blog/models.py
from django.db import models
from django.urls import reverse

class Post(models.Model):
    ...
    def get_absolute_url(self):
        return reverse("blog:detail", kwargs={"pk": self.pk})
  • A view receives an HttpRequest and returns an HttpResponse (or raises an exception).
  • Generic views (ListView, DetailView, CreateView) are worth it once you use pagination, forms and 404 handling; plain functions are clearer below that threshold.
  • Build links with {% url 'blog:detail' pk=post.pk %} or reverse() so renaming a path does not break templates.

Status codes and errors

from django.http import JsonResponse
from django.views.decorators.http import require_POST

@require_POST
def create_comment(request, pk):
    post = get_object_or_404(Post, pk=pk)
    text = request.POST.get("text", "").strip()
    if not text:
        return JsonResponse({"error": "text is required"}, status=400)
    comment = post.comment_set.create(text=text)
    return JsonResponse({"id": comment.pk}, status=201)

def handler404(request, exception):
    return render(request, "404.html", status=404)
💡
Django only uses handler404 and handler500 when DEBUG = False. Test your 404 page with the debug flag off, otherwise you will discover the undefined template name in production.

FAQ

Why does my URL not match?
Check the trailing slash — APPEND_SLASH redirects a GET from /blog/1 to /blog/1/ but a POST body is lost on a 301. Declare the slash you actually want and be consistent.
Function or class-based view?
Functions for simple, explicit logic; class-based generic views when you need the built-in pagination, form and object lookup machinery. You can mix both freely in one app.

Project layout and the ORM The admin and forms

Last refreshed 2026-09-18.