Today’s goal
Replace yesterday’s short HttpResponse with a proper HTML template. Then pass three sample posts from Python to that template.
Today’s posts are temporary dictionaries. That is intentional. You will move them into the database on Day 5.
Step 1 — Create the template folders
Inside the blog app, create this exact path:
blog/
└── templates/
└── blog/
└── index.html
The second blog folder is a namespace. It prevents this template from colliding with a template from another app later.
Step 2 — Add sample posts to the view
Replace blog/views.py with:
from django.shortcuts import render
def home(request):
posts = [
{
"title": "Learning Python one step at a time",
"description": "Small daily exercises make a new language feel familiar.",
},
{
"title": "My first Django page",
"description": "A URL, a view, and a template work together to make a page.",
},
{
"title": "Why HTML matters",
"description": "HTML gives every web page its structure.",
},
]
context = {
"site_title": "My First Blog",
"posts": posts,
}
return render(request, "blog/index.html", context)
The important connection is:
Python context dictionary → Django template
The key "posts" becomes a template variable named posts.
Step 3 — Build the HTML template
Add this to blog/templates/blog/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ site_title }}</title>
</head>
<body>
<header>
<h1>{{ site_title }}</h1>
<p>Notes about Python, Django, and learning to code.</p>
</header>
<main>
<h2>Latest posts</h2>
{% if posts %}
{% for post in posts %}
<article>
<h3>{{ post.title }}</h3>
<p>{{ post.description }}</p>
</article>
{% endfor %}
{% else %}
<p>No posts have been published yet.</p>
{% endif %}
</main>
</body>
</html>
Step 4 — Understand the template symbols
Use double braces to print a value:
{{ site_title }}
{{ post.title }}
Use brace-percent tags for template logic:
{% if posts %}
{% for post in posts %}
{% endfor %}
{% endif %}
post means one dictionary during the loop. post.title reads its "title" value.
Step 5 — Run and test
python manage.py runserver
Open http://127.0.0.1:8000/. You should see the site title followed by three posts.
Now test the empty state. Temporarily change this line in the view:
posts = []
Refresh and confirm that No posts have been published yet appears. Restore the three posts after the test.
Checkpoint
- The browser tab says My First Blog.
- Three post titles and descriptions appear.
- Changing
site_titlein the view changes the template. - An empty
postslist shows the empty message. - You can explain that context carries Python data into the template.
If it does not work
| What you see | Check this |
|---|---|
TemplateDoesNotExist | The file must be blog/templates/blog/index.html. |
| The title is blank | The context key and {{ site_title }} spelling must match. |
Invalid block tag | Check every {% tag and its closing {% end... %} tag. |
| The whole dictionary appears | Print {{ post.title }}, not {{ post }}. |
Stop here
Today is complete when the view prepares data and the template displays it. Tomorrow you will connect a separate CSS file and turn the plain page into a small card layout.
