Views, URLs & templates
Every Django page follows the same path: a URL pattern matches, a view function runs, it fetches data and returns a rendered template. Get comfortable with this cycle and the rest of the framework slots around it.
The view
A view takes a HttpRequest and returns a HttpResponse. render() does the common case — load a template, fill it
with a context dictionary, return HTML.
# shop/views.py
from django.core.paginator import Paginator
from django.shortcuts import render, get_object_or_404
from .models import Product
def product_list(request):
q = request.GET.get("q", "").strip()
products = Product.objects.filter(in_stock=True).select_related("category")
if q:
products = products.filter(name__icontains=q)
page = Paginator(products, 12).get_page(request.GET.get("page"))
return render(request, "shop/list.html", {"page": page, "q": q})
def product_detail(request, slug):
product = get_object_or_404(Product, slug=slug, in_stock=True)
return render(request, "shop/detail.html", {"product": product})
get_object_or_404 returns a clean 404 instead of an exception page when the slug does not exist.
URLs
Give each app its own urls.py, name every pattern, and set app_name so names are namespaced.
# shop/urls.py
from django.urls import path
from . import views
app_name = "shop"
urlpatterns = [
path("", views.product_list, name="product_list"),
path("p/<slug:slug>/", views.product_detail, name="product_detail"),
]
# config/urls.py
from django.urls import include, path
urlpatterns = [path("shop/", include("shop.urls"))]
| Converter | Matches | Example |
|---|---|---|
str |
Any text without a slash (default) | <str:city> |
int |
Digits, passed as an int |
<int:pk> |
slug |
Letters, numbers, hyphens, underscores | <slug:slug> |
uuid |
A UUID | <uuid:token> |
path |
Text including slashes | <path:subpath> |
Never hard-code a URL. Use reverse("shop:product_detail", args=[slug]) in Python and
{% url 'shop:product_detail' p.slug %} in templates, so changing a route breaks nothing.
Templates
Put shared structure in a base template and extend it.
<!-- templates/base.html -->
<!doctype html>
<html lang="en">
<head><title>{% block title %}Kirana Store{% endblock %}</title></head>
<body>
<header>{% include "partials/nav.html" %}</header>
<main>{% block content %}{% endblock %}</main>
</body>
</html>
<!-- templates/shop/list.html -->
{% extends "base.html" %}
{% block title %}Products{% endblock %}
{% block content %}
<form method="get"><input name="q" value="{{ q }}" placeholder="Search"></form>
{% for p in page %}
<p>
<a href="{% url 'shop:product_detail' p.slug %}">{{ p.name }}</a>
— {{ p.category.name }} — Rs {{ p.price|floatformat:2 }}
</p>
{% empty %}
<p>No products matched "{{ q }}".</p>
{% endfor %}
{% if page.has_next %}<a href="?q={{ q }}&page={{ page.next_page_number }}">Next</a>{% endif %}
{% endblock %}
Useful filters: floatformat:2 for prices, date:"d M Y" for dates, truncatechars:80, default:"—", and
intcomma from django.contrib.humanize once you add it to INSTALLED_APPS.
The template language is deliberately limited — no arbitrary Python. Business logic belongs in the view or a model method, not in the page.
Escaping keeps you safe
Django auto-escapes variables, so a product name containing <script> renders as text. Only mark content safe when
you truly control it, and run any user-supplied HTML through a sanitiser first.
Common mistakes
- Querying inside the template loop (
{{ p.category.name }}withoutselect_related) and firing one query per row. - Hard-coded URLs like
/shop/p/kaju-katli/scattered through templates. - Forgetting
{% csrf_token %}inside a POST form. - Doing pagination in Python with slicing instead of
Paginator, and loading the whole table into memory. - Putting
{% load %}tags in the child template but forgetting them in the included partial.
Practice
Build a two-page mini catalogue: a list with search and 12-per-page pagination, and a detail page reached by
slug. Confirm with the Django debug toolbar or assertNumQueries that the list page runs a constant number of
queries no matter how many products it shows.