Turning folders into web pages with Python: Difference between revisions

From XPUB & Lens-Based wiki
Line 15: Line 15:
A '''static site generator''' ('''SSG''') is a tools/script that helps you to make a website that is based on a bunch of static web pages, that don't use php, flask, node or other web application frameworks. Instead, a SSG is often loved for the fact that it creates a folder of static HTML/CSS/JS files that can be uploaded to a simple web host and require less maintenance, as they don't require a specific more complex technical setup.
A '''static site generator''' ('''SSG''') is a tools/script that helps you to make a website that is based on a bunch of static web pages, that don't use php, flask, node or other web application frameworks. Instead, a SSG is often loved for the fact that it creates a folder of static HTML/CSS/JS files that can be uploaded to a simple web host and require less maintenance, as they don't require a specific more complex technical setup.


Examples of other custom small-scale software projects/scripts that generate static web pages:
'''Examples''' of projects/scripts/practices that generate static web pages:


* [https://git.vvvvvvaria.org/varia/bots/src/branch/master/LogBot varia's logbot] + [https://vvvvvvaria.org/logs/ generated logs]  
* [https://git.vvvvvvaria.org/varia/bots/src/branch/master/LogBot varia's logbot] + [https://vvvvvvaria.org/logs/ generated logs]  

Revision as of 21:17, 10 February 2026

For the prototyping class on 10 February 2026 in the context of SI29.

Turning a folder of pictures into a photo gallery

generating HTML + CSS with Python + Jinja

This is a prototype to generate web pages based on a collection of files stored on your computer or a shared server.

The following code will generate this photo gallery: https://hub.xpub.nl/sergio/SI29/file-sharing/gallery/

Context

This prototype is basically showing you how you can make your own custom static site generator.

A static site generator (SSG) is a tools/script that helps you to make a website that is based on a bunch of static web pages, that don't use php, flask, node or other web application frameworks. Instead, a SSG is often loved for the fact that it creates a folder of static HTML/CSS/JS files that can be uploaded to a simple web host and require less maintenance, as they don't require a specific more complex technical setup.

Examples of projects/scripts/practices that generate static web pages:

How to make this?

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!

/var/www/html/SI29/file-sharing/gallery/assets/

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)

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.