きっかけ
やっぱりteratailの「staticフォルダにアクセスしようとしてエラー(一部省略)という質問がきっかけでした。と言ってもdockerで実現という点での興味だったので、深く調査するつもりはなかったのですが、とあるデータをグラフ表示することになり、どうせならDjangoをベースにしてみようと思ったことが調査の始まりでした。グラフ表示には、JavaScriptベースと考え、昔PHP/Twigベースで作ったものがあり、それを拡張しようと思ったのですが、せっかくなのでと。
はじめに
いつもならば、まずはdockerとなるのですが、今回はわたしもDjango初心者で、必要最小限の操作で何がどうなるのかを見極めるため、まずは素の環境(mac High Sierra環境)からはじめます。実は最初にdockerを使っていたのですが、以前入れていたことと、一番人気のイメージが古いバージョンだったので、乗り換えたことと、ドキュメントがバージョンにより記述の仕方が色々なのと、直接弄らないと問題の切り分けが難しそうと感じたことなど、結局一歩一歩確認した方が良かったので、ここでもそれをなぞった形で進めたいと思います。
とりあえず、Djangoを入れる
pythonはpyenvとかで入っているものとして、
$ pip install Django
$ pip list | grep Django
Django 2.1.2
で入れます。
プロジェクト作成
$ mkdir workspace
$ cd workspace
$ django-admin.py startproject workspace `pwd`
workspace$ ls
manage.py* workspace/
workspace$ ls workspace/
__init__.py settings.py urls.py wsgi.py
さて、これでmanage.pyが自動生成され、サーバが起動できる様になります。ただ、もう少し弄り、静的なファイルを表示するところまで見てみましょう。
その前に、ちょっとだけ状況を確認してみます。
workspace/urls.py
自動生成されたurls.pyですが、これが大元のルーティングを制御することになります。ただ、初期状態では、以下の内容のみです。
from django.contrib import admin
from django.urls import path
urlpatterns = [
path('admin/', admin.site.urls),
]
urlpatternsがリストでその中でルーティング情報を記載することになりますが、path関数で定義しています。これはDjango情報を見ると、Djangoのバージョンにより、若干記載方法が変わっている様です。
さて、この記載により、サーバを起動すると、http://localhost:8000/admin
により管理ページを表示することが可能になっています。
staticページ
初期状態
また、初期状態で静的なファイルを置く場所は定義されているのでしょうか。それを確認するために非常に役立つコマンド(サブコマンド)が提供されています。manage.py findstatic がそれです。結果を以下に示します。
workspace$ python manage.py findstatic .
Found '.' here:
/Users/.pyenv/versions/3.6.0/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/contrib/admin/static
という感じで、adminページのためと思われる場所が設定されている様です。実際にサーバを起動して表示されたページの内容を見ると、/static/admin/css/base.cssを参照しており、 ~/.pyenv/versions/3.6.0/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/contrib/admin/static/admin/css/のディレクトリに当該ファイルが存在している。
staticの公開(DEBUG=True)
色々な情報があり、非常に混乱したのがこの部分で、前述のteratailの質問もこの点が混乱の原因であろうと思われた。実際に確認して見ると、非常に簡単な設定のみである(ただし、DEBUG=Trueの場合)
workspace/settings.py
このファイルに以下の記述を追加するだけである。
STATICFILES_DIRS = [
os.path.join(BASE_DIR, 'static'),
]
これだけだが、試しにこれだけで先ほどのサブコマンドでどうなったか試してみましょう。
workspace$ python manage.py findstatic .
Found '.' here:
/Users/.pyenv/versions/3.6.0/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/contrib/admin/static
残念ながら、先ほどと同じ結果です。ここで焦ってはいけません。実際のディレクトリが存在しないと、ここに表示されないのです。もう少しみてみましょう。実は上記コマンドにはオプションがあります。
workspace$ python manage.py findstatic -v 3 .
Found '.' here:
/Users/.pyenv/versions/3.6.0/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/contrib/admin/static
Looking in the following locations:
/Users/dev/workspace/static
/Users/.pyenv/versions/3.6.0/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/contrib/admin/static
-vオプションでもう少し詳細な情報を表示することができます。そして、上記の結果から、設定内容は反映している様ですが、なぜFoundの中に出てきてくれないのでしょうか。答えは簡単で、実際のディレクトリが存在しないからです。
workspace$ mkdir static
workspace$ python manage.py findstatic .
Found '.' here:
/Users/dev/workspace/static
/Users/.pyenv/versions/3.6.0/Python.framework/Versions/3.6/lib/python3.6/site-packages/django/contrib/admin/static
という結果となり、無事所望のディレクトリを公開することができた様です。
それでは確認してみましょう。
workspace$ mkdir -p static/images
workspace$ cp xxx.jpg static/images/
workspace$ python manage.py runserver
Performing system checks...
System check identified no issues (0 silenced).
You have 15 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
October 25, 2018 - 14:42:53
Django version 2.1.2, using settings 'workspace.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
migrateしろと言われますが、現時点では無視し、この状態で、ブラウザから 「http://localhost:8000/static/images/xxx.jpg」 にアクセスしてみましょう。
無事画像が表示されていると思います。
ちなみに、設定ファイルの修正に失敗した場合は、runserver時にエラーが表示されるはずです。
さて、静的なファイルを公開するエントリーポイントを設定することができましたが、あくまでDEBUG=True設定時、すなわちデバッグ時に利用する場合のみとなります。その点注意してください。元気があればDEBUG=Falseの時の設定も提示しようと思います。
webサイトの公開設定
さて静的なファイルのみでは所望のグラフ生成ができないので、webサイトの公開まで行いましょう。
こちらもバージョンによったり、チュートリアルがちょっとわかりにくいことなどで混乱しやすいかも。と思ったので、これもちょっと細かめにしますが、わかってしまえば、それほど難しい設定・手順ではありません。
サブコマンド(startapp)で初期化
以下のコマンドにより、site1(これは自由に設定可能です)を公開するイメージとなります。「http://localhost:8000/site1」 でアクセスすることになります。
workspace$ python manage.py startapp site1
workspace$ ls
db.sqlite3 manage.py* site1/ static/ workspace/
workspace$ ls site1
__init__.py admin.py apps.py migrations/ models.py tests.py views.py
*ちなみに、db.sqlite3は、サーバを起動すると自動的に作成されるので、このコマンドにより生成されたわけではありません。自動生成されるのは、site1ディレクトリとなります。
ルーティング
「http://localhost:8000/site1/」 以下に各種エントリーポイントを用意する。そのためにアプリ(site1)側にルーティングを委譲することで役割分担がわかりやすくなる。それを実現するのがincludeで、プロジェクトworkspaceのurls.pyを修正し、site1に委譲する様にし、site1側で固有のルーティングを記載する。
workspace/urls.py
プロジェクト側のルーティング部の修正は以下の通り、元のコードは前述した通り、admin部分のみである。これにsite1部分を追加している。
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('site1/', include('site1.urls')),
]
site1/urls.py
取り急ぎ、単に「http://localhost:8000/site1/」 でアクセスして反応できる様にしてみましょう。
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index'),
]
これはほぼ公式tutorialsの通りです。
view, およびtemplate
view
元のview.pyは、以下の様に空っぽです。ただ、renderを使えと言わんばかりのimport。。
from django.shortcuts import render
# Create your views here.
これを以下の様に修正する。render、templateを使わずもっとシンプルに記載することもできるので、本来の趣旨とは外れるとは思いますが、ここは意図を汲んで(単に面倒になってきたということも。。)、templateを使うことを前提に進めます。
from django.shortcuts import render
def index(request):
return render(request, 'site1/index.html', {'title': "site1"})
site/urls.pyにあるview.indexが上記view.pyにあるindex関数をさし示すことになります。
そのため、site1以下のパスが何も指定されない場合、view.pyにあるindex関数が呼ばれるということになります。
template
template自体は自動生成されません。自分で作成する必要があります。
またtemplateファイルを配置する場所にも注意が必要です。
ただ、この辺りは公式tutorialsそのままですので、淡々と進めます。
workspace$ mkdir -p site1/templates/site1
テンプレートを配置する場所は上記の通り、イマイチですがフレームワークの方針でしょうがないですね。
もちろん、view.pyのindex関数でrenderの第二引数を'site1/index.html'から'index.html'にすれば、site1/templates/index.html でも良いのですが、副作用がありそうなので、自重しておきます。
さて、テンプレート例を以下に示します。
<h1>{{ title }} </h1>
hello world
<br />
{% load static %}
<img src="{% static "images/xxx.jpg" %}" width=360px alt="My image"/>
前述のindex関数でrenderした際の3つ目の引数をdictionaryにしていますが、これがContextとなり、template側で参照可能となります。{{ title }} がそれに当たります。そして静的な場所に置いたファイルの参照方法がhello world 以下に記述しています。
settings.py
さて、ここまでで一通りの準備ができたので、早速実行としても実は動作しません。
「TemplateDoesNotExist at /site1/」というエラーが発生して表示できません。前述の公式tutorialsには、APP_DIRSがTrueであれば、アプリにあるtemplatesディレクトリを参照してくれると読めたため、何をしなくても良いと勘違いしました。実はtutorialの2でmigrationsの説明があり、その中でINSTALLED_APPSに追記するという内容が含まれていたため、どのような設定をすると所望のテンプレートを読んでくれるのか理解するのに時間がかかりました。
結局、以下の通り、workspace/settings.pyを修正することでロードしてくれることを確認しております。
INSTALLED_APPS = [
'site1.apps.Site1Config',
'django.contrib.admin',
'...',
以下の手順で確認します。
workspace$ python manage.py check
System check identified no issues (0 silenced).
workspace$ python manage.py runserver
ブラウザで以下にアクセスし、所望の表示ができればOKです。
http://localhost:8000/site1
docker
そうそう、dockerですが、一応オフィシャルのイメージがあります。ただ、先に述べた通り、若干古いバージョンになっています。その関係でurlsで利用するpathの代わりにurlを利用する必要がある点以外変更なく通ります。
それ以外、特筆すべき点はありませんでした。