【Django入門4】テンプレート(Template)の基本

Djangoのテンプレートの解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、Django におけるテンプレート(Template)について解説していきます。

Django で開発するウェブアプリの基本構造は MTV モデルとなっています。

前回、下記ページで V の部分のビューに関して解説を行いました。

Djangoのビューの機能についての解説ページアイキャッチ 【Django入門3】ビュー(View)の基本とURLのマッピング

今回は、T の部分のテンプレートについて解説していきます。テンプレートは MTV モデルの1部分であり、Django でのウェブアプリ開発において重要な存在となります。

是非、このページを読み進めていただき、テンプレートがどんなものであるかを理解していっていただければと思います!

テンプレートの基本

では、テンプレートについて解説していきます。

テンプレート

テンプレートとは、ページの雛形を提供するアプリの構成部分となります。

このテンプレートファイルを利用することで、ウェブアプリで無数のページ表示を行うことが可能になります。

テンプレートファイルを利用して様々なページ表示を実現する様子

例えば、ウェブアプリでログインユーザーの情報を表示する『マイページ』を実現することを考えてみましょう!

このマイページは、当然ユーザーによって表示される情報が異なることになります。例えば、このページで『ユーザー名』『アイコン画像』『自己紹介文』が表示されるとすると、これらの情報はユーザーによって異なるはずです。

同じ種類のページでも部分部分がリクエストによって異なる様子

したがって、このようなマイページの表示を実現するためには、ユーザーの数だけ HTML を用意し、ログイン中のユーザーに応じた HTML をウェブブラウザ等のクライアントに返却するようにする必要があります。

各ページ向けのHTMLを用意しておく様子

この HTML の用意の仕方には様々な方法があります。最も単純な方法は、ユーザーごとに HTML を事前に作成しておく方法になると思います。ただ、これは単純ですが大変で、ユーザーの数の分だけ HTML を作成する必要があります。例えばウェブアプリのユーザーが一万人いれば、一万個の HTML を作成しておく必要があります…。

事前に全てのHTMLを作成して用意する方法の説明図

他の方法としては、ページの雛形部分だけを事前に作成しておき、ユーザーからマイページの表示のリクエストを受けた際に、そのユーザーの情報を雛形に埋め込んで HTML を用意する方法になります。これが、このページの主題となるテンプレートの考え方となります。

先程のマイページに注目してみれば、これらのページはユーザー毎に『ユーザー名』『アイコン画像』『自己紹介文』の内容は異なるものの、それ以外の部分は同じです。このように、同じ種類のページであれば、ページの雛形としては同じものが利用できるケースが多いです。

各ページの雛形は共通であることを示す図

したがって、ページの雛形部分だけを事前に作成しておき、ユーザーからマイページの表示のリクエストを受けた際にユーザーに応じた『ユーザー名』『アイコン画像』『自己紹介文』を雛形に埋め込んで HTML を生成して返却するようにすれば、1つの雛形から全ユーザーのマイページの表示を実現することができるようになります。

雛形にユーザーに応じた情報を埋め込んでHTMLを生成する様子

このように、基本的にウェブアプリで表示するページは雛形動的に変更が必要な部分(動的な部分)の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
render(request, template_name, context=None, content_type=None, status=None, using=None)

引数が多いですが、指定が必須なのは requesttemplate_name の2つになります。ただし、コンテキスト で説明するように、動的に HTML の一部を変化させるためには context の指定も必要となります。 

まず、request 引数には HttpRequest のサブクラス のインスタンスを指定する必要があります。この引数には、ビューが Django フレームワークから受け取った引数 request をそのまま指定してやれば良いです。

また、template_name 引数にはテンプレートファイルのパスを指定します。

このように引数を指定してビューが render 関数を実行すれば、template_name 引数に指定されたテンプレートファイルの記述に基づいて HTML が生成され、その生成結果をボディとするレスポンスを返却値として取得することができます。

render関数の引数と返却値の説明図

前述の通り、テンプレートファイルは複数用意されることが多いため、リクエストに応じて適切なテンプレートファイルを選択し、そのファイルのパスを render 関数の template_name 引数に指定する必要があります。

コンテキスト

また、render 関数には引数 context にコンテキストを指定して渡すことも可能です。

コンテキストとは

このコンテキストは辞書形式のデータであり、キー を各要素に持つデータとなります。

コンテキストの説明図

テンプレートファイルからは変数を参照することが可能です。この参照される変数の値を提供するのがコンテキストになります。

コンテキストと render 関数

テンプレートファイルにおいては、{{}} の部分は変数として扱われることになります。{{}} の内側には変数名を記述します。要は、テンプレートファイルの {{ 変数名 }} 部分は変数として扱われます。そして、render 関数で HTML が生成される際に {{ 変数名 }} 部分が変数の値に置き換えられることになります。

テンプレートファイルの変数部分が実際の値に置き換えられる様子

この変数の値は render 関数の中でコンテキストから取得されます。より具体的には、テンプレートファイルに {{ 変数名 }} が記述されている場合、render 関数の context 引数に指定されたコンテキストの '変数名' キーの値が取得されることになります。そして、HTML 生成時に {{ 変数名 }} 部分が '変数名' キーの値に置き換えられることになります。

例えば、下記のようなコンテキストを render 関数の context 引数に指定した場合、render 関数実行時にテンプレートファイルの {{ name }} 部分が YamadaHanako に置き換えられることになります。

コンテキストの例
context = {
    'name' : 'YamadaHanako',
}

こんな感じで、テンプレートファイルでは {{ 変数名 }} 部分を変数として扱うことができ、render 関数での HTML 生成時に {{ 変数名 }} が実際の値に置き換えられることになります。これを利用すれば、最初に説明したような雛形にデータを埋め込むような処理が実現できます。そして、置き換えられる値は、コンテキストにおけるキーが '変数名' となります。例えばコンテキストの変数名が context であれば、context['変数名'] で取得される値に置き換えられることになります。

コンテキストに応じた値がHTMLに埋め込まれる様子

前述の通り、コンテキストは言ってしまえば単なる辞書データです。なので、通常の辞書と同様に作成することができます。もちろん、コンテキストには複数の要素を持たせても問題ありません。

ただし、コンテキストには、テンプレートファイルから参照される全ての 変数名 のキーを持たせておく必要があるので、この点には注意してください。

例えば、下の図のテンプレートファイルには {{ name }}{{ age }}{{ comment }} の3つの変数が存在するため、用意するコンテキストには少なくとも 'name' キー・'age' キー・'comment' キーの要素が必要となります。 

contextに用意する必要のあるキーを説明する図

コンテキストの重要性

テンプレート で、ウェブアプリのページは雛形(共通部分)と動的に変化させる部分の2つから構成されると説明しましたが、コンテキストは、後者の動的に変化させることを実現するために必要なデータとなります。

また、ウェブアプリでは、リクエストに応じたレスポンス(もっと詳細に言えば、リクエストに応じた機能を実行し、その機能によって得られた結果に応じたレスポンス)を返却できるように開発することが重要となります。

そして、これはコンテキストを動的に変化させることで実現可能です。

テンプレートファイルで変数を扱うようにしておけば、コンテキストの各要素に応じて生成される HTML が変化することになります。したがって、このコンテキストをリクエストに応じて動的に変化させるようにすれば、生成される HTML もリクエストに応じて動的に変化させることができるようになります。つまり、ウェブアプリにおける「リクエストに応じたレスポンス(HTML)の返却」は、このコンテキストを動的に変化させることで実現できます。

リクエストに応じたコンテキストを生成することで、リクエストに応じたHTMLがレスポンスとして返却できることを示す図

例えば、前述の下記のような辞書データを context 引数に指定した場合、render 関数実行時にテンプレートファイルにおける {{ name }} 部分が YamadaHanako に変化することになります。

コンテキストの例1
context = {
    'name' : 'YamadaHanako',
}

それに対し、下記のような辞書データを context 引数に指定した場合、render 関数実行時にテンプレートファイルにおける {{ name }} 部分が TanakaJiro に変化することになります。

コンテキストの例2
context = {
    'name' : 'TanakaJiro',
}

このように、テンプレートファイルで変数を参照するようにしておけば、コンテキストに応じた HTML を生成することができます。さらに、コンテキストの要素をリクエストに応じて設定するようにすれば、1つのテンプレートファイルからリクエストに応じた無数の HTML を生成することが可能となります。

テンプレートが同じでもコンテキストが違えば生成されるHTMLも異なる様子

こんな感じで、コンテキストはリクエストに応じたレスポンスを返却することを実現するための重要なデータとなります。

そして、このコンテキストを用意するのはビューの役割となります(render 関数の実行に関してもビューの役割)。ここまでの説明のとおり、ビューは、クライアント(ユーザー)からのリクエストに応えられるよう、リクエストに応じたコンテキストを用意することが必要となります。例えば、データベースから検索して取得したデータからコンテキストを生成するようにし、さらにリクエストの内容によってデータベースへの検索条件を変化させるようにすれば、クライアントからのリクストに応じたコンテキストが用意できることになります。

テンプレートファイル

続いて、テンプレートファイルの詳細について説明を行なっていきます。

スポンサーリンク

HTML の基となるファイル

前述の通り、テンプレートファイルはページの雛形であり、HTML の基となるファイルです。拡張子には HTML 同様に .html が利用されます。

このテンプレートファイルからは HTML を生成することが可能で、前述で解説した通り、テンプレートファイルから HTML を生成する際には render 関数が利用されます。この render 関数を実行することで、テンプレートファイルの記述やコンテキストに従った HTML の生成が行われます。

テンプレートファイルの構成

このテンプレートファイルは、基本的には『HTML』と『Django テンプレート言語』と呼ばれる Django 特有の構文から構成されるファイルとなります。

前述で示した {{ 変数名 }} も Django テンプレート言語に従った変数を扱うための記述になります。

render 関数で HTML が生成される際には、『Django テンプレート言語』部分のみが動的に変化することになります。逆に、『Django テンプレート言語』以外の部分はそのまま出力されることになります。つまり、テンプレートファイルにおける『HTML』部分は render 関数を実行しても変化しません。

したがって、ページの雛形部分、つまり共通部分は HTML で記述を行い、動的に変化させたい部分はDjango テンプレート言語で記述することになります。

render関数の実行によってDjangoテンプレート言語部分が動的に変化する様子

Django テンプレート言語

この Django テンプレート言語は {} で囲んだ形式で記述します。

つまり、雛形を構成する HTML の中に {} を記述しておけば、その部分のみが render 関数実行時に動的に変化することなります。

この Django テンプレート言語には大きく分けて4種類のものが存在します。

  • 変数
  • テンプレートタグ
  • フィルター
  • コメント

ここでは簡単に、各種 Django テンプレート言語について説明します。

変数

『変数』は {{ 変数名 }} の形式で記述を行います。{{ 変数名 }} はテンプレートファイルの毎回動的に変化させたい部分に記述します。

前述の コンテキスト で解説したように、テンプレートファイルからは変数を参照することができます。その変数の参照先はコンテキストとなります。{{ 変数名 }} がテンプレートファイルに記述されている場合、{{ 変数名 }} 部分が、render 関数実行時にコンテキストの “キーが '変数名' の要素の値” に置き換えられることになります。

例えば、{{ name }} を記述したテンプレートファイルと、下記のコンテキストを render 関数の引数に指定した場合、render 関数実行時に {{ name }} の部分が name キーの値である YamadaHanako に置き換えられた HTML が生成されることになります。

コンテキストの例1
context = {
    'name' : 'YamadaHanako',
}

また、テンプレートファイルでは {{ user.name }} のようにインスタンスのデータ属性を参照するようなことも可能です。この場合、コンテキストの 'user' キーの値としてはデータ属性 name を持つオブジェクトを指定する必要があります。

テンプレートファイルからオブジェクトのデータ属性を参照する様子

特に多いのが、モデルのインスタンスをコンテキストの値に設定する例となります。この実例はモデルの解説ページで紹介します。

テンプレートタグ

2つ目が『テンプレートタグ』で、これは {% タグ名 %} の形式で記述します。このテンプレートタグは、単に『タグ』とも呼ばれます。

テンプレートタグの用途は様々で一言で説明するのは困難ですが、簡単に言えば何かしらの処理や制御を行うための記述であると考えると良いと思います。テンプレートファイルに {% タグ名 %} の記述があった場合、render 関数内で タグ名 に応じた処理が行われ、その結果が {% タグ名 %} 部分に埋め込まれることになります。

また、タグ名 によっては、{%%} の中に単にタグ名を記述するだけでなく、引数の指定が必要なものもありますし、処理の終端(例えばループ処理の終端)を示すための {%%} を別途記述する必要のあるものもあります。

また、引数には変数を指定することが可能なものもあります。この場合は、テンプレートファイルから変数が参照されることになりますので、その参照先としてコンテキストに変数に応じた要素を用意しておく必要があります。

例えば {% for user in users %}users の各要素に対してループを行うための記述となります。この for タグにおいては、処理をループさせる範囲を特定するために終端を表す {% endfor %} の記述が必要になります。

例えば下図のような for タグがテンプレートファイルに記述されている場合、{% for user in users %}{% endfor %} の内側に記述された内容が users の要素数分繰り返し HTML に出力されることになります。

forタグの説明図

また、この記述における 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|lengthusers の要素数に変換されることになりますので、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関数実行時のパス指定
render(request, 'app1/index.html')

要は、templates フォルダから見たテンプレートファイルの相対パスを指定すれば良いことになります。

必要となるテンプレートファイル

ここまで説明してきたように、テンプレートの仕組みを利用することで、1つのテンプレートファイルから無数の HTML を生成することができるようになります。

ただし、テンプレートファイルから生成可能な HTML は同じ雛形のものだけになります。要は同じような構成の HTML しか生成できません。そのため、構成が大きく異なるような HTML を生成するためには、別のテンプレートファイルを用意する必要があります。

そのため、ウェブアプリの開発時には基本的には複数のテンプレートファイルを用意しておくことになります。

基本的には、ウェブアプリで表示したいページの種類分のテンプレートファイルを作成することになると思います。例えば、ウェブアプリで『ログインページ』『マイページ』『登録ユーザーの一覧ページ』の3つの種類のページを表示できるようにしたいのであれば、3つのテンプレートファイルを作成する必要がある可能性が高いです。

ただし、ページの種類が異なってもページの雛形は同様である場合もあるので、その場合は1つのテンプレートファイルを複数のページで使い回すようなことも可能です。

例えば『マイページ』と『他のユーザーのページ』はページの種類は異なるものの、ページの雛形は同じである場合があります。この場合は同じテンプレートファイルを利用できることになります。

同じテンプレートファイルで雛形が同じである複数の種類のページの表示を実現する様子

ただ、ウェブアプリによっては『マイページ』と『他のユーザーのページ』のページの構成を大きく異なるものにしたい場合もあるかもしれません。この場合はこれらのページ用のテンプレートファイルを個別に用意する必要があります。

結局、どのようなテンプレートファイルをどれだけ用意するかはウェブアプリによって異なりますが、重要なのは、自身が開発したいウェブアプリの構成を考え、その構成に応じて必要な分だけテンプレートファイルは用意することになります。

テンプレートの継承

複数のテンプレートファイルの作成時に便利な仕組みが『テンプレートの継承』になります。

テンプレートの継承とは

特に、ファイル間で共通の部分が存在する複数のテンプレートファイルを作成するときに、テンプレートの継承を利用することで開発効率が向上します。

このテンプレートの継承は、『各テンプレートファイル間で共通となる構造を実装した親テンプレートファイル』と『各テンプレートファイルで固有となる構造を実装した子テンプレートファイル」を組み合わせて新たなテンプレートファイルを生成する仕組みになります。

例えば、下図は同じウェブアプリにおける「マイページ」と「ユーザー一覧のページ」の表示例を示しています。これらは、全体的に見るとページの構成は異なりますが、ヘッダーやフッターに関しては完全に共通の構造・見た目となっています。こんな感じで、ウェブアプリでは各種ページの特定の部分、特にヘッダーやフッターを共通にすることで、各種ページの操作方法や見た目の統一が図られていることが多いです。

異なる種類のページ間にも共通部分が存在することを示す図

ただ、共通部分は存在するものの、全体的なページの構成が異なるため、普通に考えると、これらのページの HTML は異なるテンプレートファイルから生成する必要があります。ですが、これらのテンプレートファイルを完全に個別に作成するのは非効率です。共通部分が存在するため、各種テンプレートファイルに全く同じ実装を行う必要があります。

テンプレートファイルを個別に用意すると共通部分の実装が重複してしまうことを示す図

で、このような、共通部分の存在する複数のテンプレートファイルを効率的に作成するための仕組みが『テンプレートの継承」となります。テンプレートの継承を利用する場合、各種テンプレートファイルで共通となる部分を1つの親テンプレートファイルに実装し、親テンプレートファイルを継承する子テンプレートファイルを用意し、この子テンプレートファイルに “テンプレートファイルごとに異なる固有部分” を実装します。

テンプレートの継承を利用するために必要となるファイルを示す図

このように親テンプレートファイルと子テンプレートファイルを作成しておけば、子テンプレートファイルに対して render 関数を実行すると、親テンプレートファイルに子テンプレートファイルの内容を埋め込んだ新たなテンプレートファイルが生成されるようになります。そして、その生成されたテンプレートファイルからコンテキストを利用して HTML ファイルが生成されることになります。

子テンプレートファイルと親テンプレートファイルからHTMLが生成される様子

したがって、子テンプレートファイルを必要な数だけ用意しておけば、その数の分の「ファイル間で共通となる部分を持ちつつ、各ファイルで固有となる部分も持つテンプレートファイル」が生成できることになります。そして、今までの解説の通り、このテンプレートファイルからコンテキストに応じた HTML を生成することができます。

子テンプレートファイルとコンテキストによって生成されるHTMLが変化することを示す図

そして、テンプレートファイルは個別でも用意することも可能ではありますが、テンプレートの継承を利用すれば共通部分の実装先のファイルは親テンプレートファイル1つのみで済み、テンプレートファイルの作成の効率が上がります。

こんな感じで、ファイル間で共通の部分が存在する複数のテンプレートファイルの作成を効率的に行うための仕組みがテンプレートの継承となります。

で、このテンプレートの継承を利用するためには、前述のとおり親テンプレートファイル(継承元となるテンプレートファイル)と親テンプレートファイルを継承する子テンプレートファイルの2種類を作成する必要があります。1つの親テンプレートファイルに対して複数の子テンプレートファイルを作成することが多いと思います。これらのファイルに関しても テンプレートファイルの置き場所 で説明した下記フォルダに設置します。

プロジェクト名/アプリ名/templates/アプリ名/

テンプレートの継承の概要の説明は以上となります。

ここで、上記の説明に対して2点補足しておきます。

まず1点目です。ここまで render 関数の中で親テンプレートファイルと子テンプレートファイルから新たなテンプレートファイルが生成されると説明してきましたが、この生成されたテンプレートファイルはどこかにファイルとして保存されるものではありません。render 関数の中で生成され、render 関数の中でのみ使用されるものとなります。

次に2点目です。render 関数には引数でテンプレートファイルのパスを指定する必要がありますが、この引数に指定するパスは『子テンプレートファイル」のものになります。親テンプレートファイル側のパスを指定しても継承が上手く動作しないので注意してください。

親テンプレートファイルの作成

ここからは、親テンプレートファイルと子テンプレートファイルの作り方の詳細について解説していきます。

まずは、親テンプレートファイルの作り方について説明していきます。

親テンプレートファイルでは、『各テンプレートファイルで共通となる構造』の定義、および『ブロック』の定義を行います。前者は、文章のとおり、各テンプレートファイルで共通となる部分となり、ここに関しては通常のテンプレートファイルと同じ書き方で作成することになります。また、後者のブロックは、テンプレートファイルごとで固有となる構造を埋め込む部分となります。

親テンプレートファイルの構造

このブロックは、render 関数実行時に子テンプレートファイルで定義された『ブロックのコンテンツ』に置き換えられることになります。なので、子テンプレートファイル毎に異なるブロックのコンテンツを定義しておけば、使用する子テンプレートファイルによって render 関数で生成されるテンプレートファイルが変化することになります。ブロックは1つのテンプレートファイルの中に複数個定義することも可能です。

そして、このブロックは block テンプレートタグにより定義することが可能です。例えば親テンプレートファイルに {% block ブロック名 %}{% endblock %} と記述を行うことで、ブロック名 という名前のブロックを定義することができます。

ブロックの定義例を示す図

こんな感じで、各テンプレートファイルで共通となる部分とブロックの定義を行うことで親テンプレートファイルを作成することができます。親テンプレートファイルの実例は、テンプレートの継承例 で子テンプレートファイルの実例と一緒に示していきます。

子テンプレートファイルの作成

続いて、子テンプレートファイルについて解説していきます。

子テンプレートファイルでは、『親テンプレートファイルの継承』と『ブロックのコンテンツの定義』を行います。どちらもテンプレートタグを利用して記述していくことになります。

まず、親テンプレートファイルの継承はテンプレートタグ extends によって実現できます。具体的には、子テンプレートファイルの先頭に {% extends 親テンプレートファイルのパス %} を記述することで 親テンプレートファイルのパス のパスにあるテンプレートファイルへの継承が実現できます。

extendsタグで親テンプレートファイルを継承する様子

また、ブロックのコンテンツの定義はテンプレートタグ block によって実現できます。具体的には、{% block ブロック名 %}{% endblock %} がブロックのコンテンツの定義となります。親テンプレートファイルではブロックを複数定義することが可能なので、子テンプレートファイルでは、継承する親テンプレートで定義されたブロック全てに対してブロックのコンテンツを定義しておく必要があります。また、このブロックのコンテンツでも Django テンプレート言語での変数の埋め込みやテンプレートタグによる制御等を利用することが可能です。

子テンプレートファイルにブロックのコンテンツを実装する様子

上記のように子テンプレートファイルを作成し、さらにそのファイルのパスを引数に指定して render 関数を実行すれば、親テンプレートファイルの {% block ブロック名 %}{% endblock %} 部分が、子テンプレートファイルにおける {% block ブロック名 %}{% endblock %} の内側に記述された内容に置き換えられたテンプレートファイルが生成されることになります。

親テンプレートファイルのブロックが子テンプレートファイルに実装したブロックのコンテンツに置き換えられる様子

そして、この生成されたテンプレートファイルと、render 関数の引数に指定されたコンテキストに基づいて HTML が生成されることになります。

テンプレートの継承例

次はテンプレートの継承の具体例を見ていきましょう!

ここでは、アプリ名は app1 とし、親テンプレートファイルのファイル名は base.html であることを前提に具体例を示していきます。

前述の通り、親テンプレートファイルには各種ページで共通となる部分の HTML の記述とブロックの定義を行います。簡単ですが、下記は親テンプレートファイルの具体的な例となります(a タグに指定している URL はてきとうなものになっているので注意してください)。

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>
        <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.htmluserlist.html)。

mypage.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 %}
userlist.html
{% 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.htmluserlist.html は共通した構成になっており、まず1行目で親テンプレートファイルである app1/base.html の継承を行なっています。テンプレートファイルの置き場所 で説明したように、アプリ内のファイルからはテンプレートファイルを アプリ名/テンプレートファイル名 によって参照可能です。

その後、mypage.htmluserlist.html ではブロック title のコンテンツの定義とブロック main のコンテンツの実装をそれぞれを行なっています。これらは、各ファイルで同じブロックに対してコンテンツを実装していることになりますが、実装内容が異なるため、親テンプレートファイルのブロックへ置き換えられることで、異なる内容のテンプレートファイルが生成されることになります。

実際に、render(request, 'app1/mypage.html') を実行した際には、下図のようなページの HTML が生成されることになります。

子テンプレートファイルから生成されるHTMLの例1

また、render(request, 'app1/userlist.html') を実行した際には下図のようなページの HTML が生成されることになります。

子テンプレートファイルから生成されるHTMLの例2

ブロック部分のみが、render 関数の引数で指定した子テンプレートファイルに応じて異なっていることが確認できると思います。それ以外の部分は親テンプレートファイルのものがそのまま使用されています。

このように、親テンプレートファイルで各種ページの共通部分を用意し、その中に各種ページの固有部分をブロックとして埋め込む仕組みがテンプレートの継承となります。

上記の例では利用していませんが、各種テンプレートファイルからテンプレートタグや変数の参照等も利用可能です。

テンプレートの継承のメリット

前述でも少し触れましたが、テンプレートの継承にはたくさんのメリットがあります。

まず、テンプレートの継承の仕組みを利用することで、子テンプレートファイル側は extends タグによる継承とブロックのコンテンツの実装さえ行えば良くなるため、テンプレートファイル作成の効率が上がります。

また、各種ページで共通となる部分は親テンプレートファイルのみに記述されるようになるため、共通部分の修正が必要になった際に修正が必要となるのは親テンプレートファイルのみとなり、修正の効率も上がります。逆に継承を利用していなかった場合、共通部分に修正が必要になった際には全てのテンプレートファイルの修正が必要となってしまいます…。また、この場合、修正漏れが発生する可能性もあります。

テンプレートファイルのメリットを説明する図1

さらに、共通部分を1つのテンプレートファイルに記述するようになることで、自然とウェブアプリの各種ページの見た目・使い方に統一感が生まれることになります。

テンプレートファイルのメリットを説明する図2

最初は面倒に感じるかもしれませんが、テンプレートの継承はメリットが多いため、是非積極的に利用するようにしてください!

スポンサーリンク

ビューとテンプレートの関係性

ここで、ここまで説明してきた内容のまとめの意味も含めて、テンプレートとビューの関係性について解説しておきたいと思います。

テンプレートの役割

ここまでの説明の通り、テンプレートはテンプレートファイルを提供することが役割であり、具体的には、テンプレートの役割は下記を行なってテンプレートファイルを提供することになります。

  • 『HTML』と『Django テンプレート言語』から構成されるテンプレートファイルを作成する
  • テンプレートファイルはウェブアプリで表示するページに応じて必要な分だけ作成する
    • テンプレートの継承を利用しても OK
  • 作成したテンプレートファイルをテンプレートファイルの置き場所に置く

そして、このテンプレートファイルの提供先はビューとなります。あくまでもテンプレートの役割はテンプレートファイルの提供であり、テンプレートファイルを利用するのはビューとなります(もっと正確に言えば、利用するのはビューから実行される render 関数となります)。

テンプレートの役割を示す図

ビューの役割

また、ビューにとって重要なのは、リクエストに応じたレスポンスを返却することになります。そして、レスポンスには HTML が含まれるため、リクエストに応じた HTML を生成する必要もあります。

そのため、複数のテンプレートファイルが存在する場合、リクエストに応じた HTML を生成するために、適切にテンプレートファイルを選択し、そのファイルのパスを render 関数の引数に指定する必要があります。

また、テンプレートファイルから変数が参照される場合、リクエストに応じた HTML を生成するために、適切にコンテキストを作成して、そのコンテキストを render 関数の引数に指定する必要もあります。

要は、テンプレートはテンプレートファイルを提供することが役割であり、そのテンプレートファイルを適切に利用して HTML を生成するのはビューの役割となります。

ビューの役割を示す図

また、ウェブアプリが受け付けるリクエスト先の URL に関しては urls.py の URL のマッピングによって決まります。ウェブアプリでリクエスト先の URL に応じた処理が実行できるよう、適切に urls.py で URL のマッピングも行なっておく必要があります。

結局、重要なのはウェブアプリがユーザーのリクエストに応じた処理の実行・レスポンスの返却を行えるようにすることなので、開発者目線で考えれば、それを実現するために urls.py やビュー、さらには今回紹介したテンプレートファイルを作成・用意しておくことが重要となります。

特にテンプレートファイルの作りによって、ビューの作成するコンテキストに必要なキー等が決まることになりますので、テンプレートの参照する変数とビューの作成するコンテキストとで話が合うように開発を行うことも重要となります。

スポンサーリンク

掲示板アプリでテンプレートを利用してみる

では、ここまで説明してきた内容を踏まえて、実際にテンプレートの利用例を示していきたいと思います。

この Django 入門 に関しては連載形式となっており、ここでは前回下記ページの 掲示板アプリでビューを利用してみる で作成したウェブアプリに対してテンプレートの仕組みを導入する形で、テンプレートの利用例を示していきたいと思います。

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

スポンサーリンク

テンプレートファイルの作成

フォルダが作成できれば、次はいよいよテンプレートファイルを作成していきます。

現状の forumviews.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 を下記のように作成したいと思います。

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 は下図のような雛形を提供するテンプレートファイルとなっています。

base.htmlが提供する雛形の説明図

要は、ナビゲーションバーと title ブロックと main ブロックを提供する親テンプレートファイルです。この base.html を継承した子テンプレートファイルには、ナビゲーションバーが引き継がれることになります。また、base.html で読み込んでいる CSS 等も引き継がれることになります。

また、base.html では title ブロックと main ブロックの2つを定義しているため、子テンプレートファイル側で、各テンプレートで実現したい雛形となるように各ブロックのコンテンツを記述してやれば、それらのコンテンツが base.html に埋め込まれることになります。

他のポイントについても説明しておくと、まず、この base.html では Bootstrap の読み込みを行うようにしています。

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での静的ファイルの扱い方の解説ページアイキャッチ 【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 %}

上記に関しては、次の連載で説明する『フォーム』に関しての説明を終えた後に有効化を行います。

MEMO

掲示板への投稿を Comment クラスで扱っているため、コメントアウトの comment と混在しやすいので注意してください

正直、後から考えて、このクラスの名前の付け方に失敗したなぁと思っています…

また、コメント以外にも、base.html では Django テンプレート言語 で紹介したテンプレートタグの利用を行なっています。それが、下記の url タグになります。base.html を確認すれば、いくつかの箇所でこのテンプレートタグを利用していることが確認できると思います。

urlタグ
<a class="nav-link" href="{% url 'users' %}">ユーザー一覧</a>

この url タグは、引数で与えられた URL 名から URL そのものに変換を行い、その変換結果を HTML に埋め込むタグとなります。

上記の場合は、'users' が引数で与えられる URL 名となります。さらに、/forum/ から始まる URL に関しては forum フォルダの urls.py が参照されるようになっており、この urls.py で下記のように URL に 'users' という名前を付けるように設定しています。

URL名の設定
path('users/', views.users_view, name='users'),

したがって、上記の url タグ部分は /forum/users/ に置き換えられ、さらに a タグによって ユーザー一覧 という文字列に対してこの URL へのリンクが貼られることになります。

このように、url タグは URL の名前から URL への置き換えを実現する重要なテンプレートタグであり、利用する機会も多いと思いますので是非覚えておいてください。

また、URL の名前の設定方法等については前回の連載となる下記ページで紹介していますので、復習したい方は下記ページを参照していただければと思います。

Djangoのビューの機能についての解説ページアイキャッチ 【Django入門3】ビュー(View)の基本とURLのマッピング

users.html

続いて、子テンプレートファイル側の作成を行なっていきます。

まずは users.html を作成していきます。このテンプレートファイルはユーザー一覧を表示するための雛形となります。

今回は、下記のように users.html を作成したいと思います。この users.html は、変数 users を参照しています。そのため、ビューの関数では 'users' キーを持つコンテキストを用意してやる必要があります。後述の ビューの変更 で具体例を示しますが、今回は 'users' キーの値として User クラスのインスタンスのリストを指定したコンテキストを用意します。

users.html
{% 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ループ
{% for user in users %}
    〜ループ処理〜
{% endfor %}

今回の例ではクラスである User のインスタンスの “リスト” をテンプレートファイルから参照することを想定していますが、実際に Django でウェブアプリを開発する際には、”クエリーセット” というモデルのインスタンスの集合を参照する機会が非常に多いです。まだモデルについての説明を行なっていないため、現状単なるクラスを利用しているだけです。ですが、クエリーセットを参照するようになったとしても、上記のような for タグで各インスタンスに対して処理を行う部分はそのまま利用できますし、この for タグも利用する機会が多いので是非覚えておいてください。

もう一点重要になるのが、下記の url タグの利用部分になります。この url タグは base.html でも使用しているのですが、その際は引数に URL の名前のみを指定していましたが、この users.html では、URL の名前に加えて user.id も引数に指定するようになっています。

urlタグへの引数指定
<td><a href="{% url 'user' user.id %}">{{ user.username }}</a></td>

このように引数を指定した場合、その url タグ部分は、”URL の名前” が URL に変換された結果の後ろ側に、user.idの値/ が追加された状態の URL に置き換えられることになります。たとえば、user.id の値が 3 である場合は、上記の url タグ部分は次の図のような URL に置き換えられることになります。

URLタグの説明図

このような記述で url タグを利用する機会も多いので、これに関しても是非覚えておいてください。 

user.html

続いて user.html を作成していきます。このテンプレートファイルは特定のユーザーの詳細情報を表示するための雛形となります。

今回は、下記のように user.html を作成したいと思います。この user.html は、変数 user を参照しています。そのため、ビューの関数では 'user' キーを持つコンテキストを用意してやる必要があります。後述の ビューの変更 で具体例を示しますが、今回は 'user' キーの値として User クラスのインスタンスを指定したコンテキストを用意します。

user.html
{% 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 クラスのインスタンスのリストを指定したコンテキストを用意します。

comments.html
{% 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 と同様の作りのため、ポイントの説明は省略します。

comment.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_viewusers.html
  • user_viewuser.html
  • comments_viewcomments.html
  • comment_viewcomment.html

なので、各種ビューの関数は、上記の使用するテンプレートファイルから参照される変数に合わせたコンテキストを作成し、上記のテンプレートファイルのパスとコンテキストを引数に指定して render 関数を実行することになります。テンプレートファイルのパスは templates フォルダからの相対パスで記述する必要がある点に注意してください。

ということで、ここまで説明してきた内容を考慮すれば、views.py は下記のように変更してやれば良いことになります。render 関数を利用するため、render 関数の import が必要な点に注意してください。

views.py
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 の構成を自分で定義するようにしていました。

Djangoのビューの機能についての解説ページアイキャッチ 【Django入門3】ビュー(View)の基本とURLのマッピング

なので、関数の中身が若干複雑になっていましたが、上記の views.py では HTML の構成は全てテンプレート側で定義するようになっているため、ほぼ、コンテキストの用意と render 関数の実行のみで処理が完結するようになっています。そして、これによって関数の中身がスッキリしていることを確認できると思います。

また、各関数ではテンプレートファイルが参照する変数に合わせてコンテキストを用意するようにしています。特に user_viewcomment_view では引数で ID が指定されるようになっており、その ID に応じたインスタンスをコンテキストにセットするようにしていますが、不正な ID が指定された場合は、今まで通り例外を発生させる必要があります。

動作確認

ソースコードの変更は以上になります。最後に動作確認を行なっておきましょう!

開発用ウェブサーバーの起動

まずは、ウェブアプリにアクセスできるよう、Django 開発用ウェブサーバーを起動してください。この開発用ウェブサーバーの起動は、manage.py が存在するフォルダ(プロジェクトの testproject フォルダの中)で下記コマンドを実行することで実現できます。

% python manage.py runserver

これにより、ウェブブラウザ等のクライアントからリクエストを受け取るウェブサーバーが起動することになります。

ページ表示の確認

ということで、次はウェブブラウザを開き、アドレスバーに下記 URL を指定します。

http://localhost:8000/forum/comments/

そうすると、下の図のようなページが表示されると思います。

/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 と同様になっている点も確認できると思います。

MEMO

前述のとおり、コンテキストの 'comments' キーの要素の値には views.py で定義されるリスト comments が指定されているため、このリスト comments に要素の追加や要素の削除の変更を加えると、表示されるページも変化されることが確認できると思います

ただし、変更が反映されるのは、開発用ウェブサーバーを再度起動したタイミングになるので注意してください

上記のページの結果より、Django テンプレート言語のフィルターの効果も確認できます。ページに表示されている “全5件” の 5 に関しては commentslength フィルターで加工された結果になります。さらに、コメントの最後の1つに関しては本文が途切れており、これは truncatechars フィルターで文字数 20 でコメントの文字列が切り詰められるよう加工されたためになります。

このように、上図のようなページの表示結果から、テンプレートの継承やテンプレートタグ・フィルターの効果等を確認できると思います。

次は、コメントの詳細情報ページを確認してみましょう。ページに表示されているコメントの本文部分には、/forum/comment/N/ の URL のリンクが設定されています(N はコメントの ID )。そして、/forum/comment/N/ の URL のリクエストを受け取った時には comment_view が実行されるよう urls.py を実装しているため、コメントの本文のリンクをクリックすればコメントの詳細情報ページが表示されることになります。ということで、いずれかのコメントの本文部分をクリックして、コメントの詳細情報ページを表示してみましょう!

/forum/comment/3/へのリクエストによって表示されるページ

前述のとおり、コメントの本文のクリックによって comment_view が実行されることになります。そして、base.html で用意された各ブロックに comment.html に記述されたブロックのコンテンツが埋め込まれる形で HTML が生成され、それがページとして表示されることになります。

この場合も、全体的な見た目は先ほど表示したコメント一覧のページと同様になります。これは、全体的な見た目は base.html で定義しており、各種テンプレートがこの親テンプレートファイルを継承するようになっているためです。このように、継承を利用することで、全体的な見た目をウェブアプリ内のページ間で統一しやすくなります。

さらに、ナビゲーションバーの ユーザー一覧 をクリックすれば、次はユーザーの一覧ページを表示することができます。

/forum/users/へのリクエストによって表示されるページ

この場合は users_view が実行され、views.py に定義されている users の各要素の情報が表示されることになります。また、コメント一覧ページの時同様に、ユーザー名をクリックすれば、そのユーザーのページが表示されることも確認できると思います。

/forum/user/2/へのリクエストによって表示されるページ

以上で、今回の動作確認は完了となります!

下記ページの 掲示板アプリでビューを利用してみる で紹介したページの表示例に比べれば、かなり見た目としては綺麗になったのではないかと思います。

Djangoのビューの機能についての解説ページアイキャッチ 【Django入門3】ビュー(View)の基本とURLのマッピング

ただし、これは別にテンプレートを利用するようになったからではなく、テンプレートを利用しなくても、ビューに直接記述する HTML を変更してやれば同じような見た目のページを実現することは実は可能です。

ですが、ビューに直接 HTML を記述すると views.py のコード量が多くなり、メンテナンス性が下がって変更しにくくなったりバグが発生しやすくなったりしてしまいます。それに対し、ビューとテンプレートとで役割を分割し、HTML の構成の定義を全てテンプレート側に行わせるようにすることで、各ファイルのコード量も減ってメンテナンス性が上がり、変更もしやすくなります。

さらに、継承を利用することで各テンプレートファイルのコード量も減りますし、それに加えてウェブアプリ内の各ページの見た目も統一しやすくなります。全体的な構造が親テンプレートファイルに集約されるので、変更・修正もしやすくなります。

このあたりの、テンプレートを利用することで得られるメリットを、今回紹介した例で実感していただければ幸いです。最初はテンプレートファイルを利用する必要があって面倒に感じるかもしれませんが、開発するウェブアプリの規模が大きくなると、テンプレートを利用するメリットがより発揮されるようになると思います。

スポンサーリンク

まとめ

このページでは、Django におけるテンプレートについて解説しました!

テンプレートはビューに対して HTML の雛形を提供する役割を持ち、ビューから HTML の構造を定義する役割を分離することで、メンテナンス性の高いウェブアプリを実現することが可能となります。

また、テンプレートは単なる HTML だけでなく、Django テンプレート言語を利用して記述することになります。Django テンプレート言語を利用することで、テンプレートファイルから変数を参照したり、テンプレートタグを利用してちょっとした処理を実現することもできますし、フィルターを利用して変数を加工したりすることもできます。

さらに、テンプレートは継承機能を持っており、これを利用することでテンプレートファイルの開発効率が上がりますし、さらにウェブアプリ内のページ間の構造を統一することもしやすくなります。

今回は深く説明しませんでしたが、もちろん CSS を読み込んでスタイルを適用することも可能です。ウェブアプリの見た目に拘りたい方は、是非 CSS やスタイルについても学んでみると良いと思います。

今回紹介したテンプレートの導入により、ページの表示に関してはかなりまともなウェブアプリを実現することができるようになりました!次の下記ページの連載ではフォームの解説を行い、フォームを導入することで、ユーザーからデータを受け取ったり、ユーザーと対話可能なウェブアプリを実現していきたいと思います!

Djangoのフォームの解説ページアイキャッチ 【Django入門5】フォームの基本

同じカテゴリのページ一覧を表示