4 · HTML forms & Jinja templates
JSON is for machines; humans need a page. An HTML `<form method="post">` collects feature values and posts them to a route, which renders a result page with Jinja — a template engine that fills `{{ ... }}` placeholders with your prediction values server-side before sending finished HTML to the browser.
To serve humans, an HTML `<form method="post">` collects inputs and POSTs them to a route; the route reads `request.form`, predicts, and RENDERS a result page with Jinja — a template engine that, ON THE SERVER, substitutes `{{ value }}` placeholders with real data and sends finished HTML. Jinja automates the same fill-the-holes idea you can do by hand with `str.format`.
Without this:
Without a form + template, a non-technical user has no way to feed the model values or read a prediction — they'd be left staring at raw JSON.
The JSON API in lesson 3 is perfect for machines — but a salesperson isn't going to curl. Humans need a web page: a form to type values into and a readable result. Flask serves that with HTML forms and Jinja templates.
The HTML form sends the input. An <form method="post" action="/predict-form"> wraps a set of <input> fields, each with a name. When the user clicks submit, the browser bundles the fields into a POST request and sends them to the action URL. Because it's a form (not JSON), the values arrive in request.form — request.form["tenure"] gives the value the user typed. (Same round-trip as before; just a different body format.)
The route handles both showing the form and processing it. A common pattern: a GET to the route shows the empty form; a POST to the same route reads request.form, runs the prediction, and shows the result. You branch on request.method.
Jinja renders the result into HTML. Here's the new piece. You don't build HTML by gluing strings together — you write a template: HTML with {{ placeholders }} and {% logic %}, and Jinja fills them in on the server with the values you pass. render_template_string("<p>Result: {{ label }}</p>", label="churn") produces <p>Result: churn</p>. Jinja's two core constructs:
{{ expression }}— prints a value into the HTML (e.g.{{ probability }}).{% statement %}— logic:{% if churn %}...{% endif %},{% for row in rows %}...{% endfor %}.
Crucially, Jinja runs server-side: by the time the response reaches the browser, every {{ }} is already replaced with real text. The browser only ever sees finished HTML — it never sees a template. (In real projects templates live in templates/*.html rendered with render_template("result.html", ...); render_template_string inlines the template for a self-contained example.)
Bonus: Jinja auto-escapes. When you print user-supplied text with {{ }}, Jinja HTML-escapes it by default, so a malicious <script> in the input becomes harmless text instead of executing — a built-in defense against injection you'd have to remember on your own with manual string formatting.
The read-along cell shows a form route plus a Jinja result template inside render_template_string. The runnable cell below strips Jinja away to reveal the core idea: a template is just text with holes, and rendering is filling the holes with values — exactly what Jinja automates, shown here with plain str.format.
A Flask route serving an HTML `<form method="post">` and a Jinja result template via `render_template_string` — `{{ }}` placeholders are filled server-side, and `request.form` reads the submitted fields.
Python (in browser)
Python runs entirely in your browser via Pyodide (~6 MB on first Run, cached after).
What does a Jinja template engine do server-side when a Flask route renders a result page?
- An HTML `<form method="post">` collects named inputs and POSTs them to a route, where Flask reads them from `request.form` (not `request.get_json()`).
- Jinja renders templates server-side: `{{ value }}` prints a value, `{% logic %}` runs control flow, and the browser only ever receives finished HTML.
- A template is text with holes and rendering fills the holes — the same idea you can do with `str.format`, which Jinja automates (and auto-escapes for safety).
Any model app with a human-facing page — an internal scoring tool, a demo for stakeholders — uses a form to collect features and a template to render the prediction.
If you remove it: Without forms and templates the model is JSON-only, usable by other programs but invisible to the non-technical people who often need to see its predictions.