ブログBLOG

\ Web・ゲーム開発に関する情報を発信中 /

【簡単】Djangoの1ページ内に複数のモデルフォームを使用して表示・保存する方法

Django
django , python
20230406_django-easy-multiple-form-tmb

ご覧いただきありがとうございます!
領護(りょうご)です。

「Djangoの1ページ内に複数のモデルフォームを表示してモデル別にデータベースへ保存したい!」ってことないですか?
マルチフォーム画像
↓↓↓
マルチフォーム画像
この記事では、Djangoの1ページ内に複数のモデルフォームをライブラリを使って簡単に表示・保存する方法を解説します。
GitHubのソースコードリンクも張っておくので、是非試して見てくださいね。
GitHubリポジトリを見る

目次

実行環境

Docker Desktop・・・4.17.1
Python・・・3.10.9
Django・・・4.1.7
django-betterforms・・・2.0.0

「django-betterforms」をインストール

Djangoのモデルフォームを複数表示・保存できるpipライブラリ「django-betterforms」をインストールします。

pip install django-betterforms

pipコマンドでインストール

requirements.txt
django-betterforms==2.0.0

requirements.txtに追加

「settings.py」の「INSTALLED_APPS」に追加

app_project/settings.py
INSTALLED_APPS = [
    ~
    "betterforms",  # 追加
]

Djangoプロジェクトの「settings.py」「INSTALLED_APPS」「betterforms」を追加
これでDjangoプロジェクトに「django-betterforms」を使用する準備ができました。

models.pyの作成

product/models.py
from django.db import models
from django.utils.translation import gettext_lazy as _

# 商品情報登録モデル
class Product(models.Model):

    # 商品名
    name = models.CharField(verbose_name=_("商品名"), max_length=64)

    # 商品詳細
    detail = models.TextField(verbose_name=_("商品詳細"), max_length=1000)

    # 商品金額
    price = models.PositiveIntegerField(verbose_name=_("商品金額"))

    # 登録日時
    created_at = models.DateTimeField(verbose_name=_("登録日時"), auto_now_add=True)


# 商品管理者登録モデル
class ProductManager(models.Model):

    # 担当者名
    name = models.CharField(verbose_name=_("担当者名"), max_length=32)

    # メールアドレス
    email = models.EmailField(verbose_name=_("email address"), max_length=256)

    # 電話番号
    phone_number = models.CharField(verbose_name=_("電話番号"), max_length=32)

商品情報登録と商品管理者登録の2つのモデルを作成しました。
このモデルを使って、フォームを2つ作成します。

forms.pyの作成

2つのモデルフォームとそれを1つにまとめるフォームの合計3つのフォームクラス作成します。

product/forms.py
from django import forms
from betterforms.multiform import MultiModelForm # インポート
from .models import Product, ProductManager

# 商品情報登録フォーム
class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = (
            "name",
            "detail",
            "price",
            "name",
        )


# 商品管理者登録フォーム
class ProductManagerForm(forms.ModelForm):
    class Meta:
        model = ProductManager
        fields = (
            "name",
            "email",
            "phone_number",
        )


# 商品情報登録+商品管理者登録フォーム(1つにまとめるフォーム)
class ProductMultiForm(MultiModelForm):

    form_classes = {
        "product_form": ProductForm,
        "product_manager_form": ProductManagerForm,
    }

下記の手順で作成していきます。

product/forms.py
from betterforms.multiform import MultiModelForm

1.複数のフォームをまとめることができる「MultiModelForm」ライブラリをインポートします


product/forms.py
# 商品情報登録フォーム
class ProductForm(forms.ModelForm):
    class Meta:
        model = Product
        fields = (
            "name",
            "detail",
            "price",
            "name",
        )


# 商品管理者登録フォーム
class ProductManagerForm(forms.ModelForm):
    class Meta:
        model = ProductManager
        fields = (
            "name",
            "email",
            "phone_number",
        )

2.「商品の情報を登録するモデル」と「商品の管理者を登録するモデル」のモデルフォームをそれぞれ作成します

product/forms.py
# 商品情報登録+商品管理者登録フォーム(1つにまとめるフォーム)
class ProductMultiForm(MultiModelForm):

    form_classes = {
        "product_form": ProductForm, #(フォーム名:モデルフォームクラス名)
        "product_manager_form": ProductManagerForm,
    }

3.それぞれ作成したモデルフォームを1つにまとめるフォームクラスを作成します
form_classesの書き方は、{"好きなフォーム名":モデルフォームクラス名}となるように書いてください。

これで複数のフォームを1つにまとめたフォームクラス「ProductMultiForm」ができました。
あとは、viewsとhtml側でフォームクラスを指定して表示していきます。

views.pyの作成

商品登録ページと商品登録成功ページのviewクラスを作成します。

product/views.py
from django.urls import reverse_lazy
from django.views.generic import TemplateView, CreateView
from .forms import ProductMultiForm

# 商品登録ページ
class ProductRegisterPage(CreateView):
    template_name = "product/register.html"
    form_class = ProductMultiForm # 1つにまとめたフォームクラスを指定
    success_url = reverse_lazy("success") # 登録完了後に遷移するURLを指定


# 商品登録成功ページ
class ProductSuccessPage(TemplateView):
    template_name = "product/success.html"

    # モデルが正しく保存されているか確認用
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx["products"] = Product.objects.all()
        ctx["product_managers"] = ProductManager.objects.all()
        return ctx

商品登録ページのform_classに1つにまとめたフォームクラス「ProductMultiForm」を指定します。
商品登録完了後は、商品登録成功ページに遷移するようにsuccess_urlも指定します。
※ 商品登録成功ページには、モデルが正しく保存できているか表示するためにコンテキストを追加しています。

htmlの作成

商品登録ページと商品登録成功ページに使用するhtmlファイルを作成します。

register.html
<h1>フォーム</h1>
<form action="" method="POST">
  {% csrf_token %}
  {% for field in form %}
  <div class="form-group">
    {{ field.label }}
    {{ field }}
  </div>
  {% endfor %}
  <button type="submit" class="btn btn-primary">登録する</button>
</form>

商品登録ページに使用するhtmlファイル「register.html」を作成
※ 見やすくするためにブートストラップでCSSを指定しています。

success.html
<h1>商品登録完了</h1>
<p>商品の登録が完了しました!</p>
<section>
  <h2>商品情報登録</h2>
  <table class="table">
    <thead>
      <tr>
        <th>商品名</th>
        <th>商品詳細</th>
        <th>商品金額</th>
        <th>登録日時</th>
      </tr>
    </thead>
    <tbody>
      {% for product in products %}
      <tr>
        <td>{{ product.name }}</td>
        <td>{{ product.detail }}</td>
        <td>{{ product.price }}</td>
        <td>{{ product.created_at }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</section>

<section>
  <h2>商品管理者登録</h2>
  <table class="table">
    <thead>
      <tr>
        <th>担当者名</th>
        <th>メールアドレス</th>
        <th>電話番号</th>
      </tr>
    </thead>
    <tbody>
      {% for product_manager in product_managers.values %}
      <tr>
        <td>{{ product_manager.name }}</td>
        <td>{{ product_manager.email }}</td>
        <td>{{ product_manager.phone_number }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</section>

商品登録成功ページに使用するhtmlファイル「success.html」を作成

urls.pyの作成

商品登録ページと商品登録完了ページの2つのURLを設定します。

product/urls.py
from django.urls import path

from . import views

urlpatterns = [
    # 商品登録ページ
    path("product/register/", views.ProductRegisterPage.as_view(), name="register"),
    # 商品登録完了ページ
    path("product/success/", views.ProductSuccessPage.as_view(), name="success"),
]

「urls.py」ファイル作成してURLを設定

app_project/urls.py
from django.contrib import admin
from django.urls import include, path
from product import urls as product_urls

urlpatterns = [
    # Django管理画面ログイン
    path("admin/", admin.site.urls),
    # 商品ページ
    path("", include(product_urls)), # 追加
]

Djangoプロジェクトの「urls.py」にもURLを設定

動作確認

それでは動作を確認してみます。

20230406_django-easy-multiple-form-02

http://127.0.0.1:8000/product/register/にアクセスすると2つのモデルフォームが1つのフォームとして表示されました。

20230406_django-easy-multiple-form-03

値を入力して「登録する」をクリックします。

20230406_django-easy-multiple-form-04

入力した値がモデル別にデータベースへ無事保存できました!

フォームを別々に分けて表示したい場合

20230406_django-easy-multiple-form-01
register.html
<h1>フォーム</h1>
<form action="" method="POST">
  {% csrf_token %}
  <div class="form1 mb-5">
    <h2>商品情報登録フォーム</h2>
    {% for field in form.product_form %} <!--ポイント -->
    <div class="form-group">
      {{ field.label }}
      {{ field }}
    </div>
    {% endfor %}
  </div>
  <div class="form2">
    <h2>商品管理者登録フォーム</h2>
    {% for field in form.product_manager_form %} <!--ポイント -->
    <div class="form-group">
      {{ field.label }}
      {{ field }}
    </div>
    {% endfor %}
  </div>
  <button type="submit" class="btn btn-primary">登録する</button>
</form>

フォームを別々に表示したい場合は、formの後に「フォーム名」を指定すると別々に表示することができます。
form_classesで書いた好きなフォーム名ですね。
例:{% for field in form.product_form %}

データベース保存前に処理を追加したい場合

product/views.py
from django.shortcuts import redirect # インポート
from django.views.generic import CreateView
from .forms import ProductMultiForm

# 商品登録ページ(データベース保存前に処理を追加したい場合)
class ProductRegisterPage(CreateView):
    template_name = "product/register.html"
    form_class = ProductMultiForm
    success_url = "success" # 登録完了後に遷移するURLを指定

    def form_valid(self, form):
        product = form["product_form"].save(commit=False)
        product_manager = form["product_manager_form"].save(commit=False)

        # ここに処理をゴニョゴニョ書く

        product.save()
        product_manager.save()
        return redirect(self.success_url)

データベースに保存する前に何か処理を追加したい場合は、「form_valid」関数をオーバーライドすることでゴニョゴニョとコードを書くことができます。
上記のコードは一例です。

DjangoのListViewでモデルを複数表示する方法

ListViewでモデルを複数表示する方法も解説しています。
こちらの記事も合わせてご覧ください!

最後に

Djangoの1ページ内に複数のモデルフォームを簡単に表示・保存する方法を解説しました。
モデルフォームを1つ表示するのは簡単なんですが、2つ表示するやり方って意外と難しんですよね...。
そんな時に是非参考にしてみてくださいね!
ソースコード全体は、下記のGitHubリンクからご覧ください。
GitHubリポジトリを見る

参考にさせて頂いたサイト様

https://github.com/fusionbox/django-betterforms


\ よかったらシェアしてね /

関連記事