読者です 読者をやめる 読者になる 読者になる

Django のクラスベース汎用ビューで FormSet を使う方法

Django のクラスベース汎用ビューの CreateView や UpdateView で、下のような FormSet を使いたくなりました。

from django.forms import ModelForm
from django.forms.models import inlineformset_factory
from receipts.models import Receipt, Detail

class ReceiptForm(ModelForm):
    class Meta:
        model = Receipt
        exclude = ("user", "created")

DetailFormSet = inlineformset_factory(Receipt, Detail)

しか〜し!CreateView や UpdateView は FormSet には対応していません。そこで、get_context_data と form_valid オーバーライドして、無理やり FormSet を使ってみました。下は CreateView の例。

from django.core.urlresolvers import reverse
from django.shortcuts import redirect
from django.views.generic import CreateView
from receipts.models import Receipt, Detail
from receipts.forms import ReceiptForm, DetailFormSet

class CreateReceiptView(CreateView):
    template_name = "receipts/create_receipt.html"
    form_clas = ReceiptForm

    def get_success_url(self):
        return reverse("receipt_detail", kwargs={"pk": self.object.id})

    def get_context_data(self, **kwargs):
        ctx = super(CreateReceiptView, self).get_context_data(**kwargs)
        if self.request.method == "POST":
            ctx["formset"] = DetailFormSet(self.request.POST, self.request.FILES)
        else:
            ctx["formset"] = DetailFormSet()
        return ctx

    def form_valid(self, form):
        ctx = self.get_context_data()
        formset = ctx["formset"]
        if formset.is_valid():
            self.object = form.save(commit=False)
            self.object.user = self.request.user
            self.object.save()

            # FormSet の内容を保存
            formset.instance = self.object
            formset.save()

            return redirect(self.get_redirect_url())
        else:
            ctx["form"] = form
            return self.render_to_response(ctx)

Django のソースコードを見ながら修正したわけじゃないので、テンプレートに渡すコンテキストに詰めるデータが足りないかも…。今回はこれで一応動きましたが。