Idenya
Request membawa input pengguna di dua tempat utama:
- query string (
/products?category=Coffee&max_price=100000): untuk request GET seperti pencarian dan penyaringan. Input-nya terlihat di URL, sehingga hasilnya bisa di-bookmark dan dibagikan. - body request (field formulir): untuk request POST yang mengubah sesuatu, seperti membuat pesanan.
Flask menyediakannya sebagai request.args dan request.form. Keduanya mirip dictionary, dan keduanya berisi teks yang diketik orang asing, jadi modul ini sama banyaknya membahas validasi input seperti membacanya.
Query string: request.args
Pelanggan yang sedang melihat-lihat toko ingin memfilter: hanya kopi, hanya barang di bawah Rp 100.000. Pilihan-pilihan itu dibawa di URL setelah tanda ?, seperti /products?category=Coffee&max_price=100000. Bagian itu disebut query string, dan Flask memberikannya kepadamu sebagai request.args:
from flask import Flask, request
app = Flask(__name__)
PRODUCTS = [
{"name": "Arabica Gayo 250g", "category": "Coffee", "price": 95000},
{"name": "Toraja Kalosi 250g", "category": "Coffee", "price": 110000},
{"name": "Ceramic Mug", "category": "Kitchen", "price": 55000},
{"name": "Pour-over Kettle", "category": "Kitchen", "price": 320000},
]
@app.get("/products")
def products():
category = request.args.get("category")
max_price = request.args.get("max_price", type=int)
rows = PRODUCTS
if category:
rows = [p for p in rows if p["category"] == category]
if max_price is not None:
rows = [p for p in rows if p["price"] <= max_price]
return ", ".join(p["name"] for p in rows) or "No products"
client = app.test_client()
print(client.get("/products").get_data(as_text=True))
print(client.get("/products?category=Coffee").get_data(as_text=True))
print(client.get("/products?category=Kitchen&max_price=100000").get_data(as_text=True))
print(client.get("/products?max_price=cheap").get_data(as_text=True))
Arabica Gayo 250g, Toraja Kalosi 250g, Ceramic Mug, Pour-over Kettle
Arabica Gayo 250g, Toraja Kalosi 250g
Ceramic Mug
Arabica Gayo 250g, Toraja Kalosi 250g, Ceramic Mug, Pour-over Kettle
request.args.get("category")mengembalikan nilainya, atauNonekalau tidak ada.type=intmengonversi nilainya; kalau konversi gagal (max_price=cheap),getmengembalikan default (None) alih-alih crash. Perhatikan bahwa request terakhir diam-diam mengabaikan nilai yang buruk; terkadang kamu lebih ingin memberi tahu pengguna (lihat di bawah).- Sebuah key bisa muncul beberapa kali (
?tag=gift&tag=new):request.args.getlist("tag")mengembalikan semuanya.
Test client bisa membuatkan query string untukmu: client.get("/products", query_string={"category": "Coffee"}).
Formulir: request.form
Membuat pesanan berbeda dengan memfilter. Ini mengubah data, jadi dikirim dengan POST, dan nilainya berasal dari formulir yang diisi pelanggan. name setiap field menjadi key yang dibaca route-mu:
<form method="post" action="/orders">
<input name="product">
<input name="quantity" type="number">
<button>Order</button>
</form>
from flask import Flask, request
app = Flask(__name__)
@app.post("/orders")
def create_order():
product = request.form.get("product", "").strip()
quantity = request.form.get("quantity", type=int)
return f"{quantity} × {product}"
r = app.test_client().post("/orders", data={"product": " Ceramic Mug ", "quantity": "2"})
print(r.get_data(as_text=True))
2 × Ceramic Mug
Setiap nilai formulir datang sebagai teks, bahkan dari input type="number". Mengonversi (type=int) dan membersihkan (.strip()) adalah tugasmu.
Validasi dulu, baru bertindak
Handler yang tangguh mengumpulkan masalah lebih dulu, lalu melaporkannya atau bertindak. Ini pola yang sama dengan yang kamu pakai untuk formulir Streamlit dan validasi pandas:
from flask import Flask, request
app = Flask(__name__)
PRODUCTS = {"Ceramic Mug": 55000, "Pour-over Kettle": 320000}
def validate_order(form):
"""Return (cleaned_order, problems)."""
problems = []
product = form.get("product", "").strip()
quantity = form.get("quantity", type=int)
if product not in PRODUCTS:
problems.append("Choose a product from the list.")
if quantity is None or not 1 <= quantity <= 20:
problems.append("Quantity must be a whole number from 1 to 20.")
return {"product": product, "quantity": quantity}, problems
@app.post("/orders")
def create_order():
order, problems = validate_order(request.form)
if problems:
return {"errors": problems}, 400
total = PRODUCTS[order["product"]] * order["quantity"]
return {"order": order, "total": total}, 201
client = app.test_client()
print(client.post("/orders", data={"product": "Ceramic Mug", "quantity": "3"}).get_json())
r = client.post("/orders", data={"product": "Teapot", "quantity": "0"})
print(r.status_code, r.get_json())
{'order': {'product': 'Ceramic Mug', 'quantity': 3}, 'total': 165000}
400 {'errors': ['Choose a product from the list.', 'Quantity must be a whole number from 1 to 20.']}
validate_order adalah fungsi biasa: kamu bisa mengetesnya tanpa Flask, dan memakainya ulang di API maupun formulir web.
Jangan pernah percaya input. Konversi ke tipe yang tepat, cek terhadap aturanmu, dan laporkan semua masalah sekaligus dengan 400. Hanya data yang valid yang sampai ke logika bisnismu.
Menampilkan ulang formulir beserta error
Untuk formulir HTML, response yang ramah terhadap input buruk adalah formulir yang sama lagi, dengan pesan dan nilai yang tadi diisi pengguna tetap ada:
from flask import Flask, request, render_template_string
app = Flask(__name__)
FORM = """
{% for e in errors %}<p class="error">{{ e }}</p>{% endfor %}
<form method="post">
<input name="email" value="{{ email }}">
<button>Subscribe</button>
</form>
"""
@app.route("/subscribe", methods=["GET", "POST"])
def subscribe():
email = request.form.get("email", "").strip()
errors = []
if request.method == "POST":
if "@" not in email:
errors.append("That doesn't look like an email address.")
else:
return f"Subscribed {email}"
return render_template_string(FORM, errors=errors, email=email), 400 if errors else 200
r = app.test_client().post("/subscribe", data={"email": "ayu.mail.id"})
print(r.status_code)
print(r.get_data(as_text=True))
400
<p class="error">That doesn't look like an email address.</p>
<form method="post">
<input name="email" value="ayu.mail.id">
<button>Subscribe</button>
</form>
Mempertahankan input pengguna (value="{{ email }}") berarti mereka cukup memperbaiki satu salah ketik alih-alih mengetik ulang semuanya. Dan karena template melakukan escape pada nilai, bahkan "email" yang jahat pun tidak bisa menyisipkan HTML.
Mengapa ini penting untuk kode yang bersih
Penanganan input adalah tempat sebagian besar bug dan celah keamanan masuk ke aplikasi. Handler yang bersih punya bentuk yang jelas: baca → konversi → validasi → bertindak, dengan validasi di fungsi biasa yang mengembalikan daftar masalah. Itu menjaga view tetap pendek, membuat aturannya bisa dites, dan memberi pengguna pesan yang membantu alih-alih error 500.
Rangkuman
request.args(query string, GET) danrequest.form(body, POST) berperilaku seperti dict berisi string..get(name, default, type=int)mengonversi dengan aman;.getlist(name)membaca key yang berulang.- Nilainya selalu teks: lakukan strip dan konversi.
- Validasi di fungsi biasa yang mengembalikan semua masalah; jawab dengan 400 beserta daftar itu.
- Untuk formulir HTML, tampilkan ulang formulir dengan pesan dan nilai dari pengguna.