もくじ
FlaskとSQLAlchemyとpython-barcodeをインストール
pip install Flask
pip install sqlalchemy
pip install python-barcode
ファイルの階層
customerbook-demo
| customerFlask.py
| customer.sqlite
|
+---static
| +---css
| | skeleton.css
| | semantic.min.css
| |
| \---js
| ajaxzip3.js
| jquery-3.5.1.min.js
| jquery.autoKana.js
|
\---templates
check.html
index.html
resister.html
CSSフレームワーク
cssのことはよく分からないので、フレームワークのskeleton.css
とSemantic UI
を使います。
skeleton.cssのダウンロードは、こちら。
Semantic UIのダウンロードは、こちら。
運用環境
セキュリティに詳しくないので、ローカル環境で運用します。
ロリポップ!やエックスサーバーで運用する場合は、以下の記事を参考にファイルを追加してください。
完成イメージ
入力フォーム

登録完了画面

sqlite3でデータベースを作成
以前つくった住所録は、SQLAlchemyでデータベースを作成しましたが、SQLAlchemyでsqlite_sequenceテーブルを作成する方法がわからなかったので、sqlite3でデータベースを作成します。
sqlite3の場合、INTEGER PRIMARY KEY AUTOINCREMENTを含むテーブルを作成すると、sqlite_sequenceが自動で作成されます。
idの番号でバーコードを作成するときに、桁数を揃えたいのでidの開始値を100,001に設定しています。
import sqlite3
db = 'customer.sqlite'
con = sqlite3.connect(db)
cur = con.cursor()
# customerテーブルがない場合、テーブルを作成
cur.execute('''create table if not exists customer(
id integer primary key autoincrement,
sei text,
mei text,
sei_kana text,
mei_kana text,
zip01 text,
pref01 text,
addr01 text,
barcode text,
datetime text)
''')
# autoincrementの開始値を100,001に設定
# ("テーブル名", 開始値-1)
cur.execute('insert into sqlite_sequence values("customer",100000)')
con.commit()
con.close()
カラムの設定
カラム名 | 内容 |
id | ID(プライマリーキー、自動入力) |
sei | 姓 |
mei | 名 |
sei_kana | 姓のフリガナ |
mei_kana | 名のフリガナ |
zip01 | 郵便番号 |
pref01 | 都道府県名 |
addr01 | 都道府県名以降の住所 |
barcode | バーコード(SVG形式) |
datetime | 作成日 |
ソースコード
基本的には、以前つくった住所録にSQLAlchemyの【データベース作成】機能を削除、【日付表示】【バーコード作成】機能を追加し、登録完了画面に表示するだけです。
index.htmlとcheck.htmlに変更はありません。
customerFlask.py
SECRET_KEYは、duckduckgoで検索バーにpw 24 strongと入力し適当に生成しています。
バーコードは【code128】形式で作成します。どの形式でもよかったのですが、桁数指定がなく手持ちのバーコードリーダーで読み取りやすかったのがcode128でした。
python-barcodeの使い方は、関連記事を見てください。
ビジコムのバーコードリーダーは、消音することができ、読み取ると振動で教えてくれるので気に入っています。音で読み取りを知らせるタイプは、狭い部屋で使うと電子音がかなり耳障りです。
from flask import Flask, render_template, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Column, Integer, Text
import datetime
import barcode
import io
import re
app = Flask(__name__)
# SECRET KEYは適当(要変更)
app.config['SECRET_KEY'] = 'vyoU2(GqrMVXDN6QdncpQGxp'
# sqlite3で作成したデータベースを指定
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///customer.sqlite'
db = SQLAlchemy(app)
# SQLAlchemyでモデルを作成
class Customer(db.Model):
__tablename__ = 'customer'
id = db.Column(Integer, primary_key = True, autoincrement = True)
sei = db.Column(Text)
mei = db.Column(Text)
sei_kana = db.Column(Text)
mei_kana = db.Column(Text)
zip01 = db.Column(Text)
pref01 = db.Column(Text)
addr01 = db.Column(Text)
barcode = db.Column(Text)
datetime = db.Column(Text)
# 入力フォームの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))
# データベースのカラムに受け取ったデータを代入
# datetimeは本日の日付を代入(yyyy-mm-dd)
book = Customer(
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],
datetime = datetime.date.today())
# データベースへ登録
db.session.add(book)
# ID番号を取得するためにflushが必要
db.session.flush()
# 最後に登録されたデータのIDを取得
for r in db.session.execute('select last_insert_rowid()'):
lastid = r[0]
# バーコードのタイプをcode128形式に
CODE128 = barcode.get_barcode_class('code128')
# 取得したIDを文字列に変換してバーコードを作成
id_barcode = CODE128(str(lastid))
# メモリ上でバイナリデータを扱うためにBytesIOを使用
fp = io.BytesIO()
# バーコード作成のオプションを辞書型で指定
options = dict(module_width=0.8)
# バーコードをSVG形式で作成
id_barcode.write(fp, options)
# 作成したバーコードをテキスト形式にdecode
svgfile = fp.getvalue().decode('utf-8')
# 正規表現で<svg>から</svg>の間を抽出
m = re.search(r'<svg(.*)/svg>', svgfile, flags=re.DOTALL)
barcode_svg = m.group()
# 作成したバーコードのsvgタグの間だけをテキストでデータベースに登録
book.barcode = barcode_svg
# データベースに書込処理
db.session.commit()
db.session.close()
# 最後に登録したIDと一致するカラムをすべて抽出
customer_infos = db.session.query(Customer).filter(Customer.id == lastid).all()
# resister.htmlに登録したデータを渡す
return render_template('resister.html', customer_infos = customer_infos)
if __name__ == '__main__':
app.run()
render template と jinja2 で表示
index.html
郵便番号から住所を自動入力するためにajaxzip3.js
を、名前のフリガナを自動入力するためにjquery.autoKana.js
を使用します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>住所録入力</title>
<script src="./static/js/jquery-3.5.1.min.js"></script>
<script src="./static/js/ajaxzip3.js"></script>
<script 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
CSSはよく分からないので、フレームワークのSemanticUIを使っています。
jinja2で登録完了画面を表示するのに、苦労した所が2つあります。
- データベースから抽出したデータの表示方法
- SVG形式のバーコードをhtmlに埋め込む方法
jinja2でデータベースから抽出したデータの表示方法
{% for info in customer_infos %}
{{ info.sei }}
{% endfor %}
- for文を使い【.カラム名】で表示するカラムを指定します。
jinja2でSVG形式のバーコードをhtmlに埋め込む方法
{% autoescape false %}
{{ info.barcode }}
{% endautoescape %}
- jinja2では、タグの部分【<>】はエスケープ処理されるようです。
- タグが含まれた部分をそのまま埋め込むには、
autoescape
をflase
にして囲みます。
jinja2の詳しい説明はこちら。
<!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/semantic.min.css">
</head>
<body>
<div class="ui text container" style="margin-top: 20px;">
<h2 class="ui center aligned header">登録完了</h2>
{% for info in customer_infos %}
<table class="ui celled striped table">
<tbody>
<tr>
<td>作成日</td>
<td colspan="2">{{ info.datetime }}</td>
</tr>
<tr>
<td>ID</td>
<td colspan="2" class="ui center aligned">
{% autoescape false %}
{{ info.barcode }}
{% endautoescape %}
</td>
</tr>
<tr>
<td>名前</td>
<td>{{ info.sei }}</td>
<td>{{ info.mei }}</td>
</tr>
<tr>
<td>フリガナ</td>
<td>{{ info.sei_kana }}</td>
<td>{{ info.mei_kana }}</td>
</tr>
<tr>
<td>郵便番号</td>
<td colspan="2">{{ info.zip01 }}</td>
</tr>
<tr>
<td>都道府県名</td>
<td colspan="2">{{ info.pref01 }}</td>
</tr>
<tr>
<td>以降の住所</td>
<td colspan="2">{{ info.addr01 }}</td>
</tr>
</tbody>
</table>
{% endfor %}
</div>
</body>
</html>
設置デモ
https://dattesar.com/customer-demo/
データベース機能は削除してあります。ボタンを押してもデータは保存されません