今回のお題
今回は、djangoテンプレートの繰り返し処理についてまとめます。
繰り返し処理といえばfor文ですが、djagnoテンプレートにはpythonのfor文にはない独自の機能がいくつか追加されています。
これらを使うことで複雑なロジックが簡潔に記述できるようになるので、自分のメモも兼ねてまとめます。
目次
- forloop
- for ~ empty
- 回数指定の繰り返し
- for ~ ifchanged
- regroup/grouper
- cycle/resetcycle
forloop
forloopはループ変数とも呼ばれる特殊な変数で、繰り返し処理に関する様々な情報を保持しています。
1 | 2 |
---|---|
counter | ループ回数(1スタート) |
counter0 | ループ回数(0スタート) |
revcounter | 残りのループ回数(そのループを含む) |
revcounter0 | 残りのループ回数(そのループを除く) |
first | 最初のループか否か(真偽値) |
last | 最後のループか否か(真偽値) |
parentloop | 親ループ(入れ子のループの場合にのみ使用可能) |
これらは属性値なので、forloop.counter
などとしてループ回数にアクセスします。
{% for member in members %}
<div>{{ forloop.counter }}人目の登録者は{{ member.name }}さんです。</div>
{% end %}
for ~ empty
for ~ empty ~ endfor
のブロックを作ることで、for文で繰り返すリストや辞書が空の場合にempty以下の内容を出力します。
{% for member in members %}
<div>{{ member.name }}</div>
{% empty %}
<div>登録されているメンバーはいません</div>
{% endfor %}
回数指定の繰り返し
djangoテンプレートではfor文の中でrange関数を呼び出すことができません。
その代替案として書籍等でwithブロックの利用が提案されているのを見かけますが、個人的にはあまりしっくり来ませんでした。
withを使ってもブロックのネストは避けられないので、いっそのこと以下のように書けば良いのではないかと思っています。
{% for member in members %}
{% if forloop.counter <= 3 %}
<div>{{ member.name }}</div>
{% endif %}
{% endfor %}
ifchanged
ifchangedブロックで囲んだ部分が前のループと違う値であった場合は出力され、同じ値であった場合には表示されなくなる。
<table>
<thead>
<tr>
<th>コード</th>
<th>書名</th>
</tr>
</thead>
{% for book in books %}
<tr>
<td>
{% ifchanged %}
{{ book.isbn }}
{% endifchanged %}
</td>
<td>
{% ifchanged %}
{{ book.title }}
{% endifchanged %}
</td>
</tr>
{% endfor %}
</table>
上記のように書くとコードが一つ前の書籍と同じ場合には出力されなくなります(以下参照)。
実際には違う本でコードが同じなんてことはありえませんが。
また、以下のようにelseブロックを作ることで前回のループと値が同じ場合の出力を決めることもできますが、elifに相当するブロックはないようです。
{% ifchanged %}
{{ book.isbn }}
{% else %}
同上
{% endifchanged %}
regroup/grouper
regroupは、あるリストから特定の属性をキーとして新しいグループを作ります。
{% regroup members by age as sorted %}
例えば上記の例ではmembersというリスト(Membersモデルオブジェクトのリストだと思ってください)をage属性の値でグループ化しています。
このときに作成されるsorted
という変数の中身は以下のようになっており、
# regroupされるmembers
members = [
{ "name": "taro", "age": 30 },
{ "name": "jiro", "age": 30 },
{ "name": "hanako", "age": 30 },
{ "name": "keji", "age": 30 },
{ "name": "yumi", "age": 25 },
{ "name": "sigeru", "age": 25 },
]
# 新しく作られた配列sorted
sorted = [
# age = 30のグループ
{ "grouper": 30,
"list": {
{ "name": "taro", "age": 30 },
{ "name": "jiro", "age": 30 },
{ "name": "hanako", "age": 30 },
{ "name": "keji", "age": 30 },
}
},
# age = 25のグループ
{ "grouper": 25,
"list": {
{ "name": "yumi", "age": 25 },
{ "name": "sigeru", "age": 25 },
}
}
]
少し分かりにくいですが、sortedは辞書を要素とするリストです。
要素の数はregroupのキー(今回はage)の値の数(今回は30と25なので2つ)になります。
また、sortedの中の一つ一つの辞書はgrouper
とlist
の二つのキーをもち、grouper
に対応する値にはageの値(30ないしは25)が与えられ、list
にはageが30ないしは25のオブジェクト全てのリストが与えられます。
cycle/resetcycle
cycleは、指定した値を順番に出力するためのものです。
例えば、
{% cycle "red" "blue" "green" "yellow" %}
とすることでred
, blue
, green
, yellow
が順番に出力されます。
また、cycle
はhtml要素の属性値としても使えるので、
{% for book in books %}
<div class="text-{% cycle 'danger' 'primary' 'success' %}">{{ book.title }}</div>
{% endfor %}
このcycleタグは同一のforループ内では何度呼び出しても同じ値を取るのですが、呼び出すたびにcycle以下を全て書くのは大変なので、as
で別名をつけて使い回すと楽になります。
<!-- 以下の2つは同じ結果になる -->
<!-- as 未使用 -->
{% for book in books %}
<div class="text-{% cycle 'danger' 'primary' 'success' %}">{{ book.title }}</div>
<div class="text-{% cycle 'danger' 'primary' 'success' %}">{{ book.isbn }}</div>
{% endfor %}
<!-- as を使用 -->
{% for book in books %}
<div class="text-{% cycle 'danger' 'primary' 'success' as bg %}">{{ book.title }}</div>
<div class="text-{{ bg }}">{{ book.isbn }}</div>
{% endfor %}
また、resetcycleはcycleの繰り返しをリセットするために使います。
{% regroup sorted by isbn as books_isbn %}
{% for book_isbn in books_isbn %}
<div>{{ book_isbn.grouper }}のグループ</div>
{% for b in book_isbn.list %}
<div class="text-{% cycle 'danger' 'primary' 'info' %}">{{ b.title }}</div>
{% endfor %}
{% resetcycle %}<!-- ここでcycleがリセットされる -->
{% endfor %}
先程のregroupと併用しています。
結果がこちら。
基本的には赤、青、水色のループですが、テスト3とテスト4は同じ色になっていますね。
これはyyy-abcdのグループからzzz-zzzzのグループにループが移るときにresetcycle
をまたぐのでcycleの値が初期化されるためです。
終わりに
以上で繰り返し処理に関するお話を終わります。
djangoには条件分岐処理などについても特有の仕様があるので、そちらについてもいずれまとめたいですね。
追記:条件分岐処理についてもまとめました。