2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Flaskでログイン状態の時ユーザ名を常に表示する(sessionとg)

Posted at

目次

はじめに

Flaskでログイン機能を実装し、ログインしていることをわかりやすくするためにユーザー名を表示したいと思ったのですが、全てのページに同じ内容(今回はユーザー名)を表示する際、簡単な方法が無いか調べてみました。
結論に至るまでの試行錯誤も余談として記録しようと思います。

結論

ログイン時にsessionにユーザー名を格納し、base.html等全ページに適用されるhtmlで表示させます。

環境

ソフトウェア バージョン
Python 3.9.0
Flask 2.2.2
Flask-Login 0.6.2

ディレクトリ構成

flask
├ flask_app
│ ├ models
│ ├ templates
│ │ ├ base.html
│ ├ └ ...
│ ├ views
│ │ └ auth.py
│ ├ __init__.py
│ └ ...
└ tests

Flaskのflashの内容をpytestでテストすると同様です。今回はviewフォルダ内のauth.py、templlatesフォルダ内のbase.htmlを使用します。

ログイン時の設定

auth.py

from flask import Blueprint, render_template, request, \
    flash, url_for, redirect,session
from flask_login import login_user
from werkzeug.security import check_password_hash
from sksk_app.models.questions import User

auth = Blueprint('auth', __name__, url_prefix='/auth')

@auth.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        email = request.form['email']
        password = request.form['password']

        user = User.query.filter_by(email=email).first()
        
        if not user or not check_password_hash(user.password, password):
            flash('パスワードが異なります')
            return redirect(url_for('auth.login'))

        flash('ログインしました')
        login_user(user)
        session['user_id'] = user.id
        session['user_name'] = user.name
        return redirect(url_for('pg.toppage'))

    page_title = 'ログイン'
    return render_template('auth/login.html',page_title=page_title)

実際のコードではなく、importなどは今回の設定に必要なものだけに絞っています。
ログインの仕組みについては本記事では割愛します。
こちらのログイン関係のコードは Flask-Login を使用してアプリケーションに認証を追加する方法 | DigitalOcean を大いに参考にしています。

sessionへのユーザー名の格納

auth.py
login_user(user)
session['user_id'] = user.id
session['user_name'] = user.name

「login_user」メソッドでログインした後に、sessionにそれぞれ値を格納します。

session is a dict that stores data across requests. When validation succeeds, the user’s id is stored in a new session. The data is stored in a cookie that is sent to the browser, and the browser then sends it back with subsequent requests. Flask securely signs the data so that it can’t be tampered with.
Login - Blueprints and Views — Flask Documentation (2.2.x)

「session['user_name']」に、ログイン時に表示したい名前を格納します。

全ページに表示する

1つのコードで全てのページに表示させるには、base.htmlという全てのページに共通するファイルを用意し、それぞれのページで読み込みます。

Each page in the application will have the same basic layout around a different body. Instead of writing the entire HTML structure in each template, each template will extend a base template and override specific sections.
Templates — Flask Documentation (2.2.x)

このbase.htmlに「session['user_name']」を表示させれば良いだけです。

注: Web上の記事や書籍では、このBase Layoutのbase.htmlをlayout.htmlとするところも多いです。私は公式チュートリアルに沿ってbase.htmlにしています。

base.html

表示させたいところに「{{session['user_name']}}」を記述します。
下記の例ではpタグの中に表示、続けて「さん」を表示させる様にします。

base.html
<p> {{session['user_name']}} さん</p>

これで全ページにユーザー名を表示することができます。

余談 render_templateとg

render_templateで全ページに設定?

ページ間で値をやりとりするとしたらsessionを使用するのは何かの機会に知りました。
また、画面に変数の値を表示する方法といえば、render_template()でHTMLを生成する際に変数を設定する方法しか知りませんでした。

ということで、render_template()とsessionを組み合わせるのかなあと思ったのですが、そのやり方でやると、viewの全ての関数でsessionから情報取得→render_templateで値の受け渡し、という膨大なコードが生まれてしまいます。
世の中のアプリがこんな面倒なことをしているとは思えないので、何か良い策は無いかなあと思っていました。

sessionもbase.htmlによるhtml生成も知っていたのに、なぜか二つを組み合わせることが思いつきませんでした。この方法を教えてくれた以下のStacOverflowの解答に感謝です。

公式チュートリアルのやり方 Flask.g

公式ではFlask.gというものを使用して、ユーザー名の表示をしています。

base.html
{% if g.user %}
  <li><span>{{ g.user['username'] }}</span>
  <li><a href="{{ url_for('auth.logout') }}">Log Out</a>
{% else %}
  <li><a href="{{ url_for('auth.register') }}">Register</a>
  <li><a href="{{ url_for('auth.login') }}">Log In</a>
{% endif %}

Templates — Flask Documentation (2.2.x)より一部抜粋

この「g」とはなんでしょうか?
アプリケーション・コンテクストを通じてデータを格納できる場所とのことです。

A namespace object that can store data during an application context. This is an instance of Flask.app_ctx_globals_class, which defaults to ctx._AppCtxGlobals.
This is a good place to store resources during a request.
flask.g - API — Flask Documentation (2.2.x)

ではアプリケーション・コンテクストとは?
アプリケーションレベルのデータをリクエストの間に記録しておくものです。

The application context keeps track of the application-level data during a request, CLI command, or other activity. Rather than passing the application around to each function, the current_app and g proxies are accessed instead.
The Application Context — Flask Documentation (2.2.x)

なんだか良さそうなものの気もしますが、sessionが「複数のリクエストにまたがって」(session is a dict that stores data across requests.)データを格納しておくのに対し、アプリケーション・コンテクストは「1つのリクエスト・CLIコマンド・その他のアクティビティの間」(The application context keeps track of the application-level data during a request, CLI command, or other activity.)データを記録するもの、とあります。

従って、リクエストが新たに作られる度にgにデータを格納する必要があります。
公式チュートリアルのアプリではgを利用するために、以下のようなコードがあります。

auth.py
@bp.before_app_request
def load_logged_in_user():
    user_id = session.get('user_id')

    if user_id is None:
        g.user = None
    else:
        g.user = get_db().execute(
            'SELECT * FROM user WHERE id = ?', (user_id,)
        ).fetchone()

bp.before_app_request() registers a function that runs before the view function, no matter what URL is requested.
Login - Blueprints and Views — Flask Documentation (2.2.x)

before_app_request()で、View機能が実行される前に、sessionからユーザーのidを取得し、そこからDBからユーザー情報を取得、gに格納、という流れです。

before_app_request()はbefore_request()のようなものだけど、Blueprintで扱うものに対して実行されるようです。

Like before_request(), but before every request, not only those handled by the blueprint. Equivalent to Flask.before_request().
before_app_request(f) - API — Flask Documentation (2.2.x)

before_request()はBlueprintを適用していないものに対し、各リクエストの前に機能を登録して実行するものです。

Register a function to run before each request.
For example, this can be used to open a database connection, or to load the logged in user from the session.
before_request(f) - API — Flask Documentation (2.2.x)

おそらくリクエスト毎にこの処理を行う目的として、本当にそのユーザーかどうかの確認をするものだと思うのですが、1つのリクエスト毎にDBから情報を取得することが疑問だったので、とりあえずこの処理は省くことにしました。

そもそもgを実装したのは、公式チュートリアルのテストを実行する際に、gから情報を取得することもテスト項目に入っていたからでした。とりあえず今のところはテストも含め、gは利用せず、sessionでユーザー名の表示をしておこうと思います。

参考

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?