自動化無しに生活無し

WEBとかAIとかLinux関係をひたすら書く備忘録系ブログ

【Django】一対多、多対多のリレーションでforms.pyを使ったバリデーションとフォームを表示

thumbnail

本記事ではDjangoで一対多、多対多のリレーションを実装した上で、forms.pyを使用し、フォームをバリデーションする。

また、単にforms.pyからフォームのテンプレートを作るだけでなく、複数指定が要求されるフォームの作り方についても解説する。

コードはDjangoで多対多のリレーションをテンプレートで表示する方法【ManyToManyField】から流用。

【1】forms.pyを使用したフォームバリデーション+forms.pyで定義したテンプレート

例えばmodels.pyが下記の様な状態だったとする。

from django.db import models
import uuid


class Category(models.Model):
    class Meta:
        db_table    = "category"

    id          = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name        = models.CharField(verbose_name="カテゴリ名",max_length=10)

    def __str__(self):
        return self.name

class Allergy(models.Model):
    class Meta:
        db_table    = "allergy"

    id          = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    name        = models.CharField(verbose_name="アレルギー名",max_length=10)

    def __str__(self):
        return self.name

class Menu(models.Model):
    class Meta:
        db_table    = "menu"

    category    = models.ForeignKey(Category,verbose_name="カテゴリ名",on_delete=models.PROTECT)
    name        = models.CharField(verbose_name="品名",max_length=20)
    breakfast   = models.BooleanField(verbose_name="朝メニュー",default=True)
    lunch       = models.BooleanField(verbose_name="昼メニュー",default=True)
    dinner      = models.BooleanField(verbose_name="夜メニュー",default=True)
    takeout     = models.BooleanField(verbose_name="テイクアウト",default=True)
    price       = models.IntegerField(verbose_name="価格")
    allergy     = models.ManyToManyField(Allergy,verbose_name="含むアレルギー",blank=True)

    def __str__(self):
        return self.name

送信するものはMenuだけとする場合、forms.pyは以下のようになる。

from django import forms 
from .models import Menu


class MenuForm(forms.ModelForm):
    class Meta:
        model   = Menu
        fields  = ["category","name","breakfast","lunch","dinner","takeout","price","allergy",]

テンプレートはこうなる。フォームタグのみ表示。

<form action="" method="POST" class="py-2">
    {% csrf_token %}

    {{ form.as_p }}

    <input class="form-control" type="submit" value="送信">
</form>

続いて、views.py

from django.shortcuts import render,redirect

from django.views import View
from .models import Menu
from .forms import MenuForm

class MenuView(View):

    def get(self, request, *args, **kwargs):

        form    = MenuForm()

        data    = Menu.objects.order_by("category")
        context = { "data":data,
                    "form":form}

        return render(request,"menulist/index.html",context)
    
    def post(self, request, *args, **kwargs):

        form    = MenuForm(request.POST)

        if form.is_valid():
            print("バリデーションOK")
            form.save()
        else:
            print("バリデーションNG")


        return redirect("menulist:index")


index   = MenuView.as_view()

多対多でも1対多でも、リレーションなしと同様にバリデーションが可能。その後は普通に.save()を実行すればよい。

この状態で、開発用サーバーを起動してブラウザで確認するとこうなる。

多対多のフォームをそのまま表示

これがforms.pyで定義したテンプレートをそのまま表示させた場合である。使いづらそうだ。

問題なく投稿とバリデーションを行うことができるが、これでは一般人には受け入れがたいものになってしまう。

【2】forms.pyを使用したフォームバリデーション+独自に作ったテンプレート

【1】では使いづらいフォームだったので、主にテンプレート部を修正する。

本項はmodels.pyforms.pyは同じ、views.pyとテンプレートが先と違う。まず、views.py。

from django.shortcuts import render,redirect

from django.views import View
from .models import Menu,Category,Allergy
from .forms import MenuForm

class MenuView(View):

    def get(self, request, *args, **kwargs):

        cates   = Category.objects.all()
        alles   = Allergy.objects.all()

        data    = Menu.objects.order_by("category")
        context = { "data":data,
                    "cates":cates,
                    "alles":alles }

        return render(request,"menulist/index.html",context)

    def post(self, request, *args, **kwargs):

        form    = MenuForm(request.POST)

        if form.is_valid():
            print("バリデーションOK")
            form.save()

        else:
            print("バリデーションNG")



        return redirect("menulist:index")


index   = MenuView.as_view()

続いて、テンプレート。やや複雑になっている。

<form action="" method="POST" class="py-2">
    {% csrf_token %}

    <div class="row">
        <div class="col-sm-6">
            <div class="py-1">
                <select name="category">
                    {% for cate in cates %}
                    <option value="{{ cate.id }}">{{ cate.name }}</option>
                    {% endfor %}
                </select>
            </div>

            <div class="py-1">
                <input type="text" name="name" placeholder="メニュー名" required>
                <input type="number" name="price" placeholder="価格" required>
            </div>

            <div class="py-1">
                <div>含有アレルギー</div>
                {% for alle in alles %}
                <input id="{{ alle.id }}" class="input_chk" type="checkbox" name="allergy" value="{{ alle.id }}">
                <label class="surround_label" for="{{ alle.id }}">{{ alle.name }}</label>
                {% endfor %}
            </div>
        </div>


        <div class="col-sm-6">
            <div class="row align-items-center">
                <div class="col-6">朝メニュー</div><div class="col-6 text-right"><input id="breakfast" class="input_chk" type="checkbox" name="breakfast" checked><label class="chk_label" for="breakfast"></label></div>
                <div class="col-6">昼メニュー</div><div class="col-6 text-right"><input id="lunch"     class="input_chk" type="checkbox" name="lunch"     checked><label class="chk_label" for="lunch"    ></label></div>
                <div class="col-6">夜メニュー</div><div class="col-6 text-right"><input id="dinner"    class="input_chk" type="checkbox" name="dinner"    checked><label class="chk_label" for="dinner"   ></label></div>
                <div class="col-6">持ち帰り</div><div class="col-6   text-right"><input id="takeout"   class="input_chk" type="checkbox" name="takeout"   checked><label class="chk_label" for="takeout"  ></label></div>
            </div>
        </div>

    </div>
    <input class="form-control" type="submit" value="送信">
</form>

これをブラウザから確認するとこうなる。

見やすくなったフォーム

とても見やすい。そして扱いやすい。複数選択だった含有アレルギーも押しやすく、PC初心者にも優しい。テンプレート側で自由にフォームを作り、適度にCSSで装飾を施すだけで全然違う。

結論

forms.pyでテンプレートで使うフォームを定義するのはどうかと思う。使用するHTMLと属性がサーバー側に制限され、【1】のようにフォームの操作性が悪くなってしまう。そういう意味でforms.pywidgetsとか要らないんじゃなかろうか?

一方で【2】のように、フロント側で記述するフォームはmodels.pyで定義したものとフォームのname属性だけ統一させ、後は自由に指定すれば、ユーザー側から見て使いやすいウェブアプリを作ることができる。それだけでなくフロントとサーバーサイドを疎結合化することも可能だ。

いずれにしても、一対多、多対多のリレーションでもforms.pyのバリデーションはリレーションなしのものと同様に行う。

ソースコード

【1】forms.pyを使用したフォームバリデーション+forms.pyで定義したテンプレート

https://github.com/seiya0723/django_m2m_forms

【2】forms.pyを使用したフォームバリデーション+独自に作ったテンプレート

https://github.com/seiya0723/django_m2m_customforms

スポンサーリンク

シェアボタン

Twitter LINEで送る Facebook はてなブログ