以前、1度のフォーム送信で複数テーブルにデータを保存する方法を記事にしたのですが、forms.pyやinlineformset_factory()のようなメソッドは、複雑な実装をする場合に、逆に使い方がややこしくなるなと感じてしまいました。
【Django】1度のフォーム送信で複数テーブルにデータを保存する方法
そこで今回、forms.pyを使用せずにフォーム送信データを複数テーブルに保存する方法を考えたので、記事にしたいと思います。
参考にしたのは下記の記事です。
【django】モデルのDBにデータを追加する create() と save()
Djangoのget_object_or_404について
【Django】formの値を取得する方法をたったの1行で解説
【Django】フォームForm:データベースへの新規登録・更新機能を組み込む(instance変数)
説明に不要なコードは適宜省いて説明しています。
使用したモデル
1度のフォーム送信で、漫画の名前を登録するテーブルと、漫画の評価を登録するテーブルの2つに、同時にデータを登録させていきます。
models.py
class Comic(models.Model):
comic_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
comic_name = models.CharField(verbose_name="漫画名",max_length=128,null=False,unique=True)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class ComicEvaluation(models.Model):
comic_evaluation_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
comic_name = models.ForeignKey(Comic,verbose_name="漫画名",to_field="comic_name",db_column="comic_name",related_name="c_name",max_length=128,null=False,on_delete=models.PROTECT)
comic_score = models.PositiveSmallIntegerField(verbose_name="評点",validators=[MaxValueValidator(100)],null=False)
comment = models.TextField(verbose_name="コメント",null=True)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
HTMLとView
htmlに関してですが、{% csrf_token %}の記載以外は一般的なhtmlと変わらない記述です。
templates/test.html
{% block content %}
<form method="POST">{% csrf_token %}
<div>漫画名<input name="comic_name" type="text"/></div>
<div>評点<input type="number" name="score"/></div>
<div>コメント<input type="text" name="comment"/></div>
<button type="submit">送信</button>
</form>
{% endblock %}
そして肝心のViewですが、こちらはrequest.POST[]でformの入力値を取得して、その取得した値を引数にしてsave()を行なっているだけです。create()でも保存は可能です。
またComicEvaluationモデルのcomic_nameに関してはComicモデルのcomic_nameの外部キーとなるため、get_object_or_404メソッドを使用して取得した値(オブジェクト?)を引数にしています。
views.py
def test(request):
if request.method == 'POST':
input_comic_name = request.POST["comic_name"]
input_score = request.POST["score"]
input_comment = request.POST["comment"]
comic = Comic(comic_name=input_comic_name)
comic.save()
saved_comic_name = get_object_or_404(Comic,comic_name=input_comic_name)
comic_evaluation = ComicEvaluation(comic_score=input_score,comment=input_comment,comic_name=saved_comic_name)
comic_evaluation.save()
return HttpResponseRedirect('../')
return render(request,'test.html')
挙動
追記
あくまでも複数テーブルにデータを保存させることを目的としたため、データベース設計やコードの書き方に問題はあると思いますが、事前にデータを入れている2テーブルを用いて選択項目を作り、3テーブルにデータを同時保存できるようにコードを書き加えたため追記しておきます。
models.py
import uuid
from django.db import models
from django.core.validators import MaxValueValidator
class Comic(models.Model):
comic_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
comic_name = models.CharField(verbose_name="漫画名",max_length=128,null=False,unique=True)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class EvaluationItem(models.Model):
evaluation_item_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
evaluation_item_id = models.PositiveSmallIntegerField(verbose_name="評価項目ID",null=False,unique=True)
evaluation_item_name = models.CharField(verbose_name="評価項目名",max_length=50,null=False)
class EvaluationItemContents(models.Model):
evaluation_item_contents_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
parent_fk = models.ForeignKey(EvaluationItem,verbose_name="親キー",db_column="parent_fk",related_name="contents",on_delete=models.PROTECT)
evaluation_item_id = models.ForeignKey(EvaluationItem,to_field="evaluation_item_id",db_column="evaluation_item_id",verbose_name="評価項目ID",related_name="items",null=False,on_delete=models.PROTECT)
item_content = models.CharField(verbose_name="項目内容名",max_length=50,null=False)
class ComicEvaluation(models.Model):
comic_evaluation_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
comic_name = models.ForeignKey(Comic,verbose_name="漫画名",to_field="comic_name",db_column="comic_name",related_name="c_names",max_length=128,null=False,on_delete=models.PROTECT)
comic_score = models.PositiveSmallIntegerField(verbose_name="評点",validators=[MaxValueValidator(100)],null=False)
comment = models.TextField(verbose_name="コメント",null=True)
nickname = models.ForeignKey(User,to_field="nickname",verbose_name="作成者",db_column="created_by",max_length=100,null=True,on_delete=models.PROTECT)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
class ComicEvaluationDetail(models.Model):
comic_evaluation_detail_pk = models.UUIDField(primary_key=True, default=uuid.uuid4,editable=False)
parent_fk = models.ForeignKey(ComicEvaluation,verbose_name="親キー",db_column="parent_fk",on_delete=models.PROTECT)
comic_name = models.ForeignKey(Comic,verbose_name="漫画名",to_field="comic_name",db_column="comic_name",related_name="c_names_detail",max_length=128,null=False,on_delete=models.PROTECT)
evaluation_item_id = models.PositiveSmallIntegerField(verbose_name="評価項目ID",null=False)
item_content = models.CharField(verbose_name="評価項目内容",max_length=50,null=True)
created_at = models.DateTimeField(verbose_name='作成日時', auto_now_add=True)
updated_at = models.DateTimeField(verbose_name='更新日時', auto_now=True)
templates/test.html
{% extends "base.html" %}
{% block title %}テストページ{% endblock %}
{% block header%}
<div>
<a class="top_page" href="{% url 'e_comic:index' %}">トップページ</a>
<a class="user_registration" href="{% url 'e_comic:users' %}">ユーザー登録</a>
</div>
<br>
{% endblock %}
{% block content %}
<form method="POST">{% csrf_token %}
<div>漫画名<input name="comic_name" type="text"/></div>
<div>評点<input type="number" name="score"/></div>
<div>コメント<input type="text" name="comment"/></div>
<div>
{% for evaluation_item in evaluation_items %}
<div>{{evaluation_item.evaluation_item_name}}
<select name="{{evaluation_item.evaluation_item_id }}">
<option selected>選択してください</option>
{% for content in evaluation_item.contents.all %}
<option value="{{content.item_content}}">{{content.item_content}}</option>
{% endfor %}
</select>
</div>
{% endfor %}
</div>
<button type="submit">送信</button>
</form>
{% endblock %}
views.py
from django.http import HttpResponseRedirect
from django.shortcuts import render
from e_comic.services.SaveFormService import getChoiceItem,saveForm
from e_comic.DAO.SaveFormDao import countChoiceItem,saveComicEvaluationDetail
def test(request):
choice_items = getChoiceItem()
if request.method == 'POST':
input_comic_name = request.POST["comic_name"]
input_score = request.POST["score"]
input_comment = request.POST["comment"]
saveForm(input_comic_name,input_score,input_comment)
count = countChoiceItem()
for i in range(count):
i += 1
input_item = request.POST[str(i)]
saveComicEvaluationDetail(input_comic_name,input_item,i)
return HttpResponseRedirect('../')
return render(request,'test.html',choice_items)
views.pyのコードが長くなりすぎるため、新たに2ファイル作成して処理を分割させました。処理の分け方に関しては今後修正していきます。
SaveFormService.py
from e_comic.DAO.SaveFormDao import choiceItem,saveComic,saveComicEvaluation
def getChoiceItem():
evaluation_items = choiceItem()
context = {
"evaluation_items" : evaluation_items
}
return context
def saveForm(input_comic_name,input_score,input_comment):
saveComic(input_comic_name)
saveComicEvaluation(input_comic_name,input_score,input_comment)
SaveFormDao.py
from django.shortcuts import get_object_or_404
from e_comic.models import Comic,ComicEvaluation,EvaluationItem,ComicEvaluationDetail
def choiceItem():
evaluation_items = EvaluationItem.objects.all()
return evaluation_items
def countChoiceItem():
item_count = EvaluationItem.objects.all().count()
return item_count
def saveComic(input_comic_name):
comic = Comic(comic_name=input_comic_name)
comic.save()
def saveComicEvaluation(input_comic_name,input_score,input_comment):
saved_comic_name = get_object_or_404(Comic,comic_name=input_comic_name)
comic_evaluation = ComicEvaluation(comic_score=input_score,comment=input_comment,comic_name=saved_comic_name)
comic_evaluation.save()
def saveComicEvaluationDetail(input_comic_name,input_item,i):
saved_comic_name = get_object_or_404(Comic,comic_name=input_comic_name)
comic_evaluation_pk = get_object_or_404(ComicEvaluation,comic_name=input_comic_name)
comic_evaluation_detail = ComicEvaluationDetail(evaluation_item_id=i,comic_name=saved_comic_name,parent_fk=comic_evaluation_pk,item_content=input_item)
comic_evaluation_detail.save()
以上です。
まだまだプログラミング経験が浅いため、ご意見いただけるととても勉強になります。