Opened 17 years ago
Closed 14 years ago
#11112 closed New feature (fixed)
Formsets not supported as steps in FormWizard
| Reported by: | Owned by: | nobody | |
|---|---|---|---|
| Component: | contrib.formtools | Version: | dev |
| Severity: | Normal | Keywords: | FormWizard FormSet wizard |
| Cc: | andy@…, santtu@…, jashugan@…, stuff4riqui@…, nils@… | Triage Stage: | Accepted |
| Has patch: | yes | Needs documentation: | no |
| Needs tests: | yes | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description
A FormSet may not be included in FormWizard.form_list, since the individual forms will not be rendered to hidden fields on the next step, and the Wizard lacks machinery to handle them, producing assorted validation and hash errors.
I've written a patch to handle FormSets in FormWizard.form_list
Attachments (2)
Change History (23)
by , 17 years ago
| Attachment: | wizard.patch added |
|---|
comment:1 by , 17 years ago
| milestone: | 1.1 → 1.2 |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
Not a 1.1 feature.
comment:2 by , 16 years ago
| Cc: | added |
|---|
by , 16 years ago
| Attachment: | wizard.patch2 added |
|---|
Fixed case where formset is not last item in wizard
comment:3 by , 16 years ago
| Cc: | added |
|---|
comment:4 by , 16 years ago
| Cc: | added |
|---|
comment:5 by , 16 years ago
| Cc: | added |
|---|
comment:6 by , 16 years ago
| milestone: | 1.2 |
|---|
1.2 is feature-frozen, moving this feature request off the milestone.
comment:7 by , 15 years ago
| Cc: | added |
|---|
comment:8 by , 15 years ago
I tried to use the patch2 but I got this error:
I don't know anything about formset internals so I dunno how to solve it
Environment:
Request Method: POST
Request URL: http://localhost:8000/curricula/inserisci-candidato-e-curriculum/
Django Version: 1.2.3
Python Version: 2.6.6
Installed Applications:
['django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.admin',
'django.contrib.sites',
'hrar']
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.middleware.doc.XViewMiddleware')
Traceback:
File "/home/visi/ARcur/parts/django/django/core/handlers/base.py" in get_response
100. response = callback(request, *callback_args, **callback_kwargs)
File "/home/visi/ARcur/parts/django/django/utils/decorators.py" in _wrapper
21. return decorator(bound_func)(*args, **kwargs)
File "/home/visi/ARcur/parts/django/django/utils/decorators.py" in _wrapped_view
76. response = view_func(request, *args, **kwargs)
File "/home/visi/ARcur/parts/django/django/utils/decorators.py" in bound_func
17. return func(self, *args2, **kwargs2)
File "/home/visi/ARcur/HRAR/formSetWizard.py" in __call__
67. form = self.get_form(next_step)
File "/home/visi/ARcur/parts/django/django/contrib/formtools/wizard.py" in get_form
47. return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
File "/home/visi/ARcur/parts/django/django/forms/formsets.py" in __init__
47. self._construct_forms()
File "/home/visi/ARcur/parts/django/django/forms/formsets.py" in _construct_forms
97. self.forms.append(self._construct_form(i))
File "/home/visi/ARcur/parts/django/django/forms/formsets.py" in _construct_form
109. defaults['initial'] = self.initial[i]
Exception Type: KeyError at /curricula/inserisci-candidato-e-curriculum/
Exception Value: 0
comment:10 by , 15 years ago
| Keywords: | wizard added |
|---|
comment:11 by , 15 years ago
I hit my same error again so I wrote here down for me and as a solution for anyone who could it this:
Be Aware of initial if improperly set could break the wizard at a FormSet Step.
comment:12 by , 15 years ago
This patch is marked as
Triage Stage: Accepted
Has patch: yes
Needs documentation: no
Needs tests: no
Patch needs improvement: no
but it's not in trunk
will it be merged ?
follow-up: 14 comment:13 by , 15 years ago
| Needs tests: | set |
|---|
It may well be marked "needs no tests" ,but a casual inspection of the patch demonstrates that this isn't the case. The patch doesn't contain tests.
comment:14 by , 15 years ago
Replying to russellm:
It may well be marked "needs no tests" ,but a casual inspection of the patch demonstrates that this isn't the case. The patch doesn't contain tests.
:)
follow-up: 19 comment:16 by , 15 years ago
hi anonymous :)
here there is an updated version based on the second patch
it's not a patch but a subclass of the current formWizard present on 1.2.4
there is no tests and probably initials doesn't work very well but I use it right now and it works
from django.contrib.formtools.wizard import FormWizard as fw
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
from django import forms
class FormWizard(fw):
@method_decorator(csrf_protect)
def __call__(self, request, *args, **kwargs):
"""
Main method that does all the hard work, conforming to the Django view
interface.
"""
if 'extra_context' in kwargs:
self.extra_context.update(kwargs['extra_context'])
current_step = self.determine_step(request, *args, **kwargs)
self.parse_params(request, *args, **kwargs)
# Sanity check.
if current_step >= self.num_steps():
raise Http404('Step %s does not exist' % current_step)
# Process the current step. If it's valid, go to the next step or call
# done(), depending on whether any steps remain.
if request.method == 'POST':
form = self.get_form(current_step, request.POST)
else:
form = self.get_form(current_step)
if form.is_valid():
# Validate all the forms. If any of them fail validation, that
# must mean the validator relied on some other input, such as
# an external Web site.
# It is also possible that validation might fail under certain
# attack situations: an attacker might be able to bypass previous
# stages, and generate correct security hashes for all the
# skipped stages by virtue of:
# 1) having filled out an identical form which doesn't have the
# validation (and does something different at the end),
# 2) or having filled out a previous version of the same form
# which had some validation missing,
# 3) or previously having filled out the form when they had
# more privileges than they do now.
#
# Since the hashes only take into account values, and not other
# other validation the form might do, we must re-do validation
# now for security reasons.
current_form_list = [self.get_form(i, request.POST) for i in range(current_step)]
for i, f in enumerate(current_form_list):
if issubclass(f.__class__, forms.formsets.BaseFormSet):
if request.POST.get("hash_%d" % i, '') != self.security_hash(request, f.management_form):
return self.render_hash_failure(request, i)
for ff in f.forms:
if request.POST.get("hash_%d_%s" % (i, ff.prefix)) != self.security_hash(request, ff) :
return self.render_hash_failure(request, i)
else:
if request.POST.get("hash_%d" % i, '') != self.security_hash(request, f):
return self.render_hash_failure(request, i)
if not f.is_valid():
return self.render_revalidation_failure(request, i, f)
else:
self.process_step(request, f, i)
# Now progress to processing this step:
self.process_step(request, form, current_step)
next_step = current_step + 1
if next_step == self.num_steps():
return self.done(request, current_form_list + [form])
else:
form = self.get_form(next_step)
self.step = current_step = next_step
return self.render(form, request, current_step)
def render(self, form, request, step, context=None):
"Renders the given Form object, returning an HttpResponse."
old_data = request.POST
prev_fields = []
if old_data:
hidden = forms.HiddenInput()
# Collect all data from previous steps and render it as HTML hidden fields.
for i in range(step):
old_form = self.get_form(i, old_data)
hash_name = 'hash_%s' % i
#handle formsets
if issubclass(old_form.__class__,forms.formsets.BaseFormSet):
# do management form and generate hash
prev_fields.extend([bf.as_hidden() for bf in old_form.management_form])
prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form.management_form))))
for f in old_form.forms:
# do each form and generate a hash for each
hash_name = 'hash_%s_%s' % (i,f.prefix)
prev_fields.extend([bf.as_hidden() for bf in f])
prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, f))))
else:
prev_fields.extend([bf.as_hidden() for bf in old_form])
prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
return self.render_template(request, form, ''.join(prev_fields), step, context)
follow-up: 18 comment:17 by , 15 years ago
Thank you, im anonymous.
No works for me :(
I add forms by using javascript. And change the variable TOTAL_FORMS. The validation does not work.
Thanks anyway
comment:18 by , 15 years ago
Replying to b3ni <camontuyu@…>:
Thank you, im anonymous.
No works for me :(
I add forms by using javascript. And change the variable TOTAL_FORMS. The validation does not work.
Thanks anyway
If you post the code to some pastebin site and the error I'll try to fix the problem
comment:19 by , 15 years ago
Replying to visik7:
This code works perfectly for me, within a subclass of Django's FormWizard, which is really nice as that's the standard workflow anyway.
Thank you very much!
comment:20 by , 15 years ago
| Severity: | → Normal |
|---|---|
| Type: | → New feature |
comment:21 by , 14 years ago
| Easy pickings: | unset |
|---|---|
| Resolution: | → fixed |
| Status: | new → closed |
Superseded by #9200.
Handle Formsets in FormWizard