Django FormWizard中Formset ChoiceField不更新及报错排查
Hey there, let's break down and fix the issues you're having with your Django FormWizard!
First, let's understand why your original approaches weren't working:
- The
get_form_initialmethod is only meant to return a dictionary of initial values, not a formset object. That's why you got the'TestForm' object has no attribute 'get'error. - When you switched to
get_form, modifying the globalform_class.extraand updating fields after instantiating the formset caused mismatches with the management form data—hence theManagementForm data is missing or has been tampered witherror. Also, updating fields post-instantiation doesn't play nicely with form validation and data handling.
Here's the corrected approach
We'll dynamically create a temporary formset class (to avoid messing with global settings) and update the ChoiceField options directly in the form's __init__ method. This ensures everything stays in sync with the wizard's data flow.
Updated views.py code
class FormWizardView(LoginRequiredMixin, SessionWizardView): template_name = 'test/test.html' def get_form(self, step=None, data=None, files=None): step = step or self.steps.current if step == 'formset_step': # Fetch cleaned data from previous steps slider_data = self.get_cleaned_data_for_step('slider_step') text_input_data = self.get_cleaned_data_for_step('text_input_step') if slider_data and text_input_data: # 1. Get the base form class from your pre-defined formset base_form = self.form_list[step].form # 2. Create a dynamic form class to update the ChoiceField class DynamicForm(base_form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Convert your text input data to valid choices format # Adjust this if your text_input_data isn't a key-value dict self.fields['text_input'].choices = [ (key, value) for key, value in text_input_data.items() ] # 3. Create a temporary formset with the correct 'extra' count from django.forms import formset_factory DynamicFormSet = formset_factory( DynamicForm, extra=slider_data['number_slider'] ) # 4. Return the formset with proper prefix and data/files return DynamicFormSet( data=data, files=files, prefix=self.get_form_prefix(step) ) # Fall back to default behavior for other steps return super().get_form(step, data, files) def done(self, form_list, **kwargs): # Handle your form submission logic here # Example: save the formset data # for form in form_list: # if hasattr(form, 'forms'): # form.save() return HttpResponse("Form submission successful!")
Key fixes explained
- No global modifications: We use
formset_factoryto create a temporary formset class instead of altering the global one. This prevents side effects for other users or subsequent requests. - ChoiceField updates in
__init__: By defining a dynamic form class, we ensure every form in the formset gets the updated choices when it's instantiated—this is the correct place to modify fields dynamically. - Proper management form handling: Passing the correct
prefix(fromself.get_form_prefix(step)) ensures the management form fields match what the wizard expects, fixing the tampering error. - Data/files passed correctly: We forward the
dataandfilesparameters to the formset, so existing input (if any) is preserved and validated properly.
Your template is fine!
You don't need to change anything in your template—keeping {{ wizard.management_form }} in place is exactly what we need for the formset to work correctly.
Quick check
Make sure the cleaned data from text_input_step is a dictionary (key-value pairs). If your text input collects data in a different format, adjust the choices list generation to match Django's required (value, label) tuple format.
内容的提问来源于stack exchange,提问作者Motta23

