Djangoでフォームの入力欄の追加をする方法(メモ)
DjangoのFormsetを使うときにフォームの追加をする方法をまとめておきます。メモなのでだいぶ雑です。
バージョンは3.2.20です。
①Djangoのみでやる
こちらのサイトを参考にやっていたのですが、グローバル変数を使っており実用向きではありません。グローバル変数を使わずに書いてみます。
models.py
from django.db import models class Group(models.Model): name = models.CharField(max_length=100) class Member(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) name = models.CharField(max_length=100) email = models.EmailField(max_length=255)
views.py
from django.views import generic from django import forms from django.shortcuts import render from .models import Group, Member class MemberView(generic.FormView): template_name = 'app/member.html' def get_success_url(self): return reverse_lazy('app:member', kwargs={'pk': self.kwargs['pk']}) def get_form(self): group = Group.objects.get(pk=self.kwargs['pk']) MemberFormset = forms.modelformset_factory( Member, fields=["name", "email",], extra=3, max_num=100, can_delete=True, ) queryset = Member.objects.filter(group=group) if self.request.method == 'POST': formset = MemberFormset(**self.get_form_kwargs()) else: formset = MemberFormset(queryset=queryset) return formset def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) return context def get_form_kwargs(self): kwargs = super.get_form_kwargs() if self.request.POST and 'add' in self.request.POST: kwargs['data'] = self.request.POST.copy() kwargs['data']['form-TOTAL_FORMS'] = int(kwargs['data']['form-TOTAL_FORMS']) + 1 return kwargs def post(self, request, *args, **kwargs): if 'add' in request.POST: context = self.get_context_data(**kwargs) return render(request, self.template_name, context) return super().post(request, *args, **kwargs) def form_valid(self, form): if 'submit' in self.request.POST: data = form.cleaned_data group = Group.objects.get(pk=self.kwargs['pk']) for member_parameter in data: if member_parameter: try: member = member_parameter['id'] member.name = member_parameter['name'] member.email = member_parameter['email'] member.save() except: Member.objects.create( group=group, name=member_parameter['name'], email=member_parameter['email'], ) return super().form_valid(form)
テンプレートには {{ form }}
で描画できます。
②JavaScriptを使ってやる
こちらのサイトを参考にやりました。JavaScriptはこちらのものを一部変更して、更新処理などをできるようにしました。
models.py(①と同じです)
from django.db import models class Group(models.Model): name = models.CharField(max_length=100) class Member(models.Model): group = models.ForeignKey(Group, on_delete=models.CASCADE) name = models.CharField(max_length=100) email = models.EmailField(max_length=255)
forms.py
from .models import Group, Member from django import forms MemberFormset = forms.inlineformset_factory( Group, Member, fields=('name', 'email'), extra=0, can_delete=True )
views.py
from .forms import MemberFormset from .models import Group, Member from django.shortcuts import redirect, render, get_object_or_404 def member(request, pk): group = get_object_or_404(Group, pk=pk) formset = MemberFormset(request.POST or None, instance=group) if request.method == 'POST' and formset.is_valid(): formset.save() return redirect('app:member', pk=group.pk) context = { 'formset': formset, 'group': group, } return render(request, 'app/member.html', context)
$(function() { // フォームの追加をする部分は同じなので省略します $('#form').submit(function() { const text = $('.text'); $('[name=form-TOTAL_FORMS]').val(text.length); // それぞれの入力欄の__prefix__をindexで置換する text.each(function(index, element){ originalHtml = $(element).html(); replacedHtml = $(element).html().replace(/__prefix__/g, index); // empty_formのみ置換の処理を行う if (originalHtml !== replacedHtml) { // valueが消えるのですべての項目に対して保存 value_name = $(element).find("#id_form-__prefix__-name").val(); value_email = $(element).find("#id_form-__prefix__-email").val(); is_delete = $(element).find("#id_form-__prefix__-DELETE").prop('checked'); $(element).html(replacedHtml); $(element).find("#id_form-" + index + "-name").val(value_name); $(element).find("#id_form-" + index + "-email").val(value_email); $(element).find("#id_form-" + index + "-DELETE").prop('checked', is_delete); } }); }); });
テンプレートにはすでに入力されている(されていない場合は空)formsetと、empty_formを1つ書いておきます。
ビューの実装はこちらも参考にしました。
2023/10/26追記: JavaScriptも一部変える必要があったので更新しました。