短期集中:PythonでWeb名簿管理 その3

Python
この記事は約26分で読めます。

Python勉強企画、9日目もCGIです。 今回こそは名簿管理を完成させます。Pythonの文法・メソッドの使用法としては新しい話はないので、解説少なめです。

生徒情報の編集機能

編集機能の仕様

画面設計

更新機能の画面は、以下のようにします。

新規登録画面とほぼ同じですが、以下の点が異なります。

  • 画面が表示された時点で、studentlistテーブルから読みだしたデータが表示されています。
  • 学籍番号は編集できません。

『編集』ボタンをクリックするとstudentlistテーブル上のデータが更新されます。

登録データの書式

編集可能な各項目の書式や、書式チェック・エラー表示の様式は新規登録と同じです。前回の解説を参照して下さい。

全ソースコード

#!C:/Programs/Python/python.exe

import io
import sys
import cgi
import mysql.connector
import re

form = cgi.FieldStorage()
error=[]

con = mysql.connector.connect(user="root", password="", host="localhost", db="webdb", charset="utf8")
cur = con.cursor(dictionary=True)

if "ac" in form and form["ac"].value=="edit":
    id       = form["id"].value       if "id" in form       else ""
    course   = form["course"].value   if "course" in form   else ""
    grade    = form["grade"].value    if "grade" in form    else ""
    name     = form["name"].value     if "name" in form     else ""
    birthday = form["birthday"].value if "birthday" in form else ""
    
    if not re.match("^[0-9]{5}$", id):
        error.append("学籍番号が異常です")
    if not re.match("^.{1,10}$", course):
        error.append("専攻は1~10文字です")
    if not re.match("^[123]$", grade):
        error.append("学年は1~3の半角数字です")
    if not re.match("^.{1,20}$", name):
        error.append("氏名は1~20文字です")
    if not re.match("^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$", birthday):
        error.append("誕生日は0000-00-00の形式です")

    if len(error)==0:
        sql = "UPDATE studentlist SET course=%s,grade=%s,name=%s,birthday=%s WHERE id=%s"
        cur.execute(sql,  (course, grade, name, birthday, id))
        con.commit()
        cur.close()
        con.close()
        print("Location: studentlist.py\n")
        exit()

else:
    if "id" in form:
        id = form["id"].value
        sql="SELECT * FROM studentlist WHERE id=%s"
        cur.execute(sql, (id,))
        rows = cur.fetchall()
        if len(rows)==1:
            course   = rows[0]["course"]
            grade    = rows[0]["grade"]
            name     = rows[0]["name"]
            birthday = rows[0]["birthday"]
        else:
            id=""
            error.append("指定された学籍番号の学生の情報がありません")
    else:
        id=""
        error.append("学籍番号が指定されていません")

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>
    """)

if len(error) != 0:
    print("<ul style='color:red'>")
    for mes in error:
        print("<li>%s</li>" % mes)
    print("</ul>")

if id != "":
    print("""<form action="" method="post">
<input type="hidden" name="id" value="%(id)s">
<input type="hidden" name="ac" value="edit">
<table>
    <tr><th>学籍番号</th><td>%(id)s</td></tr>
    <tr><th>専攻</th><td><input type="text" name="course" value="%(course)s"></td></tr>
    <tr><th>学年</th><td><input type="text" name="grade" value="%(grade)s"></td></tr>
    <tr><th>氏名</th><td><input type="text" name="name" value="%(name)s"></td></tr>
    <tr><th>生年月日</th><td><input type="text" name="birthday" value="%(birthday)s"></td></tr>
</table>
<button type="submit">編集</button>
</form>"""  % {"id":id, "course":course, "grade":grade, "name":name, "birthday":birthday})

print("""<hr>
<a href="studentlist.py">編集せずに一覧に戻る</a>
</body>
</html>
""")

コード解説

冒頭部分~書式チェック

#!C:/Programs/Python/python.exe

import io
import sys
import cgi
import mysql.connector
import re

form = cgi.FieldStorage()
error=[]

con = mysql.connector.connect(user="root", password="", host="localhost", db="webdb", charset="utf8")
cur = con.cursor(dictionary=True)

if "ac" in form and form["ac"].value=="edit":
    id       = form["id"].value       if "id" in form       else ""
    course   = form["course"].value   if "course" in form   else ""
    grade    = form["grade"].value    if "grade" in form    else ""
    name     = form["name"].value     if "name" in form     else ""
    birthday = form["birthday"].value if "birthday" in form else ""
    
    if not re.match("^[0-9]{5}$", id):
        error.append("学籍番号が異常です")
    if not re.match("^.{1,10}$", course):
        error.append("専攻は1~10文字です")
    if not re.match("^[123]$", grade):
        error.append("学年は1~3の半角数字です")
    if not re.match("^.{1,20}$", name):
        error.append("氏名は1~20文字です")
    if not re.match("^[0-9]{4}\-[0-9]{2}\-[0-9]{2}$", birthday):
        error.append("誕生日は0000-00-00の形式です")

ここまでは新規登録のスクリプト studentinsert.py とほぼ同じです。唯一の違いは、

form["ac"].value=="edit"

の部分だけです。

更新処理

    if len(error)==0:
        sql = "UPDATE studentlist SET course='%s',grade='%s',name='%s',birthday='%s' WHERE id='%s'"
        cur.execute(sql,  (course, grade, name, birthday, id))
        con.commit()
        cur.close()
        con.close()
        print("Location: studentlist.py\n")
        exit()

SQLがUPDATEになった以外はほぼ『新規登録機能』の挿入処理と同じですが、UPDATEではid重複の例外が発生しないのでtry~exceptを省いています。

データベースからの読み出し

else:
    if "id" in form:
        id = form["id"].value
        sql="SELECT * FROM studentlist WHERE id=%s"
        cur.execute(sql, (id,))
        rows = cur.fetchall()
        if len(rows)==1:
            course   = rows[0]["course"]
            grade    = rows[0]["grade"]
            name     = rows[0]["name"]
            birthday = rows[0]["birthday"]
        else:
            id=""
            error.append("指定された学籍番号の学生の情報がありません")
    else:
        id=""
        error.append("学籍番号が指定されていません")

フォームからデータが送信されていない場合の処理です。

『フォームから”ac”が送信されていない』=『生徒一覧からの遷移』とし、渡されたidに対応するレコードをstudentlistテーブルから読みだします。

sql="SELECT * FROM studentlist WHERE id=%s"
cur.execute(sql, (id,))

この部分には注意が必要です。cur.execute() メソッドでプレースホルダー機能を使っていますが、このように置き換える値が1つだけの場合は『 (id, ) 』のようにカンマ『 , 』が必要です。『 ( id ) 』のようにカンマをつけないと、タプルではなく括弧なしで『 id 』と記述したのと同じと見なされてエラーになってしまうようです。(これで10分悩みました)

        rows = cur.fetchall()
        if len(rows)==1:
            course   = rows[0]["course"]
            grade    = rows[0]["grade"]
            name     = rows[0]["name"]
            birthday = rows[0]["birthday"]
        else:
            id=""
            error.append("指定された学籍番号の学生の情報がありません")

len(rows) はSELECT文の結果のレコードを要素とするリストです。よってlen(rows)はSELECTで抽出されたレコード数を表します。レコードが1件でない場合は渡されたidに不具合があったとして変数errorに”指定された学籍番号の学生の情報がありません”というエラーを追加し、変数idの内容を空文字列にしています。

    else:
        id=""
        error.append("学籍番号が指定されていません")

id が渡されていない場合( 『 “id” in form 』が成立しなかった場合)には、この else 節に移動し、変数errorに “学籍番号が指定されていません” というエラーメッセージを追加します。

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>
    """)

この部分には特に説明することはないでしょう。

エラーメッセージを出力

if len(error) != 0:
    print("<ul style='color:red'>")
    for mes in error:
        print("<li>%s</li>" % mes)
    print("</ul>")

これも『新規追加機能』にあったものと同じです。

フォームの表示

if id != "":
    print("""<form action="" method="post">
<input type="hidden" name="id" value="%(id)s">
<input type="hidden" name="ac" value="edit">
<table>
    <tr><th>学籍番号</th><td>%(id)s</td></tr>
    <tr><th>専攻</th><td><input type="text" name="course" value="%(course)s"></td></tr>
    <tr><th>学年</th><td><input type="text" name="grade" value="%(grade)s"></td></tr>
    <tr><th>氏名</th><td><input type="text" name="name" value="%(name)s"></td></tr>
    <tr><th>生年月日</th><td><input type="text" name="birthday" value="%(birthday)s"></td></tr>
</table>
<button type="submit">編集</button>
</form>"""  % {"id":id, "course":course, "grade":grade, "name":name, "birthday":birthday})

idが指定されていない、idに該当するレコードがない場合(その場合は変数 id に空文字列が代入されています)、フォーム自体を表示しないようになっています。

フォームの各入力欄に初期値を表示するため、%演算子を利用した書式文字列を使用しています。変数course、grade、name、birthday には、一覧画面から遷移した場合には studentlistテーブルから読み出された値が、書式のエラーによってフォーム自身から遷移した場合にはフォームに入力されていた値が代入されています。

htmlの出力(後半)

print("""<hr>
<a href="studentlist.py">編集せずに一覧に戻る</a>
</body>
</html>
""")

htmlの最後の部分(フォームを表示しない場合でも必要な部分)を出力しています。

動作テスト

この機能を使って、生徒の情報を編集してみましょう。

以下の例では、学籍番号(id)=”01002″の生徒『高碕侑』の専攻を変更しています。

※いや名前が間違ってる奴おるやん、そっち先に直せや、という方、そちらで試していただいても結構ですw

  1. 生徒一覧画面で、学籍番号01002の行の『編集』をクリック

2. 学籍番号01002の生徒の情報を初期値としたフォームが表示されます

3. 専攻を『普通科』から『音楽科』に書き換え、『編集』ボタンをクリックします

4. 自動的に一覧表示画面に戻り、学籍番号01002の生徒の専攻が『音楽科』に変わっていることを確認します。

うまく動いたでしょうか。

ところで、学籍番号の付番の仕方として『特定の桁が専攻に対応している』となっていることがあり、そのような場合には転科すると学籍番号も変化する、という運用をしている学校もあると思うのですが、このプログラムでは学籍番号=主キーとしていることもあって編集は不可にしてあります。

※ちなみにブログ主がいま通っている学校は、学籍番号の上から2桁目が専攻を表していますが、転科しても学籍番号は変わりません。転科しようが休学しようが留年しようが卒業まで同じ学籍番号です。

まぁ、データベースアクセスの勉強のためのサンプルなんで、それ以上はツッコまないで下さい…

生徒情報の削除機能

削除機能の仕様

画面設計

生徒一覧画面で『削除』ボタンを押すと、該当する生徒の情報を表示する削除画面(確認画面)に遷移します。

編集項目はなく、『削除』ボタンをクリックすると該当する生徒のレコードが削除され、ただちに一覧表示画面に遷移します。

全ソースコード

#!D:/Programs/Python/python.exe

import io
import sys
import cgi
import mysql.connector
import re

form = cgi.FieldStorage()
id = form["id"].value if "id" in form else ""
error=[]

con = mysql.connector.connect(user="root", password="", host="localhost", db="webdb", charset="utf8")
cur = con.cursor(dictionary=True)

if "ac" in form and form["ac"].value=="delete":
    sql = "DELETE FROM studentlist WHERE id=%s"
    cur.execute(sql,  (id, ))
    con.commit()
    cur.close()
    con.close()
    print("Location: studentlist.py\n")
    exit()

else:
    if id != "":
        sql="SELECT * FROM studentlist WHERE id=%s"
        cur.execute(sql, (id, ))
        rows = cur.fetchall()
        if len(rows)==1:
            course   = rows[0]["course"]
            grade    = rows[0]["grade"]
            name     = rows[0]["name"]
            birthday = rows[0]["birthday"]
        else:
            id=""
            error.append("指定された学籍番号の学生の情報がありません")
    else:
        error.append("学籍番号が指定されていません")

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>
    """)

if len(error) != 0:
    print("<ul style='color:red'>")
    for mes in error:
        print("<li>%s</li>" % mes)
    print("</ul>")

if id != "":
    print("""<form action="" method="post">
<input type="hidden" name="id" value="%(id)s">
<input type="hidden" name="ac" value="delete">
<table>
    <tr><th>学籍番号</th><td>%(id)s</td></tr>
    <tr><th>専攻</th><td>%(course)s</td></tr>
    <tr><th>学年</th><td>%(grade)s</td></tr>
    <tr><th>氏名</th><td>%(name)s</td></tr>
    <tr><th>生年月日</th><td>%(birthday)s</td></tr>
</table>
<button type="submit">削除</button>
</form>"""  % {"id":id, "course":course, "grade":grade, "name":name, "birthday":birthday})

print("""<hr>
<a href="studentlist.py">削除せずに一覧に戻る</a>
</body>
</html>
""")

コード解説

今回はほとんど説明することはありません。編集のプログラムからデータの書式チェックの部分を省いたような内容です。

このプログラムでは、話を簡単にするため、削除確認画面で『削除』ボタンをクリックして遷移してきた場合(form[“ac”].value==”delete”)の場合は問答無用でDELETE文を実行しています。もしidが適切な値でなかったとしても、単にどのレコードも削除されないだけで、エラーなどは表示せず黙って一覧画面に遷移する仕様です。

動作テスト

では、動作テストしてみましょう。以下の例では、学籍番号(id)=”12001″の生徒『嵐千砂都』の情報を削除しています。

※生徒名簿からの削除=退学となるのですが、たぶん『人間関係のトラブル』のような悲愴な理由ではなく、原宿あたりに新設された『音楽に力を入れている学校』に転校するのでしょう

  1. 生徒一覧画面で、学籍番号12001の行の『削除』をクリック

2. 学籍番号12001の生徒の情報が表示されるので、『削除』ボタンをクリックします。

3. 自動的に生徒一覧画面に戻ります。学籍番号12001の生徒が消えていることを確認します。

うまく動いたでしょうか。

実際に使われているシステムでは、『生徒を削除』=『生徒のレコードを削除』ではなく、『削除した』というフラグを立てるだけ、ということもよくあります。

ようやく完成

というわけで、データベースの機能をひととおり利用できるWebアプリケーションが完成しました。

夏休みが終わるまでに Python を身につけよう!と知識0の状態で図書館からテキストを借りてきて10日足らず。借りてきた本が文法の説明ばかりでサンプルがまったく載っていないため、『なら自分でいろいろ作ってみるか!』という動機で始めた短期集中記事でしたが、なんとかなるものです。

コメント

タイトルとURLをコピーしました