The default behavior of form wizard is that if you go back and come back to the current form, you will lose all the data(non-file) and files. The reason is that the prev
button is associated with render_goto_step
method. In the doc about render_goto_step, it says:
This method is called when the step should be changed to something else than the next step. By default, this method just stores the requested step goto_step in the storage and then renders the new step. If you want to store the entered data of the current step before rendering the next step, you can overwrite this method.
The following will solve part of your problems.
class NewWizard(NamedUrlSessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
def done(self, form_list, form_dict, **kwargs):
return render(self.request, 'simpletest/done.html', {
'form_data':[form.cleaned_data for form in form_list]
})
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def render_goto_step(self, goto_step, **kwargs):
print('under render_goto_step')
print(self.storage.current_step)
form1 = self.get_form(self.storage.current_step, data=self.request.POST,files=self.request.FILES)
if form1.is_valid:
print("form.is valid")
self.storage.set_step_data(self.storage.current_step, self.process_step(form1))
print('after set_step_data')
self.storage.set_step_files(self.storage.current_step, self.process_step_files(form1))
print('after set_step_files')
else:
print('under form.errors')
print(form1.errors)
######### this is from render_goto_step method
self.storage.current_step = goto_step
form = self.get_form(
data=self.storage.get_step_data(self.steps.current),
files=self.storage.get_step_files(self.steps.current))
return redirect(self.get_step_url(goto_step))
Unfortunately, it cannot solve the image preview problem. I am not entirely sure about it, but this seems not related to the render_goto_step
function per se because even the ones saved by post
method to the session storage cannot be rendered. For example, if you add an image in form2, hit submit, and go to form3, and hit prev
, you will see that image in form2 is gone, although the value(title) is there.
It seems that django and form wizard just do not render these files
because they are dictionaries not files themselves. They are either <UploadedFile>
or <InMemoryUploadedFile>
objects.
What to do about image preview?
1. I was able to solve this problem by saving the file data into Model. Override post
method and render_goto_step
method to make sure that the image is saved to model both when you hit submit (post)
and hitprev, first--- render_goto_step
.
In addition, in order to render the image in your template, override get_context_data
method and pass mypost
instance.
Please note that: in the following code, I simplified the save to model
portion by just save to pk=1
object. You have to change it accordingly.
class NewWizard(NamedUrlSessionWizardView):
file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
def done(self, form_list, form_dict, **kwargs):
return render(self.request, 'simpletest/done.html', {
'form_data':[form.cleaned_data for form in form_list]
})
def get_template_names(self):
return [TEMPLATES[self.steps.current]]
def render_goto_step(self, goto_step, **kwargs):
print('under render_goto_step')
print(self.storage.current_step)
form1 = self.get_form(self.storage.current_step, data=self.request.POST,files=self.request.FILES)
if form1.is_valid:
print("form.is valid")
self.storage.set_step_data(self.storage.current_step, self.process_step(form1))
print('after set_step_data')
self.storage.set_step_files(self.storage.current_step, self.process_step_files(form1))
print('after set_step_files')
############ check if it is step_second, save file to model.
if self.steps.current =='step_second':
print('under render_goto_step step_second')
if 'imagefile' in self.request.FILES.keys():
f = self.request.FILES['imagefile']
print(f)
if f:
mypost = MyPost.objects.get(pk=1)
mypost.image = f
mypost.save()
print('saved')
else:
print('under form.errors')
print(form1.errors)
######### this is from render_goto_step method
self.storage.current_step = goto_step
form = self.get_form(
data=self.storage.get_step_data(self.steps.current),
files=self.storage.get_step_files(self.steps.current))
return redirect(self.get_step_url(goto_step))
def post(self, *args, **kwargs):
wizard_goto_step = self.request.POST.get('wizard_goto_step', None)
if wizard_goto_step and wizard_goto_step in self.get_form_list():
return self.render_goto_step(wizard_goto_step)
print('wizard_goto_step')
print(wizard_goto_step)
print('current')
print(self.steps.current)
# get the form for the current step
form = self.get_form(data=self.request.POST, files=self.request.FILES)
# and try to validate
if form.is_valid():
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))
############ check if it is step_second, save file to model.
if self.steps.current =='step_second':
print('under step_second')
f = self.request.FILES['imagefile']
print(f)
if f:
mypost = MyPost.objects.get(pk=1)
mypost.image = f
mypost.save()
print('saved')
return self.render_next_step(form)
else:
# check if the current step is the last step
if self.steps.current == self.steps.last:
# no more steps, render done view
return self.render_done(form, **kwargs)
else:
# proceed to the next step
return self.render_next_step(form)
return self.render(form)
return super(NewWizard, self).post(*args, **kwargs)
def get_context_data(self, form, **kwargs):
context = super().get_context_data(form=form, **kwargs)
mypost = MyPost.objects.get(pk=1)
context.update({'mypost': mypost})
return context
and in your template, use image.url
like below:
<div class="preview">%
{% if mypost.image %}
<img id="fileip-preview" src="{{mypost.image.url}}" alt="....">
{% else %}
<img id="fileip-preview" src="" alt="....">
{% endif %}
</div>
2. Another viable way is to first read the file and pass it to the template in views. I did not try to implement this. But this post might provide you an idea about how to implement it.
3. Another seemingly viable way is to use history API
and localStorage
. The idea is to mimic the browser back and forward buttons. As you can see when you use the browser to go back and come back to current, you can see that all your info is retained. It seemed that so many things that users do can affect history states, such as using back/forward rather than prev, submit; refreshing pages, back/forward from refreshed pages, etc. I tried this approach and felt like it should be a winner, but abandoned it eventually.