Skip to the resource

Day 2 of 7 · 60–90 minutes

Build a template and show sample posts

Move HTML out of the Python view, pass a small list through context, and display it with a template loop.

Today you finish withA real HTML page showing three sample blog posts.

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_title in the view changes the template.
  • An empty posts list shows the empty message.
  • You can explain that context carries Python data into the template.

If it does not work

What you seeCheck this
TemplateDoesNotExistThe file must be blog/templates/blog/index.html.
The title is blankThe context key and {{ site_title }} spelling must match.
Invalid block tagCheck every {% tag and its closing {% end... %} tag.
The whole dictionary appearsPrint {{ 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.