4. Flaskを使いこなす1¶
Flaskrにユーザー管理画面と認証処理を追加しながら進めていきます。
Flask公式のチュートリアルではユーザーログイン処理がありますが、 config.pyに記述したユーザーのみになっています。
今回はユーザーを追加・編集・削除できるような画面を作成し、 そのユーザーを使ってログインできるようにしましょう。
4.1 ユーザーログイン処理を追加する1¶
samples/04/01を参考にして下さい
ユーザークラスの追加¶
models.pyにUserクラスを追加します。
ここでは、emailとpasswordでログインするユーザーを作ります。
flaskr/models.py
from sqlalchemy.orm import synonym
from werkzeug import check_password_hash, generate_password_hash
from flaskr import db
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), default='', nullable=False)
email = db.Column(db.String(100), unique=True, nullable=False)
_password = db.Column('password', db.String(100), nullable=False)
def _get_password(self):
return self._password
def _set_password(self, password):
if password:
password = password.strip()
self._password = generate_password_hash(password)
password_descriptor = property(_get_password, _set_password)
password = synonym('_password', descriptor=password_descriptor)
def check_password(self, password):
password = password.strip()
if not password:
return False
return check_password_hash(self.password, password)
@classmethod
def authenticate(cls, query, email, password):
user = query(cls).filter(cls.email==email).first()
if user is None:
return None, False
return user, user.check_password(password)
def __repr__(self):
return u'<User id={self.id} email={self.email!r}>'.format(
self=self)
class Entry(db.Model):
__tablename__ = 'entries'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.Text)
text = db.Column(db.Text)
def __repr__(self):
return '<Entry id={id} title={title!r}>'.format(
id=self.id, title=self.title)
def init():
db.create_all()
データベースの作成¶
つくったらデータベースを初期しておきましょう。
しかし、いちいちpythonインタプリタから初期化するのは面倒なので、 コマンドラインから作成できるようにしておきましょう。
まずは、Flaks-Scriptプラグインをインストールします。:
echo Flask-Script >> requirements.txt
pip install Flask-Script
次にmanage.pyを変更します。
manage.py
from __future__ import print_function
from flask.ext.script import Manager
from flaskr import app, db
manager = Manager(app)
@manager.command
def init_db():
db.create_all()
if __name__ == '__main__':
manager.run()
作成したらデータベースを作成しなおしましょう。:
rm flaskr/flaskr.db
python manage.py init_db
ユーザー用のビューを追加¶
ユーザーを管理するためのページをベースを作りましょう。
views.pyに追加していきます。
from flask import request, redirect, url_for, render_template, flash
from flaskr import app, db
from flaskr.models import Entry
@app.route('/')
def show_entries():
entries = Entry.query.order_by(Entry.id.desc()).all()
return render_template('show_entries.html', entries=entries)
@app.route('/add', methods=['POST'])
def add_entry():
entry = Entry(
title=request.form['title'],
text=request.form['text']
)
db.session.add(entry)
db.session.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))
@app.route('/users/')
def user_list():
return 'list users'
@app.route('/users/<int:user_id>/')
def user_detail(user_id):
return 'detail user ' + str(user_id)
@app.route('/users/<int:user_id>/edit/', methods=['GET', 'POST'])
def user_edit(user_id):
return 'edit user ' + str(user_id)
@app.route('/users/create/', methods=['GET', 'POST'])
def user_create():
return 'create a new user'
@app.route('/users/<int:user_id>/delete/', methods=['DELETE'])
def user_delete(user_id):
return NotImplementedError('DELETE')
- routeのパラメータ
- int: 整数
- float: 浮動小数点
- path: 文字列(スラッシュも受け取る)
- HTTP method
- GET
- HEAD
- POST
- PUT
- DELETE
- OPTIONS
4.2 ユーザーログイン処理を追加する2¶
samples/04/02を参考にして下さい
ユーザー管理画面を作っていきます。
ユーザー作成 /users/create の追加¶
flaskr/views.py:
from flaskr.models import Entry, User
...
@app.route('/users/create/', methods=['GET', 'POST'])
def user_create():
if request.method == 'POST':
user = User(name=request.form['name'],
email=request.form['email'],
password=request.form['password'])
db.session.add(user)
db.session.commit()
return redirect(url_for('user_list'))
return render_template('user/edit.html')
flaskr/templates/user/edit.html
{% extends "layout.html" %}
{% block body %}
<h2>{{ 'Edit User' if user else 'Add User'}}</h2>
<form action="" method="post">
<dl>
<dt>Name:
<dd><input type=text size=20 name=name value="{{ user.name if user }}">
<dt>Email:
<dd><input type=text size=20 name=email value="{{ user.email if user }}">
<dt>password:
<dd><input type=password size=20 name=password value="">
<dd><input type=submit value={{ 'save' if user else 'create' }}>
</dl>
</form>
{% endblock %}
ユーザー一覧 /users/ の追加¶
flaskr/views.py:
@app.route('/users/')
def user_list():
users = User.query.all()
return render_template('user/list.html', users=users)
flaskr/templates/user/list.html
{% extends "layout.html" %}
{% block body %}
<h2>Users</h2>
<ul>
{% for user in users %}
<li><a href="{{ url_for('user_detail', user_id=user.id) }}">{{ user.name }}</a></li>
{% else %}
<li><em>Unbelievable. No users here so far</em></li>
{% endfor %}
</ul>
<a href="{{ url_for('user_create') }}">create user</a>
{% endblock body %}
ユーザー一覧 /users/<int:user_id>/ の追加¶
flaskr/views.py:
@app.route('/users/<int:user_id>/')
def user_detail(user_id):
user = User.query.get(user_id)
return render_template('user/detail.html', user=user)
flaskr/templates/user/detail.html
{% extends "layout.html" %}
{% block body %}
<h2>{{ user.name }}</h2>
<div>
<div>{{ user.email }}</div>
</div>
<div>
<ul>
<li><a href="{{ url_for('user_edit', user_id=user.id) }}">edit</a></li>
<li><a class="user-delete-link" href="#" data-delete-url="{{ url_for('user_delete', user_id=user.id) }}">delete</a></li>
</ul>
</div>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script>
$(function() {
$(".user-delete-link").on("click", function() {
var delete_url = $(this).attr('data-delete-url');
$.ajax({
url: delete_url,
type: 'DELETE',
success: function(response) {
if (response.status == 'OK') {
window.location = '{{ url_for('user_list') }}';
} else {
alert('Delete failed.')
}
}
});
return false;
});
});
</script>
{% endblock body %}
ユーザー一覧 /users/<int:user_id>/edit/ の追加¶
flaskr/views.py:
from flask import request, redirect, url_for, render_template, flash, abort
...
@app.route('/users/<int:user_id>/edit/', methods=['GET', 'POST'])
def user_edit(user_id):
user = User.query.get(user_id)
if user is None:
abort(404)
if request.method == 'POST':
user.name=request.form['name']
user.email=request.form['email']
user.password=request.form['password']
db.session.add(user)
db.session.commit()
return redirect(url_for('user_detail', user_id=user_id))
return render_template('user/edit.html', user=user)
ユーザー一覧 /users/<int:user_id>/delete/ の追加¶
flaskr/views.py:
from flask import request, redirect, url_for, render_template, flash, abort, \
jsonify
...
@app.route('/users/<int:user_id>/delete/', methods=['DELETE'])
def user_delete(user_id):
user = User.query.get(user_id)
if user is None:
response = jsonify({'status': 'Not Found'})
response.status_code = 404
return response
db.session.delete(user)
db.session.commit()
return jsonify({'status': 'OK'})
実行してみましょう¶
python manage.py runserver
4.3 ユーザーログイン処理を追加する3¶
samples/04/03を参考にして下さい
さらにモクモクと作っていきましょう。
ログイン画面の追加¶
flaskr/views.py:
from flask import request, redirect, url_for, render_template, flash, abort, \
jsonify, session
...
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user, authenticated = User.authenticate(db.session.query,
request.form['email'], request.form['password'])
if authenticated:
session['user_id'] = user.id
flash('You were logged in')
return redirect(url_for('show_entries'))
else:
flash('Invalid email or password')
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('user_id', None)
flash('You were logged out')
return redirect(url_for('show_entries'))
flaskr/templates/login.html
{% extends "layout.html" %}
{% block body %}
<h2>Login</h2>
<form action="{{ url_for('login') }}" method=post>
<dl>
<dt>Email:
<dd><input type=text name=email>
<dt>Password:
<dd><input type=password name=password>
<dd><input type=submit value=Login>
</dl>
</form>
{% endblock %}
flaskr/templates/layout.html
<!doctype html>
<title>Flaskr</title>
<link rel=stylesheet type=text/css href="{{ url_for('static', filename='style.css') }}">
<div class=page>
<h1>Flaskr</h1>
{% if session.user_id %}
<a href="{{ url_for('logout') }}">logout</a>
<a href="{{ url_for('show_entries') }}">entries</a>
<a href="{{ url_for('user_list') }}">users</a>
{% else %}
<a href="{{ url_for('login') }}">login</a>
<a href="{{ url_for('show_entries') }}">entries</a>
{% endif %}
{% for message in get_flashed_messages() %}
<div class=flash>{{ message }}</div>
{% endfor %}
{% block body %}{% endblock %}
</div>
ログインしないと記事を追加できないように変更します。
flaskr/templates/show_entries.html
{% extends "layout.html" %}
{% block body %}
{% if session.user_id %}
<form action="{{ url_for('add_entry') }}" method=post class=add-entry>
<dl>
<dt>Title:
<dd><input type=text size=20 name=title>
<dt>Text:
<dd><textarea name=text rows=5 cols=20></textarea>
<dd><input type=submit value=Share>
</dl>
</form>
{% endif %}
<ul class=entries>
{% for entry in entries %}
<li><h2>{{ entry.title }}</h2>{{ entry.text|safe }}
{% else %}
<li><em>Unbelievable. No entries here so far</em>
{% endfor %}
</ul>
{% endblock %}
実行してみましょう¶
python manage.py runserver
4.4 ユーザー管理画面は認証していない人には見せたくない¶
事前にユーザーを作ってからやらないと・・・ログインできなくなりますw
ユーザー管理画面など一般ユーザーには見せたくないページがあります。
先ほど作成したユーザー管理画面を、ログインしないと見れないように変更します。
flaskr/views.py:
from functools import wraps
from flask import request, redirect, url_for, render_template, flash, abort, \
jsonify, session, g
...
def login_required(f):
@wraps(f)
def decorated_view(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.path))
return f(*args, **kwargs)
return decorated_view
@app.before_request
def load_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = User.query.get(session['user_id'])
...
@app.route('/users/')
@login_required
def user_list():
...
@app.route('/users/<int:user_id>/')
@login_required
def user_detail(user_id):
...
@app.route('/users/<int:user_id>/edit/', methods=['GET', 'POST'])
@login_required
def user_edit(user_id):
...
@app.route('/users/create/', methods=['GET', 'POST'])
@login_required
def user_create():
...
@app.route('/users/<int:user_id>/delete/', methods=['DELETE'])
def user_delete(user_id):
- before_request
- routeで追加したendpointの前に呼ばれる。
- beforeがあるということは、afterもある。
- session[‘user_id’]に格納したuser.idからUserを取得
- gというFlaskインスタンス内のグローバルのような変数にuserを追加
- login_required
- デコレータを定義
- g.userを確認してログインしているかをチェックしている。
書き換えたviews.pyはこちら
from functools import wraps
from flask import request, redirect, url_for, render_template, flash, abort, \
jsonify, session, g
from flaskr import app, db
from flaskr.models import Entry, User
def login_required(f):
@wraps(f)
def decorated_view(*args, **kwargs):
if g.user is None:
return redirect(url_for('login', next=request.path))
return f(*args, **kwargs)
return decorated_view
@app.before_request
def load_user():
user_id = session.get('user_id')
if user_id is None:
g.user = None
else:
g.user = User.query.get(session['user_id'])
@app.route('/')
def show_entries():
entries = Entry.query.order_by(Entry.id.desc()).all()
return render_template('show_entries.html', entries=entries)
@app.route('/add', methods=['POST'])
def add_entry():
entry = Entry(
title=request.form['title'],
text=request.form['text']
)
db.session.add(entry)
db.session.commit()
flash('New entry was successfully posted')
return redirect(url_for('show_entries'))
@app.route('/users/')
@login_required
def user_list():
users = User.query.all()
return render_template('user/list.html', users=users)
@app.route('/users/<int:user_id>/')
@login_required
def user_detail(user_id):
user = User.query.get(user_id)
return render_template('user/detail.html', user=user)
@app.route('/users/<int:user_id>/edit/', methods=['GET', 'POST'])
@login_required
def user_edit(user_id):
user = User.query.get(user_id)
if user is None:
abort(404)
if request.method == 'POST':
user.name=request.form['name']
user.email=request.form['email']
if request.form['password']:
user.password=request.form['password']
#db.session.add(user)
db.session.commit()
return redirect(url_for('user_detail', user_id=user_id))
return render_template('user/edit.html', user=user)
@app.route('/users/create/', methods=['GET', 'POST'])
@login_required
def user_create():
if request.method == 'POST':
user = User(name=request.form['name'],
email=request.form['email'],
password=request.form['password'])
db.session.add(user)
db.session.commit()
return redirect(url_for('user_list'))
return render_template('user/edit.html')
@app.route('/users/<int:user_id>/delete/', methods=['DELETE'])
def user_delete(user_id):
user = User.query.get(user_id)
if user is None:
response = jsonify({'status': 'Not Found'})
response.status_code = 404
return response
db.session.delete(user)
db.session.commit()
return jsonify({'status': 'OK'})
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
user, authenticated = User.authenticate(db.session.query,
request.form['email'], request.form['password'])
if authenticated:
session['user_id'] = user.id
flash('You were logged in')
return redirect(url_for('show_entries'))
else:
flash('Invalid email or password')
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('user_id', None)
flash('You were logged out')
return redirect(url_for('show_entries'))