導入
いよいよ「書き込み」です。フォームから記事を投稿し、保存して、一覧へ戻る。第4章のフォームと第5章のモデルが、ここで1つにつながります。
説明
GET のときは空のフォームを表示し、POST のときは検証して保存、成功したら一覧へリダイレクト(PRG パターン)します。
import django
from django.conf import settings
if not settings.configured:
settings.configure(DEBUG=True, SECRET_KEY="dev",
ROOT_URLCONF=__name__, ALLOWED_HOSTS=["testserver"],
INSTALLED_APPS=[],
DATABASES={"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}})
django.setup()
from django.db import models, connection
from django.http import HttpResponse
from django.shortcuts import redirect
from django.urls import path
from django import forms
from django.test import Client
class Post(models.Model):
title = models.CharField(max_length=100)
body = models.TextField()
class Meta:
app_label = "blog"
with connection.schema_editor() as schema:
schema.create_model(Post)
# 入力フォーム(項目と検証を宣言)
class PostForm(forms.Form):
title = forms.CharField(max_length=100)
body = forms.CharField(widget=forms.Textarea)
def post_create(request):
if request.method == "POST":
form = PostForm(request.POST)
if form.is_valid(): # 検証OKなら
Post.objects.create(**form.cleaned_data) # 保存
return redirect("/") # 一覧へ戻す
return HttpResponse("入力エラー: " + str(form.errors))
return HttpResponse("(ここに投稿フォームを表示)")
def post_list(request):
titles = [p.title for p in Post.objects.all()]
return HttpResponse("記事: " + " / ".join(titles))
urlpatterns = [
path("", post_list),
path("create/", post_create),
]
client = Client()
# 正しい投稿 → 保存され、302で / へ
r = client.post("/create/", {"title": "投稿テスト", "body": "本文です"})
print("投稿後ステータス:", r.status_code, "->", r["Location"])
# 不正な投稿(titleが空)→ 検証で弾かれる
print("不正投稿:", client.post("/create/", {"title": "", "body": "x"}).content.decode())
# 一覧を確認
print(client.get("/").content.decode())
form = PostForm(request.POST)… 送信データでフォームを作る。form.is_valid()… 検証。通ればform.cleaned_data({"title": ..., "body": ...})に安全な値が入る。Post.objects.create(**form.cleaned_data)… 辞書を展開してそのまま保存。フォームの項目名とモデルのフィールド名をそろえておくと、この1行で書けます。- 保存後は
redirect("/")。これがフォーム処理の黄金パターンです。
実務では、モデルからフォームを自動生成する ModelForm(レッスン18で紹介)を使うと、form.save() の1行で保存でき、さらに短く書けます。
やってみよう
client.post("/create/", {...}) の title / body を変えて何度か投稿し、client.get("/") の一覧が増えることを確かめましょう。title を空にすると検証で弾かれ、保存されません。
演習
PostForm に必須の整数フィールド views(forms.IntegerField)を追加し、投稿時に閲覧数も一緒に保存できるようにしてください。Post モデルにも views = models.IntegerField(default=0) を足す必要があります。正しい投稿 {"title": "x", "body": "y", "views": "5"} が 302 になれば成功です。
ヒント1を見る
モデルに views = models.IntegerField(default=0)、フォームに views = forms.IntegerField() を追加します。
ヒント2を見る
保存は Post.objects.create(**form.cleaned_data) のままで、views も含めて保存されます。テストの POST データに "views": "5" を足します。