Tujuan Praktikum
Setelah menyelesaikan praktikum ini, mahasiswa mampu:
- Menjelaskan cara kerja aplikasi web (request dan response).
- Membuat aplikasi Flask dan menjalankan server pengembangan.
-
Membuat routing dengan
@app.route. -
Menampilkan data menggunakan
template Jinja2 (
render_template,{{ }},{% for %},{% if %}). -
Mempercantik tampilan dengan Bootstrap dan
template induk (
layout.html).
Persiapan
Kebutuhan praktikum:
- VS Code
- Project toko-elektronik
- Database + models + repository/produk_repository.py (dari pertemuan sebelumnya)
- Virtual environment aktif (venv)
Aktifkan venv, lalu pasang Flask:
venv\Scripts\activate
pip install flask
pip freeze > requirements.txt
macOS / Linux: gunakan
source venv/bin/activate.
Buat file app.py dan folder
templates/ di project. Struktur project kini:
toko-elektronik/
├── database/toko.db
├── models/
├── repository/
│ └── produk_repository.py
├── templates/ <- baru (semua file HTML di sini)
│ ├── layout.html
│ ├── beranda.html
│ ├── produk.html
│ ├── detail.html
│ └── 404.html
└── app.py <- baru (menggantikan main.py)
Konsep Singkat
Dari Terminal ke Web
Selama Pertemuan 12–13, aplikasi kita berjalan di
terminal dan menampilkan data dengan
print(). Mulai sekarang, aplikasi yang
sama akan tampil sebagai
halaman web di browser.
Kabar terpentingnya: "mesin" aplikasi tidak dibongkar. Repository dan database dari pertemuan sebelumnya dipakai apa adanya. Yang berubah hanya cara menampilkan.
Cara Kerja Aplikasi Web
Aplikasi web bekerja dengan pola request–response:
-
Pengguna membuka alamat (misalnya
/produk) di browser → mengirim request. - Flask menerima request, menentukan fungsi mana yang menanganinya (routing).
- Fungsi mengambil data lewat repository (sama seperti di terminal).
- Data digabung dengan template HTML menjadi halaman utuh.
- Halaman dikirim kembali ke browser sebagai response.
Tiga Istilah Baru
| Istilah | Arti |
|---|---|
| Routing | Menentukan fungsi mana yang menangani suatu alamat/URL |
| Template | Berkas HTML yang bisa disisipi data Python |
| Jinja2 |
Bahasa template bawaan Flask ({{ }} dan
{% %})
|
Langkah 1 - Aplikasi Flask Pertama
Buka app.py, tulis kode paling sederhana:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def beranda():
return "<h1>Halo, Toko Elektronik!</h1>"
if __name__ == "__main__":
app.run(debug=True)
Jalankan:
python app.py
Output di terminal:
* Running on http://127.0.0.1:5000
* Debugger is active!
Buka browser ke alamat http://127.0.0.1:5000. Akan
tampil tulisan "Halo, Toko Elektronik!".
Penjelasan:
| Bagian | Arti |
|---|---|
Flask(__name__) |
Membuat aplikasi Flask |
@app.route("/") |
Menangani alamat / (halaman utama) |
def beranda(): |
Fungsi yang dijalankan saat alamat itu dibuka |
app.run(debug=True) |
Menjalankan server; debug=True menampilkan
error & memuat ulang otomatis
|
Tips: dengandebug=True, setiap kali kode disimpan, server otomatis memuat ulang. Tidak perlu menjalankan ulang manual. Untuk menghentikan server, tekanCtrl+C.
Langkah 2 - Menghubungkan Repository
Menampilkan teks biasa tidak menarik. Mari tampilkan
data produk sungguhan dari database. Perbarui
app.py:
from flask import Flask, render_template
from repository.produk_repository import ProdukRepository
app = Flask(__name__)
repo = ProdukRepository() # repository yang SAMA dari Pertemuan 12
@app.route("/produk")
def daftar_produk():
produk = repo.ambil_semua()
return render_template("produk.html", produk=produk)
if __name__ == "__main__":
app.run(debug=True)
Perhatikan baris produk = repo.ambil_semua(). Ini
method yang persis sama dengan yang dipakai di
terminal pada Pertemuan 12. Tidak ada yang diubah.
render_template("produk.html", produk=produk)
berarti: ambil file produk.html, kirimkan data
produk ke dalamnya, lalu hasilkan halaman jadi.
Langkah 3 - Template Induk (layout.html)
Agar semua halaman punya tampilan seragam (navbar, Bootstrap), kita buat satu template induk. Halaman lain cukup mewarisinya.
Buat templates/layout.html:
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block title %}Toko Elektronik{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="/">Toko Elektronik</a>
<div class="navbar-nav">
<a class="nav-link" href="/">Beranda</a>
<a class="nav-link" href="/produk">Produk</a>
</div>
</div>
</nav>
<div class="container my-4">
{% block content %}{% endblock %}
</div>
</body>
</html>
Penjelasan:
-
Bootstrap dipanggil lewat satu baris
<link>dari CDN. Cukup itu untuk mendapat tampilan rapi. -
{% block title %}dan{% block content %}adalah lubang yang akan diisi oleh halaman turunan. -
Navbar ini otomatis muncul di semua halaman
yang mewarisi
layout.html.
Langkah 4 - Menampilkan Data dengan Jinja2
Sekarang buat templates/produk.html yang mewarisi
induk dan menampilkan tabel produk:
{% extends "layout.html" %}
{% block title %}Daftar Produk{% endblock %}
{% block content %}
<h1 class="mb-4">Daftar Produk</h1>
<table class="table table-striped table-hover bg-white shadow-sm">
<thead class="table-dark">
<tr>
<th>ID</th>
<th>Nama Produk</th>
<th>Kategori</th>
<th class="text-end">Harga</th>
<th class="text-center">Stok</th>
<th></th>
</tr>
</thead>
<tbody>
{% for p in produk %}
<tr>
<td>{{ p.id_produk }}</td>
<td>{{ p.nama_produk }}</td>
<td><span class="badge bg-secondary">{{ p.kategori }}</span></td>
<td class="text-end">Rp {{ "{:,}".format(p.harga) }}</td>
<td class="text-center">
{% if p.stok < 10 %}
<span class="badge bg-danger">{{ p.stok }}</span>
{% else %}
<span class="badge bg-success">{{ p.stok }}</span>
{% endif %}
</td>
<td><a href="/produk/{{ p.id_produk }}" class="btn btn-sm btn-outline-primary">Detail</a></td>
</tr>
{% endfor %}
</tbody>
</table>
<p class="text-muted">Total: {{ produk|length }} produk</p>
{% endblock %}
Inilah tiga alat utama Jinja2:
| Sintaks | Fungsi | Contoh |
|---|---|---|
{{ ... }} |
Menampilkan nilai | {{ p.nama_produk }} |
{% for ... %} |
Perulangan | membuat satu baris tabel per produk |
{% if ... %} |
Percabangan | badge merah bila stok < 10 |
Perhatikan kaitannya dengan Python:
-
{% for p in produk %}sama sepertifor p in produk:—produkadalah data yang dikirimrender_template. -
{{ p.nama_produk }}mengakses atribut objek, persis seperti di Python. -
{{ "{:,}".format(p.harga) }}memberi pemisah ribuan (Rp 8,500,000).
Jalankan python app.py, buka
http://127.0.0.1:5000/produk:
Data yang sama dari terminal kini tampil sebagai tabel web yang rapi.
Langkah 5 - Halaman Beranda dan Detail
Beranda dengan Ringkasan
Tambahkan route beranda di app.py:
@app.route("/")
def beranda():
daftar = repo.ambil_semua()
total_nilai = sum(p.nilai_persediaan() for p in daftar)
return render_template("beranda.html", jumlah=len(daftar), total_nilai=total_nilai)
Buat templates/beranda.html:
{% extends "layout.html" %}
{% block title %}Beranda - Toko Elektronik{% endblock %}
{% block content %}
<h1 class="mb-4">Selamat Datang</h1>
<div class="row">
<div class="col-md-6">
<div class="card text-bg-primary mb-3">
<div class="card-body">
<h5 class="card-title">Jumlah Produk</h5>
<p class="card-text fs-2">{{ jumlah }}</p>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card text-bg-success mb-3">
<div class="card-body">
<h5 class="card-title">Nilai Persediaan</h5>
<p class="card-text fs-2">Rp {{ "{:,}".format(total_nilai) }}</p>
</div>
</div>
</div>
</div>
<a href="/produk" class="btn btn-primary">Lihat Daftar Produk</a>
{% endblock %}
Beranda kini menampilkan Jumlah Produk = 4 dan
Nilai Persediaan = Rp 132.500.000 (memakai method
nilai_persediaan() dari Pertemuan 11).
Halaman Detail (Route dengan Parameter)
Route dapat menerima nilai dari URL. Tambahkan di
app.py:
@app.route("/produk/<int:id_produk>")
def detail_produk(id_produk):
p = repo.ambil_by_id(id_produk)
if p is None:
return render_template("404.html", pesan=f"Produk id {id_produk} tidak ditemukan"), 404
return render_template("detail.html", p=p)
<int:id_produk> berarti bagian URL itu adalah
angka yang dikirim ke fungsi. Jadi
/produk/1 memanggil detail_produk(1).
Buat templates/detail.html:
{% extends "layout.html" %}
{% block title %}{{ p.nama_produk }}{% endblock %}
{% block content %}
<div class="card shadow-sm">
<div class="card-body">
<h2 class="card-title">{{ p.nama_produk }}</h2>
<p class="text-muted">{{ p.kategori }}</p>
<hr>
<p><strong>Harga:</strong> Rp {{ "{:,}".format(p.harga) }}</p>
<p><strong>Stok:</strong> {{ p.stok }}</p>
<p><strong>Nilai Persediaan:</strong> Rp {{ "{:,}".format(p.nilai_persediaan()) }}</p>
<a href="/produk" class="btn btn-secondary">Kembali</a>
</div>
</div>
{% endblock %}
Buat juga templates/404.html untuk produk yang tidak
ditemukan:
{% extends "layout.html" %}
{% block content %}
<div class="alert alert-warning">
<h4>Tidak Ditemukan</h4>
<p>{{ pesan }}</p>
<a href="/produk" class="btn btn-primary">Kembali ke Daftar Produk</a>
</div>
{% endblock %}
Uji: buka /produk/1 (tampil detail Laptop), lalu
/produk/999 (tampil pesan tidak ditemukan).
Langkah 6 - app.py Lengkap
Berikut app.py utuh setelah semua route digabung:
from flask import Flask, render_template
from repository.produk_repository import ProdukRepository
app = Flask(__name__)
repo = ProdukRepository()
@app.route("/")
def beranda():
daftar = repo.ambil_semua()
total_nilai = sum(p.nilai_persediaan() for p in daftar)
return render_template("beranda.html", jumlah=len(daftar), total_nilai=total_nilai)
@app.route("/produk")
def daftar_produk():
produk = repo.ambil_semua()
return render_template("produk.html", produk=produk)
@app.route("/produk/<int:id_produk>")
def detail_produk(id_produk):
p = repo.ambil_by_id(id_produk)
if p is None:
return render_template("404.html", pesan=f"Produk id {id_produk} tidak ditemukan"), 404
return render_template("detail.html", p=p)
if __name__ == "__main__":
app.run(debug=True)
Perhatikan: di seluruhapp.pytidak ada satu pun perintah SQL. Semua urusan database tetap ditanganirepo. Inilah bukti nyata manfaat pemisahan lapisan dari Pertemuan 12 — kita mengganti tampilan dari terminal ke web tanpa menyentuh repository sedikit pun.
Referensi Fungsi Penting
Flask
| Perintah | Fungsi |
|---|---|
Flask(__name__) |
Membuat aplikasi |
@app.route("/alamat") |
Menentukan alamat/URL |
@app.route("/produk/<int:id>")
|
Alamat dengan parameter angka |
render_template("file.html", data=data)
|
Menggabungkan template dengan data |
app.run(debug=True) |
Menjalankan server pengembangan |
return ..., 404 |
Mengirim response dengan kode status |
Jinja2 (di dalam template)
| Sintaks | Fungsi | |
|---|---|---|
{{ nilai }} |
Menampilkan nilai | |
{% for x in daftar %} ...
{% endfor %}
|
Perulangan | |
{% if syarat %} ... {% else %} ...
{% endif %}
|
Percabangan | |
{% extends "layout.html" %} |
Mewarisi template induk | |
{% block nama %} ...
{% endblock %}
|
Menandai bagian yang bisa diisi | |
| `{{ daftar\ | length }}` | Filter (di sini: menghitung jumlah) |
{{ "{:,}".format(angka) }} |
Format ribuan |
Padanan Terminal → Web
| Terminal (Pertemuan 12) | Web (Pertemuan 14) |
|---|---|
print(p) |
{{ p }} di template |
for p in repo.ambil_semua(): print(p) |
{% for p in produk %} di template |
Menu angka (input()) |
Alamat URL & tautan |
repo.ambil_semua() |
repo.ambil_semua() (sama)
|
Pengujian
Pastikan hasil praktikum sesuai berikut:
-
python app.pyberjalan dan server aktif dihttp://127.0.0.1:5000. - Halaman beranda menampilkan Jumlah Produk = 4 dan Nilai Persediaan = Rp 132.500.000.
-
Halaman
/produkmenampilkan tabel berisi 4 produk dengan navbar Bootstrap di atas. - Produk dengan stok kurang dari 10 ditandai badge merah, selebihnya hijau.
-
Halaman
/produk/1menampilkan detail Laptop;/produk/999menampilkan pesan "tidak ditemukan". -
Navbar muncul di semua halaman (bukti
layout.htmldiwarisi).
Troubleshooting
| Masalah | Penyebab | Solusi |
|---|---|---|
TemplateNotFound: produk.html |
File HTML bukan di folder templates/ |
Pastikan semua template ada di folder templates
|
Halaman menampilkan {{ p.nama_produk }} mentah
|
File dibuka langsung, bukan lewat Flask |
Akses lewat http://127.0.0.1:5000, bukan klik
file HTML
|
ModuleNotFoundError: No module named
'repository'
|
app.py tidak di folder utama project |
Letakkan app.py di root project |
| Tampilan polos tanpa Bootstrap | Tidak ada internet / link CDN salah |
Periksa koneksi dan baris
<link> Bootstrap
|
Address already in use |
Server lain masih jalan di port 5000 |
Tekan Ctrl+C di terminal lama, atau ubah port:
app.run(port=5001)
|
| Perubahan tidak muncul | Lupa menyimpan / server tidak reload | Simpan file; pastikan debug=True |
| Halaman 404 dari Flask (bukan milik kita) | Alamat salah ketik | Cek @app.route dan URL di browser |
Tugas Praktikum
Kembangkan aplikasi web:
-
Tambahkan halaman
/produk/kategori/<nama>yang menampilkan produk pada satu kategori saja (mis./produk/kategori/Elektronik). Tambahkan method pendukung di repository bila perlu. - Pada halaman daftar produk, tambahkan baris total di bawah tabel yang menampilkan jumlah keseluruhan nilai persediaan.
-
Tambahkan menu "Pelanggan" di navbar
dan halaman
/pelangganyang menampilkan daftar pelanggan (pakaiPelangganRepositorybila sudah dibuat di tugas Pertemuan 12, atau buat sederhana). - Ubah tampilan beranda: tambahkan satu kartu (card) lagi yang menampilkan jumlah produk dengan stok menipis (< 10).
-
Ganti warna atau tema navbar, dan tambahkan
footer berisi nama serta NIM Anda di
layout.html(agar muncul di semua halaman).
Kumpulkan seluruh file .py dan
.html beserta tangkapan layar tiap halaman di browser
(format PDF).
Kesimpulan
Pada pertemuan ini aplikasi penjualan berpindah dari terminal ke
web menggunakan Flask. Mahasiswa
mempelajari pola request–response, membuat
routing dengan @app.route,
menampilkan data dengan template Jinja2 ({{ }}, {% for %}, {% if %}), serta merapikan
tampilan dengan Bootstrap dan template induk
layout.html. Hal terpenting: seluruh
repository dan database dari pertemuan sebelumnya dipakai tanpa
perubahan
— yang berganti hanya lapisan tampilan. Inilah bukti nyata mengapa
kode disusun berlapis sejak awal. Pada pertemuan berikutnya,
aplikasi web ini akan dilengkapi kemampuan
menambah, mengubah, dan menghapus data melalui
form (Flask CRUD), sehingga menjadi aplikasi
penjualan yang benar-benar interaktif.