#はじめに
今回、掲示板の作成について、以下の通り6つに分類にして記述した。
(1)環境構築
(2)csvで掲示板
(3)SQLとSQLAlchemyで掲示板
(4)PostgreSQLとSQLAlchemyで掲示板
(5)SQLAlchemyを使ってデータ操作
(6)Postgresqlを使ってデータ操作
#(1)環境構築
デスクトップにディレクトリtestを作成。
test内に仮想環境を構築して起動。
python3 -m venv .
source bin/activate
必要なフレームワークとwebサーバーをインストール。
pip install flask
pip install gunicorn
#(2)csvで掲示板
まず、ローカル環境でcsvを使って掲示板を作る。
##①ディレクトリ構成
test
├app.py
├articles.csv
├Procfile
├requirements.txt
└templates
├index.html
├layout.html
└index_result.html
##②csvデータを用意する
articles.csvを作成し、分かりやすさの観点から、あらかじめ以下のデータを入力しておく。
たま,眠いにゃー
しろ,腹減ったにゃー
クロ,なんだか暖かいにゃー
たま,ぽえーぽえーぽえー
ぽんたん,トイレットペーパーがない
なおちん,チーン
##③メインとなるapp.pyを作成する
#coding: utf-8
from flask import Flask,request,render_template
app = Flask(__name__)
@app.route('/')
def bbs():
lines = []
#with openしてcsvファイルを読み込む
with open('articles.csv',encoding='utf-8') as f:
lines = f.readlines() #readlinesはリスト形式でcsvの内容を返す
#index.htmlに返す
return render_template('index.html',lines=lines)
#postメソッドを受け取る
@app.route('/result',methods=['POST'])
def result():
#requestでarticleとnameの値を取得する
article = request.form['article']
name = request.form['name']
#csvファイルに上書きモードで書き込む
with open('articles.csv','a',encoding='utf-8') as f:
f.write(name + ',' + article + '\n')
#index_result.htmlに返す
return render_template('index_result.html',article=article,name=name)
if __name__ == '__main__':
app.run(debug=False)
##④掲示板本体とその他テンプレ
{% extends 'layout.html' %}
{% block content %}
<h1> にゃん子掲示板</h1>
<form action='/result' method='post'>
<label for='name'>にゃん子の名前</label>
<input type='text' name='name'>
<p></p>
<label for='article'>投稿</label>
<input type='text' name='article'>
<button type='subimit'>書き込む</button>
</form>
<p></p>
<p></p>
<table border=1>
<tr><th>にゃん子の名前</th><th>投稿内容</th></tr>
{% for line in lines: %}
<!--columnという変数をセット(jinja2の変数セットにはsetが必要) -->
<!--splitを利用して,で分類する。splitはリストを返す -->
{% set column = line.rstrip().split(',') %}
<tr><td>{{column[0]}}</td><td>{{column[1]}}</td></tr>
{% endfor %}
</table>
{% endblock %}
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<title>Nyanko BBS</title>
<style>body{padding:10px;}</style>
</head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
{% extends 'layout.html' %}
{% block content %}
<h1>にゃ-んと掲示板に書き込みました</h1>
<p>{{name}}{{article}}</p>
<!--formで/に戻る -->
<form action='/' method='get'>
<button type='submit'>戻る</button>
</form>
{% endblock %}
##⑤Herokuへデプロイする
ローカル環境でテストした後に、Herokuへデプロイする。
Herokuへのデプロイ詳細は以下の記事に書いた通りなので、エッセンスのみとし、詳細説明を省く。
Heroku、Flask、Python、Gitでアップロードする方法(その②)
Heroku、Flask、Python、Gitでアップロードする方法(その③)
Herokuにログインし、Heroku上にアプリを作成
heroku login
アプリ名はcat-bbsとした。
Heroku create cat-bbs
ディレクトリappを初期化して、
git init
Herokuとローカル環境を紐つけて、
heroku git:remote -a cat-bbs
ディレクトリappにrequirements.txtを作成して、
pip freeze > requirements.txt
ディレクトリapp内にProcfileを作成し、以下を入力。
この時、gの前はブランク一つ必要、また、:appの前のappは、app.pyのappという意味なので注意が必要(form.pyなら、form:app)
web: gunicorn app:app --log-file -
全てをaddして、
git add .
今回は、the-firstという名前でcommitして、
git commit -m'the-first'
Herokuにpushする。
git push heroku master
最後に、
heroku open
heroku openのコマンドを入力すると、ブラウザが立ち上がり以下が表示された。
にゃん子の名前を”いわし”とし、投稿内容を”魚が大好き”と投稿すると、
ちゃんと掲示板に書き込みされた。
herokuでは書き込みされたcsvは一定時間(30分)経過すると消えてしまうので、データベースの組み込みに取り掛かる。
#(3)SQLとSQLAlchemyで掲示板
##①ディレクトリ構成等
test
├app.py
├articles.csv
├Procfile
├requirements.txt
├assets
│ ├init.py
│ ├database.py
│ └models.py
│
└templates
├index.html
├layout.html
└index_result.html
SQLAlchemyとは、Pythonの中では最もよく利用されているORMの一つ。
最初にsqlite3のバージョン確認(Mac)と、sqlalchemyをインストールする。
sqlite3 --version
pip install sqlalchemy
また、app.pyから、database.pyやmodels.pyをモジュールとして読み込むために必要なファイルとして、 init.pyをassetsフォルダ内に作成する(アンダーバーがつくので注意)
touch __init__.py
##②SQLAlchemyの初期設定
以下の2つのファイルをassetsフォルダ内に作成する。
database.py・・・sqliteやmysqlなど、どのデータベースを使うのかを定義するファイル
models.py・・・そのデータベースにどのような情報を入れるかを定義するファイル
まず、database.pyは以下の通り。
#coding: utf-8
#database.py/sqliteなど、どのデータベースを使うのか初期設定を扱うファイル
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session,sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import datetime
import os
#data_dbという名前で、database.pyのある場所に(os.path.dirname(__file__))、絶対パスで(os.path.abspath)、data_dbを保存する
database_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),'data.db')
#データベースsqliteを使って(engin)、database_fileに保存されているdata_dbを使う、またechoで実行の際にsqliteを出す(echo=True)
engine = create_engine('sqlite:///' + database_file,convert_unicode=True,echo=True)
db_session = scoped_session(
sessionmaker(
autocommit = False,
autoflush = False,
bind = engine
)
)
#declarative_baseのインスタンス生成する
Base = declarative_base()
Base.query = db_session.query_property()
#データベースの初期化をする関数
def init_db():
#assetsフォルダのmodelsをインポート
import assets.models
Base.metadata.create_all(bind=engine)
次に、models.pyは以下の通り。
ここで、投稿日時も掲示板に反映させるようした。
#coding: utf-8
from sqlalchemy import Column,Integer,String,Boolean,DateTime,Date,Text
from assets.database import Base
from datetime import datetime as dt
#データベースのテーブル情報
class Data(Base):
#テーブルnameの設定,dataというnameに設定
__tablename__ = "data"
#Column情報を設定、uniqueはFalseとする(同じ値でも認めるという意味)
#主キーは行を検索する時に必要、通常は設定しておく
id = Column(Integer,primary_key=True)
#nameは投稿者
name = Column(Text,unique=False)
#articleは投稿内容
article = Column(Text,unique=False)
#timestampは投稿日時
timestamp = Column(DateTime,unique=False)
#初期化する
def __init__(self,name=None,article=None,timestamp=None):
self.name = name
self.article = article
self.timestamp = timestamp
##③app.pyを修正する
データベースの作成や削除等には以下2つが必要なためインポートする。
assetsフォルダのdatabaseモジュールから変数de_sessionのインポートと、assetsフォルダのmodelsモジュールから、Dataクラスをインポート。
from assets.database import db_session
from assets.models import Data
###③−1データベースへの書き込み
index.htmlからarticle、nameの値を取得する処理が必要。また、それぞれの値をで取得時の(書き込み時の)日時をtoday()で取得し、today変数に代入する処理が必要。具体的には以下の通り。
article = request.form['article']
name = request.form['name']
today = datetime.datetime.today()
上記の内容をrowに格納し、db_sessionとde_commitでデータベースに書き込む処理が必要。具体的には以下の通り。
row = Data(name=name,article=article,timestamp=today)
db_session.add(row)
db_session.commit()
###③−2データベースからの読み込み
データベースからデータを読み込むには、db_session.query(Data).all()で取得できる。
例えば、データベースの中の値を取り出すために以下のように記述すると、
db_session.query(Data.name,Data.article,Data.timestamp).all()
以下のようにリスト形式で出力される*(分かりやすさの観点から、掲示板で何件か投稿し、データベースに保存された場合を想定)*
('ミケ', '本日は晴れ', datetime.datetime(2020, 3, 13, 0, 7, 4, 828409)),
('しろ', '明日は雨だにゃー', datetime.datetime(2020, 3, 13, 0, 7, 4, 828409)),
('クロ', 'ぽかぽか', datetime.datetime(2020, 3, 13, 0, 7, 4, 828409)),
('ぽんたん', 'にゃーにゃーカラスは紙飛行機', datetime.datetime(2020, 3, 13, 0, 7, 4, 828409)),
('しろ', '腰が痛いにゃー', datetime.datetime(2020, 3, 13, 0, 7, 46, 513144)),
('ミケ', 'もんだろにゃ?', datetime.datetime(2020, 3, 13, 0, 8, 57, 193710)),
('クロ', 'ぽかぽか', datetime.datetime(2020, 3, 13, 0, 9, 42, 45228)),
('ミケ', '本日は曇り', datetime.datetime(2020, 3, 13, 0, 17, 13, 709028)),
('ブー太郎', '今日は一日雨かにゃー', datetime.datetime(2020, 3, 14, 13, 26, 29, 438012)),
index.htmlに読み込んだデータベースの内容を返す処理が必要。具体的には以下の通り。
data = db_session.query(Data.name,Data.article,Data.timestamp).all()
return render_template('index.html',data=data)
これまでの修正についてまとめると、app.py全体としては以下の通り。
#coding: utf-8
from flask import Flask,request,render_template
import datetime
#データベースを使うにあたり追加
from assets.database import db_session
from assets.models import Data
app = Flask(__name__)
@app.route('/')
def bbs():
#データベースから読み込む
data = db_session.query(Data.name,Data.article,Data.timestamp).all()
#index.htmlに返す
return render_template('index.html',data=data)
#postメソッドを受け取る
@app.route('/result',methods=['POST'])
def result():
#requestでarticleとnameの値を取得する
article = request.form['article']
name = request.form['name']
#today関数でpostメソッドを受け取った日時を変数に代入
today = datetime.datetime.today()
#index_resultからの情報をデータベースに書き込む
row = Data(name=name,article=article,timestamp=today)
db_session.add(row)
db_session.commit()
#index_result.htmlに返す
return render_template('index_result.html',article=article,name=name)
if __name__ == '__main__':
app.run(debug=False)
###③−4(参考)データベースからの削除
参考として、読み込んだデータベースからの削除は以下の通り。
db_session.query(Data).allから削除したい項目を指定して(以下のケースは1番目の項目)、de_session.deleteを使う
#coding: utf-8
from assets.database import db_session
from assets.models import Data
def csv_sakujo():
data = db_session.query(Data).all()
datum = data[0]
db_session.delete(datum)
db_session.commit()
csv_sakujo()
###③−5(参考)読み込んだデータベースをcsvに書き出し
参考として、読み込んだデータベースをcsvに書き出すファイルは以下の通り。
#coding: utf-8
from assets.database import db_session
from assets.models import Data
#データを読み込む
def csv_kakikomi():
data = db_session.query(Data.name,Data.article,Data.timestamp).all()
print(data)
#csvファイルに書き込みモードで書き込む#
with open('articles2.csv','w',encoding='utf-8') as f:
for i in data:
f.write(str(i[0])+',')
f.write(str(i[1])+',')
f.write(str(i[2])+',' + '\n')
csv_kakikomi()
##④index_html.pyを修正する
app.pyから送られてきたdataの値を表示する。
dataの値のうち、data[2]は現在日時であるが、投稿前はNoneの値があるため、if文でエラーにならないように設定。投稿後はdatatime型をstr型にstrftimeを用いて変換した上で表示。
{% extends 'layout.html' %}
{% block content %}
<h1> にゃん子掲示板</h1>
<form action='/result' method='post'>
<label for='name'>にゃん子の名前</label>
<input type='text' name='name'>
<p></p>
<label for='article'>投稿</label>
<input type='text' name='article'>
<button type='subimit'>書き込む</button>
</form>
<p></p>
<p></p>
<table border=1>
<tr>
<th>にゃん子の名前</th>
<th>投稿内容</th>
<th>投稿日時</th>
</tr>
{% for datum in data %}
<tr>
<td>{{datum[0]}}</td>
<td>{{datum[1]}}</td>
{% if datum[2] == None %}
<td>{{datum[2]}}</td>
{% else %}
<td>{{datum[2].strftime('%Y年%m月%d日/%H時%M分%S秒')}}</td>
{% endif %}
</tr>
{% endfor %}
</table>
{% endblock %}
ここまでを、一度ローカル環境で正常に動くかどうかを試す。
問題なく稼働するのを確認したら、次にHeokuへのデプロイと、HerokuのPostgreSQLを使う。
#(4)PostgreSQLとSQLAlchemyで掲示板
Herokuへデプロイし、PostgreSQLを使う。
##①環境準備
postgresqlをbrewを使ってインストールする。
brew install postgresql
次に postgresqlを使うためのpython用のドライバーとして、 psycopg2-binaryをインストールする。psycopg2をそのままインストールすると、なぜかエラーが出るため、psycopg2-binaryをインストール(原因不明)。
pip install psycopg2-binary
次にdatabase.pyを修正するが、environというHeroku上の環境変数を見に行ってDATABASE_URLというデータベースを取得する処理を記述する。environには接続先のURLがセットされる。また、orをつけることで、ローカル環境上はsqliteをデータベースとして参照することとした。herokuに接続されている場合はpostgresqlのurlを参照して、接続されていない場合はsqlを参照に行くという格好。具体的には以下の通り。
engine = create_engine(os.environ.get('DATABASE_URL') or 'sqlite:///' + database_file,convert_unicode=True,echo=True)
修正後のapp.py全体は以下となる
#coding: utf-8
#database.py/sqliteなど、どのデータベースを使うのか初期設定を扱うファイル
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session,sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import datetime
import os
database_file = os.path.join(os.path.abspath(os.path.dirname(__file__)),'data.db')
engine = create_engine(os.environ.get('DATABASE_URL') or 'sqlite:///' + database_file,convert_unicode=True,echo=True)
db_session = scoped_session(
sessionmaker(
autocommit = False,
autoflush = False,
bind = engine
)
)
#declarative_baseのインスタンス生成する
Base = declarative_base()
Base.query = db_session.query_property()
#データベースの初期化をする
def init_db():
#assetsフォルダのmodelsをインポート
import assets.models
Base.metadata.create_all(bind=engine)
##②Herokuへデプロイする
Herokuへデプロイする。
heroku login
Herokuとローカル環境を紐つけて、
heroku git:remote -a cat-bbs
あらためて、ディレクトリappにrequirements.txtを作成する。
(psycopg2-binaryをインストールしたため、再度の作成が必要。)
pip freeze > requirements.txt
Prockfileは作成済みのため今回は触らず。
全てをaddして、
git add .
今回は、the-secondという名前でcommitして、
git commit -m'the-second'
Herokuにpushする。
git push heroku master
最後にheroku openする
herokuにデプロイする前に、データベースの初期化を行う。
heroku上でpythonを起動する(pythonモード)。
heroku run python
データベースを初期化する。
pythonモードで以下を記述。
from assets.database import init_db
init_db()
pythonモードを終了し、herokuをrestartして、openする。
heroku restart
heroku open
ブラウザで以下を確認して成功。
#(5)SQLAlchemyを使ってデータ操作
例として、データベースの1番上の項目を削除してみる(”クロ”)。
Heokuのpythonモード起動。
heroku run python
pythonモードで以下を記述
from assets.database import db_session
from assets.models import Data
data = db_session.query(Data).all()
datum = data[0]
db_session.delete(datum)
db_session.commit()
として、heroku openしてブラウザで確認すると、
一番上の”クロ”が削除された。
(pythonモードは忘れずに終了させること)
#(6)Postgresqlを使ってデータ操作
PostgreSQL をインストールすると、heroku pg コマンドで Heroku Postgres を操作できるようになる。
例えば以下を入力すると、インストールした Heroku Postgresの状況は以下のように確認できる。
heroku pg:info
=== DATABASE_URL
Plan: Hobby-dev
Status: Available
Connections: 2/20
PG Version: 12.2
Created: 2020-03-14 04:53 UTC
Data Size: 8.2 MB
Tables: 1
Rows: 3/10000 (In compliance)
Fork/Follow: Unsupported
Rollback: Unsupported
Continuous Protection: Off
hobby-dev プラン (無料枠) で、Status は Available (有効) 。
以下を入力すると、Heroku Postgresに接続できる。
heroku pg:psql
接続後はPostgreSQLのコマンドを使用
例えば、1番上の項目を削除してみる(”テスト”、”うんち”)。
テーブル一覧の表示のコマンド
\dt;
テーブル内のデータを一覧するコマンド
select * from data(テーブル名);
以下が出力される。
cat-bbs::DATABASE=> select * from data;
id | name | article | timestamp
----+------------+--------------------+----------------------------
3 | テスト | うんち | 2020-03-14 05:59:38.062361
4 | プーさん | なし | 2020-03-14 15:14:12.453124
5 | まちゃあき | それがどうした | 2020-03-14 15:14:12.453124
6 | どぶろっく | だから | 2020-03-14 15:14:12.635542
7 | 変だ | なし | 2020-03-14 15:14:12.635542
8 | おう | そだね | 2020-03-14 15:14:12.453124
9 | ニューあ | ムーン | 2020-03-14 15:32:49.082485
10 | 女子 | 高め | 2020-03-14 15:59:30.175208
11 | ほんま | 相談 | 2020-03-14 15:59:47.029891
12 | え? | フォンド | 2020-03-14 16:15:58.35794
13 | なおき | テスト | 2020-03-14 16:24:47.435301
14 | ぽち | ぽちでも猫だにゃん | 2020-03-14 22:52:41.633207
(12 rows)
次に、deleteで1番上の項目を削除する(”テスト”、”うんち”)。
delete from data(テーブル名) where id=3;
とすると、
cat-bbs::DATABASE=> select * From data;
id | name | article | timestamp
----+------------+--------------------+----------------------------
4 | プーさん | なし | 2020-03-14 15:14:12.453124
5 | まちゃあき | それがどうした | 2020-03-14 15:14:12.453124
6 | どぶろっく | だから | 2020-03-14 15:14:12.635542
7 | 変だ | なし | 2020-03-14 15:14:12.635542
8 | おう | そだね | 2020-03-14 15:14:12.453124
9 | ニューあ | ムーン | 2020-03-14 15:32:49.082485
10 | 女子 | 高め | 2020-03-14 15:59:30.175208
11 | ほんま | 相談 | 2020-03-14 15:59:47.029891
12 | え? | フォンド | 2020-03-14 16:15:58.35794
13 | なおき | テスト | 2020-03-14 16:24:47.435301
14 | ぽち | ぽちでも猫だにゃん | 2020-03-14 22:52:41.633207
(11 rows)