Free Ebook cover Django Fundamentals: From First App to a Complete Backend

Django Fundamentals: From First App to a Complete Backend

New course

12 pages

Views, Templates (Briefly), and the Request Lifecycle for Backend Pages

Capítulo 6

Estimated reading time: 8 minutes

+ Exercise

The request lifecycle for backend pages

When a staff user visits an internal page (for example, /dashboard/), Django processes the request through a predictable pipeline. Understanding this flow helps you structure views cleanly and debug issues quickly.

What happens from URL to response

  • Browser sends an HTTP request (method like GET/POST, headers, cookies, body).
  • Django resolves the URL to a view callable (function-based view or class-based view).
  • Middleware runs (before and after the view). Common middleware handles sessions, authentication, CSRF protection, security headers, etc.
  • The view executes: reads request data, performs business logic (often via ORM queries), and returns an HttpResponse (HTML, redirect, 404, etc.).
  • Template rendering (if used): the view passes context data to a template to produce HTML.
  • Response goes back to the browser.

For backend pages, your view is typically responsible for: (1) authorization checks, (2) retrieving data, (3) validating input on POST, and (4) returning an HTML response or redirect.

Views for backend pages: function-based patterns

A view is a Python callable that takes a request and returns a response. For internal dashboards, function-based views (FBVs) are often straightforward and explicit.

A minimal HTML response

from django.http import HttpResponse

def health_check(request):
    return HttpResponse("OK")

This is useful for simple endpoints, but most backend pages should render templates so you can keep HTML out of Python.

Handling GET vs POST in one view

A common backend pattern is: GET shows a page (form, list, detail), POST processes an action (create/update/delete) and then redirects.

Continue in our app.

You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.

Or continue reading below...
Download App

Download the app

from django.shortcuts import render, redirect
from django.contrib import messages

def settings_page(request):
    if request.method == "POST":
        # validate and save settings
        # ...
        messages.success(request, "Settings updated")
        return redirect("settings")

    # GET: show current settings
    context = {"timezone": "UTC"}
    return render(request, "backoffice/settings.html", context)

Key idea: after a successful POST, redirect to a GET page (Post/Redirect/Get) to avoid duplicate submissions on refresh.

Core shortcuts you will use constantly

render(): return HTML from a template

render(request, template_name, context) loads a template, renders it with context data, and returns an HttpResponse.

from django.shortcuts import render

def dashboard(request):
    context = {
        "active_users": 128,
        "failed_jobs": 3,
    }
    return render(request, "backoffice/dashboard.html", context)

redirect(): send the user elsewhere

redirect() returns an HTTP 302 (or 301 if configured) to a URL or URL pattern name.

from django.shortcuts import redirect

def go_to_dashboard(request):
    return redirect("dashboard")  # uses URL name

get_object_or_404(): fetch or return 404

Backend pages often show a detail view for an object by ID. If the object does not exist, you want a proper 404 instead of a server error.

from django.shortcuts import get_object_or_404, render
from .models import Ticket

def ticket_detail(request, ticket_id):
    ticket = get_object_or_404(Ticket, pk=ticket_id)
    return render(request, "backoffice/tickets/detail.html", {"ticket": ticket})

This keeps your code clean and avoids manual try/except blocks for common “not found” cases.

Common backend page patterns

1) List pages (tables, queues, worklists)

List pages show many objects, often with basic filtering and ordering. Keep the view focused on: reading query parameters, building a queryset, and rendering.

from django.shortcuts import render
from .models import Ticket

def ticket_list(request):
    status = request.GET.get("status")  # e.g. open/closed

    qs = Ticket.objects.all().order_by("-created_at")
    if status:
        qs = qs.filter(status=status)

    context = {
        "tickets": qs,
        "selected_status": status,
    }
    return render(request, "backoffice/tickets/list.html", context)

Practical tips for list pages:

  • Prefer query parameters for filters (GET) so the URL is shareable.
  • Keep filtering logic readable; if it grows, move it into a helper function or a service layer.
  • Don’t do heavy computation in templates; compute derived values in Python and pass them via context.

2) Detail pages (inspect one record)

Detail pages typically load one object and show related data. Use select_related/prefetch_related when you know you’ll need related objects to reduce database queries.

from django.shortcuts import get_object_or_404, render
from .models import Ticket

def ticket_detail(request, ticket_id):
    ticket = get_object_or_404(
        Ticket.objects.select_related("assignee"),
        pk=ticket_id,
    )
    return render(request, "backoffice/tickets/detail.html", {"ticket": ticket})

3) Simple dashboards (metrics + recent activity)

A dashboard view often combines a few small queries and aggregates. The goal is to keep it fast and predictable.

from django.db.models import Count
from django.shortcuts import render
from .models import Ticket

def dashboard(request):
    counts = (
        Ticket.objects.values("status")
        .annotate(total=Count("id"))
        .order_by()
    )
    recent = Ticket.objects.order_by("-created_at")[:10]

    context = {
        "counts": list(counts),
        "recent_tickets": recent,
    }
    return render(request, "backoffice/dashboard.html", context)

Step-by-step: build a “detail + action” backend page (GET shows, POST updates)

This pattern is common in internal tools: a staff user opens a record, then performs an action (change status, assign user, add internal note).

Step 1: Define the view with GET and POST branches

from django.contrib import messages
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_http_methods
from .models import Ticket

@require_http_methods(["GET", "POST"])
def ticket_close(request, ticket_id):
    ticket = get_object_or_404(Ticket, pk=ticket_id)

    if request.method == "POST":
        ticket.status = "closed"
        ticket.save(update_fields=["status"])
        messages.success(request, "Ticket closed")
        return redirect("ticket-detail", ticket_id=ticket.id)

    return render(request, "backoffice/tickets/confirm_close.html", {"ticket": ticket})

Notes:

  • @require_http_methods prevents unexpected methods from hitting your logic.
  • POST performs the mutation and then redirects to the detail page.
  • messages is a convenient way to show feedback on the next page load.

Step 2: Create a confirmation template (brief, functional)

<!-- backoffice/tickets/confirm_close.html -->
{% extends "backoffice/base.html" %}

{% block content %}
  <h1>Close ticket #{{ ticket.id }}</h1>
  <p>Are you sure you want to close “{{ ticket.title }}”?</p>

  <form method="post">
    {% csrf_token %}
    <button type="submit">Confirm close</button>
    <a href="{% url 'ticket-detail' ticket.id %}">Cancel</a>
  </form>
{% endblock %}

Even for internal pages, keep CSRF protection enabled for POST forms.

Templates (briefly): structure, context, and safe output

Template inheritance: consistent layout for internal pages

Backend UIs usually share navigation, a header, and a content area. Template inheritance avoids duplication.

<!-- backoffice/base.html -->
<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Backoffice</title>
  </head>
  <body>
    <nav>
      <a href="{% url 'dashboard' %}">Dashboard</a>
      <a href="{% url 'ticket-list' %}">Tickets</a>
    </nav>

    <main>
      {% if messages %}
        <ul>
          {% for message in messages %}
            <li>{{ message }}</li>
          {% endfor %}
        </ul>
      {% endif %}

      {% block content %}{% endblock %}
    </main>
  </body>
</html>

Child templates extend the base and fill in {% block content %}.

Context data: what you pass from the view

Context is a dictionary of values made available to the template. Keep it explicit and predictable.

def ticket_list(request):
    qs = Ticket.objects.all()
    return render(request, "backoffice/tickets/list.html", {
        "tickets": qs,
        "page_title": "Tickets",
    })

In the template:

<h1>{{ page_title }}</h1>
<ul>
  {% for t in tickets %}
    <li>
      <a href="{% url 'ticket-detail' t.id %}">#{{ t.id }} — {{ t.title }}</a>
    </li>
  {% empty %}
    <li>No tickets found.</li>
  {% endfor %}
</ul>

Safe output practices: escaping and when to be careful

Django templates escape variables by default, which helps prevent XSS when displaying user-provided content.

  • Safe by default: {{ ticket.title }} is HTML-escaped.
  • Avoid marking content safe unless you trust it: using |safe can render raw HTML and introduce XSS if the content is not sanitized.
  • Prefer plain text fields for internal notes unless you have a clear sanitization strategy.

Example of what to avoid unless the content is sanitized:

{{ ticket.description|safe }}

Choosing response types for backend pages

GoalTypical responseCommon helper
Show a pageHTMLrender()
After successful POSTRedirect to a GET URLredirect()
Object not found404 pageget_object_or_404()
Invalid method405 Method Not Allowed@require_http_methods

Practical checklist for clean backend views

  • Keep views thin: fetch data, validate input, call domain logic, return a response.
  • Use GET for reading, POST for changing: avoid mutations on GET endpoints.
  • Redirect after POST: prevents accidental resubmission.
  • Use get_object_or_404(): consistent behavior for missing records.
  • Pass explicit context: templates should not guess or compute heavy logic.
  • Rely on default escaping: be cautious with |safe and HTML-rich fields.

Now answer the exercise about the content:

In a Django backend page that handles both GET and POST in one view, what is the recommended behavior after a successful POST to prevent duplicate submissions on refresh?

You are right! Congratulations, now go to the next page

You missed! Try again.

After a successful POST, redirecting to a GET page (Post/Redirect/Get) avoids accidental duplicate form submissions if the user refreshes.

Next chapter

Django Forms and Validation for Reliable Data Entry

Arrow Right Icon
Download the app to earn free Certification and listen to the courses in the background, even with the screen off.