実習1:Flaskでユーザ登録できるようにしよう
ここまでの例をもとに、ユーザログイン画面に「ユーザ登録」をするリンクを配置して、
ユーザ自身がアカウント登録をできるようにしてみましょう。
仕様は次のようにします。
ユーザ名がすでに存在していれば登録できない
Emailアドレスがすでに存在していれば登録できない
ユーザには2つのパスワード入力を行わせて、合致していないと登録できない
パスワードを省略することはできない
ユーザ登録が終わると、ログイン画面にリダイレクトする
インストール
既にCONDA環境になっているのであれば抜けておきましょう。
code: bash
$ conda deactivate
conda環境を作ります。
code: bash
$ conda create -y -n flask python=3.6
$ conda activate flask
次にパッケージをインストールしましょう。
pip でインストールする場合
code: bash
$ pip install flask flask-login flask-sqlalchemy flask-WTF flask-migrate
$ pip install python-dotenv flask-shell-ipython
conda でインストールする場合
code: bash
$ conda install flask flask-login flask-sqlalchemy flask-WTF flask-migrate
$ pip install python-dotenv
conda を使ってインストールすると wekzeug が新しいため flask_wtf でインポートエラーになります。次の修正を行うと回避できます。
code: site-packages/flask_wtf/recaptcha/validators.py 修正前
from werkzeug import url_encode
code: site-packages/flask_wtf/recaptcha/validators.py 修正後
from werkzeug.urls import url_encode
code: site-packages/flask_wtf/recaptcha/widgets.py 修正前
from werkzeug import url_encode
code: site-packages/flask_wtf/recaptcha/widgets.py 修正後
from werkzeug.urls import url_encode
ディレクトリを準備
アプリケーションディレクトリを作成します。
code: bash
$ mkdir authdemo
$ cd authdemo
$ mkdir app
$ mkdir app/templates
$ echo FLASK_DEBUG=1 > .flaskenv
構成ファイルを作成
app/config.py を作成しましょう。
code: Python
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
SECRET_KEY = os.environ.get('SECRET_KEY') or 'SECRET_KEY_HERE'
#
DATABASE_NAME = os.environ.get('DATABASE_NAME') or 'authdemo.db'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, DATABASE_NAME)
SQLALCHEMY_TRACK_MODIFICATIONS = False
SECRET_KEYを作成しましょう。
code: Python
python3 -c "import secrets; print(secrets.token_urlsafe(24))"
この出力を SECRET_KEY_HERE と置き換えます。
アプリケーションの初期化ファイルを作成
app/__init__.py を作成しましょう。
code: python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import flask_login
from app.config import Config
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
from app import views, models
モデルファイルを作成
app/models.py を作成しましょう。
code: python
from app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(16), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
def __repr__(self):
return f'<User {self.username}>'
def set_password(self, password):
self.password_hash = generate_password_hash(password)
return self.password_hash
def check_password(self, password):
return check_password_hash(self.password_hash, password)
@login_manager.user_loader
def load_user(id):
return User.query.get(int(id))
URLディスパッチファイルを作成
app/views.py を作成しましょう。
code: Python
from flask import flash, redirect, url_for, render_template
from flask_login import (
current_user, login_user, logout_user, login_required
)
from app import app
from app.models import User
from app.forms import LoginForm, RegistrationForm
@app.route('/')
def login():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user is None:
flash('Invalid username')
return redirect(url_for('login'))
user.set_password(form.password.data)
if not user.check_password(form.password.data):
flash('Invalid password')
return redirect(url_for('login'))
login_user(user)
return redirect(url_for('index'))
return render_template('login.html', title='Sign In', form=form)
@app.route('/logut')
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/index')
@login_required
def index():
return render_template('index.html', title='Sign In')
def signup():
if current_user.is_authenticated:
return redirect(url_for('index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('Congratulations, you are now a registered user!')
return redirect(url_for('login'))
return render_template('signup.html', title='Register', form=form)
テンプレートファイルを作成
ベーステンプレートとなる app/templates/base.html を作成します。
code: HTML
<html>
<head>
{% if title %}
<title>{{ title }} - Flask AuthDemo </title>
{% else %}
<title>Flask AuthDemo</title>
{% endif %}
</head>
<body>
<div>
Navigation:
<a href="{{ url_for('index') }}">Home</a>
{% if current_user.is_anonymous %}
<a href="{{ url_for('login') }}">Login</a>
{% else %}
<a href="{{ url_for('logout') }}">Logout</a>
{% endif %}
</div>
<hr>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul>
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
{% block content %}{% endblock %}
</body>
</html>
これを継承するチャイルドテンプレートファイルを作成します。
app/templates/index.html
code: HTML
{% extends "base.html" %}
{% block content %}
<h1>Hello {{ current_user.username }}!</h1>
{% endblock %}
app/templates/login.html を作成します。
code: Python
{% extends "base.html" %}
{% block content %}
<h1>Sign In</h1>
<form action="" method="post" novalidate>
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}
</p>
<p>
{{ form.password.label }}<br>
{{ form.password(size=32) }}
</p>
<p><a href="{{ url_for('signup') }}">Click to Sign up!</a></p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
app/templates/signup.html を作成します。
code: HTML
{% extends "base.html" %}
{% block content %}
<h1>Register</h1>
<form action="" method="post">
{{ form.hidden_tag() }}
<p>
{{ form.username.label }}<br>
{{ form.username(size=32) }}<br>
{% for error in form.username.errors %}
<span style="color: red;">error }}</span>
{% endfor %}
</p>
<p>
{{ form.email.label }}<br>
{{ form.email(size=64) }}<br>
{% for error in form.email.errors %}
<span style="color: red;">error }}</span>
{% endfor %}
</p>
<p>
{{ form.password1.label }}<br>
{{ form.password1(size=32) }}<br>
{% for error in form.password1.errors %}
<span style="color: red;">error }}</span>
{% endfor %}
</p>
<p>
{{ form.password2.label }}<br>
{{ form.password2(size=32) }}<br>
{% for error in form.password2.errors %}
<span style="color: red;">error }}</span>
{% endfor %}
</p>
<p>{{ form.submit() }}</p>
</form>
{% endblock %}
フォームを作成
app/forms.pyを作成します。
code: Python
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField, SubmitField
from wtforms.validators import (
ValidationError, DataRequired, Email, EqualTo
)
from app.models import User
class LoginForm(FlaskForm):
submit = SubmitField('Sign In')
class RegistrationForm(FlaskForm):
password2 = PasswordField('Repeat Password',
validators=[DataRequired(),
EqualTo('password')])
submit = SubmitField('Register')
def validate_username(self, username):
user = User.query.filter_by(username=username.data).first()
if user is not None:
raise ValidationError('Please use a different username.')
def validate_email(self, email):
user = User.query.filter_by(email=email.data).first()
if user is not None:
raise ValidationError('Please use a different email address.')
データベースを作成
code: bash
$ flask db init
$ flask db migrate -m "users table"
$ flask db upgrade
アプリケーションを実行してみましょう。
code: bash
$ flask run
flask-debugtoolbar を使ってみよう
開発時の効率がよくなるツールバーを貼り付けてくれる便利なモジュールです。
code: bash
$ pip install flask-debugtoolbar
app/__init__.py を次のように変更します。
code: Python
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
import flask_login
from flask_debugtoolbar import DebugToolbarExtension # <-- HERE
from app.config import Config
app = Flask(__name__)
app.config.from_object(Config)
db = SQLAlchemy(app)
migrate = Migrate(app, db)
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
toolbar = DebugToolbarExtension(app) # <-- HERE
from app import views, models
あとは普通にFlaskアプリケーションを起動するだけです。
FLAS_DEBUGが1に設定されているときだけツールバーが表示されます。
https://gyazo.com/8a73081c548040a02317ac429befe1f1
https://gyazo.com/3ebd1077a76ecf4199c727ca7c8e621d
flask-skeleton を使ってみよう
flask-skeleton というパッケージがあります。これを使うとFlaskアプリケーションのセットアップが簡単になります。
code: bash
$ pip install flask-skeleton
ディレクトリを移動して、新しくFlaskのアプリケーションを作ってみましょう。
code: bash
$ cd ..
$ create-flask-app hello
:
Continue with these settings? y/N: y $ ls -CF hello
README.md config.py wsgi.py
__pycache__/ requirements.txt
app/ tests/
$ cd hello
$ create-flask-app --help
Usage: create-flask-app OPTIONS APP_NAME Create a flask app skeleton.
Options:
-d, --dir PATH Where to create your app. Defaults to the current directory.
-e, --env Create a virtual environment.
-g, --git Initialize a git repository.
-V, --version Show the version and exit.
-h, --help Show this message and exit.
最低限の設定やコードが書かれているので、このままでもアプリケーションは起動します。
code: bash
$ flask run
https://gyazo.com/a26c1d3e04648b6c9ed4dded73cebd34
”Login" のリンクをクリックするとログイン画面になります。
https://gyazo.com/f69c7f0b7942b94b3bae1c03fb7c965f
”Register"をクリックするとユーザ登録画面になります。
https://gyazo.com/e847ad5eccba0fd46c8688122e4dddc0
実習で行ってきたことのほとんどがコマンドひとつで済んでしまいます。
あとは、これをベースに開発をすすめて行くことになります。