DataLens

Template dengan Jinja2

Pindahkan HTML dari Python ke template, isi dengan data, gunakan loop dan percabangan di dalamnya, bagikan satu layout dasar, dan biarkan auto-escaping menjagamu tetap aman.

Level: Menengah · 11 menit baca

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

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>

Sintaks Jinja dalam satu tabel

SintaksFungsiContoh
{{ ... }}mencetak nilai{{ customer.name }}
{% ... %}logika: loop, if, block{% for o in orders %} ... {% endfor %}
{# ... #}komentar, tidak muncul di output{# hanya untuk developer #}
`\` filtermengubah 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 &lt;script&gt;alert(&#39;hi&#39;)&lt;/script&gt;</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.

Ide utama

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

Latihan modul ini

Soal-soal untuk pelajaran ini berjalan di browser-mu dan langsung mengecek jawabanmu.

Berikutnya: Menyajikan file statis →