はじめに
Djangoチュートリアル③(モデルの作成、Django Admin)に続いて、アプリを作成してみる。はじめての Django アプリ作成、その 3、現場で使える Django の教科書(基礎編)を参考にする。
(※本記事後半で出てくる画面イメージは はじめての Django アプリ作成、その 2 の [API で遊んでみる] にしたがって、"What's up?" という Question の作成およびそれに紐づく "Not much" "The sky" という Choice 作成を実行していないと表示されないので注意。)
パスコンバータ
チュートリアルにしたがって以下のように polls/views.py にビュー関数を追加し、追加したビューと結びつく URLconf を polls/urls.py に登録する。
def detail(request, question_id):
return HttpResponse("You're looking at question %s." % question_id)
def results(request, question_id):
response = "You're looking at the results of question %s."
return HttpResponse(response % question_id)
def vote(request, question_id):
return HttpResponse("You're voting on question %s." % question_id)
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
ここで重要なのは、urls.py にて記述されている '<int:question_id>' である。<> を使用すると、URL の一部をキーワード変数としてビュー関数に渡すことができる。たとえば "/polls/34/" というリクエストが来た場合、detail 関数は引数 question_id として 34 を受け取る。これは__パスコンバータ__という記法で、Django は「int, str, path, slug, uuid」の5種類のコンバータを提供しており、またコンバータを自作することもできる。
DTL (Django Template Language)
Django には DTL というテンプレートシステムがあり、変数表示やフィルタ、テンプレートタグなどを実現することができる。どんなものがあるかなどの詳細は公式ドキュメントを参照。
ここまでで扱ったビュー関数は、デザインに関する部分もコーディングされていた(デザインといっても文字列を並べているだけだが)ため、ページのデザインを変更する場合には Python コードを編集しなくてはならない。そこで、DTL を用いてデザインを Python から分離する。
まず config/settings.py の TEMPLATES を設定する。DIRS のみデフォルトから設定を変更している。
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')],
'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',
],
},
},
]
- BACKEND : テンプレートエンジンを設定する。デフォルト以外にも Jinja2 などを設定できるらしい。Jinja2 の方が機能が豊富ということも聞いたが、ここは一旦デフォルトのままで Django に慣れてきたら試したい。
- DIRS : どのディレクトリを優先してテンプレートを探しに行くかという順番を指定する。デフォルトでは [(空白)] だが、ここでは上記のように [os.path.join(BASE_DIR, 'templates')] としておく。(
import os
を忘れずに。)設定を変えた理由は後述。 - APP_DIRS : テンプレートを探す際に各アプリケーションディレクトリ直下の templates ディレクトリを優先的に探しにいくかどうかを設定する。デフォルトでは True 。
- OPTIONS : 正直あんまりよくわからない。わかる人教えてください。上記コードはデフォルトのまま。
ここで DIRS の設定を変更した理由を述べる。チュートリアルでは、デフォルト設定のまま polls/templates/polls なるディレクトリを作成し、その中にテンプレート index.html を作成している。この構成は明らかにわかりにくく、また polls/templates ディレクトリの直下に polls サブディレクトリを作らなければよいという考えもチュートリアルに記載の以下理由から適切でない。
テンプレートの名前空間
作ったテンプレートを (polls という別のサブディレクトリを作らずに) 直接 polls/templates の中に置いてもいいのではないか、と思うかもしれませんね。しかし、それは実際には悪い考えです。Django は、名前がマッチした最初のテンプレートを使用するので、もし 異なる アプリケーションの中に同じ名前のテンプレートがあった場合、Django はそれらを区別することができません。そのため、Django に正しいテンプレートを教えてあげる必要がありますが、一番簡単な方法は、それらに 名前空間を与える ことです。アプリケーションと同じ名前をつけた もう一つの ディレクトリの中にテンプレートを置いたのは、そういうわけなのです。
そこでよりわかりやすいディレクトリ構成とするために、ベースディレクトリ直下に templates ディレクトリを作成し、そのサブディレクトリとして各アプリケーション単位のテンプレートを扱うようにした。実際の構成を以下に示す。
mysite (<- ベースディレクトリ)
|-- manage.py
|-- config (<- 設定ディレクトリ)
| |-- __init__.py
| |-- asgi.py
| |-- settings.py
| |-- urls.py
| `-- wsgi.py
|-- polls (<- アプリケーション)
| |-- __init__.py
| |-- admin.py
| |-- apps.py
| |-- migrations
| | `-- __init__.py
| |-- models.py
| |-- tests.py
| `-- views.py
`-- templates (<- テンプレート)
`-- polls
`-- index.html
上記構成にすることで、わかりやすく、かつ各アプリケーションのテンプレートをベースディレクトリから一元管理することができる。このような理由から DIRS の設定を変更している。「この構成にするのであれば、各アプリケーションディレクトリ直下の templates を優先的に探す APP_DIRS は False にすべきじゃないの?」という声があるかと思うが、その場合管理サイトのテンプレートが見つからなくなるなどの問題が起きるので True のままにする。(試したい場合は 'APP_DIRS': False
として開発サーバーを立ち上げた後、 http://localhost:8000/admin/ にアクセスするとエラーが起きることが確認できる。)
ちなみに admin に関するテンプレートは django/contrib/admin/templates/admin
にあった。django のソースファイルの位置は $ python -c "import django; print(django.__path__)"
でわかる。
テンプレートの作成
テンプレートを作成する前に、テンプレートを表示するために polls/views.py の index 関数を変更する。
from django.shortcuts import render
from .models import Question
def index(request):
latest_question_list = Question.objects.order_by('-pub_date')[:5]
context = {'latest_question_list': latest_question_list}
return render(request, 'polls/index.html', context)
render() 関数は、第1引数に request オブジェクトを、第2引数にテンプレート名を、第3引数(任意)に dict 型で指定したコンテキストを受け取る。テンプレートを指定のコンテキストでレンダリングして、その HttpResponse オブジェクトを返す。
templates/polls ディレクトリ直下に上記で指定した index.html ファイルを作成し、以下のコードを書く。コード内の latest_question_list は上記 polls/views.py で指定されたコンテキストのキーで、キーに対応する変数を意味する。
{% if latest_question_list %}
<ul>
{% for question in latest_question_list %}
<li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
{% endfor %}
</ul>
{% else %}
<p>No polls are available.</p>
{% endif %}
上記コードは DTL 記法で書かれている。__{% %} で囲まれたものはテンプレートタグ__で、{% if (条件分岐) %} や {% for (ループ) %} などの便利なテンプレートタグが用意されている。ここでは latest_question_list が存在する場合はその数の分だけリストで表示し、存在しない場合は "No polls are availabel. と表示されるようなものとなっている。また a タグ内の href は <li><a href="/polls/{{ question.id }}/">{{ question.question_text }}</a></li>
のようにリンクの一部をハードコードしても書けるが、URL の変更が煩雑になるため、ここでは {% url %} というテンプレートタグを用いている。 このテンプレートタグは polls.urls.py に path() 関数で定義した URL を逆引きできるようになる。本記事のはじめに書いた polls/urls.py を再掲する。
from django.urls import path
from . import views
app_name = 'polls'
urlpatterns = [
path('', views.index, name='index'),
path('<int:question_id>/', views.detail, name='detail'),
path('<int:question_id>/results/', views.results, name='results'),
path('<int:question_id>/vote/', views.vote, name='vote'),
]
{% url %} の第1引数は URL のパターン名( [app_name]:[path_name] )を、第2引数以降は正規表現グループが含まれている場合にそのパターンにマッチするように値を指定する。つまり <li><a href="{% url 'polls:detail' question.id %}">{{ question.question_text }}</a></li>
の場合は、上記 polls/urls.py に登録された detail のパスである '<int:question_id>/' を逆引きしている。こうすることで URL パターンを変更したい場合にテンプレートを変更することなく、 polls/urls.py のパスのみ変更すればよくなる。app_name はなくても逆引きできるが、アプリケーションが多数になったときに同名のビューが含まれていても問題ないように app_name で名前空間を定義しておくのが望ましい。
http://localhost:8000/polls/ にアクセスすると、以下のような箇条書きのリストが表示されるのが確認できる。
404 エラー
ビューにはリクエストに対応する HttpResponse オブジェクトを返す以外に、例外を返すという役割をもつ。ここでは、指定された投票の質問文を表示する detail ビューに例外処理を実装する。以下コードを polls/views.py に追記する。
from django.shortcuts import get_object_or_404, render
from .models import Question
# ...
def detail(request, question_id):
question = get_object_or_404(Question, pk=question_id)
return render(request, 'polls/detail.html', {'question': question})
get_object_or_404() 関数は、 get() を実行し、オブジェクトが存在しない場合は Http404 を発生させる。上記 detail ビューを動かすために、polls/detail.html を作成し、以下コードを書く。
<h1>{{ question.question_text }}</h1>
<ul>
{% for choice in question.choice_set.all %}
<li>{{ choice.choice_text }}</li>
{% endfor %}
</ul>
http://localhost:8000/polls/1/ にアクセスすると、以下のような画面が表示される。また以下画面は templates/polls/index.html でリンク先に指定されているため、 http://localhost:8000/polls/ から What's up? をクリックしても表示される。
http://localhost:8000/polls/2/ のように、他の数字をつけてアクセスすると、オブジェクトが存在しないためエラーとなる。
おわりに
現場で使える Django の教科書(基礎編)のおかげで理解が進む。いい本だ。続いて、はじめての Django アプリ作成、その 4を進めていく。
関連記事のリンクは以下。