Idenya
Membuat HTML dengan f-string masih bisa untuk sebuah judul. Untuk halaman sungguhan (tabel pesanan, bar navigasi, footer di setiap halaman), string Python yang penuh HTML jadi tidak terbaca dan berbahaya. Template menyelesaikan masalah ini: file HTML berisi placeholder, yang diisi Flask memakai template engine Jinja2.
Pembagiannya rapi: Python memutuskan data apa yang ditampilkan; template memutuskan tampilannya.
Template pertamamu
Template disimpan di folder bernama templates/ di samping aplikasimu:
shop/
├── app.py
└── templates/
└── product.html
templates/product.html (sudah disertakan di modul ini):
<h1>{{ name }}</h1>
<p>Price: Rp {{ "{:,}".format(price) }}</p>
app.py:
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/products/<int:product_id>")
def product(product_id):
return render_template("product.html", name="Ceramic Mug", price=55000)
client = app.test_client()
print(client.get("/products/6").get_data(as_text=True))
<h1>Ceramic Mug</h1>
<p>Price: Rp 55,000</p>
render_template("product.html", name=..., price=...)memuat file daritemplates/dan mengisinya.{{ expression }}mencetak sebuah nilai. Di dalamnya, Jinja memahami variabel, atribut (order.total), key (order["total"]), dan ekspresi sederhana.
Sintaks Jinja dalam satu tabel
| Sintaks | Fungsi | Contoh | |||
|---|---|---|---|---|---|
{{ ... }} | mencetak nilai | {{ customer.name }} | |||
{% ... %} | logika: loop, if, block | {% for o in orders %} ... {% endfor %} | |||
{# ... #} | komentar, tidak muncul di output | {# hanya untuk developer #} | |||
| `\ | ` filter | mengubah nilai | `{{ name\ | upper }}, {{ items\ | length }}` |
Loop dan kondisi
Untuk eksperimen cepat (dan di situs ini), render_template_string menerima template berupa string, bukan file. Sintaksnya sama persis:
from flask import Flask, render_template_string
app = Flask(__name__)
ORDERS_PAGE = """
<h1>Orders ({{ orders|length }})</h1>
{% if orders %}
<ul>
{% for o in orders %}
<li>#{{ o.id }}: {{ o.customer }}, Rp {{ "{:,}".format(o.total) }}{% if o.total > 500000 %} <b>big</b>{% endif %}</li>
{% endfor %}
</ul>
{% else %}
<p>No orders yet.</p>
{% endif %}
"""
@app.route("/orders")
def orders():
data = [
{"id": 38, "customer": "Dimas Pratama", "total": 90000},
{"id": 33, "customer": "Kartika Rahma", "total": 735000},
]
return render_template_string(ORDERS_PAGE, orders=data)
client = app.test_client()
print(client.get("/orders").get_data(as_text=True))
<h1>Orders (2)</h1>
<ul>
<li>#38: Dimas Pratama, Rp 90,000</li>
<li>#33: Kartika Rahma, Rp 735,000 <b>big</b></li>
</ul>
Baris kosongnya berasal dari tag {% %}; browser mengabaikannya. (app.jinja_env.trim_blocks = True menghapusnya kalau kamu peduli.)
o.id berfungsi untuk atribut objek maupun key dict: Jinja mencoba keduanya.
Filter
Template sering butuh nilai yang sedikit diubah khusus untuk ditampilkan: nama dalam huruf kapital, panjang sebuah list, nilai pengganti saat sesuatu tidak ada, catatan panjang yang dipendekkan. Filter Jinja melakukannya di dalam template. Kamu menulis nilainya, sebuah |, lalu nama filternya:
from flask import Flask, render_template_string
app = Flask(__name__)
with app.app_context():
print(render_template_string("{{ name|upper }} | {{ name|title }} | {{ items|length }} | {{ missing|default('n/a') }}",
name="ayu lestari", items=[1, 2, 3]))
print(render_template_string("{{ price|round(1) }} | {{ tags|join(', ') }} | {{ note|truncate(12) }}",
price=12.345, tags=["coffee", "gift"], note="Fragile, handle with care"))
AYU LESTARI | Ayu Lestari | 3 | n/a
12.3 | coffee, gift | Fragile,...
Auto-escaping: keamanan gratis
Ini pertanyaan yang lebih penting daripada kelihatannya: apa yang terjadi kalau seorang pelanggan mengetik HTML, atau bahkan sebuah script, sebagai namanya, lalu template-mu menampilkan nama itu di halaman?
from flask import Flask, render_template_string
app = Flask(__name__)
with app.app_context():
print(render_template_string("<p>Hello {{ name }}</p>", name="<script>alert('hi')</script>"))
<p>Hello <script>alert('hi')</script></p>
Jinja melakukan escape pada < dan >, sehingga browser menampilkan teksnya alih-alih menjalankan script-nya. Tanpa escaping, siapa pun yang bisa mengetik di formulir (ulasan, nama, alamat) bisa menyisipkan JavaScript ke halamanmu; ini disebut cross-site scripting (XSS). Template yang di-render Flask melakukan escape secara default. Membuat HTML dengan f-string tidak, dan itulah alasan utama untuk berhenti melakukannya.
Masukkan data ke HTML dengan {{ }} di template, jangan pernah dengan string formatting. Escaping terjadi otomatis, jadi input pengguna tidak bisa berubah menjadi kode.
Template inheritance: satu layout, banyak halaman
Setiap halaman memakai header dan footer yang sama. Tulis sekali saja di base template dengan block yang diisi oleh tiap halaman:
templates/base.html (disertakan di modul ini):
<!doctype html>
<html>
<head><title>{% block title %}Toko Kita{% endblock %}</title></head>
<body>
<nav><a href="/">Home</a> · <a href="/about">About</a></nav>
<main>{% block content %}{% endblock %}</main>
<footer>© Toko Kita</footer>
</body>
</html>
templates/about.html:
{% extends "base.html" %}
{% block title %}About · Toko Kita{% endblock %}
{% block content %}<h1>About us</h1><p>Coffee and desk gear since 2023.</p>{% endblock %}
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/about")
def about():
return render_template("about.html")
html = app.test_client().get("/about").get_data(as_text=True)
print(html)
<!doctype html>
<html>
<head><title>About · Toko Kita</title></head>
<body>
<nav><a href="/">Home</a> · <a href="/about">About</a></nav>
<main><h1>About us</h1><p>Coffee and desk gear since 2023.</p></main>
<footer>© Toko Kita</footer>
</body>
</html>
{% extends %} berarti "mulai dari base.html"; setiap {% block %} menggantikan block dengan nama yang sama di base. Ubah navigasinya sekali di base.html, dan semua halaman ikut berubah.
Simpan logika di Python
Jinja bisa berhitung dan memanggil method, tetapi tahan dirimu. Hitung total, saring list, dan format uang di view (atau fungsi helper), lalu kirim nilai yang siap tampil ke template. Template yang penuh logika sama sulitnya dites seperti fungsi 300 baris.
Mengapa ini penting untuk kode yang bersih
Template memisahkan tampilan dari logika, versi web dari pembagian "persiapan data → tampilan" yang sudah kamu pakai di setiap track. View jadi pendek (muat data, panggil render_template), template berupa HTML yang mudah dibaca dan bisa diedit desainer, dan auto-escaping menghilangkan seluruh golongan bug keamanan.
Rangkuman
- Template disimpan di
templates/;render_template("file.html", key=value)mengisinya. {{ value }}mencetak;{% for %}/{% if %}menambah logika;{# #}komentar;|filtermengubah nilai.render_template_stringmelakukan hal yang sama dengan string.- Nilai di-escape otomatis, mencegah XSS; jangan pernah membuat HTML dari input pengguna dengan f-string.
{% extends "base.html" %}dan{% block name %}membagikan satu layout ke banyak halaman.- Simpan perhitungan di Python; template hanya menampilkan.