Opened 14 years ago
Closed 13 years ago
#17850 closed New feature (wontfix)
Passing FormWizard.as_view(form_list) a dynamic form_list
| Reported by: | Owned by: | nobody | |
|---|---|---|---|
| Component: | contrib.formtools | Version: | 1.4-beta-1 |
| Severity: | Normal | Keywords: | formwizard |
| Cc: | kristoffer.snabb@… | Triage Stage: | Design decision needed |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
The following technique fails to provide the expected behavior. The error returned is:
TypeError: issubclass() arg 1 must be a class # happens in /django/contrib/formtools/wizard/views.py", line 166, in get_initkwargs
urls.py:
from django.conf.urls import patterns, url
# from views import survey_wizard
from views import SurveyWizard
from forms import get_survey_page_list
# I'd rather do this dynamically as well, but this *should* work
survey_page_list = get_survey_page_list('274a442b-67ec-11e1-b010-78ca39b1b597')
urlpatterns = patterns('',
url(r'^(?P<survey>[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})$', SurveyWizard.as_view(form_list=survey_page_list)),
)
forms.py:
def get_survey_page_list(survey):
"""
Build a list of survey pages for the form wizard to use.
args:
survey - a unicode string passed in from a URL parameter
"""
survey_page_list = []
# Since we're receiving a captured URL parameter, we have to use that
# string to acutally fetch the survey object.
s = Survey.objects.get(pk=survey)
for page in s.page_set.all().order_by('number'):
step_name = unicode(page.number)
form_class = make_survey_page_form(survey, page.number)
survey_page_list.append((step_name, form_class))
return SortedDict(survey_page_list)
Looking into the Werkzeug debugger reveals that the functions called in forms.py are grabbing and returning a SortedDict of forms:
>>> dir()
['args', 'cls', 'condition_dict', 'form', 'form_list', 'i', 'init_form_list', 'initial_dict', ]
>>> form_list
django.utils.datastructures.SortedDict({u'1': <class 'mysite.survey.forms._SurveyPageForm'>, u'2': <class 'mysite.survey.forms._SurveyPageForm'>, u'3': <class 'mysite.survey.forms._SurveyPageForm'>})
>>> initial_dict
>>> init_form_list
django.utils.datastructures.SortedDict({u'0': u'1', u'1': u'2', u'2': u'3'})
Change History (8)
follow-up: 7 comment:1 by , 14 years ago
comment:2 by , 14 years ago
The problem is that the WizardView takes a form_list of classes and not object instances. I cannot say if this is a bug or a design choice, maybe a performance issue? Anyway I would also appreciate a possibility to create forms more dynamically.
Taken from WizardView:
if issubclass(form, formsets.BaseFormSet):
# if the element is based on BaseFormSet (FormSet/ModelFormSet)
# we need to override the form variable.
form = form.form
comment:3 by , 14 years ago
| Cc: | added |
|---|
comment:4 by , 14 years ago
Yes, no matter what I did, WizardView was always expecting some kind of list. :)
I'm also not sure if this a bug, but I think dynamic forms is a feature worth having.
Here's what I went with, but the form wizard is still acting weird, not giving me a next button when there are multiple steps:
class SurveyWizard(CookieWizardView):
def dispatch(self, request, *args, **kwargs):
survey_page_list = get_survey_page_list(kwargs['survey'])
self.form_list = self.get_initkwargs(survey_page_list)['form_list']
return super(SurveyWizard, self).dispatch(request, *args, **kwargs)
def done(self, form_list, **kwargs):
# do something
Also, I passed SurveyWizard.as_view() a list filled with a bogus form to avoid the wrong number of arguments error. The form list is overridden by dispatch above, anyway.
Thanks to SmileyChris on #django for walking me through the source for a few hours to figure this out.
comment:5 by , 14 years ago
| Triage Stage: | Unreviewed → Design decision needed |
|---|---|
| Type: | Bug → New feature |
This isn't really a bug, to be honest. You're expecting a behavior that the form wizard wasn't written for. Marking as design decision needed.
comment:7 by , 13 years ago
Replying to jrhorn424:
Using a slightly different technique yields a different error.
TypeError: get_initkwargs() takes at least 2 arguments (1 given) # in "/django/django/contrib/formtools/wizard/views.py", line 118, in as_view
This happens because you're currently forced to provide a form_list.
views.py:
class SurveyWizard(CookieWizardView): def get_form_list(self): form_list = get_survey_page_list(self.kwargs['survey']) return form_list survey_wizard = SurveyWizard.as_view()I suspect this is because SurveyWizard.as_view() is expecting an argument to pass along to get_initkwargs.
Yes, you have to provide a form_list, but - as a quick fix - you could provide a dummy form list with one form. You could then override the get_form_list method as you already did and it should work.
What we need is a way to tell the wizard that the empty form_list is ok. For example with an attribute in a subclass "allow_empty_form_list = True".
comment:8 by , 13 years ago
| Resolution: | → wontfix |
|---|---|
| Status: | new → closed |
As jezdez says, this isn't what the wizard is designed for, sorry.
Using a slightly different technique yields a different error.
#urls.py
from django.conf.urls import patterns, url from views import survey_wizard urlpatterns = patterns('', url(r'^(?P<survey>[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12})$', survey_wizard), )views.py:
class SurveyWizard(CookieWizardView): def get_form_list(self): form_list = get_survey_page_list(self.kwargs['survey']) return form_list ### This fails, too ### # @property # def form_list(self): # form_list = get_survey_page_list(self.kwargs['survey']) # return form_list survey_wizard = SurveyWizard.as_view()I suspect this is because SurveyWizard.as_view() is expecting an argument to pass along to get_initkwargs. Werkzeug:
>>> dir() ['args', 'cls', 'kwargs'] >>> args () >>> kwargs {} >>> cls <class 'mysite.survey.views.SurveyWizard'>However, I'm at a loss as to how else to get a dynamically generated list of forms into form_list.