By default some of Django’s class-based views support just a single form per view. This, of course, does not always play nicely with what you’re trying to accomplish. I’ve yet to come across a concrete example of how to accomplish this so after a bit of experimentation and some time spent diving into Django’s code, I’ve created a decent workaround. Please let me know in the comments if there’s a better way to do this.
For this example I’ll use a generic UpdateView and two ModelForms. The same technique should work with most of the class-based views as well as regular forms.
I’ll also mention that this handles the validation and submission of either form, but not both forms at the same time. The user is only submitting a single form, so you only need to handle that one.
There’s nothing special about the models or the templates, so let’s look at the view:
|
from django.views.generic import UpdateView |
|
from django.shortcuts import get_object_or_404 |
|
from django.core.urlresolvers import reverse_lazy |
|
from app.models import Model |
|
from app.forms import Form1, Form2 |
|
|
|
|
|
class MyView(UpdateView): |
|
|
|
template_name = 'template.html' |
|
form_class = Form1 |
|
second_form_class = Form2 |
|
success_url = reverse_lazy('success') |
|
|
|
def get_context_data(self, **kwargs): |
|
context = super(MyView, self).get_context_data(**kwargs) |
|
if 'form' not in context: |
|
context['form'] = self.form_class(initial={'some_field': context['model'].some_field}) |
|
if 'form2' not in context: |
|
context['form2'] = self.second_form_class(initial={'another_field': context['model'].another_field}) |
|
return context |
|
|
|
def get_object(self): |
|
return get_object_or_404(Model, pk=self.request.session['someval']) |
|
|
|
def form_invalid(self, **kwargs): |
|
return self.render_to_response(self.get_context_data(**kwargs)) |
|
|
|
def post(self, request, *args, **kwargs): |
|
|
|
# get the user instance |
|
self.object = self.get_object() |
|
|
|
# determine which form is being submitted |
|
# uses the name of the form's submit button |
|
if 'form' in request.POST: |
|
|
|
# get the primary form |
|
form_class = self.get_form_class() |
|
form_name = 'form' |
|
|
|
else: |
|
|
|
# get the secondary form |
|
form_class = self.second_form_class |
|
form_name = 'form2' |
|
|
|
# get the form |
|
form = self.get_form(form_class) |
|
|
|
# validate |
|
if form.is_valid(): |
|
return self.form_valid(form) |
|
else: |
|
return self.form_invalid(**{form_name: form}) |
The first thing the view does is set the template, success_url and the two forms. Next we define the get_context_data method which adds either form to the context if it is missing.
UPDATE: There was an issue with the get_context_data method in which the form would not be populated with values from the model if a form was submitted with invalid data (and self.form_invalid was called). The current fix is to manually pass the data to the form using the initial argument.
The get_object method overrides the default behavior and gets a model instance based on a session variable. This isn’t necessary if you’re using the standard method of providing a pk or slug in the URL.
The next change is very important – override the default form_invalid method. The default method returns a single form in the context:
|
return self.render_to_response(self.get_context_data(form=form)) |
Since we have two forms, with different names, we need to switch to using **kwargs instead.
Finally, we override the post method to determine which form was submitted based on the name of the submit button (you could alternatively use a hidden field), validate the form and call form_valid or form_invalid. These methods will save the form and redirect to the success_url, or return to the view and show any errors, respectively.
And for the sake of clarity, the forms in your templates simply need a named submit button:
|
<form action="{% url 'myview' %}" method="post" enctype="multipart/form-data"> |
|
… |
|
<input type="submit" name="form" value="Submit" /> |
|
</form> |
|
<form action="{% url 'myview' %}" method="post" enctype="multipart/form-data"> |
|
… |
|
<input type="submit" name="form2" value="Submit" /> |
|
</form> |
Like this:
Like Loading...