Opened 12 years ago
Closed 11 years ago
#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 by , 12 years ago
| Component: | Generic views → contrib.formtools | 
|---|
comment:2 by , 12 years ago
| Triage Stage: | Unreviewed → Accepted | 
|---|
comment:3 by , 12 years ago
| Cc: | added | 
|---|
comment:4 by , 11 years ago
| Resolution: | → fixed | 
|---|---|
| Status: | new → 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!
Seems like it could be a useful hook.