【Django】Djangoで静的ファイルをS3に保存する一番簡単な方法【AWS S3】

# Djangoで静的ファイルをS3に保存する一番簡単な方法

調べたらカスタムストレージクラスを作る方法がいろいろ出てきたけど意味不明だったので自分用に。
多分これが一番簡単だと思います。

**【参考】**
- [Django mediaファイルとAWS S3](https://qiita.com/sand/items/b897aa47c304b7fbdcb5) 
- [django-storages](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html)

## pyをいじっていく

### 1. models.py , forms.py

一般的な方法でPhotoモデル,Photoformモデルを作成する
自分はDjnagoBrosというサイトで紹介されている写真投稿サイトを作って、それを改造する形で実装しました。

### 2. project_name/urls.py

本番環境想定の設定にしてみる

```py
if settings.DEBUG:
    urlpatterns += static(
        settings.MEDIA_URL, document_root=settings.MEDIA_ROOT
    )
```
- settings.py の DEBUG が True の場合だけ Local に保存されるように設定

## 必要ライブラリをインストールする

S3にアップするにはやはりAWSと連携するライブラリを使う必要がある
しっかり用意されてるので、それを使ってく
※pyenvを使っている場合、仮想環境に入っておくことを忘れないように

```sh
$ pip install boto3
$ pip install django-storages
```

## S3にバケットを作成する

コンソールで作成

- バケットポリシー
ポリシーは[django-storage](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html)のUsageにあるものをコピーしてくる
AWSIDやバケット名は自分のやつに変える

```json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::AWS_ACCOUNT_ID:user/bucket_name"
            },
            "Action": [
                "s3:PutObject",
                "s3:GetObjectAcl",
                "s3:GetObject",
                "s3:ListBucket",
                "s3:DeleteObject",
                "s3:PutObjectAcl"
            ],
            "Resource": [
                "arn:aws:s3:::bucket_name/*",
                "arn:aws:s3:::bucket_name"
            ]
        }
    ]
}
```

## IAM ユーザ作成

S3の操作権限を持つIAMユーザを作る
- 名前 : なんでもいいです
- プログラムによるアクセス
- S3FullAccess (めんどくさいので) (絞る場合、↑のポリシーで指定したものだけ入れたらOK)
- 認証情報 : DLしてローカルPCにでも保存しておきます

## settings.py 編集

- DEBUGモード解除

```py
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False # Falseにする

ALLOWED_HOSTS = ["127.0.0.1", "xxx.xxx.xxx.xxx"] # DEBUG = False の時は指定しないと起動できない
# この辺はまだよくわかってないので今回はとりあえず127.0.0.1を許可しておく
```

- INSTALLED_APPS に 'storages' 追加

pip でインストールしておいた`django-storage`を使うために必要

```py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django_cleanup',
    'app',
    'storages', # 追加
]
```

- static設定

```py
# STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR, "static"),
)

# Media files
# MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# EDIA_URL = '/media/'

# staticファイルの参照先
STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
# mediaファイル保存先
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
```
- **STATICFILES_DIRS** : プロジェクトのアプリで使うstaticファイルを格納している、サーバ内の場所を指定する (今回は project_name/static)
- **STATICFILES_STORAGE** : CSSなどを参照するstaticファイルの保管先を指定
- **DEFAULT_FILE_STORAGE** : 投稿機能によってアップロードされた写真の保管先を指定
- 'storages.backends.s3boto3.S3Boto3Storage' を指定することで↓のS3設定を参照するようになる
- 元のmedia設定は不要なのでコメントアウトか、消しておく

- S3設定

```py
# アクセスキーID
AWS_ACCESS_KEY_ID = 'AKIA**************'
# シークレットアクセスキー
AWS_SECRET_ACCESS_KEY = '*************************'
# バケット名
AWS_STORAGE_BUCKET_NAME = 'bucket_name'
# 保存先URL
STATIC_URL = 'https://%s.s3.ap-northeast-1.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME
AWS_LOCATION = 'static'
```
- **AWS_ACCESS_KEY_ID , AWS_SECRET_ACCESS_KEY** : IAM作成時にDLしてきた認証情報を入力する
- **AWS_STORAGE_BUCKET_NAME** : さっき作ったバケット名を入力する
- **STATIC_URL** : バケットのURLを入力する
  - 適当にファイルおいて、AWSコンソールでそのファイルの詳細の「オブジェクトURL」を見たらわかる
- **AWS_LOCATION** : static関係のファイルをコピーする際、バケット内にディレクトリを作りたい場合指定
  - 今回は bucket_name/static/静的ファイル という構造にしたいので、'static' を入力

必須項目は以上。 他にもいろいろ指定できるけど、とりあえず↑だけでよさそう

## collectstatic 実行

このままだとdjangoがstaticを認識できないので、collectstaticをいうコマンドを使ってstaticファイルを同期する

```sh
プロジェクトのディレクトリ上で
$ python manage.py collectstatic

You have requested to collect static files at the destination
location as specified in your settings.

This will overwrite existing files!
Are you sure you want to do this?

Type 'yes' to continue, or 'no' to cancel: yes ← yesを入力

129 static files copied.
```

collectstaticがうまくいって、S3のバケットに static ディレクトリ、その中に admin , css ディレクトリができていればOK！

## 画像のS3アップロード確認

runnserver して 実際に画像を投稿してみる
    →S3の static ディレクトリの中に photos/ が作成されて、その中に実際の画像ファイルが保存されていればOK！

画像保存先のディレクトリ名はモデルで指定↓

```py:models.py
class Photo(models.Model):
    title = models.CharField(max_length=150)
    comment = models.TextField(blank=True)
    image = models.ImageField(upload_to='photos') # ←これ
    category = models.ForeignKey(Category, on_delete=models.PROTECT)
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    created_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title
```