Code

Opened 7 months ago

Last modified 3 months ago

#21667 new New feature

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)

Attachments (0)

Change History (3)

comment:1 Changed 7 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 3 months ago by timo

  • Triage Stage changed from Unreviewed to Accepted

Seems like it could be a useful hook.

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

  • Cc bnafta@… added

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as new
The owner will be changed from nobody to anonymous. Next status will be 'assigned'
as The resolution will be set. Next status will be 'closed'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.