Python + Flask を使ったWebアプリ作成③(DB接続〜パッケージ構成修正編)





続きです。
SQLAlchemy を使ったDB接続処理と、パッケージ構成の修正をします。


*参考



*環境

  • MacOS
  • Python 3.6.3
  • Flask 1.0.2

*SQLAlchemy でのDB接続処理

ログインユーザーと投稿したデータを保存できるようにします。
データベースは今回は sqlite を使っています。

テーブル定義は class でモデルを作成し、db.Column()でカラムを定義します。
def __repr__()を実装しておくと、DBの値を確認するときに値の出力形式を指定できるようになります。

<home.py>
from datetime import datetime  
from flask import Flask, render_template, url_for, flash, redirect  
from flask_sqlalchemy import SQLAlchemy   <-- 追加
from forms import RegistrationForm, LoginForm  
  
app = Flask(__name__)  
  
app.config['SECRET_KEY'] = 'cfb33786023cc152019e747a051f73c6'  
# ----- 追加ここから -----
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
    password = db.Column(db.String(60), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return "User('{}', '{}', '{}')".format(self.username, self.email, self.image_file)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return "Post('{}', '{}')".format(self.title, self.date_posted)
# ----- 追加ここまで -----
    
posts = [  
    {  
        'author': 'Corey Schafer',  
        'title': 'Blog Post 1',  
        'content': 'First post content',  
        'date_posted': 'April 20, 2018'  
  },  
    {  
        'author': 'Jane Doe',  
        'title': 'Blog Post 2',  
        'content': 'Second post content',  
        'date_posted': 'April 21, 2018'  
  }  
]  
  
  
@app.route('/')  
@app.route('/home')  
def home():  
    return render_template('home.html', posts=posts)  
  
  
@app.route('/about')  
def about():  
    return render_template('about.html', title='About')  
  

@app.route('/register', methods=['GET', 'POST'])  
def register():  
    form = RegistrationForm()  
    if form.validate_on_submit():  
        # POSTリクエスト(登録時)
        flash('Account created for %s!' % form.username.data, 'success')  
        return redirect(url_for('home'))  
    # 初期表示時    
    return render_template('register.html', title='Register', form=form)  
  

@app.route('/login', methods=['GET', 'POST'])  
def login():  
    form = LoginForm()  
    if form.validate_on_submit():  
        # POSTリクエスト(ログイン時)
        if form.email.data == 'admin@blog.com' and form.password.data == 'password':  
            flash('You have been logged in!', 'success')  
            return redirect(url_for('home'))  
        else:  
            flash('Login Unsuccessful. Please check username and password', 'danger')  
    # 初期表示時
    return render_template('login.html', title='Login', form=form)  
  
  
if __name__ == '__main__':  
    app.run(debug=True)


ターミナルから確認をしてみます。
create_all()をすると定義したテーブルが作成されます。
$ python
>>> from home import db
>>> db.create_all()

User テーブルにデータを登録します。
定義したあとにdb.session.add()をしてデータを追加し、db.session.commit()で確定します。
>>> from home import User, Post
>>> user_1 = User(username='Corey', email='C@demo.com', password='password')
>>> db.session.add(user_1)
>>> user_2 = User(username='JohnDoe', email='jd@demo.com', password='password')
>>> db.session.add(user_2)
>>> db.session.commit()

全て表示するときはquery.all()、最初の1件はquery.first()、条件での絞り込みはquery.filter_by()を使います。
この出力ときにhome.pyで定義した__repr__()の内容が出力されます。
また、drop_all()をすると全てのデータが削除されます。
>>> User.query.all()
[User('Corey', 'C@demo.com', 'default.jpg'), User('JohnDoe', 'jd@demo.com', 'default.jpg')]
>>> User.query.first()
User('Corey', 'C@demo.com', 'default.jpg')
>>> User.query.filter_by(username='Corey').all()
[User('Corey', 'C@demo.com', 'default.jpg')]
>>> User.query.filter_by(username='Corey').first()
User('Corey', 'C@demo.com', 'default.jpg')


*パッケージ構成の修正

home.pyにDB接続やAPI接続処理が混ざっているので、これを機能ごとに別ファイルに切り出します。
DB接続処理をmodels.py、API接続処理はroutes.pyに移植します。最終的には下記のディレクトリ構成になります。
├── run.py
└── flaskblog
    ├── __init__.py
    ├── forms.py
    ├── models.py
    ├── routes.py
    ├── static
    └── templates

作業ディレクトリ直下にflaskblogフォルダを新規作成します。その配下に__init__.pyを作成して、home.pyの下記設定部分を移植します。
__init__.pyに書いてある内容は、別のファイルからimportして使うことができます。
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)

app.config['SECRET_KEY'] = 'cfb33786023cc152019e747a051f73c6'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)

from flaskblog import routes

新規にmodels.pyを作成して、home.pyのDB定義の部分を移植します。
from datetime import datetime
from flaskblog import db


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
    password = db.Column(db.String(60), nullable=False)
    posts = db.relationship('Post', backref='author', lazy=True)

    def __repr__(self):
        return "User('{}', '{}', '{}')".format(self.username, self.email, self.image_file)


class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)

    def __repr__(self):
        return "Post('{}', '{}')".format(self.title, self.date_posted)

新規にroutes.pyを作成して、home.pyのAPIの部分を移植します。
from flask import render_template, url_for, flash, redirect
from flaskblog import app
from flaskblog.forms import RegistrationForm, LoginForm
from flaskblog.models import User, Post


posts = [
    {
        'author': 'Corey Schafer',
        'title': 'Blog Post 1',
        'content': 'First post content',
        'date_posted': 'April 20, 2018'
    },
    {
        'author': 'Jane Doe',
        'title': 'Blog Post 2',
        'content': 'Second post content',
        'date_posted': 'April 21, 2018'
    }
]


@app.route('/')
@app.route('/home')
def home():
    return render_template('home.html', posts=posts)


@app.route('/about')
def about():
    return render_template('about.html', title='About')


@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegistrationForm()
    if form.validate_on_submit():
        flash('Account created for %s!' % form.username.data, 'success')
        return redirect(url_for('home'))
    return render_template('register.html', title='Register', form=form)


@app.route('/login', methods=['GET', 'POST'])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        if form.email.data == 'admin@blog.com' and form.password.data == 'password':
            flash('You have been logged in!', 'success')
            return redirect(url_for('home'))
        else:
            flash('Login Unsuccessful. Please check username and password', 'danger')
    return render_template('login.html', title='Login', form=form)

作業ディレクトリ直下にrun.pyを新規作成し、home.pyの下記部分を移植します。
from flaskblog import app

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

home.pyの内容を別ファイルに全て移植したので、home.pyを削除します。
また、ディレクトリ直下に置いていたstatictemplateフォルダをflaskblog配下に移動します。
これでパッケージ構成の修正が完了しました。


*所感

DB接続については、sqlite で十分動作確認はできるので、プロトタイプなど少ないデータで確認を行いたい段階では sqlite だと早く実装できます。
パッケージ構成の考え方や別ディレクトリにあるファイルのimportなどの理解ができていなかったのですが、自分で構成を修正してみることで理解が深まりました。

Previous
Next Post »

人気の投稿