[ログイン機能] テストユーザー作り、ログイン

# いるもん

```ruby:準備
pip install flask flask-wtf flask-login flask-bcrypt email_validator
```

# 前提条件
## テストユーザー作りログインしないと、ログインできない
## 新規登録画面でユーザー作りログインしたら、ログイン❌


### テストユーザー作成 (全部コマンド)

```ruby:
flask shell
from app import db, User
from werkzeug.security import generate_password_hash

# 新しいユーザーを作成
username = 'test'
password = 'password'  # 任意のパスワード
hashed_pw = generate_password_hash(password)

new_user = User(username=username, password=hashed_pw)
db.session.add(new_user)
db.session.commit()

print(f"User '{username}' created successfully!")

exit()

サーバー再起動

flask run

テストユーザー   "test" とpw "password"でログイン
```

# 今のテスト(ログイン用ユーザー)

```ruby:
 user : testuser

 pw : testpassword
```

### [in 新規登録画面]ログインしてたら、マイページへ行かせる

```ruby:
from flask_login import login_required, current_user

@app.route("/register", methods=["GET", "POST"])
@login_required  # ログインしているユーザーがアクセスできないように
def register():
    if current_user.is_authenticated:
        flash('既にログインしてんじゃん.', 'warning')
        return redirect(url_for('mypage')) 
```

### 既に会員登録されてたら、別の画面にリダイレクトさせる

```ruby:
form = RegisterForm()
    if form.validate_on_submit():
        existing_user = User.query.filter_by(username=form.username.data).first()
        if existing_user:
            flash('Username already exists.', 'danger')
            return redirect(url_for('リダイレクトさせたいpage'))
```

### 会員登録されてなかったら、パスワードを暗号化し、DBにユーザーとパスワードを追加してmypageへ

```ruby:
hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        new_user = User(username=form.username.data, password=hashed_pw)
        db.session.add(new_user)
        db.session.commit()
        login_user(new_user)
        flash('Account created successfully!', 'success')
        return redirect(url_for('mypage'))
    return 
```

# お決まり文 + secretKey設定 + login_manager設定

```ruby:
from flask import Flask, render_template, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, login_user, login_required, logout_user, current_user, UserMixin
from flask_bcrypt import Bcrypt
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField
from wtforms.validators import DataRequired, Length, EqualTo
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'

db = SQLAlchemy(app)
# Flask-Loginの設定
login_manager = LoginManager(app)
login_manager.login_view = 'login'  # ログインしていない場合、loginページにリダイレクト
login_manager.login_message = "ログインしてください"  # ログインが必要な場合に表示されるメッセージ
bcrypt = Bcrypt(app)
```

### Userモデル

```ruby:
class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True, nullable=False)
    password = db.Column(db.String(150), nullable=False)
```

# secretKey設定

```ruby:
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)
```

# ログインマネージャー設定

```ruby:
db = SQLAlchemy(app)
# Flask-Loginの設定
login_manager = LoginManager(app)
login_manager.login_view = 'login'  # ログインしていない場合、loginページにリダイレクト
login_manager.login_message = "ログインしてください"  # ログインが必要な場合に表示されるメッセージ
bcrypt = Bcrypt(app)
```

# login画面 templates/login.html

```ruby:
<!DOCTYPE html>
<html>
<head><title>Login</title></head>
<body>
    <form method="POST">
        {{ form.hidden_tag() }}
        {{ form.username.label }} {{ form.username() }}<br>
        {{ form.password.label }} {{ form.password() }}<br>
        {{ form.submit() }}
    </form>
</body>
</html>
```

# run.py中身

```ruby:
from app import app, db

if __name__ == "__main__":
    with app.app_context():
        db.create_all()  
        
    app.run(debug=True, use_reloader=True)
```

### mypageに行った時の処理

```ruby:
@app.route("/mypage")
@login_required
def mypage():
    return f"ようこそ、{current_user.id}さん！"
```

### ログアウトさせたら、ログインページへ

```ruby:
@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for("login"))
```

### LoginFormモデル : ログイん 何使ってログインさせたい?(例 : 名前とパスワード)

```ruby:
class LoginForm(FlaskForm):
    username = StringField("Username", validators=[DataRequired()])
    password = PasswordField("Password", validators=[DataRequired()])
    submit = SubmitField("Login")
```

### RegisterFormモデル : 何使ってユーザー登録させたい？

```ruby:
class RegisterForm(FlaskForm):
    username = StringField('Username', validators=[DataRequired(), Length(min=2, max=150)])
    password = PasswordField('Password', validators=[DataRequired(), Length(min=6)])
    confirm_password = PasswordField('Confirm Password', validators=[DataRequired(), EqualTo('password')])
    submit = SubmitField('Register')
```

### ユーザー登録されてたら登録されてるidを見つける

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

### [ログイン画面]ユーザー登録されてたらログインさせmypageへ

```ruby:
@app.route("/", methods=["GET", "POST"])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()
        if user and bcrypt.check_password_hash(user.password, form.password.data):
            login_user(user)
            flash('ログイン成功!', 'success')
            return redirect(url_for("mypage"))
```

### [in ログイン画面]ユーザー登録dataがない エラーメッセージ表示し、ログイン画面へ 

```ruby:
@app.route("/", methods=["GET", "POST"])
def login():
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.username.data).first()

    else:
        flash('ログイン失敗. 名前とパスワチェックしてね', 'danger')
    return render_template("login.html", form=form)
```


### [in 新規登録画面] 登録されてたらログイン画面へ、登録されてなかったら新規登録画面へ遷移、登録する

```ruby:app.py
@app.route("/register", methods=["GET", "POST"])
def register():
    form = RegisterForm()
    if form.validate_on_submit():
        existing_user = User.query.filter_by(username=form.username.data).first()
        if existing_user:
            flash('Username already exists.', 'danger')
            return redirect(url_for('register'))
        
        hashed_pw = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        new_user = User(username=form.username.data, password=hashed_pw)
        db.session.add(new_user)
        db.session.commit()
        login_user(new_user)
        flash('アカウント作成成功!', 'success')
        return redirect(url_for('mypage'))
    return render_template("register.html", form=form)
```

### 新規登録画面 html

```ruby:templates/register.html
<!DOCTYPE html>
<html>
<head><title>Register</title></head>
<body>
    <form method="POST">
        {{ form.hidden_tag() }}
        {{ form.username.label }} {{ form.username() }}<br>
        {{ form.password.label }} {{ form.password() }}<br>
        {{ form.submit() }}
    </form>
    <a href="{{ url_for('login') }}">ログインはこちら</a>
</body>
</html>
```

### ユーザー新規登録したら、DBからユーザーとパスワードを探し、パスワードとユーザーを一致させmypageへ行かせる or ログインできなかったらエラー表示

```ruby:
if form.validate_on_submit():
    user = User.query.filter_by(username=form.username.data).first()
    if user and bcrypt.check_password_hash(user.password, form.password.data):
        login_user(user)
        return redirect(url_for("mypage"))
    else:
        flash('Login failed. Check your username and password.', 'danger')
```

### DBからデータ探す

```ruby:
User.query.filter_by(username=form.username.data).first()
```

### ログインさせてる人にのみ、させたい処理 "@login_required"

```ruby:ログインさせてる人にのみ、maypage表示
@app.route("/mypage")
@login_required
def mypage():
    return f"ようこそ、{current_user.username}さん！"
```


### password暗号化失敗エラー (テストユーザー作ってログイン時)

```ruby: ValueError: Invalid salt
Form submitted successfully!
[2025-04-27 22:59:28,701] ERROR in app: Exception on / [POST]
Traceback (most recent call last):
  File "/Users/chomechomesuke/.pyenv/versions/3.13.1/lib/python3.13/site-packages/flask/app.py", line 1511, in wsgi_app
    response = self.full_dispatch_request()
  File "/Users/chomechomesuke/.pyenv/versions/3.13.1/lib/python3.13/site-packages/flask/app.py", line 919, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/Users/chomechomesuke/.pyenv/versions/3.13.1/lib/python3.13/site-packages/flask/app.py", line 917, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/chomechomesuke/.pyenv/versions/3.13.1/lib/python3.13/site-packages/flask/app.py", line 902, in dispatch_request
    return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)  # type: ignore[no-any-return]
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  File "/Users/p/app/app.py", line 50, in login
    if user and bcrypt.check_password_hash(user.password, form.password.data):
                ~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/chomechomesuke/.pyenv/versions/3.13.1/lib/python3.13/site-packages/flask_bcrypt.py", line 225, in check_password_hash
    return hmac.compare_digest(bcrypt.hashpw(password, pw_hash), pw_hash)
                               ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^
ValueError: Invalid salt
```


### パスワ暗号化失敗エラー解決策 (Valuseerror)

```ruby:
flask shell

from app import app, db, User, bcrypt

app.app_context().push()

db.create_all()

username = 'testuser'  
password = 'testpassword'  

hashed_pw = bcrypt.generate_password_hash(password).decode('utf-8')

email = 'test@example.com'
email_verified = False

new_user = User(username=username, password=hashed_pw, email=email, email_verified=email_verified)

db.session.add(new_user)
db.session.commit()


exit()

サーバー再起動
```
### [テストユーザー作成時]emailにnull制約つけてて、emailなしで作ろうとしたらエラー

![スクリーンショット 2025-04-30 13.03.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628823/d72b18cd-cb59-4afd-98b6-71da3cd9e0b3.png)


### with app.app_context():コマンドがないよ

![スクリーンショット 2025-04-30 13.25.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628823/dec80535-e5a0-41c1-aca1-2b94d208cf73.png)

### email_verified = Falseを'False'にしてるとエラーに

![スクリーンショット 2025-04-30 13.40.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/628823/8c9a511b-0e4e-4efa-8325-2fee0ae720bc.png)


### コマンド入力ミスが治ってないよエラー

```ruby:
sqlalchemy.exc.PendingRollbackError: This Session's transaction has been rolled back due to a previous exception during flush. To begin a new transaction with this Session, first issue Session.rollback(). Original exception was: (builtins.TypeError) Not a boolean value: 'False'
[SQL: INSERT INTO user (username, email, password, email_verified) VALUES (?, ?, ?, ?)]
[parameters: [{'password': '$2b$12$1iytbHfDhQ0sxXoSZPO7re0QRh1qNociHMTInjHM0V82pq7C1acCC', 'email_verified': 'False', 'username': 'testuser', 'email': 'test@example.com'}]] (Background on this error at: https://sqlalche.me/e/20/7s2a)
```

### コマンド入力ミスが治ってないよエラ-解決策


```ruby:
db.session.rollback()
```

### テーブルないよエラー

```ruby:
cursor.execute(statement, parameters)
    ~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: user
[SQL: INSERT INTO user (username, email, password, email_verified) VALUES (?, ?, ?, ?)]
[parameters: ('testuser', 'test@example.com', '$2b$12$VoDrv77qG2PTF1NqV1adm.qtvh1r5bh2yy2Jb7gFE6/0visbAuPH2', 0)]
(Background on this error at: https://sqlalche.me/e/20/e3q8)
```