- 画像ファイルアップロード機能の使い方を理解する
- 画像と紐づくデータベース操作を理解する
- 画像ファイルの表示方法と削除方法を理解する
上記をまとめ、具体的なコードや画像ファイルの使い方を重点的に解説します。
画像表示アプリのディレクトリ構成
本記事では、以下のディレクトリ構成で画像表示アプリを開発します。
flask-image
├── .env
├── apps
│ ├── app.py
│ ├── config.py
│ ├── static
│ │ └── bootstrap.min.css
│ ├── image
│ │ ├── __init__.py
│ │ ├── forms.py
│ │ ├── models.py
│ │ ├── static/css
│ │ │ └── style.css
│ │ ├── templates
│ │ │ └── image
│ │ │ ├── base.html
│ │ │ ├── index.html
│ │ │ └── upload.html
│ │ └── views.py
| └── images
├── local.sqlite
└── migrations
- flask-image:開発用ディレクトリ名
- apps:格納されたアプリを管理するディレクトリ
- auth:認証アプリの関連ファイルを格納したディレクトリ
- image:画像表示アプリの関連ファイルを格納したディレクトリ
- images:画像ファイルを格納するディレクトリ
- static/css:静的ファイルのディレクトリ
- templates:HTMLファイルを格納したディレクトリ
各ファイルについては、後述の中で解説していきます。
画像表示アプリの完成画面は、以下になります。
Flaskを利用するためのPython環境構築
Pythonで開発を実施する際、開発プロジェクトごとに専用の実行環境を作成し切り替えることができます。
また、開発用として一時的に作成する実行環境を「仮想環境」と呼びます。
ローカル環境で開発を進めるにあたり、venvモジュールを利用し仮想環境を構築します。
venvは、Pythonに標準搭載された仮想環境用モジュールです。
venvを利用することで、プロジェクトごとに分離したPython実行環境を構築できるため、各実行環境でそれぞれのパッケージを管理できます。
Pythonによる仮想環境の構築方法が知りたい人は、「【Python】ダウンロードとインストール方法から開発環境構築まで解説!」を一読ください。
Flaskのインストール
本記事では、WindowsPCによる任意のディレクトリにてFlaskをインストールしています。
インストール自体は簡単で、以下のコマンドを実行しましょう。
pip install flask
上記のコマンドを実行すると、インストールが完了します。
FlaskによるWebアプリ開発の事前準備
任意で作成したディレクトリにcdコマンドで移動後、仮想環境を作成します。
py -m venv venv
次に、仮想環境をアクティベート(有効化)します。
./venv/Scripts/Activate
pip listを利用するとパッケージの状況が確認できます。
インストールしたのちにpip listでパッケージ状況を確認すると以下のように表示されます。
(venv) PS C:\Users\sugir\Documents\flask-image> pip list
Package Version
------------ -------
blinker 1.8.2
click 8.1.7
colorama 0.4.6
Flask 3.0.3
itsdangerous 2.2.0
Jinja2 3.1.4
MarkupSafe 2.1.5
pip 24.0
Werkzeug 3.0.4
さらに、本プログラムでは環境変数の設定ファイルとして.envファイルを利用します。
.envファイルを利用すると、アプリ単位で環境変数を設定できます。
以下のコマンドでpython-dotenvをインストールします。
pip install python-dotenv
pip listで確認します。
(venv) PS C:\Users\sugir\Documents\flask-image> pip list
Package Version
------------- -------
blinker 1.8.2
click 8.1.7
colorama 0.4.6
Flask 3.0.3
itsdangerous 2.2.0
Jinja2 3.1.4
MarkupSafe 2.1.5
pip 24.0
python-dotenv 1.0.1
Werkzeug 3.0.4
flaskのインストールが完了したので画像一覧表示アプリを開発する準備が整いました。
Blueprintによるアプリ登録
始めに、以下のディレクトリとファイルを作成します。
flask-image
├── .env
└── apps
├── app.py
└── image
└── views.py
Blueprintによるアプリ登録を実施します。
# Flaskのインポート
from flask import Flask
def create_app():
# Flaskのインスタンス作成
app = Flask(__name__)
# imageパッケージからviewsをインポート
from apps.image import views as image_views
# register_blueprintを利用してviewsのimageをアプリへ登録
app.register_blueprint(image_views.image, url_prefix="/image")
return app
- flaskのインポート
- flaskのインスタンス作成
- imageパッケージからviewsをインポート
- register_blueprintを利用してviewsのimageをアプリへ登録
from flask import Flask
flaskをインポートし、Flaskフレームワークの機能を利用できるようにします。
def create_app():
app = Flask(__name__)
from apps.image import views as image_views
app.register_blueprint(image_views.image, url_prefix="/image")
return app
create_app関数を作成し、appへFlaskインスタンスを作成します。
本プログラムで開発するimageパッケージからviews.pyをインポートします。
Blueprint機能によるregister_blueprint関数を利用し、imageアプリを登録します。
また、url_prefixに/imageを指定し、views.pyのエンドポイント全てのURLがimageから始まるよう設定します。
次に、views.pyにてBlueprintによるアプリを作成します。
from flask import Blueprint
image = Blueprint(
"image",
__name__,
template_folder = "templates",
static_folder = "static",
)
@image.route("/")
def index():
return 'Hello World!'
- Blueprintのインポート
- Blueprintにてimageを作成
- デコレータ関数にてURLマッピング
- index関数で任意の文字列を出力
from flask import Blueprint
作成するimageアプリのviews.pyでは、Blueprintをインポートします。
image = Blueprint(
"image",
__name__,
template_folder = "templates",
static_folder = "static",
)
imageアプリをBlueprintオブジェクトで生成します。
@image.route("/")
def index():
return 'Hello World!'
ひとまず、デコレータ関数にてURLマッピングしており、任意の文字列を出力しておきます。
また、.envファイルにて環境変数を設定します。
FLASK_APP = apps.app:create_app
FLASK_DEBUG = 1
アプリを生成するのが関数の場合、「モジュール:関数」と指定します。
また、アプリを生成する関数名がcreate_app関数の場合、Flaskが自動でcreate_app関数を呼び出せます。
これらのディレクトリ/ファイルを起点にコードを追加していきます。
一度、flask routesコマンドでルーティングの確認します。
(venv) PS C:\Users\sugir\Documents\flask-image> flask routes
Endpoint Methods Rule
------------ ------- -----------------------------
image.index GET /image/
image.static GET /image/static/<path:filename>
static GET /static/<path:filename>
ルートが割り当てられていることが分かります。
次に、flask runコマンドを実行し127.0.0.5000/image/にアクセスしてみましょう。
Hello World!が左上に表示されたかと思います。
SQLAlchemyとMigrateの事前準備
次に、データベースを利用するための事前準備を行います。
- SQLAlchemyのインストール
- Migrateのインストール
上記どちらもflaskの拡張機能としてインストールできます。
SQLAlchemyとMigrateのインストール
pip installコマンドでSQLAlchemyとMigrateをインストールします。
pip install flask-sqlalchemy
pip install flask-migrate
pip listコマンドでパッケージを確認します。
(venv) PS C:\Users\sugir\Documents\flask-image> pip list
Package Version
----------------- -------
alembic 1.13.3
blinker 1.8.2
click 8.1.7
colorama 0.4.6
Flask 3.0.3
Flask-Migrate 4.0.7
Flask-SQLAlchemy 3.1.1
greenlet 3.1.1
itsdangerous 2.2.0
Jinja2 3.1.4
Mako 1.3.5
MarkupSafe 2.1.5
pip 24.0
python-dotenv 1.0.1
SQLAlchemy 2.0.35
typing_extensions 4.12.2
Werkzeug 3.0.4
インストールが完了したのでapp.pyにて利用準備を実施します。
SQLAlchemyとMigrateの利用準備
ここからはSQLAlchemyとMigrateの利用準備のため、app.pyにコードを追加します。
# Flaskのインポート
from flask import Flask
# SQLAlchemyのインポート
from flask_sqlalchemy import SQLAlchemy
# Migrateのインポート
from flask_migrate import Migrate
# SQLAlchemyのインスタンス作成
db = SQLAlchemy()
def create_app():
# Flaskのインスタンス作成
app = Flask(__name__)
# SQLalchemyとアプリを連携
db.init_app(app)
# Migrateとアプリを連携
Migrate(app, db)
# imageパッケージからviewsをインポート
from apps.image import views as image_views
# register_blueprintを利用してviewsのimageappをアプリへ登録
app.register_blueprint(image_views.image, url_prefix="/image")
return app
- SQLAlchemyのインポート
- Migrateのインポート
- SQLAlchemyのインスタンス作成
- SQLalchemyとアプリを連携
- Migrateとアプリを連携
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
先ほどインストールしたSQLAlchemyとMigrateをインポートします。
db = SQLAlchemy()
変数dbへSQLAlchemyをインスタンス化します。
db.init_app(app)
Migrate(app, db)
create_app関数内にて、SQLAlchemyとMigrateをアプリに連携させます。
models.pyの作成と実装
次に、以下のディレクトリに__init__.pyとmodels.pyを追加で作成します。
flask-image
├── .env
└── apps
├── app.py
└── image
├── __init__.py
├── models.py
└── views.py
ファイル作成が完了したら、models.pyにコードを追加します。
from apps.app import db
class UserImage(db.Model):
__tablename__ = "user_images"
id = db.Column(db.Integer, primary_key=True)
image_path = db.Column(db.String)
- apps.appからdbをインポート
- db.Modelを継承したUserImageクラス作成
- テーブル名の指定
- カラムの定義
from apps.app import db
apps.appからSQLAlchemyをインスタンス化したdbをインポートします。
class UserImage(db.Model):
__tablename__ = "user_images"
id = db.Column(db.Integer, primary_key=True)
image_path = db.Column(db.String)
db.Modelを継承したUserImageクラスを作成し、テーブル名を指定します。
作成したいカラム(列)を定義します。
__init__.pyによるモデルの宣言
モデルを作成できたら、__init__.pyにてmodels.pyをインポートします。
import apps.image.models
コンフィグ設定
次に、以下のディレクトリにconfig.pyと画像ファイル保存用のimagesディレクトリを追加で作成します。
flask-image
├── .env
└── apps
├── app.py
├── config.py
├── image
│ ├── __init__.py
│ ├── models.py
│ └── views.py
└── images
ファイル作成が完了したら、config.pyにコードを追加します。
from pathlib import Path
basedir = Path(__file__).parent.parent
# BaseConfigクラスを作成
class BaseConfig:
SECRET_KEY = "{SECRET_KEY}"
# 画像アップロード先にapps/imagesを指定
UPLOAD_FOLDER = str(Path(basedir, "apps", "images"))
# BaseConfigクラスを継承しLocalConfigクラスを作成
class LocalConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = f"sqlite:///{basedir/'local.sqlite'}"
SQLALCHEMY_TRACK_MODIFICATIONS = False
# config辞書にマッピング
config = {
"local": LocalConfig,
}
- Pathをインポート
- baseのパスになるbasedirを作成
- BaseConfigクラス作成
- 画像アップロード先にapps/imagesを指定
- config辞書を作成
from pathlib import Path
Pythonの標準ライブラリであるPathをインポートします。
basedir = Path(__file__).parent.parent
basedirは、Pathによってflask-imageディレクトリのパスを格納しています。
class BaseConfig:
SECRET_KEY = "{SECRET_KEY}"
UPLOAD_FOLDER = str(Path(basedir, "apps", "images"))
BaseConfigクラスを作成し、SECRET_KEYとUPLOAD_FOLDERを定義しています。
Flaskには、画像ファイルアップロード先を指定するためにconfigとしてUPLOAD_FOLDERが用意されています。
UPLOAD_FOLDERにパスを指定することで指定したディレクトリに画像をアップロードできます。
class LocalConfig(BaseConfig):
SQLALCHEMY_DATABASE_URI = f"sqlite:///{basedir/'local.sqlite'}"
SQLALCHEMY_TRACK_MODIFICATIONS = False
BaseConfigクラスを継承しLocalConfigクラスを作成します。
config = {
"local": LocalConfig,
}
最後に、config辞書を作成しcreate_app関数に引き渡せられるようlocalの値を持たせます。
config設定は、アプリにおいてどのように管理するか重要です。
開発環境・ステージング環境・本番環境など各環境で設定すべきコンフィグの値が変わるためです。
本記事では、環境を簡易に変更できるfrom_object形式で設定しています。
以下は、コンフィグ設定時に利用される環境設定の方法です。
- from_objectを使う方法
- from_mappingを使う方法
- from_envvarを使う方法
- from_pyfileを使う方法
- from_fileを使う方法
また、データベースを利用する際にセッションを利用するため、SECRET_KEYの設定が必要になります。
そのため、config設定したあとにapp.pyにインポートしcreate_appへ引数を持たせ、コンフィグを読み込ませます。
# Flaskのインポート
from flask import Flask
# SQLAlchemyのインポート
from flask_sqlalchemy import SQLAlchemy
# Migrateのインポート
from flask_migrate import Migrate
# configをインポート
from apps.config import config
# SQLAlchemyのインスタンス作成
db = SQLAlchemy()
def create_app(config_key):
# Flaskのインスタンス作成
app = Flask(__name__)
# config_keyにマッチする環境のコンフィグを読み込む
app.config.from_object(config[config_key])
# SQLalchemyとアプリを連携
db.init_app(app)
# Migrateとアプリを連携
Migrate(app, db)
# imageパッケージからviewsをインポート
from apps.image import views as image_views
# register_blueprintを利用してviewsのimageappをアプリへ登録
app.register_blueprint(image_views.image, url_prefix="/image")
return app
- configのインポート
- create_appに引数config_keyを持たせる
- config_keyにマッチする環境のコンフィグを読み込む
from apps.config import config
ppsディレクトリに存在するconfig.pyをインポートします。
app.config.from_object(config[config_key])
create_app関数にconfig_keyといった引数を持たせ、config.pyで作成した辞書から指定の値を読み込めるよう設定しています。
次に、環境設定ファイルである.envファイルに変更を追加します。
FLASK_APP = apps.app:create_app("local")
FLASK_DEBUG = 1
config.pyで作成したconfig辞書からlocalを読み込むように設定しています。
データベースの初期化とマイグレーション
次に、データベースの初期化とマイグレーションファイルを作成します。
マイグレーションファイルは、データベースの設計書のようなものです。
マイグレーションファイルを実行することで記述してきた内容がデータベースに反映されます。
データベースの初期化とマイグレーションを実施するflask dbコマンドは以下になります。
- flask db init
- flask db migrate
- flask db upgrade
- flask db downgrade
flask db init
flask db initコマンドは、データベースを初期化します。
ここでは、flask db initコマンドを実行するとflask-imageディレクトリ配下にmigrationsディレクトリが作成されます。
(venv) PS C:\Users\sugir\Documents\flask-image> flask db init
Creating directory 'C:\\Users\\sugir\\Documents\\flask-image\\migrations' ... done
Creating directory 'C:\\Users\\sugir\\Documents\\flask-image\\migrations\\versions' ... done
Generating C:\Users\sugir\Documents\flask-image\migrations\alembic.ini ... done
Generating C:\Users\sugir\Documents\flask-image\migrations\env.py ... done
Generating C:\Users\sugir\Documents\flask-image\migrations\README ... done
Generating C:\Users\sugir\Documents\flask-image\migrations\script.py.mako ... done
Please edit configuration/connection/logging settings in 'C:\\Users\\sugir\\Documents\\flask-image\\migrations\\alembic.ini' before proceeding.
flask db migrate
flask db migrateコマンドは、データベースのマイグレーションファイルを作成します。
flask db migrateコマンドを実行すると、Model定義をもとにmigrations/versions配下にPythonファイルでデータベースに適用する前の情報が生成されます。
(venv) PS C:\Users\sugir\Documents\flask-image> flask db migrate
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added table 'user_images'
Generating C:\Users\sugir\Documents\flask-image\migrations\versions\01c477262caa_.py ... done
flask db upgrade
flask db upgradeコマンドは、マイグレーション情報を実際にデータベースに反映します。
ここでは、flask db upgradeコマンドを実行するとuser_imagesテーブルが生成されます。
(venv) PS C:\Users\sugir\Documents\flask-image> flask db upgrade
INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> 01c477262caa, empty message
VSCodeでデータベースを確認する
VSCodeでデータベースの内容を確認できるように設定する方法です。
VSCodeの左サイドバーにある拡張機能アイコンをクリックし、検索バーで「SQLite」を検索します。
作成されたデータベースファイル(ここではlocal.sqlite)を右クリックし、「Open Database」を選択すると「SQLITE EXPLORER」が左下に表示されます。
データベースの作成と操作が実行できるようになりました。
DBを使った画像一覧表示・画像ファイルアップロード機能作成
ここでは、データベースを利用して以下の機能を作成していきます。
- 画像一覧表示機能の作成
- 画像ファイルアップロード機能の作成
- 削除機能の作成
画像一覧表示機能の作成
画像一覧表示機能作成における主な手順は以下になります。
- 画像一覧機能のエンドポイント作成
- 画像表示機能のエンドポイント作成
- 画像一覧表示機能のテンプレート作成
- 動作確認
画像一覧表示機能のエンドポイント作成
まず始めに、画像一覧機能のエンドポイントを作成します。
from apps.app import db
from apps.image.models import UserImage
from flask import Blueprint, render_template
image = Blueprint(
"image",
__name__,
template_folder = "templates",
static_folder = "static",
)
@image.route("/")
def index():
user_images = db.session.query(UserImage).all()
return render_template("iamge/index.html", user_images=user_images)
- dbのインポート
- UserImageのインポート
- render_templateのインポート
- UserImage情報を参照
- index.htmlファイル出力
from apps.app import db
from apps.image.models import UserImage
from flask import Blueprint, render_template
db、UserImage、テンプレートを利用するためのrender_templateをインポートします。
@image.route("/")
def index():
user_images = db.session.query(UserImage).all()
return render_template("iamge/index.html", user_images=user_images)
user_images変数にデータを格納し、index.htmlへuser_imagesとして値を渡しています。
画像表示機能のエンドポイント作成
次に、画像表示機能のエンドポイントを作成します。
実際に、指定されたディレクトリとファイルを読み込むために必要になります。
from apps.app import db
from apps.image.models import UserImage
from flask import Blueprint, render_template, send_from_directory, current_app
image = Blueprint(
"image",
__name__,
template_folder = "templates",
static_folder = "static",
)
@image.route("/")
def index():
user_images = db.session.query(UserImage).all()
return render_template("image/index.html", user_images=user_images)
@image.route("/images/<path:filename>")
def image_file(filename):
return send_from_directory(current_app.config["UPLOAD_FOLDER"], filename)
- flaskからsend_from_directory, current_appの追加インポート
- image_fileエンドポイント作成
- send_from_directoryで返す
from flask import Blueprint, render_template, send_from_directory, current_app
UPLOAD_FOLDERのパス取得とパス/画像ファイル名を返すsend_from_directoryを利用するため、flaskから追加インポートします。
@image.route("/images/<path:filename>")
def image_file(filename):
return send_from_directory(current_app.config["UPLOAD_FOLDER"], filename)
デコレータ関数にて、image_fileエンドポイントを作成します。
引数にfilenameを持たせ、画像を右クリックでタブを開けば画像表示されるようになります。
画像一覧表示機能のテンプレート作成
次に、画像一覧表示機能を反映させたテンプレートを作成します。
また、apps/staticにはCSSフレームワークであるBootStrapを採用しています。
以下、BootStrapのダウンロードサイトになります。
flask-image
├── .env
└── apps
├── app.py
├── config.py
├── static/css
│ └── bootstrap.min.css
├── image
│ ├── __init__.py
│ ├── models.py
│ ├── static/css
│ │ └── style.css
│ ├── templates
│ │ └── image
│ │ ├── base.html
│ │ └── index.html
│ └── views.py
└── images
テンプレート作成では、共通テンプレートとしてbase.htmlから作成します。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ImageApp</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
<link rel="stylesheet" href="{{ url_for('static', filename='css/bootstrap.min.css') }}" />
<link rel="stylesheet" href="{{ url_for('image.static', filename='css/style.css') }}" />
</head>
<body>
<nav class="navbar navbar-dark bg-dark">
<div class="container-fluid">
<ul class="navbar-nav flex-row">
<li class="nav-item pe-2">
<button class="navbar-toggler" type="button" data-bs-toggle="offcanvas" data-bs-target="#offcanvasDarkNavbar" aria-controls="offcanvasDarkNavbar" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
</li>
<li class="nav-item mt-1">
<a class="navbar-brand" href="{{ url_for('image.index') }}">ImageApp</a>
</li>
</ul>
<div class="offcanvas offcanvas-start text-bg-dark" tabindex="-1" id="offcanvasDarkNavbar" aria-labelledby="offcanvasDarkNavbarLabel">
<div class="offcanvas-header">
<h5 class="offcanvas-title" id="offcanvasDarkNavbarLabel">ImageApp</h5>
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="offcanvas" aria-label="Close"></button>
</div>
<div class="offcanvas-body">
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
<li class="nav-item">
<a class="nav-link d-flex align-items-center" href="/image">
<span class="material-symbols-outlined pe-2">image</span>
画像一覧
</a>
</li>
<li class="nav-item">
<a class="nav-link d-flex align-items-center" href="/image/upload">
<span class="material-symbols-outlined pe-2">upload</span>
画像アップロード
</a>
</li>
</ul>
</div>
</div>
</nav>
{% block content %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz" crossorigin="anonymous"></script>
</body>
</html>
以下、index.htmlはbase.htmlを継承しています。
{% extends "image/base.html" %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2 g-4 mx-2 my-2">
{% for user_image in user_images %}
<div class="col">
<div class="card">
<img src="{{ url_for('image.image_file', filename=user_image.image_path) }}" class="card-img-top img-thumbnail" style="border: none;" alt="画像">
</div>
</div>
{% endfor %}
</div>
{% endblock %}
動作確認
flask runコマンドでアプリを起動し、127.0.0.1:5000/image/へアクセスします。
また、ナビゲーションバーのトグルを押下すると、左から各ページボタンが表示されます。
画像ファイルアップロード機能の作成
いよいよ本丸となる画像ファイルアップロード機能の実装を実施します。
画像ファイルアップロード機能作成における主な手順は以下になります。
- 画像ファイルアップロード機能のフォームクラス作成
- 画像ファイルアップロード機能のエンドポイント作成
- 画像ファイルアップロード機能のテンプレート作成
- 動作確認
画像ファイルアップロード機能のフォームクラス作成
forms.pyに画像ファイルアップロード機能のフォームクラスを追加します。
また、フォームの拡張機能であるflask-wtfを利用します。
flask-wtfはバリデーションチェックやCSRF対策の機能を持ったフォーム作成できます。
フォーム作成時のメリットがあるため、インストールしておきましょう。
pip install flask-wtf
(venv) PS C:\Users\sugir\Documents\flask-image> pip list
Package Version
----------------- -------
alembic 1.13.3
blinker 1.8.2
click 8.1.7
colorama 0.4.6
Flask 3.0.3
Flask-Migrate 4.0.7
Flask-SQLAlchemy 3.1.1
Flask-WTF 1.2.1
greenlet 3.1.1
itsdangerous 2.2.0
Jinja2 3.1.4
Mako 1.3.5
MarkupSafe 2.1.5
pip 24.0
python-dotenv 1.0.1
SQLAlchemy 2.0.35
typing_extensions 4.12.2
Werkzeug 3.0.4
WTForms 3.1.2
インストールの確認ができたら、ファイルを作成します。
flask-image
├── .env
└── apps
├── app.py
├── config.py
├── static/css
│ └── bootstrap.min.css
├── image
│ ├── __init__.py
│ ├── forms.py
│ ├── models.py
│ ├── static/css
│ │ └── style.css
│ ├── templates
│ │ └── image
│ │ ├── base.html
│ │ └── index.html
│ └── views.py
└── images
# flask_wtfからFlaskFormクラスをインポート
from flask_wtf import FlaskForm
# flask_wtf.fileから各フィールドをインポート
from flask_wtf.file import FileAllowed, FileField, FileRequired
# wtformsからサブミットフィールドをインポート
from wtforms.fields.simple import SubmitField
class UploadImageForm(FlaskForm):
# ファイルフィールドに必要なバリデーションを設定
image = FileField(
validators = [
FileRequired("画像ファイルを指定してください"),
FileAllowed(["png", "jpg", "jpeg"], "サポートされていない画像形式です"),
]
)
submit = SubmitField("アップロード")
- flask_wtfからFlaskFormクラスをインポート
- flask_wtf.fileから各フィールドをインポート
- wtformsからサブミットフィールドをインポート
- アップロードイメージフォームクラス作成
from flask_wtf import FlaskForm
from flask_wtf.file import FileAllowed, FileField, FileRequired
from wtforms.fields.simple import SubmitField
フォーム拡張機能を利用するためにFlaskFormクラスをインポートします。
ファイルフィールドに必要な各フィールドをインポートします。
サブミットフィールドをインポートします。(<input type=”submit”>フィールドを生成します。)
class UploadImageForm(FlaskForm):
# ファイルフィールドに必要なバリデーションを設定
image = FileField(
validators = [
FileRequired("画像ファイルを指定してください"),
FileAllowed(["png", "jpg", "jpeg"], "サポートされていない画像形式です"),
]
)
submit = SubmitField("アップロード")
FlaskFormを継承したUploadImageFormクラスを作成し、必要なバリデーションを設定します。
FileRequiredによるファイル指定、FileAllowedによる画像形式を設定します。
画像ファイルアップロード機能のエンドポイント作成
画像ファイルアップロード機能のエンドポイントを作成します。
from apps.app import db
from apps.image.models import UserImage
# flaskからcurrent_app, redirect, url_forを追加インポート
from flask import Blueprint, render_template, send_from_directory, current_app, redirect, url_for
# uuidをインポート
import uuid
# Pathをインポート
from pathlib import Path
# UploadImageFormをインポート
from apps.image.forms import UploadImageForm
image = Blueprint(
"image",
__name__,
template_folder = "templates",
static_folder = "static",
)
@image.route("/")
def index():
user_images = db.session.query(UserImage).all()
return render_template("image/index.html", user_images=user_images)
@image.route("/images/<path:filename>")
def image_file(filename):
return send_from_directory(current_app.config["UPLOAD_FOLDER"], filename)
@image.route("/upload", methods=["GET", "POST"])
def upload_image():
# UploadImageFormを利用してバリデーションチェック
uploadimageform = UploadImageForm()
if uploadimageform.validate_on_submit():
# アップロードされた画像ファイルを取得
file = uploadimageform.image.data
# ファイルのファイル名と拡張子を取得しファイル名をuuidに変換
ext = Path(file.filename).suffix
image_uuid_file_name = str(uuid.uuid4()) + ext
# 画像保存
image_path = Path(current_app.config["UPLOAD_FOLDER"], image_uuid_file_name)
file.save(image_path)
# DB保存
user_image = UserImage(
image_path = image_uuid_file_name
)
db.session.add(user_image)
db.session.commit()
# アップロード後はリダイレクトで画像一覧画面へ
return redirect(url_for("image.index"))
return render_template("image/upload.html", form=uploadimageform)
- flaskからredirect, url_forの追加インポート
- uuidのインポート
- Pathのインポート
- forms.pyからUploadImageFormクラスをインポート
- uploadエンドポイント作成
- UploadImageFormのインスタンス作成
- バリデーションチェック
- ファイル取得とファイル名変換
- 画像保存とDB保存
- リダイレクトで画像一覧表示画面へ
from flask import Blueprint, render_template, send_from_directory, current_app, redirect, url_for
import uuid
from pathlib import Path
from apps.image.forms import UploadImageForm
flaskからcurrent_app、redirect、url_forをそれぞれインポートします。
uuidは、ファイル名をそのままにするとセキュリティ上問題になる可能性があるため、ファイル名変換に利用します。
また、forms.pyで作成したUploadImageFormクラスをインポートします。
@image.route("/upload", methods=["GET", "POST"])
def upload_image():
# UploadImageFormを利用してバリデーションチェック
uploadimageform = UploadImageForm()
if uploadimageform.validate_on_submit():
# アップロードされた画像ファイルを取得
file = uploadimageform.image.data
# ファイルのファイル名と拡張子を取得しファイル名をuuidに変換
ext = Path(file.filename).suffix
image_uuid_file_name = str(uuid.uuid4()) + ext
# 画像保存
image_path = Path(current_app.config["UPLOAD_FOLDER"], image_uuid_file_name)
file.save(image_path)
# DB保存
user_image = UserImage(
image_path = image_uuid_file_name
)
db.session.add(user_image)
db.session.commit()
# アップロード後はリダイレクトで画像一覧画面へ
return redirect(url_for("image.index"))
return render_template("image/upload.html", form=uploadimageform)
デコレータ関数にて、uploadエンドポイントを作成します。
UploadImageFormクラスをインスタンス化します。
画像ファイルアップロード時のバリデーションチェックを実施します。
始めに、アップロードされた画像ファイルを取得します。
取得したファイルの拡張子を取得し格納します。
uuidにてファイル名を変換し、拡張子を付与します。
image_pathは、current_app.config[“UPLOAD_FOLDER”]でUPLOAD_FOLDERのパスを取得し、image_uuid_file_nameを格納します。
file.saveにて、画像保存します。
DBへの保存は、UserImageクラスのimage_path属性にimage_uuid_fole_nameを格納し、db.session.addと.commitで追加/登録します。
render_templateでformデータをテンプレートに渡しています。
画像ファイルアップロード機能のテンプレート作成
次に、画像ファイルアップロード機能を反映させたテンプレートを作成します。
flask-image
├── .env
└── apps
├── app.py
├── config.py
├── static/css
│ └── bootstrap.min.css
├── image
│ ├── __init__.py
│ ├── forms.py
│ ├── models.py
│ ├── static/css
│ │ └── style.css
│ ├── templates
│ │ └── image
│ │ ├── base.html
│ │ ├── index.html
│ │ └── upload.html
│ └── views.py
└── images
upload.htmlを作成します。
{% extends "image/base.html" %}
{% block content %}
<div class="mx-2 my-2">
<h5>画像アップロード</h5>
<p>アップロードする画像を選択してください</p>
<form action="{{ url_for('image.upload_image') }}" method="POST" enctype="multipart/form-data" novalidate="novalidate">
{{ form.hidden_tag() }}
<div>
<label>
<span>{{ form.image(class="form-control-file") }}</span>
</label>
</div>
{% for error in form.image.errors %}
<span style="color:red;">{{ error }}</span>
{% endfor %}
<hr/>
<div>
<label>{{ form.submit(class="btn btn-primary") }}</label>
</div>
</form>
</div>
{% endblock %}
動作確認
flask runコマンドでアプリを起動し、127.0.0.1:5000/image/へアクセスします。
サイドバーから画像アップロードページに遷移すると、以下のようなページが表示されます。
実際に、ファイル選択ボタンから画像ファイルを選択し、アップロードすると画像一覧ページにリダイレクトされます。
問題なければ、アップロードした画像は画像一覧ページで確認できます。
削除機能の作成
削除機能作成における主な手順は以下になります。
- 削除機能のフォームクラス作成
- 削除機能のエンドポイント作成
- 画像一覧表示のテンプレートに削除機能追加
- 動作確認
削除機能のフォームクラス作成
forms.pyに削除機能のフォームクラスを追加します。
# flask_wtfからFlaskFormクラスをインポート
from flask_wtf import FlaskForm
# flask_wtf.fileから各フィールドをインポート
from flask_wtf.file import FileAllowed, FileField, FileRequired
# wtformsからサブミットフィールドをインポート
from wtforms.fields.simple import SubmitField
class UploadImageForm(FlaskForm):
# ファイルフィールドに必要なバリデーションを設定
image = FileField(
validators = [
FileRequired("画像ファイルを指定してください"),
FileAllowed(["png", "jpg", "jpeg"], "サポートされていない画像形式です"),
]
)
submit = SubmitField("アップロード")
# 画像削除フォームクラス作成
class DeleteImageForm(FlaskForm):
submit = SubmitField("delete")
- DeleteImageFormクラスの作成
class DeleteImageForm(FlaskForm):
submit = SubmitField("delete")
FlaskFormを継承したDeleteImageFormクラスを作成し、delete用サブミットフィールドを設定しています。
削除機能のエンドポイント作成
削除機能のエンドポイントを作成します。
from apps.app import db
from apps.image.models import UserImage
from flask import Blueprint, render_template, send_from_directory, current_app, redirect, url_for
# uuidをインポート
import uuid
# Pathをインポート
from pathlib import Path
# forms.pyからDeleteImageFormクラスを追加インポート
from apps.image.forms import UploadImageForm, DeleteImageForm
# osをインポート
import os
image = Blueprint(
"image",
__name__,
template_folder = "templates",
static_folder = "static",
)
@image.route("/")
def index():
user_images = db.session.query(UserImage).all()
deleteimageform = DeleteImageForm()
return render_template("image/index.html", user_images=user_images, form=deleteimageform)
@image.route("/images/<path:filename>")
def image_file(filename):
return send_from_directory(current_app.config["UPLOAD_FOLDER"], filename)
@image.route("/upload", methods=["GET", "POST"])
def upload_image():
# UploadImageFormを利用してバリデーションチェック
uploadimageform = UploadImageForm()
if uploadimageform.validate_on_submit():
# アップロードされた画像ファイルを取得
file = uploadimageform.image.data
print(file)
# ファイルのファイル名と拡張子を取得しファイル名をuuidに変換
ext = Path(file.filename).suffix
print(Path(file.filename))
print(ext)
image_uuid_file_name = str(uuid.uuid4()) + ext
# 画像保存
image_path = Path(current_app.config["UPLOAD_FOLDER"], image_uuid_file_name)
file.save(image_path)
# DB保存
user_image = UserImage(
image_path = image_uuid_file_name
)
db.session.add(user_image)
db.session.commit()
# アップロード後はリダイレクトで画像一覧画面へ
return redirect(url_for("image.index"))
return render_template("image/upload.html", form=uploadimageform)
@image.route("/delete/<string:image_id>", methods=["POST"])
def delete_image(image_id):
# user_imagesテーブルからレコードを削除
delete_image = db.session.query(UserImage).filter(UserImage.id == image_id).first()
os.remove(f"apps/images/{delete_image.image_path}")
db.session.query(UserImage).filter(UserImage.id == image_id).delete()
db.session.commit()
return redirect(url_for("image.index"))
- forms.pyからDeleteImageFormクラスを追加インポート
- osのインポート
- index関数にdeleteimageformを追加
- delete_imageエンドポイント作成
- imagesディレクトリから画像削除
- DBから画像データ削除
- 画像一覧ページにリダイレクト
from apps.image.forms import UploadImageForm, DeleteImageForm
import os
forms.pyで作成したDeleteImageFormクラスとimagesディレクトリから画像削除するためosライブラリをインポートします。
@image.route("/")
def index():
user_images = db.session.query(UserImage).all()
deleteimageform = DeleteImageForm()
return render_template("image/index.html", user_images=user_images, form=deleteimageform)
index関数内にDeleteImageFormをインスタンス化し、テンプレートへformを渡しています。
@image.route("/delete/<string:image_id>", methods=["POST"])
def delete_image(image_id):
# user_imagesテーブルからレコードを削除
delete_image = db.session.query(UserImage).filter(UserImage.id == image_id).first()
os.remove(f"apps/images/{delete_image.image_path}")
db.session.query(UserImage).filter(UserImage.id == image_id).delete()
db.session.commit()
return redirect(url_for("image.index"))
delete_imageエンドポイントを作成しています。
idを利用してDBから一意の画像データを取得し、os.removeでimagesディレクトリから画像ファイルを削除しています。
また、画像削除後にDBからも一意のレコードを削除しています。
削除が完了したら画像一覧表示画面へリダイレクトします。
画像一覧表示のテンプレートに削除機能追加
次に、画像一覧表示画面のテンプレートファイルに削除ボタンを追加します。
{% extends "image/base.html" %}
{% block content %}
<div class="row row-cols-1 row-cols-md-2 g-4 mx-2 my-2">
{% for user_image in user_images %}
<div class="col">
<div class="card">
<img src="{{ url_for('image.image_file', filename=user_image.image_path) }}" class="card-img-top img-thumbnail" style="border: none;" alt="画像">
<div class="card-body">
<div class="d-flex justify-content-end">
<form action="{{ url_for('image.delete_image', image_id=user_image.id) }}" method="POST">
{{ form.hidden_tag() }}
{{ form.submit(class="form-delete-btn material-symbols-outlined")}}
</form>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% endblock %}
BootStrap以外にも、style.cssを記述しています。
.form-delete-btn {
border: none;
background:#fff;
}
動作確認
以下は削除ボタン追加後の画面になります。
画像右下部に追加した削除ボタンを押下すると、画像が削除されたことが分かります。
flask-loginによるログイン機能を実装した認証アプリ開発
- Blueprintによるアプリ登録
- SQLAlchemyとMigrateの事前準備
- コンフィグ設定
- データベースを使ったCRUD機能作成
- サインアップ機能の作成
- ログイン機能の作成
- ログアウト機能の作成
本記事では、メインとして画像表示アプリの開発を目的にしています。
そのため、ログイン機能を実装した認証アプリを合わせて開発したい場合は、「【Flask】flask-loginによるログイン機能を実装した認証アプリ開発」を一読ください。
コメント