Flask で未登録ユーザーの情報をまとめて定義する方法

Flaskは、pythonの軽量なwebフレームワークとして有名です。
そんなFlaskで、ログイン機能を実装する際によく使用されるのが、「**Flask-Login**」です。

今回取り上げたいのは、未登録ユーザー(Anonymous)についてです。

webサービスを作っていると、未登録のユーザーの扱いに困ることが、度々あります。「idがない」「usernameを取得できない」など、登録済みのユーザーと同じ処理では扱えません。
こういう情報を、取得したいときに未登録ユーザーと登録済みユーザーを区別して、その都度条件分岐して処理するのは非常に面倒ですし、**未登録ユーザーの情報が散らばってしまいます**。「usernameをanonymousにしよう」「idは0でいいや」「addressは、地球でいいか」などが、コードのいろいろなところに、はびこることになるでしょう。

このような問題を解決するために、Flask-Loginでは、未登録ユーザーを管理するための「**anonymous_user**」というプロパティを指定できます。

この記事では、anonymous_userに特化して説明と実装をします。

## 目次
- Flask-Loginの説明
- anonymous_userの使い方
- サンプルアプリの実装
- つまったところ
- おまけ

## 対象
- Flask-Loginを使っているが未登録ユーザーの定義が散らばって困ってる人
- flaskでアプリケーションを作っている人
- pythonでwebサービスを作ろうとしている人

## Flask-Loginの説明
[Flask-Loginの公式サイト](https://flask-login.readthedocs.io/en/latest/)の文頭には、このように書かれています。

>Flask-Login provides user session management for Flask. It handles the common tasks of logging in, logging out, and remembering your users’ sessions over extended periods of time.

つまり、Flaskで作られているアプリのログイン・ログアウトなどを管理してくれます。

### Flask-Loginをインストールする
まずは、Flask-Loginをインストールしましょう。

```bash
$ pip install flask-login
```

### LoginManager()のインスタンスを生成する
ログイン機能を管理するflask_loginのLoginManagerクラスを利用します。

```python:app.py
from flask import Flask
from flask_login import LoginManager

app = Flask(__name__)
app.config["SECRET_KEY"] = "sample1202"

login_manager = LoginManager()
login_manager.init_app(app)
```

## anonymous_userの使い方

### デフォルトで定義されていること
flask_loginには、デフォルトで未登録ユーザーのいくつかのメソッドとプロパティが定義されています。

- `is_active` and `is_authenticated` are False
- `is_anonymous` is True
- `get_id()` returns None

これらは、自分で何も定義しなくても呼ぶことができます。

### 未登録ユーザー情報をカスタマイズ
実際にアプリを作っていると、登録済みユーザーに使っている上記以外のメソッドやプロパティを未登録ユーザーにも適用したい場面が出てくるでしょう。

そのために自分が使用しやすいように、また、**情報を散らばらせずにまとめて管理するために**追加のメソッドとプロパティを定義します。

```python:custom_anonymous_user.py
from flask_login import AnonymousUserMixin


# 未登録ユーザーのデフォルトのメソッドなどが定義されているAnonymousUserMixinをさせます。
class CustomAnonymousUser(AnonymousUserMixin):
    # プロパティとしても呼べるようにしておきます。
    @property
    def id(self):
        return 0

    @property
    def username(self):
        return "Anonymous"
    
    @property
    def address(self):
        return "earth"
    
    @property
    def age(self):
        return 99
```

app.pyにanonymous_userプロパティを定義します。

```python:app.py
# 先程の内容の最下部に追記してください。

# CustomAnonymousUserは自分で定義するクラス
login_manager.anonymous_user = CustomAnonymousUser
```

## サンプルアプリの実装
やはり実装してなんぼ！サンプルアプリを作っていきましょう。
ここまでの内容に少し加えて、正しく未登録ユーザの情報が取得できるか、ブラウザで確認してみます。

### ディレクトリ構成

```
├── app.py
├── custom_anonymous_user.py
└── templates
    └── index.html
```

### コントローラーを実装
コントローラー(flaskではviewsって呼ぶこともある)を書いていきます。

flask_loginには、**current_user**という便利な機能もあります。
current_userは、名前のままで、現在のユーザ情報を管理してくれています。current_userという変数は、もちろんコントローラーの中で使用できますし、ビューでも引数として渡さなくても使用できます。

```python:app.py
from flask import Flask, render_template
from flask_login import LoginManager, current_user # ← current_userを追記

from custom_anonymous_user import CustomAnonymousUser

app = Flask(__name__)
app.config["SECRET_KEY"] = "sample1202"

login_manager = LoginManager()
login_manager.init_app(app)
login_manager.anonymous_user = CustomAnonymousUser

@app.route("/", methods=["GET"])
def index():
    return render_template("index.html")

if __name__ == "__main__":
    app.run()
```

### 未登録ユーザー管理クラスを実装
上記で実装した`custom_anonymous_user.py`の内容と全く同じ。

```custom_anonymous_user.py
# 省略
```

### ビューを実装
htmlも書きます。

```html:templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>サンプル</title>
</head>
<body>
    <h2>未登録ユーザー情報取れてる？？</h2>
    <p>id: {{ current_user.id }}</p>
    <p>username: {{ current_user.username }}</p>
    <p>address: {{ current_user.address }}</p>
    <p>age: {{ current_user.age }}</p>
</body>
</html>
```

あとは、app.pyを動かすだけです。

```
$ python app.py
```

## つまったところ
### get_id()とかis_anonymousが使えないぞ？
原因:
カスタマイズした未登録ユーザー管理クラス(今回だとCustomAnonymousUser)に**AnonymousUserMixin**クラスを継承していなかったから。

未登録ユーザー用にデフォルトで定義されているget_id()とかis_anonymousを今後一切使用しない予定であれば、継承しなくても正常に動きます。

## おまけ
最後までお付き合いいただき、ありがとうございました。
少しでもお役に立てたのであれば、嬉しいです。

[Renttle](htttps://renttle.jp)というサービスを開発中です。ぜひ、使ってみて、レビューをください。