2021/09/28
Django Admin モデル テンプレートDjango get_absolute_urlを使った詳細ページへのリンクの貼り方
Djangoで開発をしていく中でモデルインスタンスに紐づく詳細ページへのリンクを貼ることはよくあると思います。その際のおすすめの実装方法についてです。
結論
詳細ページへのリンクを貼る際は、Modelクラスに実装したget_absolute_urlメソッドを呼び出す。
例
まずは、以下の2つの例を見てください。どちらもArticleモデルの詳細ページへのリンクを貼っています。
index.html
# 例1
{% for article in articles %}
<p>
<a href="articles/{{ article.pk }}/">{{ article }}</a>
</p>
{% endfor %}
# 例2
{% for article in articles %}
<p>
<a href="{% url 'detail' pk=article.pk %}">{{ article }}</a>
</p>
{% endfor %}
urls.pyはこのような設定を想定しています。
urls.py
urlpatterns = [
path("", views.index),
path("articles/<int:pk>/", views.detail, name="detail"),
]
例1が推奨されないことはなんとなく予測がつくのではないでしょうか?
この書き方だと、記事のURLがarticles/<int: pk>/
からarticles/detail/<int: pk>/
に変更された場合、該当箇所のコードを全て書き換えていく必要があります。
仮に100箇所でこのリンクの貼り方をしていた場合、100箇所全てを1つ1つ直していく必要があります。
例2の場合はどうでしょう?
URLがarticles/<int: pk>/
からarticles/detail/<int: pk>/
に変更されてもurls.pyで設定されているname=detail
に変更がない限りは、コードの修正は不要となります。
ではurls.pyのname=detail
がname=article-detail
に変更されたらどうでしょう?
この場合は、例1と同じように該当箇所をhref="{% url 'article-detail' pk=article.pk %}"
に修正していく必要が出てきます。
また、urls.pyの"articles/<int:pk>/"
が"articles/<int:article_pk>/"
のようにパスコンバーター名が変更された場合も同様に、HTML側で{% url 'detail' pk=article.pk %}
→ {% url 'detail' article_pk=article.pk %}
の修正をしていく必要があります。
この問題も解消するのが次に示す例3のget_absolute_urlを使った実装方法です。
get_absolute_urlをモデルに実装する
Articleモデルにget_absolute_urlを実装します。
models.py
from django.urls import reverse
class Article(models.Model):
...
def get_absolute_url(self):
return reverse('detail', kwargs={'pk' : self.pk})
get_absolute_urlメソッドは、各Articleインスタンスに対応するURLを返すことになります。
これにより、仮にURLパスや、パスにつけたnameが変更された場合でもget_absolute_url内の処理だけを修正すればよくなり、HTML側での修正は不要となります。
index.html
# 例3
{% for article in articles %}
<p>
<!-- パスやnameの変更の影響を受けない -->
<a href="{{ article.get_absolute_url }}">{{ article }}</a>
</p>
{% endfor %}
ちなみに、get_absolute_urlを実装するとAdmin画面で「サイト上で表示」ボタンが表示されるようになります。
複数モデルでのテンプレート使い回し
最後にメリットをもう1つ。
Article以外のモデルでもget_absolute_urlメソッドを実装すれば、以下のようなテンプレートを1つ作っておき各モデル間で使い回すことができます。
object_list.html
{% for object in objects %}
<p>
<a href="{{ object.get_absolute_url }}">{{ object }}</a>
</p>
{% endfor %}
これはget_absolute_urlに限った話ではなく、メソッドを共通化しておくことによるメリットです。
可能であれば抽象モデルでget_absolute_urlメソッドを実装して、その抽象モデルを継承してArticleモデルなどのクラスを実装すると良いと思います。