Django + Heroku + WhiteNoise + AWS S3 によるWebアプリのデプロイ

#はじめに
Djangoで作成したWebアプリをHerokuにデプロイする方法や、静的ファイルをWhiteNoiseで管理する方法、画像ストレージをAWSと対応させる方法などをそれぞれ断片的に解説した記事はありますが、一連の流れをまとめた記事があまり無かったため、本記事でまとめて解説しようと思います。
#要旨
本記事では、Djangoで作成したWebアプリにおいて、静的ファイルをWhiteNoiseで管理し、画像や動画などのメディアファイルはAWS S3で管理して、最後にHerokuでデプロイするまでの流れを解説します。
#準備
1. Djangoを用いたWebアプリを作成
2. [AWSのアカウント作成](https://aws.amazon.com/jp/register-flow/)
3. [Herokuのアカウント作成](https://signup.heroku.com)

#Webアプリの開発環境
###環境
Python == 3.6.7
Django == 1.11.15

###サンプルアプリ
以下のようなアプリ構成で進めていきます。

```
sample
    ├──Procfile
    ├── blog
    │   ├── __init__.py
    │   ├── admin.py
    │   ├── apps.py
    │   ├── migrations
    │   │   ├── __init__.py
    │   │   └── 0001_initial.py
    │   ├── models.py
    │   ├── tests.py
    │   └── views.py
    ├── manage.py
    ├── requirements.txt
    ├── config
    │   ├── __init__.py
    │   ├── settings.py
    │   ├── urls.py
    │   └── wsgi.py
    ├── runtime.txt
    ├── templates
    ├── static			
    └── myvenv
```
ただし、myvenvはvirtualenv(仮想環境)です。
また、マイグレーションファイルは作成済みとします。(```python manage.py makemigrations```)

最初に、以下のコマンドで仮想環境を起動しておきます。

```
$ source myvenv/bin/activate
```

#各種設定
まずは、必要なパッケージをインストールします。

```terminal:
(myvenv)$ pip install gunicorn 
(myvenv)$ pip install dj-database-url
(myvenv)$ pip install psycopg2
(myvenv)$ pip install psycopg2-binary
```

Prockfileには以下のように、wsgi.pyが保存されている場所を指定します。

```:Procfile
web: gunicorn config.wsgi
```

続いて、runtime.txtには以下のように記述します。(heroku上で動かすpythonのバージョンを指定)
Pythonの最新版を指定しないと、herokuから警告が出ます。

```:runtime.txt
python-3.6.7
```

続いては、wsgi.pyの編集です。
初期状態では、該当部分が```"プロジェクト名".settings```となっていますが、今回はconfigの下にsettings.pyがあるため、下記のように変更します。

```python3:wsgi.py
import os
from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings") #変更

application = get_wsgi_application()
```
HerokuではSQLiteが動作しないため、setting.pyに以下のコードを追加します。

```python3:settings.py
import dj_database_url
db_from_env = dj_database_url.config()
DATABASES['default'].update(db_from_env)
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')

DEBUG = False
ALLOWED_HOSTS = ['*']
```

#静的ファイル
静的ファイルを管理するために、WhiteNoiseをインストールします。


```terminal:
(myvenv)$ pip install whitenoise
```
続いては、settings.pyを編集していきます。
MIDDLEWAREに`whitenoise.middleware.WhiteNoiseMiddleware`を追加します。

```python3:settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware', #追加
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
```

さらにsettings.pyに以下のコードを追加します。

```python3:settings.py
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
STATIC_ROOT = os.path.join(BASE_DIR, 'static_root')
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
```

最後に以下のコマンドを実行します。
すると、static_rootというフォルダが生成されます。
静的ファイルの準備はこれで完了です。

```terminal:
(myvenv)$ python manage.py collectstatic
```

#メディアファイル
###AWSの各種設定
メディアファイルをAWS S3で管理するには、AWSのアカウント作成、AWS側での各種設定を行っておく必要があります。
今回の目的であるメディアファイルの管理でのAWS側の設定は下記の記事がわかりやすいです。
[Django、静的ファイル、メディアファイルをAWS S3で管理](https://narito.ninja/detail/27/)
上記の記事では、静的ファイルとメディアファイルを共に、AWS S3で管理していますが、今回は静的ファイルはWhite Noiseで管理するため、メディアファイルのみをAWS S3で管理します。
また、AWS側の設定に関しては、以下の記事も参考になると思います。
[[Django] ファイルアップロード機能の使い方 [クラウドストレージ編]](https://qiita.com/okoppe8/items/7ecbc92187171926238f)

###パッケージ，コードの追加
AWS側の設定を終えたら、必要なパッケージをインストールします。

```terminal:
(myvenv)$ pip install django-storages
(myvenv)$ pip install boto3
```

続いて、settings.pyの```INSTALLED_APPS```に```'stoages'```を追加します。

```python3:settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'blog',
    'storages', #追加
    ]
```
さらに、settings.pyに以下のコードを追加します。
アクセスキーなどはAWS側の設定の時に取得できます。
```AWS_LOCATION```でメディアファイルの保存先も指定します。

```python3:settings.py
AWS_ACCESS_KEY_ID = 'アクセスキー'
AWS_SECRET_ACCESS_KEY = 'シークレットアクセスキー'
AWS_STORAGE_BUCKET_NAME = '<作成したバケットの名前>'
AWS_S3_CUSTOM_DOMAIN = '%s.s3.amazonaws.com' % AWS_STORAGE_BUCKET_NAME
AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400', 
}

AWS_LOCATION = 'media'
DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'
MEDIA_URL = "https://%s/%s/" % (AWS_S3_CUSTOM_DOMAIN, AWS_LOCATION)
```

これで、Webアプリでアップロードされたメディアファイルは、AWS S3の作成したバケットの中の```media/```に格納されていきます。

#Herokuへデプロイ
下記のコマンドによりheroku toolbeltをインストールして、herokuのコマンドを打てるようにします。

```terminal:
brew install heroku/brew/heroic
```

続いて、これまでインストールしたパッケージをrequirements.txtに書き込みます。

```
(myvenv)$ pip freeze > requirements.txt
```

次に、以下のgitコマンドを入力して、これまで作成したファイルをコミットしていきます。

```terminal:
(myvenv)$ git init           
(myvenv)$ git add -A .
(myvenv)$ git commit -m 'first commit'                            
 ```
最後に、以下のherokuコマンドを順に打ち込んでいきます。
```heroku open```で作成したアプリが開けたら、無事成功です。

 ```terminal:
(myvenv)$ heroku login
(myvenv)$ heroku create
(myvenv)$ heroku config:set DISABLE_COLLECTSTATIC=1
(myvenv)$ git push heroku master
(myvenv)$ heroku ps:scale web=1
(myvenv)$ heroku run python manage.py migrate
(myvenv)$ heroku open
```

#参考
[Django、静的ファイル、メディアファイルをAWS S3で管理](https://narito.ninja/detail/27/)
[[Django] ファイルアップロード機能の使い方 [クラウドストレージ編]](https://qiita.com/okoppe8/items/7ecbc92187171926238f)
[DjangoアプリをHerokuにデプロイする時のエラー対処 whitenoise編](https://qiita.com/ymhr1121/items/344c4eb300ab9972d0c2)
[DjangoアプリをHerokuにデプロイする[その1]](https://qiita.com/Shitimi_613/items/6627d0ce042d38b86893)
[[Django] Heroku デプロイ方法 2018年版](https://qiita.com/okoppe8/items/76cdb202eb15aab566d1)
[Django Girls Tutorial](https://djangogirlsjapan.gitbook.io/workshop_tutorialjp)