LoginSignup
0
0

More than 1 year has passed since last update.

【python】勉強メモ 速習Flask⑥

Posted at

はじめに

今回は、ログイン機能を実装します。

以下の記事の続きとなります。
【python】勉強メモ 速習Flask①
【python】勉強メモ 速習Flask②
【python】勉強メモ 速習Flask③
【python】勉強メモ 速習Flask④
【python】勉強メモ 速習Flask⑤

前回までの環境

ディレクトリ構造

pydir/
 ┣myproject/ ->仮想環境有効化のコマンドを実行&flask runコマンドもここで実行
   ┣venv/
   ┣instance/
    ┣blog.db
   ┣hello.py
   ┣app.py
   ┣templates/
    ┣hello.html
    ┣index.html ->編集有り
    ┣base.html
    ┣create.html
    ┣singup.html ->今回作成
    ┣login.html ->今回作成

コード

前回まで作成した「新規登録」「更新」「削除」を行うコードを掲載しておきます。

app.py
from flask import Flask
from flask import render_template
from flask import request
from flask import redirect
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"
db = SQLAlchemy(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=True)
    body = db.Column(db.String(300), nullable=True)
    created_at = db.Column(db.DateTime, nullable=False,default=datetime.now(pytz.timezone('Asia/Tokyo')))

# GETとPOSTを受け取れる様にする。
@app.route("/", methods=['GET', 'POST'])
def index():
    # ページにアクセスしたら(HTTP GETでアクセスしたら)
    if request.method == 'GET':
        # DBに登録されたデーターを全てPostで取得する。取得したデーターは、postsに、リスト形式で格納される。
        posts = Post.query.all()
        # index.htmlに表示するため、posts=postsで、取得したデーターを渡す。
        return render_template('index.html', posts=posts)


@app.route("/create", methods=['GET', 'POST']) 
def create():
    if request.method == 'POST':
        title = request.form.get('title')
        body = request.form.get('body')

        post = Post(title=title, body=body)

        db.session.add(post)
        # 変更を保存
        db.session.commit()

        return redirect('/')
    else:
        return render_template('create.html')

# update処理を作成
@app.route("/<int:id>/update", methods=['GET', 'POST']) 
def update(id):
    post = Post.query.get(id)
    
    if request.method == 'GET':
        return render_template('update.html', post=post)
    
    # 更新ボタンが押されると、POSTが送信されるめ、eles移行が実行される
    else:
        post.title = request.form.get('title') # 書き換え
        post.body = request.form.get('body') # 書き換え
        
        # 不要なのでコメントアウト
        # post = Post(title=title, body=body)
        
        # db.session.add(post) 更新なので、addする必要はないため削除
        # commitだけすれば良い
        db.session.commit()

        return redirect('/')
        # return render_template('create.html')

# delete処理作成
# deleteは、POSTが流れることはないため、GETのみにする
@app.route("/<int:id>/delete", methods=['GET'])

def delete(id):
    post = Post.query.get(id)
    
    # delete処理
    db.session.delete(post)
    db.session.commit()
    
    return redirect('/')    
index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>

<a href="/create" role="button">新規作成画面</a>
{% for post in posts %}
<article>
    <h2>{{ post.title }}</h2>
    <a href="/{{post.id}}/update" role="button">編集</a>
    <!-- 削除ボタン作成 -->
    <a href="/{{post.id}}/delete" role="button">削除</a>
    <p>作成日時: {{ post.created_at }}</p>
    <p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}
base.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <!-- ここに、HTMLの共通化が埋め込まれる -->
    {% block content %}
    {% endblock %}
</body>
</html>
create.html
{% extends "base.html" %}
{% block content %}
<h1>新規登録</h1>
<form method="POST">
    <label for="title">タイトル</label>
    <input type="text" name="title">
    <label for="body">内容</label>
    <input type="text" name="body">
    <input type="submit" value="新規登録">
</form>
{% endblock %}
update.html
{% extends "base.html" %}
{% block content %}
<h1>編集</h1>
<form method="POST">
    <label for="title">タイトル</label>
    <!-- value="{{ post.title }}" は、renderから情報を取得してくる-->
    <input type="text" name="title" value="{{ post.title }}">
    <label for="body">内容</label>
    <!-- value="{{ post.body }}" も、renderから情報を取得してくる-->
    <input type="text" name="body" value="{{ post.body }}">
    <input type="submit" value="更新">
</form>
{% endblock %}

事前準備

pip install flask-login

app.pyを以下の様に編集

app.py
from flask import Flask
from flask import render_template
from flask import request
from flask import redirect
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin, LoginManager # 今回追加
import os # 今回追加 セッションを暗号化するために必要
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"

#今回追加
#セッション状態を暗号化するための設定
# os.urandom(24)でランダムな値を生成し、SECRET_KEYという環境変数にセットする
app.config['SECRET_KEY'] = os.urandom(24)
db = SQLAlchemy(app)

# 今回追加
# LoginManagerクラスのインスタンス化
login_manager = LoginManager()

# 今回追加
# loginManagerと今回作成しているappの紐づけを行う
login_manager.init_app(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=True)
    body = db.Column(db.String(300), nullable=True)
    created_at = db.Column(db.DateTime, nullable=False,default=datetime.now(pytz.timezone('Asia/Tokyo')))

# 今回追加
# UserMixinを継承することで、flaskのloginで必要な機能を持たせたClassを作成できる
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username= db.Column(db.String(30), unique=True)
    password = db.Column(db.String(12))


@app.route("/", methods=['GET', 'POST'])
def hello():
    
    if request.method == 'GET':
        posts = Post.query.all()
        return render_template('index.html', posts=posts)


@app.route("/create", methods=['GET', 'POST']) 
def create():
    if request.method == 'POST':
        title = request.form.get('title')
        body = request.form.get('body')

        post = Post(title=title, body=body)

        db.session.add(post)
        db.session.commit()

        return redirect('/')
    else:
        return render_template('create.html')

@app.route("/<int:id>/update", methods=['GET', 'POST']) 
def update(id):
    post = Post.query.get(id)
    
    if request.method == 'GET':
        return render_template('update.html', post=post)
    
    else:
        post.title = request.form.get('title')
        post.body = request.form.get('body')
        
        db.session.commit()

        return redirect('/')

@app.route("/<int:id>/delete", methods=['GET'])

def delete(id):
    post = Post.query.get(id)
    
    # delete処理
    db.session.delete(post)
    db.session.commit()
    
    return redirect('/')

対話モード
userというテーブルを作成したため、再度DBを作成(例のやつ)DB作成が完了したら
以下のファイルを作成

ユーザー登録を行う、signup.htmlの作成

{% extends "base.html" %}
{% block content %}
<h1>ユーザー登録</h1>
<form method="POST">
    <label for="">ユーザー名</label>
    <input type="text" name="username">
    <label for="password">パスワード</label>
    <input type="password" name="password">
    <input type="submit" value="新規登録">
</form>
{% endblock %}

login.htmlの作成

login.html
{% extends "base.html" %}
{% block content %}
<h1>ログイン画面</h1>
<form method="POST">
    <label for="title">ユーザー名</label>
    <input type="text" name="username">
    <label for="body">パスワード</label>
    <input type="password" name="password">
    <input type="submit" value="ログイン">
</form>
{% endblock %}

app.pyにsignup、login、logout機能実装

app.py
from flask import Flask
from flask import render_template
from flask import request
from flask import redirect
from flask_sqlalchemy import SQLAlchemy
# 今回追加
from flask_login import UserMixin, LoginManager, login_user, logout_user, login_required
# 今回追加 pip install必要
from werkzeug.security import generate_password_hash, check_password_hash
# 今回追加 セッションを暗号化するために必要
import os
from datetime import datetime
import pytz

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///blog.db"

#今回追加
#セッション状態を暗号化するための設定
# os.urandom(24)でランダムな値を生成し、SECRET_KEYという環境変数にセットする
app.config['SECRET_KEY'] = os.urandom(24)
db = SQLAlchemy(app)

# 今回追加
# LoginManagerクラスのインスタンス化
login_manager = LoginManager()

# 今回追加
# loginManagerと今回作成しているappの紐づけを行う
login_manager.init_app(app)

class Post(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title= db.Column(db.String(50), nullable=True)
    body = db.Column(db.String(300), nullable=True)
    created_at = db.Column(db.DateTime, nullable=False,default=datetime.now(pytz.timezone('Asia/Tokyo')))

# 今回追加
# UserMixinを継承することで、flaskのloginで必要な機能を持たせたClassを作成できる
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username= db.Column(db.String(30), unique=True)
    password = db.Column(db.String(12))

# 今回追加
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(init(user_id))


@app.route("/", methods=['GET', 'POST'])
# 以下のデコレーターは、ログインしているユーザーだけに適用される
@login_required # 今回追加
def index():
    if request.method == 'GET':
        posts = Post.query.all()
        return render_template('index.html', posts=posts)


# 今回作成
@app.route("/signup", methods=['GET', 'POST']) 
def signup():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')

        # Userクラスに引数の情報を入れ、インスタンス化を行う
        user = User(username=username, password=generate_password_hash(password, method='sha256'))
        # インスタンス化した user をDBに追加
        db.session.add(user)
        # 変更を保存
        db.session.commit()

        # signup後は、ログインページへリダイレクト
        return redirect('/login')
    else:
        return render_template('signup.html')


# 今回作成
@app.route("/login", methods=['GET', 'POST']) 
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')

        # Userの中から、ユーザーを探す
        user = User.query.filter_by(username=username).first()
        
        # ユーザーが入力したパスワードが正しいか、ハッシュ値で確認を行う
        if check_password_hash(user.password, password):
            # パスワードが合っていたらログイン
            login_user(user)
            # ログインできたら、トップページにリダイレクト
            return redirect('/')

    else:
        return render_template('login.html')

# 今回作成
@app.route("/logout")

@login_required
def logout():
    logout_user()
    return redirect('login')


@app.route("/create", methods=['GET', 'POST']) 
@login_required
def create():
    if request.method == 'POST':
        title = request.form.get('title')
        body = request.form.get('body')

        post = Post(title=title, body=body)

        db.session.add(post)
        # 変更を保存
        db.session.commit()

        return redirect('/')
    else:
        return render_template('create.html')

# update処理を作成
@app.route("/<int:id>/update", methods=['GET', 'POST'])
@login_required
def update(id):
    post = Post.query.get(id)
    
    if request.method == 'GET':
        return render_template('update.html', post=post)
    
    # 更新ボタンが押されると、POSTが送信されるめ、eles移行が実行される
    else:
        post.title = request.form.get('title') # 書き換え
        post.body = request.form.get('body') # 書き換え
        
        # 不要なのでコメントアウト
        # post = Post(title=title, body=body)
        
        # db.session.add(post) 更新なので、addする必要はないため削除
        # commitだけすれば良い
        db.session.commit()

        return redirect('/')

# delete処理作成
# deleteは、POSTが流れることはないため、GETのみにする
@app.route("/<int:id>/delete", methods=['GET'])
@login_required
def delete(id):
    post = Post.query.get(id)
    
    # delete処理
    db.session.delete(post)
    db.session.commit()
    
    return redirect('/')

signup.html
{% extends "base.html" %}
{% block content %}
<h1>ユーザー登録</h1>
<form method="POST">
    <label for="">ユーザー名</label>
    <input type="text" name="username">
    <label for="password">パスワード</label>
    <input type="password" name="password">
    <input type="submit" value="新規登録">
</form>
{% endblock %}
login.html
{% extends "base.html" %}
{% block content %}
<h1>ログイン画面</h1>
<form method="POST">
    <label for="title">ユーザー名</label>
    <input type="text" name="username">
    <label for="body">パスワード</label>
    <input type="password" name="password">
    <input type="submit" value="ログイン">
</form>
{% endblock %}
index.html
{% extends "base.html" %}
{% block content %}
<h1>ブログアプリケーション</h1>

<a href="/create" role="button">新規作成画面</a>
<a href="/logout" role="button">ログアウト</a>
{% for post in posts %}
<article>
    <h2>{{ post.title }}</h2>
    <a href="/{{post.id}}/update" role="button">編集</a>
    <!-- 削除ボタン作成 -->
    <a href="/{{post.id}}/delete" role="button">削除</a>
    <p>作成日時: {{ post.created_at }}</p>
    <p>{{ post.body }}</p>
</article>
{% endfor %}
{% endblock %}

127.0.0.1:5000にアクセスすると、@login_requiredでユーザー認証を行いログインしていない
ため、以下の様にUnauthorizedとなる。
Flask-1.PNG

http://127.0.0.1:5000/signupへアクセスし、ユーザーとパスワード登録を行う。
ユーザー名:testuser
パスワード:1234
Flask-2.PNG

新規登録を行うと、ログイン画面に戻るので、登録したユーザーとパスワードを入力する。
Flask-3.PNG

ログインすると、以下の様にトップページが表示される。
Flask-4.PNG

0
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
0
0