pythonでWebアプリを作成するのに、どうしてもログイン機能が必要になりました。Flaskでログイン機能を実装するには、flask-loginが便利なようです。しかし、オブジェクト指向もクラスも理解していない状態で実装するとなると、結構難しかったです。Webアプリの作成は、かなり幅広い知識が必要みたいで、完成までかなり時間がかかりそうです。
Flaskって便利なのですが、使い方を日本語で説明してるサイトが少なくて、エラーが出ると調べるのが面倒なんですよね。
もくじ
準備
インストール
pip install flask
pip install flask-login
pip install sqlalchemy
import
FlaskとSQLAlchemyも使うので、必要なものをimportしておきます。
SQLAlchemy
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql.schema import Column
from sqlalchemy.sql.sqltypes import Integer,String
from sqlalchemy.orm import scoped_session,sessionmaker
Flask
from flask import Flask,render_template
app = Flask(__name__)
app.config['SECRET_KEY'] = 'dev'
データベース
sample.sqliteには、こんな感じで1つだけデータを入れています。
id | username | password |
---|---|---|
1 | dattesar | dattesar_pass |
flask-login
from flask_login import (
UserMixin,LoginManager,login_manager,
login_user,login_required,logout_user
)
engine = create_engine('sqlite:///sample.sqlite')
Base = declarative_base()
class User(UserMixin,Base):
__tablename__ = 'test'
id = Column(Integer,primary_key=True)
username = Column(String,unique=True)
password = Column(String,nullable=False)
Base.metadata.create_all(engine)
session_user = scoped_session(sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
))
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return session_user.query(User).get(int(user_id))
@app.route('/')
def index():
user = session_user.query(User).filter(User.username == 'dattesar').one_or_none()
login_user(user)
return render_template('login_test.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return render_template('logout.html')
こんな感じで、flask-loginの使い方のサイトにサンプルコードがのっていることが多いです。このように書けばログイン機能を実装できるのですが、何をやっているのか全くわかりません。
LoginManager
login_manager = LoginManager()
login_manager.init_app(app)
このあたりは、Login_Managerを作成して初期化しているのだろうな、と想像できますが、中で何をしているかはわかりません。FlaskやSQLAlchemyをimportした時も、中で何をしているかはわからないので、個人的にはそれほど気にはなりません。LoginManagerっていう機能を使っているんだろうな、という感じです。
@login_manager.user_loader
個人的に、すごく気になるのはここです。
@login_manager.user_loader
def load_user(user_id):
return session_user.query(User).get(int(user_id))
この関数、一体何をしてるの?
サンプルコードがのっているサイトでも、さらっと流されていてあまり説明されていないので、ちょっと調べてみました。
user_loader
This sets the callback for reloading a user from the session. The function you set should take a user ID (a
unicode
) and return a user object, orNone
if the user does not exist.Parameters: callback (callable) – The callback for retrieving a user object.
https://flask-login.readthedocs.io/en/latest/#flask_login.LoginManager.user_loader
get_id()
This method must return a
https://flask-login.readthedocs.io/en/latest/#your-user-classunicode
that uniquely identifies this user, and can be used to load the user from theuser_loader
callback. Note that this must be aunicode
– if the ID is natively anint
or some other type, you will need to convert it tounicode
.
なるほど、よくわかりません。
UserMixin
UserMixinのソースコードを見てみます。
class UserMixin(object):
https://flask-login.readthedocs.io/en/latest/_modules/flask_login/mixins.html#UserMixin
”’ This provides default implementations for the methods that Flask-Login expects user objects to have. ”’
(中略)
def get_id(self):
try:
return text_type(self.id)
(以下略)
なるほど、何となくわかってきました。
UserMixinのidカラムをテキストタイプで返す感じなのでしょうか?だから、idカラムがintなら、intに変換してねって書いてあったんですね。
class User(UserMixin,Base):
__tablename__ = 'test'
id = Column(Integer,primary_key=True)
username = Column(String,unique=True)
password = Column(String,nullable=False)
検証(UserMixin)
class User(UserMixin,Base):
__tablename__ = 'test'
username = Column(String,primary_key=True)
password = Column(String,nullable=False)
試しに、idカラム無しで作成してみると・・・
NotImplementedError: No `id` attribute - override `get_id`
やっぱりエラーが出ます。
ということは、このget_id()
を書き換えれば、Userクラスの他のカラム値を返せるみたいです。
usernameを返す場合は、こんな感じです。
class User(UserMixin,Base):
__tablename__ = 'test'
username = Column(String,primary_key=True)
password = Column(String,nullable=False)
def get_id(self):
return self.username
UserMixinとuser_loaderのイメージ
- UserMixinのget_id()の戻り値を関数load_user()の引数に
- その値を元に、idカラムがIntegerなのでint型に変換して、Userクラスのデータベースからデータを入手
よくわかっていませんが、イメージ的にはこんな感じなのでしょうか?
SQLAlchemy
query.get()
flask-loginと一緒に使っているので、最初は混同していましたが、session_user.query(User).get(int(user_id))
の部分は、SQLAlchemyの機能です。get()
を使ったことがなかったので、調べてみます。
method
https://docs.sqlalchemy.org/en/14/orm/query.htmlsqlalchemy.orm.Query.
get(ident)
Return an instance based on the given primary key identifier, orNone
if not found.
要するに、primary key
に設定されているカラムから取ってきますよ、
適合するデータがなかったりprimary key
が無いとNone
を返しますよ、ということみたいです。
それで、よくあるサンプルコードでは、id
カラムがあって、しかも、id
カラムがprimary key
に設定されているから、エラーがでないんですね。
UserMixin分割
UserMixinを分けて書くこともできるようです。
engine = create_engine('sqlite:///sample.sqlite')
Base = declarative_base()
class User(Base):
__tablename__ = 'test'
id = Column(Integer,primary_key=True)
username = Column(String,unique=True)
password = Column(String,nullable=False)
class LoginUser(UserMixin,User):
pass
Base.metadata.create_all(engine)
session_user = scoped_session(sessionmaker(
autocommit=False,
autoflush=False,
bind=engine
))
login_manager = LoginManager()
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return session_user.query(LoginUser).get(int(user_id))
@app.route('/')
def index():
user = session_user.query(LoginUser).filter(LoginUser.username == 'dattesar').one_or_none()
login_user(user)
return render_template('login_test.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return render_template('logout.html')
参照サイト
https://flask-login.readthedocs.io/en/latest/
https://docs.sqlalchemy.org/en/14/orm/query.html