Python + Flask を使ったWebアプリ作成⑤(アカウント情報更新編)

下記記事の続きです。
今回は、アカウント情報の更新機能を追加します。









*環境

  • MacOS
  • Python 3.6.3
  • Flask 1.0.2


*参考



*アカウント更新画面を追加

アカウント情報を表示して、更新する画面を作成します。
<template/account.html>
{% extends "layout.html" %}
{% block content %}
    <div class="content-section">
        <!-- アカウント情報 -->
        <div class="media">
            <img class="rounded-circle account-img" src="{{ image_file }}">
            <div class="media-body">
                <h2 class="account-heading">{{ current_user.username }}</h2>
                <p class="text-secondary">{{ current_user.email }}</p>
            </div>
        </div>
        <!-- ファイル送信 -->
        <form method="POST" action="" enctype="multipart/form-data">
         <!-- バリデーションエラーが解消されたらエラーを消す -->
            {{ form.hidden_tag() }}
            <!-- 入力フォーム -->
            <fieldset class="form-group">
                <!-- fieldsetタグのタイトル -->
                <legend class="border-bottom md-4">Account Info</legend>
                <div class="form-group">
                 <!-- ユーザー名 -->
                    {{ form.username.label(class="form-control-label") }}

                    {% if form.username.errors %}
                    {{ form.username(class="form-control form-control-lg is-invalid") }}
                    <div class="invalid-feedback">
                        {% for error in form.username.errors %}
                        <span>{{ error }}</span>
                        {% endfor %}
                    </div>
                    {% else %}
                    {{ form.username(class="form-control form-control-lg") }}
                    {% endif %}
                </div>
                <!-- メールアドレス -->
                <div class="form-group">
                    {{ form.email.label(class="form-control-label") }}
                    {% if form.email.errors %}
                    {{ form.email(class="form-control form-control-lg is-invalid") }}
                    <div class="invalid-feedback">
                        {% for error in form.email.errors %}
                        <span>{{ error }}</span>
                        {% endfor %}
                    </div>
                    {% else %}
                    {{ form.email(class="form-control form-control-lg") }}
                    {% endif %}
                </div>
                <!-- 画像ファイル -->
                <div class="form-group">
                    {{ form.picture.label() }}
                    {{ form.picture(class="form-control-file") }}
                    {% if form.picture.errors %}
                        {% for error in form.picture.errors %}
                            <span class="text-danger">{{ error }}</span></br>
                        {% endfor %}
                    {% endif %}
                </div>
            </fieldset>
            <div class="form-group">
                {{ form.submit(class="btn btn-outline-info") }}
            </div>
        </form>
    </div>
{% endblock content %}


*アカウント更新処理を追加

更新するアカウント情報を保持するためのFormを追加します。
Flask logincurrent_userを使うとログインの有無を確認することができます。
既に登録済みのユーザー名だった場合はバリデーションエラーにする処理も、このFormクラスに実装します。
<forms.py>
from flask_wtf import FlaskForm
from flask_wtf.file import FileField, FileAllowed   <-- 追加
from flask_login import current_user                <-- 追加
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Length, Email, EqualTo, ValidationError
from flaskblog.models import User

...

class UpdateAccountForm(FlaskForm):
    username = StringField('Username',
                           validators=[DataRequired(), Length(min=2, max=20)])
    email = StringField('Email',
                        validators=[DataRequired(), Email()])
    picture = FileField('Update Profile Picture', validators=[FileAllowed(['jpg', 'png'])])
    submit = SubmitField('Update')

    def validate_username(self, username):
        if username.data != current_user.username:
            user = User.query.filter_by(username=username.data).first()
            if user:
                raise ValidationError('That username is taken. Please choose a different one.')

    def validate_email(self, email):
        if email.data != current_user.email:
            email = User.query.filter_by(email=email.data).first()
            if email:
                raise ValidationError('That email is taken. Please choose a different one.')

アカウント情報の表示、更新処理をするAPIを追加します。
画像処理ライブラリPIL(Pillow)thumbnailを使うことで画像を縮小することができます。
取得した画像はsecretsで乱数を作成し、安全なファイル名として保存します。
<routes.py>
import os              <-- 追加
import secrets         <-- 追加
from PIL import Image  <-- 追加

from flaskblog.forms import RegistrationForm, LoginForm, UpdateAccountForm  <-- 追加

...

def save_picture(form_picture):
    random_hex = secrets.token_hex(8)
    _, f_ext = os.path.splitext(form_picture.filename)
    picture_fn = random_hex + f_ext
    picture_path = os.path.join(app.root_path, 'static/profile_pics', picture_fn)
    form_picture.save(picture_path)

    output_size = (125, 125)
    i = Image.open(form_picture)
    i.thumbnail(output_size)
    i.save(picture_path)
    return picture_fn


@app.route('/account', methods=['GET', 'POST'])
@login_required
def account():
    form = UpdateAccountForm()
    if form.validate_on_submit():
        if form.picture.data:
            picture_file = save_picture(form.picture.data)
            current_user.image_file = picture_file
        current_user.username = form.username.data
        current_user.email = form.email.data
        db.session.commit()
        flash('your account has been updated!', 'success')
        return redirect((url_for('account')))

    elif request.method == 'GET':
        form.username.data = current_user.username
        form.email.data = current_user.email

    image_file = url_for('static', filename='profile_pics/' + current_user.image_file)
    return render_template('account.html', title='Account', image_file=image_file, form=form)



初期表示時の画像を追加

アカウント情報に表示する画像をflaskblog/static/profile_pics/default.jpgに格納しておきます。
(初期表示時はdefault.jpgが表示される実装にしています)

起動します。
$ python run.py


ヘッダーのaccountをクリックしてアカウント画面に遷移します。











更新内容を入力して Update ボタンを押下すると、
更新成功のメッセージが表示されて情報が反映されます。













*所感

画像を保存する際はトークンとして保存したほうが良いこと、サイズが大きな画像でも対応できるように縮小するようにしておくこと、といった画像の扱い方を知らなかったので勉強になりました。今後画像を扱う際は気をつけようと思います。


Previous
Next Post »

人気の投稿