はじめに
今回は、ログイン機能を実装します。
以下の記事の続きとなります。
【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となる。
http://127.0.0.1:5000/signup
へアクセスし、ユーザーとパスワード登録を行う。
ユーザー名:testuser
パスワード:1234