Flask画像アップロードチュートリアル

Flask, DynamoDB, Lambda, S3, Zappaを使用した画像投稿・保存・表示チュートリアル #

はじめに #

このチュートリアルでは、Flask, DynamoDB, Lambda, S3, Zappaを使用して、画像を投稿・保存・表示するウェブアプリケーションを作成する方法を紹介します。AWS Lambdaを使ってリクエストを処理し、Amazon S3に画像を保存し、DynamoDBでメタデータを管理します。

必要なツールのインストール #

$ pip install flask boto3 zappa

プロジェクトの構成 #

プロジェクトディレクトリを作成し、次のような構成でファイルを作成します。

image-app/
  ├── app.py
  ├── templates/
  │   ├── index.html
  │   ├── upload.html
  │   └── view.html
  └── zappa_settings.json

S3バケットの設定 #

AWSコンソールでS3バケットを作成し、バケット名をメモしておきます。

app.pyとzappa用に二つ必要です。

DynamoDBテーブルの設定 #

AWSコンソールでDynamoDBテーブルを作成し、テーブル名とパーティションキーをメモしておきます。

## 画像のアップロードと表示機能の実装 app.pyに画像のアップロードと表示機能を実装します。

[app.py]

from flask import Flask, render_template, request, redirect, url_for
import boto3
import os
from werkzeug.utils import secure_filename
from uuid import uuid4
from botocore.exceptions import NoCredentialsError

app = Flask(__name__)

# S3およびDynamoDBの設定
S3_BUCKET = 'your-s3-bucket-name'
DYNAMO_TABLE = 'your-dynamodb-table-name'
s3 = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(DYNAMO_TABLE)

ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
    response = table.scan()
    images = response['Items']
    return render_template('index.html', images=images)

@app.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        if 'file' not in request.files:
            return redirect(request.url)

        file = request.files['file']

        if file.filename == '':
            return redirect(request.url)

        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file_extension = filename.split('.')[-1]
            unique_filename = f"{uuid4()}.{file_extension}"
            image_id = str(uuid4())
            try:
                s3.upload_fileobj(file, S3_BUCKET, unique_filename)
                table.put_item(
                    Item={
                        'id': image_id,
                        'file_name': unique_filename,
                    }
                )
                return redirect(url_for('view', image_id=image_id))
            except NoCredentialsError:
                return "Error: S3へのアクセス権がありません。"

    return render_template('upload.html')


@app.route('/view/<string:image_id>')
def view(image_id):
    try:
        response = table.get_item(Key={'id': image_id})
        file_name = response['Item']['file_name']
        image_url = s3.generate_presigned_url('get_object', Params={'Bucket': S3_BUCKET, 'Key': file_name}, ExpiresIn=3600)
        return render_template('view.html', image_url=image_url)
    except KeyError:
        return "Error: 画像が見つかりません。"

if __name__ == '__main__':
    app.run()

このコードでは、index()関数でアプリケーションのホーム画面、upload()関数で画像アップロード機能、view()関数でアップロードされた画像の表示機能をそれぞれ実装しています。また、S3およびDynamoDBの設定も行っています。

[templates/index.html]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"content="width=device-width,initial-scale=1.0">
    <title>Image App</title>
</head>
<body>
    <h1>Welcome to the Image App</h1>
    <p><a href="{{ url_for('upload') }}">Uploadan image</a></p>

    <h2>Uploaded Images</h2>
    {% for image in images %}
        <p>
            <a href="{{ url_for('view',image_id=image.id) }}">Image: {{image.file_name }}</a>
        </p>
    {% endfor %}
</body>
</html>

[templates/update.html]

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Upload Image</title>
  </head>
  <body>
    <h1>Upload an Image</h1>
    <form action="{{ url_for('upload') }}" method="post" enctype="multipart/form-data">
      <p><input type="file" name="file"></p>
      <p><input type="submit" value="Upload"></p>
    </form>
    <p><a href="{{ url_for('index') }}">Back to Home</a></p>
  </body>
</html>

[templates/view.html]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View Image</title>
</head>
<body>
    <h1>View Image</h1>
    <p><img src="{{ image_url }}" alt="Uploaded Image"></p>
    <p><a href="{{ url_for('upload') }}">Upload another image</a></p>
    <p><a href="{{ url_for('index') }}">Back to Home</a></p>
</body>
</html>

Zappaを使ってLambdaにデプロイ #

{
  "production": {
    "app_function": "app.app",
    "aws_region": "ap-northeast-1",
    "s3_bucket": "your-zappa-deploy-bucket",
    "project_name": "image-app",
    "runtime": "python3.8",
    "timeout_seconds": 30
  }
}

Zappaを使ってデプロイします。

$ zappa deploy production

ウェブアプリケーションのテスト #

デプロイが完了したら、表示されたURLにアクセスしてウェブアプリケーションをテストします。画像をアップロードし、正常に表示されるか確認します。

以上で、Flask, DynamoDB, Lambda, S3, Zappaを使用して画像を投稿・保存・表示するウェブアプリケーションの作成が完了しました。

おまけ 削除機能を追加する #

削除機能を追加するには、以下の手順でapp.pyとview.htmlを修正してください。

  • app.pyに画像削除用の新しいルートと関数を追加します。
@app.route('/delete/<string:image_id>', methods=['POST'])
def delete(image_id):
    try:
        response = table.get_item(Key={'id': image_id})
        file_name = response['Item']['file_name']
        s3.delete_object(Bucket=S3_BUCKET, Key=file_name)
        table.delete_item(Key={'id': image_id})
        return redirect(url_for('index'))
    except KeyError:
        return "Error: 画像が見つかりません。"

またapp.pyのview()関数でimage_idをテンプレートに渡すように修正します。

return render_template('view.html', image_url=image_url, image_id=image_id)
  • view.htmlに削除ボタンを追加します

[templates/view.html]

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>View Image</title>
</head>
<body>
    <h1>View Image</h1>
    <img src="{{ image_url }}" alt="Uploaded image">
    <form action="{{ url_for('delete', image_id=image_id) }}" method="post">
        <input type="submit" value="Delete Image">
    </form>
    <p><a href="{{ url_for('index') }}">Back to home</a></p>
</body>
</html

この修正により、画像の詳細ページに削除ボタンが表示され、そのボタンをクリックすることで画像を削除できるようになります。画像が削除された後、ユーザーはアプリケーションのホームページにリダイレクトされます。