django-rest-frameworkでS3上のファイルのURLを返す
※この記事は Django Advent Calendar 2019 12日目の記事 です。
HTMLのレスポンス形式で、Cloud Storage上のファイルのURLを載せて返す方法は既に書きました。
django-storages の設定をした後、DetailViewなどでデータを返しurlフィルタなんかでPATHを表示させればうまく表示できるって感じでした。
django-rest-framework(以下DRF)だとどうなるのかというのが気になったので、AWSのS3のPATHをJSONレスポンスに含める方法を試しました。
やってみたソースコードは以下にあります。
d-kusk/drf-storage - github
環境
- Python 3.7.2
- Django 2.2.7
- djangorestframework 3.10.3
- django-storages 1.8
- boto3 1.10.32
- Pillow 6.2.1
ディレクトリ構成
.
├── config // Project dir
├── entry
├── manage.py
├── requirements.txt
├── :
└── venv
Djangoの準備
DjangoでプロジェクトとDRFのインストールや初期設定は終えている前提で進めます。
S3とのやり取りを行うために以下のパッケージをインストール。
$ source venv/bin/activate.fish // 適宜変えてください
$ pip install boto3 django-storages Pillow
config/setting.py には以下を設定。ドキュメントに載っていた必須項目をベースに+アルファみたいな感じ。
# django-storages
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# AWS
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
AWS_S3_REGION_NAME = os.environ.get('AWS_S3_REGION_NAME')
AWS_DEFAULT_ACL = os.environ.get('AWS_DEFAULT_ACL', 'public-read')
AWS_LOCATION = 'static' # s3バケット上のベースとなるファイルパス
# STATIC_URL = '/static/'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
STATIC_URL = 'https://%s/%s/' % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
S3の設定
バケットの作成と準備。
ドキュメントを参考に進めていく。
IAMユーザーを作ってバケットに紐づけておく。(ここは割愛)
プライバシーポリシーはドキュメントに有った以下を使用。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObjectAcl",
"s3:GetObject",
"s3:ListBucket",
"s3:DeleteObject",
"s3:PutObjectAcl"
],
"Resource": [
"arn:aws:s3:::example-bucket-name/*",
"arn:aws:s3:::example-bucket-name"
]
}
]
}
これを行った上で、一旦S3にあがるか確認。
$ ./manage.py collectstatic
実行が終わると、S3のバケット上に static ディレクトリが作成され、中にCSSやJSのファイルが作成されているはず。
Entry アプリの作成と画像のアップロード
カスタムユーザーモデルでプロフィール画像を設定しようと思ったんですが、ちょっと面倒だなってなってしまったのでサクッと試しやすいEntryアプリを作りました。雑なブログみたいなイメージ。
$ ./manage.py startapp entry
config/settings.py
:
INSTALLED_APPS = [
:
'rest_framework',
'entry',
]
:
以下のようにモデルを作成し、 thumbnail に画像の情報を持つ想定。
entry/models.py
from django.db import models
class Entry(models.Model):
title = models.CharField('タイトル', max_length=128)
content = models.TextField('本文')
thumbnail = models.ImageField('サムネイル', null=True)
class Meta:
db_table = 'entry'
verbose_name = '記事'
verbose_name_plural = '記事一覧'
def __str__(self):
return(self.title)
あとはこれに ViewSet と Serializer を作成。
entry/ViewSet.py
from rest_framework import viewsets
from .Serializer import EntrySerializer
from .models import Entry
class EntryViewSet(viewsets.ModelViewSet):
queryset = Entry.objects.all()
serializer_class = EntrySerializer
entry/Serializer.py
from rest_framework import serializers
from .models import Entry
class EntrySerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Entry
fields = ['title', 'content', 'thumbnail']
便宜上管理画面に項目を追加しておく。
entry/admin.py
from django.contrib import admin
from .models import Entry
@admin.register(Entry)
class EntryAdmin(admin.ModelAdmin):
pass
URLの紐付け
APIとしてアクセスできるように urls.py で設定をしておく。
config/urls.py
:
from rest_framework import routers
from entry.ViewSet import EntryViewSet
router = routers.DefaultRouter()
router.register(r'api/entry', EntryViewSet)
urlpatterns = [
path('', include(router.urls)),
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')),
]
確認
管理画面からデータを入れておく。
ブラウザから api-auth/
にアクセス。
DRFのプレビューができる機能にアクセスできるので、その後上で紐付けた api/entry にアクセスすると EntryViewSet 経由で処理が行われてこんな感じのレスポンスが返ってくるのが確認できます。
DBから取得したデータ(画像のPATHを保存しているみたいです)から django-storages で S3 から取得。取得したデータを EntrySerializer でまとめているイメージですかね。
やってみて
やっぱライブラリがいい感じにやってくれるなーって思いました。
自分で取得してまとめる方法もあるみたいですが、そっちは試せていないのでまた今度。