Shynya
@Shynya

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

Django 1つのmodelを複数formから保存

解決したいこと

プログラミング初心者です。
Djangoで問題集(長文問題に各小問題が複数ある)のようなWebアプリを作成しています。

forms.pyのformの複数のfieldを、ある程度固めてCSSで折り畳んだ状態からクリックで表示されるようにする為に、formの段階で複数に分け、入力値を1つのmodelに保存させたく思っています。

必要項目をformに入力後、「作成する」ボタンを押した時点でエラーになりうまくいきません。

views.pyのCreateViewが間違っていると思うのですが。
どなたか解決方法をお教えていただけますでしょうか。
よろしくお願いいたします。

発生している問題・エラー

django.db.utils.IntegrityError: NOT NULL constraint failed: book_question2.user_id

該当するソースコード

views.py
class CreateQuestionView2(LoginRequiredMixin, CreateView):
    template_name = 'book/question_create2.html'
    form_class = QuestionForm2
    form_classA = QuestionForm2A
    form_classB = QuestionForm2B
    form_classC = QuestionForm2C
    form_classD = QuestionForm2D
    form_classE = QuestionForm2E
    form_classF = QuestionForm2F
    form_classG = QuestionForm2G
    form_classH = QuestionForm2H
    form_classI = QuestionForm2I
    form_classJ = QuestionForm2J
    form_classLast = QuestionForm2last
    success_url = reverse_lazy('create-question2')

    def get_context_data(self, **kwargs):
        user = self.request.user
        context = CreateView.get_context_data(self, **kwargs)
        form1 = self.form_classA(self.request.POST or None)
        form2 = self.form_classB(self.request.POST or None)
        form3 = self.form_classC(self.request.POST or None)
        form4 = self.form_classD(self.request.POST or None)
        form5 = self.form_classE(self.request.POST or None)
        form6 = self.form_classF(self.request.POST or None)
        form7 = self.form_classG(self.request.POST or None)
        form8 = self.form_classH(self.request.POST or None)
        form9 = self.form_classI(self.request.POST or None)
        form10 = self.form_classJ(self.request.POST or None)
        formLast = self.form_classLast(self.request.POST or None)
        context.update({'form1':form1})
        context.update({'form2':form2})
        context.update({'form3':form3})
        context.update({'form4':form4})
        context.update({'form5':form5})
        context.update({'form6':form6})
        context.update({'form7':form7})
        context.update({'form8':form8})
        context.update({'form9':form9})
        context.update({'form10':form10})
        context.update({'formLast':formLast})
        return context
urls.py
urlpatterns = [
path('question2/create/', views.CreateQuestionView2.as_view(), name='create-question2')
]

↓QuestionForm2とQuestion2Aのみ表示させてますが、QuestionForm2B〜QuestionForm2JとQuestionForm2Lastもあります。

forms.py
class QuestionForm2(ModelForm):
    class Meta:
        model = Question2
        fields = ['questionBig', 'thumbnailBig1', 'thumbnailBig2', 'thumbnailBig3', 'thumbnailBig4', 'thumbnailBig5']
        widgets = {
            'questionBig': forms.Textarea(attrs={'rows':5, 'cols':60}),      
        }

class QuestionForm2A(ModelForm):
    class Meta:
        model = Question2
        fields = ['questionSmall1','thumbnailQ1Small1', 'thumbnailQ2Small1', 'thumbnailQ3Small1', 'answerSmall1','wronganswer1Small1','wronganswer2Small1','wronganswer3Small1','wronganswer4Small1','wronganswer5Small1','wronganswer6Small1','wronganswer7Small1','wronganswer8Small1','wronganswer9Small1','hint1Small1','hint2Small1','explanationSmall1', 'thumbnailA1Small1', 'thumbnailA2Small1', 'thumbnailA3Small1']
        widgets = {
            'questionSmall1': forms.Textarea(attrs={'rows':3, 'cols':45}),
            'answerSmall1': forms.Textarea(attrs={'rows':1, 'cols':45}),


            'wronganswer1Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer2Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer3Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),

            'wronganswer4Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer5Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer6Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer7Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer8Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'wronganswer9Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),

            'explanationSmall1': forms.Textarea(attrs={'rows':1, 'cols':45}),
            'hint1Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),
            'hint2Small1': forms.Textarea(attrs={'rows':1, 'cols':43}),       
        }

↓このように折り畳んだ問題が第10問まであり一番下に「作成する」ボタンがあります。

スクリーンショット 2023-06-17 19.21.24.png

models.py
class Question2(models.Model):
    questionBig = models.TextField(verbose_name="問題(大)")
    thumbnailBig1 = models.ImageField(verbose_name="画像1 (問題大)", null=True, blank= True)
    thumbnailBig2 = models.ImageField(verbose_name="画像2 (問題大)", null=True, blank= True)
    thumbnailBig3 = models.ImageField(verbose_name="画像3 (問題大)", null=True, blank= True)
    thumbnailBig4 = models.ImageField(verbose_name="画像4 (問題大)", null=True, blank= True)
    thumbnailBig5 = models.ImageField(verbose_name="画像5 (問題大)", null=True, blank= True)

    questionSmall1 = models.TextField(verbose_name="問題1")
    thumbnailQ1Small1 = models.ImageField(verbose_name="画像1 (問題)", null=True, blank= True)
    thumbnailQ2Small1 = models.ImageField(verbose_name="画像2 (問題)", null=True, blank= True)
    thumbnailQ3Small1 = models.ImageField(verbose_name="画像3 (問題)", null=True, blank= True)
    answerSmall1 = models.TextField(verbose_name="正解")
    wronganswer1Small1= models.TextField(verbose_name="誤回答1")
    wronganswer2Small1 = models.TextField(verbose_name="誤回答2")
    wronganswer3Small1 = models.TextField(verbose_name="誤回答3")
    wronganswer4Small1 = models.TextField(verbose_name="誤回答4", null=True, blank= True)
    wronganswer5Small1 = models.TextField(verbose_name="誤回答5", null=True, blank= True)
    wronganswer6Small1 = models.TextField(verbose_name="誤回答6", null=True, blank= True)
    wronganswer7Small1 = models.TextField(verbose_name="誤回答7", null=True, blank= True)
    wronganswer8Small1 = models.TextField(verbose_name="誤回答8", null=True, blank= True)
    wronganswer9Small1 = models.TextField(verbose_name="誤回答9", null=True, blank= True)
    explanationSmall1 = models.TextField(verbose_name="解説", null=True,  blank=True,)
    thumbnailA1Small1 = models.ImageField(verbose_name="画像1 (正解)", null=True, blank= True)
    thumbnailA2Small1 = models.ImageField(verbose_name="画像2 (正解)", null=True, blank= True)
    thumbnailA3Small1 = models.ImageField(verbose_name="画像3 (正解)", null=True, blank= True)
    hint1Small1 = models.TextField(verbose_name="ヒント1", null=True,  blank=True,)
    hint2Small1 = models.TextField(verbose_name="ヒント2", null=True,  blank=True,)

    questionSmall2 = models.TextField(verbose_name="問題2")
    thumbnailQ1Small2 = models.ImageField(verbose_name="画像1 (問題)", null=True, blank= True)
    thumbnailQ2Small2 = models.ImageField(verbose_name="画像2 (問題)", null=True, blank= True)
    thumbnailQ3Small2 = models.ImageField(verbose_name="画像3 (問題)", null=True, blank= True)
    answerSmall2 = models.TextField(verbose_name="正解")
    wronganswer1Small2 = models.TextField(verbose_name="誤回答1")
    wronganswer2Small2 = models.TextField(verbose_name="誤回答2")
    wronganswer3Small2 = models.TextField(verbose_name="誤回答3")
    wronganswer4Small2 = models.TextField(verbose_name="誤回答4", null=True, blank= True)
    wronganswer5Small2 = models.TextField(verbose_name="誤回答5", null=True, blank= True)
    wronganswer6Small2 = models.TextField(verbose_name="誤回答6", null=True, blank= True)
    wronganswer7Small2 = models.TextField(verbose_name="誤回答7", null=True, blank= True)
    wronganswer8Small2 = models.TextField(verbose_name="誤回答8", null=True, blank= True)
    wronganswer9Small2 = models.TextField(verbose_name="誤回答9", null=True, blank= True)
    explanationSmall2 = models.TextField(verbose_name="解説", null=True,  blank=True,)
    thumbnailA1Small2 = models.ImageField(verbose_name="画像1 (正解)", null=True, blank= True)
    thumbnailA2Small2 = models.ImageField(verbose_name="画像2 (正解)", null=True, blank= True)
    thumbnailA3Small2 = models.ImageField(verbose_name="画像3 (正解)", null=True, blank= True)
    hint1Small2 = models.TextField(verbose_name="ヒント1", null=True,  blank=True,)
    hint2Small2 = models.TextField(verbose_name="ヒント2", null=True,  blank=True,)

  #コードが長いため、「第3問目」〜「第9問目」を省略

    questionSmall10 = models.TextField(verbose_name="問題10", null=True, blank= True)
    thumbnailQ1Small10 = models.ImageField(verbose_name="画像1 (問題)", null=True, blank= True)
    thumbnailQ2Small10 = models.ImageField(verbose_name="画像2 (問題)", null=True, blank= True)
    thumbnailQ3Small10 = models.ImageField(verbose_name="画像3 (問題)", null=True, blank= True)
    answerSmall10 = models.TextField(verbose_name="正解", null=True, blank= True)
    wronganswer1Small10 = models.TextField(verbose_name="誤回答1", null=True, blank= True)
    wronganswer2Small10 = models.TextField(verbose_name="誤回答2", null=True, blank= True)
    wronganswer3Small10 = models.TextField(verbose_name="誤回答3", null=True, blank= True)
    wronganswer4Small10 = models.TextField(verbose_name="誤回答4", null=True, blank= True)
    wronganswer5Small10 = models.TextField(verbose_name="誤回答5", null=True, blank= True)
    wronganswer6Small10 = models.TextField(verbose_name="誤回答6", null=True, blank= True)
    wronganswer7Small10 = models.TextField(verbose_name="誤回答7", null=True, blank= True)
    wronganswer8Small10 = models.TextField(verbose_name="誤回答8", null=True, blank= True)
    wronganswer9Small10 = models.TextField(verbose_name="誤回答9", null=True, blank= True)
    explanationSmall10 = models.TextField(verbose_name="解説", null=True,  blank=True,)
    thumbnailA1Small10 = models.ImageField(verbose_name="画像1 (正解)", null=True, blank= True)
    thumbnailA2Small10 = models.ImageField(verbose_name="画像2 (正解)", null=True, blank= True)
    thumbnailA3Small10 = models.ImageField(verbose_name="画像3 (正解)", null=True, blank= True)
    hint1Small10 = models.TextField(verbose_name="ヒント1", null=True,  blank=True,)
    hint2Small10 = models.TextField(verbose_name="ヒント2", null=True,  blank=True,)

    category= models.CharField(
        verbose_name="カテゴリ", 
        max_length=100,
        choices = CATEGORY2
        )
    created_at = models.DateTimeField(auto_now_add=True)
    
    shoseki= models.CharField(
        verbose_name="書籍",
        max_length=200,
        null=True,  blank=True,
        choices = SHOSEKILIST
    )
    shoseki_page = models.CharField(verbose_name="書籍ページ", max_length=10, null=True,  blank=True,)
    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)

自分で試したこと

ネット記事を参考に、views.pyに下記関数を追加してみましたが、同様のエラーになりました。

views.py
#上記 「class CreateQuestionView2(LoginRequiredMixin, CreateView):」の下に追加

    if form.is_valid():
            with transaction.atomic():
                create_user = request.user
                form.save()
                form1.save()
                form2.save()
                form3.save()
                form4.save()
                form5.save()
                form6.save()
                form7.save()
                form8.save()
                form9.save()
                form10.save()
                formLast.save()
        else:
            self.form_invalid(form)

        return HttpResponseRedirect(self.get_success_url())
0

1Answer

django.db.utils.IntegrityError: NOT NULL constraint failed: book_question2.user_id

これはNULLが許可されていないのに、NULLの状態なのでエラーになっています。
「book_question2.user_id」に数値を渡すか、NULLを許可する必要があります。

ちなみに、モデルを修正した方が良いかもしれません。

・リレーション
・1対多(one to many)
・多対多(many to many)

などで調べてみてください。

もっとシンプルでスマートに作れると思います。

1Like

Comments

  1. @Shynya

    Questioner

    ご回答ありがとうございます。

    冗長的なコードだらけなんだろうととは思いながら、とにかく動くものをすぐ作りたく書いていてどうしようもなくなり質問させていただいた次第です。
    アドバイスいただいたキーワードなどで理解を深めてよりよいものを作れるようになれればと思います。

    今回の問題は、forms.pyを使用せず、views.pyのCreateQuestionView2に長々とコードを書くことでとりあえず動くようにはなりました。(これまでCreateViewは書籍を参考にクラスベースビューで書いていたので関数ベースビューにするのが大変でしたが。。)

    初めての投稿でしたので、ご丁寧に対応くださり本当にありがとうございました。
    私もいつか困っている方の手助けができるよう頑張ります!

Your answer might help someone💌