methodで、GETとPOSTを選んで送るrequest.methodで、GETとPOSTを受け分けるrequest.formで受け取るGETとPOSTで動きが変わる、おみくじアプリを作るよ。
おみくじ
今回のリクエスト:POST
中吉
選択肢:大吉・中吉・小吉・末吉
いつものおみくじ(GET)
大吉・吉・凶の中から1つ選ぶよ。
[いつものおみくじを引く]
オリジナルおみくじ(POST)
選択肢(空白で区切って書こう)
[大吉 中吉 小吉 末吉 ]
[オリジナルおみくじを引く]
| リクエスト | おみくじ |
|---|---|
| GET | 用意された「大吉・吉・凶」から引く |
| POST | フォームに書いた選択肢から引く |
ページは1枚だけ。同じURLでも、GETとPOSTのどちらで送るかによって、おみくじの中身が変わるよ。
ブラウザからサーバーへ送るリクエストには、GETやPOSTなどのHTTPメソッドを指定するよ。
| メソッド | 主な目的 | 今回の使い方 |
|---|---|---|
| GET | ページやデータを受け取る | いつものおみくじを引く |
| POST | データを送って、サーバーに処理してもらう | 書いた選択肢を送って、おみくじを引いてもらう |
「データをちょうだい!!!」というのがGET 「このデータを上げるから、こういうふうに表示してね」というのがPOST
VS Codeで、以下のフォルダ構成を用意しよう
2_get_post
├── get_post.md
├── app.py
├── templates
│ └── index.html
└── static
└── style.css
前回のFlaskサーバーが動いていたら、そのターミナルでCtrl+Cを押して停止しておこう。
ターミナルで、Flaskが入っているか確認しよう。
python -m pip show flask
見つからない場合は、次のコマンドでインストールしよう。
python -m pip install flask
最初に3つのファイルを用意しよう。遊んでみてから、コードを読んでいくよ。
app.py:リクエストを受け取って、おみくじを引くimport random
from flask import Flask, render_template, request
app = Flask(__name__)
# いつものおみくじの選択肢
DEFAULT_CHOICES = ["大吉", "吉", "凶"]
@app.route("/", methods=["GET", "POST"])
def omikuji():
if request.method == "POST":
# フォームに書いた選択肢を、空白で区切ってリストにする
choices = request.form.get("choices", "").split()
else:
choices = DEFAULT_CHOICES
# 選択肢が空のときは、いつものおみくじにする
if not choices:
choices = DEFAULT_CHOICES
result = random.choice(choices)
return render_template(
"index.html",
method=request.method,
choices=choices,
result=result,
)
if __name__ == "__main__":
app.run(debug=True, port=8000)
templates/index.html:結果と2つのフォーム<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>おみくじ</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<main>
<h1>おみくじ</h1>
<section class="result">
<p>今回のリクエスト:{{ method }}</p>
<p class="fortune">{{ result }}</p>
<p>選択肢:{{ choices | join("・") }}</p>
</section>
<section>
<h2>いつものおみくじ(GET)</h2>
<p>大吉・吉・凶の中から1つ選ぶよ。</p>
<form action="/" method="get">
<button type="submit">いつものおみくじを引く</button>
</form>
</section>
<section>
<h2>オリジナルおみくじ(POST)</h2>
<form action="/" method="post">
<label for="choices">選択肢(空白で区切って書こう)</label>
<input id="choices" name="choices" type="text" placeholder="例:大吉 中吉 小吉 末吉" required>
<button type="submit">オリジナルおみくじを引く</button>
</form>
</section>
</main>
</body>
</html>
static/style.css:見た目を整えるbody {
margin: 0;
padding: 24px;
background: #f5f3eb;
color: #24352b;
font-family: sans-serif;
line-height: 1.8;
}
main {
max-width: 680px;
margin: 0 auto;
}
section {
margin-bottom: 24px;
padding: 16px 24px;
background: white;
border-radius: 8px;
}
h2 {
font-size: 1.1rem;
}
.result {
text-align: center;
border: 3px solid #b23a2e;
}
.fortune {
margin: 0;
color: #b23a2e;
font-size: 3rem;
font-weight: bold;
}
form {
display: grid;
gap: 8px;
}
input, button {
padding: 10px;
font: inherit;
}
button {
background: #b23a2e;
color: white;
border: none;
cursor: pointer;
}
ターミナルがapp.pyのある2_get_postフォルダを開いていることを確認して、実行する。
python app.py
ブラウザで次のURLを開こう。index.htmlを直接ダブルクリックするのではなく、FlaskのURLへアクセスしてね。
http://127.0.0.1:8000/
GETになっているね。大吉 中吉 小吉 末吉と書いて、「オリジナルおみくじを引く」を押す。「今回のリクエスト」がPOSTになり、書いた選択肢から結果が出る。カレー ラーメン すしのように、今日の晩ごはんを決めるおみくじにもできるよ。選択肢は半角スペースでも全角スペースでも区切れるよ。
アドレス欄にURLを入力してページを開くと、ブラウザはGETのリクエストを送る。だから、ページを開いただけで「いつものおみくじ」が引かれていたんだ。
<form action="/" method="get">
<button type="submit">いつものおみくじを引く</button>
</form>
| 書き方 | 役割 |
|---|---|
action="/" |
送り先のURL |
method="get" |
GETで送るという指定 |
type="submit" |
フォームを送るボタン |
このフォームには入力欄がないので、送る値はない。ボタンを押すと/へGETのリクエストが送られ、ページを開き直したのと同じことになるよ。アドレス欄の最後に?が付くのは、「GETで送る値(ここでは空っぽ)」の目印なんだ。
<form action="/" method="post">
<input id="choices" name="choices" type="text" ... required>
<button type="submit">オリジナルおみくじを引く</button>
</form>
method="post"なので、ボタンを押すとPOSTで送られる。送り先は、GETと同じ/だよ。
入力欄のname="choices"が、送るときの名前になる。
入力欄に「大吉 中吉 小吉 末吉」と書く
↓ ボタン
POST /
choices = 大吉 中吉 小吉 末吉
POSTでは、入力した値はURLではなく、リクエストの本文(ボディ)に入って送られる。だからアドレス欄には選択肢が出てこないんだ。
requiredは、空欄のまま送らないようブラウザに確認してもらう指定だよ。
request.formから受け取るchoices = request.form.get("choices", "").split()
requestには、ブラウザから届いたリクエストの情報が入っている。POSTで送られたフォームの値は、request.formから取り出すよ。
| 書き方 | 役割 |
|---|---|
request.form.get("choices", "") |
choicesという名前の値を取り出す。なければ空文字列"" |
.split() |
文字列を空白で区切って、リストにする |
"大吉 中吉 小吉 末吉"
↓ split()
["大吉", "中吉", "小吉", "末吉"]
入力欄のnameと、request.form.get()の名前は、そろえておく必要があるよ。
@app.route("/", methods=["GET", "POST"])
def omikuji():
if request.method == "POST":
choices = request.form.get("choices", "").split()
else:
choices = DEFAULT_CHOICES
methods=["GET", "POST"]は、「/ではGETとPOSTの両方を受け付ける」という設定。書かなかった場合はGETだけを受け付けるよ。
request.methodには、届いたリクエストのメソッドが"GET"や"POST"という文字列で入っている。if文で、選択肢をどちらにするか分けているんだ。
ページを開く・いつものボタン → GET / → choices = ["大吉", "吉", "凶"]
オリジナルのボタン → POST / → choices = フォームに書いた選択肢
同じURLでも、メソッドが違えば別のお願いになる。これがGETとPOSTを区別する意味だよ。
if not choices:
choices = DEFAULT_CHOICES
入力欄にスペースだけを書いて送ると、split()の結果は空のリスト[]になる。空のリストからは選べないので、そのときは「いつものおみくじ」にしている。Pythonでは、空のリストは条件式で偽として扱われるよ。
random.choice()result = random.choice(choices)
random.choice()は、リストから要素を1つランダムに選ぶ関数だよ。おみくじの仕組みは、この1行だけ!
return render_template(
"index.html",
method=request.method,
choices=choices,
result=result,
)
render_template()で、メソッド・選択肢・結果をHTMLへ渡している。HTML側では{{ }}で表示するよ。
<p>今回のリクエスト:{{ method }}</p>
<p class="fortune">{{ result }}</p>
<p>選択肢:{{ choices | join("・") }}</p>
choices | join("・")は、リストの要素を「・」でつないで1つの文字列にする書き方。["大吉", "吉", "凶"]が大吉・吉・凶と表示されるよ。
いつものおみくじとオリジナルおみくじを引いた後、アドレス欄を見てみよう。どちらも/(GETでは最後に?)で、オリジナルの選択肢はどこにも出ていないね。
「いつものおみくじ」を引いた後に再読み込みすると、何も聞かれずに新しい結果が出る。
「オリジナルおみくじ」を引いた後に再読み込みすると、「フォームを再送信しますか?」のような確認が出るよ。POSTは「データを送って処理してもらう」お願いなので、ブラウザが「同じ内容をもう一度送っていい?」と確認してくれるんだ。
「続行」を押すと、同じ選択肢がもう一度POSTで送られ、新しい結果が出る。
index.htmlのオリジナルおみくじのフォームを、method="get"に書き換えて保存しよう。
<form action="/" method="get">
ブラウザを再読み込みしてから、選択肢を書いて「オリジナルおみくじを引く」を押す。
http://127.0.0.1:8000/?choices=カレー+ラーメン+すし
アドレス欄に選択肢が出てきたね。GETでは、送る値がURLの?より後ろに付くんだ。
でも、出てくる結果は大吉・吉・凶のどれか。Flaskはrequest.methodが"GET"なので、いつものおみくじにしているんだ。
確かめたら、method="post"に戻して保存しよう。
| GET | POST | |
|---|---|---|
| 値を入れる場所 | URLの?より後ろ |
リクエストの本文 |
| アドレス欄・履歴 | 値が見える・残る | 値は見えない |
| 再読み込み | そのまま送り直す | 再送信の確認が出る |
| Flaskでの受け取り方 | request.args |
request.form |
| 向いている使い方 | ページを開く、検索する | ログイン、登録、送信 |
もしパスワードをGETで送ると、URLごと履歴に残り、画面をのぞいた人にも見えてしまう。パスワードや個人情報、登録する内容はPOSTで送るのが基本だよ。
GETでURLに付いた値は、Flaskでは
request.argsで取り出せる。今回は使わないけれど、検索ページなどでよく使うよ。
ブラウザの検証ツールを開いて、「Network(ネットワーク)」を選ぼう。
Request MethodがGETになっているか見る。Request MethodがPOSTになっているか見る。choicesと書いた選択肢を探す。CSSなどのリクエストも表示されるので、localhostや127.0.0.1という名前のリクエストに注目してね。
POSTでも、検証ツールを開けば送った値が見えるね。POSTは「アドレス欄や履歴に残さない」送り方で、「誰にも見えない」送り方ではないんだ。
通信そのものを守るのは、URLが
https://で始まるときの仕組みの役目だよ。
DEFAULT_CHOICESに"中吉"、"小吉"、"末吉"を追加しよう。HTMLの「大吉・吉・凶の中から1つ選ぶよ。」の文も、合わせて直してね。
オリジナルおみくじに大吉 大吉 大吉 凶と書いて、何度か引いてみよう。大吉が出やすくなるね。random.choice()はリストの要素を同じ確率で選ぶので、同じ言葉を多く入れると出やすくなるんだ。
次の場合、GETとPOSTのどちらが向いているか考えよう。
文字の代わりに画像で引ける、おみくじの画像版だよ。GETでは大吉・吉・凶の画像から、POSTではフォームで選んだ画像から1枚を選ぶ。早く終わった人は、動かして遊んでみよう。
画像おみくじのフォルダ
├── app.py
├── templates
│ └── index.html
└── static
├── style.css
└── img
├── daikichi.png
├── kichi.png
└── kyou.png
static/imgには、大吉・吉・凶の画像を用意しよう。
app.pyimport base64
import random
from flask import Flask, render_template, request
app = Flask(__name__)
# いつものおみくじの選択肢(名前と画像の場所)
DEFAULT_CHOICES = [
{"name": "大吉", "src": "/static/img/daikichi.png"},
{"name": "吉", "src": "/static/img/kichi.png"},
{"name": "凶", "src": "/static/img/kyou.png"},
]
def to_data_url(image):
# 画像のデータを文字に変換して、<img>でそのまま表示できる形にする
data = base64.b64encode(image.read()).decode()
return f"data:{image.mimetype};base64,{data}"
@app.route("/", methods=["GET", "POST"])
def omikuji():
if request.method == "POST":
# フォームで送られた画像を、1枚ずつ選択肢に加える
choices = []
for image in request.files.getlist("images"):
if image.filename:
choices.append({"name": image.filename, "src": to_data_url(image)})
else:
choices = DEFAULT_CHOICES
# 画像が送られていないときは、いつものおみくじにする
if not choices:
choices = DEFAULT_CHOICES
result = random.choice(choices)
return render_template(
"index.html",
method=request.method,
choices=choices,
result=result,
)
if __name__ == "__main__":
app.run(debug=True, port=8000)
templates/index.html<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>画像おみくじ</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<main>
<h1>画像おみくじ</h1>
<section class="result">
<p>今回のリクエスト:{{ method }}</p>
<img class="fortune" src="{{ result.src }}" alt="{{ result.name }}">
<p>{{ result.name }}</p>
<p>選択肢</p>
<div class="choices">
{% for choice in choices %}
<img src="{{ choice.src }}" alt="{{ choice.name }}">
{% endfor %}
</div>
</section>
<section>
<h2>いつものおみくじ(GET)</h2>
<p>大吉・吉・凶の中から1つ選ぶよ。</p>
<form action="/" method="get">
<button type="submit">いつものおみくじを引く</button>
</form>
</section>
<section>
<h2>画像でオリジナルおみくじ(POST)</h2>
<form action="/" method="post" enctype="multipart/form-data">
<label for="images">画像を選ぼう(何枚でもOK)</label>
<input id="images" name="images" type="file" accept="image/*" multiple required>
<button type="submit">オリジナルおみくじを引く</button>
</form>
</section>
</main>
</body>
</html>
static/style.cssbody {
margin: 0;
padding: 24px;
background: #f5f3eb;
color: #24352b;
font-family: sans-serif;
line-height: 1.8;
}
main {
max-width: 680px;
margin: 0 auto;
}
section {
margin-bottom: 24px;
padding: 16px 24px;
background: white;
border-radius: 8px;
}
h2 {
font-size: 1.1rem;
}
.result {
text-align: center;
border: 3px solid #b23a2e;
}
.fortune {
width: 240px;
max-width: 100%;
height: 240px;
object-fit: contain;
}
.choices {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 8px;
}
.choices img {
width: 64px;
height: 64px;
object-fit: contain;
border: 1px solid #ddd;
border-radius: 4px;
}
form {
display: grid;
gap: 8px;
}
input, button {
padding: 10px;
font: inherit;
}
button {
background: #b23a2e;
color: white;
border: none;
cursor: pointer;
}
| 症状 | 確認すること |
|---|---|
No module named 'flask' |
今使っているPythonでpython -m pip install flaskを実行したか |
TemplateNotFound |
app.pyの隣のtemplatesフォルダにindex.htmlがあるか |
{{ ... }}がそのまま表示される |
HTMLファイルを直接開かず、FlaskのURLを開いているか |
405 Method Not Allowed |
@app.route()にmethods=["GET", "POST"]を書いたか |
| オリジナルで引いても大吉・吉・凶になる | フォームがmethod="post"か。入力欄のnameとrequest.form.get()の名前が一致しているか |
Address already in use |
前回のFlaskが動いていないか。先にそのターミナルでCtrl+Cを押そう |
method="get"のフォームではGETが送られるmethod="post"のフォームでは、入力した値がリクエストの本文に入って送られるmethods=["GET", "POST"]とrequest.methodで、1つのURLでGETとPOSTを受け分けられるrequest.formで受け取る授業の最後はターミナルでCtrl+Cを押して、テストサーバーを停止しよう。
次回は、今日の「送る・受け取る」を使って、川柳APIとつながるWebサイトを作るよ!