Python勉強企画、7日目もCGIです。 名簿管理をネタに、データベース処理をためしてみます。
仕様を決定しよう
全体の仕様
とある学校の生徒名簿という体のプログラムです。
機能
機能は、
・生徒情報の一覧表示
・生徒情報の新規追加
・生徒情報の編集
・生徒情報の削除
という、データベースアプリケーションに求められる機能をひととおり付けることにします。
各機能は別のページ(スクリプト)で実装することにします。
管理する情報
管理する生徒情報は以下のようにします。
項目 | データの仕様 | データ例 |
---|---|---|
学籍番号 | 生徒を一意に判別できる。5桁の半角数字文字列 | “01001” |
専攻 | 1~10文字の文字列。日本語可。 | “普通科” |
学年 | 1~3の整数値 | 2 |
氏名 | 1~20文字の文字列。日本語可。 | “山田太郎” |
生年月日 | 日付 | 2005-07-02 |
生徒情報の一覧表示
今日はまず、生徒情報の一覧表示ページを作ります。
機能はシンプルに、
・全生徒の情報が表形式で表示される
・各生徒毎に『編集』『削除』ボタンがあり、クリックすると生徒情報の編集機能、生徒情報の削除機能の画面へ遷移する
・新規追加機能の画面へは一覧画面からリンクする
とします。
画面のイメージはこんな感じです。CSSなど一切使っていないのでシンプルというより殺風景ですが…。
まずは、準備
ライブラリのインストール
今回は、xampp環境でインストールされるデータベース MySQL(XAMPPの最近のバージョンでは MariaDB に変更されていますが、MySQL互換なので同じ設定で動きます)を利用します。PythonからMySQLを利用するためには外部ライブラリが必要です。MySQL用の外部ライブラリは何種類かあるのですが、今回は mysql-connector-python を使うことにします。MySQLの公式ライブラリのようです。さっさとインストールしてしまいましょう。
C:\> py -m pip install mysql-connector-python
Collecting mysql-connector-python
Downloading mysql_connector_python-8.0.26-cp39-cp39-win_amd64.whl (799 kB)
|████████████████████████████████| 799 kB 3.3 MB/s
Collecting protobuf>=3.0.0
Downloading protobuf-3.17.3-cp39-cp39-win_amd64.whl (909 kB)
|████████████████████████████████| 909 kB ...
Requirement already satisfied: six>=1.9 in d:\programs\python\lib\site-packages (from protobuf>=3.0.0->mysql-connector-python) (1.16.0)
Installing collected packages: protobuf, mysql-connector-python
Successfully installed mysql-connector-python-8.0.26 protobuf-3.17.3
C:\>
データベースの準備
データベースの作成
MySQLで、以下のデータベースを作成します。
データベース名:webdb
照合順序:utf8_general_ci
具体的な方法についてはここでは触れませんが、XAMPP環境ならphpmyadminを使えば簡単です。
テーブルの作成
『管理する情報』の仕様にしたがって、以下のようにテーブルを作成します。
テーブル名:studentlist
カラム名 | データ型 | 照合順序 | 意味 | 備考 |
---|---|---|---|---|
id | varchar(5) | utf8_general_ci | 学籍番号 | 主キー |
course | varchar(10) | utf8_general_ci | 専攻 | |
grade | int | 学年 | ||
name | varchar(20) | utf8_general_ci | 氏名 | |
birthday | date | 生年月日 |
初期値として、適当なデータを入れておきます。
テーブル作成&サンプルデータ用のSQLを置いておきます。
サンプルデータには突っ込まないで下さい。いろいろな意味で(苦笑)
CREATE TABLE meibo(
id VARCHAR(5),
course VARCHAR(10),
grade INT,
name VARCHAR(20),
birthday DATE,
PRIMARY KEY(id)
);
INSERT INTO studentlist(id,course,grade,name,birthday)
VALUES('01001','普通科', 2,'上原歩夢', '2005-03-01'),
('11001','普通科', 1,'中須かすみ', '2006-01-23'),
('14001','国際交流科', 1,'桜坂しずく', '2005-04-03'),
('95001','ライフデザイン科',3,'朝香かりん', '2003-06-29'),
('03001','情報処理科', 2,'宮下愛', '2004-05-30'),
('95002','ライフデザイン科',3,'近江彼方', '2003-12-16'),
('01003','普通科', 2,'優木せつ菜', '2004-08-08'),
('94001','国際交流科', 3,'エマヴェルデ','2004-02-05'),
('13001','情報処理科', 1,'天王寺璃奈', '2005-11-13'),
('01002','普通科', 2,'高咲侑', '2005-03-05'),
('12001','音楽科', 1,'嵐千砂都', '2006-02-25');
全ソースコード
#!C:/Programs/Python/python.exe
import io
import sys
import mysql.connector
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
print("Content-Type: text/html; charset=utf-8")
print()
print("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>生徒一覧</title>
</head>
<body>
<h1>生徒一覧</h1>
""")
con = mysql.connector.connect(host="localhost", port=3306, user="root", password="", db="webdb", charset="utf8")
cur = con.cursor(dictionary=True)
cur.execute("SELECT * FROM studentlist")
rows = cur.fetchall()
print("""
<tr><td>%(id)s</td><td>%(course)s</td><td>%(grade)s</td><td>%(name)s</td><td>%(birthday)s</td>
<form action="studentedit.py" method="post">
<input type="hidden" name="id" value="%(id)s">
<td><button type="submit">編集</button></td>
</form>
<form action="studentdelete.py" method="post">
<input type="hidden" name="id" value="%(id)s">
<td><button type="submit">削除</button></td>
</form>
</tr>""" % row)
cur.close()
con.close()
print("""<hr>
<a href="studentinsert.py">生徒の新規追加</a>
</body>
</html>""")
コード解説
冒頭部分
#!C:/Programs/Python/python.exe
import io
import sys
import mysql.connector
例によって1行目はpython実行ファイルのパスを記述しています。
importするモジュールは、前回と同じく文字化け対策の ioモジュールと sysモジュール、そしてデータベースMySQLを使用するための mysqlモジュールです。
HTML出力(前半)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8")
print("Content-Type: text/html; charset=utf-8")
print()
print("""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>生徒一覧</title>
</head>
<body>
<h1>生徒一覧</h1>
""")
最初の行は出力文字コードを utf-8 にするための処理です。
それ以降はすべてHTMLを出力するためのprint()関数(複数行文字列)です。
データベースアクセス
con = mysql.connector.connect(host="localhost", port=3306, user="root", password="", db="webdb", charset="utf8")
cur = con.cursor(dictionary=True)
cur.execute("SELECT * FROM studentlist")
rows = cur.fetchall()
この部分が今回製作したプログラムの中心、データベースアクセスのための処理です。
データベースにアクセスする基本的な手順は以下の通りです。
- データベースに接続し、”データベース接続オブジェクト”を取得する
- “カーソル”を取得する
- SQLを実行する
- (実行したSQLがSELECTの場合)カーソルからレコードを取得する
データベースに接続し、”データベース接続オブジェクト”を取得する
データベースに接続するには、mysql.connector.connect() メソッドを使用します。
書式は以下の通りです。
mysql.connector.connect( 接続情報 )
接続情報には、今回は以下のものを指定しています。
プロパティ名 | 意味 | デフォルト値 |
---|---|---|
host | データベースのホスト名 | localhost |
port | データベースの接続ポート | 3306 |
user | データベース接続ユーザ名 | カレントユーザー |
password | データベース接続パスワード | パスワードなし |
db | データベース名 | データベース選択なし |
charset | 文字セット | デフォルトの文字セット |
con = MySQLdb.connect(host="localhost", port=3306, user="root", password="", db="webdb", charset="utf8")
XAMPP環境で実験する場合はだいたいこの設定で接続できると思います。
- host=”localhost” は、このCGIスクリプトが実行されているWebサーバとデータベースサーバが同じであることを表します。
- XAMPPでインストールされたMySQLでは、localhostからの接続の場合はこの例のように rootユーザがパスワードなしで接続できます。レンタルサーバの場合には管理ページなどから接続ユーザとパスワードを確認・設定して下さい。
- charsetがhttpやPythonでの指定と違い、”utf-8″ ではなく “utf8″(ハイフンがない)であることに注意して下さい。
※host と port はデフォルト値のままなので省略しても動きそうです。
“カーソル”を取得する
カーソルを取得するには、データベース接続オブジェクトの cursor() メソッドを使用します。
書式は以下の通りです。
カーソル.cursor()
引数は必須ではありませんが、このプログラムでの例のように dictionary=True を指定すると、SELECT文で読み出したデータをfetchone()/fetchall() したときにカラム名で参照できるようになります。これを指定しないとインデックス番号でしか参照できません。
cur = con.cursor(dictionary=True)
SQLを実行する
SQLを実行するには、カーソルのメソッド execute() を使用します。書式は以下の通りです。
カーソル.execute( SQL文 )
SELECTで読みだしたレコードは、execute()メソッドの返却値ではなく、次のfetchone()/fetchall()メソッドで取得します。
カーソルからレコードを取得する
SELECTを実行した場合、JavaやPHPではSQLを実行するメソッドの返却値から結果のレコードを取り出すのですが、Pythonではカーソルから取り出します。
SELECTの結果からレコードを1件だけ取り出すには fetchone() メソッドを使用します。書式は以下の通りです。
カーソル.fetchone()
返却値は各カラムの値を格納した dict(辞書)型オブジェクトです。dict型とは添字(番号)の代わりに文字列をキーとして使用する配列のようなものです。PHP や JavaScript の連想配列とだいたい同じです。
例として、サンプルデータの id=’01001′ のレコードを取得すると、下記のようになります。
{‘id’: ‘01001’, ‘course’: ‘普通科’, ‘grade’: 2, ‘name’: ‘上原歩夢’, ‘birthday’: datetime.date(2005, 3, 1)}
『 キー : 値 』を列挙する、JSON型式とよく似た記述です。この形式でPythonのプログラム中に直接データを記述することもできますが、詳細は別の機会に。
SELECT文で取り出されたレコードをすべて読み出すには、fetchall() メソッドを使用します。書式はfetchone()と同じです。
カーソル.fetchall()
返却値は、レコードをdict型で表したものを複数まとめたタプル型のオブジェクトです。タプルとは変更のきかない配列のようなものです。例えばサンプルデータをすべて読み出し、fetchall() した場合の返却値は、
(
{‘id’: ‘01001’, ‘course’: ‘普通科’, ‘grade’: 2, ‘name’: ‘上原歩夢’, ‘birthday’: datetime.date(2005, 3, 1)},
{‘id’: ‘01002’, ‘course’: ‘普通科’, ‘grade’: 2, ‘name’: ‘高咲侑’, ‘birthday’: datetime.date(2005, 3, 5)},(略)
)
のようになります。
結果表示
print("<table>")
print("<tr><th>学籍番号</th><th>専攻</th><th>学年</th><th>生徒名</th><th>生年月日</th></tr>")
for row in rows:
print("""
<tr><td>%(id)s</td><td>%(course)s</td><td>%(grade)s</td><td>%(name)s</td><td>%(birthday)s</td>
<form action="studentedit.py" method="post">
<input type="hidden" name="id" value="%(id)s">
<td><button type="submit">編集</button></td>
</form>
<form action="studentdelete.py" method="post">
<input type="hidden" name="id" value="%(id)s">
<td><button type="submit">削除</button></td>
</form>
</tr>""" % row)
print("</table>")
データベースから読みだした結果を表示しているのがこの部分です。
fetchall()で取得した複数のレコードを順に処理する
fetchall()の返却値はタプル型です。タプル型は『配列のようなもの』なので、for文を使って要素(この場合はレコードを表すdict型オブジェクト)を1つずつ取り出し、繰り返し処理を行うことができます。
for row in rows:
変数rowsに代入されたタプルの中から、dict型のレコードが1件ずつ変数rowに代入され、処理が繰り返されます。各カラムの値は row[カラム名] で参照できます。
表示する
print("""
<tr><td>%(id)s</td><td>%(course)s</td><td>%(grade)s</td><td>%(name)s</td><td>%(birthday)s</td>
<form action="studentedit.py" method="post">
<input type="hidden" name="id" value="%(id)s">
<td><button type="submit">編集</button></td>
</form>
<form action="studentdelete.py" method="post">
<input type="hidden" name="id" value="%(id)s">
<td><button type="submit">削除</button></td>
</form>
</tr>""" % row)
この部分でレコード1件の内容、html としては tr 要素一つ分を表示しています。長いですが1つのprint()関数です。
print() の引数の文字列から、Pythonとは無関係なただの文字列を省略してまとめると、
print(“””… %(id)s … %(course)s … %(grade)s … %(name)s … %(birthday)s …
… %(id)s … %(id)s …””” % row)
となっています。これはおみくじの回でも使用した、%演算子を利用した書式文字列(他言語のsprintfに相当する機能)を拡張したものです。
以前使用した記述法は、
“…%s…%s…” % (値1, 値2)
という型式で、書式文字列中の %s や %d の部分に、後述の値1、値2…が順に当てはめられるというものでした。当然、%sや%dの個数と値の個数は一致している必要がありました。
これを拡張したのが今回の記述法です。
“… %(キー1)s … %(キー2)s …” % { キー1 : 値1, キー2 : 値2 }
書式文字列の中では %s の代わりに %(キー)s と記述し、文字列中に挿入する値も『 ( 値1, 値2…) 』の型式(タプル型)ではなく『 { キー1:値1, キー2:値2 … } 』のdict型になっています。こうすると、記述した順番とは無関係に、書式文字列中の対応するキーの部分に値が挿入されます。
%s や %d の個数と値の個数が異なっていても問題はなく、書式文字列中で使用されているキーがすべて辞書型に含まれていれば正常に動作します。
たとえば、
“プログラミング言語 %(lang)s の使用人口は世界第 %(rank)d 位です”
% {“lang”:”Python”, “rank”:3}
という記述は、
プログラミング言語 Python の使用人口は世界第 3 位です
となります。
“プログラミング言語 %(lang)s の使用人口は世界第 %(rank)d 位です”
% {“rank”:3, “lang”:”Python”}
のようにdict型内での記述の順番を変えても、
“プログラミング言語 %(lang)s の使用人口は世界第 %(rank)d 位です”
% {“rank”:3, “lang”:”Python”, “country”:”Japan”}
のようにdict型内に書式文字列で使われていないキーがあっても、結果は変わりません。
ではこのプログラムでの記述をもう一度観てみましょう。
print(“””… %(id)s … %(course)s … %(grade)s … %(name)s … %(birthday)s …
… %(id)s … %(id)s …””” % row)
このプログラムの例では、値としてdict型の書式 {キー:値, … } を直接記述するのではなく、dict型の値が代入された変数rowを使用しています。また、書式文字列中にキー”id” が3回使われていますが、これはすべて同じ値(dict型内のキーidの値)に置き換わります。
データベースの切断
cur.close()
con.close()
データベースを利用する処理が終わったら、カーソル及びデータベースを切断します。切断にはカーソル及びデータベース接続オブジェクトの close() メソッドを使用します。
カーソル.close()
データベース接続オブジェクト.close()
記述しなくてもプログラム終了時に自動的に切断されるのですが、使い終わったら早めに切断する方がよいのではないかと思います。
HTMLの表示(後半)
print("""<hr>
<a href="studentinsert.py">生徒の新規追加</a>
</body>
</html>""")
後半というか、あと4行のHTMLを出力するだけですが。
今宵はここまでにしとうございまする
編集、削除、追加は次回。
コメント