Create views that can manage more than 1 form, possibly in more than 1 </form> tag.
Clone or download the repo and add multiforms.py, qualname and django_betterforms to your project
The python 3.3+ __qualname__ attribut is used to retrieve a FormClass' name. For older versions it
uses source code inspection, and if that fails it will instanciate a copy of the FormClass and use
its __name__ instead. If you use python 3.2- and don't want to use the automatic naming system, you
can simply use a ("name", FormClass) tuples instead of a FormClass to manually assign a name instead.
A prefix is automatically assigned to every form based on its class' lower case name or user defined name. On POST, a form will be assigned POST data if its prefix is found in the POST keys. Without this check, every forms would receive POST data, be bound, and thus go through form validation, weither or not they where part of the same </form> tag of the submitted form.
A FormClass cannot be used twice unless each duplicate class is given a unique name. This is done by
giving a ("name", FormClass) tuple instead of a FormClass. Note that if you are using multiple
duplicates of the same FormClass, a django Formset might be more appropriate.
form_classes = [
    ContactForm,
    ("mycontactform", ContactForm),
    ("i_just_want_to_rename_it", SubscriptionForm),
]
Use success_url if there is a single succes url,
success_url = reverse_lazy("app_name:my_view")
or success_urls with a list of urls, one per FormClass or tuple.
success_urls = [
    reverse_lazy("app_name:contact_view"),
    reverse_lazy("app_name:contact_view"),
    reverse_lazy("app_name:my_view")
]
Barebone version, with no <form_name>_method overload support. After a succesfull validation the
bound forms are sent to form_valid as a dictionnary, where you will have to check which form or forms
where received.
def form_valid(self, form):
    # We can access individual forms like so:
    form1 = form["usercartform"].cleaned_data.get("action")
    form2 = form["cartupdateform"].cleaned_data.get('title')
    action = form1.cleaned_data.get("action")
    # ...
    return super().form_valid(form)
You can overload the getters get_initial, get_prefix and get_form_kwargs on a per form basis by defining a
get_<form's name>_<method name> for it.
class Example(MultiFormView):
    # ...
    form_classes = [
        ContactForm,
        ("mycontactform", ContactForm),
        ("i_just_want_to_rename_it", SubscriptionForm), 
    ]
    # ...
    
    def get_contactform_form_kwargs(self, form_name):
        kwargs = super().get_form_kwargs(form_name)
        kwargs["some_args"] = "some_args"
        return kwargs
After form(s) validation, the view uses the valid form's name to checks if a corresponding
<form name>_form_valid method was defined and call it.
def contactform_form_valid(self, form):
    title = form.cleaned_data.get('title')
    print(title)
    return super().form_valid(form) 
If there are more than 1 valid form (when multiple Form are used in a single </form>), it iterates
through the form names and picks the first matching method it finds. A form dict containing all valid
forms will be passed as argument, instead of a single form.
If no method is found, it calls form_valid just like in FormsView.
You can use a ModelForm instead of a Form as long as you add an instance keyword argument with a model instance.
def get_contactform_form_kwargs(self, form_name):
    kwargs = super().get_form_kwargs(form_name)
    kwargs['instance'] = Contact.objects.get(name="Batman")
    return kwargs
Not yet packaged.
No automated tests yet, maybe one day.
- Alexandre Cox
 
This project is licensed under the BSD 3-clause License - see the LICENSE.md file for details