7
5

More than 1 year has passed since last update.

djangoテンプレートの繰り返し処理についてまとめる

Last updated at Posted at 2021-11-02

今回のお題

今回は、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などとしてループ回数にアクセスします。

forloop使用例
{% for member in members %}
<div>{{ forloop.counter }}人目の登録者は{{ member.name }}さんです。</div>
{% end %}

for ~ empty

for ~ empty ~ endforのブロックを作ることで、for文で繰り返すリストや辞書が空の場合にempty以下の内容を出力します。

for~empty使用例
{% for member in members %}
<div>{{ member.name }}</div>
{% empty %}
<div>登録されているメンバーはいません</div>
{% endfor %}

回数指定の繰り返し

djangoテンプレートではfor文の中でrange関数を呼び出すことができません。

その代替案として書籍等でwithブロックの利用が提案されているのを見かけますが、個人的にはあまりしっくり来ませんでした。

withを使ってもブロックのネストは避けられないので、いっそのこと以下のように書けば良いのではないかと思っています。

3回だけ繰り返したい場合
{% for member in members %}
  {% if forloop.counter <= 3 %}
    <div>{{ member.name }}</div>
  {% endif %}
{% endfor %}

ifchanged

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>

上記のように書くとコードが一つ前の書籍と同じ場合には出力されなくなります(以下参照)。
実際には違う本でコードが同じなんてことはありえませんが。

Image from Gyazo

また、以下のようにelseブロックを作ることで前回のループと値が同じ場合の出力を決めることもできますが、elifに相当するブロックはないようです。

{% ifchanged  %}
  {{ book.isbn }}
{% else %}
  同上
{% endifchanged %}

Image from Gyazo
先ほどまで空欄だった部分に同上の文字が入る。

regroup/grouper

regroupは、あるリストから特定の属性をキーとして新しいグループを作ります。

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の中の一つ一つの辞書はgrouperlistの二つのキーをもち、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 %}

とすると
Image from Gyazo
となります(BootStrapを使いました)。

この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 %}

結果
Image from Gyazo

また、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と併用しています。

結果がこちら。
Image from Gyazo
基本的には赤、青、水色のループですが、テスト3とテスト4は同じ色になっていますね。

これはyyy-abcdのグループからzzz-zzzzのグループにループが移るときにresetcycleをまたぐのでcycleの値が初期化されるためです。

終わりに

以上で繰り返し処理に関するお話を終わります。

djangoには条件分岐処理などについても特有の仕様があるので、そちらについてもいずれまとめたいですね。

追記:条件分岐処理についてもまとめました。

7
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
5