Django テンプレートとフォーム
テンプレートの基本
{# blog/templates/blog/base.html #}
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>{% block title %}ブログ{% endblock %}</title>
{% block extra_css %}{% endblock %}
</head>
<body>
<header>
<nav>
<a href="{% url 'blog:post_list' %}">記事一覧</a>
{% if user.is_authenticated %}
<span>{{ user.username }}</span>
<a href="{% url 'logout' %}">ログアウト</a>
{% else %}
<a href="{% url 'login' %}">ログイン</a>
{% endif %}
</nav>
</header>
<main>
{% block content %}{% endblock %}
</main>
</body>
</html>
テンプレート継承
{# blog/templates/blog/post_list.html #}
{% extends 'blog/base.html' %}
{% block title %}記事一覧{% endblock %}
{% block content %}
<h1>記事一覧</h1>
{% for post in posts %}
<article>
<h2><a href="{% url 'blog:post_detail' slug=post.slug %}">{{ post.title }}</a></h2>
<p>{{ post.body|truncatewords:30 }}</p>
<time>{{ post.created_at|date:'Y年m月d日' }}</time>
</article>
{% empty %}
<p>記事がありません。</p>
{% endfor %}
{# ページネーション #}
{% 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 %}
{% endblock %}
テンプレートタグとフィルター
{# 変数の表示 #}
{{ post.title }}
{{ post.body|linebreaksbr }} {# 改行をbrタグに変換 #}
{{ post.body|truncatechars:100 }} {# 100文字で切り詰め #}
{{ post.created_at|date:'Y/m/d H:i' }}
{{ price|floatformat:0 }} {# 小数点以下を丸める #}
{{ items|length }} {# 要素数 #}
{{ text|default:'未設定' }} {# Noneや空文字の場合のデフォルト #}
{# 条件分岐 #}
{% if post.status == 'published' %}
<span class="badge">公開中</span>
{% elif post.status == 'draft' %}
<span class="badge">下書き</span>
{% endif %}
{# ループ #}
{% for post in posts %}
{{ forloop.counter }}. {{ post.title }}
{% endfor %}
{# インクルード #}
{% include 'blog/_sidebar.html' with recent_posts=posts %}
{# 静的ファイル #}
{% load static %}
<link rel="stylesheet" href="{% static 'css/style.css' %}">
<img src="{% static 'images/logo.png' %}" alt="ロゴ">
フォームクラス
# blog/forms.py
from django import forms
from .models import Post
class PostForm(forms.ModelForm):
class Meta:
model = Post
fields = ['title', 'body', 'category', 'status']
widgets = {
'title': forms.TextInput(attrs={
'class': 'form-control',
'placeholder': 'タイトルを入力'
}),
'body': forms.Textarea(attrs={
'class': 'form-control',
'rows': 10
}),
}
def clean_title(self):
title = self.cleaned_data['title']
if len(title) < 3:
raise forms.ValidationError('タイトルは3文字以上必要です')
return title
フォームの表示
<form method="post" enctype="multipart/form-data">
{% csrf_token %}
{% for field in form %}
<div class="form-group">
<label for="{{ field.id_for_label }}">{{ field.label }}</label>
{{ field }}
{% if field.errors %}
{% for error in field.errors %}
<span class="error">{{ error }}</span>
{% endfor %}
{% endif %}
</div>
{% endfor %}
<button type="submit">保存</button>
</form>
まとめ
- テンプレート継承(extends/block)でレイアウトを共通化
{% url %} でURL逆引き、{% static %} で静的ファイル参照
{% csrf_token %} でCSRF対策(POSTフォームに必須)
ModelForm でモデルと連動したフォームを簡単に作成
- フィルター(date, truncatewords等)でテンプレート内のデータ整形