【Python】CGI+SQLiteでブラウザからデータベースの入出力を行う

前書き
Pythonは標準ライブラリとしてCGIの機能とSQLiteとの接続機能が搭載されており、外部からのライブラリを使用せずともウェブアプリケーションの開発が可能です。
一方で、PythonにはDjangoやFlaskをはじめとして、データベースとの接続が可能なウェブアプリケーションフレームワークが充実しています。そのため改めてCGIのみを用いたアプリケーションの開発はあまり行われていない印象を受けます。
今回のCGIを用いるメリットとしては、データ接続用のアプリケーションをできるだけ自分の手で構築することや、データのやり取りについて学ぶ際に有効であると考えられます。
そこで、本チュートリアルではPythonのCGIとSQLiteのみを用いて、それらの使い方を学びながらデータ入力システムを開発していきます。 今回の例ではCGIとSQLiteを用いて学校の授業記録を行うシステムを開発します。
方法
開発環境
このチュートリアルで使用している開発環境は以下の通りです。
- Windows10
- Python 3.7.4
CGIもSQLiteもすでにPythonに標準搭載されているため、改めて外部のライブラリをインストールする必要はありません。 使用するモジュールは以下の2つです。
- cgi
- sqlite3
cgi
HTTPサーバー上でスクリプトを実行するためのモジュールです。(公式ドキュメント)
cgiを使うためにはimport cgiと指定します。
今回使用するクラスは以下の通りです。
| クラス名 | 機能 | 
|---|---|
| FieldStorage | 入力されたフォームデータの取得 | 
sqlite3
SQLクエリを用いてデータベースへアクセスするためのモジュールです。(公式ドキュメント)
sqlite3を使うためにはimport sqlite3と指定します。
基本設定
データベースへの接続を行うためには、データベースのファイル名を指定します。変数として格納しなくても直接sqlite3.connect()の引数として指定することも可能です。
cur = con.cursor()によってCursorインスタンスとすることで、その後のexecute()メソッドによりSQLを実行することができます。
# データベースの接続設定
dbname = 'TEST.db'  # データベースのファイル名
conn = sqlite3.connect(dbname) # 接続するデータベースの指定
cur = conn.cursor() # Cursorインスタンスの作成
データベースの作成
フリーソフトであるDB Browser for SQLite(公式サイト)を用いると、GUIを通して視覚的かつ直感的にデータベースを作成・編集することができます。
詳しいデータベースの作成方法はリンク先公式サイトのドキュメントをご参照ください。
今回は以下のようなテーブルをpersonsという名前で作成しました。この後のデータベース編集によって、授業の内容が順次新しい行として追加されるようになります。
| id | name | teac | date | coma | subj | text | page | 
|---|---|---|---|---|---|---|---|
| 授業id | 生徒名 | 教師名 | 日付 | 授業コマ | 科目 | テキスト | ページ | 
SQLの実行
SQLiteではSELECT文によって指定したカラムの情報を取得することができます。SELECT *とすることですべてのカラムをFROMの後ろに指定したテーブルから取得できます。
また、fetchall()は、一致した全ての行をリストとして取得するメソッドです。これによりd_list変数にテーブル内の情報を全て格納しています。
cur.execute('SELECT * FROM persons')
d_list = cur.fetchall()
ディレクトリ構成
│  index.html
│  TEST.db
│
├─cgi-bin
│      get.py
│      post.py
│
├─get
│      index.html
│
├─html
│      after-get.html
│      after-post.html
│
└─post
        index.html
| ファイル | 機能 | 
|---|---|
| index.html | トップページ | 
| TEST.db | 記録先のデータベースファイル | 
| cgi-bin/get.py | データベースからテーブルを取得するpyファイル | 
| cgi-bin/post.py | データベースのテーブルへ新規な行を挿入するpyファイル | 
| get/index.html | データベースからテーブルを取得するpyファイル | 
| html/after-get.html | データを確認するためのhtmlファイル | 
| html/after-post.html | データ投稿後のhtmlファイル | 
| post/index.html | データを入力するためのhtmlファイル | 
コード
index.html
<html>
<head>
<title>授業記録</title>
<meta http-equiv="content-type" charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style type="text/css">
div {
  font-size: 20px;
  margin-bottom: 24;
}
body {
  background-color: #e1f5d5;
}
h2{
  background-color: #cbffd4;
}
</style>
</head>
<body>
<h2>授業記録システム</h2>
<div>
    <a href="/post/">記録</a>
</div>
<div>
    <a href="/get/">確認</a>
</div>
</body>
</html>
cgi-bin/get.py
import cgi, cgitb, sqlite3, codecs
# トレースバックを得るためにcgitbを有効化する。
cgitb.enable()
print("Content-Type: text/html")
print()
form = cgi.FieldStorage()
form_check = 0
# データベースの接続設定
dbname = 'TEST.db'  # データベースのファイル名
conn = sqlite3.connect(dbname) # 接続するデータベースの指定
cur = conn.cursor() # Cursorインスタンスの作成
cur.execute('SELECT * FROM persons')
d_list = cur.fetchall()
# html形式の出力を得る
result = '''
<table border="1">
<tr>
<th>日付</th>
<th>テキスト</th>
<th>ページ</th>
</tr>
'''
for data in d_list:
    if form["name"].value in data:
        if form["subj"].value in data:
            result += "<tr>"
            result += "<td>" + str(data[3]) + "</td>"
            result += "<td>" + str(data[6]) + "</td>"
            result += "<td>" + str(data[7]) + "</td>"
            result += "</tr>"
html = codecs.open('./html/after-get.html', 'r', 'utf-8').read()
html = html.replace('{% student %}', form["name"].value)
html = html.replace('{% subject %}', form["subj"].value)
html = html.replace('{% result %}', result)
print(html)
# conn.commit()
cur.close()
conn.close()
cgi-bin/post.py
import cgi, cgitb, sqlite3, codecs
cgitb.enable()
print("Content-Type: text/html")
print()
form = cgi.FieldStorage()
form_check = 0
dbname = 'TEST.db'
conn = sqlite3.connect(dbname)
cur = conn.cursor()
cur.execute("INSERT INTO persons values(?, ?, ?, ?, ?, ?, ?, ?)",
              (None,
              form["name"].value,
              form["teac"].value,
              form["date"].value,
              form["coma"].value,
              form["subj"].value,
              form["text"].value,
              form["page"].value))
html = codecs.open('./html/after-post.html', 'r', 'utf-8').read()
print (html)
conn.commit()
cur.close()
conn.close()
get/index.html
<html>
<head>
<title>記録確認</title>
<meta http-equiv="content-type" charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style type="text/css">
div {
  font-size: 20px;
  margin-bottom: 24;
}
body {
  background-color: #e1f5d5;
}
h2{
  background-color: #cbffd4;
}
select {
  font-size:20px;
}
button{
  font-size:20px;
  background-color: #ffcfcf;
}
</style>
</head>
<body>
<h2>■授業記録の確認</h2>
<form action="/cgi-bin/get.py" method="get">
  <div>
    <label for="name">見たい人の名前</label>
    <select name="name" id="name" value=" ">
      <option value=" "> </option>
      <option value="織田信長">織田信長</option>
      <option value="豊臣秀吉">豊臣秀吉</option>
      <option value="徳川家康">徳川家康</option>
      <option value="伊達政宗">伊達政宗</option>
      <option value="上杉謙信">上杉謙信</option>
      <option value="武田信玄">武田信玄</option>
    </select>
  </div>
  <div>
    <label for="subj">見たい科目</label>
    <select name="subj" id="subj" value=" ">
      <option value=" "> </option>
      <option value="数学">数学</option>
      <option value="英語">英語</option>
      <option value="国語">国語</option>
      <option value="社会">社会</option>
      <option value="理科">理科</option>
    </select>
  </div>
  <div>
    <button>確認</button>
  </div>
</form>
<hr>
<a href="/">トップへもどる</a>
</body>
</html>
html/after-get.html
<html>
<head>
<title>投稿完了</title>
<meta http-equiv="content-type">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style type="text/css">
div {
  font-size: 20px;
  margin-bottom: 24;
}
body {
  background-color: #e1f5d5;
}
h2{
  background-color: #cbffd4;
}
select {
  font-size:20px;
}
button{
  font-size:20px;
  background-color: #ffcfcf;
}
div.result {
  font-size: 20px;
  margin-bottom: 24;
  background-color: white;
}
div.top {
  font-size: 14px;
}
table{}
</style>
</head>
<body>
<h2>検索結果</h2>
<div>生徒 = {% student %}</div>
<div>科目 = {% subject %}</div>
  {% result %}
<hr>
<div class="top">
  <a href="/">トップへもどる</a>
</div>
</body>
</html>
html/after-post.html
<html>
<head>
<title>投稿完了</title>
<meta http-equiv="content-type">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style type="text/css">
div {
  font-size: 20px;
  margin-bottom: 24;
}
body {
  background-color: #e1f5d5;
}
h2{
  background-color: #cbffd4;
}
select {
  font-size:20px;
}
button{
  font-size:20px;
  background-color: #ffcfcf;
}
</style>
</head>
<body>
<h2>おつかれさまでした。</h2>
<h3>同じような内容を入力するときは、前のページに戻ってから投稿してください。</h3>
<hr>
<a href="/">トップへもどる</a>
</body>
</html>
post/index.html
<html>
<head>
<title>授業記録</title>
<meta http-equiv="content-type" charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<style type="text/css">
div {
  font-size: 20px;
  margin-bottom: 24;
}
body {
  background-color: #e1f5d5;
}
h2{
  background-color: #cbffd4;
}
select {
  font-size:20px;
}
button{
  font-size:20px;
  background-color: #ffcfcf;
}
</style>
</head>
<body>
<h2>■授業記録の投稿</h2>
<form action="/cgi-bin/post.py" method="post">
  <div>
    <label for="name">名前</label>
    <select name="name" id="name" value=" ">
      <option value=" "> </option>
      <option value="織田信長">織田信長</option>
      <option value="豊臣秀吉">豊臣秀吉</option>
      <option value="徳川家康">徳川家康</option>
      <option value="伊達政宗">伊達政宗</option>
      <option value="上杉謙信">上杉謙信</option>
      <option value="武田信玄">武田信玄</option>
    </select>
  </div>
  <div>
    <label for="teac">講師</label>
    <select name="teac" id="teac" value=" ">
      <option value=" "> </option>
      <option value="ベートーベン">ベートーベン</option>
      <option value="モーツァルト">モーツァルト</option>
      <option value="シューベルト">シューベルト</option>
      <option value="バッハ">バッハ</option>
      <option value="ヨハンシュトラウス">ヨハンシュトラウス</option>
      <option value="滝廉太郎">滝廉太郎</option>
    </select>
  </div>
  <div>
    <label for="date">授業日</label>
    <input type="date" name="date">
  </div>
  <div>
    <label for="coma">授業コマ</label>
    <select name="coma" id="coma" value=" ">
      <option value=" "> </option>
      <option value="0">⓪ 13:40 ~ 15:10</option>
      <option value="1">① 15:20 ~ 16:50</option>
      <option value="2">② 17:00 ~ 18:30</option>
      <option value="3">③ 18:40 ~ 20:10</option>
      <option value="4">④ 20:20 ~ 21:50</option>
    </select>
  </div>
  <div>
    <label for="subj">科目</label>
    <select name="subj" id="subj" value=" ">
      <option value=" "> </option>
      <option value="数学">数学</option>
      <option value="英語">英語</option>
      <option value="国語">国語</option>
      <option value="社会">社会</option>
      <option value="理科">理科</option>
    </select>
  </div>
  <div>
    <label for="text">教材</label>
    <select name="text" id="text" value=" ">
      <option value=" "> </option>
      <option value="ワーク">ワーク</option>
      <option value="オリジナル">オリジナル</option>
      <option value="春_講習">春_講習</option>
      <option value="夏_講習">夏_講習</option>
    </select>
  </div>
  <div>
    <label for="page">ページ</label>
      <input name="page" id="page" value="10-12 14 19">
    </select>
  </div>
  <div>
    <button>投稿</button>
  </div>
</form>
<hr>
<a href="/">トップへもどる</a>
</body>
</html>
ローカルサーバーの起動
Pythonを用いてローカルサーバーを起動するためには、index.htmlのあるディレクトリに移動して、以下のコマンドを実行します。
python -m http.server --cgi
起動すると以下のような出力が得られます。
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
任意のブラウザ上でURLをhttp://localhost:8000/と入力してアクセスするか、ローカルサーバーを立ち上げているIPアドレスをlocalhostの代わりに入力してアクセスします(例:http://192.168.xx.xx:8000/)。
結果
上記のプログラムをローカルサーバー上で実行し、ブラウザでhttp://localhost:8000/へアクセスすると以下のページが得られます。

このページのうち、「記録」から授業記録の投稿が行えます。 また、「確認」から授業記録の確認を行うことができます。
授業の記録
「記録」のリンクへ飛ぶと、授業記録を入力することができます。

授業記録を投稿するためには、今回のテーブル構成では以下の入力が必要になります。
- 名前
- 講師
- 授業日
- 授業コマ
- 科目
- 教材
- ページ
HTMLで日付の入力を行わせるには、<input type="date">を指定します。
これにより、日付入力欄とその中にカレンダーのマークが現れます。
カレンダーをクリックすると、カレンダーから直接日付の入力が可能です。
<div>
 <label for="date">授業日</label>
 <input type="date" name="date">
</div>
それぞれの内容をフォームから入力してから、「投稿」をクリックすることでデータベースへの記録が完了します。
授業の確認

授業記録を確認するためには、トップページから「確認」へ移動します。
見たい人の名前(生徒名)と見たい科目を選択し、「確認」をクリックすることで授業記録を確認できます。
今回のコードでは確認できる内容は「日付」「テキスト」「ページ」となっています。
以上の内容で、CGIを用いてデータベースへの入出力が行えました。
シンプルな構成ですが最低限の入出力が行えていますので、これを応用してブラウザ上から勤怠記録・観測データ記録・Q&Aフォームの作成ができることも期待されます。
なお、セキュリティの面では改善の余地がありますので、実務に使用する際にはローカル環境でのみ扱うか、Flaskなどのウェブアプリケーションフレームワークを使用することをおすすめします。
Flaskを用いたアプリケーションの例は下記をご参照ください。
関連記事: