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方式を採用してほしいなー。
Discussion
汎用UpdateViewの作成ありがとうございます!
model or querysetをどこにも指定していないので、self.get_object()時に落ちるのですが何か抜け漏れ等ありませんでしょうか?
検討外れなことを言っていたら申し訳ないです;;