Django ひとつの view に複数の form をもたせる方法と拡張
背景
Recently, I had to implement the handling of two different forms on
the same page in Django. Though this use case is surprisingly common, I
couldn't find many examples on how to do it the "Django" way.
-- Handling Multiple Forms on the Same Page in Django最近、Djangoで2つの異なるフォームをひとつのページに表示させなきゃいけないことがあった。びっくりするほどよくあることなのに、"Django的"に書く方法がほとんど見当たらなかった。
激しく同意。
そしてしっかり"Django的"なソリューションを残してくれたインド人に感謝。
GitHub Gist badri/cbv_multiple_forms.html
使ってみて思うこと
class MultipleFormsDemoView(MultiFormsView):
template_name = "pages/cbv_multiple_forms.html"
form_classes = {
'contact': ContactForm,
'subscription': SubscriptionForm,
}
success_urls = {
'contact': reverse_lazy('form-redirect'),
'subscription': reverse_lazy('form-redirect'),
}
def contact_form_valid(self, form):
title = form.cleaned_data.get('title')
form_name = form.cleaned_data.get('action')
print(title)
return HttpResponseRedirect(self.get_success_url(form_name))
def subscription_form_valid(self, form):
email = form.cleaned_data.get('email')
form_name = form.cleaned_data.get('action')
print(email)
return HttpResponseRedirect(self.get_success_url(form_name))
複数のformをひとつのviewで扱えて感激。ただ、[form_name]_form_valid(self, form)
を自分で実装しなきゃいけなくてフォームクラスが長くなりがち。この例はフォーム2個だからいいけど、今作ろうとしてるアプリは1ページにフォームが7個もある。
Lakshmiさんが作ってくれたMultiFormsView
の良さを残しつつ、generic.UpdateView
みたいにできないだろうか?
やってみた
Lakshmiさんの作ったMultiFormMixin
、ProcessMultipleFormsView
と、DjangoのModelFormMixin
、SingleObjectTemplateResponseMixin
をうまいこと使って、MultiFormsUpdateView
を作ってみました。
# Lakshmi さんの multiforms.py のつづき
class ModelMultiFormMixin(ModelFormMixin, MultiFormMixin):
def get_form_kwargs(self, form_name):
kwargs = {}
kwargs.update({'initial': self.get_initial(form_name)})
kwargs.update({'prefix': self.get_prefix(form_name)})
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
if hasattr(self, 'object'):
kwargs.update({'instance': self.object})
return kwargs
def get_initial(self, form_name):
initial_method = 'get_%s_initial' % form_name
if hasattr(self, initial_method):
return getattr(self, initial_method)()
else:
return {'action': form_name}
def get_prefix(self, form_name):
return self.prefixes.get(form_name, self.prefix)
def get_context_data(self, **kwargs):
kwargs.setdefault('view', self)
if self.extra_context is not None:
kwargs.update(self.extra_context)
return kwargs
def get_success_url(self, form_name=None):
return self.success_urls.get(form_name, self.success_url)
def forms_valid(self, forms, form_name):
"""If the forms are valid, save the associated model."""
obj = forms.get(form_name)
obj.save()
return HttpResponseRedirect(self.get_success_url(form_name))
class BaseMultipleFormsUpdateView(ModelMultiFormMixin, ProcessMultipleFormsView):
"""
Base view for updating an existing object.
Using this base class requires subclassing to provide a response mixin.
"""
def get(self, request, *args, **kwargs):
self.object = self.get_object()
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super().post(request, *args, **kwargs)
class MultiFormsUpdateView(SingleObjectTemplateResponseMixin, BaseMultipleFormsUpdateView):
pass
なお、FormクラスはMultipleForm
クラスのかわりに、そのModelForm版であるModelMultipleForm
クラスを継承して作る必要がある。
class ModelMultipleForm(forms.ModelForm):
action = forms.CharField(max_length=60, widget=forms.HiddenInput())
そうすると、viewがかなりスッキリする。
class MultipleFormsDemoView(MultiFormsUpdateView):
template_name = "pages/cbv_multiple_forms.html"
form_classes = {
'contact': ContactForm,
'subscription': SubscriptionForm,
}
success_urls = {
'contact': reverse_lazy('form-redirect'),
'subscription': reverse_lazy('form-redirect'),
}
作り方(良い子はマネしないでね)
まずはFormViewの構造を知るところから。
ふむふむ。
次はUpdateViewの構造。
FormView
に比べて、BaseFormView
がBaseUpdateView
に変わったことで、FormMixin
を継承する前にModelFormMixin
がはさまった。
ModelFormMixin
はSingleObjectMixin
も継承している。また、TemplateResponseMixin
の前にSingleObjectTemplateResponseMixin
がはさまったが、こっちはこのまま使えそうだ。
ここでMultiFormsViewの構造を勉強。
FormView
に比べて、FormMixin
がMultiFormMixin
に変わっている。また、ProcessFormView
の前にProcessMultipleFormsView
がはさまっている。
UpdateView
と比べると、ModelFormMixin
がMultiFormMixin
に変わっていることがわかる。SingleObjectTemplateResponseMixin
は言わずもがな。
つまり、目指す姿はこうや!(ドン!
あとは試行錯誤して作りましたとさ。
めでたしめでたし!
つぶやき
Django公式のgenericビューもLakshmi方式を採用してほしいなー。