前回Flaskのフォームからデータを送信する方法を扱いました。ただ、WEBアプリを作るならデータベース(以後DB)を使わないと便利なアプリ、サービスを作ることができません。
Python(Flask)ではSQLを書いて実行することもできなくはないですが、SQLAlchemy
と言うORマッパーを使うのが一般的かと思います。
同じPythonのフレームワークDjnagoには独自のORマッパーが標準でついているのでそれを使えばいいですが、Flaskなどの軽量フレームワークにはORマッパーがついていないのでSQLAlchemy
を導入するところから始めます。
ORマッパーはSQLを書かずにPythonなどの言語を書くだけでSQLを作成してDBのデータを取り扱えるものです。
最近のWEBアプリ開発ではORマッパーを使った開発が一般的です。
この記事は昔書いたDjangoでDB使う記事をFlask用に改造して修正したものです。内容的には大体同じです。
Pythonで作るFlaskアプリ記事一覧
内容 | |
---|---|
part1 | 簡単なページ作成 |
part2 | HTMLテンプレート表示 |
part3 | "画像" "CSS" "Javascript"実装 |
part4 | フォーム送信 |
part5 | データベースの値取得・更新← ココ |
ついでに、SQLAlchemy使いますのでDBから取得するときにどんなふうに書くんだっけ?ってよくなるので逆引きできる記事あったらいいなーと思って書いた記事
ソースコード
この章のコードは以下です。確認やコピペでどうぞ
0. 準備:データベース(DB)を入れてFlaskで使う
DBはなんでもいいですが、今回はテストアプリなのでSQLite
を使用します。
引き続き仮想環境はpipenvなのでインストールするときはpipenv
を使用します。
※ pipenv使ってないならpip使ってインストール。
$ pipenv install Flask-SQLAlchemy
設定を記述するためのファイルconfig.py
に追記。
SQLALCHEMY_DATABASE_URI
に書いた名前でsqliteのファイルが作成されます。今回はsample_flask.db
にしようと思います。ファイル名は任意です。
DEBUG = True
SQLALCHEMY_DATABASE_URI = 'sqlite:///sample_flask.db'
SQLALCHEMY_TRACK_MODIFICATIONS = True
続いて__init__.py
に追記。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy # 追加
app = Flask(__name__)
app.config.from_object('testapp.config')
db = SQLAlchemy(app) # 追加
import testapp.views
__init__.py
に書いたことで、ファイル一つ一つにDBの設定を書かなくてもdb
をimportしてdb
を使うことでSQLAlchemyでDBを操作できるので便利に使えます。
1. モデルを作成
テーブルの定義を書いたり、DBにまつわる処理を記述するのがモデルです。テーブルを作成したいので定義を書いていきます。
1-1. テーブルを作る
データを保存するためにテーブルを作成する必要があります。
ORマッパー使わないとSQLで定義を書いて作成することになりますが、SQLAlchemyマッパーならPythonのclass(モデル)にテーブルの定義を記述すればテーブルをいい感じで作ってくれます。
データを保存する手順として
- モデルファイルを作成してテーブルのデータ構造などを記述
- ターミナルにてテーブル作成コマンド実行
という流れでテーブルが作れます。
DjangoだとORマッパーがついていて、使わないかもしれないテーブルも自動で作ってくれます。それが便利だったら邪魔だったりします。Flaskは軽量フレームワークです。良くも悪くも最低限しか機能がないので自分で良いように作っていくことができるのがFlaskの強みです。
1-2. models.py作成
データを保存するためにDBにテーブルを作っていきます。
modelsフォルダを作成。今回の例ならtestappのディレクトリ配下になります。
from testapp import db
from datetime import datetime
class Employee(db.Model):
__tablename__ = 'employee'
id = db.Column(db.Integer, primary_key=True) # システムで使う番号
name = db.Column(db.String(255)) # 社員名
mail = db.Column(db.String(255)) # メール
is_remote = db.Column(db.Boolean) # リモート勤務しているか
department = db.Column(db.String(255)) # 部署
year = db.Column(db.Integer, default=0) # 社歴
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now) # 作成日時
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now, onupdate=datetime.now) # 更新日時
これがデータ構造です。例としていろんな社員(従業員)情報管理アプリでも作ってみます。
データの定義
書いたコードの説明をします。説明いらないから取り合えず動かしたいだけなら2. DBにテーブルを作成に行っても大丈夫です。
以下がテーブルに登録するデータの型を定義しています。
- db.Integer: 整数型のデータ保存
- db.String: 文字列のデータ保存
- db.Boolean: TrueかFalseのデータ保存
- db.DateTime: 日時データの保存。日にちだけで良いならdb.Date使用する
db
は__init__.py
に記述したdb
と名前つけてimportしています。なので解説する人や記事によってdb.
は変わります。
オプションで定義をより細かく設定
- IDは自動採番。プライマリーキー
- 文字列で
String(255)
のようにすると文字数を255文字に制限できる - オプション引数:
nullable
はデータがNULLを許可するかしないかFalse
orTrue
- オプション引数:デフォルト値を指定するなら
default
を使う。-
year
なら新規登録時に指定しなかったら0を入れる -
created_at
,updated_at
は作成時の日時を設定する
-
- オプション引数:
onupdate
を使うとデータが更新されたときに日時を更新できます。
2. DBにテーブルを作成
ちゃんと開発するならDBの変更履歴を使えるFlask-Migrate
というものをインストールして使うと良いと思います。
今回は基礎的なアプリなので一番手軽な方法でDBを作成します。
2.1. 手軽な方法でDB・テーブルを作成
この方法はターミナルを開いてpythonでSQLAlchemy
のメソッドを直接実行する方法です。
今回、employee
テーブルを作成するので、作成するemployee
モデルをimportします。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object('testapp.config')
db = SQLAlchemy(app)
from .models import employee # 追加
import testapp.views
作成する employeeを読み込まないとテーブルが作成されなかったのでこのように読み込んでいます。
仮想環境のpipenvの中でコマンドを実行する場合はpipenv shell
で仮想環境の中に入る必要があります。
$ pipenv shell
$ python
>>> from testapp import db
>>> db.create_all()
問題なければ/Users/dev/flask/testapp/sample_flask.db
と言うファイルが作成されたと思います。
これがSQLiteのデータを保存しているファイルになります。
3. FlaskでDBのCRUD(登録・取得・更新・削除)を行う
DBとテーブルを作成できたのでいよいよDBを使ったWEBアプリ作成をしていこうと思います。
まず最初は新規作成を行います。
3.1 CRUDを作る。CRUD (Create): データ新規作成・登録
formからデータを受け取ってデータをDBに登録までを一気にやると初心者向け記事では優しくないかなと思いましたので、まずは特定のURLにアクセスすると固定の値が作成されるようにして、少しずつ改造していこうと思います。
3.1.1 新規データ追加ページ(仮)を作る
少しずつ改造していこうと思うので、ボタンを押したら固定の値がDBに書き込まれる機能を最初に作ります。
views.pyに以下を追加してページを追加のためのページを作成します。
ここは前回の記事の復習ですのでコードのみ書きます。
@app.route('/add_employee', methods=['GET', 'POST'])
def add_amployee():
return render_template('testapp/add_employee.html')
テンプレートも追加必要ですね。
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>データの追加だけ行うページ</h2>
<form action="/add_employee" method="POST">
<button type="submit" class="btn btn-primary">従業員追加</button>
</form>
</div>
{% endblock %}
今回も共通化したlayout.html
とbootstrap使ってます。
ページが正しくできたか確認します。以下にアクセスすればとりあえず画面は表示されます。
ボタンを押すとPOSTで送信
とブラウザに表示されるかと思います。
※ 元のページに戻るときはアドレスバーをhttp://127.0.0.1:5000/add_employee
と入力して移動してください。リロードだとうまくいかないので
3.1.2 固定値をDBに書き込む
POSTでアクセスされたらDBにデータを書き込むコードを書いてみましょう。
from flask import render_template, request, redirect, url_for
.
.
from testapp import db
from testapp.models.employee import Employee
.
.
.
@app.route('/add_employee', methods=['GET', 'POST'])
def add_employee():
if request.method == 'GET':
return render_template('testapp/add_employee.html')
if request.method == 'POST':
employee = Employee(
name='Tanaka',
mail="aaa@aa.com",
is_remote=False,
department="develop",
year=2
)
db.session.add(employee)
db.session.commit()
return redirect(url_for('index'))
従業員追加というボタンを押すとindexのページ(トップページ)に戻って何も起こっていないように思えますが、データが作成されています。
コード説明
if request.method == 'GET'
は新規登録のためのページを表示(htmlをレンダリング)する処理のためのコードです。add_employee.html
を元にHTML表示させる処理だけして処理終了です。
POSTで送信された場合には、その下の if request.method == 'POST':
のブロックに入ります。
Employee
がモデル。モデルがテーブルのカラムを持っているようなイメージ持つといいかも。各カラムの値に固定値を入れるとemployee
という変数(モデルインスタンス)がDBのテーブルで言ったらレコード1行分のデータ入ってる感じになります。
データが入ったのでdb.session.add(モデルインスタンス)
, db.session.commit()
でDBに登録しています。
add
だけだと登録されなくて、commit
することでDBに登録できますので登録の際はadd
, commit
忘れずに!
処理が終わったらindex
ページにリダイレクト。
リダイレクトがないとボタンを押したけど、画面に全く変化ないので違和感たっぷりになりますので。
3.2 フォームからデータを受け取ったデータをDBに登録
前回の記事でフォームから受け取ったデータを受け取って処理を書く方法の基礎に触れました。
今度は新しいページにフォーム作って、フォームから受け取ったデータをDBに登録しようと思います。
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>データの追加だけ行うページ</h2>
<form action="/add_employee" method="POST">
<label for="name">従業員名</label>
<input name="name"></input>
<label for="mail">メールアドレス</label>
<input name="mail"></input>
<label for="name">リモートワークの有無</label>
<input type="checkbox" name="is_remote">
<label for="department">部署</label>
<input name="department"></input>
<label for="year">勤続年数</label>
<input type="number" name="year" min="0">
<button type="submit" class="btn btn-primary">従業員追加</button>
</form>
</div>
{% endblock %}
全て1行になって見にくいですが入力に必要なフォームはできました。
見た目綺麗にするのは後にして先に進めます。
@app.route('/add_employee', methods=['GET', 'POST'])
def add_employee():
if request.method == 'GET':
return render_template('testapp/add_employee.html')
if request.method == 'POST':
form_name = request.form.get('name') # str
form_mail = request.form.get('mail') # str
# チェックなしならFalse。str -> bool型に変換
form_is_remote = request.form.get('is_remote', default=False, type=bool)
form_department = request.form.get('department') # str
# int, データないとき0
form_year = request.form.get('year', default=0, type=int)
employee = Employee(
name=form_name,
mail=form_mail,
is_remote=form_is_remote,
department=form_department,
year=form_year
)
db.session.add(employee)
db.session.commit()
return redirect(url_for('index'))
適当に入力してボタンを押せばデータが登録されます。
コード説明
request.form
を使うとImmutableMultiDict
という形で普通のdictっぽいものに機能が少し追加されたものの中にデータが格納されています。まあ、dictの形式でformに入力した情報が入ってると思っておいてもいいかと思います。
- dictの機能の、
.get('ここにキー')
を使ってる。request.form['name']
みたいな書き方と基本は同じ。-
.get()
で取得するとデータが未入力の場合None
になる - 例えば
request.form['is_remote']
とするとチェックボックスをクリックしてないとデータがカラ。なのでエラーになる。それを防ぐ -
default
のオプションを引数を入れることでNoneだったときに代わりにデフォルトの値を指定できる。 -
type
のオプションを引数を入れることで指定の型に変換してくれる。strがデフォルト値なのでstr型でいいなら省略可能。int, boolなど使える
-
-
request.form
はstr
型。注意!-
つまり
request.form.get('is_remote')
と取得するだけだと'false'
と言う「文字列」になる。 - falseにしたつもりなのに判定式書くと文字列の
'false'
はTrueとなる!ついでに'true'
はTrue。
-
つまり
3.3 CRUDを作る。【Read】: DBのデータ取得・読み取り
DBに入っているデータを取得してみます。先程までに登録したデータを取得してデータを表示します。
作成するのは二つのページ。
全データ取得して表示する一覧ページ → 一覧ページ作成
個別のデータを取得して表示する詳細ページ → 詳細ページ作成
3.3.1 一覧ページ作成
最初にEmployeeのデータ全てを取得して画面に表示する処理を書いてみます。
以下を追記。
@app.route('/employees')
def employee_list():
employees = Employee.query.all()
return render_template('testapp/employee_list.html', employees=employees)
employee_list.html
というテンプレートにデータを埋め込んで表示させます。
employee_list.html
はないのでファイルを作成。
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>従業員一覧</h2>
<ul>
{% for employee in employees %}
<li>名前: {{ employee.name }} メール: {{ employee.mail }}</li>
{% endfor %}
</ul>
</div>
{% endblock %}
URLは以下にて作成してます。
この表示みたいに登録したデータ表示されたらOKです。
コード説明
- views.pyで
.query.all()
は全データ取得という意味。employees
という名前の変数(任意の名前でOK)にDBから取得したデータを入れる。 -
employees
という名前でテンプレートに変数として渡す - テンプレートで渡された
employees
をfor
を使って一つずつ(1レコードずつ)データを分解してデータを埋め込む - テンプレートの共通部分は
layout.html
に記述している
見た目改善 bootstrapのtable
せっかくなので他のデータもまとめて表示したいのでtableに直してみます。使っているデータは同じでbootstrapでtableの見た目を整えただけですのでviews.pyは変更いりません。
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>従業員一覧</h2>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">名前</th>
<th scope="col">メール</th>
<th scope="col">リモート状況</th>
<th scope="col">部署</th>
<th scope="col">勤続年数</th>
</tr>
</thead>
<tbody>
{% for employee in employees %}
<tr>
<th scope="row">{{ employee.id }}</th>
<td>{{ employee.name }}</td>
<td>{{ employee.mail }}</td>
{% if employee.is_remote %}
<td>リモート</td>
{% else %}
<td>出勤</td>
{% endif %}
<td>{{ employee.department }}</td>
<td>{{ employee.year }}年</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endblock %}
見た目が少しまともになりましたね。
3.3.2 詳細ページ作成
全データは取得できたので詳細ページを作ろうと思います。
一覧ページは全従業員のデータを表示しましたが、詳細ページでは一人の従業員の詳細情報を表示するページになります。
まあ、よくある機能というかページですが今回テーブルを1つしか作っていないので詳細ページなのに一覧と表示する内容が同じです
まあ、そこは気にせずに作り方を紹介します。
一覧ページと作りがほぼ同じなので流用して少しだけ修正します。
employee_detail.html
というテンプレートを作成。このテンプレートに特定の従業員の情報を表示するページを作ります。
views.py
に下記を追記。
@app.route('/employees/<int:id>')
def employee_detail(id):
employee = Employee.query.get(id)
return render_template('testapp/employee_detail.html', employee=employee)
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>従業員詳細</h2>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">名前</th>
<th scope="col">メール</th>
<th scope="col">リモート状況</th>
<th scope="col">部署</th>
<th scope="col">勤続年数</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">{{ employee.id }}</th>
<td>{{ employee.name }}</td>
<td>{{ employee.mail }}</td>
{% if employee.is_remote %}
<td>リモート</td>
{% else %}
<td>出勤</td>
{% endif %}
<td>{{ employee.department }}</td>
<td>{{ employee.year }}年</td>
</tr>
</tbody>
</table>
</div>
{% endblock %}
のようにアクセスすると詳細ページ表示できます。URLの一番最後の数値がデータのIDに当たるので、変えるとこれまでDBに登録した該当のデータが表示できます。
コード説明
views.pyのデコレータの部分'/employees/<int:id>
のように書くと
のようにアクセスするとid = 1
で値が入ります。
-
def employee_detail(id)
の引数のid
に値が入る-
<int:id>
のように型を書いたのでint型で取得できる
-
-
.query.get(ここに値)
はプライマリーキーを値として与えることでデータを取得できる- idがプライマリーキーになっているケースが多いと思う
-
.query.all()
は全て取得(複数件取得)なのでリストのデータを取り出すような感じで、forで一つずつ取り出す必要があったが、get()
は単一のデータなのでforなどで一つずつ取り出す必要ない
forで取り出す必要ないのでforを消しているのと、単一レコードのデータなのでemployees
→employee
の単数系にしてますがその他はほぼ同じです。
補足
飛ばしてもOKです。存在しないIDを入れた場合にエラー出したいという場合があります。
このIDのデータがないです。このままだとページは表示されるけどデータはないような表示なると思います。
これが容認できないケースもあるのでアクセスするとエラーにさせることができます。
今回は詳しくはやりませんがエラーを出してエラーページを表示するということをすると完成度が上がります。
@app.route('/employees/<int:id>')
def employee_detail(id):
employee = Employee.query.get_or_404(id)
# もしくは以下のようにデータが一つでないとエラー出すことで1つなのを補償する方法も。
# employee = Employee.query.filter(Employee.id == id).one()
return render_template('testapp/employee_detail.html', employee=employee)
get_or_404
はデータが取得できない場合404 Not Foundを出してくれる便利機能。.one()
を使った方法はデータが一つでない場合はエラーが出せるのでtry catchで処理を工夫するといいと思います。
3.4 CRUDを作る。【Update】: DBのデータ更新・編集
以前入力した情報を編集する機能を作ります。
まずは最初に、formを設置して情報を編集できるページを作成しましょう。
3.4.1 編集ページ作成
以下を追記して、その後employee_edit.html
を作成します。
@app.route('/employees/<int:id>/edit', methods=['GET'])
def employee_edit(id):
# 編集ページ表示用
employee = Employee.query.get(id)
return render_template('testapp/employee_edit.html', employee=employee)
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>データの編集ページ</h2>
<form action="{{ url_for('employee_update', id=employee.id) }}" method="POST">
<label for="name">従業員名</label>
<input name="name"></input>
<label for="mail">メールアドレス</label>
<input name="mail"></input>
<label for="name">リモートワークの有無</label>
<input type="checkbox" name="is_remote">
<label for="department">部署</label>
<input name="department"></input>
<label for="year">勤続年数</label>
<input type="number" name="year" min="0">
<button type="submit" class="btn btn-primary">従業員追加</button>
</form>
</div>
{% endblock %}
以下にアクセスすればページが表示できると思います。
コード解説
以前作った新規追加ページに似ていますが、<form>
の送り先を動的に変更できるようにurl_for()
を使っています。
編集ページのURLは例えばhttp://127.0.0.1:5000/employees/1/edit
のようになりidが「1」の人の編集ページです。
-
/employees/1/edit
のidを示す1をユーザーに合わせて送り先を変更しないと別のユーザーの情報を編集してしまうという事態になってしまいますのでここは動的に作る必要がるわけです。 -
url_for('employee_update', id=employee.id)
は第一引数にviews.pyに書いたメソッド名、第二引数はメソッドの上に書いたデコレータのrouteに'/employees/<int:id>/edit'
と書きました。なので、id
が必要なのでDBから取ったidを与えることで送り先と編集データが一致します。
検証ツールで送り先確認
検証できる人は一応確認すると理解が深まるかなと思います。
検証ツールで確認してみるとこのページのURL http://127.0.0.1:5000/employees/1/update
の「1」の数字に合わせてフォームでの送り先が動的に変化します。
http://127.0.0.1:5000/employees/1/edit
http://127.0.0.1:5000/employees/2/edit
などで試してみてください。
chromeならF12押してElementの項目にて画像の左上のスマホアイコンの隣のポインターみたいなアイコン押して該当箇所を画面上でクリックすれば確認できます。
3.4.2 編集ページ改善
今の状態だと編集ページのくせに、以前の入力したデータが表示されていないのでとても不便です。
ということで初期値として以前DBに登録したデータを表示させるようにします。
{% extends "layout.html" %}
{% block content %}
<div class="container mt-4">
<h2>データの編集ページ</h2>
<form action="{{ url_for('employee_update', id=employee.id) }}" method="POST">
<label for="name">従業員名</label>
<input name="name" value="{{ employee.name }}"></input>
<label for="mail">メールアドレス</label>
<input name="mail" value="{{ employee.mail }}"></input>
<label for="name">リモートワークの有無</label>
<input
type="checkbox"
name="is_remote"
{% if employee.is_remote %}checked{% endif %}
>
<label for="department">部署</label>
<input name="department" value="{{ employee.department }}"></input>
<label for="year">勤続年数</label>
<input type="number" name="year" min="0" value="{{ employee.year }}">
<button type="submit" class="btn btn-primary">従業員追加</button>
</form>
</div>
{% endblock %}
-
入力フォームの場合は
value
を使います(この値がフォームの値になる)。- views.pyで
employee = Employee.query.get(id)
の箇所でDBからデータは取得してテンプレートでemployee
で使えるようにしていた。render_template('testapp/employee_edit.html', employee=employee)
の第二引数。 -
employee
に各データがはいっているのでこれを使ってデータを埋め込むことで表示が可能です。
- views.pyで
-
checkboxはTure or Falseではチェックの切り替えができなかったので
{% if employee.is_remote %}checked{% endif %}
のようにすることでTrueだった場合にはチェックボックスにチェックが入るようにしています。
(...もう少しかっこよく書ける方法あったら教えてください。)
3.4.3 更新機能実装
編集ページはできたので送られてきたデータを更新するメソッドを書きます。
以下を追記します。
@app.route('/employees/<int:id>/update', methods=['POST'])
def employee_update(id):
employee = Employee.query.get(id) # 更新するデータをDBから取得
employee.name = request.form.get('name')
employee.mail = request.form.get('mail')
employee.is_remote = request.form.get('is_remote', default=False, type=bool)
employee.department = request.form.get('department')
employee.year = request.form.get('year', default=0, type=int)
db.session.merge(employee)
db.session.commit()
return redirect(url_for('employee_list'))
これで完成したので
問題なかったら一覧画面にリダイレクトされてデータが更新されたのが確認できると思います。
コード解説
-
テンプレートで
<form action="{{ url_for('employee_update', id=employee.id) }}" method="POST">
の箇所に書いた動的に生成したURLのidがdef employee_update(id):
の引数のidに入る。 -
employee = Employee.query.get(id)
でidを指定して更新するデータを取得 -
各データを
employee.name = request.form.get('name')
のようにデータを更新していきます。request.form.get()
は新規登録と同じなので説明は省略。 -
データを入れたら、
db.session.merge(employee)
のようにmergeとすることでデータを更新できる。そしてcommit()
をしてDBに更新したデータを登録。 -
更新が終わったら
return redirect(url_for('employee_list'))
で従業員一覧ページにリダイレクト。- テンプレートでも使った
url_for
。employee_list
のようにメソッド名を指定するとURLを作成してくれる関数
- テンプレートでも使った
3.5 CRUDを作る。【Delete】: DBのデータ削除
最後にデータの削除です。これはこれまでのものがわかっていれば簡単に理解できると思います。
一覧ページで削除ボタンを押したら削除するというのが一般的かと思いますので一覧ページに削除ボタンをつけようと思います。
ページはないのでviews.pyにはPOSTの処理だけ追記します。
@app.route('/employees/<int:id>/delete', methods=['POST'])
def employee_delete(id):
employee = Employee.query.get(id)
db.session.delete(employee)
db.session.commit()
return redirect(url_for('employee_list'))
一覧ページの該当箇所を編集。勤続年数の下と<td>{{ employee.year }}年</td>
の下にボタンと送信機能を追加してます。
<table class="table table-striped">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">名前</th>
<th scope="col">メール</th>
<th scope="col">リモート状況</th>
<th scope="col">部署</th>
<th scope="col">勤続年数</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% for employee in employees %}
<tr>
<th scope="row">{{ employee.id }}</th>
<td>{{ employee.name }}</td>
<td>{{ employee.mail }}</td>
{% if employee.is_remote %}
<td>リモート</td>
{% else %}
<td>出勤</td>
{% endif %}
<td>{{ employee.department }}</td>
<td>{{ employee.year }}年</td>
<td>
<form action="{{ url_for('employee_delete', id=employee.id) }}" method="POST">
<input class="btn btn-danger btn-sm" type="submit" value="削除">
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
削除ボタンを一番後ろにつけました。以前作成した新規登録ページにてID 3の斉藤さんを試しに追加したのでこれを削除してみます。
ボタンを押したらデータが消えました。本当は削除ボタンを押したら、モーダルなんかを出して「削除してもいいですか?」とか出した方がいいですが、この記事は基礎的な内容なのでそれは機会があればということで。
4. 最後に表示とリンクを少し整える
これまでURL直打ちで対応していたのでURLを追加するのと、デザインがテキトーなところがあるので修正して完了にします。
デザインは既に導入したbootstrap 5
を使います。
4.1. 一覧画面に詳細画面遷移リンクをつける【bootstrap】
まずは名前をクリックしたら詳細ページにリンクできるようにする
<td>
<a class="text-decoration-none" href="{{ url_for('employee_detail', id=employee.id) }}">
{{ employee.name }}
</a>
</td>
押したら遷移できるか確認してください。
4.2. 詳細画面に編集画面ボタンをつける【bootstrap】
情報の編集ページに遷移できるリンクやボタンなどが今はないので不便です。
なので詳細画面に「編集ページに遷移できるボタン」を設置しようと思います。
tableの右上にリンクのボタンを設置しようと思います。
.
.
<div class="container mt-4">
<div class="row mb-4">
<div class="col-9">
<h2>従業員詳細</h2>
</div>
<div class="col-3">
<a class="btn btn-outline-primary" href="{{ url_for('employee_edit', id=employee.id) }}" role="button">情報編集</a>
</div>
</div>
<table class="table table-striped">
.
.
情報編集ボタンを押すと遷移できるか確認してみてください。
4.3. bootstrapナビバーに遷移リンクをつける
放置していたbootstrapで作ったナビバーに遷移リンクをつけます。
リンクをつけただけです。
<nav class="navbar navbar-expand-lg navbar-light" style="background-color: #e3f2fd;">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('employee_list') }}">従業員管理</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNavAltMarkup" aria-controls="navbarNavAltMarkup" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNavAltMarkup">
<div class="navbar-nav">
<a class="nav-link" href="{{ url_for('add_employee') }}">新規登録</a>
<a class="nav-link" href="{{ url_for('sample_form') }}">ジャンケンアプリ</a>
</div>
</div>
</div>
</nav>
この場合動的にURLを作る必要もないのでurl_for()
使わなくてもhref="/employees"
みたいに、ベタ書きでもいいのですが、views.pyでURLを変更した時にこちらは変更忘れてページにアクセスできなくなるということがなくなるので使うと良いと思います。
4.4. 新規登録ページ・編集ページを綺麗にする
formが1行になって非常にわかりにくかったので少し綺麗にしました。見た目良くするためにcheckboxをradioに変えてますがそれ以外はbootstrapのデザインを適用させただけです。
長くなったので折り畳んでます↓
新規作成画面デザイン修正コード
{% extends "layout.html" %}
{% block content %}
<div class="container mt-5 mb-5 d-flex justify-content-center">
<div class="card px-1 py-4" style="width: 400px;">
<div class="card-body">
<h6 class="card-title mb-3">新規登録ページ</h6>
<form action="/add_employee" method="POST">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<input name="name" class="form-control" type="text" placeholder="名前"></input>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<input name="mail" class="form-control" type="text" placeholder="メールアドレス"></input>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<div class="btn-group" role="group" aria-label="is_remote toggle">
<input type="radio" class="btn-check" name="is_remote" id="is_remote1" autocomplete="off" value="false" checked>
<label class="btn btn-outline-primary" for="is_remote1">出社</label>
<input type="radio" class="btn-check" name="is_remote" id="is_remote2" autocomplete="off" value="true">
<label class="btn btn-outline-primary" for="is_remote2">リモート</label>
</div>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<input name="department" class="form-control" type="text" placeholder="部署"></input>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<input name="year" min="0" class="form-control" type="number" placeholder="勤続年数"></input>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col text-center">
<button type="submit" class="btn btn-primary">従業員追加</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
情報編集画面デザイン修正コード
{% extends "layout.html" %}
{% block content %}
<div class="container mt-5 mb-5 d-flex justify-content-center">
<div class="card px-1 py-4" style="width: 400px;">
<div class="card-body">
<h6 class="card-title mb-3">データの編集ページ</h6>
<form action="{{ url_for('employee_update', id=employee.id) }}" method="POST">
<div class="row">
<div class="col-sm-12">
<div class="form-group">
<input name="name" value="{{ employee.name }}" class="form-control" type="text" placeholder="名前"></input>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<input name="mail" value="{{ employee.mail }}" class="form-control" type="text" placeholder="メールアドレス"></input>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<div class="btn-group" role="group" aria-label="is_remote toggle">
<input type="radio" class="btn-check" name="is_remote" id="is_remote1" autocomplete="off" value="false" {% if not employee.is_remote %}checked{% endif %}>
<label class="btn btn-outline-primary" for="is_remote1">出社</label>
<input type="radio" class="btn-check" name="is_remote" id="is_remote2" autocomplete="off" value="true" {% if employee.is_remote %}checked{% endif %}>
<label class="btn btn-outline-primary" for="is_remote2">リモート</label>
</div>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<input name="department" value="{{ employee.department }}" class="form-control" type="text" placeholder="部署"></input>
</div>
</div>
</div>
<div class="row mt-2">
<div class="col-sm-12">
<div class="form-group">
<input name="year" min="0" value="{{ employee.year }}" class="form-control" type="number" placeholder="勤続年数"></input>
</div>
</div>
</div>
<div class="row mt-3">
<div class="col text-center">
<button type="submit" class="btn btn-primary">従業員追加</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
Pythonで作るFlaskアプリ記事一覧
内容 | |
---|---|
part1 | 簡単なページ作成 |
part2 | HTMLテンプレート表示 |
part3 | "画像" "CSS" "Javascript"実装 |
part4 | フォーム送信 |
part5 | データベースの値取得・更新← ココ |
ソースコード
この章のコードは以下です。確認やコピペでどうぞ