How to Dynamically Repeat Steps in Django Formwiza

2019-06-21 21:28发布

I'm trying to repeat a step in Django Formwizard (Django 1.4) conditionally based on a checkbox in the step. The form creates an object, and has a checkbox (hopefully) allowing them to repeat the step and create another object of the same model with the same form.

I saw this answer: Django FormWizard Dynamically Alter form_list but unfortunately I think it only applies to older versions of the FormWizard.

The process_step function doesn't have a form_list attribute anymore. It also doesn't have a current step attribute (or step attribute) but I can access the current step through the QueryDict of the form. It contains a dictionary of forms, but I don't think inserting another step into that dictionary will do anything, and unfortunately since it's a dictionary not a list I'd have to modify every step key after where I insert it.

So, is there a good way to add new steps into the form list with Django 1.4 FormWizard?

Update -- beginning to think overriding get_next_step(self, step) might be the way to go, but any input is much appreciated.

Update #2 -- Tried working with get_next_step, but was unable to insert a new form into the instance's form_list. Doesn't mean it's not possible -- ideas?

3条回答
爷、活的狠高调
2楼-- · 2019-06-21 21:40

Well, I figured something out. You can override get_form_list to insert things into the form list, but you have to save them in self.form_list as well as just returning the form_list. For my purposes, this is ok, but if you're using the conditional_dict as well, you'll have to be careful here.

def get_form_list(self):
    s = self.storage.current_step                                                                
    s_data = self.get_cleaned_data_for_step(s) or {}                                             
    add_another = s_data.get('add_another', False)                                   
    if add_another is True:                                                                      
        index = self.form_list.keyOrder.index(s)+1                                               
        key = "add_another-{0}".format(index)
        self.form_list.insert(index, key,                                                        
                wizard_forms.Wizard4)                                                   
    form_list = super(Wizard, self).get_form_list()
    return form_list

I'm probably going to clean this up a bit, but that's the gist of it. The first couple lines get the current step and obtaining the flag for determining whether or not to insert another step following this one. If that flag is true, we get the index of the current step in the form, add one to it, and insert at that index (insert inserts before the index given). We use a key based on the index so that we don't get overlapping keys regardless of how many times the step is repeated. Then, we insert the new step into self.form_list so that it's instance wide, call get_form_list on self (which has the new self.form_list), and return the form_list as required.

Some things to note:

  • You have to use self.storage.current_step rather than self.steps.current because the latter causes infinite recursion.
  • The fact that we're working with self.form_list after initialization means things might get a little messy if you're using a conditional_dict. That said, they very well might not, since the conditional_dict assigns keys to conditions, and none of the keys in our form_list are changing, just the indices.

Well, that's it! Obviously you need to add some logic in done() to figure out how to save each of these, but that's specific to whatever you're doing.

查看更多
等我变得足够好
3楼-- · 2019-06-21 21:41

Verdict is, it's impossible. I haven't quite figured out why it worked sometimes with the prior two solutions, but after digging into the code for the form wizard, it's clear that each request reinitializes the instance form-list (well, technically it's a new instance), and so even if you update the list as it stands in storage, or update it in get_form_list(), there are some calls that directly hit the instance variable rather than accessing it via get_form_list() and it would take some major refactoring to adjust that.

I went another route for this project, but might try to put something together for this soon.

查看更多
虎瘦雄心在
4楼-- · 2019-06-21 22:06

The previous answer I posted works pretty inconsistently, and I thought it was some weird session collision bug, but turns out it's because get_form_list isn't always called in time for the action that requires the form. I moved the same logic into get_next_step and it now works smoothly.

def get_next_step(self, step=None):
    if step is None:
        step = self.steps.current
    s_data = self.get_cleaned_data_for_step(step) or {}
    add_another = s_data.get('add_another', False)
    if add_another is True:
        index = self.form_list.keyOrder.index(step)+1
        key = "add-another-{0}".format(index)
        self.form_list.insert(index, key,
                wizard_forms.Wizard4)
    return super(Wizard, self).get_next_step(step)
查看更多
登录 后发表回答