LoginSignup
1

More than 1 year has passed since last update.

【Django】forms.pyを使わずに送信データを複数テーブルに保存させる方法

Last updated at Posted at 2022-05-06

 以前、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')

挙動

Image from Gyazo

Image from Gyazo

追記

 あくまでも複数テーブルにデータを保存させることを目的としたため、データベース設計やコードの書き方に問題はあると思いますが、事前にデータを入れている2テーブルを用いて選択項目を作り、3テーブルにデータを同時保存できるようにコードを書き加えたため追記しておきます。

Image from Gyazo

Image from Gyazo

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()

 以上です。
 まだまだプログラミング経験が浅いため、ご意見いただけるととても勉強になります。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1