ご覧いただきありがとうございます!
領護(りょうご)です。
「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コマンドでインストール
django-betterforms==2.0.0
requirements.txtに追加
「settings.py」の「INSTALLED_APPS」に追加
INSTALLED_APPS = [
~
"betterforms", # 追加
]
Djangoプロジェクトの「settings.py」の「INSTALLED_APPS」に「betterforms」を追加
これでDjangoプロジェクトに「django-betterforms」を使用する準備ができました。
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つのフォームクラス作成します。
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,
}
下記の手順で作成していきます。
from betterforms.multiform import MultiModelForm
1.複数のフォームをまとめることができる「MultiModelForm」ライブラリをインポートします
# 商品情報登録フォーム
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.「商品の情報を登録するモデル」と「商品の管理者を登録するモデル」のモデルフォームをそれぞれ作成します
# 商品情報登録+商品管理者登録フォーム(1つにまとめるフォーム)
class ProductMultiForm(MultiModelForm):
form_classes = {
"product_form": ProductForm, #(フォーム名:モデルフォームクラス名)
"product_manager_form": ProductManagerForm,
}
3.それぞれ作成したモデルフォームを1つにまとめるフォームクラスを作成します
form_classesの書き方は、{"好きなフォーム名":モデルフォームクラス名}となるように書いてください。
これで複数のフォームを1つにまとめたフォームクラス「ProductMultiForm」ができました。
あとは、viewsとhtml側でフォームクラスを指定して表示していきます。
views.pyの作成
商品登録ページと商品登録成功ページのviewクラスを作成します。
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ファイルを作成します。
<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を指定しています。
<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を設定します。
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を設定
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を設定
動作確認
それでは動作を確認してみます。
http://127.0.0.1:8000/product/register/にアクセスすると2つのモデルフォームが1つのフォームとして表示されました。
値を入力して「登録する」をクリックします。
入力した値がモデル別にデータベースへ無事保存できました!
フォームを別々に分けて表示したい場合
<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 %}
データベース保存前に処理を追加したい場合
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リポジトリを見る