#21667 closed New feature (fixed)

Allow dynamic form classes with WizardView

Reported by: nickname123 Owned by: nobody
Component: contrib.formtools Version: 1.6
Severity: Normal Keywords:
Cc: bnafta@… Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

The WizardView does not currently support dynamic form classes without overriding the entire get_form method.

My mixin below demonstrates an easy way to make this convenient. I needed this functionality to support using modelform_factory at runtime to accommodate logic that varies depending on choices made previously in the wizard. A simple use case is to support dynamic "required" fields.

class WizardDynamicFormClassMixin(object):
    def get_form_class(self, step):
        return self.form_list[step]
    
    def get_form(self, step=None, data=None, files=None):
        """
        This method was copied from the base Django 1.6 wizard class in order to
        support a callable `get_form_class` method which allows dynamic modelforms.
        
        Constructs the form for a given `step`. If no `step` is defined, the
        current step will be determined automatically.

        The form will be initialized using the `data` argument to prefill the
        new form. If needed, instance or queryset (for `ModelForm` or
        `ModelFormSet`) will be added too.
        """
        if step is None:
            step = self.steps.current
        # prepare the kwargs for the form instance.
        kwargs = self.get_form_kwargs(step)
        kwargs.update({
            'data': data,
            'files': files,
            'prefix': self.get_form_prefix(step, self.form_list[step]),
            'initial': self.get_form_initial(step),
        })
        if issubclass(self.form_list[step], forms.ModelForm):
            # If the form is based on ModelForm, add instance if available
            # and not previously set.
            kwargs.setdefault('instance', self.get_form_instance(step))
        elif issubclass(self.form_list[step], forms.models.BaseModelFormSet):
            # If the form is based on ModelFormSet, add queryset if available
            # and not previous set.
            kwargs.setdefault('queryset', self.get_form_instance(step))
        return self.get_form_class(step)(**kwargs)

This is a simple demonstration of usage:

def get_form_class(self, step):
	if step == STEP_FOO:
		try:
			choice_foo = self.get_cleaned_data_for_step(STEP_FOO)["choice_foo"]
		except KeyError:
			pass
		else:
			# get_wizard_form_class() would return a model form with fields
			# that depend on the value of "choice"
			return ModelFoo(choice=choice_foo).get_wizard_form_class()
	return super(WizardFoo, self).get_form_class(step)

Change History (4)

comment:1 Changed 21 months ago by aaugustin

  • Component changed from Generic views to contrib.formtools
  • Needs documentation unset
  • Needs tests unset
  • Patch needs improvement unset

comment:2 Changed 17 months ago by timo

  • Triage Stage changed from Unreviewed to Accepted

Seems like it could be a useful hook.

comment:3 Changed 17 months ago by Fabio Caritas Barrionuevo da Luz <bnafta@…>

  • Cc bnafta@… added

comment:4 Changed 11 months ago by gchp

  • Resolution set to fixed
  • Status changed from new to closed

formtools has been extracted into its own repository (​https://github.com/django/django-formtools/). Because of this, the issue tracking for this package has been moved to GitHub issues. I'm going to close this ticket, but I've created a GitHub issue to replace it where the conversation can continue: ​https://github.com/django/django-formtools/issues/16. Thanks!

Note: See TracTickets for help on using tickets.
Back to Top