LoginSignup
70
69

More than 3 years have passed since last update.

Django mediaファイルとAWS S3

Last updated at Posted at 2019-05-01

Django staticファイルとAWS S3 - Qiita
Django mediaファイルとAWS S3 - Qiita

※今回の記事はDjangoを利用したブログである「Djangoブログ」(仮称)作成時に調査したことをまとめたものです。Djangoは初めてでしたが3週間程度で作りました。α版ですが、どうぞユーザ登録して使ってみてください。
「Djangブログ」 - GAEにDeployしたものです

本記事ではDjangoのmediaファイルの扱いと、AWS S3へのアップロードについてまとめます。

DjangoではFileField、ImageFieldでアップロードされるファイルをmediaファイルと呼びます。

Form fields - 公式ドキュメント

1.localへのアップロード - 開発環境(DEBUG=True)

1-1.テストプロジェクト作成

python -m venv localupload
source localupload/bin/activate
cd localupload/
pip freeze
pip install django
django-admin startproject localupload .

Remoteからアクセスするための設定です。

localupload/settings.py
---
ALLOWED_HOSTS = ["www.mypress.jp"]
---

ここまででプロジェクトが動作するので確認します。

python manage.py migrate
python manage.py runserver 0:8080

1-2.localへのアップロード

localへのアップロードするアプリケーションを作成します。

python manage.py startapp uploadapp
localupload/settings.py
---
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'uploadapp',
]
---
localupload/settings.py
---
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
---
  • MEDIA_URL : メディアファイル公開時のURLのプレフィクス。url=http://アプリのドメイン+MEDIA_URL+メディアファイル名
  • MEDIA_ROOT : サーバから見たメディアルートの絶対パス. プロジェクトトップディレクトリ/media
localupload/urls.py
from django.conf import settings
from django.contrib import admin
from django.urls import path, include
from django.conf.urls.static import static

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('uploadapp.urls')),
]
if settings.DEBUG:
    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

開発中(DEBUG=True)の時は最後のスニペットを追加することで、画像はlocalなStorageのMEDIA_ROOTに保存され、localなurl(相対url)のMEDIA_URLで参照できます。 しかし本番環境(DEBUG=False)ではこのやり方は推奨されていません。staticファイルの場合と同じです。

静的ファイル (画像、JavaScript、CSS など) を管理する - 公式ドキュメント
Django staticファイルとAWS S3 - Qiita

uploadapp/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('', views.Photo.as_view()),
]

さて今回は、FileFieldでなくImageFieldを使います。ImageFieldは次のようなものです。

  • ImageFieldは画像を扱うことに特化したFileFieldの派生フィールドです
  • ImageFieldにはPillowが必要。
  • 登録時のバリデーションで画像ファイルのチェックをする。画像ファイルでないもの、データの破損があるものは登録エラーとする(専用エラーメッセージあり)。
  • 管理用のフィールドを事前に用意すると、登録時に画像の高さと幅(pixel単位)を取得して保存する

それではPillowをインストールします。

pip install Pillow

ImageFieldを使ってmodelsを定義します。

uploadapp/models.py
from django.db import models

class PhotoModel(models.Model):
    image = models.ImageField(upload_to='images')

    def __str__(self):
        return self.image.url

formsを定義します。

uploadapp/forms.py
from django import forms
from .models import PhotoModel

class PhotoForm(forms.ModelForm):

    class Meta:
        model = PhotoModel
        fields = '__all__'

viewsを定義します。

uploadapp/views.py
from django.views import generic
from .forms import PhotoForm
from .models import PhotoModel

class Photo(generic.CreateView):
    model = PhotoModel
    form_class = PhotoForm
    template_name = 'uploadapp/upload.html'
    success_url = '/'

    def get_context_data(self, **kwargs):
        context = super(Photo, self).get_context_data(**kwargs) # はじめに継承元のメソッドを呼び出す
        context["photos"] = PhotoModel.objects.all()
        return context

テンプレートのフォルダを作成します。

mkdir -p uploadapp/templates/uploadapp/

アップロードフォームとアップロード済みの画像リスト表示です。

uploadapp/templates/uploadapp/upload.html
    <form action="" method="POST" enctype="multipart/form-data">{% csrf_token %}        {{ form.as_p }}
        <button type="submit">アップロード</button>
    </form>

    {% for photo in photos %}
        <a href="{{ photo.image.url }}">{{ photo }}</a>
        <hr>
    {% endfor %}

プロジェクトを起動します。

python manage.py runserver 0:8080

2つの画像をアップロード済みの画面です。

image.png

MEDIA_URL の設定を考慮して、以下のHTMLソースを確認してください。photo.image.url="/media/images/tree5.jpg" のように展開されています。

HTMLソース
    <form action="" method="POST" enctype="multipart/form-data"><input type="hidden" name="csrfmiddlewaretoken" value="m0qzyYvLhuUckbF5ctHAAJOCThqZ24Q7RE5c70EQ4bnTm05v3F33aYrn7br4jed2">
        <p><label for="id_image">Image:</label> <input type="file" name="image" accept="image/*" required id="id_image"></p>
        <button type="submit">アップロード</button>
    </form>


        <a href="/media/images/tree4jpeg.jpeg">/media/images/tree4jpeg.jpeg</a>
        <hr>

        <a href="/media/images/tree5.jpg">/media/images/tree5.jpg</a>
        <hr>

MEDIA_ROOTの設定を確認してください。プロジェクトのトップディレクトリでtreeコマンドを発行した結果です。

$ tree media/
media/
├── images
│   ├── tree4jpeg.jpeg
│   └── tree5.jpg
└── tree4jpeg.jpeg

2.Remote の AWS S3 へアップロード - 本番環境(DEBUG=False)

ここから、画像をAWS S3にアップロードする設定を見ていきます。本番環境なので、基本はstatcファイルもAWS S3にdeployする設定を行い、追加でmediaファイルもAWS S3にアップロードする設定を行います。

staticファイルとAWSの設定については「Django staticファイルとAWS S3 -Qiita」を参照してください。

まずは本番環境の設定です。

localupload/settings.py
---
DEBUG = False
---

AWS S3に新バケットを作り、以下のポリシーを付与します。

 {
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"AddPerm",
      "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::media-test-reiwa/*"]
    }
  ]
}

必要なライブラリをインストールします。

pip install boto3
pip install django-storages

ライブラリを有効にします。

localupload/settings.py
---
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'uploadapp',
    'storages',      # 追加
]
---
localupload/settings.py
# STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

AWS_ACCESS_KEY_ID = 'AKIAXXXXXXXXXXXXXXXXXXXXX'
AWS_SECRET_ACCESS_KEY = 'YYYYYYYYYYYYYYYYYYYYYY'
AWS_STORAGE_BUCKET_NAME = 'media-test-reiwa'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}
AWS_LOCATION = 'static'
AWS_DEFAULT_ACL = None
STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'


MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

DEFAULT_FILE_STORAGE = 'localupload.storage_backends.MediaStorage'

staticファイルの時の設定に加えて、DEFAULT_FILE_STORAGEの設定を追加しています。

(説明) static assets と media assets

  • static ファイルはSTATICFILES_STORAGE=S3Boto3Storageで設定。collectstaticが処理する。
  • media ファイルはDEFAULT_FILE_STORAGEで設定。S3Boto3Storageを拡張し、'media'ディレクトリにアップロードするよう指定する。

S3Boto3Storageの拡張はDEFAULT_FILE_STORAGEで指定したように、以下のstorage_backends.pyで行います。同一ファイル名の画像がアップロードされたら上書きせずに適当にリネームするように設定しています。

localupload/storage_backends.py
from storages.backends.s3boto3 import S3Boto3Storage

# 画像は同ファイル名での上書きを許さない
class MediaStorage(S3Boto3Storage):
    location = 'media'
    file_overwrite = False

staticディレクトリを作成し、staticファイルをcollectstaticでAWS S3にdeployします。

mkdir static
python manage.py collectstatic

ここでDBテーブルから前にアップロードしたエントリーを全削除しておきます。
プロジェクトを起動し、画像ファイルを2個アップロードします。

python manage.py runserver 0:8080

以下のような画面が確認できます。

image.png

AWS S3のバケットにはstaticとmediaディレクトリできています。

image.png

mediaディレクトリには確かに2ファイルがアップロードされています。

image.png

今回は以上です。

70
69
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
70
69