久しぶりに Flask のプロジェクトを作成しようとしたらディレクトリ構成からさっぱり忘れていたので、チュートリアルをなぞって思い出してみる。ついでに最新の環境(pipenv とか)を適用する。
参考にしたサイト
基本的にはここの手順通り、ただディレクトリ構成とか少し直している。
環境構築 エディタは VSCode を使い、pipenv でモジュール管理を行う。まずはじめに Linter として flake8 と Formatter として autopep8 をインストールする。
pipenv install flake8 autopep8
続いて Flask 関係のモジュールをインストールする。
pipenv install flask flask-sqlalchemy flask-wtf sqlalchemy
pipenv について ちょっと余談だけど pipenv は install するとデフォルトで venv 環境(仮想環境)にモジュールがインストールされる。
Python3 組み込みの venv は Windows 環境だと .venv/Scripts/activate.bat
で環境が有効になり、 .venv/Scripts/deactivate.bat
で環境から抜ける動きをするはずだが、なぜか deactivate がボクの環境ではうまく動かなく、それが原因で今まで使ってこなかった。(PowerShell を exit すれば仮想環境から抜けれたが、毎回するのはめんどい…)
pipenv では pipenv shell
で仮想環境が有効になり、 exit
で抜けることができる。それだけでなく、仮想環境での実行は pipenv run python ****.py
という風にアクティベートしなくても実行できたり、分かりやすくやり易いようになっている。モジュールのインストールもアクティベート無しで仮想環境の外から行うことができるので、基本的に仮想環境を無効のまま操作することができていい感じだ。
ディレクトリ構成 先にディレクトリ構成を晒すと以下のような感じにしていく。
. ├── Pipfile ├── Pipfile.lock ├── README.md ├── app │ ├── __init__.py │ ├── controllers │ │ ├── __init__.py │ │ └── auth_controller.py │ ├── forms │ │ ├── __init__.py │ │ └── auth_form.py │ ├── models │ │ ├── __init__.py │ │ └── auth.py │ ├── static │ └── templates │ ├── 404.html │ └── auth │ └── signin.html ├── config.py └── run.py
環境変数の登録 開発環境では以下 2 点の環境変数を設定しておく。
環境変数
値
PIPENV_VENV_IN_PROJECT
true
FLASK_ENV
development
ルートのファイル作成 run.py プロジェクトルートに実行の起点となる run.py を作成する。参考サイトでは host='0.0.0.0'
となっているが、実行したとき http://0.0.0.0:8000/ にアクセスしても正常に表示されなかったので、host='localhost'
としている。(http://localhost:8000/ なら問題なくアクセスできた。)
""" app/run.py """ from app import appapp.run(host='localhost' , port=8080 , debug=True )
config.py flask の設定を記述する、これは参考サイトのままだ。DB は sqlite になっているが、まずはそのままにしておく。
""" app/config.py """ import osDEBUG = True BASE_DIR = os.path.abspath(os.path.dirname(__file__)) SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(BASE_DIR, 'app.db' ) DATABASE_CONNECT_OPTIONS = {} THREADS_PER_PAGE = 2 CSRF_ENABLED = True CSRF_SESSION_KEY = "secret" SECRET_KEY = "secret"
モデルの作成 参考サイトではルートの app ディレクトリ配下にはモジュール単位で機能を置くようになっているが、モジュール単位ではなくレイヤー単位で分けたいと思う。ちなみに django もモジュール単位(機能単位?)で分けるのが慣例のようだ。しかし models.py や controllers.py といったファイルがモジュール別とは言え乱立するのはなんか気持ちが悪いし、rails 的なレイヤー単位のディレクトリ構成に慣れているので後者を採用したい。
""" app/models/auth.py """ from app import dbclass Base (db.Model) : __abstract__ = True id = db.Column(db.Integer, primary_key=True ) date_created = db.Column(db.DateTime, default=db.func.current_timestamp()) date_modified = db.Column(db.DateTime, default=db.func.current_timestamp(), onupdate=db.func.current_timestamp()) class User (Base) : __tablename__ = 'auth_user' name = db.Column(db.String(128 ), nullable=False ) email = db.Column(db.String(128 ), nullable=False , unique=True ) password = db.Column(db.String(192 ), nullable=False ) role = db.Column(db.SmallInteger, nullable=False ) status = db.Column(db.SmallInteger, nullable=False ) def __init__ (self, name, email, password) : self.name = name self.email = email self.password = password def __repr__ (self) : return '<User %r>' % (self.name)
pylint ではなく flake8 を使う ちなみに Linter として flake8 を採用しているが、flake8 ではなく pylint を使うとこのモデルで大量のエラーを吐く。(db.Column
が見つからないよ~というエラーが全ての db 参照に出る)
pylint 側の問題なのか、VSCode(Python 拡張)の問題なのか微妙だが、SQLAlchemy intellisense #292 に書いてある通り pylint ではなく flake8 を使うことで一応回避はできる。
ビューの作成 ビューとしてフォームクラス(app/forms/auth_form.py
)とテンプレート(app/templates/auth/signin.html
)を作成する。
""" app/forms/auth_form.py """ from flask_wtf import Formfrom wtforms import TextField, PasswordFieldfrom wtforms.validators import Required, Emailclass LoginForm (Form) : email = TextField('Email Address' , [Email(), Required(message='Forgot your email address?' )]) password = PasswordField('Password' , [ Required(message='Must provide a password. ;-)' )])
{% macro render_field(field, placeholder=None) %} {% if field.errors %} <div > {% elif field.flags.error %} <div > {% else %} <div > {% endif %} {% set css_class = 'form-control ' + kwargs.pop('class', '') %} {{ field(class=css_class, placeholder=placeholder, **kwargs) }} </div > {% endmacro %} <div > <div > <legend > Sign in</legend > {% with errors = get_flashed_messages(category_filter=["error"]) %} {% if errors %} <div > {% for error in errors %} {{ error }} <br > {% endfor %} </div > {% endif %} {% endwith %} {% if form.errors %} <div > {% for field, error in form.errors.items() %} {% for e in error %} {{ e }} <br > {% endfor %} {% endfor %} </div > {% endif %} <form method ="POST" action ="." accept-charset ="UTF-8" role ="form" > {{ form.csrf_token }} {{ render_field(form.email, placeholder="Your Email Address", autofocus="") }} {{ render_field(form.password, placeholder="Password") }} <div > <label > <input type ="checkbox" name ="remember" value ="1" > Remember Me </label > <a role ="button" href ="" > Forgot your password?</a > <span class ="clearfix" > </span > </div > <button type ="submit" name ="submit" > Sign in</button > </form > </div > </div >
テンプレートのフォーマットが崩れているのは VSCode のフォーマッタが jinja2 テンプレートを認識していない(サポートされていない)からだ。VSCode 拡張で jinja はあるのだが、コードハイライトしかしてくれないぽい。
正直これだけの理由で jinja2 じゃないテンプレートにしたいんだけど、他のテンプレートエンジンの情報が無さ過ぎて jinja2 から動けないでいる…
コントローラの作成 基本的にアプリ自体はそれほどスケールしないことがほとんどだが、コントローラが 2 つになったらもう必要になることと、使い方を忘れない為に(!)Blueprint で分割を前提につくる。
""" app/controllers/auth_controller.py """ from flask import Blueprint, request, render_template, \ flash, session, redirect, url_for from werkzeug import check_password_hashfrom app.forms.auth_form import LoginFormfrom app.models.auth import Usermod_auth = Blueprint('auth' , __name__, url_prefix='/auth' ) @mod_auth.route('/signin/', methods=['GET', 'POST']) def signin () : form = LoginForm(request.form) if form.validate_on_submit(): user = User.query.filter_by(email=form.email.data).first() if user and check_password_hash(user.password, form.password.data): session['user_id' ] = user.id flash('Welcome %s' % user.name) return redirect(url_for('auth.home' )) flash('Wrong email or password' , 'error-message' ) return render_template("auth/signin.html" , form=form)
app/__init__.py
の作成最後に app/__init__.py
を作成し、アプリの初期化や Blueprint へのコントローラ登録を行ったりする。
""" app/__init__.py """ from flask import Flask, render_templatefrom flask_sqlalchemy import SQLAlchemyapp = Flask(__name__) app.config.from_object('config' ) db = SQLAlchemy(app) @app.errorhandler(404) def not_found (error) : return render_template('404.html' ), 404 from app.controllers.auth_controller import mod_authapp.register_blueprint(mod_auth) db.create_all()
開発サーバーで動作確認 これで最低限のファイル類は準備できたので、以下コマンドで開発サーバーを立ち上げて動作確認を行う。
http://localhost:8000/auth/signin/ にアクセスしてサインインのフォームが表示されれば OK。
実行環境
Python 3.6.3
flask 1.0.2
sqlalchemy 1.2.8