このページでは、Django におけるテンプレート(Template)について解説していきます。
Django で開発するウェブアプリの基本構造は MTV モデルとなっています。
前回、下記ページで V の部分のビューに関して解説を行いました。
【Django入門3】ビュー(View)の基本とURLのマッピング今回は、T の部分のテンプレートについて解説していきます。テンプレートは MTV モデルの1部分であり、Django でのウェブアプリ開発において重要な存在となります。
是非、このページを読み進めていただき、テンプレートがどんなものであるかを理解していっていただければと思います!
Contents
テンプレートの基本
では、テンプレートについて解説していきます。
テンプレート
テンプレートとは、ページの雛形を提供するアプリの構成部分となります。
このテンプレートファイルを利用することで、ウェブアプリで無数のページ表示を行うことが可能になります。
例えば、ウェブアプリでログインユーザーの情報を表示する『マイページ』を実現することを考えてみましょう!
このマイページは、当然ユーザーによって表示される情報が異なることになります。例えば、このページで『ユーザー名』『アイコン画像』『自己紹介文』が表示されるとすると、これらの情報はユーザーによって異なるはずです。
したがって、このようなマイページの表示を実現するためには、ユーザーの数だけ HTML を用意し、ログイン中のユーザーに応じた HTML をウェブブラウザ等のクライアントに返却するようにする必要があります。
この HTML の用意の仕方には様々な方法があります。最も単純な方法は、ユーザーごとに HTML を事前に作成しておく方法になると思います。ただ、これは単純ですが大変で、ユーザーの数の分だけ HTML を作成する必要があります。例えばウェブアプリのユーザーが一万人いれば、一万個の HTML を作成しておく必要があります…。
他の方法としては、ページの雛形部分だけを事前に作成しておき、ユーザーからマイページの表示のリクエストを受けた際に、そのユーザーの情報を雛形に埋め込んで HTML を用意する方法になります。これが、このページの主題となるテンプレートの考え方となります。
先程のマイページに注目してみれば、これらのページはユーザー毎に『ユーザー名』『アイコン画像』『自己紹介文』の内容は異なるものの、それ以外の部分は同じです。このように、同じ種類のページであれば、ページの雛形としては同じものが利用できるケースが多いです。
したがって、ページの雛形部分だけを事前に作成しておき、ユーザーからマイページの表示のリクエストを受けた際にユーザーに応じた『ユーザー名』『アイコン画像』『自己紹介文』を雛形に埋め込んで HTML を生成して返却するようにすれば、1つの雛形から全ユーザーのマイページの表示を実現することができるようになります。
このように、基本的にウェブアプリで表示するページは雛形と動的に変更が必要な部分(動的な部分)の2種類から構成されています。
したがって、雛形となる部分のみを事前に作成しておき、ウェブアプリがリクエストを受け取った時に、その雛形に対してリクエストに応じた情報を動的に埋め込んで HTML を生成するようにすれば、1つの雛形からリクエストに応じたページ表示を実現することができるようになります。
このような、部分的に表示結果を動的に変更可能な雛形を提供することがテンプレートの役割となります。このような雛形はテンプレートファイルと呼ばれます。そして、このテンプレートファイルを提供することが、MTV におけるテンプレート(Template)の役割となります。
また、1つのテンプレートファイルから生成可能な HTML の構成は全て共通となります。そのため、異なる構成のページを表示できるようにしたいのであれば、別のテンプレートファイルを作成しておく必要があります。したがって、基本的にウェブアプリを実現する上で必要になるテンプレートファイル、すなわちテンプレートから提供するテンプレートファイルは複数になります。
大体テンプレートの意味合いに関しては理解していただけたのではないでしょうか?
また、テンプレートファイルからの HTML の生成は、次に説明する render
関数によって行われます。さらに、動的に変更可能な部分に埋め込むデータはコンテキストと呼ばれるデータによって提供されます。これに関しては後述の コンテキスト で説明します。
スポンサーリンク
render
関数
ということで、次は render
関数について解説していきます。
render
関数とは
render
関数は、先程説明したテンプレートファイルから HTML を生成する関数になります。より正確に言えば、テンプレートファイルから HTML を生成し、さらにその HTML をボディとするレスポンス(HttpResponse
のインスタンス)を返却する関数となります。
この render
関数は Django フレームワークから提供される関数で、django.shortcuts
で定義されています。さらに、この render
関数はビューから実行される関数となります。
render
関数の引数
render
関数の引数は下記のように定義されています。
render(request, template_name, context=None, content_type=None, status=None, using=None)
引数が多いですが、指定が必須なのは request
・template_name
の2つになります。ただし、コンテキスト で説明するように、動的に HTML の一部を変化させるためには context
の指定も必要となります。
まず、request
引数には HttpRequest のサブクラス
のインスタンスを指定する必要があります。この引数には、ビューが Django フレームワークから受け取った引数 request
をそのまま指定してやれば良いです。
また、template_name
引数にはテンプレートファイルのパスを指定します。
このように引数を指定してビューが render
関数を実行すれば、template_name
引数に指定されたテンプレートファイルの記述に基づいて HTML が生成され、その生成結果をボディとするレスポンスを返却値として取得することができます。
前述の通り、テンプレートファイルは複数用意されることが多いため、リクエストに応じて適切なテンプレートファイルを選択し、そのファイルのパスを render
関数の template_name
引数に指定する必要があります。
コンテキスト
また、render
関数には引数 context
にコンテキストを指定して渡すことも可能です。
コンテキストとは
このコンテキストは辞書形式のデータであり、キー
と 値
を各要素に持つデータとなります。
テンプレートファイルからは変数を参照することが可能です。この参照される変数の値を提供するのがコンテキストになります。
コンテキストと render
関数
テンプレートファイルにおいては、{{
~ }}
の部分は変数として扱われることになります。{{
~ }}
の内側には変数名を記述します。要は、テンプレートファイルの {{ 変数名 }}
部分は変数として扱われます。そして、render
関数で HTML が生成される際に {{ 変数名 }}
部分が変数の値に置き換えられることになります。
この変数の値は render
関数の中でコンテキストから取得されます。より具体的には、テンプレートファイルに {{ 変数名 }}
が記述されている場合、render
関数の context
引数に指定されたコンテキストの '変数名'
キーの値が取得されることになります。そして、HTML 生成時に {{ 変数名 }}
部分が '変数名'
キーの値に置き換えられることになります。
例えば、下記のようなコンテキストを render
関数の context
引数に指定した場合、render
関数実行時にテンプレートファイルの {{ name }}
部分が YamadaHanako
に置き換えられることになります。
context = {
'name' : 'YamadaHanako',
}
こんな感じで、テンプレートファイルでは {{ 変数名 }}
部分を変数として扱うことができ、render
関数での HTML 生成時に {{ 変数名 }}
が実際の値に置き換えられることになります。これを利用すれば、最初に説明したような雛形にデータを埋め込むような処理が実現できます。そして、置き換えられる値は、コンテキストにおけるキーが '変数名'
の 値
となります。例えばコンテキストの変数名が context
であれば、context['変数名']
で取得される値に置き換えられることになります。
前述の通り、コンテキストは言ってしまえば単なる辞書データです。なので、通常の辞書と同様に作成することができます。もちろん、コンテキストには複数の要素を持たせても問題ありません。
ただし、コンテキストには、テンプレートファイルから参照される全ての 変数名
のキーを持たせておく必要があるので、この点には注意してください。
例えば、下の図のテンプレートファイルには {{ name }}
・{{ age }}
・{{ comment }}
の3つの変数が存在するため、用意するコンテキストには少なくとも 'name'
キー・'age'
キー・'comment'
キーの要素が必要となります。
コンテキストの重要性
テンプレート で、ウェブアプリのページは雛形(共通部分)と動的に変化させる部分の2つから構成されると説明しましたが、コンテキストは、後者の動的に変化させることを実現するために必要なデータとなります。
また、ウェブアプリでは、リクエストに応じたレスポンス(もっと詳細に言えば、リクエストに応じた機能を実行し、その機能によって得られた結果に応じたレスポンス)を返却できるように開発することが重要となります。
そして、これはコンテキストを動的に変化させることで実現可能です。
テンプレートファイルで変数を扱うようにしておけば、コンテキストの各要素に応じて生成される HTML が変化することになります。したがって、このコンテキストをリクエストに応じて動的に変化させるようにすれば、生成される HTML もリクエストに応じて動的に変化させることができるようになります。つまり、ウェブアプリにおける「リクエストに応じたレスポンス(HTML)の返却」は、このコンテキストを動的に変化させることで実現できます。
例えば、前述の下記のような辞書データを context
引数に指定した場合、render
関数実行時にテンプレートファイルにおける {{ name }}
部分が YamadaHanako
に変化することになります。
context = {
'name' : 'YamadaHanako',
}
それに対し、下記のような辞書データを context
引数に指定した場合、render
関数実行時にテンプレートファイルにおける {{ name }}
部分が TanakaJiro
に変化することになります。
context = {
'name' : 'TanakaJiro',
}
このように、テンプレートファイルで変数を参照するようにしておけば、コンテキストに応じた HTML を生成することができます。さらに、コンテキストの要素をリクエストに応じて設定するようにすれば、1つのテンプレートファイルからリクエストに応じた無数の HTML を生成することが可能となります。
こんな感じで、コンテキストはリクエストに応じたレスポンスを返却することを実現するための重要なデータとなります。
そして、このコンテキストを用意するのはビューの役割となります(render
関数の実行に関してもビューの役割)。ここまでの説明のとおり、ビューは、クライアント(ユーザー)からのリクエストに応えられるよう、リクエストに応じたコンテキストを用意することが必要となります。例えば、データベースから検索して取得したデータからコンテキストを生成するようにし、さらにリクエストの内容によってデータベースへの検索条件を変化させるようにすれば、クライアントからのリクストに応じたコンテキストが用意できることになります。
テンプレートファイル
続いて、テンプレートファイルの詳細について説明を行なっていきます。
スポンサーリンク
HTML の基となるファイル
前述の通り、テンプレートファイルはページの雛形であり、HTML の基となるファイルです。拡張子には HTML 同様に .html
が利用されます。
このテンプレートファイルからは HTML を生成することが可能で、前述で解説した通り、テンプレートファイルから HTML を生成する際には render
関数が利用されます。この render
関数を実行することで、テンプレートファイルの記述やコンテキストに従った HTML の生成が行われます。
テンプレートファイルの構成
このテンプレートファイルは、基本的には『HTML』と『Django テンプレート言語』と呼ばれる Django 特有の構文から構成されるファイルとなります。
前述で示した {{ 変数名 }}
も Django テンプレート言語に従った変数を扱うための記述になります。
render
関数で HTML が生成される際には、『Django テンプレート言語』部分のみが動的に変化することになります。逆に、『Django テンプレート言語』以外の部分はそのまま出力されることになります。つまり、テンプレートファイルにおける『HTML』部分は render
関数を実行しても変化しません。
したがって、ページの雛形部分、つまり共通部分は HTML で記述を行い、動的に変化させたい部分はDjango テンプレート言語で記述することになります。
Django テンプレート言語
この Django テンプレート言語は {
〜 }
で囲んだ形式で記述します。
つまり、雛形を構成する HTML の中に {
〜 }
を記述しておけば、その部分のみが render
関数実行時に動的に変化することなります。
この Django テンプレート言語には大きく分けて4種類のものが存在します。
- 変数
- テンプレートタグ
- フィルター
- コメント
ここでは簡単に、各種 Django テンプレート言語について説明します。
変数
『変数』は {{ 変数名 }}
の形式で記述を行います。{{ 変数名 }}
はテンプレートファイルの毎回動的に変化させたい部分に記述します。
前述の コンテキスト で解説したように、テンプレートファイルからは変数を参照することができます。その変数の参照先はコンテキストとなります。{{ 変数名 }}
がテンプレートファイルに記述されている場合、{{ 変数名 }}
部分が、render
関数実行時にコンテキストの “キーが '変数名'
の要素の値” に置き換えられることになります。
例えば、{{ name }}
を記述したテンプレートファイルと、下記のコンテキストを render
関数の引数に指定した場合、render
関数実行時に {{ name }}
の部分が name
キーの値である YamadaHanako
に置き換えられた HTML が生成されることになります。
context = {
'name' : 'YamadaHanako',
}
また、テンプレートファイルでは {{ user.name }}
のようにインスタンスのデータ属性を参照するようなことも可能です。この場合、コンテキストの 'user'
キーの値としてはデータ属性 name
を持つオブジェクトを指定する必要があります。
特に多いのが、モデルのインスタンスをコンテキストの値に設定する例となります。この実例はモデルの解説ページで紹介します。
テンプレートタグ
2つ目が『テンプレートタグ』で、これは {% タグ名 %}
の形式で記述します。このテンプレートタグは、単に『タグ』とも呼ばれます。
テンプレートタグの用途は様々で一言で説明するのは困難ですが、簡単に言えば何かしらの処理や制御を行うための記述であると考えると良いと思います。テンプレートファイルに {% タグ名 %}
の記述があった場合、render
関数内で タグ名
に応じた処理が行われ、その結果が {% タグ名 %}
部分に埋め込まれることになります。
また、タグ名
によっては、{%
〜 %}
の中に単にタグ名を記述するだけでなく、引数の指定が必要なものもありますし、処理の終端(例えばループ処理の終端)を示すための {%
〜 %}
を別途記述する必要のあるものもあります。
また、引数には変数を指定することが可能なものもあります。この場合は、テンプレートファイルから変数が参照されることになりますので、その参照先としてコンテキストに変数に応じた要素を用意しておく必要があります。
例えば {% for user in users %}
は users
の各要素に対してループを行うための記述となります。この for
タグにおいては、処理をループさせる範囲を特定するために終端を表す {% endfor %}
の記述が必要になります。
例えば下図のような for
タグがテンプレートファイルに記述されている場合、{% for user in users %}
〜 {% endfor %}
の内側に記述された内容が users
の要素数分繰り返し HTML に出力されることになります。
また、この記述における users
はテンプレートファイルが参照する変数となりますので、コンテキストには 'users'
をキーとする要素を用意しておく必要があります。さらに、この場合は users
はイテラブルなオブジェクトとして扱われることになるため、'users'
の値はリストなどのイテラブルなオブジェクトである必要があります。for
タグから参照することの多いオブジェクトがクエリーセットになります。このクエリーセットに関してはモデルの解説の中で紹介します。
ここでは for
タグの紹介を行いましたが、他にも様々なテンプレートタグが存在します。各種テンプレートタグについては別途ページを作成して解説を行いたいと思います。
フィルター
フィルターは、参照する変数を加工する仕組みになります。
フィルターを利用すれば、変数 で説明した {{ 変数名 }}
を変数の値にそのまま置き換えるのではなく、変数を何らかの形に加工したものに置き換えることができます。また、変数部分のみだけでなく、テンプレートタグ で説明したテンプレートタグ {%
〜 %}
内で利用する変数に対してフィルターを適用することも可能です。
フィルターは、変数名|フィルター名
の形式で記述を行なって利用します。これによって、変数名
の参照する値を フィルター名
のフィルターで加工することができます。また、フィルターによっては引数を受け取るものもあり、その場合は 変数名|フィルター名:引数
の形式で記述を行うことで引数を指定することになります。
例えば、フィルターの1つに length
が存在します。この length
は、変数を、その変数の要素数や長さに変換するフィルターとなります。例えば、コンテキストの 'users'
キーの値として要素数が 10
のリストが指定されている場合、テンプレートファイルに {{ users|length }}
が記述されていれば、この部分が HTML 生成時に 10
に置き換えられることになります。特にモデルを利用しだすとクエリーセットと呼ばれるデータを扱う機会が多くなり、クエリーセットの要素数を HTML に出力したい場合等に length
はよく利用されます。
また、フィルターには truncatechars
というものも存在し、これを利用することでページに表示される文字列を “引数で指定した文字数” で自動的に切り詰めることができるようになります。例えば、コンテキストの 'text'
キーの値として 'abcdefghijk'
という文字列が指定されている場合、テンプレートファイルに {{ text|truncatechars:3 }}
と記述しておけば、HTML 生成時に下記の文字列に置き換えられることになります。
ab…
文字列が引数に指定した文字数以下に切り詰められるため、文字列が長すぎるとレイアウトが崩れてしまう様な場合に便利です。
前述の通り、フィルターは、テンプレートタグから参照する変数に対しても適用することもできます。例えば下記のように記述を行えば、users|length
は users
の要素数に変換されることになりますので、users
の要素数が 0
以外の場合のみ Hello World
が HTML に埋め込まれることになります。
{% if users|length != 0 %}
Hello World
{% endif %}
コメント
さらに、通常のプログラミング言語や HTML と同様に、Django テンプレート言語にもコメント機能が存在します。Django テンプレート言語でのコメントの書き方は2種類あります。
1つが下記の形式の書き方で、この場合は {#
〜 #}
の部分がコメントとして扱われることになります。特定の1行をコメントアウトしたり、行内の一部をコメントアウトするようなこともできますが、この書き方では複数行を一度にコメントアウトトするようなことはできません。
{# コメントととして扱われる #}
複数行を一度にコメントアウトしたい場合は、下記のようにテンプレートタグとしてコメント部分を指定する必要があります。
{% comment '不要になったためコメントアウト' %} コメント として 扱われる {% endcomment %}
スポンサーリンク
テンプレートファイルの置き場所
続いてテンプレートファイルの置き場所について説明していきます。
置き場所
テンプレートファイルの置き場所は下記となります。
プロジェクト名/アプリ名/templates/アプリ名/
テンプレートファイルの置き場所をフォルダ構成で図示すると下の図のようになります。
プロジェクト名
のフォルダは startproject
コマンド実行によって、プロジェクト名
フォルダの直下の アプリ名
のフォルダは startapp
コマンド実行によってそれぞれ自動的に作成されます。
ですが、その下にある templates
フォルダと アプリ名
フォルダは自動的には作成されませんので、手動でフォルダを作成しておく必要があります。
そして、上記のフォルダの中に .html
という拡張子のテンプレートファイルを、後述の 必要となるテンプレートファイル で解説しているように、ウェブアプリに必要な分だけ用意しておく必要があります。
テンプレートファイルのパス
また、上記のフォルダの中にテンプレートファイルを置いた場合、アプリ内のファイル、例えば views.py
や他のテンプレートファイルからは次のパスによりアクセスすることができるようになります。
アプリ名/ファイル名
例えば、アプリ名が app1
でテンプレートファイルのファイル名が index.html
である場合、views.py
から render
関数を実行する際には下記のように引数を指定することになります。
render(request, 'app1/index.html')
要は、templates
フォルダから見たテンプレートファイルの相対パスを指定すれば良いことになります。
必要となるテンプレートファイル
ここまで説明してきたように、テンプレートの仕組みを利用することで、1つのテンプレートファイルから無数の HTML を生成することができるようになります。
ただし、テンプレートファイルから生成可能な HTML は同じ雛形のものだけになります。要は同じような構成の HTML しか生成できません。そのため、構成が大きく異なるような HTML を生成するためには、別のテンプレートファイルを用意する必要があります。
そのため、ウェブアプリの開発時には基本的には複数のテンプレートファイルを用意しておくことになります。
基本的には、ウェブアプリで表示したいページの種類分のテンプレートファイルを作成することになると思います。例えば、ウェブアプリで『ログインページ』『マイページ』『登録ユーザーの一覧ページ』の3つの種類のページを表示できるようにしたいのであれば、3つのテンプレートファイルを作成する必要がある可能性が高いです。
ただし、ページの種類が異なってもページの雛形は同様である場合もあるので、その場合は1つのテンプレートファイルを複数のページで使い回すようなことも可能です。
例えば『マイページ』と『他のユーザーのページ』はページの種類は異なるものの、ページの雛形は同じである場合があります。この場合は同じテンプレートファイルを利用できることになります。
ただ、ウェブアプリによっては『マイページ』と『他のユーザーのページ』のページの構成を大きく異なるものにしたい場合もあるかもしれません。この場合はこれらのページ用のテンプレートファイルを個別に用意する必要があります。
結局、どのようなテンプレートファイルをどれだけ用意するかはウェブアプリによって異なりますが、重要なのは、自身が開発したいウェブアプリの構成を考え、その構成に応じて必要な分だけテンプレートファイルは用意することになります。
テンプレートの継承
複数のテンプレートファイルの作成時に便利な仕組みが『テンプレートの継承』になります。
テンプレートの継承とは
特に、ファイル間で共通の部分が存在する複数のテンプレートファイルを作成するときに、テンプレートの継承を利用することで開発効率が向上します。
このテンプレートの継承は、『各テンプレートファイル間で共通となる構造を実装した親テンプレートファイル』と『各テンプレートファイルで固有となる構造を実装した子テンプレートファイル」を組み合わせて新たなテンプレートファイルを生成する仕組みになります。
例えば、下図は同じウェブアプリにおける「マイページ」と「ユーザー一覧のページ」の表示例を示しています。これらは、全体的に見るとページの構成は異なりますが、ヘッダーやフッターに関しては完全に共通の構造・見た目となっています。こんな感じで、ウェブアプリでは各種ページの特定の部分、特にヘッダーやフッターを共通にすることで、各種ページの操作方法や見た目の統一が図られていることが多いです。
ただ、共通部分は存在するものの、全体的なページの構成が異なるため、普通に考えると、これらのページの HTML は異なるテンプレートファイルから生成する必要があります。ですが、これらのテンプレートファイルを完全に個別に作成するのは非効率です。共通部分が存在するため、各種テンプレートファイルに全く同じ実装を行う必要があります。
で、このような、共通部分の存在する複数のテンプレートファイルを効率的に作成するための仕組みが『テンプレートの継承」となります。テンプレートの継承を利用する場合、各種テンプレートファイルで共通となる部分を1つの親テンプレートファイルに実装し、親テンプレートファイルを継承する子テンプレートファイルを用意し、この子テンプレートファイルに “テンプレートファイルごとに異なる固有部分” を実装します。
このように親テンプレートファイルと子テンプレートファイルを作成しておけば、子テンプレートファイルに対して render
関数を実行すると、親テンプレートファイルに子テンプレートファイルの内容を埋め込んだ新たなテンプレートファイルが生成されるようになります。そして、その生成されたテンプレートファイルからコンテキストを利用して HTML ファイルが生成されることになります。
したがって、子テンプレートファイルを必要な数だけ用意しておけば、その数の分の「ファイル間で共通となる部分を持ちつつ、各ファイルで固有となる部分も持つテンプレートファイル」が生成できることになります。そして、今までの解説の通り、このテンプレートファイルからコンテキストに応じた HTML を生成することができます。
そして、テンプレートファイルは個別でも用意することも可能ではありますが、テンプレートの継承を利用すれば共通部分の実装先のファイルは親テンプレートファイル1つのみで済み、テンプレートファイルの作成の効率が上がります。
こんな感じで、ファイル間で共通の部分が存在する複数のテンプレートファイルの作成を効率的に行うための仕組みがテンプレートの継承となります。
で、このテンプレートの継承を利用するためには、前述のとおり親テンプレートファイル(継承元となるテンプレートファイル)と親テンプレートファイルを継承する子テンプレートファイルの2種類を作成する必要があります。1つの親テンプレートファイルに対して複数の子テンプレートファイルを作成することが多いと思います。これらのファイルに関しても テンプレートファイルの置き場所 で説明した下記フォルダに設置します。
プロジェクト名/アプリ名/templates/アプリ名/
テンプレートの継承の概要の説明は以上となります。
ここで、上記の説明に対して2点補足しておきます。
まず1点目です。ここまで render
関数の中で親テンプレートファイルと子テンプレートファイルから新たなテンプレートファイルが生成されると説明してきましたが、この生成されたテンプレートファイルはどこかにファイルとして保存されるものではありません。render
関数の中で生成され、render
関数の中でのみ使用されるものとなります。
次に2点目です。render
関数には引数でテンプレートファイルのパスを指定する必要がありますが、この引数に指定するパスは『子テンプレートファイル」のものになります。親テンプレートファイル側のパスを指定しても継承が上手く動作しないので注意してください。
親テンプレートファイルの作成
ここからは、親テンプレートファイルと子テンプレートファイルの作り方の詳細について解説していきます。
まずは、親テンプレートファイルの作り方について説明していきます。
親テンプレートファイルでは、『各テンプレートファイルで共通となる構造』の定義、および『ブロック』の定義を行います。前者は、文章のとおり、各テンプレートファイルで共通となる部分となり、ここに関しては通常のテンプレートファイルと同じ書き方で作成することになります。また、後者のブロックは、テンプレートファイルごとで固有となる構造を埋め込む部分となります。
このブロックは、render
関数実行時に子テンプレートファイルで定義された『ブロックのコンテンツ』に置き換えられることになります。なので、子テンプレートファイル毎に異なるブロックのコンテンツを定義しておけば、使用する子テンプレートファイルによって render
関数で生成されるテンプレートファイルが変化することになります。ブロックは1つのテンプレートファイルの中に複数個定義することも可能です。
そして、このブロックは block
テンプレートタグにより定義することが可能です。例えば親テンプレートファイルに {% block ブロック名 %}{% endblock %}
と記述を行うことで、ブロック名
という名前のブロックを定義することができます。
こんな感じで、各テンプレートファイルで共通となる部分とブロックの定義を行うことで親テンプレートファイルを作成することができます。親テンプレートファイルの実例は、テンプレートの継承例 で子テンプレートファイルの実例と一緒に示していきます。
子テンプレートファイルの作成
続いて、子テンプレートファイルについて解説していきます。
子テンプレートファイルでは、『親テンプレートファイルの継承』と『ブロックのコンテンツの定義』を行います。どちらもテンプレートタグを利用して記述していくことになります。
まず、親テンプレートファイルの継承はテンプレートタグ extends
によって実現できます。具体的には、子テンプレートファイルの先頭に {% extends 親テンプレートファイルのパス %}
を記述することで 親テンプレートファイルのパス
のパスにあるテンプレートファイルへの継承が実現できます。
また、ブロックのコンテンツの定義はテンプレートタグ block
によって実現できます。具体的には、{% block ブロック名 %}
〜 {% endblock %}
がブロックのコンテンツの定義となります。親テンプレートファイルではブロックを複数定義することが可能なので、子テンプレートファイルでは、継承する親テンプレートで定義されたブロック全てに対してブロックのコンテンツを定義しておく必要があります。また、このブロックのコンテンツでも Django テンプレート言語での変数の埋め込みやテンプレートタグによる制御等を利用することが可能です。
上記のように子テンプレートファイルを作成し、さらにそのファイルのパスを引数に指定して render
関数を実行すれば、親テンプレートファイルの {% block ブロック名 %}{% endblock %}
部分が、子テンプレートファイルにおける {% block ブロック名 %}
〜 {% endblock %}
の内側に記述された内容に置き換えられたテンプレートファイルが生成されることになります。
そして、この生成されたテンプレートファイルと、render
関数の引数に指定されたコンテキストに基づいて HTML が生成されることになります。
テンプレートの継承例
次はテンプレートの継承の具体例を見ていきましょう!
ここでは、アプリ名は app1
とし、親テンプレートファイルのファイル名は base.html
であることを前提に具体例を示していきます。
前述の通り、親テンプレートファイルには各種ページで共通となる部分の HTML の記述とブロックの定義を行います。簡単ですが、下記は親テンプレートファイルの具体的な例となります(a
タグに指定している URL はてきとうなものになっているので注意してください)。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<header>
<h1>マイアプリ</h1>
<nav class="navbar navbar-expand navbar-light bg-light">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="dummy1">トップページ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="dummy2">マイページ</a>
</li>
<li class="nav-item">
<a class="nav-link" href="dummy3">ユーザー一覧</a>
</li>
</ul>
</nav>
</header>
<main class="container">
{% block main %}{% endblock %}
</main>
<footer>
<p>https://daeudaeu.com/</p>
</footer>
</body>
</html>
この親テンプレートファイルは下図のようなブロックを定義するファイルとなります。
要はページのタイトル部分と本文部分がブロックとなっており、その他の部分は各種ページで共通のものとなるように実装したテンプレートファイルとなります。
ページのタイトル部分のブロックはブロック名が title
であり、本文部分のブロックはブロック名が main
となります。これらのブロックのコンテンツは子テンプレートファイル側に実装する必要があります。
したがって、子テンプレートファイルとしては下記のような構造のファイルを用意する必要があります。下記では2つの子テンプレートファイルの例を示しています(mypage.html
と userlist.html
)。
{% extends "app1/base.html" %}
{% block title %}
マイページ
{% endblock %}
{% block main %}
<h2>マイページ</h2>
<p>ユーザー名 : 山田太郎</p>
<p>アカウント作成日 : 2023/2/27</p>
<p>メールアドレス : taro@yamada.jp</p>
{% endblock %}
{% extends "app1/base.html" %}
{% block title %}
ユーザー一覧
{% endblock %}
{% block main %}
<h2>ユーザー一覧</h2>
<table>
<thead>
<tr>
<th>ユーザー</th>
</tr>
</thead>
<tbody>
<tr><td>山田太郎</td></tr>
<tr><td>山田花子</td></tr>
<tr><td>田中二郎</td></tr>
<tr><td>佐藤三郎</td></tr>
</tbody>
</table>
{% endblock %}
上記の mypage.html
と userlist.html
は共通した構成になっており、まず1行目で親テンプレートファイルである app1/base.html
の継承を行なっています。テンプレートファイルの置き場所 で説明したように、アプリ内のファイルからはテンプレートファイルを アプリ名/テンプレートファイル名
によって参照可能です。
その後、mypage.html
と userlist.html
ではブロック title
のコンテンツの定義とブロック main
のコンテンツの実装をそれぞれを行なっています。これらは、各ファイルで同じブロックに対してコンテンツを実装していることになりますが、実装内容が異なるため、親テンプレートファイルのブロックへ置き換えられることで、異なる内容のテンプレートファイルが生成されることになります。
実際に、render(request, 'app1/mypage.html')
を実行した際には、下図のようなページの HTML が生成されることになります。
また、render(request, 'app1/userlist.html')
を実行した際には下図のようなページの HTML が生成されることになります。
ブロック部分のみが、render
関数の引数で指定した子テンプレートファイルに応じて異なっていることが確認できると思います。それ以外の部分は親テンプレートファイルのものがそのまま使用されています。
このように、親テンプレートファイルで各種ページの共通部分を用意し、その中に各種ページの固有部分をブロックとして埋め込む仕組みがテンプレートの継承となります。
上記の例では利用していませんが、各種テンプレートファイルからテンプレートタグや変数の参照等も利用可能です。
テンプレートの継承のメリット
前述でも少し触れましたが、テンプレートの継承にはたくさんのメリットがあります。
まず、テンプレートの継承の仕組みを利用することで、子テンプレートファイル側は extends
タグによる継承とブロックのコンテンツの実装さえ行えば良くなるため、テンプレートファイル作成の効率が上がります。
また、各種ページで共通となる部分は親テンプレートファイルのみに記述されるようになるため、共通部分の修正が必要になった際に修正が必要となるのは親テンプレートファイルのみとなり、修正の効率も上がります。逆に継承を利用していなかった場合、共通部分に修正が必要になった際には全てのテンプレートファイルの修正が必要となってしまいます…。また、この場合、修正漏れが発生する可能性もあります。
さらに、共通部分を1つのテンプレートファイルに記述するようになることで、自然とウェブアプリの各種ページの見た目・使い方に統一感が生まれることになります。
最初は面倒に感じるかもしれませんが、テンプレートの継承はメリットが多いため、是非積極的に利用するようにしてください!
スポンサーリンク
ビューとテンプレートの関係性
ここで、ここまで説明してきた内容のまとめの意味も含めて、テンプレートとビューの関係性について解説しておきたいと思います。
テンプレートの役割
ここまでの説明の通り、テンプレートはテンプレートファイルを提供することが役割であり、具体的には、テンプレートの役割は下記を行なってテンプレートファイルを提供することになります。
- 『HTML』と『Django テンプレート言語』から構成されるテンプレートファイルを作成する
- テンプレートファイルはウェブアプリで表示するページに応じて必要な分だけ作成する
- テンプレートの継承を利用しても OK
- 作成したテンプレートファイルをテンプレートファイルの置き場所に置く
そして、このテンプレートファイルの提供先はビューとなります。あくまでもテンプレートの役割はテンプレートファイルの提供であり、テンプレートファイルを利用するのはビューとなります(もっと正確に言えば、利用するのはビューから実行される render
関数となります)。
ビューの役割
また、ビューにとって重要なのは、リクエストに応じたレスポンスを返却することになります。そして、レスポンスには HTML が含まれるため、リクエストに応じた HTML を生成する必要もあります。
そのため、複数のテンプレートファイルが存在する場合、リクエストに応じた HTML を生成するために、適切にテンプレートファイルを選択し、そのファイルのパスを render
関数の引数に指定する必要があります。
また、テンプレートファイルから変数が参照される場合、リクエストに応じた HTML を生成するために、適切にコンテキストを作成して、そのコンテキストを render
関数の引数に指定する必要もあります。
要は、テンプレートはテンプレートファイルを提供することが役割であり、そのテンプレートファイルを適切に利用して HTML を生成するのはビューの役割となります。
また、ウェブアプリが受け付けるリクエスト先の URL に関しては urls.py
の URL のマッピングによって決まります。ウェブアプリでリクエスト先の URL に応じた処理が実行できるよう、適切に urls.py
で URL のマッピングも行なっておく必要があります。
結局、重要なのはウェブアプリがユーザーのリクエストに応じた処理の実行・レスポンスの返却を行えるようにすることなので、開発者目線で考えれば、それを実現するために urls.py
やビュー、さらには今回紹介したテンプレートファイルを作成・用意しておくことが重要となります。
特にテンプレートファイルの作りによって、ビューの作成するコンテキストに必要なキー等が決まることになりますので、テンプレートの参照する変数とビューの作成するコンテキストとで話が合うように開発を行うことも重要となります。
スポンサーリンク
掲示板アプリでテンプレートを利用してみる
では、ここまで説明してきた内容を踏まえて、実際にテンプレートの利用例を示していきたいと思います。
この Django 入門 に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでビューを利用してみる で作成したウェブアプリに対してテンプレートの仕組みを導入する形で、テンプレートの利用例を示していきたいと思います。
【Django入門3】ビュー(View)の基本とURLのマッピング掲示板アプリのプロジェクト一式の公開先
この Django 入門 の連載を通して開発している掲示板アプリのプロジェクトは GitHub の下記レポジトリで公開しています。
https://github.com/da-eu/django-introduction
また、前述のとおり、ここでは前回の連載の 掲示板アプリでビューを利用してみる で作成したプロジェクトをベースに変更を加えていきます。このベースとなるプロジェクトは下記のリリースで公開していますので、必要に応じてこちらからプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-view
さらに、ここから説明していく内容の変更を加えたプロジェクトも下記のリリースで公開しています。ソースコードの変更等を行うのが面倒な場合など、必要に応じて下記からプロジェクト一式を取得してください。
https://github.com/da-eu/django-introduction/releases/tag/django-template
テンプレートフォルダの作成
では、前述の通り、掲示板アプリでビューを利用してみる で示したウェブアプリを変更してテンプレートを導入していきたいと思います。
まず、テンプレートファイルの置き場所となるフォルダを作成します。テンプレートファイルの置き場所 で解説したように、テンプレートファイルの置き場所は下記のパスとなります。
プロジェクト名/アプリ名/templates/アプリ名/
今回作成しているウェブアプリにおいて、プロジェクト名は testproject
、アプリ名は forum
となります。
現状では、templates
フォルダ以下が未作成の状態となっていますので、forum
フォルダの下に templates
フォルダを、さらにその templates
フォルダの下に forum
フォルダを新規作成してください。
例えば、ターミナル等を利用しているのであれば、プロジェクトフォルダ testproject
の中(manage.py
が存在するフォルダ)で下記のコマンドを実行することで、これらのフォルダを作成することができます。
% mkdir forum/templates
% mkdir forum/templates/forum
スポンサーリンク
テンプレートファイルの作成
フォルダが作成できれば、次はいよいよテンプレートファイルを作成していきます。
現状の forum
の views.py
では下記の5つの関数を用意しています。
index_view
:トップページを表示する(リダイレクトするだけ)users_view
:ユーザー一覧ページを表示するuser_view
:特定のユーザーの詳細情報ページを表示するcomments_view
:投稿済みコメント一覧ページを表示するcomment_view
:特定のコメントの詳細情報ページを表示する
index_view
を除くビューの関数では、自身の関数の中で HTML を生成し、それをボディとする HttpResponse
のインスタンスを返却するようになっています。
index_view
を除く4つのビューの関数で、この HTML の生成が行えるように、ここから下記の4つのテンプレートファイルを導入していきます。
users.html
:ユーザー一覧ページのテンプレートuser.html
:特定のユーザーの詳細情報ページのテンプレートcomments.html
:投稿済みコメント一覧ページのテンプレートcomment.html
:特定のコメントの詳細情報ページのテンプレート
つまり、これらのテンプレートファイルを導入することで、各種ビューの関数で生成する HTML の基になるテンプレートファイルがテンプレートから提供されるようにしていきます。
したがって、各種ビューの関数はテンプレートファイルから参照される変数を提供するためのコンテキストの作成と、render
関数による HTML の生成および render
関数の返却値の返却を行えば良いだけになります(render
関数の引数として、使用するテンプレートファイルの指定を行う必要もあります)。
つまり、テンプレートがテンプレートファイルを提供する役割を果たすため、ビューはページの雛形に関しては考える必要がなく、雛形に対して埋め込みたいデータの準備、および、表示したいページに合わせた雛形(テンプレートファイル)の選択を行えば良いだけになります。その分、ビューの関数の実装はシンプルになります。
また、テンプレートの継承 で説明したように、テンプレートファイルは親テンプレートファイルを継承することが可能です。今回は、親テンプレートファイルとして base.html
を作成し、この base.html
を継承する形で上記の4つのテンプレートファイルを子テンプレートファイルとして作成していきたいと思います。
ということで、ここからは base.html
+上記の4つの子テンプレートファイルの作成を行なっていきたいと思います。前述の通り、これらのテンプレートファイルは下記フォルダの中に作成していくことになります。
testproject/forum/templates/forum/
base.html
では、まずは親テンプレートファイルとなる base.html
を作成していきます。
今回は、base.html
を下記のように作成したいと思います。
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<header>
<nav class="navbar navbar-expand navbar-dark bg-primary">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link navbar-brand" href="{% url 'index' %}">掲示板</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'users' %}">ユーザー一覧</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'comments' %}">コメント一覧</a>
</li>
{% comment 'フォーム説明後に追加' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'post' %}">コメント投稿</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">ユーザー登録</a>
</li>
{% endcomment %}
</ul>
</nav>
</header>
<main class="container my-5 bg-light">
{% block main %}{% endblock %}
</main>
</body>
</html>
少し複雑なテンプレートファイルになっていますが、この base.html
は下図のような雛形を提供するテンプレートファイルとなっています。
要は、ナビゲーションバーと title
ブロックと main
ブロックを提供する親テンプレートファイルです。この base.html
を継承した子テンプレートファイルには、ナビゲーションバーが引き継がれることになります。また、base.html
で読み込んでいる CSS 等も引き継がれることになります。
また、base.html
では title
ブロックと main
ブロックの2つを定義しているため、子テンプレートファイル側で、各テンプレートで実現したい雛形となるように各ブロックのコンテンツを記述してやれば、それらのコンテンツが base.html
に埋め込まれることになります。
他のポイントについても説明しておくと、まず、この base.html
では Bootstrap の読み込みを行うようにしています。
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
この Bootstrap が読み込まれることで、Bootstrap で定義されているスタイルを各種テンプレートファイルから利用できるようになります。そして、テンプレートファイルの各タグで利用したいスタイルを class
に指定することで、見た目の綺麗なウェブアプリを簡単に実現することができます。
もちろん Django においても自身で CSS ファイルを作成し、自身で定義したスタイルを利用することも可能です。ただし、その場合はウェブアプリが動作するサーバーから CSS ファイルを提供できるようにする必要があり、そのための手順が若干面倒です。
そのため、今回は外部サイトから Bootstrap を取得し、Bootstrap で定義されるスタイルを利用するようにしています。もし、自身で作成した CSS ファイルを利用したい場合は、下記ページで手順を示していますので、詳しくは下記ページを参考にしていただければと思います。
【Django】静的ファイルの扱い方(画像・CSS・JS)また、base.html
では下記の部分を Django テンプレート言語 で説明したコメントを利用してコメントアウトをしています。
{% comment 'フォーム説明後に追加' %}
<li class="nav-item">
<a class="nav-link" href="{% url 'post' %}">コメント投稿</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'register' %}">ユーザー登録</a>
</li>
{% endcomment %}
上記に関しては、次の連載で説明する『フォーム』に関しての説明を終えた後に有効化を行います。
掲示板への投稿を Comment
クラスで扱っているため、コメントアウトの comment
と混在しやすいので注意してください
正直、後から考えて、このクラスの名前の付け方に失敗したなぁと思っています…
また、コメント以外にも、base.html
では Django テンプレート言語 で紹介したテンプレートタグの利用を行なっています。それが、下記の url
タグになります。base.html
を確認すれば、いくつかの箇所でこのテンプレートタグを利用していることが確認できると思います。
<a class="nav-link" href="{% url 'users' %}">ユーザー一覧</a>
この url
タグは、引数で与えられた URL 名から URL そのものに変換を行い、その変換結果を HTML に埋め込むタグとなります。
上記の場合は、'users'
が引数で与えられる URL 名となります。さらに、/forum/
から始まる URL に関しては forum
フォルダの urls.py
が参照されるようになっており、この urls.py
で下記のように URL に 'users'
という名前を付けるように設定しています。
path('users/', views.users_view, name='users'),
したがって、上記の url
タグ部分は /forum/users/
に置き換えられ、さらに a
タグによって ユーザー一覧
という文字列に対してこの URL へのリンクが貼られることになります。
このように、url
タグは URL の名前から URL への置き換えを実現する重要なテンプレートタグであり、利用する機会も多いと思いますので是非覚えておいてください。
また、URL の名前の設定方法等については前回の連載となる下記ページで紹介していますので、復習したい方は下記ページを参照していただければと思います。
【Django入門3】ビュー(View)の基本とURLのマッピングusers.html
続いて、子テンプレートファイル側の作成を行なっていきます。
まずは users.html
を作成していきます。このテンプレートファイルはユーザー一覧を表示するための雛形となります。
今回は、下記のように users.html
を作成したいと思います。この users.html
は、変数 users
を参照しています。そのため、ビューの関数では 'users'
キーを持つコンテキストを用意してやる必要があります。後述の ビューの変更 で具体例を示しますが、今回は 'users'
キーの値として User
クラスのインスタンスのリストを指定したコンテキストを用意します。
{% extends "forum/base.html" %}
{% block title %}
ユーザー一覧
{% endblock %}
{% block main %}
<h1>ユーザー一覧(全{{ users|length }}人)</h1>
<table class="table table-hover">
<thead>
<tr>
<th>ユーザー</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td><a href="{% url 'user' user.id %}">{{ user.username }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
ポイントは、親テンプレートファイルである base.html
を継承している点と、base.html
に用意されている title
ブロックと main
ブロックのコンテンツの実装を行っている点になります。
また、このテンプレートファイルでは Django テンプレート言語 で紹介した length
フィルターと truncatechars
フィルターを利用しています。例えば、{{ users|length }}
部分はコンテキストで与えられる users
の要素数に加工されて HTML に埋め込まれることになります。
さらに、下記部分では for
タグを利用しており、これにより下記の 〜ループ処理〜
の部分が users
の各要素 user
に対して繰り返し実行されることになります。この繰り返しの中では users
の各要素 user
のデータ属性を参照してリンクの設定や名前の HTML への埋め込みなどを行うようにしています。
{% for user in users %}
〜ループ処理〜
{% endfor %}
今回の例ではクラスである User
のインスタンスの “リスト” をテンプレートファイルから参照することを想定していますが、実際に Django でウェブアプリを開発する際には、”クエリーセット” というモデルのインスタンスの集合を参照する機会が非常に多いです。まだモデルについての説明を行なっていないため、現状単なるクラスを利用しているだけです。ですが、クエリーセットを参照するようになったとしても、上記のような for
タグで各インスタンスに対して処理を行う部分はそのまま利用できますし、この for
タグも利用する機会が多いので是非覚えておいてください。
もう一点重要になるのが、下記の url
タグの利用部分になります。この url
タグは base.html
でも使用しているのですが、その際は引数に URL の名前のみを指定していましたが、この users.html
では、URL の名前に加えて user.id
も引数に指定するようになっています。
<td><a href="{% url 'user' user.id %}">{{ user.username }}</a></td>
このように引数を指定した場合、その url
タグ部分は、”URL の名前” が URL に変換された結果の後ろ側に、user.idの値/
が追加された状態の URL に置き換えられることになります。たとえば、user.id
の値が 3
である場合は、上記の url
タグ部分は次の図のような URL に置き換えられることになります。
このような記述で url
タグを利用する機会も多いので、これに関しても是非覚えておいてください。
user.html
続いて user.html
を作成していきます。このテンプレートファイルは特定のユーザーの詳細情報を表示するための雛形となります。
今回は、下記のように user.html
を作成したいと思います。この user.html
は、変数 user
を参照しています。そのため、ビューの関数では 'user'
キーを持つコンテキストを用意してやる必要があります。後述の ビューの変更 で具体例を示しますが、今回は 'user'
キーの値として User
クラスのインスタンスを指定したコンテキストを用意します。
{% extends "forum/base.html" %}
{% block title %}
{{ user.username }}
{% endblock %}
{% block main %}
<h1>ユーザー({{ user.id }})</h1>
<h2>{{ user.username }}の情報</h2>
<table class="table table-hover">
<tbody>
<tr><th>名前</th><td>{{ user.username }}</td></tr>
<tr><th>連絡先</th><td>{{ user.email|urlize }}</td></tr>
<tr><th>年齢</th><td>{{ user.age }}</td></tr>
</tbody>
</table>
{% endblock %}
継承やブロックのコンテンツの実装を行なっている点は users.html
と同様になります。
ポイントを挙げるとすれば、{{ user.email|urlize }}
の部分で、この部分では urlize
フィルターを利用しています。これにより、|
の前側の部分の URL やメールアドレスのテキストが、リンクを設定された状態のものに置き換えられることになります。
comments.html
次は comments.html
を作成します。このテンプレートファイルはコメント一覧を表示するための雛形となります。
今回は、下記のように comments.html
を作成したいと思います。この comments.html
は、変数 comments
を参照しています。そのため、ビューの関数では 'comments'
キーを持つコンテキストを用意してやる必要があります。後述の ビューの変更 で具体例を示しますが、今回は 'comments'
キーの値として Comment
クラスのインスタンスのリストを指定したコンテキストを用意します。
{% extends "forum/base.html" %}
{% block title %}
コメント一覧
{% endblock %}
{% block main %}
<h1>コメント一覧(全{{ comments|length }}件)</h1>
<table class="table table-hover">
<thead>
<tr>
<th>本文</th>
</tr>
</thead>
<tbody>
{% for comment in comments %}
<tr>
<td><a href="{% url 'comment' comment.id %}">{{ comment.text|truncatechars:20 }}</a></td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
users.html
とは一覧として表示する対象が異なるものの、テンプレートファイルとしては同様の作りとなります。ということで、ポイントの説明は省略させていただきます。
comment.html
最後に用意するのは comment.html
になります。このテンプレートファイルは特定のコメントの詳細情報を表示するための雛形となります。
今回は、下記のように comment.html
を作成したいと思います。この comment.html
は、変数 comment
を参照しています。そのため、ビューの関数では 'comment'
キーを持つコンテキストを用意してやる必要があります。後述の ビューの変更 で具体例を示しますが、今回は 'comment'
キーの値として Comment
クラスのインスタンスを指定したコンテキストを用意します。
こちらも、特にコメントアウトしている部分以外は user.html
と同様の作りのため、ポイントの説明は省略します。
{% extends "forum/base.html" %}
{% block title %}
コメント
{% endblock %}
{% block main %}
<h1>コメント({{ comment.id }})</h1>
<table class="table table-hover">
<tr><td>本文</td><td>{{ comment.text }}</td></tr>
<tr><td>投稿日</td><td>{{ comment.date|date }}</td></tr>
</table>
{% endblock %}
ビューの変更
テンプレートファイルが全て用意できましたので、次はビューの変更を行なっていきます。
各ビューの関数で行うことは、基本的にはコンテキストを用意し、さらに render
関数を実行することとのみとなります。ただし、render
関数の引数には使用するテンプレートファイルを指定する必要があり、各ビューの役割に応じて使用するテンプレートファイルを適切に選択して指定する必要があります。
具体的には、現状のビューには下記の関数を用意しており(index_view
はリダイレクトするだけなので省略)、
users_view
:ユーザー一覧ページを表示するuser_view
:特定のユーザーの詳細情報ページを表示するcomments_view
:投稿済みコメント一覧ページを表示するcomment_view
:特定のコメントの詳細情報ページを表示する
テンプレートから提供されるテンプレートファイルは下記の4つとなっていますので、
users.html
:ユーザー一覧ページのテンプレートuser.html
:特定のユーザーの詳細情報ページのテンプレートcomments.html
:投稿済みコメント一覧ページのテンプレートcomment.html
:特定のコメントの詳細情報ページのテンプレート
各ビューで使用するテンプレートファイルは下記のようになります。
users_view
:users.html
user_view
:user.html
comments_view
:comments.html
comment_view
:comment.html
なので、各種ビューの関数は、上記の使用するテンプレートファイルから参照される変数に合わせたコンテキストを作成し、上記のテンプレートファイルのパスとコンテキストを引数に指定して render
関数を実行することになります。テンプレートファイルのパスは templates
フォルダからの相対パスで記述する必要がある点に注意してください。
ということで、ここまで説明してきた内容を考慮すれば、views.py
は下記のように変更してやれば良いことになります。render
関数を利用するため、render
関数の import
が必要な点に注意してください。
from django.http import Http404
from django.shortcuts import redirect, render
class User:
def __init__(self, id, username, email, age):
self.id = id
self.username = username
self.email = email
self.age = age
import datetime
class Comment:
def __init__(self, id, text, date):
self.id = id
self.text = text
self.date = date
users = [
User(1, 'Yamada Taro', 'taro@yamada.jp', 18),
User(2, 'Yamada Hanako', 'hanako@yamada.jp', 22),
User(3, 'Sato Saburo', 'saburo@sato.jp', 53),
User(4, 'Takahashi Shiro', 'shiro@takahashi.jp', 64)
]
comments = [
Comment(1, 'おはようございます', datetime.datetime(2023, 3, 4, 12, 4, 0)),
Comment(2, 'いい天気ですねー', datetime.datetime(2023, 4, 5, 16, 21, 0)),
Comment(3, '明日もよろしくお願いします', datetime.datetime(2000, 12, 25, 1, 55, 0)),
Comment(4, 'おやすみなさい', datetime.datetime(2024, 1, 1, 1, 37, 0)),
Comment(5, '山路を登りながら、こう考えた。智に働けば角が立つ。情に棹させば流される。意地を通とおせば窮屈だ。とかくに人の世は住みにくい。', datetime.datetime(2012, 10, 8, 3, 49, 0)),
]
def index_view(request):
return redirect(to='comments')
def users_view(request):
context = {
'users' : users
}
return render(request, 'forum/users.html', context)
def user_view(request, user_id):
if user_id > len(users) or user_id < 1:
raise Http404('Not found user')
user = users[user_id - 1]
context = {
'user' : user
}
return render(request, 'forum/user.html', context)
def comments_view(request):
context = {
'comments' : comments
}
return render(request, 'forum/comments.html', context)
def comment_view(request, comment_id):
if comment_id > len(comments) and comment_id < 1:
raise Http404('Not found comment')
comment = comments[comment_id - 1]
context = {
'comment' : comment
}
return render(request, 'forum/comment.html', context)
この変更のポイントですが、まず、下記ページの 掲示板アプリでビューを利用してみる で作成した views.py
では、views.py
の各関数の中で HTML の構成を自分で定義するようにしていました。
なので、関数の中身が若干複雑になっていましたが、上記の views.py
では HTML の構成は全てテンプレート側で定義するようになっているため、ほぼ、コンテキストの用意と render
関数の実行のみで処理が完結するようになっています。そして、これによって関数の中身がスッキリしていることを確認できると思います。
また、各関数ではテンプレートファイルが参照する変数に合わせてコンテキストを用意するようにしています。特に user_view
と comment_view
では引数で ID が指定されるようになっており、その ID に応じたインスタンスをコンテキストにセットするようにしていますが、不正な ID が指定された場合は、今まで通り例外を発生させる必要があります。
動作確認
ソースコードの変更は以上になります。最後に動作確認を行なっておきましょう!
開発用ウェブサーバーの起動
まずは、ウェブアプリにアクセスできるよう、Django 開発用ウェブサーバーを起動してください。この開発用ウェブサーバーの起動は、manage.py
が存在するフォルダ(プロジェクトの testproject
フォルダの中)で下記コマンドを実行することで実現できます。
% python manage.py runserver
これにより、ウェブブラウザ等のクライアントからリクエストを受け取るウェブサーバーが起動することになります。
ページ表示の確認
ということで、次はウェブブラウザを開き、アドレスバーに下記 URL を指定します。
http://localhost:8000/forum/comments/
そうすると、下の図のようなページが表示されると思います。
上記 URL へのリクエストがあった際には comments_view
が実行されることになり、comments_view
でははテンプレートファイル comments.html
から HTML が生成されることになります。comments.html
からは base.html
が継承され、上の図のように base.html
で用意したナビゲーションバーが表示されるようになっています。
また、base.html
で用意した title
ブロック部分と main
ブロック部分に、comments.html
に記述した各ブロックのコンテンツが埋め込まれるようにもなっています。
さらに、comments.html
では comments
を参照しており、comments_view
からはコンテキストとして views.py
で定義している comments
のリストが渡されるため、for
タグの繰り返しによって views.py
で定義している comments
の各要素の情報が HTML に埋め込まれて表示されるようになっています。ページの表示結果から、views.py
のリスト comments
の要素数と同じだけの本文が表示されていることが確認できると思います。また、本文として表示される内容が、comments
の各要素のデータ属性 text
と同様になっている点も確認できると思います。
前述のとおり、コンテキストの 'comments'
キーの要素の値には views.py
で定義されるリスト comments
が指定されているため、このリスト comments
に要素の追加や要素の削除の変更を加えると、表示されるページも変化されることが確認できると思います
ただし、変更が反映されるのは、開発用ウェブサーバーを再度起動したタイミングになるので注意してください
上記のページの結果より、Django テンプレート言語のフィルターの効果も確認できます。ページに表示されている “全5件” の 5
に関しては comments
を length
フィルターで加工された結果になります。さらに、コメントの最後の1つに関しては本文が途切れており、これは truncatechars
フィルターで文字数 20
でコメントの文字列が切り詰められるよう加工されたためになります。
このように、上図のようなページの表示結果から、テンプレートの継承やテンプレートタグ・フィルターの効果等を確認できると思います。
次は、コメントの詳細情報ページを確認してみましょう。ページに表示されているコメントの本文部分には、/forum/comment/N/
の URL のリンクが設定されています(N
はコメントの ID )。そして、/forum/comment/N/
の URL のリクエストを受け取った時には comment_view
が実行されるよう urls.py
を実装しているため、コメントの本文のリンクをクリックすればコメントの詳細情報ページが表示されることになります。ということで、いずれかのコメントの本文部分をクリックして、コメントの詳細情報ページを表示してみましょう!
前述のとおり、コメントの本文のクリックによって comment_view
が実行されることになります。そして、base.html
で用意された各ブロックに comment.html
に記述されたブロックのコンテンツが埋め込まれる形で HTML が生成され、それがページとして表示されることになります。
この場合も、全体的な見た目は先ほど表示したコメント一覧のページと同様になります。これは、全体的な見た目は base.html
で定義しており、各種テンプレートがこの親テンプレートファイルを継承するようになっているためです。このように、継承を利用することで、全体的な見た目をウェブアプリ内のページ間で統一しやすくなります。
さらに、ナビゲーションバーの ユーザー一覧
をクリックすれば、次はユーザーの一覧ページを表示することができます。
この場合は users_view
が実行され、views.py
に定義されている users
の各要素の情報が表示されることになります。また、コメント一覧ページの時同様に、ユーザー名をクリックすれば、そのユーザーのページが表示されることも確認できると思います。
以上で、今回の動作確認は完了となります!
下記ページの 掲示板アプリでビューを利用してみる で紹介したページの表示例に比べれば、かなり見た目としては綺麗になったのではないかと思います。
【Django入門3】ビュー(View)の基本とURLのマッピングただし、これは別にテンプレートを利用するようになったからではなく、テンプレートを利用しなくても、ビューに直接記述する HTML を変更してやれば同じような見た目のページを実現することは実は可能です。
ですが、ビューに直接 HTML を記述すると views.py
のコード量が多くなり、メンテナンス性が下がって変更しにくくなったりバグが発生しやすくなったりしてしまいます。それに対し、ビューとテンプレートとで役割を分割し、HTML の構成の定義を全てテンプレート側に行わせるようにすることで、各ファイルのコード量も減ってメンテナンス性が上がり、変更もしやすくなります。
さらに、継承を利用することで各テンプレートファイルのコード量も減りますし、それに加えてウェブアプリ内の各ページの見た目も統一しやすくなります。全体的な構造が親テンプレートファイルに集約されるので、変更・修正もしやすくなります。
このあたりの、テンプレートを利用することで得られるメリットを、今回紹介した例で実感していただければ幸いです。最初はテンプレートファイルを利用する必要があって面倒に感じるかもしれませんが、開発するウェブアプリの規模が大きくなると、テンプレートを利用するメリットがより発揮されるようになると思います。
スポンサーリンク
まとめ
このページでは、Django におけるテンプレートについて解説しました!
テンプレートはビューに対して HTML の雛形を提供する役割を持ち、ビューから HTML の構造を定義する役割を分離することで、メンテナンス性の高いウェブアプリを実現することが可能となります。
また、テンプレートは単なる HTML だけでなく、Django テンプレート言語を利用して記述することになります。Django テンプレート言語を利用することで、テンプレートファイルから変数を参照したり、テンプレートタグを利用してちょっとした処理を実現することもできますし、フィルターを利用して変数を加工したりすることもできます。
さらに、テンプレートは継承機能を持っており、これを利用することでテンプレートファイルの開発効率が上がりますし、さらにウェブアプリ内のページ間の構造を統一することもしやすくなります。
今回は深く説明しませんでしたが、もちろん CSS を読み込んでスタイルを適用することも可能です。ウェブアプリの見た目に拘りたい方は、是非 CSS やスタイルについても学んでみると良いと思います。
今回紹介したテンプレートの導入により、ページの表示に関してはかなりまともなウェブアプリを実現することができるようになりました!次の下記ページの連載ではフォームの解説を行い、フォームを導入することで、ユーザーからデータを受け取ったり、ユーザーと対話可能なウェブアプリを実現していきたいと思います!
【Django入門5】フォームの基本