Django staticファイル まとめ

#この記事について

Djangoのstaticファイル（静的ファイル）の設定がわかりずらかったので整理してみました。Djangoのバージョンは1.11。わりとバージョンによって利用方法に微妙な違いがあるようです。

#staticファイル(静的ファイル)の概要

Djangoでは、アプリケーションで処理を行わない静的ファイル（例:js,css,image等）をstaticファイルと呼びます。（他のフレームワークでassetsファイルと呼ばれるものと同じです）

Djangoに限らず、一般にWebアプリケーションは静的ファイルの配置について次のような指針があります。

- 静的ファイルはWebサーバのドキュメントルート下に配置する。
- ソースコード、実行ファイルはホームディレクトリに配置する。

これにより、静的ファイルはセキィリティは低いがアプリケーションを介さないため応答速度が早く、アプリケーションは余計な処理から解放されるので処理効率が上がるという効果があります。 

*※ これは古いもう認識のようでして、静的ファイルに対するリクエストも一旦アプリケーション側で受け、キャッシュや圧縮技術を駆使してWebサーバよりも高速に静的ファイルの配信を行う方法があります。Herokuにデプロイするときに使うwhitenoiseがそれです。いずれにしても静的ファイルの配信機能を、アプリケーションのメインロジックの管轄から外して高速化を図るという面では同じです。*


この指針は本番環境のためのもので、開発時は静的ファイルとプログラムを一緒の場所にまとめておく方が合理的というジレンマがあります。Djangoではそのギャップを埋めるため、以下の枠組みを決めています。
  
1. staticファイルのディレクトリはアプリケーションのディレクトリごとに作る

2. アプリケーションごとのstaticディレクトリを指定ディレクトリに集約コピーする機能（collectstatic）を提供する。本番環境デプロイ時に実行し、集約されたディレクトリからstaticファイルを配信する。

3. デバッグ時に起動するWebサーバは、本番構成のstaticファイルへのリクエストを開発構成に読み替えて応答する。開発と本番は設定「DEBUG」で切り替える。
  
この枠組みにより、個々のアプリケーションをモジュールとして簡単にプロジェクトに追加・削除できるようになっています。

#利用方法

##設定ファイル

設定ファイル(setting.py)内で、staticファイルに関する設定項目は以下の５つです。

- STATIC_URL
- STATIC_ROOT
- STATICFILES_DIRS
- STATICFILES_STORAGE
- STATICFILES_FINDERS

###STATIC_URL

配信用のディレクトリ名
外部から見ると、プロジェクトのURL + 配信用のURL という形式となります。
デフォルトの「'/static/'」から変更することはあまり無いでしょう。

###STATIC_ROOT
集約用の管理コマンド「manage.py collectstatic」を実行した時に、staticファイルがコピーされるディレクトリのパス。実際の指定内容は後述のデプロイを参照

###STATICFILES_DIRS (Prefixes (optional))
各アプリケーションのstatic以外に配信するディレクトリがある場合に追加する。

-  例：全アプリケーションが共通で使う静的ファイルがある場合、プロジェクトディレクトリ直下にstaticディレクトリを作成し、STATICFILES_DIRSにパスを追加する。オプションでSTATIC_URLとは異なる名前で公開できます。

###STATICFILES_STORAGE

Amazon S3 や CDNを利用する際に、利用するサービスのAPIを指定します。
設定がある場合、collectstaticの実行で自動的にリモートへのアップロードが行われるようです。

###STATICFILES_FINDERS

※現状、STATICFILES_STORAGEを指定する時のみ指定が必要です。デフォルト値に追加する形で指定しなくてはなりません。


あと、収集機能自体は組み込みアプリケーションの「django.contrib.staticfiles」で動作するのでINSTALLED_APPSから削除すると動かなくなります。

##staticディレクトリの作成

各アプリケーションディレクトリの直下にstaticという名前でディレクトリを作成し、さらにstaticディレクトリの直下にアプリケーション名でディレクトリを作成する。
※ 各アプリケーションのstaticディレクトリが一つのディレクトリに集約されるので、名前が衝突しないようするための工夫です。
  
その下にjs,css等のディレクトリを好みで作成してファイルを配置していきます。
  
## テンプレートへの記述

記述方法は２つあります。

###１．現行の推奨方法

テンプレートで{% load static %}を定義してから{static}タグを使う

```html:template.html
{% load static %}
<img src="{% static "myapp/image.jpg" %}" alt="My image"/>
```
※1.9以前のバージョンでは{% load staticfiles %}を使っていました。

###２．context_processors.staticを使う方法

まず設定ファイルの変更が必要

```py:setting.py
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'django.template.context_processors.static', #ここ追加
            ],
        },
    },
]
```
そしてテンプレートを修正する

```html:template.html
<img src="{{ STATIC_URL }}myapp/example.jpg" alt="My image"/>
```


参考：[{{ STATIC_URL }}を読み込まない時の対応方法-Django 1.8.1]
(http://kazsoga.com/static-tag-django-1-8-1/)

２番の方法の方が楽に思えるのですが、クラウドやCDNで配信するしている場合トラブルが起きる場合があるのであまり推奨されていない雰囲気です。

#### 参考
[Stop Using STATIC_URL in Templates - Mastering Django Static Files](
http://staticfiles.productiondjango.com/blog/stop-using-static-url-in-templates/)
[DjangoテンプレートのstaticタグとSTATIC_URLコンテキスト変数の使い分け](http://tokibito.hatenablog.com/entry/20150721/1437481636)

##ファイルにアクセスできないときは

開発時、staticディレクトリを作ってファイルを置いたのになぜかアクセスできない場合があります。そのような時のために、対象のstaticファイルが今有効なのかどうかを確認するコマンド「manage.py findstatic」が用意されています。

```bat:
(env) C:\Sandbox\mypj>manage.py help findstatic
usage: manage.py findstatic [-h] [--version] [-v {0,1,2,3}]
                            [--settings SETTINGS] [--pythonpath PYTHONPATH]
                            [--traceback] [--no-color] [--first]
                            staticfile [staticfile ...]
```

「.」で公開対象のstaticディレクトリの一覧が表示されます

```bat:
(env) C:\Sandbox\mypj>manage.py findstatic .
Found '.' here:
  C:\Sandbox\mypj\static
  c:\Sandbox\mypj\env\lib\site-packages\django\contrib\admin\static
  C:\Sandbox\mypj\myapp\static
```

パス名を与えると該当するファイルの一覧が表示されます

```bat
(env) C:\Sandbox\mypj>manage.py findstatic myapp/a.png
Found 'myapp/a.png' here:
  C:\Sandbox\mypj\myapp\static\myapp\a.png
manage.py findstatic ファイルパス
```

**引数はファイル名ではなく、staticディレクトリ以下のパス名であることに注意してください。**
複数のファイルがヒットした場合、最初にヒットしたファイルが有効になります（firstオプションで確認可）。
また、適当な一時ディレクトリを作成し、collectstaticを実行してみるのも有効な確認方法です。

##本番環境へのデプロイ

本番環境へのデプロイ時はcollectstaticを実行します。

```bat
(env) C:\Sandbox\mypj>manage.py collectstatic
Copying 'C:\Sandbox\mypj\static\b.PNG'
Copying 'c:\Sandbox\mypj\env\lib\site-packages\django\contrib\admin\static\admin\css\base.css'
Copying 'c:\Sandbox\mypj\env\lib\site-packages\django\contrib\admin\static\admin\css\changelists.css'
Copying 'c:\Sandbox\mypj\env\lib\site-packages\django\contrib\admin\static\admin\css\dashboard.css'
略
64 static files copied to 'C:\Sandbox\mypj\dummy'.
```
簡単に言えば、ルールに従ってファイルコピーし、読み取り専用プロパティを設定しています。実行せずにリストアップするだけのdryrunオプションもあります。

STATIC_ROOTに何を設定し、いつどこでcollectstaticを実行するかは本番環境の構成とデプロイ戦略によって変わってくるようです。ちょっと調べた感じだと以下の通りです。

collectstatic実行例：

- whitenoise利用時：

STATIC_ROOTは相対パスでプロジェクト内のディレクトリを指定。
本番環境でcollectstaticを実行後、アプリ起動時にプロジェクト内ディレクトリをwhitenoiseが読み込み配信します。
Herokuでは git push 時にcollectstaticを自動実行するように整備し、ユーザーがcollectstaticを実行する手間を省いてくれています。

- whitenoise非利用時(手動設定)

STATIC_ROOTは絶対パスを指定。作業手順によっては相対パスになることも。
本番環境でcollectstaticを実行するか、ローカルに出力後rsync等でに本番環境にコピーする。別途Webサーバの設定でstaticディレクトリを公開する必要があります。

とりあえず公式サイトみてスクリプト等を研究してください。
[Django公式 静的ファイルのデプロイ](https://docs.djangoproject.com/ja/1.11/howto/static-files/deployment/)


- クラウドやCDNのAPIを使う場合

  STATICFILES_STORAGEとSTATICFILES_FINDERSを適切に設定し、collectstaticを実行すると、API経由で適切にアップロードされるようです。細かい指定方法は未調査。

また社内LANなどでセキュリティを気にしなくてよいときは、collectstaticを行わず開発環境と同様に使う方法もあります。「manage.py runserver --insecure」について調べてください。

[AWS / PHP / Python ちょいメモ Django での static files の扱い方まとめ （追加）](http://hideharaaws.hatenablog.com/entry/2017/02/23/145828)

#参考

[静的ファイルの公開方法 - Django 1.4 documentation](http://docs.djangoproject.jp/en/latest/howto/static-files.html)
[Django 静的ファイル (static) 関係の設定まとめ](https://qiita.com/aion/items/add0c983150a2a1e100d)
[AWS / PHP / Python ちょいメモ Django での static files の扱い方まとめ]
(http://hideharaaws.hatenablog.com/entry/2014/12/12/230825)
[undefined!! - [Django]staticfilesの使い方](http://hyokonia.tumblr.com/post/7036722697/djangostaticfiles%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9)