Flaskでログイン画面を実装するときの備忘録

# 概要
Flaskでログイン画面を実装するときの覚え書き。

# ファイル構成
```
.
├── Dockerfile
├── __init__.py 
├── app.py
├── models
│   ├── __init__.py 
│   └── models.py
├── requirements.txt
└── templates
    ├── __init__.py 
    ├── index.html
    ├── login.html
    └── signup.html
```


# models.py
```python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin


app = Flask(__name__,template_folder='../templates')
app.config['SQLALCHEMY_DATABASE_URI']= 'postgresql://postgres:password@10.20.1.13:5432/test'
app.config['SECRET_KEY'] = os.urandom(24)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO']=True

db = SQLAlchemy(app)

login_manager = LoginManager()
login_manager.init_app(app)


class User(UserMixin, db.Model):
	id = db.Column(db.Integer, primary_key=True)
	username = db.Column(db.Text, nullable=False, unique=True)
	password = db.Column(db.Text)
```
# login.html
```python
{% extends 'index.html' %}
{% block content %}
<h1>ログイン画面</h1>
<form method="POST">
    <label for="">ユーザ名</label>
    <input type="text" name="username" required>
    <label for="">パスワード</label>
    <input type="password" name="password" required>
    <input type="submit" value="ログイン">
    {% if error_message %}
    <p style="color: red;">{{ error_message }}</p>
    {% endif %}
    <br><br>
    <a href='/signup'>戻る</a>
</form>
{% endblock %}
```

# signup.html
```python
{% extends 'index.html' %}
{% block content %}
<h1>ユーザ登録</h1>
<form method="POST">
    <label for="">ユーザ名</label>
    <input type="text" name="username" required>
    <label for="">パスワード</label>
    <input type="password" name="password" required>
    <input type="submit" value="サインイン">
    {% if error_message %}
    <p style="color: red;">{{ error_message }}</p>
    {% endif %}
    <br><br>
    <a href='/login'>ログインはこちら</a>
</form>

{% endblock %}
```

# index.html
```html
{% block content %}
<html lang="jp">
<head>
    <meta charset="UTF-8">
    <title>テスト</title>
</head>
<body>
    <a href="/logout" role="button">ログアウト</a>
</body>
</html>
{% endblock %}
```

# app.html
```python
from models.models import db, app, login_manager
from flask import render_template, request, redirect
from flask_login import login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash


@login_manager.unauthorized_handler
def unauthorized():
    return redirect('/signup')


@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))


@app.route('/logout')
@login_required
def logout():
    logout_user()
    return redirect('/login')


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter_by(username=username).first()
        if not user:
            return render_template('login.html', error_message='登録されていません')
        if check_password_hash(user.password, password):
            login_user(user)
            return redirect('/')
    else:
        return render_template('login.html')


@app.route('/signup', methods=['GET', 'POST'])
def signup():
    if request.method == "POST":
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter_by(username=username).first()
        if user:
            return render_template('signup.html', error_message='既に使われているユーザー名です')

        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('/', methods=['GET','POST'])
@login_required
def index():
"""省略"""
    return render_template('index.html', test = test)


if __name__ == '__main__':
    app.debug = True
    app.run(debug=True, host='0.0.0.0', port=5003)
```
# 画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/8862145d-0e24-d6ef-ccb0-792ef62bedf0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/4692599a-c90a-5699-839f-13da798c0282.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/172886d9-e694-21a0-c9c9-9754d127d161.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/9f83a4b5-c4dc-796c-3247-2c48be2398bd.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/8abac131-0b13-d97f-1eba-144ecf4dc347.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/eedbef85-565b-c08a-06cd-8b235b306fda.png)