Adding Layouts and Context¶
Goal¶
This part wraps every page in a shared layout, adds a detail page at /notes/<id>/, and uses inherit_context to publish data once for the entire page tree.
By the end the index links to each note and the detail page renders a single note pulled from the URL.
Prerequisites¶
You have finished Building the First Page.
The index page at / lists two seeded notes from the database.
Walkthrough¶
Add a Root Layout¶
A layout.djx placed in any directory wraps every page below it.
For the Notes application the most common layout sits next to the page tree at notes/pages/layout.djx.
<!doctype html>
<html>
<head>
<title>{{ site_name }}</title>
</head>
<body>
<header>
<a href="{% url 'next:page_' %}"><h1>{{ site_name }}</h1></a>
<p>{{ tagline }}</p>
</header>
<main>
{% block template %}{% endblock template %}
</main>
</body>
</html>
Writing the {% block template %} placeholder explicitly is the recommended practice because it controls where the page body lands.
The framework substitutes the body of each page into that block.
When a layout omits the block, the framework still wraps the page body in a {% block template %} block so an ancestor layout’s placeholder stays valid.
Reverse names such as next:page_ come from the file router.
See File Router for how directories become URLs and URL Reversing for helpers such as page_reverse.
Remove the now redundant HTML envelope from notes/pages/template.djx and keep only the page body.
<ul>
{% for note in notes %}
<li>
<a href="{% url 'next:page_notes_id' id=note.id %}">{{ note.title }}</a>
<small>{{ note.created_at|date:"Y-m-d H:i" }}</small>
</li>
{% endfor %}
</ul>
Refresh http://127.0.0.1:8000/ to confirm that the layout renders the title and the list now uses anchor tags.
Add the Detail Page¶
Create a new directory notes/pages/notes/[id]/.
The bracketed segment is a URL parameter that the file router captures as id.
The untyped [id] segment matches any string, and DUrl["id", int] in the page module coerces it to an integer.
The typed [int:id] directory form rejects non-numeric URLs at routing time before any page code runs, see File Router.
from django.shortcuts import get_object_or_404
from notes.models import Note
from next.pages import context
from next.urls import DUrl
@context("note")
def fetch_note(note_id: DUrl["id", int]) -> Note:
return get_object_or_404(Note, pk=note_id)
The DUrl["id", int] annotation is a DI marker (DI marker), an annotation in the type position that tells the framework where a value comes from.
Dependency injection means the framework fills a function’s parameters from their names and type annotations rather than from an explicit call.
See Dependency Injection for the full model.
Here the DUrl["id", int] marker tells the dependency injector to read the id segment captured by the [id] directory and coerce it to int.
The segment name is given explicitly because the parameter note_id differs from the captured segment.
The get_object_or_404() shortcut is the standard Django way to fetch a row or return a 404 response.
An id that matches no note returns Django’s standard 404 response.
See Customize 404 and 500 Pages for customising what the visitor sees.
Add the matching template.
<article>
<h2>{{ note.title }}</h2>
{% if note.body %}<p>{{ note.body }}</p>{% endif %}
<small>{{ note.created_at|date:"Y-m-d H:i" }}</small>
<p><a href="{% url 'next:page_' %}">Back to all notes</a></p>
</article>
Click a note from the index and confirm that the detail page renders the captured note.
The URL name next:page_notes_id reverses with a single keyword argument id and is generated from the directory shape.
Trace the Layout Stack¶
The detail page renders inside the same root layout because every ancestor layout.djx wraps every descendant page.
Drop a second layout inside notes/pages/notes/ to wrap only the detail pages.
<section>
<nav>
<a href="{% url 'next:page_' %}">All notes</a>
</nav>
{% block template %}{% endblock template %}
</section>
Reload /notes/1/ and you should see both layouts at once.
The root layout wraps the inner layout which wraps the detail template.
Composition works by folding each descendant body into the parent {% block template %} placeholder, so adding ancestor layouts never requires changes to the inner templates.
Use Counts Across Pages¶
Add a small bit of inherited context that the layout reads on every page.
Append the note_count function to the existing notes/pages/page.py and add from notes.models import Note at the top of the file.
@context("note_count", inherit_context=True)
def note_count() -> int:
return Note.objects.count()
Reference the count in the layout.
<header>
<a href="{% url 'next:page_' %}"><h1>{{ site_name }}</h1></a>
<p>{{ tagline }} ({{ note_count }} notes)</p>
</header>
Every page now shows the running count without any per-page wiring.
Checkpoint¶
Your project tree looks like this.
pages/
layout.djx
page.py
template.djx
notes/
layout.djx
[id]/
page.py
template.djx
The index links to each note.
The detail page renders a single note pulled from the URL.
Inherited context flows from the root page.py down to every page.
Common Pitfalls¶
- Layout shows but page body does not.
Add
{% block template %}{% endblock template %}to the innermostlayout.djxthat wraps the page. An ancestor layout that omits the block still wraps its descendants because the framework folds the inner layout into a{% block template %}block on the way out. The placeholder is mandatory only in the innermost layout that should host the page body.DUrlresolves toNonewhen the captured segment is missing.DUrl[T]reads the segment whose name matches the parameter and coerces the value toT. The supported types are documented in Dependency Injection.DUrl["name"]returns the captured segment in string form. When the Python parameter name differs from the segment, useDUrl["id", int]for an[id]directory.- Inherited context not available in a descendant.
Make sure the
page.pythat publishes the context sits in a directory above the page that consumes it, and the context function declaresinherit_context=True.
See Troubleshooting for the full catalog of errors and fixes.
Next Steps¶
Pages are still inline HTML. The next part extracts the note rendering into a reusable component with its own CSS and JS.
See also
Components and Static Assets adds components and static assets.
Layouts covers layout composition rules.
Context covers inherit_context and serialization options.