Flaskでログイン機能を使ってみたいな、と思い、
CRUDアプリに追加する形で作成しました!!
CRUDは以下の記事を参考にしています。
手順
パッケージの準備
今回、CRUDアプリを作成しているので、その後、新たに必要となるパッケージをAnacondaでインストールします。
% conda install flask-login
CRUDの記事では、pipを使用していますが
flask関連は基本的にAnacondaでインストールできます!
PythonにおけるDBの準備
from flask import Flask, redirect,render_template, request
from flask_sqlalchemy import SQLAlchemy
+ from flask_login import LoginManager, UserMixin
+ import os
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
+ app.config['SECRET_KEY'] = os.urandom(24)
db = SQLAlchemy(app)
+ login_manager = LoginManager()
+ login_manager.init_app(app)
class Tweet(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(20), nullable=False)
body = db.Column(db.String(140), nullable=False)
+ class User(UserMixin, db.Model):
+ id = db.Column(db.Integer, primary_key=True)
+ username = db.Column(db.String(50), nullable=False, unique=True)
+ password = db.Column(db.String(25))
+ @login_manager.user_loader
+ def load_user(user_id):
+ return User.query.get(int(user_id))
色々気になる箇所があると思いますが、順に解説します〜!
CRUDからの変更箇所はハイライトしてあります!
秘密鍵の登録
app.config['SECRET_KEY'] = os.urandom(24)
現在ユーザーがログイン中なのか、ログアウト状態なのかなどのセッション情報を管理するため、Flaskアプリのコンフィグレーションに秘密鍵(SECRET_KEY)を定義します。
ログインマネージャー
login_manager = LoginManager()
login_manager.init_app(app)
Flask-loginを使用する上で、最も重要(活躍する)なClass、
LoginManager
を使っています。
ログインマネージャには、IDからユーザを読み込む方法、ログインが必要なときにユーザをどこに送るかなど、アプリケーションとFlask-Loginを連携させるためのコードが含まれています。
Flask-login公式より
色々ありますが、要は色々と便利な仕事してくれるから定義しようね!くらいでOKです!
(詳しく知りたい方はURL方覗いてみましょう)
また、login_manager.init_app(app)
では、アプリのインスタンス変数を用いて
ログイン周りの初期化を行なってくれます。
Userクラス
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(25))
若干Railsのモデル周りと似ていますが、DBに関しては
Flask-alchemyと同様に、Classで定義します。
このClassは、以下の図と同じ意味です。
言語化すると、以下のような状態です。
- ID 主キーの整数
- ユーザ名 50文字までの文字列型、値は必ず入る、DB内で唯一である必要あり
- パスワード 25文字までの文字列型
load_user
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
flask-loginでは、セッションに保存されているユーザIDから
ユーザオブジェクトをリロードするために使用されるので
上記のようにload_userを定義しています!
DBをコマンドで作成
以下のコマンドを実行してDB作成しましょう!
CRUDの時と同じですね!
% python
>>> from app import db
>>> db.create_all()
>>> exit()
面倒だなぁ、と思う人はシェルスクリプトを作ってみると良いですね!
例はこちらです🙌
#!/bin/sh
python -c "from app import db; db.create_all()"
先ほどのコマンドを./create_db.sh
と
ターミナルに打つだけで実行できるようにしたものです!
(もっと効率化したい場合は、Makefileという手段もあります✨)
サインアップ機能
準備が長々とありましたが、ここからが本番です🔥
早速コードをお見せします!
from flask import Flask, redirect,render_template, request
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin
+ from werkzeug.security import generate_password_hash
import os
# ~~~importからdeleteの後まで変更がないので、省略~~~
@app.route('/tweets/<int:id>/delete',methods=['GET'])
def delete(id):
tweet = Tweet.query.get(id)
#投稿を削除
db.session.delete(tweet)
#削除を反映
db.session.commit()
return redirect('/')
+ @app.route('/signup', methods=['GET', 'POST'])
+ def signup():
+ if request.method == "POST":
+ username = request.form.get('username')
+ password = request.form.get('password')
+ # Userのインスタンスを作成
+ user = User(username=username, password=generate_password_hash(password, method='sha256'))
+ db.session.add(user)
+ db.session.commit()
+ return redirect('login')
+ else:
+ return render_template('signup.html')
htmlのページはtemplates
配下に新規作成!
{% extends 'application.html' %}
{% block content %}
<h1>ユーザ登録</h1>
<form method="POST">
<label for="">ユーザ名</label>
<input type="text" name="username">
<label for="">パスワード</label>
<input type="password" name="password">
<input type="submit" value="新規登録">
</form>
{% endblock %}
signup関数はPOSTメソッドがリクエストされた時、ユーザを新規登録する関数です。
以下に、解説を載せます!
パスワードの扱い
今回、パスワードはそのまま保存するのではなく、
SHA256というハッシュ関数を利用してハッシュ値として保存しています。
SHA256は簡易的なブロックチェーンの実装にも利用されることがありますが、
SHA256について気になる方は、以下のサイトを見てみると良いかもしれません!
DBの動き
DBの動きは、CRUDの時と同じですが
今回は、ユーザの情報をセッションに追加し
問題なければ、トランザクションをコミットするようになっています。
ログイン機能
from flask import Flask, redirect,render_template, request
from flask_sqlalchemy import SQLAlchemy
- from flask_login import LoginManager, UserMixin
+ from flask_login import LoginManager, UserMixin, login_user
- from werkzeug.security import generate_password_hash
+ from werkzeug.security import generate_password_hash, check_password_hash
# ~~~importからsignupの後まで変更がないので、省略~~~
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# Userのインスタンスを作成
user = User(username=username, password=generate_password_hash(password, method='sha256'))
db.session.add(user)
db.session.commit()
return redirect('login')
else:
return render_template('signup.html')
+ @app.route('/login', methods=['GET', 'POST'])
+ def login():
+ if request.method == "POST":
+ username = request.form.get('username')
+ password = request.form.get('password')
+ # Userテーブルからusernameに一致するユーザを取得
+ user = User.query.filter_by(username=username).first()
+ if check_password_hash(user.password, password):
+ login_user(user)
+ return redirect('/tweets')
+ else:
+ return render_template('login.html')
signup
関数と流れはほとんど同じですが
若干注意点を以下にまとめています!
ユーザの取得方法
今回、ユーザはDBにある中で、名前を検索(filter_by
)してヒットしたものの中で
一番上(first
)にあるものを取得しています。
今回のDBでは、ユーザ名は主キーかつユニークなので
名前で取得しても問題はありません🙌
パスワードの検証
今回、パスワードの扱いとして、ハッシュ値で保存する形式を取っています。
そのため、check_password_hash
では入力として受け取ったパスワードを一度
ハッシュ値に変換後、DBから取得したユーザのパスワード(ハッシュ値)を比較します。
ハッシュ値が一致すれば、ログインできます✨
ログアウト機能
from flask import Flask, redirect,render_template, request
from flask_sqlalchemy import SQLAlchemy
- from flask_login import LoginManager, UserMixin, login_user
+ from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
import os
# ~~~importからloginの後まで変更がないので、省略~~~
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# Userテーブルからusernameに一致するユーザを取得
user = User.query.filter_by(username=username).first()
if check_password_hash(user.password, password):
login_user(user)
return redirect('/tweets')
else:
return render_template('login.html')
+ @app.route('/logout')
+ @login_required
+ def logout():
+ logout_user()
+ return redirect('login')
login_required
login_required
は、ログインを必要とする関数の前に、
ルーティングと同じ感覚でつけることで、制限をつけることができます。
つまり、ログインしていないと実行できない機能を作ることに役立ちます✊
また、logout_user
で、ログインの時と同様に
flask-loginがよしなにログアウトしてくれます!
できることリスト
ここまで色々とやってきましたが
記事が長くなってきたので
やってみると面白いかもな〜というリストを載せておきます!
実際に私が実装できたものを載せているので
トライしてみてください🔥
- DBのセッション・コミットに関するエラーハンドリング(初級)
- フラッシュを活用したアラート機能(初級)
- 投稿に作成時間・更新時間を追加する(中級)
- 投稿の編集・削除を投稿したユーザだけに限定する機能(中級〜上級)
コードの全容
from flask import Flask, redirect,render_template, request
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
import os
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SECRET_KEY'] = os.urandom(24)
db = SQLAlchemy(app)
login_manager = LoginManager()
login_manager.init_app(app)
class Tweet(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(20), nullable=False)
body = db.Column(db.String(140), nullable=False)
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), nullable=False, unique=True)
password = db.Column(db.String(25))
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
@app.route('/')
def top():
return render_template('top.html')
@app.route('/tweets')
def index():
tweets = Tweet.query.all()
return render_template('tweets/index.html', tweets=tweets)
@app.route('/tweets/new',methods=['GET','POST'])
def create():
if request.method == 'POST':
# POSTメソッドの時の処理。
title = request.form.get('title')
body = request.form.get('body')
tweet = Tweet(title=title,body=body)
# DBに値を送り保存する
db.session.add(tweet)
db.session.commit()
return redirect('/')
else:
# GETメソッドの時の処理
return render_template('tweets/new.html')
@app.route('/tweets/<int:id>/edit',methods=['GET','POST'])
def update(id):
tweet = Tweet.query.get(id)
if request.method == 'GET':
return render_template('tweets/edit.html',tweet=tweet)
else:
tweet.title = request.form.get('title')
tweet.body = request.form.get('body')
db.session.commit()
return redirect('/')
@app.route('/tweets/<int:id>/delete',methods=['GET'])
def delete(id):
tweet = Tweet.query.get(id)
#投稿を削除
db.session.delete(tweet)
#削除を反映
db.session.commit()
return redirect('/')
@app.route('/signup', methods=['GET', 'POST'])
def signup():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# Userのインスタンスを作成
user = User(username=username, password=generate_password_hash(password, method='sha256'))
db.session.add(user)
db.session.commit()
return redirect('login')
else:
return render_template('signup.html')
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == "POST":
username = request.form.get('username')
password = request.form.get('password')
# Userテーブルからusernameに一致するユーザを取得
user = User.query.filter_by(username=username).first()
if check_password_hash(user.password, password):
login_user(user)
return redirect('/tweets')
else:
return render_template('login.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect('login')
if __name__ == '__main__':
app.run(debug=True)
参考文献