🥐 はじめに
こんにちは、tosane932です。
2026年5月からPythonの独学を始め、AIを活用しながらベーカリー向け売上管理アプリ「Bakery Hub」を開発しています。
今回で、Python基礎の学び直しシリーズも 第10時限目 になりました。
前回の第9時限目では、店舗メモツールを題材にCRUD(作成・取得・更新・削除)を学びました。
今回は、そこから少し戻って、Webアプリの基本的な仕組みを確認していきます。
例えば、Bakery Hubの商品登録画面。
商品名と売価を入力して、登録ボタンを押す。
普段は当たり前のように操作していますが、改めて考えると疑問があります。
ブラウザに入力した商品情報は、どうやってPythonに届いているんだろう?
🐹「商品名と値段を入力して、登録ボタンを押す……」
🐹「それだけでデータベースに保存されるけど、裏では何をしてるんだろう?」
今回はこの疑問を出発点に、GET・POST・request を学び直してみます。
📖 第1章|GETとPOSTって何が違う?
Webアプリでは、ブラウザとサーバーが情報をやり取りしています。
ブラウザからサーバーへ送る依頼を、HTTPリクエスト と呼びます。
例えば、
- 商品登録画面を表示してほしい
- 入力した商品情報を登録してほしい
といった依頼です。
ここで登場するのが、GETとPOSTです。
GETとPOSTの基本
| HTTPメソッド | 主な役割 | Bakery Hubでの例 |
|---|---|---|
| GET | 情報や画面を取得する | 商品登録画面を開く |
| POST | データを送信して処理を依頼する | 商品の登録・更新を依頼する |
🐹「GETは『画面を見せて!』で、POSTは『この商品を登録して!』ってこと?」
今回の商品登録機能では、そのイメージで理解できそうです。
ただし、POSTは必ずデータベースに保存するためのもの、というわけではありません。
送信された情報を使って計算したり、別の処理をしたりすることもあります。
流れを図にすると
【GET】
ブラウザ
│
│「商品登録画面を見せて」
▼
Flask / Python
│
│ HTMLを返す
▼
ブラウザに画面が表示される
【POST】
ブラウザ
│
│「この商品情報を処理して」
▼
Flask / Python
│
│ 入力された情報を確認
▼
データベースへ保存
📖 第2章|FlaskはGETとPOSTをどう受け取る?
ここからは、Bakery Hubの実コードを見てみます。
商品登録を担当する app.py には、次のコードがあります。
@app.route("/", methods=["GET", "POST"])
@admin_or_guest_required
def index():
注目したいのは、
methods=["GET", "POST"]
です。
これは / というURLで、GETとPOSTの両方を受け付ける設定です。
コードを分解してみる
| コード | 役割 |
|---|---|
@app.route("/") |
URLとPythonの関数を結びつける |
methods=["GET", "POST"] |
GETとPOSTを受け付ける |
@admin_or_guest_required |
管理者またはゲストの利用条件を確認する |
def index(): |
処理を担当する関数を定義する |
🐹「あれ?GETもPOSTも、同じ index() 関数が担当するの?」
そうなんです。
同じURLでも、リクエストの種類によって処理を分けられます。
では、どうやって見分けるのでしょうか。
そこで登場するのが、次の if 文です。
📖 第3章|request.method と if 文
Bakery Hubの index() 関数には、次の条件分岐があります。
if request.method == "POST":
これは、今回のリクエストがPOSTかどうかを確認しています。
POSTなら、商品情報を受け取って登録・更新する処理へ進みます。
GETなら、この条件には入らず、後半の画面表示処理へ進みます。
🐹「……あれ?これって普通の if 文じゃない?」
そうです。
以前学んだPythonの条件分岐と同じ構造です。
if 条件:
処理
さらに、
request.method == "POST"
は比較演算子 == を使っています。
POSTなら True、そうでなければ False と判定されるわけです。
request とは?
request は、Flaskが現在のHTTPリクエストの情報を扱うために用意しているオブジェクトです。
| 書き方 | 役割 |
|---|---|
request.method |
GET・POSTなどを確認する |
request.form |
フォームの入力データを扱う |
request.args |
URLのクエリパラメータを扱う |
🐹「Web開発って難しい処理ばかりかと思ったけど、ちゃんとPythonの基本文法が使われてるんだね」
こういう発見があると、基礎を学び直している意味を感じます。
📖 第4章|HTMLからPythonへデータを送る
次は、ブラウザ側のコードです。
Bakery Hubの index.html には、次の記述があります。
<form method="POST">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
method="POST" と書いてあります。
この指定によって、フォームを送信するときにPOSTリクエストが使われます。
今回のフォームには action 属性が指定されていないため、基本的には現在のページのURLへ送信されます。
HTMLとPythonの役割分担
| 担当 | 役割 |
|---|---|
| HTML | 商品名・売価の入力欄を用意する |
| ブラウザ | 入力された情報をHTTPで送る |
| Flask | リクエストを受け取る |
| Python | 入力値を確認して処理する |
🐹「なるほど。HTMLで入力した情報をブラウザが送って、Flaskが受け取るんだ!」
ここで、ようやくHTMLとPythonのつながりが見えてきました。
csrf_token って何?
コードには、もう一つ気になるものがあります。
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
これは、不正なフォーム送信を防ぐために使われる仕組みです。
今回はGET・POSTの理解が中心なので、詳しい説明は別の機会にしたいと思います。
📖 第5章|request.form で商品情報を受け取る
ここからが、今回特に理解したかった部分です。
Bakery Hubでは、フォームから送信された商品情報を次のように受け取っています。
product_names = request.form.getlist("prod_name")
product_prices = request.form.getlist("prod_price")
product_ids = request.form.getlist("product_id")
なぜ getlist() を使う?
🐹「getlist()……名前からして、リストで取得するってこと?」
そうです。
Bakery Hubでは複数の商品をまとめて登録できるため、同じ名前で送信された値をリストで取得しています。
例えば、次の3商品を入力したとします。
| 商品名 | 売価 |
|---|---|
| クロワッサン | 280円 |
| あんぱん | 180円 |
| 食パン | 350円 |
フォームから取得した情報は、次のようなイメージになります。
product_names = [
"クロワッサン",
"あんぱん",
"食パン",
]
product_prices = [
"280",
"180",
"350",
]
ここで一つ気になりました。
売価なのに、"280" とダブルクォーテーションで囲まれています。
フォームから受け取る数値は文字列
HTMLで数値を入力しても、request.form から取得する値は基本的に文字列です。
price = "280"
print(type(price))
実行結果:
<class 'str'>
🐹「えっ、280円って入力したのに、整数の280じゃないの?」
画面で数字に見えていても、Python側では文字列として受け取っているんですね。
そのため、必要に応じて整数へ変換したり、入力値が正しいか確認したりする処理が必要になります。
get() と getlist() の違い
| メソッド | 役割 |
|---|---|
request.form.get("name") |
指定した名前の値を1つ取得する |
request.form.getlist("name") |
同じ名前の値をリストで取得する |
📖 第6章|受け取ったデータをそのまま保存しない
🐹「よし!商品名と値段を受け取ったら、そのままデータベースに保存すればいいんだね!」
……と思ったのですが、実際にはその前に確認が必要です。
もし商品名が空っぽだったら?
もし売価がマイナスだったら?
もし想定外の数字が送られてきたら?
そうしたデータをそのまま保存しないために、入力値を検証しています。
商品名のチェック
Bakery Hubでは、次のコードで商品名を確認しています。
normalized_name = name.strip()
if not 1 <= len(normalized_name) <= PRODUCT_NAME_MAX_LENGTH:
logger.warning("Rejected product update with invalid product name.")
return "商品名は1文字以上100文字以内で入力してください。", 400
コードを分解する
| コード | 意味 |
|---|---|
name.strip() |
前後の空白などを取り除く |
len(normalized_name) |
商品名の文字数を調べる |
1 <= ... <= PRODUCT_NAME_MAX_LENGTH |
許可された文字数か確認する |
not |
条件を満たしていない場合を判定する |
return ..., 400 |
エラーを返して処理を終了する |
現在のBakery Hubでは、商品名は 1〜100文字 に制限されています。
🐹「商品名が空白だけだったとしても、strip() したら空文字になるんだね」
ここにも、以前学んだ文字列処理や条件分岐が登場しています。
売価のチェック
売価にも条件があります。
price_value = _parse_bounded_nonnegative_integer(
price,
PRODUCT_PRICE_MAX,
)
if price_value is None or price_value % PRODUCT_PRICE_STEP != 0:
logger.warning("Rejected product update with invalid price.")
return "売価は0〜9,990円の10円単位で入力してください。", 400
現在のBakery Hubでは、売価は 0〜9,990円の10円単位 です。
| 入力値 | 判定 |
|---|---|
| 280円 | OK |
| 350円 | OK |
| 285円 | NG |
| -100円 | NG |
| 10,000円 | NG |
🐹「285円も整数なのに、なぜ駄目なんだろう?」
10円単位という条件があるためです。
ここでは % という剰余演算子が使われています。
例えば、
print(280 % 10) # 0
print(285 % 10) # 5
10で割った余りが0なら、10円単位という条件を満たします。
📖 第7章|データベースへの保存
入力値の確認が終わると、データベースへの保存処理に進みます。
新しい商品を追加する場合、Bakery Hubでは次のコードが使われています。
db.session.add(
Product(
dataset=current_dataset,
year=year,
month=month,
name=prod["name"],
price=prod["price"],
image_key=prod["image_key"],
)
)
そして、必要な処理をまとめたあとに、
db.session.commit()
が実行されます。
🐹「あっ!add() と commit() は前にも見たことがある!」
SQLAlchemyを使ったデータベース操作ですね。
| 処理 | 役割 |
|---|---|
db.session.add() |
新しい商品を保存対象に追加する |
db.session.commit() |
変更を確定する |
db.session.rollback() |
エラー時などに未確定の変更を取り消す |
実際のBakery Hubでは、新規追加だけでなく、既存商品の更新や非表示処理も行っています。
今回は新規追加部分を中心に読みました。
📖 第8章|GETでもデータを送れる?
ここまでPOSTを中心に見てきましたが、GETでも情報を渡すことがあります。
Bakery Hubには次のコードがあります。
month = request.args.get("month", type=int)
これは、URLのクエリパラメータから month を取得する処理です。
例えば、
/?month=10
というURLなら、10月を指定しているイメージです。
type=int が指定されているため、整数に変換できれば整数として取得します。
🐹「GETでも、表示したい月を伝えられるんだね!」
GETでは、検索条件や表示対象などをクエリパラメータで指定することがあります。
request の役割を整理
| 書き方 | 何を扱う? |
|---|---|
request.method |
リクエストの種類 |
request.form |
フォームから送られたデータ |
request.args |
URLのクエリパラメータ |
似たような名前ですが、取得する情報が違います。
この3つは、今後も何度か読み直したいところです。
📖 第9章|商品登録の流れをまとめる
今回学んだ内容を、商品登録の流れに沿って整理してみます。
① ブラウザからGETでアクセス
│
▼
② Flaskが商品登録画面を表示
│
▼
③ 商品名と売価を入力
│
▼
④ HTMLフォームからPOSTで送信
│
▼
⑤ Flaskがrequest.formで受け取る
│
▼
⑥ Pythonで入力値を検証
│
▼
⑦ SQLAlchemyで商品を追加・更新
│
▼
⑧ commit()で変更を確定
│
▼
⑨ 登録完了画面を表示
🐹「登録ボタン一つで、こんなに処理が動いていたんだね」
今回登場したのは、
- HTTPのGET・POST
- Flaskのルーティング
-
requestからのデータ取得 - Pythonの条件分岐
- リスト
- 文字列処理
- 入力値の検証
- SQLAlchemyのデータベース操作
などです。
こうして並べると多く感じますが、すでに学んだPythonの基本文法も含まれています。
バラバラに覚えていたことが、一つの機能の中でつながっているのが面白いですね。
📝 第10時限目の理解度チェック
最後に、今回の内容を5問で復習してみます。
| 問題 | 内容 |
|---|---|
| Q1 | GETとPOSTの主な違いは? |
| Q2 |
request.method == "POST" は何を確認している? |
| Q3 |
request.form.getlist() を使う理由は? |
| Q4 | なぜフォームから受け取った売価を検証する? |
| Q5 |
db.session.commit() は何をする? |
✅ 解答例を見る
Q1. GETとPOSTの主な違いは?
GETは情報の取得、POSTはデータを送って処理を依頼するときなどに使います。
Q2. request.method == "POST" は何を確認している?
今回のHTTPリクエストがPOSTかどうかを確認しています。
Q3. request.form.getlist() を使う理由は?
同じ名前で送られた複数の値を、リストとして取得するためです。
Q4. なぜフォームから受け取った売価を検証する?
文字列として送られた値が、アプリの決めた形式や範囲に合っているか確認するためです。
Q5. db.session.commit() は何をする?
データベースへの変更を確定します。
🐹 おわりに
今回は、Bakery Hubの商品登録機能を使って、GET・POST・requestを学び直しました。
僕はAIを活用しながらWebアプリを開発していますが、開発を続けるにつれて、自分の理解が追いつかなくなってきた部分があります。
特にBakery Hubは、独学を始めた頃と比べて機能が増え、コードも大きくなりました。
だからこそ、今は新しい機能を追加するだけでなく、すでに動いているコードを読み返す時間を大切にしています。
今回の商品登録も、最初は単純な機能だと思っていました。
でも実際には、ブラウザとFlaskがHTTPでやり取りし、Pythonが入力値を確認し、SQLAlchemyがデータベースへの変更を担当していました。
それぞれの仕組みを一度に全部理解するのは難しいですが、以前学んだ if 文やリスト、文字列処理が実際に使われていると分かったのは嬉しい発見です。
🐹「第10時限目まで来たけど、まだまだ知らないことがいっぱいあるなぁ……」
🐹「でも、前に学んだことが実際のアプリで使われてると、ちょっと嬉しいね!」
今後も、コードを丸暗記するのではなく、なぜその処理が必要なのか、自分の言葉で説明できるようになりたいと思っています。
最後まで読んでいただき、ありがとうございました!
🔗 参考資料・関連リンク
公式ドキュメント
🐹 Python基礎シリーズ|これまでの学習記録
Bakery Hub(旧sales_data_app)の実コードを教材に、Pythonの基礎からWebアプリ開発の仕組みまで学び直しています。
| 時限 | 学習テーマ | 記事 |
|---|---|---|
| 第1時限目 | 変数・データ型・比較演算子・if文 | 📖 読む |
| 第2時限目 | リスト・辞書・set・for文 | 📖 読む |
| 第3時限目 | 関数・引数・return・関数の呼び出し・型ヒント | 📖 読む |
| 第4時限目 | モジュール・import・例外・try・except | 📖 読む |
| 第5時限目 | クラス・インスタンス・init・self・属性 | 📖 読む |
| 第6時限目 | f文字列・アンパック・リスト内包表記・join | 📖 読む |
| 第7時限目 | strip・len・splitlines・スライス | 📖 読む |
| 第8時限目 | None・is・is not・isinstance | 📖 読む |
| 第9時限目 | CRUD・Create・Read・Update・Delete | 📖 読む |
| 第10時限目 | GET・POST・request | 📍 今回の記事 |
🥐 Bakery Hub
GitHub|Tosane Works
Bakery Hub|公開デモ
システム概要|実コードで学ぶページ
