9.

Django テンプレートの href の書き方|url タグと static・reverse

編集
この記事の要点
  • Django テンプレートの hrefパスを直書きせず {% url %} タグで組み立てる
  • urls.pypath(..., name="..."){% url %} の第 1 引数になる
  • 引数は位置でもキーワードでも渡せる。クエリ文字列は {% url %} の外側に書く
  • アプリ名前空間を付けたら {% url 'blog:detail' %} のようにコロンで区切る
  • 静的ファイルへのリンクは {% static %}、モデルからは get_absolute_url()

なぜ直書きしないのか

<!-- 直書き。URL 設計を変えると全テンプレートを直すことになる -->
<a href="/blog/2026/09/07/">記事</a>

<!-- 名前で参照。urls.py を変えても自動で追従する -->
<a href="{% url 'blog:detail' pk=article.pk %}">記事</a>

直書きは、サブディレクトリ配下に配置したときにも壊れます。{% url %} なら FORCE_SCRIPT_NAME やプレフィックスの変更にも追従します。

urls.py に名前を付ける

# config/urls.py
from django.urls import include, path

urlpatterns = [
    path("blog/", include("blog.urls")),
]

# blog/urls.py
from django.urls import path
from . import views

app_name = "blog"                 # 名前空間

urlpatterns = [
    path("", views.index, name="index"),
    path("<int:pk>/", views.detail, name="detail"),
    path("<int:year>/<int:month>/", views.archive, name="archive"),
    path("tag/<slug:slug>/", views.tag, name="tag"),
]

テンプレートから参照する

<a href="{% url 'blog:index' %}">一覧</a>

<!-- 位置引数 -->
<a href="{% url 'blog:detail' article.pk %}">{{ article.title }}</a>

<!-- キーワード引数(読みやすい) -->
<a href="{% url 'blog:archive' year=2026 month=9 %}">2026年9月</a>

<!-- 変数に入れて使い回す -->
{% url 'blog:detail' article.pk as detail_url %}
<a href="{{ detail_url }}">詳細</a>
<a href="{{ detail_url }}#comments">コメント</a>

ビュー名は必ずクォートで囲みます。囲まないと変数として解釈され、NoReverseMatch になります。

クエリ文字列とフラグメント

<!-- {% url %} の中に ?page=2 は書けない。外に付ける -->
<a href="{% url 'blog:index' %}?page=2&amp;sort=new">次のページ</a>

<!-- ページャーで現在の条件を保ちたいとき -->
<a href="?page={{ page_obj.next_page_number }}">次へ</a>

<!-- アンカー -->
<a href="{% url 'blog:detail' article.pk %}#comment-{{ comment.id }}">該当コメント</a>

HTML の属性値では & をエスケープして &amp; と書くのが正式です。ブラウザは & のままでも解釈しますが、バリデータでは警告になります。

リンクの種類ごとの書き方

用途書き方
サイト内のページ{% url 'blog:detail' pk %}
CSS / JS / 画像{% static 'css/style.css' %}
アップロードされたファイル{{ article.image.url }}
モデルの詳細ページ{{ article.get_absolute_url }}
外部サイトURL を直書き + rel="noopener"
メールmailto: + アドレス
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{{ article.image.url }}" alt="{{ article.title }}">

<a href="https://example.com/" target="_blank" rel="noopener noreferrer">外部</a>

{% static %} を使うには、そのテンプレートの先頭で {% load static %} が必要です。読み込みの詳細は 静的ファイルの読み込み を参照してください。

モデル側に URL を持たせる

from django.db import models
from django.urls import reverse

class Article(models.Model):
    title = models.CharField(max_length=200)

    def get_absolute_url(self):
        return reverse("blog:detail", kwargs={"pk": self.pk})
<a href="{{ article.get_absolute_url }}">{{ article.title }}</a>

定義しておくと、CreateViewUpdateView の保存後の遷移先としても自動的に使われ、管理画面にも「サイト上で表示」ボタンが出ます。テンプレートではかっこを書かない点に注意してください。

Python 側で URL を作る

from django.shortcuts import redirect
from django.urls import reverse

url = reverse("blog:detail", kwargs={"pk": 1})     # '/blog/1/'
return redirect("blog:detail", pk=1)               # 名前をそのまま渡せる

# メールなどに載せる完全な URL
full = request.build_absolute_uri(url)             # 'https://example.com/blog/1/'

NoReverseMatch の直し方

NoReverseMatch: Reverse for 'detail' not found.
'detail' is not a valid view function or pattern name.
原因確認するところ
name= を付けていないurls.pypath()
名前空間を書き忘れapp_name があるなら 'blog:detail' と書く
引数の数が合わない<int:pk> があるのに引数なしで呼んでいないか
型が合わない<int:pk> に文字列やスラッグを渡していないか
ビュー名をクォートしていない{% url blog:detail %} は不可
include() していないルートの urls.py に登録されているか
# 登録されている URL 名を一覧する
python manage.py shell -c "from django.urls import get_resolver; print(sorted(get_resolver().reverse_dict.keys()))"

一覧から詳細へリンクする

<ul>
{% for article in articles %}
  <li>
    <a href="{% url 'blog:detail' article.pk %}">{{ article.title }}</a>
    <span>{{ article.created_at|date:"Y年n月j日" }}</span>
  </li>
{% empty %}
  <li>記事がありません</li>
{% endfor %}
</ul>

{% empty %}ループする対象が空だったときに使われます。{% if articles %} で囲む必要がありません。

いま開いているページを強調する

<nav>
  <a href="{% url 'blog:index' %}"
     class="{% if request.resolver_match.url_name == 'index' %}active{% endif %}">
    一覧
  </a>
</nav>

request.resolver_match には、いま処理されているルートの情報(url_name / app_name / kwargs)が入っています。パス文字列を比較するより壊れにくい方法です。

request をテンプレートで使うには、settings.pyTEMPLATEScontext_processorsdjango.template.context_processors.request が入っている必要があります(既定で入っています)。

ページ送りのリンク

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

<span>{{ page_obj.number }} / {{ page_obj.paginator.num_pages }}</span>

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

?page=2 だけを書くと現在の他の検索条件が消えます。条件を保ちたい場合は、ビュー側で組み立てた文字列を渡すのが確実です。

def index(request):
    params = request.GET.copy()
    params.pop("page", None)
    return render(request, "blog/index.html", {
        "page_obj": page_obj,
        "query_string": params.urlencode(),   # 'q=Python&sort=new'
    })
<a href="?page={{ page_obj.next_page_number }}&amp;{{ query_string }}">次へ</a>

安全面の注意

  • ユーザーが入力した URL をそのまま href に出さないjavascript: で始まる値を入れられる。http / https だけを許可する
  • target="_blank" には rel="noopener noreferrer" を付ける
  • Django のテンプレートは既定でエスケープされる|safe{% autoescape off %} を安易に使わない
  • ログイン後の遷移先を ?next= で受け取るときは、外部ドメインでないことを検証する(ビューでリダイレクト

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. Templateの使用準備
  2. Template の定義方法
  3. テンプレートの作成と共通化
  4. setting.pyにおけるテンプレートの設定
  5. テンプレートの名前の重複について
  6. 静的ファイルの読み込み
  7. if文
  8. テンプレートで定数を使用する方法
  9. aタグのhrefの記載方法

最近更新/作成されたページ