RizTech Academy logo
RizTech Academy
Views, Templates and FormsLesson 1 of 630 min

Function-based views and URLs

A view is the code that runs when a URL is requested — the step 4 in the request cycle where your logic lives. Django offers two styles: function-based views (a plain Python function) and class-based views (the next-but-one lesson). We start with function views because they are explicit — you can see exactly what happens — and because everything a class-based view does, a function view does visibly. This lesson writes real views for Nidaan and wires them to URLs.

A view is a function: request in, response out

The contract from the request-cycle lesson, made concrete. A view takes an HttpRequest and returns an HttpResponse:

# patients/views.py
from django.shortcuts import render
from .models import Patient


def patient_list(request):
    city = request.GET.get("city", "Pune")            # read a query parameter, default "Pune"
    patients = Patient.objects.filter(city=city).order_by("name")
    return render(request, "patients/patient_list.html", {"patients": patients, "city": city})

Three things happen: read input from the request, query the database with the ORM, and return a rendered response. request.GET.get("city", "Pune") reads ?city=… from the URL (with a default). render(...) is the workhorse — it takes the request, a template name, and a context dictionary, renders the template with that context, and returns an HttpResponse. Verified: this view returns HTTP 200 with the patients listed. render is the helper you will use in almost every view that produces a page.

Wiring a view to a URL

A view does nothing until a URL points at it. Each app owns a urls.py:

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

urlpatterns = [
    path("", views.patient_list, name="patient_list"),
    path("new/", views.patient_create, name="patient_create"),
]

And the project's root urls.py includes the app's, under a prefix:

# config/urls.py
from django.urls import path, include

urlpatterns = [
    path("admin/", admin.site.urls),
    path("patients/", include("patients.urls")),   # /patients/ and /patients/new/
]

So patient_list answers /patients/ and patient_create answers /patients/new/. Each path() takes a route, a view, and — importantly — a name. The name lets you refer to the URL by name elsewhere (redirect("patient_list"), {% url "patient_list" %} in templates) instead of hard-coding the path, so if the route changes, the name still resolves. Always name your URLs; hard-coded paths scattered through code and templates are a maintenance trap.

Capturing parameters from the URL

Many views need a value from the path — a specific patient's id, say. Path converters capture it:

# patients/urls.py
path("<int:patient_id>/", views.patient_detail, name="patient_detail"),
# patients/views.py
from django.shortcuts import get_object_or_404

def patient_detail(request, patient_id):        # the captured value arrives as an argument
    patient = get_object_or_404(Patient, id=patient_id)
    return render(request, "patients/patient_detail.html", {"patient": patient})

<int:patient_id> matches an integer in the URL and passes it to the view as patient_id. So /patients/7/ calls patient_detail(request, patient_id=7). The converter (int, str, slug, uuid) both matches and types the value. And get_object_or_404 is the idiom you will reach for constantly: fetch the object, or raise a 404 if it does not exist — far better than a bare Patient.objects.get(id=…), which would raise a DoesNotExist (a 500 error, the wrong response) when the id is missing. A missing record should be a 404, and get_object_or_404 gives you that for free.

Handling GET and POST in one view

A view that shows a form and processes its submission handles both HTTP methods, branching on request.method:

def patient_create(request):
    if request.method == "POST":
        form = PatientForm(request.POST)      # bind the submitted data
        if form.is_valid():
            patient = form.save()
            return redirect("patient_list")   # success: redirect
    else:
        form = PatientForm()                  # GET: an empty form
    return render(request, "patients/patient_form.html", {"form": form})

The pattern (which the forms and redirect lessons detail): on GET, show an empty form; on POST, validate the submitted data and either save-and-redirect or re-render with errors. Verified: a valid POST returns 302 (redirect) and creates the record; an invalid POST returns 200 (re-render) and does not. request.method branching is how one view serves both the display and the submission of a form.

Returning other kinds of response

render and redirect cover most pages, but a view can return any HttpResponse:

from django.http import HttpResponse, JsonResponse

def ping(request):
    return HttpResponse("ok")                          # plain text/HTML
def stats(request):
    return JsonResponse({"patients": Patient.objects.count()})   # JSON

JsonResponse serialises a dict to JSON (useful for simple endpoints, though the API module uses Django REST Framework for real APIs). The point stands: a view's job is to return an HttpResponse, and Django gives you helpers for the common kinds — HTML via render, a redirect via redirect, JSON via JsonResponse, or a hand-built HttpResponse for anything else.

Check your work

What a view is. A function taking HttpRequest and returning HttpResponse; it reads input, queries via the ORM, and returns a response. Verified: patient_list returns 200 with the data.

What render does. Takes the request, a template name, and a context dict; renders and returns an HttpResponse — the workhorse for pages.

Why URLs get a name. So you refer to them by name (redirect(...), {% url %}) instead of hard-coding paths — routes can change, names still resolve.

Capturing path parameters. <int:patient_id> matches and types a URL segment, passed to the view as an argument; get_object_or_404 fetches it or returns a proper 404 (not a 500).

GET/POST in one view. Branch on request.method: GET shows an empty form, POST validates and either redirects (verified 302, created) or re-renders with errors (verified 200, not created).

Other responses. A view can return any HttpResponse — JsonResponse for JSON, a plain HttpResponse, or a redirect.

Practice

  1. Write patient_list and wire it at /patients/; load it and confirm the list renders (200).
  2. Add a name to each URL, then use {% url "patient_list" %} in a template instead of the literal path; confirm it still works, then change the route and confirm the name still resolves.
  3. Add patient_detail with <int:patient_id> and get_object_or_404; visit a real id and a missing one, and confirm you get the detail page and a proper 404 respectively.
  4. Replace get_object_or_404 with Patient.objects.get(...) for a missing id and observe the 500 error; reason about why 404 is the correct response.
  5. Write patient_create handling GET and POST; confirm a valid POST redirects (302) and an invalid one re-renders (200).
  6. Add a stats view returning JsonResponse with a count; load it and read the JSON.

Official documentation

Next: templates and template inheritance.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship