Djangoの基本(16)。テンプレートタグの紹介とそのカスタマイズ方法
での Django基礎講座(15):テンプレートフィルタの仕組みとカスタマイズ方法 では、Django のテンプレートフィルタの性質と仕組みを紹介し、そのカスタマイズ方法を詳しく説明しました。今日は、 Django のテンプレートタグのカテゴリとそのカスタマイズ方法についてお話します。
テンプレートタグ(タグ)とは
テンプレートタグは、{% %}括弧で囲みます。一般的なテンプレートタグは、{% load xxxx %}, {% block xxxx %}, {% if xxx %}, {% url 'xxxx' %}です。これらのテンプレートタグは、本質的には関数でもあり、タグ名は一般に関数名となります。これらのタグの主な目的は、テンプレートをレンダリングするためのコードを読み込んだり、渡されたパラメーターに対して特定の論理的な判断や計算を行い、それを返すことです。
例えば、以下の例のurlタグは、名前付きurlと記事IDの2つのパラメータを受け取り、それらを解析してblog/article/4/のようなリンクを生成して返します。
<a href="{% url 'blog:article_detail' article.id %}">details</a>
Django テンプレートタグ (タグ) のカテゴリ
Django のテンプレートタグは 2 つのカテゴリに分類されます。
-
simple_tag (シンプルなタグ : データを処理したり、文字列を返したり、コンテキストに変数を設定したり追加したりします。
-
inclusion_tag (包含タグ) : データを処理し、レンダリングされたテンプレートを返します。
Django に慣れている人なら、一般的にビュービューでコンテキストを設定し、それを通してデータをテンプレートに渡すことを知っています。コンテキストとは、変数とその値のコレクションです。simple_tag を使うことで、ビューの外側のコンテキストに変数を設定したり、追加したりすることができます。注意: Django 1.9 以降では assign_tag をサポートしていませんので、 simple_tag を使ってください。
テンプレートタグをカスタマイズする方法
まず、アプリのディレクトリに templatetags という名前の新しいフォルダを作成する必要があります(他の名前は使用できません)。そのディレクトリに、カスタムテンプレートタグ関数のための python ファイルを作成する必要があります。この場合は blog_extras.py ですが、他の名前でもかまいません。全体のディレクトリ構造は以下の通りです。
blog/
__init__.py
models.py
templatetags/
__init__.py
blog_extras.py
views.py
テンプレート内でカスタムテンプレートタグを使用するには、{% load blog_extras %}を使ってカスタムフィルタをロードし、{% tag_name %}を介して使用する必要があります。
カスタムテンプレートタグの簡単な3つの例
文字列を返すタグ、テンプレート・コンテキストに変数を渡すタグ、レンダリングされたテンプレートを表示するタグの3つの単純なテンプレート・タグを定義します。以下のコードをblog_extra.pyに追加します。
#blog_extra.py
from django import template
import datetime
from blog.models import Article
register = template.Library()
# use simple tag to show string
@register.simple_tag
def total_articles():
return Article.objects.filter(status='p').count()
# use simple tag to set context variable
@register.simple_tag
def get_first_article():
return Article.objects.filter(status='p').order_by('-pub_date')[0]
# show rendered template
@register.inclusion_tag('blog/latest_article_list.html')
def show_latest_articles(count=5):
latest_articles = Article.objects.filter(status='p').order_by('-pub_date')[:count]
return {'latest_articles': latest_articles, }
# latest_article_list.html
<ul>
{% for article in latest_articles %}
<li>{
{ article.title }} </li>
{% endfor %}
</ul>
# index.html (独自のテンプレートタグを使用)
{% extends "blog/base.html" %}
{% load blog_extras %}
{% block content %}
<p> Number of articles: {% total_articles %}</p>
{% show_latest_articles %}
{% get_first_article as first_article %}
<p>First article: </p>
<p>{
{ first_article.title }}</p>
{% endblock %}
最終的な表示イメージは以下のようになります。
複雑な例:テンプレートやコンテキストからパラメータを受け取り、結果を返す。
上記の3つの簡単な例では、メッセージの受け渡しは一方通行です。タグ関数がテンプレートやコンテキストから引数を受け取り、それらを処理し、文字列やレンダリングされたテンプレートを返す方が一般的です。
次の例のshow_resultsタグは、投票結果を返すために、テンプレートから渡されたpollの引数を受け取る必要があります。
{% show_results poll %}
この時点で、show_results関数を次のように書くことができます。
@register.inclusion_tag('results.html')
def show_results(poll):
choices = poll.choices_set.all()
return {'choices': choices}
もちろん、poll変数がテンプレートに表示される必要はありません。多くの場合、オブジェクトやオブジェクトのリストはすでにグローバル変数のコンテキストに存在しており、takes_context=Trueを使用してコンテキストから直接変数を使用することができます。pollがすでにコンテキストに存在すると仮定すると、上のコードは次のように変更することができます。
@register.inclusion_tag('results.html', takes_context=True)
def show_results(context):
choices = context['poll'].choices_set.all()
return {'choices': choices}
この時点で、テンプレートは以下のコードに簡略化され、投票結果を表示するための引数pollが不要になります。
{% show_results %}
テンプレートから渡される複数のパラメータを処理する方法
show_results poll %}のカスタムタグ関数は、テンプレートから渡されるpollパラメータを1つだけ受け取ります。テンプレートは複数のパラメータを渡すこともでき、 Django のタグ関数は、パラメータ名や数がわかっている場合、渡されたパラメータを位置で処理することができます。
{% my_tag "abcd" book.title warning=message profile=user.profile %}
@register.inclusion_tag('my_template.html')
def my_tag(a, b, *args, **kwargs):
warning = kwargs['warning']
profile = kwargs['profile']
...
return ...
しかし、{% url "article_detail" article.id article.slug %}のようなurlタグは明らかにもっと複雑です。未知の数のパラメータと未知の名前のパラメータを取ることができ、二重引用符の有無も問いません。
この場合の Django のアプローチは、タグがあるノードをパースし (パーサ)、受け取った文字列を全体としてトークンとし、まずトークンを分割し、その後個別に処理する、というものです。
次に、{% format_time %}タグが時刻の日付をどのようにフォーマットするかを見てみましょう。
<p>Published at at {% format_time article.pub_date "%Y-%m-%d %I:%M %p" %}. </p>
カスタムformat_timeタグ関数の全コードは以下のとおりです。
from django import template
register = template.Library()
@register.tag(name="format_time")
def do_format_time(parser, token):
try:
# split_contents() knows not to split quoted strings.
tag_name, date_to_be_formatted, format_string = token.split_contents()
except ValueError:
raise template.TemplateSyntaxError(
"%r tag requires exactly two arguments" % token.contents.split()[0]
)
if not (format_string[0] == format_string[-1] and format_string[0] in ('"', "'")):
raise template.TemplateSyntaxError(
"%r tag's argument should be in quotes" % tag_name
)
return FormatTimeNode(date_to_be_formatted, format_string[1:-1])
class FormatTimeNode(template.Node):
def __init__(self, date_to_be_formatted, format_string):
self.date_to_be_formatted = template.Variable(date_to_be_formatted)
self.format_string = format_string
def render(self, context):
try:
actual_date = self.date_to_be_formatted.resolve(context)
return actual_date.strftime(self.format_string)
except template.VariableDoesNotExist:
return ''
では、上記のコードがどのように動作するのかに注目してみましょう。
-
Django テンプレートパーサーはテンプレート全体をスキャンし、 format_time タグを見つけ、新しいノード Node として扱い、長い文字列 format_time article.pub_date "%Y-%m-%d" をトークンとして得ます。
-
get_format_timeメソッドは、トークン自身のsplit_contentsメソッドを使って、上記の文字列をタグ名、フォーマットされる日付(date)、フォーマットの3つに分割し、FormatTimeNodeが処理するためのフォーマットされる日付とフォーマットを返します format_string[1:-1] の機能は、ダブルクォートを削除することです。
-
ノードクラス FormatTimeNode は、ノードのレンダリング、render メソッドによる新しいノードのレンダリング、およびコンテキストによるテンプレートへの他の変数の受け渡し(下図参照)を担当します。render メソッドが具象値を返さない場合、空文字列を返す必要があります。
def render(self, context):
actual_date = self.date_to_be_formatted.resolve(context)
context['formatted_time'] = actual_date.strftime(self.format_string)
return ''
parseメソッドによる連続パージング
時々、{% comment %}や{% endcomment %}のようなタグに出くわすことがあります。このとき、parseメソッドで本当のnodelistをパースする必要があります。これは非常に複雑なので、後で Django のソースコードを解析するときに取り上げる予定です。
概要
この記事では、Django のテンプレートタグの 2 つの主要なカテゴリ (シンプルタグと包含タグ) と、テンプレートタグをカスタマイズする方法について、テンプレートがタグ関数に 1 つ以上の引数を渡す方法に焦点を当てて解説しています。この記事があなたのお役に立てれば幸いです。
グレートリバードッグ
2018.9.19
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
ハートビート・エフェクトのためのHTML+CSS
-
HTML ホテル フォームによるフィルタリング
-
HTML+cssのボックスモデル例(円、半円など)「border-radius」使いやすい
-
HTMLテーブルのテーブル分割とマージ(colspan, rowspan)
-
ランダム・ネームドロッパーを実装するためのhtmlサンプルコード
-
Html階層型ボックスシャドウ効果サンプルコード
-
QQの一時的なダイアログボックスをポップアップし、友人を追加せずにオンラインで話す効果を達成する方法
-
sublime / vscodeショートカットHTMLコード生成の実装
-
HTMLページを縮小した後にスクロールバーを表示するサンプルコード
-
html のリストボックス、テキストフィールド、ファイルフィールドのコード例