2025年12月19日金曜日

【Django】LIstViewでページネーションを行うときにテンプレートに引き渡されるオブジェクト

Djangoの汎用ビューであるListViewがテンプレートファイルに渡されるオブジェクトは通常は【object_list】となる。

最終的にページネーションを行いたいときは、作成するViewに

class SpotListView(ListView):

model = spot

template_name = 'spot_list.html'

paginate_by = 10

のように pagenate_by = 10 とような記述が必要。 

この記述を書いて、テンプレートに引き渡されるオブジェクトは二つある 

テンプレートで使える変数
変数名                                    中身
object_list                            model のQuerySet(全件)

*context_object_name =  spots のようにした場合は,[spots]がobject_list の代わりに引き渡される

それに加えて上記の例のようにページネーションを行った場合は、

テンプレートで使える変数
変数名                                    中身
object_list                            model のQuerySet(全件)
page_obj                               ページ用の特別なオブジェクト

上記の二つが渡されることになる。

ページネーションが有効なときに渡される有効な変数は他にもある

テンプレートで使える変数
変数名                                    中身
object_list                            model のQuerySet(全件)
page_obj                               ページ用の特別なオブジェクト
paginator                            全体のページ管理情報
is_paginated                    ページネーションされているか(True / False)

❓なぜ page_obj という名前なのか?

page_obj は Django 内部で、django.core.paginator.Page クラスの インスタンス です。

つまり、単なる QuerySet ではない「ページ番号」「次ページがあるか」などの情報も持っている

👉 「ページそのもの」 を表すオブジェクトなので page_obj という名前になっています。

page_obj が持つ主な属性

属性意味
page_obj.object_list現在ページのデータ
page_obj.number現在のページ番号
page_obj.has_next次ページがあるか
page_obj.has_previous前ページがあるか
page_obj.next_page_number次のページ番号
page_obj.previous_page_number前のページ番号

ページネーション対応のコード例

<ul>
  {% for spot in page_obj %}
    <li>{{ spot.title }}</li>
  {% endfor %}
</ul>

{% if is_paginated %}
  <div class="pagination">
    {% if page_obj.has_previous %}
      <a href="?page={{ page_obj.previous_page_number }}">前へ</a>
    {% endif %}

    {{ page_obj.number }} / {{ page_obj.paginator.num_pages }}

    {% if page_obj.has_next %}
      <a href="?page={{ page_obj.next_page_number }}">次へ</a>
    {% endif %}
  </div>
{% endif %}




2025年12月17日水曜日

Django の標準URLの動作

Django のDetailViewを扱うときのテクニック 

DetailView は内部で次のように考えています:

  • URLから pk という名前の引数 を探す

  • なければ slug という名前の引数 を探す

  • どちらも無ければ エラー

内部ロジック(イメージ):

pk = self.kwargs.get("pk")

slug = self.kwargs.get("slug")

if not pk and not slug:

    raise AttributeError("Generic detail view must be called with either an object pk or a slug")


通常は、URLConfで主キーに「pk」またはslug を指定してあると見ます。
そのため、次のようにURLConfで上記の名称を使わないとエラーとなります。

path("info/<int:key>/", views.stationView.as_view())

Djangoの標準動作では、
    URL変数名:key を指定している
    しかし、DetailViewが探している名前:pk

pk を期待しているのに、key と<int:key> と指定しているためです。
もちろんこのようなしてでも問題はありませんが、下記のように指定することにより正しく扱われることになります。

class stationView(DetailView):
    model = station
    pk_url_kwarg = "key"

このように指定することにより、Djangoは「URLの key を pk として使えばいいんだな」と考えてくれます。
下記の表を参考にしながら、コーディングしましょう。

URLconf
DetailView設定
結果
<int:pk>
何も書かない
✅ OK
<int:key>
何も書かない
❌ エラー
<int:key>
pk_url_kwarg="key"
✅ OK
<slug:slug>
slug_field + slug_url_kwarg
✅ OK


slug の場合は?
        URLConf
path("info/<slug:slug>/", ...)

            Views 

class StationNameView(DetailView):
    slug_field = "slug"
    slug_url_kwarg = "slug"

    • URL変数名 ≠ slugslug_url_kwarg が必要
    • 一致していれば省略可能
      という動作となります
まとめ

なぜ Django は pk を固定で探すのか?

理由は 慣例と安全性です。

  • ほとんどの DetailView は <int:pk> を使う

  • 初心者が迷わないよう、デフォルトを固定している

  • 明示したい場合だけ pk_url_kwarg を指定する