DataLens

Menangani data formulir dan query string

Baca filter pencarian dari query string dan formulir yang dikirim dari body, konversi dan validasi nilainya, lalu tampilkan ulang formulir beserta error-nya.

Level: Menengah · 10 menit baca

Latihan modul ini

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

Idenya

Request membawa input pengguna di dua tempat utama:

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

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.

Ide utama

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&#39;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

Latihan modul ini

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

Berikutnya: Mengembalikan JSON dan membuat API sederhana →