前回 は住所録を作りましたが、データをグローバル変数に持っていたため、サーバーが再起動したりするとデータは消失してしまいます。データをファイルに保存することで安全なアプリを実現できます。ファイルは例えばCSV形式等でも良いのですが、簡単なデータベースを使ってみましょう。
今回使うテクニック・知識
- データベース
WEBアプリを作るときには MySQL や PostgreSQL のような本格的なデータベースを使うことが多いですが、小規模なシステムや実験レベルの実装に使うには荷が重いです。今回は手軽に使えるデータベース SQLite の Python モジュールである sqlite3 を使用することにします。SQLite はデータの実体がひとつのファイルなので取り扱いが簡単で、そのデータベースファイルをバックアップするだけでデータを退避することができます。設定項目も最低限で済み、プログラミングが楽なので初めて使うデータベースとしては最適です。
WindowsのWSLやMacのターミナルにはデフォルトで sqlite3 がインストールされているはずですが、必要ならインストールしてください。
1 2 |
$ sudo apt install sqlite3 (Windows/WSLの場合) $ brew install sqlite3 (Macの場合) |
- SQL
データベース内のデータを処理するにはSQLというデータベース言語を使用します。SQL言語は SQLite だけでなくデータベースを扱うときには必ず必要になるので基本的なことは勉強しておいてください。今回のサンプルでは単純な例しか出てきませんので前提知識がなくてもソースコードから理解できると思います。
- SQLAlquemy
Python のモジュール SQLAlquemy を使うとデータベースをクラスのように扱え、見通しの良いプログラムが書けます。しかし、今回のような単純なアプリでは SQLAlquemy の学習コストが高く付いてしまうので使いません。本格的なアプリを作るときには検討する価値があります。公式文書はこちらです。
ソースコード
- app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# coding: utf-8 from flask import Flask, render_template, request, redirect, url_for # このプログラムではsqlite3を使います。 import sqlite3 app = Flask(__name__) # データベースアクセスのためのグローバル変数 conn: sqlite3.Connection cur: sqlite3.Cursor DATABASE_NAME = 'ADDRESS_BOOK.db' # データベースに接続するための関数。重複して呼ばれても問題ないように考慮してある。 def connect_database(): global conn, cur # データベースを作成して接続する(既に存在していれば再接続) conn = sqlite3.connect(DATABASE_NAME) cur = conn.cursor() # address_book というテーブルがまだ無ければ作る。 sql = 'CREATE TABLE IF NOT EXISTS address_book(\ id INTEGER PRIMARY KEY AUTOINCREMENT,\ name STRING,\ address STRING)' cur.execute(sql) conn.commit() # 初期画面:現在の住所録を表示する @app.route('/') def index(): global cur connect_database() cur.execute('SELECT * FROM address_book') data = cur.fetchall() return render_template('index.html', page='menu', address_book=data) # 住所入力の画面を表示 @app.route('/input') def input(): return render_template('index.html', page='input') # 入力された住所データをデータベースに格納する @app.route('/checkin', methods=['GET', 'POST']) def checkin(): try: if request.method == 'POST': name = request.form['name'] address = request.form['address'] else: name = request.args.get('name', '') address = request.args.get('address', '') except Exception as e: return str(e) # 本当はここで入力データのチェックが必要だが省略している。 # ・SQLインジェクションへの対策 # ・データの妥当性チェック # ・重複登録の確認・排除、etc. # データベースに格納 global cur, conn #connect_database() sql = 'INSERT INTO address_book(name,address) VALUES(?,?)' data = [name,address] cur.execute(sql, data) conn.commit() # 初期画面に遷移させる return redirect(url_for('index')) if __name__ == '__main__': app.run(debug=True) |
- index.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<!--index.html--> <!DOCTYPE html> <html> <head> <title>Flask Sample</title> </head> <body> <h2>Flaskテスト</h2> {% if page == "menu" %} これはFlaskのテストのための住所録です。</br> <table border="1"> <th>内部管理No.</th><th>名前</th><th>住所</th> {% for record in address_book %} <tr> <td>{{ record[0] }}</td><td>{{ record[1] }}</td><td>{{ record[2] }}</td> </tr> {% endfor %} </table> <a href="/input">住所録を入力。</a> {% elif page == "input" %} <form method="POST" action="/checkin"> <div>名前:<input type="text" name="name"></div> <div>住所:<input type="text" name="address"></div> <input type="submit" value="送信"> <input type="reset" value="取消"> </form> {% else %} ここには来ないはずです。 {% endif %} </body> </html> |
表示をカッコよく
Flaskと直接には関係ないのですが、WEBページの表示を整えるためのツールとして Bootstrap4 というものがあります。一部の書籍やネット記事では旧バージョンの Bootstrap3 を前提に書いているものがありますので混同しないように注意してください。Bootstrap を使うと、HTML に記述を追加することでWEBページの見栄えを大幅に改善できます。上記のサンプルで使用した index.html を下のように変更してみてください。
- index.html(Bootstrap版)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
<!doctype html> <html lang="ja"> <head> <!-- Bootstrap4 template start --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" integrity="sha384-GJzZqFGwb1QTTN6wy59ffF1BuGJpLSa9DkKMp0DgiMDm4iYMj70gZWKYbI706tWS" crossorigin="anonymous"> <!-- Bootstrap4 template end --> <title>Flask Sample</title> </head> <body> <div class="container"> <nav class="navbar navbar-dark bg-dark"> <a href="#" class="navbar-brand">Flaskテスト(住所録)</a> </nav> {% if page == "menu" %} <table class="table"> <th>内部管理No.</th><th>名前</th><th>住所</th> {% for record in address_book %} <tr> <td>{{ record[0] }}</td><td>{{ record[1] }}</td><td>{{ record[2] }}</td> </tr> {% endfor %} </table> <a href="/input"> <button type="button" class="btn btn-dark">住所を入力</button> </a> {% elif page == "input" %} <div class="form-group"> <form method="POST" action="/checkin"> <div>名前:<input type="text" name="name" class="form-control"></div> <div>住所:<input type="text" name="address" class="form-control"></div> <input type="submit" value="送信"> <input type="reset" value="取消"> </form> </div> {% else %} ここには来ないはずです。 {% endif %} <!-- Bootstrap4 template start --> <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.6/umd/popper.min.js" integrity="sha384-wHAiFfRlMFy6i5SRaxvfOCifBUQy1xHdJ/yoi7FRNXMRBu5WHdZYu1hA6ZOblgut" crossorigin="anonymous"></script> <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/js/bootstrap.min.js" integrity="sha384-B0UglyR+jN6CkvvICOB2joaf5I4l3gm9GU6Hc1og6Ls7i6U/mkkaduKaBhlAXv9k" crossorigin="anonymous"></script> <!-- Bootstrap4 template end --> </div> </body> </html> |
Bootstrap についてはここでは説明しませんので別途勉強してください。解説記事はたくさんありますが、筆者がお世話になっているサイトを紹介しておきます。
機能拡張
名前と住所を入力してデータベースに格納するだけのアプリを作りましたが、実用するには足りない機能がたくさんあります。
-
- 名前の読みがなや郵便番号など、住所録として必要な機能。
- データを初期化・編集・削除する機能。
- SQLインジェクションと呼ばれるセキュリティ攻撃への対策。etc.
Flaskの練習としてアプリを完成させてください。
コメント