Based on this answer by @ybendana:
Again, we use is_bound
to check if the form is capable of validation. See this section of the documentation:
Bound and unbound forms
A Form instance is either bound to a set of data, or unbound.
- If it’s bound to a set of data, it’s capable of validating that data and rendering the form as HTML with the data displayed in the HTML.
- If it’s unbound, it cannot do validation (because there’s no data to validate!), but it can still render the blank form as HTML.
We use a list of tuples for form objects and their details allowing for more extensibility and less repetition.
However, instead of overriding get()
, we override get_context_data()
to make inserting a new, blank instance of the form (with prefix) into the response the default action for any request. In the context of a POST request, we override the post()
method to:
- Use the
prefix
to check if each form has been submitted
- Validate the forms that have been submitted
- Process the valid forms using the
cleaned_data
- Return any invalid forms to the response by overwriting the
context
data
# views.py
class MultipleForms(TemplateResponseMixin, ContextMixin, View):
form_list = [ # (context_key, formcls, prefix)
("form_a", FormA, "prefix_a"),
("form_b", FormB, "prefix_b"),
("form_c", FormC, "prefix_c"),
...
("form_x", FormX, "prefix_x"),
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Add blank forms to context with prefixes
for context_key, formcls, prefix in self.form_list:
context[context_key] = formcls(prefix=prefix)
return context
def post(self, request, *args, **kwargs):
# Get object and context
self.object = self.get_object()
context = self.get_context_data(object=self.object)
# Process forms
for context_key, formcls, prefix in self.form_list:
if prefix in request.POST:
# Get the form object with prefix and pass it the POST data to \
# validate and clean etc.
form = formcls(request.POST, prefix=prefix)
if form.is_bound:
# If the form is bound (i.e. it is capable of validation) \
# check the validation
if form.is_valid():
# call the form's save() method or do whatever you \
# want with form.cleaned_data
form.save()
else:
# overwrite context data for this form so that it is \
# returned to the page with validation errors
context[context_key] = form
# Pass context back to render_to_response() including any invalid forms
return self.render_to_response(context)
This method allows repeated form entries on the same page, something I found did not work with @ybendana's answer.
I believe it wouldn't be masses more work to fold this method into a Mixin class, taking the form_list
object as an attribute and hooking get_context_data()
and post()
as above.
Edit: This already exists. See this repository.
NB:
This method required TemplateResponseMixin
for render_to_response()
and ContextMixin
for get_context_data()
to work. Either use these Mixins or a CBV that descends from them.