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

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

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

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

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

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

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

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

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

テンプレートの基本

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

テンプレート

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

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

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

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

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

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

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

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

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

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

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

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

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

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

雛形にリクエストに応じた情報を埋め込んでHTMLを生成する様子

このように、基本的にウェブアプリで表示するページは雛形動的に変更が必要な部分(動的な部分)の2種類から構成されています。

したがって、雛形となる部分のみを事前に作成しておき、ウェブアプリがリクエストを受け取った時に、その雛形に対してリクエストに応じた情報を動的に埋め込んで HTML を生成するようにすれば、1つの雛形からリクエストに応じたページ表示を実現することができるようになります。

雛形に対してリクエストに応じたデータを埋め込むことで、リクエストに応じたページの動的生成を行うことが可能となる様子

このような、部分的に表示結果を動的に変更可能な雛形を提供することがテンプレートの役割となります。このような雛形はテンプレートファイルと呼ばれます。また、テンプレートファイルからの HTML の生成は、次に説明する render 関数によって行われます。また、動的に変更可能な部分に埋め込むデータはコンテキストと呼ばれるデータによって提供されます。これに関しては後述の コンテキスト で説明します。

また、1つのテンプレートファイルから生成可能な 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 関数

例えば、テンプレートファイルに {{ name }} と記述している場合、render 関数で HTML が生成される際に {{ name }} 部分が name 変数の値に置き換えられることになります。

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

例えば、下記のような辞書データを render 関数の context 引数に指定した場合、{{ name }} 部分は YamadaHanako に置き換えられることになります。

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

つまり、テンプレートファイルから参照される変数 変数名 は、render 関数での HTML 生成時に、コンテキストにおけるキーが '変数名' に置き換えられることになります。例えばコンテキストの変数名がcontext であれば、context['変数名'] で取得される値に置き換えられることになります。

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

前述の通り、コンテキストは辞書であり、キーには文字列を指定する必要がありますが、値には整数・文字列・クラスのインスタンス・リストなどの様々なオブジェクトを指定することが可能です。また、コンテキストには複数の要素を持たせても問題ありません。

ただし、テンプレートファイルから変数を参照する場合、その変数の参照先となるデータがコンテキストに存在する必要があります。したがって、その参照される変数の '変数名' をキーとする要素を全て用意しておく必要があります。

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

コンテキストの重要性

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

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

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

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

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

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

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

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

ここまで解説してきたように、テンプレートはリクエストに応じたページを表示するための仕組みであり、その役割はテンプレートファイルをビューに提供することになります。

それに対し、コンテキストを用意するのはビューの役割となります。ユーザーからのリクエストに応じられるよう、リクエストに応じたコンテキストを用意することが重要となります。

テンプレートファイル

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

スポンサーリンク

HTML の基となるファイル

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

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

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

このテンプレートファイルは、基本的には『HTML』と『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 の要素数分繰り返し実行され、各要素の情報が出力されることになります。

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つのテンプレートファイルを複数のページで使い回すようなことも可能です。

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

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

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

ということで、どのようなテンプレートファイルをどれだけ用意するかはウェブアプリによって異なります。

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

テンプレートの継承

複数のテンプレートファイルを作成するのにあたって覚えておきたいのが『テンプレートの継承』になります。

このテンプレートの継承について説明する前に、一点注意点について説明しておきます。このページのここまでの解説では、『共通』という言葉は「同じ種類のページに対し、リクエストの内容(例えばユーザーなど)に関らず同じ構成となる部分」に対して使ってきましたが、この節では『共通』という言葉を「種類の異なるページではあるものの同じ構成となる部分」に対して使うので、この違いに注意してください。

例えば、下図はマイページとユーザー一覧のページの例を示すもので、種類の異なるページではあるものの、ページのヘッダー部分とフッター部分(青色背景部分)は共通です。

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

こういった共通部分は、各種テンプレートファイル、この例で言えば、マイページ用のテンプレートファイルとユーザー一覧ページ用のテンプレートファイルそれぞれに記述するのではなく、親テンプレートファイルを用意し、それを各種ページ向けの子テンプレートから継承することで実現することができます。

異なる種類のページ間の共通部分をテンプレートの継承を利用して実現する様子

テンプレートファイルの作成は当然ページの種類が多くなればなるほど作業が大変になります。ですが、このテンプレートの継承を利用することで、共通部分は1つの親テンプレートファイルの記述のみで済むようになるため、ある程度テンプレートファイルの作成を効率的に行うことができるようになります。

また、テンプレートファイルの修正も楽になり、さらに各種ページの見た目の統一化も図りやすくなります。メリットが多いので是非テンプレートの継承は覚えておきましょう!

テンプレートの継承方法

このテンプレートの継承は Django テンプレート言語 でも紹介したテンプレートタグを利用して実現します。後述でも解説しますが、具体的には extends タグの利用により継承を実現することができます。

また、テンプレートの継承を行うためには、親テンプレートファイル(継承元となるテンプレートファイル)と親テンプレートファイルを継承する子テンプレートファイルを用意することになります。これらのファイルに関しても テンプレートファイルの置き場所 で説明した下記フォルダに設置します。

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

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

テンプレートの継承を行うにあたり、まず親テンプレートファイルの作成を行います。この親テンプレートファイルでは、各種ページで共通となる部分をテンプレートファイルとして記述を行います。

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

また、親テンプレートファイルでは子テンプレートファイルごとに内容を変化させたい部分をブロックとして定義します。ブロックは block タグにより定義することが可能で、例えば {% block ブロック名 %}{% endblock %} と記述を行うことで、ブロック名 という名前のブロックを定義することができます。ブロックは1つのテンプレートファイルの中に複数個定義することも可能です。

親テンプレートファイルでブロックの定義を行う様子

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

さらに、子テンプレートファイルでは、親テンプレートファイルを継承することを意味するテンプレートタグの記述と、親テンプレートファイルで用意されたブロックのコンテンツ(中身)の記述を行います。ブロックのコンテンツでは、他のテンプレートタグの記述や変数の参照を行うことも可能です。

まず、親テンプレートファイルの継承は、{% extends 親テンプレートファイルのパス %} のテンプレートタグにより実現することができます。

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

また、ブロックのコンテンツの記述は親テンプレートファイルで定義されるブロックの種類ごとに行う必要があります。子テンプレートファイルの中に {% block ブロック名 %}{% endblock %} を記述しておけば、その内側に記述した内容が ブロック名 のブロックのコンテンツとして指定されることになります。

子テンプレートファイルにブロックの中身を記述する様子

これで親テンプレートファイルと子テンプレートファイルが作成できたことになります。

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

これにより、render 関数が実行された際に、extends タグによって親テンプレートファイルが読み込まれ、さらに親テンプレートファイルに定義されたブロックに子テンプレートで記述されたブロックのコンテンツが埋め込まれることになります。具体的には、親テンプレートファイルにおける {% block ブロック名 %}{% endblock %} の部分が、子テンプレートファイルにおける {% block ブロック名 %}{% endblock %} の中に記述された内容に置き換えられます。

render関数で親テンプレートファイルで定義されたブロックが子ブロックに記述されたブロックの中身に置き換えられる様子

つまり、親テンプレートファイルさえ作成しておけば、子テンプレートファイルでは継承を行うための記述とブロックのコンテンツの記述さえ行えば良いようになるため、テンプレートファイルの作業効率が上がります。

もちろん、親テンプレートファイルも子テンプレートファイルもテンプレートファイルとなりますので、Django テンプレート言語を記述して変数の埋め込みやテンプレートタグによる制御を行うこともできます。

テンプレートの継承例

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

ここでは、アプリ名は 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

ブロック部分のみが使用する子テンプレートファイルによって異なっていることが確認できると思います。それ以外の部分は親テンプレートファイルのものがそのまま使用されています。

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

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

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

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

まず、テンプレートの継承の仕組みを利用することで、子テンプレートファイル側は 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】ビューの基本とURLのマッピング

テンプレートフォルダの作成

では、前述の通り、掲示板アプリでビューを利用してみる で示したウェブアプリを変更してテンプレートを導入していきたいと思います。

まず、テンプレートファイルの置き場所となるフォルダを作成します。テンプレートファイルの置き場所 で解説したように、テンプレートファイルの置き場所は下記のパスとなります。

プロジェクト名/アプリ名/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:特定のコメントの詳細情報ページを表示する

これらのビューの関数では、自身の関数の中で HTML を生成し、それをボディとする HttpResponse のインスタンスを返却するようになっています。

これらの関数の対し、下記の4つのテンプレートファイルを導入していきます。

  • users.html:ユーザー一覧ページのテンプレート
  • user.html:特定のユーザーの詳細情報ページのテンプレート
  • comments.html:投稿済みコメント一覧ページのテンプレート
  • comment.html:特定のコメントの詳細情報ページのテンプレート

つまり、これらのテンプレートファイルを導入することで、各種ビューの関数が表示したいページのテンプレートが上記のテンプレートファイルから提供されるようになります。

MEMO

ビューの関数が5つであるのに対し、導入するテンプレートファイルが4つなのは、index_view が単純に他のページへリダイレクトを行うだけで、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 への置き換えを実現する重要なテンプレートタグであり、利用する機会も多いと思いますので是非覚えておいてください。

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 に対して繰り返し実行されることになります。

forループ
{% for user in users %}
    〜ループ処理〜
{% endfor %}

さらに、この繰り返しの中で users の各要素 user のデータ属性を参照してリンクの設定や名前の HTML への埋め込みなどを行うようにしています。

今回の例では単なるクラスである User のインスタンスのリストをテンプレートファイルから参照することを想定していますが、実際に Django でウェブアプリを開発する際には、クエリーセットというモデルのインスタンスの集合を参照する機会が非常に多いです。まだモデルについての説明を行なっていないため、現状単なるクラスを利用しているだけです。

ですが、クエリーセットを参照するようになったとしても、上記のような for タグで各インスタンスに対して処理を行う部分はそのまま利用できますし、この for タグも利用する機会が多いので是非覚えておいてください。

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】ビューの基本と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 と同様になっている点も確認できると思います。

また、全5件の 5 に関しては commentslength フィルターで加工した結果であり、コメントの最後の1つに関しては本文が長いため、truncatechars フィルターで加工されて途中で途切れていることも確認できると思います。

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

さらに、ページに表示されているいずれかの本文をクリックすれば、その本文に対応するコメントの詳細情報のページに遷移します。

/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】ビューの基本とURLのマッピング

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

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

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

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

まとめ

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

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

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

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

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

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

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

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