Turning folders into web pages with Python
For the prototyping class on 10 February 2026 in the context of SI29.
Gallery example
1. Install uv
(if you don't have uv already)
https://docs.astral.sh/uv/getting-started/installation/
$ curl -LsSf https://astral.sh/uv/install.sh | sh
2. Initialize project
$ uv init gallery
3. Prepare an assets folder
$ mkdir assets
4. Put some files in the assets folder
Just copy and paste!
5. Work on main.py
from glob import glob
from jinja2 import Template
def main():
assets = glob('assets/*')
with open('template.html','r') as f:
html_template = f.read()
jinja_template = Template(html_template)
output = jinja_template.render(assets=assets)
with open('index.html', 'w') as f:
f.write(output)
if __name__ == "__main__":
main()
6. Work on your template.html
<style>
img {
max-width: 200px;
object-fit: contain;
}
</style>
<ol>
{% for asset in assets %}
<li><img src="{{asset}}"/></li>
{% endfor %}
</ol>
7. Run the script
$ uv run main.py
8. See the result
Open the index.html file in your browser.
Possible extension points
Timestamps
# Sort by timestamp
assets = sorted(assets, key=os.path.getmtime)
# You can reverse the sort by passing reverse=True to the sorted function
assets = sorted(assets, key=os.path.getmtime, reverse=True)
Uploader
Media type
File size
# Sort by size
assets = sorted(assets, key=os.path.getsize)
Anecdotes
How about setting up a small gossip system for our gallery?
Choose a file to annotate, and create a file with the extension .anecdote
$ touch photo_1_2026-02-02_11-56-09.jpg.anecdote
Open the anecdote with a text editor and write your gossips there!
$ nano photo_1_2026-02-02_11-56-09.jpg.anecdote
Then in our python code we check for anecdotes:
[...]
assets_or_anecdotes = []
for asset in assets:
# Every asset has a file...
file_maybe_anecdote = {
"file": asset
}
# But some also have an anecdote
if asset.endswith('.anecdote'):
# If it is we read its content into the file_maybe_anecdote dictionary
with open(asset, 'r') as a:
file_maybe_anecdote['anecdote'] = a.read()
# Append it to the list of assets
assets_or_anecdotes.append(file_maybe_anecdote)
[...]
output = jinja_template.render(assets=assets_or_anecdotes)
In the template, we use conditionals to check if an asset is an anecdote or not, and we render it accordingly.
<ol>
{% for asset in assets %}
{% if asset.anecdote %}
<p>{{ asset.anecdote }}</p>
{% else %}
<li><img src="{{asset.file}}"/></li>
{% endif %}
{% endfor %}
</ol>
(to try out: how to make more anecdotes for the same file?)
Alt text
How to repurpose the anecdotes system to write alt captions for the images?
Hyperlinks
Structured Metadata
# Add some metadata to the assets list
import os
import pwd
from datetime import datetime
#[...]
assets_with_meta = []
for asset in assets:
# Load and parse stats about a file
# https://docs.python.org/3/library/stat.html
stat = os.stat(asset)
size = stat.st_size
# Work with datetime
# https://www.programiz.com/python-programming/datetime/strftime
last_modified = datetime
.fromtimestamp(stat.st_mtime)
.strftime("%m/%d/%Y, %H:%M:%S")
# Get username from user id
# https://stackoverflow.com/questions/842059#comment3162593_2899055
user = pwd.getpwuid(stat.st_uid).pw_name
# Create a dictionary with all the info
asset_with_meta = {
"file": asset,
"user": user,
"last_modified": last_modified,
"size": size
}
# Append it to the list of assets
assets_with_meta.append(asset_with_meta)
Then you can pass your assets_with_meta list to the template...
output = jinja_template.render(assets=assets_with_meta)
And play around with it in the template.html file
<ol>
{% for asset in assets %}
<li>
<img src="{{asset.file}}"/>
{{asset.user}} - {{asset.last_modified}} - {{asset.size}}
</li>
{% endfor %}
</ol>
etc!!!
Hopefully this triggers ideas for more extension points.