1

Background

I'm building a very large form to process customer submissions, so the end goal is to allow the user to resume the form where they left off at a later date. The form is fully functional using a FormWizard (NamedUrlSessionWizardView, actually). The Django docs mention a final save is accomplished in the done method, and leave this as an exercise to the reader. This works OK if the user completes this in one sitting, but not if you want to restore this later.

In my case, an email address is used to lookup past progress, and send a unique link to the user. This sets up the form and returns the user to where they left off. This works fine as long as your session is still valid, but not if it isn't (different computer, etc). What I would like to do is save the form data (these are ModelForms) after each step. I'll restore the user's state when they return.

Research

This answer is about the only solution I can find, but the solution is the same thing that the standard FormWizard.post() method does:

if form.is_valid():
    # if the form is valid, store the cleaned data and files.
    self.storage.set_step_data(self.steps.current, self.process_step(form))
    self.storage.set_step_files(self.steps.current, self.process_step_files(form))

My Question

What is the proper way/place in a FormWizard to take action on, and save the form data out after each step?

Community
  • 1
  • 1
Chris
  • 321
  • 3
  • 15
  • _Edit: This was in response to a comment that was quickly deleted about creating my own storage that does this for me._ Sounds like an interesting idea - would I just create more problems than it's worth than just saving the instance as normal during the `WizardView.process_step(form)` call? Documentation doesn't say I _shouldn't_ do that, but it doesn't say I _should_, either. – Chris Jan 14 '16 at 22:35

1 Answers1

0

You should be able to save the data directly to the ModelForm as you go along by simply writing it into the post method.

if self.steps.current == "form1":
    data = self.request.POST["form1-response"]
    user = CustomerModel.objects.get(id=self.request.user.id)
    user.response = data
    user.form_step = "form1"
    user.save()

form_step, in this case, is simply a bookmark that you can use to direct the user back to the right step on their return. You should remove any already-saved fields from the done method, so they don't get overwritten.

If you do it this way, you may need to construct a dispatch method that rebuilds the management form when the user logs back in.

Alternatively, you might be able to get away with saving the user's session (or the relevant parts) into a session field on the model, then write a dispatch method for the SessionWizardView that injects the relevant information back in. I've never attempted it, but if you can get it to work, it might be preferable from an aesthetic standpoint depending on how many steps you have to cover.

Finally, if you can rely on your users not to clear their cookies and to use the same browser when they return, you can maybe cheat and set use persistent cookies.

Hopefully that will get you started. I'd be interested to see how you end up getting it to work. Good luck!

Adam Starrh
  • 6,428
  • 8
  • 50
  • 89
  • 2
    Unfortunately, I abandoned the attempt. Doing it manually with existing function-based views was just more time effective. That said, this is roughly along the lines I pursued, and I think it may have gotten me closer. I was starting to perceive that I was creating a lot of problems by not starting fresh - it seemed as if I was overriding far too much Wizard functionality to make this reliable and maintainable. This is still a good reference for myself and others in their attempts. – Chris Jan 31 '16 at 22:43
  • In this case, what is the difference between `a = self.request.user` and `b = CustomerModel.objects.get(id=self.request.user.id)`? `b` seems like its just an extra db query, however, I've noticed that if you do.. `a.first_name = 'Travis'` then `a.save()` it is reset at some point. I know its saved to database, but on the next view it is reverted to what it was before the save call. – teewuane Sep 10 '16 at 19:39
  • This is sort of Psuedo code. I suppose `CustomerModel.objects.get(user=self.request.user.id)` would have been a better example. Normally there is an extra model with an fk link to the `User` object that holds extra details, so it doesn't work to call the `User` directly. – Adam Starrh Sep 11 '16 at 15:07
  • Also, when you write this into the `post` method of Formtools, this is only called on step 1. Therefore, unless you call this extra model again and do something with it, won't be touched by the wizard again. – Adam Starrh Sep 11 '16 at 15:11