Les 4 · Module 1 · Flask-basis
Statische bestanden en een nette layout
Geef de kassa een eigen stijl: CSS, afbeeldingen en JavaScript serveren uit static/, met url_for, cache-busting en een responsive basislayout.
๐ฏ Na deze les kun je
- CSS, afbeeldingen en scripts serveren uit de map static/
- met url_for('static', filename=…) naar statische bestanden linken
- een responsive basislayout bouwen met een header, menu en hoofdinhoud
- uitleggen waarom browsers statische bestanden cachen en hoe je dat omzeilt bij een wijziging
Je kassa heeft nu templates, maar ziet eruit als 1995: zwarte Times New Roman op wit. In deze les geef je de
app een eigen stylesheet, een logo en een klein script. Zulke bestanden veranderen niet per verzoek en heten
daarom statische bestanden (static files). Flask serveert ze voor je uit de map static/.
De map static
kassa-web/
โโโ app.py
โโโ static/
โ โโโ css/kassa.css
โ โโโ img/logo.svg
โ โโโ js/kassa.js
โโโ templates/
Alles in static/ is bereikbaar onder /static/โฆ. Zet een bestand static/css/kassa.css neer en open
http://127.0.0.1:5000/static/css/kassa.css: je ziet de CSS. Je hoeft geen route te schrijven.
Linken met url_for('static', โฆ)
Schrijf in base.html nooit /static/css/kassa.css met de hand, maar:
<link rel="stylesheet" href="{{ url_for('static', filename='css/kassa.css') }}">
<script src="{{ url_for('static', filename='js/kassa.js') }}" defer></script>
Draait je app later onder een subpad (bijvoorbeeld /kassa/) of komen statische bestanden van een CDN, dan
hoef je alleen de configuratie aan te passen, niet elke template.
Een basislayout met CSS
Werk templates/base.html bij:
<!doctype html>
<html lang="nl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block titel %}Kassa{% endblock %} ยท kassa-web</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/kassa.css') }}">
</head>
<body>
<header class="kop">
<img src="{{ url_for('static', filename='img/logo.svg') }}" alt="" width="40" height="40">
<nav>
<a href="{{ url_for('home') }}">Start</a>
<a href="{{ url_for('toevoegen') }}">Toevoegen</a>
<a href="{{ url_for('bon_tonen') }}">Bon</a>
</nav>
</header>
<main class="inhoud">{% block inhoud %}{% endblock %}</main>
</body>
</html>
En static/css/kassa.css:
:root { --accent: #2a9d8f; --tekst: #264653; }
* { box-sizing: border-box; }
body { margin: 0; font-family: system-ui, sans-serif; color: var(--tekst); }
.kop { display: flex; align-items: center; gap: 1rem; padding: .75rem 1rem; background: var(--accent); }
.kop nav { display: flex; gap: 1rem; flex-wrap: wrap; }
.kop a { color: white; text-decoration: none; }
.inhoud { max-width: 40rem; margin: 2rem auto; padding: 0 1rem; }
table { width: 100%; border-collapse: collapse; }
td { padding: .4rem; border-bottom: 1px solid #ddd; }
td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.duur { background: #fff3cd; }
.fout { color: #9b2226; font-weight: bold; }
Voorspel eerst: je verandert --accent in #e76f51, slaat op en laadt de pagina opnieuw. Zie je meteen de
nieuwe kleur?
Uitkomst
Meestal wel met de debug-server, maar niet altijd. Browsers cachen statische bestanden. Flask stuurt er
een Last-Modified-header en een ETag bij, zodat de browser kan vragen "is dit nog hetzelfde?" (antwoord
304 Not Modified). Zie je de oude kleur, doe dan een harde herlaadactie (Ctrl+Shift+R). In productie los je
dit op met cache-busting, zie hieronder.
Cache-busting
In productie wil je dat browsers CSS lang bewaren (sneller), maar wel direct een nieuwe versie ophalen na een wijziging. Het simpelste trucje: zet een versie in de URL.
import os
@app.context_processor
def statische_versie():
def static_v(pad):
stempel = int(os.path.getmtime(os.path.join(app.static_folder, pad)))
return url_for("static", filename=pad, v=stempel)
return {"static_v": static_v}
In de template: href="{{ static_v('css/kassa.css') }}" โ /static/css/kassa.css?v=1760380000. Verandert het
bestand, dan verandert de URL, en haalt de browser hem opnieuw op.
Een context processor voegt variabelen of functies toe aan elke template. Je komt hem later weer tegen voor de ingelogde gebruiker.
Een klein beetje JavaScript
static/js/kassa.js vraagt bevestiging voor het leegmaken van de bon:
document.addEventListener("submit", (e) => {
if (e.target.matches("[data-bevestig]") && !confirm(e.target.dataset.bevestig)) {
e.preventDefault();
}
});
En in bon.html: <form method="post" action="โฆ" data-bevestig="Weet je het zeker?">. Merk op: geen
onclick="โฆ" in de HTML. Losse scripts uit een bestand werken ook als je later een strenge
Content-Security-Policy instelt (les 14).
Typische fouten
- 404 op
/static/css/kassa.css: het bestand staat intemplates/of de map heetstatics. - Afbeelding kapot in een submap-pagina: je schreef
src="static/img/logo.svg"zonder/aan het begin. Meturl_forgebeurt dit niet. - Wijziging niet zichtbaar: cache. Harde herlaadactie of cache-busting.
Oefenen
- Maak
static/img/logo.svg: een eenvoudige cirkel met een munt-kleur, en toon hem in de kop. - Voeg een favicon toe (
static/favicon.icoof.svg) en link hem inbase.html. - Gebruik
static_vvoor de CSS en controleer metcurl -Idat de URL een?v=heeft en het bestand200geeft. - Maak de menu-link van de huidige pagina vet. Tip:
request.endpointis ook in templates beschikbaar.
Modelantwoord oefening 1
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
<circle cx="20" cy="20" r="18" fill="#e9c46a" stroke="#264653" stroke-width="3"/>
<circle cx="20" cy="20" r="9" fill="none" stroke="#264653" stroke-width="2"/>
</svg>
alt="" in base.html is hier juist: het logo is decoratief en het menu ernaast zegt al waar je bent.
Modelantwoord oefening 2
<link rel="icon" href="{{ url_for('static', filename='favicon.svg') }}" type="image/svg+xml">
Zonder link vraagt de browser zelf /favicon.ico op; die geeft dan een 404 in je log. Wil je dat stil
hebben, zet dan ook een favicon.ico in static/ en een route /favicon.ico die er met redirect naar wijst.
Modelantwoord oefening 3
curl -s http://127.0.0.1:5000/ | grep kassa.css
curl -I "http://127.0.0.1:5000/static/css/kassa.css?v=1760380000"
De eerste regel toont href="/static/css/kassa.css?v=โฆ". Flask negeert de query-parameter bij het serveren:
het bestand komt gewoon terug met 200 OK.
Modelantwoord oefening 4
{% macro menulink(endpoint, label) %}
<a href="{{ url_for(endpoint) }}"{% if request.endpoint == endpoint %} aria-current="page"{% endif %}>{{ label }}</a>
{% endmacro %}
Met CSS .kop a[aria-current] { font-weight: bold; }. aria-current helpt ook schermlezers, beter dan alleen een class.
Lokaal oefenen
Geen live lab: open de site in je browser en gebruik de ontwikkelaarstools (F12 โ Netwerk) om te zien welke bestanden geladen worden, met welke statuscode (200 of 304).
Samenvatting
static/wordt automatisch geserveerd onder/static/; link meturl_for('static', filename=โฆ).- Een basislayout met CSS-variabelen en flexbox geeft alle pagina's รฉรฉn stijl.
- Browsers cachen statische bestanden; met een versie in de URL dwing je een nieuwe versie af.
- Volgende les: formulieren echt goed verwerken en valideren.