Django
このガイドは Hypermedia Components を Django + htmx と組み合わせます。
任意の django-htmx パッケージは
便利なリクエスト側ヘルパーを足しますが、以下はすべてそれなしでも
動きます。
アセット読み込み
Section titled “アセット読み込み”HC の dist ファイルを static ディレクトリに置き、base.html から参照
します。
static/ assets/ hc/ hc.css hc.behaviors.min.js macros/ index.min.js htmx.min.js{# templates/base.html #}{% load static %}<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <title>{% block title %}My app{% endblock %}</title> <link rel="stylesheet" href="{% static 'assets/hc/hc.css' %}"> <script defer src="{% static 'assets/htmx.min.js' %}"></script> <script type="module" src="{% static 'assets/hc/hc.behaviors.min.js' %}"></script> <script type="module" src="{% static 'assets/hc/macros/index.min.js' %}"></script>
{# auto-init の installCsrfHeader() ビヘイビアが毎リクエスト読み取り、 data-header で Django の期待するヘッダー名に変えます。 #} <meta name="csrf-token" content="{{ csrf_token }}" data-header="X-CSRFToken"></head><body>
{% block content %}{% endblock %}
<div class="hc-toast-region" data-hc-toast-region role="region" aria-label="Notifications"></div></body></html><meta name="csrf-token"> タグはキットの blessed な CSRF 規約です:
auto-init の behaviors バンドルに入っている installCsrfHeader() が
リクエスト時に読み取り、すべての htmx リクエストにヘッダーを付与します —
data-header で Django の X-CSRFToken に改名します。JavaScript を
書く必要はなく、meta に差し替えられたローテーション後のトークンも
自動で拾われます。(htmx ネイティブの配線が好みなら <body> の
data-hx-headers でも動きます —
htmx → CSRF を参照。)
コンポーネントの描画
Section titled “コンポーネントの描画”再利用可能なテンプレートスニペットは HC のパーツクラスとよく組み合い ます。シンプルなフィールドのインクルード:
{# templates/components/_field.html #}<div class="hc-field"{% if invalid %} data-invalid="true"{% endif %}> <label class="hc-field__label" for="{{ name }}">{{ label }}</label> <input id="{{ name }}" name="{{ name }}" value="{{ value|default_if_none:'' }}" class="hc-input" {% if invalid %} aria-invalid="true" aria-describedby="{{ name }}-error" {% endif %}> {% if message %} <p id="{{ name }}-error" class="hc-field__message">{{ message }}</p> {% endif %}</div>ページからの利用:
<form method="post" action="{% url 'users:create' %}" data-hx-post="{% url 'users:create' %}" data-hx-target="this" data-hx-swap="outerHTML"> {% include 'components/_field.html' with name='email' label='Email' value=form.email.value message=form.email.errors|first invalid=form.email.errors %} <button class="hc-button" data-variant="primary" type="submit">Create</button></form>Django フォームのスタイリング
Section titled “Django フォームのスタイリング”カスタム HTML を書かずに Django のフォームウィジェットへ hc-input を
適用するには、フォームクラスで属性を設定します:
class UserForm(forms.Form): email = forms.EmailField( widget=forms.EmailInput(attrs={'class': 'hc-input'}) )または {% django-widget-tweaks %} ライブラリを使ってテンプレート側で:
{% load widget_tweaks %}{{ form.email|add_class:"hc-input"|attr:"aria-invalid:true" }}HTML フラグメントを返す
Section titled “HTML フラグメントを返す”htmx リクエストには、ターゲット領域のフラグメントだけを返します。
フラグメントごとに専用テンプレートを使うか、request.htmx で分岐
します。
from django.shortcuts import renderfrom django.template.loader import render_to_stringfrom django.http import HttpResponse
def list_items(request): items = Item.objects.all() template = 'items/_rows.html' if request.htmx else 'items/list.html' return render(request, template, {'items': items})
def delete_item(request, item_id): item = get_object_or_404(Item, pk=item_id) item.delete() return HttpResponse( '', headers={ 'HX-Trigger': json.dumps({ 'hc:toast': { 'message': f'Deleted "{item.name}".', 'variant': 'success', }, }), }, )items/_rows.html:
{% for item in items %} <tr id="item-{{ item.id }}"> <td>{{ item.name }}</td> <td> <span class="hc-badge" data-variant="{{ item.status }}"> {{ item.get_status_display }} </span> </td> <td> <span class="hc-action"> <button class="hc-button" data-size="sm" data-variant="error" type="button" data-hc-confirm="Delete {{ item.name }}?" data-hx-delete="{% url 'items:delete' item.id %}" data-hx-trigger="hc:confirmed" data-hx-target="closest tr" data-hx-swap="outerHTML" data-hx-disabled-elt="this" data-hx-indicator="closest .hc-action"> Delete </button> <span class="hc-spinner htmx-indicator" aria-hidden="true"></span> </span> </td> </tr>{% endfor %}HX-Trigger によるトースト
Section titled “HX-Trigger によるトースト”クライアント側の通知には HX-Trigger レスポンスヘッダーを使います。
hc:toast のドキュメント化されたリスナーがトーストビヘイビアです。
import jsonfrom django.http import HttpResponse
def save_item(request): # …mutate state… return HttpResponse( rendered_fragment, headers={ 'HX-Trigger': json.dumps({ 'hc:toast': {'message': 'Saved.', 'variant': 'success'}, }), }, )小さなヘルパーで JSON エンコードの繰り返しを避けられます:
def hx_trigger(response, events): response['HX-Trigger'] = json.dumps(events) return responseCSRF のヒント
Section titled “CSRF のヒント”- Django は POST / PUT / PATCH / DELETE に CSRF トークンを要求します。
base.htmlの<meta name="csrf-token" … data-header="X-CSRFToken">タグ(上記レイアウト参照)だけで十分です —installCsrfHeader()が すべての htmx リクエストにヘッダーを付与します。フォームボディで トークンを運べない DELETE / PATCH も含めてです。 - meta タグはサーバ側で
{{ csrf_token }}を読むため、JavaScript が CSRF クッキーを読む必要は一切ありません — 多層防御としてCSRF_COOKIE_HTTPONLY = Trueを設定することもできます。 - トークンのローテーション: Django はログイン時に CSRF トークンを ローテートします。レイアウトをキャッシュせず(上記のように) テンプレートから meta タグを描画すれば、次のフルページロードで 新しいトークンが届きます。
htmx の検知
Section titled “htmx の検知”django-htmx ミドルウェアは request.htmx(htmx リクエストで
truthy)、request.htmx.boosted、およびリクエストヘッダーを属性として
追加します。ミドルウェアなしなら、自分でヘッダーを確認します:
def list_items(request): is_htmx = request.headers.get('HX-Request') == 'true' template = 'items/_rows.html' if is_htmx else 'items/list.html' return render(request, template, {'items': Item.objects.all()})whitenoise— 本番でハッシュつきアセット URL が機能するようSTATIC_ROOTとSTATICFILES_STORAGEを設定してください。HC の アセットは静的で、キャッシュバスティングの恩恵を受けます。form.as_pのデフォルト — 同梱のフォーム描画はhc-inputを 出力しません。フィールドを手で描画するか、widget attrs / widget-tweaks でクラスを注入してください。HX-Trigger-After-Settle— htmx が DOM 変更のセトリングを終えた 後にだけ発火すべきイベントには、HX-Triggerの代わりにHX-Trigger-After-Settleヘッダーを使ってください。