趣味で作成しているWebアプリ(Python+Flask+waitress)に管理画面をつけたので手順メモ。
管理画面で行いたいことは、データベース(MySQL)への新規データ追加、既存レコード編集、削除だけとシンプルなので、Flask-AdminとFlask-Loginでささっと構築。
慣れていない人でも2~3時間もあれば完成する。とても簡単。
手順
- 必要な情報をそろえる
- 必要な環境、パッケージを用意
- 管理画面とログイン画面のコードを書く
- 動作確認
1. 必要な情報をそろえる
まずは必要な情報を揃えます。管理対象のデータベース情報を確認します。DB名、テーブル名、カラム名など。これをもとにモデル定義します。
DBへの接続はWebアプリ側で用意してあるのでそれを流用します。
2. 必要な環境、パッケージを用意
必要なのは以下の通り。pipでinstallします。
- Flask-Admin
- Flask-SQLAlchemy
- Flask-Login
- Werkzeug
- pymysql
いきなり本環境に入れるのは怖いため、実験用のvenv仮想環境を用意して、そこで一通り試してから本番用プロジェクトにマージします。
3. 管理画面とログイン画面のコードを書く
管理画面とログイン画面を定義するコードを書きます。
とりあえずapp.pyに全部書いて動作確認します。
基本動作が確認できたら、可読性、保守性のため 本番用app.py はできるだけシンプルにしておきたいので、管理画面/ログイン画面用に書いたコードを app.py の外にadmin.py とかに分離して、BluePrintで読み込ませます。
管理画面を使うユーザーは自分だけでDBにUserテーブル作るのも面倒なので、ログイン用アカウントもコードで定義しちゃいます(行儀悪いけど)。パスワードはさすがに平文管理はまずいのでハッシュ化して、dotenvに保持します。
管理画面のhtmlテンプレートは、デフォではFlask-Adminのライブラリ内にありますが、カスタムテンプレートを指定できるので、ライブラリ内からコピーして、templates/admin/base.html において、それを加工していきます。
ライブラリの場所は下記のような感じです。
<your_virtualenv>/lib/python3.8/site-packages/flask_admin/templates/bootstrap3/admin/base.html
ログイン画面用のテンプレート(login.html)も、/templatesフォルダ内に置きます。
アプリ全体のCSRFがあるので、ログイン画面のform要素も忘れずに forms.pyに定義しておきます(未定義だとThe CSRF token is missing.でエラーになる)。
ログアウトボタン(/logout)を base.html に追加します。
4. 動作確認
実験環境で基本部分を確認できたら、本番用のプロジェクトにマージして動作確認。本当にお手軽に構築できるのが素敵。
コード
from flask import Flask, jsonify, request, redirect, url_for, render_template
from flask import Blueprint
from waitress import serve
from flask_wtf.csrf import CSRFProtect
from dotenv import load_dotenv
import os
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin, AdminIndexView
from flask_admin.contrib.sqla import ModelView
from flask_login import LoginManager, UserMixin, login_user, logout_user, current_user, login_required
from werkzeug.security import check_password_hash
from forms import LoginForm
~その他のimport部は省略~
app = Flask(__name__)
# .envファイルから環境変数を読み込む
load_dotenv()
# その他の既存コードは省略
# SQLAlchemyの設定
app.config['SQLALCHEMY_DATABASE_URI'] = f"mysql+pymysql://{os.getenv('DATABASE_USER')}:{os.getenv('DATABASE_PASSWORD')}@{os.getenv('DATABASE_HOST')}/{os.getenv('DATABASE_NAME')}"
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# テーブルのモデル定義。
class Info(db.Model):
__tablename__ = 'info'
id = db.Column(db.Integer, primary_key=True)
natural_number = db.Column(db.Integer, nullable=False)
explanation = db.Column(db.Text)
url = db.Column(db.Text)
class PrimeList(db.Model):
__tablename__ = 'prime_list'
id = db.Column(db.Integer, primary_key=True)
prime_num = db.Column(db.Integer)
prime_info = db.Column(db.Text)
# Flask-Loginの設定
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = 'login'
# ハッシュ化されたパスワードを設定
ADMIN_USERNAME = '*****'
ADMIN_PASSWORD_HASH = 'pbkdf2:sha256:*****'
class User(UserMixin):
def __init__(self, id):
self.id = id
@login_manager.user_loader
def load_user(user_id):
if user_id == ADMIN_USERNAME:
return User(user_id)
return None
@app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
username = form.username.data
password = form.password.data
if username == ADMIN_USERNAME and check_password_hash(ADMIN_PASSWORD_HASH, password):
user = User(username)
login_user(user)
return redirect(url_for('admin.index'))
return render_template('login.html', form=form)
@app.route('/logout')
def logout():
logout_user()
return redirect(url_for('login'))
class MyAdminIndexView(AdminIndexView):
def is_accessible(self):
return current_user.is_authenticated
class MyModelView(ModelView):
def is_accessible(self):
return current_user.is_authenticated
# Flask-Adminの設定
admin = Admin(app, name='My Admin', template_mode='bootstrap3', index_view=MyAdminIndexView(), base_template='admin/base.html')
admin.add_view(MyModelView(Info, db.session))
admin.add_view(MyModelView(PrimeList, db.session))