I'm migrating my website over to Python and Jinja (no frameworks, I wanted to make my own modules and scripts and learn Python better in the process), and I'm using this Real Python tutorial for making a for loop and outputting the data to a template: https://realpython.com/primer-on-jinja-templating/#leverage-for-loops
I've adapted the write_messages.py
code to work with my existing template for my posts and the data, so the template HTML looks like this:
{% for post in posts %}
<article>
<h2><a class="permalink" href="/">{{ title }}</a></h2>
<p>{{ body }}</p>
<time>{{ date }}</time>
<p class="tag">Tags: <a class="tags" href="/tags/{{ tags }}">{{ tags }}</a></p>
<hr class="solid">
</article>
{% endfor %}
And the Python script, which I'm calling write-posts.py
, looks like this:
# write-posts.py
from jinja2 import Environment, FileSystemLoader
from datetime import datetime, timezone
title = "First Post"
slug = "first-post"
date = 2025-7-6
body = "Hello world!"
tags = "First Post", "Second Tag"
posts = [
{"title": "First Post",},
]
environment = Environment(loader=FileSystemLoader("templates"))
template = environment.get_template("index.html")
filename = "testindex.html"
for post in posts:
content = template.render(
post,
title=title,
slug=slug,
date=date,
body=body,
tags=tags
)
with open(filename, mode="w", encoding="utf-8") as message:
message.write(content)
print(f"... wrote {filename}")
Lines 5-11 in write-posts.py
are giving me problems: 1) Using those lines as-is, the testindex.html
file generates, but does not output the HTML code for the <article>
section. If I remove lines 5-9 and leave the posts
dictionary in there, then the terminal tells me NameError: name 'title' is not defined. Did you mean: 'tuple'?
(I know I don't need both sets of data in here, I was just trying to figure out how to resolve the NameError and left the code in.)
I've looked over the tutorial's code and mine several times and can't figure out what I'm doing wrong. Any ideas?
You have for
-loop in code and the same for
-loop in template and it makes no sense.
If in code you run template for every post then you don't need for
-loop in template.
<article>
<h2><a class="permalink" href="/">{{ title }}</a></h2>
<p>{{ body }}</p>
<time>{{ date }}</time>
<p class="tag">Tags: <a class="tags" href="/tags/{{ tags }}">{{ tags }}</a></p>
<hr class="solid">
</article>
But in open()
it needs to use append mode
to add every <article>
to file.
Using write mode
it would replace previous <article>
with new <article>
for post in posts:
content = template.render(
post=post,
title=title,
slug=slug,
date=date,
body=body,
tags=tags
)
with open(filename, mode="a", encoding="utf-8") as message:
message.write(content)
print(f"... wrote {filename}")
Or you would have to open it before for
-loop and keep it open for all for
-loop (and it can use write mode
with open(filename, mode="w", encoding="utf-8") as message:
for post in posts:
content = template.render(
post=post,
title=title,
slug=slug,
date=date,
body=body,
tags=tags
)
message.write(content)
print(f"... wrote {filename}")
But you may also send all posts at once
content = template.render(
posts=posts, # <-- `posts` instead of `post`
title=title,
slug=slug,
date=date,
body=body,
tags=tags
)
with open(filename, mode="w", encoding="utf-8") as message:
message.write(content)
print(f"... wrote {filename}")
and then it needs for
-loop inside template
{% for post in posts %}
<article>
<h2><a class="permalink" href="/">{{ title }}</a></h2>
<p>{{ body }}</p>
<time>{{ date }}</time>
<p class="tag">Tags: <a class="tags" href="/tags/{{ tags }}">{{ tags }}</a></p>
<hr class="solid">
</article>
{% endfor %}
In both versions you can also use {{ post.title }}
instead of {{ title }}
to have unique title for every post.
To make it more useful you could create post with own body
, date
, etc.
posts = [
{"title": "First Post", "body": "Hello", "date": ...},
{"title": "Second Post"< "body": "Hello Again", "date": ...},
]
and send only posts
content = template.render(posts=posts)
with open(filename, mode="w", encoding="utf-8") as message:
message.write(content)
print(f"... wrote {filename}")
and in template use post.title
, post.body
, post.date
. etc.
{% for post in posts %}
<article>
<h2><a class="permalink" href="/">{{ post.title }}</a></h2>
<p>{{ post.body }}</p>
<time>{{ post.date }}</time>
<p class="tag">Tags: <a class="tags" href="/tags/{{ post.tags }}">{{ post.tags }}</a></p>
<hr class="solid">
</article>
{% endfor %}