sgykfjsm.github.com

DjangoをTango with Djangoで学ぶ - 2 -

DjangoをTango with Djangoで学ぶ - 1 -の続き

これまでは表示するHTML(ただのテキストだけど)をviews.pyに直接記載していたが、Djangoでも他のWebフレームワークと同様にテンプレートシステムを備えている。

テンプレートシステムとは簡単に言うと、独自のシンタックスとHTMLを混ぜておきて、独自シンタックスの部分をサーバーサイドで指定された文字列などに置き換えたりすることだ。場合によっては多少の制御構文を備えており、ループ文やIF文を提供するテンプレートシステムも多い。

Djangoでテンプレートシステムを利用する場合、${workspace}/tango_with_django_project/templates/${application}というフォルダを作ればよい。今回の場合だと以下の様になる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$ ls -la
total 20
drwxr-xr-x  9 sgyk   306  9  6 09:40 ./
drwxr-xr-x  9 sgyk   306  9  4 22:40 ../
drwxr-xr-x 12 sgyk   408  9  6 09:35 .git/
-rw-r--r--  1 sgyk  3329  9  4 23:09 .gitignore
drwxr-xr-x 12 sgyk   408  9  6 09:35 .idea/
-rw-r--r--  1 sgyk 12288  9  4 22:52 db.sqlite3
-rwxr-xr-x  1 sgyk   823  9  4 22:40 manage.py*
drwxr-xr-x 11 sgyk   374  9  4 23:50 rango/
drwxr-xr-x  8 sgyk   272  9  6 09:40 tango_with_django_project/

$ ls -la tango_with_django_project/
total 12
drwxr-xr-x 8 sgyk  272  9  6 09:40 ./
drwxr-xr-x 9 sgyk  306  9  6 09:40 ../
-rw-r--r-- 1 sgyk    0  9  4 22:40 __init__.py
drwxr-xr-x 6 sgyk  204  9  4 23:39 __pycache__/
-rw-r--r-- 1 sgyk 3166  9  4 23:10 settings.py
drwxr-xr-x 3 sgyk  102  9  6 09:40 templates/
-rw-r--r-- 1 sgyk  930  9  4 23:39 urls.py
-rw-r--r-- 1 sgyk  428  9  4 22:40 wsgi.py

$ ls -la tango_with_django_project/templates/
total 0
drwxr-xr-x 3 sgyk 102  9  6 09:40 ./
drwxr-xr-x 8 sgyk 272  9  6 09:40 ../
drwxr-xr-x 2 sgyk  68  9  6 09:40 rango/

次にDjangoにtango_with_django_project/templates/rangoあるテンプレートを利用するということを教えてあげる必要がある。具体的にはtango_with_django_project/settings.pyに以下に設定を追加する必要がある。

1
2
3
4
5
6
7
...
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],  # <-- ここにテンプレートのパスを追加する
        'APP_DIRS': True,
...

早速追加したいところだけど注意点がある。それは、DIRSに追加するパスは絶対パスでなければならないということだ。しかし、通常、開発時のテンプレートのパスとプロダクション環境で動作させるときに配置されるテンプレートのパスは異なるはず。異なる部分はワークスペース部分のパスになる。例えば、開発時は/Users/you/workspaceがワークスペースかもしれないが、デプロイ時のパスは/home/production/applicationとなるかもしれない。この違いの吸収するようにしないといけない。

もちろん、各環境ごとのパスを1つずつハードコーディングするのはダメである。一方、tango_with_django_project/settings.pyをよく見てみると、次の1文に気づくはずだ。

1
2
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

BASE_DIRtango_with_django_project/settings.pyの絶対パスを取得して、2つ上のディレクトリパスを取得している。もし/Users/you/workspace/tango_with_django_project/settings.pyだとすると、BASE_DIRには/Users/you/workspaceが格納される。

1
2
3
4
5
6
7
$ python
Python 3.5.3 (default, Sep  4 2017, 22:33:15)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.42)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.path.dirname(os.path.dirname("/Users/you/workspace/tango_with_django_project/settings.py"))
'/Users/you/workspace'

ということで、/Users/you/workspace/tango_with_django_project/templates/rangoのようにパスをハードコートするのではなく、BASE_DIRを利用して以下の様に設定すれば良いことになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git diff
diff --git a/tango_with_django_project/settings.py b/tango_with_django_project/settings.py
index 8d38aa9..3b12343 100644
--- a/tango_with_django_project/settings.py
+++ b/tango_with_django_project/settings.py
@@ -52,10 +52,12 @@ MIDDLEWARE = [

 ROOT_URLCONF = 'tango_with_django_project.urls'

+TEMPLATE_DIR = os.path.join(BASE_DIR, 'templates')
+
 TEMPLATES = [
     {
         'BACKEND': 'django.template.backends.django.DjangoTemplates',
-        'DIRS': [],
+        'DIRS': [TEMPLATE_DIR, ],
         'APP_DIRS': True,
         'OPTIONS': {
             'context_processors': [

テンプレートの場所の設定が済んだので実際にテンプレートファイルを記述していく。ここでは以下のようなファイルをrango/templates/index.htmlとして用意する。

templates/rango/index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Rango says...</title>
</head>
<body>
<h1>Rango says...</h1>
<div>
  hey there parther!<br />
  <strong></strong><br />
</div>
<div>
  <a href="/rango/about/">About</a>
</div>
</body>
</html>

ほとんどはただのHTMLファイルだが、“をプレースホルダーとして設定している。AnsibleやJinja2などを経験しているならわかるが、このプレースホルダーがサーバーサイドで任意の変数に置き換えられる。

次にレンダリング処理を記述する。記述する箇所はもちろんrango/views.pyだ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ git diff rango/views.py
diff --git a/rango/views.py b/rango/views.py
index 117afb3..6aeae71 100644
--- a/rango/views.py
+++ b/rango/views.py
@@ -1,5 +1,21 @@
 from django.http import HttpResponse
+from django.shortcuts import render


 def index(request):
-    return HttpResponse("Rango says hey there partner!")
+    """
+    Construct a dicitionary to pass to the template engine as its context.
+    Note the key boldmessage is the same as  in the template!
+    """
+    context_dict = {'boldmessage': 'Crunchy, creamy, cookie, candy, cupcake!'}
+
+    """
+    Return a rendered response to send to the client.
+    We make use of the shortcut function to make our lives easier.
+    Note that the first parameter is the template we wish to use.
+    """
+    return render(request, 'rango/index.html', context=context_dict)

これも見れば大体わかるだろう。render(request, 'index.html', context=context_dict)だけに注目すれば良い。renderの引数の1つ目はリクエストオブジェクトだ。これはお作法として「1つめはリクエストオブジェクト」とおぼえておけば良い。2つ目の引数は、このレンダリング処理が利用するテンプレートの位置を教えている。ディレクトリの位置は先ほど'DIRS': [TEMPLATE_DIR, ]と設定しているので、ここではTEMPLATE_DIR以降の相対パスを記述すれば良い、ということになる。では3つ目は?これはcontextを渡している。一口にcontextといっても意味するところはまさにcontextに依存するわけだが、ここでは「テンプレートファイルに渡す情報」と理解しておけば良い。よってcontext=context_dict{'boldmessage': 'Crunchy, creamy, cookie, candy, cupcake!'}という辞書変数をテンプレートファイルに渡していることを意味する。

さて、コードの順序とは逆だが、ここでテンプレートファイルに渡しているコンテキスト情報を見てみる。キーがboldmessageで値がCrunchy, creamy, cookie, candy, cupcake!だ。今回の処理では、「テンプレートファイルの中にあるboldmessageというプレースホルダーにCrunchy, creamy, cookie, candy, cupcake!という値を渡す」ということになる。実際にpython runserverを実行して http://127.0.0.1:8000/rango/ にアクセスすると太字になったCrunchy, creamy, cookie, candy, cupcake!がブラウザに表示されるはずだ。

HTMLが配信されるようになった。とくれば次はCSSやJavascriptなどを使いたくなる。このような静的なファイルは本来はDjangoの守備範囲ではない。なぜならば、これは「静的」であり、サーバーサイドで生成するようなものではないからだ。とはいえ、Djangoアプリケーションで静的ファイルを配信することはもちろん可能である。

まず静的ファイルを配置するディレクトリを設定する。今回はtemplateディレクトリと同様にプロジェクトのルートにstaticディレクトリを配置する。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ls -l
total 20
drwxr-xr-x 11 sgyk   374  9  6 23:16 ./
drwxr-xr-x  9 sgyk   306  9  4 22:40 ../
drwxr-xr-x 12 sgyk   408  9  6 22:55 .git/
-rw-r--r--  1 sgyk  3329  9  4 23:09 .gitignore
drwxr-xr-x 12 sgyk   408  9  6 23:01 .idea/
-rw-r--r--  1 sgyk 12288  9  4 22:52 db.sqlite3
-rwxr-xr-x  1 sgyk   823  9  4 22:40 manage.py*
drwxr-xr-x 11 sgyk   374  9  6 22:48 rango/
drwxr-xr-x  2 sgyk    68  9  6 23:16 static/    # <- コレ
drwxr-xr-x  7 sgyk   238  9  6 22:48 tango_with_django_project/
drwxr-xr-x  3 sgyk   102  9  6 22:47 templates/

次にtemplatesディレクトリのときと同様にDjangoにstaticディレクトリの位置を教えてあげれば良い。ここでも絶対パスとして定義するようにすること。

1
2
3
4
5
6
7
8
9
10
11
$ git diff
diff --git a/tango_with_django_project/settings.py b/tango_with_django_project/settings.py
index 634f1b7..cc817d7 100644
--- a/tango_with_django_project/settings.py
+++ b/tango_with_django_project/settings.py
@@ -121,3 +121,5 @@ USE_TZ = True
 # https://docs.djangoproject.com/en/1.11/howto/static-files/

 STATIC_URL = '/static/'
+
+STATIC_DIR = os.path.join(BASE_DIR, 'static')

次にSTATICFILES_DIRSを定義する。以下のようにすれば良い。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git diff
diff --git a/tango_with_django_project/settings.py b/tango_with_django_project/settings.py
index 634f1b7..2cec3bc 100644
--- a/tango_with_django_project/settings.py
+++ b/tango_with_django_project/settings.py
@@ -121,3 +121,7 @@ USE_TZ = True
 # https://docs.djangoproject.com/en/1.11/howto/static-files/

 STATIC_URL = '/static/'

STATIC_DIR = os.path.join(BASE_DIR, 'static')
+
+STATICFILES_DIRS = [STATIC_DIR, ]

STATICFILES_DIRSについては「そういうもの」とおぼえておけばよい。要はお作法なのだ。詳細を知りたいなら https://docs.djangoproject.com/en/1.11/howto/static-files/ を参照すること。また、STATIC_URL = '/static/'もちゃんと定義されていることを確認すること。これはDjangoプロジェクトを始める時点で生成されるファイルに予め記述されているはずだが、なければ上記のように定義しておく。STATIC_URLは定義されている値から想像できるように、静的ファイルを配信するときのURLだ。例えば、画像を/static/rango.jpgとして配置すると、ブラウザ上では http://127.0.0.1:8000/static/rango.jpg としてアクセスできるようになる。また、STATIC_URLに定義する値はスラッシュを前後につけること。STATIC_URL = 'static/'STATIC_URL = '/static'ではエラーになる。

では静的ファイルをどのようにテンプレートに組み込むか。これは簡単だ。まずは変更後のテンプレートファイルを見て欲しい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ git diff templates/rango/index.html
diff --git a/templates/rango/index.html b/templates/rango/index.html
index 8d06f2d..6895561 100644
--- a/templates/rango/index.html
+++ b/templates/rango/index.html
@@ -1,4 +1,7 @@
 <!DOCTYPE html>
+
+{% load staticfiles %}
+
 <html lang="en">
 <head>
   <meta charset="UTF-8">
@@ -13,5 +16,9 @@
 <div>
   <a href="/rango/about/">About</a>
 </div>
+<div>
+  <img src="{% static "rango.jpg" %}" alt="Picture of Rango" />
+</div>
+
 </body>
 </html>

テンプレートファイルの上部で{% load staticfiles %}と書いてDjangoに対して静的ファイルを使うことを宣言する。次に通常のHTMLタグを記述して参照したい静的ファイルを{% static "rango.jpg" %}と書く。これについて説明する。{{ foo }}をプレースホルダーと呼んでいたのに対して、{% ... %}テンプレートタグと呼ぶ。詳細な説明は一旦スキップして、ここでは{% static %}として先ほど設定したSTATIC_URLと結びついて参照先URLを生成する。つまり、コードで表現すると{% static "rango.jpg" %}STATIC_URL + "rango.jpg"となり/static/smile.jpgという文字列を生成する。

先述した静的ファイルの配信はあくまでサイトないしはWebアプリケーションのアセットを前提としたものだった。しかし、例えばユーザーやサイト管理者が任意に画像をアップロードし、それをコンテンツとして配信しないといけない場合はどうだろうか。こういったファイルは更新または削除されうるため、「静的」なコンテンツとは言えない。しかし、サーバーサイドのアプリケーションが生成するとも言えない。こういった場合、やはりDjangoに「このファイルは外部ユーザーが管理しているファイルですよ」と教えてあげる設定をしてあげれば良い。

まずは以下の様に外部からアップロードされるファイルを保存する場所を設定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git diff tango_with_django_project/settings.py
diff --git a/tango_with_django_project/settings.py b/tango_with_django_project/settings.py
index 2cec3bc..7e8a909 100644
--- a/tango_with_django_project/settings.py
+++ b/tango_with_django_project/settings.py
@@ -125,3 +125,9 @@ STATIC_URL = '/static/'
 STATIC_DIR = os.path.join(BASE_DIR, 'static')

 STATICFILES_DIRS = [STATIC_DIR, ]
+
+MEDIA_DIR = os.path.join(BASE_DIR, 'media')
+
+MEDIA_ROOT = MEDIA_DIR
+
+MEDIA_URL = '/media/'

次に、このMEDIA_URLをアプリケーションのURLとして登録してあげる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ git diff tango_with_django_project/urls.py
diff --git a/tango_with_django_project/urls.py b/tango_with_django_project/urls.py
index 85abbc4..7050919 100644
--- a/tango_with_django_project/urls.py
+++ b/tango_with_django_project/urls.py
@@ -13,13 +13,15 @@ Including another URLconf
     1. Import the include() function: from django.conf.urls import url, include
     2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
 """
+from django.conf import settings
 from django.conf.urls import url
 from django.contrib import admin
 from django.conf.urls import include
+from django.conf.urls.static import static
 from rango import views

 urlpatterns = [
     url(r'^$', views.index, name='index'),
     url(r'^rango/', include('rango.urls')),
     url(r'^admin/', admin.site.urls),
-]
+] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

これにより、http://127.0.0.1:8000/media/user.jpg などのようにアクセスできるようになる。

なお、django.conf.urls.static.staticを見ればわかるが、このURLでアクセスできるのはsettings.pyの中でDEBUG = Trueとなっているときだけである。詳細は https://docs.djangoproject.com/en/1.11/howto/static-files/#serving-files-uploaded-by-a-user-during-development を参照すること。

次に、テンプレートで使用する場合はリクエストオブジェクトにMEDIA_URLの情報を与えるためにdjango.template.context_processors.mediaを与える。このcontext_processorsについては https://docs.djangoproject.com/en/1.11/ref/templates/api/#django-template-context-processors-media を参照すること。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ git diff tango_with_django_project/settings.py
diff --git a/tango_with_django_project/settings.py b/tango_with_django_project/settings.py
index 7e8a909..68b8335 100644
--- a/tango_with_django_project/settings.py
+++ b/tango_with_django_project/settings.py
@@ -65,6 +65,7 @@ TEMPLATES = [
                 'django.template.context_processors.request',
                 'django.contrib.auth.context_processors.auth',
                 'django.contrib.messages.context_processors.messages',
+                'django.template.context_processors.media',
             ],
         },
     },

テンプレートへの実際の埋め込みは以下の通り。上の設定でリクエストオブジェクトはMEDIA_URLが使えるようになっているので、素直にそのまま設定すれば良い。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/tango_with_django_project (git)-[master][!][*]
$ git diff templates/rango/
diff --git a/templates/rango/index.html b/templates/rango/index.html
index 6895561..08398ef 100644
--- a/templates/rango/index.html
+++ b/templates/rango/index.html
@@ -19,6 +19,8 @@
 <div>
   <img src="{% static "smile.jpg" %}" alt="Picture of Rango" />
 </div>
-
+<div>
+  <img src="{{ MEDIA_URL }}user.jpg" %}" alt="Picture of User" />
+</div>
 </body>
 </html>

テンプレートの話はとりあえずここまで。