jazzband / django-formtools

A set of high-level abstractions for Django forms
https://django-formtools.readthedocs.io
BSD 3-Clause "New" or "Revised" License
784 stars 135 forks source link

Handling multiple forms in one step #272

Open marcinkaczmarek10 opened 4 months ago

marcinkaczmarek10 commented 4 months ago

Is there a proper way to handle multiple forms in one step in a wizard, e.g. MultipleFormWizardView, which would accept a sublist of forms for each step. Was this considered an additional feature in the future?

rlalik commented 2 months ago

Perhaps not the best solution, but currently I am working on a project where I need to combine several forms into one form. Here is my solutions:

class AggregateForm(forms.Form):
    """
    """

    template_name = "aggregate_form.html"

    # extra field to accecp this combination of forms
    confirm_actions = forms.BooleanField(
        label="Confirm these actions",
        required=False,
        initial=True,
    )

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        initial = kwargs.get("initial")

        def update_widgets(name, form, template=None, extras=None):
            if extras is None:
                extras = {}

            new_form = form(prefix=self.prefix, initial=initial[f"{name}_data"], **extras)
            if template:
                new_form.template_name = template
            setattr(self, f"{name}_form", new_form)

        update_widgets(
            "form1",            # some label for form, should be the same like in the initial dictionary
            Form1,              # Form1 is a model form
            "form1_view.html",  # template for this form
            extras={},          # extra parameters for form
        )

        update_widgets(
            "form2",
            Form2,
            "form2_view.html",
            extras={},
        )

AggregateFormSet = forms.formset_factory(AggregateForm, extra=0)

and in the view, in the get_form_initial():

from django.forms.models import model_to_dict

#...

    def get_form_initial(self, step):
        if step == "the_step":
            initials = []
            for ...:  # collect initial data
                initials.append(
                    {
                        "form1_data": model_to_dict(model1_obj), # label plus "_data" suffix
                        "form2_data": model_to_dict(model1_obj),
                    }

            return initials

        return self.initial_dict.get(step, {})

and finally in the master template aggregate_form.html:

...
{{ form.form1_form }}
{{ form.form2_form }}
...