Django staticファイルとAWS S3

[Django staticファイルとAWS S3 - Qiita](https://qiita.com/sand/items/2eae781e2b904e0e67b9)
[Django mediaファイルとAWS S3 - Qiita](https://qiita.com/sand/items/b897aa47c304b7fbdcb5)


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



**本記事ではDjangoのstaticファイルの扱いと、AWS S3へのdeployについてまとめます。**


staticファイルの配信は、開発環境と本番環境では異なります。開発環境（DEBUG=True）ではDjangoのデフォルトのINSTALLED_APPSである**django.contrib.staticfiles**を使います。本番環境では、今回は**AWS S3**を使います。（通常は、AWS S3を使うよりはNginxとかを使う場合が多いかもです。）


* 開発環境(DEBUG=True)：各アプリケーションのstaticディレクトリ(django.contrib.staticfiles)
* 本番環境(DEBUG=False) ：AWS S3 (django-storages)


[静的ファイル (画像、JavaScript、CSS など) を管理する - 公式ドキュメント](https://docs.djangoproject.com/ja/2.2/howto/static-files/#serving-files-uploaded-by-a-user-during-development)
[Django staticファイル まとめ](https://qiita.com/okoppe8/items/38688fa9259f261c9440)


#１．テストプロジェクト作成

テスト環境を作ります。

##１－１．プロジェクト作成

まずはDjangoのプロジェクトを作成します。

```
python -m venv statictest
source statictest/bin/activate
cd statictest
pip freeze   # インストール済みは空であることを確認
pip install django  # Djangoをインストール
django-admin startproject statictest .
```

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

```python:statictest/settings.py
---
ALLOWED_HOSTS = ["www.mypress.jp"]
---
```

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

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

##１－２．テストアプリ作成

test1とtest2というアプリケーションを作成します。

```
python manage.py startapp test1
python manage.py startapp test2
```

アプリを追加します。

```python:statictest/settings.py
---
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'test1.apps.Test1Config',     # 追加
    'test2.apps.Test2Config',     # 追加
]
---
```


URLパスを追加します。

```python:statictest/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
    path('test1/', include('test1.urls')),
    path('test2/', include('test2.urls')),
]
```


```python:test1/urls.py
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('', TemplateView.as_view(template_name="index.html")),
]
```


```python:test2/urls.py
from django.urls import path
from django.views.generic import TemplateView

urlpatterns = [
    path('', TemplateView.as_view(template_name="index.html")),
]
```




#２． 開発環境 - DEBUG=True


##２－１．アプリ単位でのstatic設定（開発環境）

DEBUG=Trueで**django.contrib.staticfiles**を使います。この場合、staticファイルは、各アプリケーションディレクトリ直下のstaticディレクトリに置くことになります。 **staticファイルは、runserver実行時に自動的に各staticディレクトリから配信されます。**


まず２つのアプリケーション(test1,test2)の下に、staticとtemplatesディレクトリを作成し、さらにそれぞれの下にtest1、とtest2というディレクトリを作成します。

```
mkdir -p ./test1/{static,templates}/test1
mkdir -p ./test2/{static,templates}/test2
```

test1アプリケーションに関して言えば、staticファイルはtest1/static/test1/の下に置きます。普通は、必要に応じてさらにcss、js、等のディレクトリを作成します。

今回はstaticファイルはcssに関して説明するので、以下のようにサブディレクトリを作成します。

```
mkdir ./test1/static/test1/css
mkdir ./test2/static/test2/css
```


```html:test1/templates/test1/index.html
{% load static %}
<html>
  <head>
    <title>Django blog</title>
    <link rel="stylesheet" href="{% static 'test1/css/base.css' %}">
  </head>
  <body>
    <header>
      <h1>test1</h1>
    </header>
    <div>
      test page!
    </div>
  </body>
</html>
```


```html:test1/templates/test2/index.html
{% load static %}
<html>
  <head>
    <title>Django blog</title>
    <link rel="stylesheet" href="{% static 'test2/css/base.css' %}">
  </head>
  <body>
    <header>
      <h1>test1</h1>
    </header>
    <div>
      test page!
    </div>
  </body>
</html>
```


```css:test1/static/test1/css/base.css
h1 {
  color: red;
}
```


```css:test2/static/test2/css/base.css
h1 {
  color: blue;
}
```

以上の設定を行ったら、プロジェクトを起動してアクセスしてみます。ブラウザでアクセスしてみます。思い通りの結果です。

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/16f83243-0a91-b7e2-d266-0fbefa0fd5b0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/ba825737-a157-c1b9-58de-dc4da910034c.png)




##２－２．共通のstatic設定


test1とtest2で共通に使うstaticファイルを考えます。プロジェクトディレクトリ直下にstaticディレクトリをつくり、そこに共通のcssファイルを置くことにします。

```
mkdir -p ./static/css
```

上で作ったstaticディレクトリが有効になるように**STATICFILES_DIRS**を設定します。

```python:statictest/settings.py
---
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]  # 追加
```

以下のような共通のbase.cssを定義します。前とはディレクトリ名が違うことに注意してください。

```css:static/css/base.css
div {
  color: green;
}
```

テンプレートで、追加したbase.cssを参照するようにします。test1とtest2の両方を変更します。


```html:test1/templates/test1/index.html
{% load static %}
<html>
  <head>
    <title>Static test1</title>
    <link rel="stylesheet" href="{% static 'test1/css/base.css' %}">
    <link rel="stylesheet" href="{% static 'css/base.css' %}">
  </head>
  <body>
    <header>
      <h1>test1</h1>
    </header>
    <div>
      test page!
    </div>
  </body>
</html>
```

以上の設定を行ったら、プロジェクトを起動してアクセスしてみます。ブラウザでアクセスしてみます。思い通りの結果です。「test page!」が緑色になっています。

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

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/3d751491-b8fa-a4af-487b-ba58880288fa.png)



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/63282c69-d75d-f16b-6792-ad4b46852ddf.png)



##２－３．DEBUG=False にしてみる

ここまでの実験はstatictest/settings.pyのDEBUGをTrueにした場合の結果です。DEBUGをFalseにするとstaticファイル(base.css)が一つも読み込まれなくなります。**django.contrib.staticfiles**がstaticファイルの配信を止めてしまうからです。それでは本番環境(Debug=False)ではどうするかが以下の記事になります。



#３．本番環境（AWS S3）- DEBUG=False

DEBUG=Falseの場合には、staticファイルの配信には**django.contrib.staticfiles**を使うことができません。セキュリティや効率の問題があるので避けるべきだとガイドされています。今回はAWS S3へ全てのstaticファイルをDeployすることで、AWS S3に配信させることにします。

##３－１．AWS IAMとS3の作成

programmatic accessのIAMユーザを作成し、グループの権限に**AmazonS3FullAccess**を指定します。その他はデフォルトで作成し、Access key ID と Secret access keyをメモっておきます。


AWS S3のバケットを作ります。オプションは全てデフォルト。staticファイルは匿名ユーザに公開しますので、以下のような**バケットポリシー**を付与します。[「バケットポリシーの例 - Amazon公式」](https://docs.aws.amazon.com/ja_jp/AmazonS3/latest/dev/example-bucket-policies.html#example-bucket-policies-use-case-2)

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



##３－２．static配信にAWS S3を利用するための設定

PythonからAWSへアクセスするためには**boto3**を使用します。**django-storages**はクラウドストレージを、staticファイルやアップロード画像ファイル（mediaファイル）の保存に使うためのものです。ここではstaticファイル(cssファイル）のみの使用例を示します。画像のアップロードなどmediaファイルについては割愛させていただきますが、markdownXなどと併用したときはとても便利に使えます。いずれ**django-storages + markdownX**に関する記事を掲載できればと思いますが、今回は触れません。


```
pip install boto3
pip install django-storages
```

アプリケーションを追加します。

```python:statictest/settings.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'test1.apps.Test1Config',
    'test2.apps.Test2Config',
    'storages',               # 追加
]
```

さらにdjango-storagesとAWSの設定をsettings.pyに追加します。先ほどメモしたAccess key ID と Secret access keyやバケット名などを設定します。

```python:statictest/settings.py
# STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]

AWS_ACCESS_KEY_ID = 'AKIAXXXXXXXXXXXXXX'
AWS_SECRET_ACCESS_KEY = 'YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY'
AWS_STORAGE_BUCKET_NAME = 'static-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'
```


以上の設定をした後に、以下のコマンドを発行します。

```
python manage.py collectstatic
```


このコマンドで以下のようにAWS S3に全てのstaticファイルがdeployされます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/61915/d5676562-8041-9a04-9aec-7d4d9aa01517.png)


以上の設定を行ったら、プロジェクトを起動してアクセスしてみます。ブラウザでアクセスしてみます。思い通りの結果です。

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

index.htmlを確認してみます。

レンダー前のテンプレートは以下のようなものでした。

```html:test1/templates/test1/index.html(render前)
{% load static %}
<html>
  <head>
    <title>Static test1</title>
    <link rel="stylesheet" href="{% static 'test1/css/base.css' %}">
    <link rel="stylesheet" href="{% static 'css/base.css' %}">
  </head>
  <body>
    <header>
      <h1>test1</h1>
    </header>
    <div>
      test page!
    </div>
  </body>
</html>
```

レンダー後にブラウザに表示されたHtmlは以下のようになります。base.cssのurlがamazonのものになっていることに注意してください。

```html:test1/templates/test1/index.html(render後)
<html>
  <head>
    <title>Static test1</title>
    <link rel="stylesheet" href="https://static-test-reiwa.s3.amazonaws.com/static/test1/css/base.css">
    <link rel="stylesheet" href="https://static-test-reiwa.s3.amazonaws.com/static/css/base.css">
  </head>
  <body>
    <header>
      <h1>test1</h1>
    </header>
    <div>
      test page!
    </div>
  </body>
</html>
```


今回は以上です。