RESTful multi-step / multi-page forms in Django (ideas/examples) - django

What are some RESTful ways to transition between pages of a multi-page form in Django?
My current method:
form POSTs to same page
form view validates and stores POST data in session
form view redirects to the next form page upon successful validation.
next form checks if previous data exists. if not, redirects to first form.
Are redirects between form pages bad? Should forms POST to the next form page? If so, where should form validation happen?

What you're doing sounds like an implementation of the PRG model for handling multi step forms.
You probably want to POST to the current step, and then redirect to the next step only if the form validates. There is no problem with redirecting between steps; in fact, as you probably discovered, it allows you to support the browser back button in your forms.

Django has form wizards which handle this machinery (in case you were unaware).
http://docs.djangoproject.com/en/dev/ref/contrib/formtools/form-wizard/

Related

Django post form fields to another page

I have a Django form that takes filter options for a report. The report page is a separate view that renders the report based on the form data.
My first pass at this, I simply set the action of the form to the report page and method to GET. The form data was then passed directly the report view via the querystring which I would use GET to retrieve. The problem with this was that this bypassed form validation since the the form did not post back to its own view.
My next pass, I removed the form action (so it would post back to itself) and used form_valid (I am using class based views) to encode the form data and redirect to the report view like so:
ReportOptionsView(FormView)
form_class = OptionsForm
template_name = 'my_report_options.html'
report = reverse_lazy('my_report')
def form_valid(self, form):
qstr = urlencode(form.cleaned_data)
return redirect(self.report+"?"+qstr)
The report page works the same -I just retrieve the information from the querystring to filter the models and display the report.
I would prefer the form data not appear on the querystring of the report page. When I tried to redirect to the report page using a POST method is where I starting having trouble. Even going back to my original flow setting the form action to the report page (thus losing validation) and setting the form method to POST, I got 405 errors. I realize that there may be ways to do this using Javascript, but would prefer to stick with Django/Python
My question is, what is the proper method in Django to take cleaned data from a validated form and POST that data to separate view so that the form data is not exposed in the URL?

How to handle a POST request from a Django form loaded via a custom template tag?

I added a Django form to my Bootstrap nav bar to be included on every page, and it renders as it should with the appropriate values. The form was added using an inclusion_tag. However, I'm now at a loss as to how to handle the request from the form. Upon submission, whichever page the user was on should reload with updated content from the form submission. For more context, see my earlier question: How to place a django form in a nav bar so that it appears on every page?
Answering my own question (again). To handle a request from a form that appears on every page and loaded via a custom template tag, create a url path and corresponding view -- e.g. '/form-submission/' and form_submission_view. In the view, handle the form processing logic as you normally would for a POST request, but then return a redirection back to whatever page the user was on when the form was submitted, like so:
return redirect(request.POST.get('path'))

How do I redirect a Django form submit to a new tab after form validation?

I have a Django form with two dropdown integer fields and a submit button.
A user selects values from these dropdown fields and click on submit button.
The Django template performs a form 'post' and sends these two dropdown values to my Django views.
Django views performs the form validation by validating these two dropdown fields values.
If the form is not valid, it will send the error message to the Django template and I can display the error message on the same form page above the form.
If the form is valid, then it will redirect to a new URL.
Issue: How do I tell my Django view to redirect the URL to a new tab only after the form is valid?
I'm assuming that you're already familiar with Djangos Form View. Within the form_valid(self, form) method of your class based view simply replace return super().form_valid(form) with redirect(reverse('YOUR_TARGET_VIEW')). If you have other mixins within your view that rely on form_valid you may also call super().form_valid(form) prior to returning the redirect reponse but without returning its returned data further.
As of displaying error messages you may refer to the django message framework.
EDIT: A cleaner way to do this, may be to overwrite success_url within your processing but this may not be appropriate to all use cases as you need to provide usefull defaults within each request path.

How do I set the "next" context variable in Django so I can redirect my user to the appropriate page?

I'm aware that there is a context variable called "next" which allows you to specify the URL to redirect to after a page, which is commonly used when you want to redirect a user to a login page, then redirect them back to the page they were at before the login page once they've logged in.
In my views I've resorted to manually setting it via the URL such as redirecting to /login/?next=/someurl, but this isn't a clean way. I've tried googling and such, but there is surprisingly little information about it online.
How exactly do you set the context variable "next"? My site has a form that anyone can see, but only logged in users can submit. Right now if the user isn't logged in, they will get redirected to the login page with the "?next=/someurl/" attached to it so they get sent back to the original form once they log in.
However, from the login page there is a link to the sign up page for users who don't have an account, and I want to set "next" to be the original form page so that after they sign up they are redirected back to the original form. Any ideas or advice?
It sounds like you want to not simply use next for one redirect, but persist it across two redirects:
Some form page -> login -> signup -> Get back to some form
For the login page by itself, Django provides some automatic help for this (if you use the built-in auth views). But the second hop to the signup page requires some custom code explained below.
If you are using Django's built-in login view, and have created your own template for it, then Django will pass the current value of next to the template as a context variable called (appropriately) next. You can use that in the link to your sign-up page like this:
Sign me up!
Consequently, in the view you create to handle your user signup page, the value of next will be accessible as a GET param:
def signup(request):
if request.method == 'GET':
next = request.GET.get('next', None)
if next:
# Add it as a hidden input in your signup form
# Or re-append it as a GET param
# Or stick it in the user's session, which is
# what this example does:
request.session['next'] = next
Finally, in the same signup view, when you respond to the POSTed signup form, retrieve the next value in whichever way you chose to propogate it in the GET request, and redirect to it's value.
def signup(request):
....
# POST handling section
if signup_form.is_valid():
next = request.session.get('next', None)
if next:
# See caution note below!
return redirect(next)
Caution:
Be aware that you should check and sanitize the next value before you redirect to it after processing the signup form, to prevent browser-side tampering. For example, it's common to validate that the URL belongs to your own domain (if that's appropriate) and/or that Django's resolve function is able to successfully resolve it.

How to write good form validation in Django?

I've seen Django's samples and I can see they have decent error handling. However I want to see if there is yet a better approach, a general pattern to handle form validation errors in Django. This is the sample I found here:
def contact(request):
if request.method == 'POST': # If the form has been submitted...
form = ContactForm(request.POST) # A form bound to the POST data
if form.is_valid(): # All validation rules pass
# Process the data in form.cleaned_data
# ...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else:
form = ContactForm() # An unbound form
return render_to_response('contact.html', {
'form': form,
})
In particular, I was wondering:
How can the view in "/thanks/" be sure that the form was validated? Are there any common ways to pass the successful validation of the form to the next view? Or do I need to do something manually such as setting a flag in request's session?
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
EDIT: With regards to #1 I am referring to cases like user manually entering the '/thanks/' url or going back and forth through history pages and accidentally openning it without any form being validated. (Do we still show the "thanks" page? or we need to somehow re-validate why we are in thanks view).
The view can be sure that the form is validated because it will only be called if the form is valid...
If the page is generated through a post request the browser will always ask you that when hitting refresh... I guess the only way to avoid this would be redirecting to another page!
How can the view in "/thanks/" be sure that the form was validated?
form.is_valid() should thoroughly check any field or - if necessary - any combination, cornercase, etc. That's basically it. The views knows, the form was valid if it renders. There is no need to include redundant information in the session.
How can one write this code in a way that when form is NOT valid and the page is shown with errors upon submission, if user refreshes the browser it wouldn't ask the user if they want to POST data again?
I am not sure what the point would be. The form contains errors and the user may correct them or leave. To render a page that would not ask for form resubmission, one could use a redirect, just as in the valid case. The error markup would have to be done manually in that case.