FlaskとSQLiteとjinja2で簡易住所録をつくる

FlaskとSQLAlchemyをインストール

pip install Flask
pip install sqlalchemy

SQLAlchemyのメリット

  • SQL文を書かなくてもよい
  • 異なるデータベースに移行するとき(SQLiteからMYSQLなど)、変更が少なくてすむ

らしいのですが、正直よくわかりません。

ファイルの階層

addressbook-demo
|   bookFlask.py
|
+---static
|   +---css
|   |       skeleton.css
|   |
|   \---js
|           ajaxzip3.js
|           jquery-3.5.1.min.js
|           jquery.autoKana.js
|
\---templates
        check.html
        index.html
        resister.html

以上のように作成します。郵便番号から住所を自動入力するためにajaxzip3.jsを、名前のフリガナを自動入力するためにjquery.autoKana.jsを使用します。

skeleton.css

cssのことはよく分からないので、フレームワークのskeleton.cssを使います。

skeleton.cssのダウンロードは、こちら

運用環境

セキュリティに詳しくないので、ローカル環境で運用します。

ロリポップ!エックスサーバーで運用する場合は、以下の記事を参考にファイルを追加してください。

フォームの完成イメージ

このような入力フォームを作っていきます。

データベースを作成

ファイル名は、address.sqlite

テーブル名は、address

カラム名は、以下のように設定しています。

カラム名
idID(プライマリーキー、自動入力)
sei
mei
sei_kana姓のフリガナ
mei_kana名のフリガナ
zip01郵便番号
pref01都道府県名
addr01都道府県名以降の住所
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String

app = Flask(__name__)

app.config['SECRET_KEY'] = 'secret key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///address.sqlite'

db = SQLAlchemy(app)

class Addressbook(db.Model):
    __tablename__ = 'address'

    id = db.Column(Integer, primary_key = True, autoincrement = True)
    sei = db.Column(String(10))
    mei = db.Column(String(10))
    sei_kana = db.Column(String(20))
    mei_kana = db.Column(String(20))
    zip01 = db.Column(String(8))
    pref01 = db.Column(String(4))
    addr01 = db.Column(String(60))

    def __init__(self, sei, mei, sei_kana, mei_kana, zip01, pref01, addr01):
        self.sei = sei
        self.mei = mei
        self.sei_kana = sei_kana
        self.mei_kana = mei_kana
        self.zip01 = zip01
        self.pref01 = pref01
        self.addr01 = addr01

db.create_all()

データベースが存在していないと、SQLiteでデータベースを作成します。

SQLAlchemyはよく分からないので、これが正解かわかりませんが、とりあえず動作しています。

作成されたデータベース

sqlite> .schema
CREATE TABLE address (
        id INTEGER NOT NULL,
        sei VARCHAR(10),
        mei VARCHAR(10),
        sei_kana VARCHAR(20),
        mei_kana VARCHAR(20),
        zip01 VARCHAR(8),
        pref01 VARCHAR(4),
        addr01 VARCHAR(60),
        PRIMARY KEY (id)
);

render templateとjinja2で表示

bookFlask.py

from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, String

app = Flask(__name__)

app.config['SECRET_KEY'] = 'secret key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///address.sqlite'

db = SQLAlchemy(app)

class Addressbook(db.Model):
    __tablename__ = 'address'

    id = db.Column(Integer, primary_key = True, autoincrement = True)
    sei = db.Column(String(10))
    mei = db.Column(String(10))
    sei_kana = db.Column(String(20))
    mei_kana = db.Column(String(20))
    zip01 = db.Column(String(8))
    pref01 = db.Column(String(4))
    addr01 = db.Column(String(60))

    def __init__(self, sei, mei, sei_kana, mei_kana, zip01, pref01, addr01):
        self.sei = sei
        self.mei = mei
        self.sei_kana = sei_kana
        self.mei_kana = mei_kana
        self.zip01 = zip01
        self.pref01 = pref01
        self.addr01 = addr01

db.create_all()

# 入力フォームのname属性をリスト化
name_att = ['sei','mei','sei_kana','mei_kana','zip01','pref01','addr01']

@app.route('/')
def index():
    return render_template('index.html', name_att = name_att)

@app.route('/check', methods=['POST'])
def check():
    # index.htmlのフォームから受け取るデータ用リストを作成
    check_list = []

    # 受け取ったデータをリストに追加
    for att in name_att:
        check_list.append(request.form.get(att))

    return render_template('check.html', check_list = check_list, name_att = name_att)

@app.route('/resister', methods=['POST'])
def resister():
    # check.htmlのフォームから受け取るデータ用リストを作成
    resister_list = []

    # 受け取ったデータをリストに追加
    for att in name_att:
        resister_list.append(request.form.get(att))

    # データベースのカラムに受け取ったデータを入力   
    book = Addressbook(
        sei = resister_list[0], 
        mei = resister_list[1],
        sei_kana = resister_list[2],
        mei_kana = resister_list[3],
        zip01 = resister_list[4],
        pref01 = resister_list[5],
        addr01 = resister_list[6])
    
    # データベースへの書込処理
    db.session.add(book)
    db.session.commit()
    db.session.close()

    return render_template('resister.html')

if __name__ == '__main__':
    app.run()

index.html

  • htmlには詳しくないので、VSCodeの!で作成されるデフォルトにコードを追加しています。
  • cssは、フレームワークのskeleton.cssを使用しています。
  • 後で変更しやすいように、name_attリストを受け取ってname属性に代入しています。
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>住所録入力</title>
    <script type="text/javascript" src="./static/js/jquery-3.5.1.min.js"></script>
    <script type="text/javascript" src="./static/js/ajaxzip3.js"></script>
    <script type="text/javascript" src="./static/js/jquery.autoKana.js"></script>
    <script>
        $(document).ready(function(){
            $.fn.autoKana('input[name="{{name_att[0]}}"]', 'input[name="{{name_att[2]}}"]', {katakana:true});
            $.fn.autoKana('input[name="{{name_att[1]}}"]', 'input[name="{{name_att[3]}}"]', {katakana:true});
        });
    </script>
    <link rel="stylesheet" type="text/css" href="./static/css/skeleton.css">
</head>
<body>
    <form action="/check" method="POST">
        <label>名前</label>
        <input type="text" name="{{name_att[0]}}" placeholder="姓">
        <input type="text" name="{{name_att[1]}}" placeholder="名">
        <label>フリガナ</label>
        <input type="text" name="{{name_att[2]}}">
        <input type="text" name="{{name_att[3]}}">
        <label>郵便番号(半角)</label>
        <input type="text" name="{{name_att[4]}}" size="10" maxlength="8" onKeyUp="AjaxZip3.zip2addr(this,'','{{name_att[5]}}','{{name_att[6]}}');" placeholder="1234567">
        <label>都道府県</label>
        <input type="text" name="{{name_att[5]}}" size="20">
        <label>以降の住所</label>
        <input type="text" name="{{name_att[6]}}" size="40">
        <input class="button-primary" type="submit" value="確認">
    </form>
</body>
</html>

check.html

  • 入力項目の確認画面は、jinja2のfor文を使って簡潔に表示しています。
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登録確認</title>
    <link rel="stylesheet" type="text/css" href="./static/css/skeleton.css">
</head>
<body>
    <form action="/resister" method="POST">
        {% for i in range(check_list | length) %}
            {% if i == 0 or i == 2 %}
                <input type="text" name="{{name_att[i]}}" value="{{check_list[i]}}" readonly>
            {% elif i == 6 %}
                <input type="text" name="{{name_att[i]}}" value="{{check_list[i]}}" readonly size="40"><br>
            {% else %}
                <input type="text" name="{{name_att[i]}}" value="{{check_list[i]}}" readonly><br>
            {% endif %}
        {% endfor %}

        <button type="button" onclick="history.back()">戻る</button>
        <input class="button-primary" type="submit" value="登録">
    </form>
</body>
</html>

resister.html

  • 登録完了と表示するだけのページです。
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登録完了</title>
</head>
<body>
    登録完了
</body>
</html>

設置デモ

https://dattesar.com/addressbook-demo

データベース機能は削除してあります。ボタンを押してもデータは保存されません。