Hi Stackoverflow people,
I am irritated by the the Django Form handling, if the form submits to new page and the form validation fails. I intended to return to the earlier submitted form, and display the error message for correction.
The error message will be displayed, but the url link is not changing.
How can I change the *else statement of the is_valid() statement*, in order to redirect to the earlier form?
Thank you for your advice!
urls.py
urlpatterns = patterns("",
url(r"^add/$", "remap.views.add_project", name="add_project_location"),
url(r"^add/details/$", "remap.views.add_project_details", name="add_project_details"),
)
views.py
def add_project_details(request):
if request.method == 'POST': # If the form has been submitted...
locationForm = LocationForm(request.POST)
if locationForm.is_valid(): # All validation rules pass
locality = locationForm.cleaned_data['locality']
state = locationForm.cleaned_data['state']
country = locationForm.cleaned_data['country']
projectForm = ProjectForm(initial = {
'locality': locality,
'state': state,
'country': country,
})
return render_to_response('map/add_project_details.html', {
'projectForm': projectForm,
}, context_instance = RequestContext(request))
else:
print locationForm.errors
return render_to_response('map/add_project_location.html', {
'locationForm': locationForm,
}, context_instance = RequestContext(request))
else:
return HttpResponseRedirect('../') # Redirect to the location reg if site is requested w/o proper location
There's no cause to get irritated about Django doing exactly what it is designed to do.
Your redirect is on the else clause corresponding to if request.method == 'POST'. So, it will only take effect if this is not a form submission. In other words, that view will never actually display an empty form.
Then, depending on whether or not the POSTed form is valid, you display one of two templates. You're not doing any redirecting at this point.
However, even if you had asked Django to redirect when the form is invalid, I doubt you would get the result you want. This is because the rules of HTTP (not Django) say that you can't redirect a POST - so your request to the redirected view would be a GET, without any of the invalid data or error messages.
You can't update the url unless you tell the browser to do a redirect. You are returning two different html versions on the same url which can be confusing to the user.
What you should do instead is have two views. The /add url and add_project_location view would accept a post and validate, staying on that url and view until success.
Upon success it would set some session variables, redirect to the add_project_details page. Merging them like this is just not possible because of how browsers work.
Related
I am building a website and I want various views that will ask the user to request a quote from our page. I want to keep the code as DRY as possible so I am writing a view quote which will receive the quote requests from various views and, if there is a validation error redirect back to the page that made the request. I managed to solve this using the super bad practice 'global variables'. I need a better solution, I would like redirecting to respective view with the current form so I can iterate through the form.errors. Here is my code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
global session_form
session_form = form
return redirect('Main:' + quote_for) # Here I would like to send form instead of storing in global variable`
You can use the HttpResponseRedirect function, and pass as argument the page that made the request.
return HttpResponseRedirect(request.META.get('HTTP_REFERER'))
All the META data is store on a dictionary, if you want to learn more check the documentation.
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
If you redirect to the referrer, form.errors will be empty, as redirection is always a GET request.
I can think of two solutions to your problem:
Submit forms asynchronously using JavaScript and so populate the errors
Make all the views containing the form support POST - one way to do this would be to create a base class that inherits from FormView
The second option is a typical way of handling forms in Django - you process both POST and GET inside the same view.
After two days of searching I finally found the answer. Instead of saving form in request.session I just save request.POST and then redirect. Here is the code:
def send_quote(request):
form = Quote(request.POST)
if form.is_valid():
# do stuff when valid
return redirect('Support:thanks', name=name or None)
quote_for = request.POST['for_what']
request.session['invalid_form'] = request.POST
return redirect('Main:endview')
def endview(request):
session_form = request.session.pop('invalid_form', False)
if session_form:
form = Quote(session_form)
# render template again with invalid form ;)
Now I can repeat this with all the views I want and just change the what_for input of each form to match the respective view (Like I intended).
Below is the code from views.py where I am using render_to_response to direct the user to done.html along with a dictionary of variables. But, I actually want to direct the user to a url /home_main/#signin_completeand pass the dictionary of variables that are callable. Can someone please suggest if there is a way of doing this ?
def done(request):
"""Login complete view, displays user data"""
scope = ' '.join(GooglePlusAuth.DEFAULT_SCOPE)
return render_to_response('done.html', {
'user': request.user,
'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None),
'plus_scope': scope
}, RequestContext(request))
EDIT
My requirement is to render a second page (signin_complete) from a multipage html (home_main.html). Currently, I am achieving this by redirecting the user with HttpResponseRedirect as shown below. But, I would also like to pass a callable dictionary that I can use in the second page of the multipage html.
Here is a link that gives more information of a multipage html under multipage template structure.
def done(request):
"""Login complete view, displays user data"""
scope = ' '.join(GooglePlusAuth.DEFAULT_SCOPE)
return HttpResponseRedirect('/home_main/#signin_complete')
Below is the dictionary that I would like to pass to the second page (sign_complete) in the multi page html.
{
'user': request.user,
'plus_id': getattr(settings, 'SOCIAL_AUTH_GOOGLE_PLUS_KEY', None),
'plus_scope': scope
}
The session is the place to store data between requests.
# in done():
request.session['dict_to_save'] = my_dict_to_save
return redirect('/new/url/to/redirect/to')
# in the new view:
values_from_session = request.session.pop('dict_to_save', None)
It would be much better if you would redirect request inside done() method, like the docs advises you to do.
This solves your issue as well, since you can define your own url to redirect to, there's related SO question of how to add hash tags when redirecting.
I have a Django page with a form. I have some view code which deals with the form as normal but prepopulates the form with initial data from the a user's session if available. This is so that when a user returns to this form they see previously selected options (yes, the form is quite extensive).
My views,py:
def myView(request):
...
form = ProjectInfoForm(request.POST or None)
if form.is_valid():
# process form, including a redirect
...
# if there is form data in the session, let's use that
# to initaliaze our from with data
if key in request.session:
form = ProjectInfoForm(
initial={
'model': request.session.get('model'),
...
}
)
return render_to_response(template_name, {
...
}, RequestContext(request))
Problem is: if I load the session data then the page does not display any error messages. The form does fail validation, I am just not getting the any output. Is there some conflict here with initial?
Any help would be much appreciated.
Well, from that snippet, if the session data is found then you completely re-initialize the form: the original, validated instance, which contained the POST data and any errors, has now been disposed. Presumably you would want to only enter that second if if the request is not a POST.
I have a simple form, that when submitted redirects to a success page.
I want to be able to use the data that was submitted in the previous step, in my success page.
As far as I know, you can't pass POST data when redirecting, so how do you achieve this?
At the moment I'm having to just directly return the success page from the same URL, but this causes the dreaded resubmission of data when refreshed.
Is using request.session the only way to go?
I do this all the time, no need for a session object. It is a very common pattern POST-redirect-GET. Typically what I do is:
Have a view with object list and a form to post data
Posting successfully to that form saves the data and generates a redirect to the object detail view
This way you save upon POST and redirect after saving.
An example view, assuming a model of thingies:
def all_thingies(request, **kwargs):
if request.POST:
form = ThingieForm(request.POST)
if form.is_valid():
thingie = form.save()
return HttpResponseRedirect(thingie.get_absolute_url())
else:
form = ThingieForm()
return object_list(request,
queryset = Thingie.objects.all().order_by('-id'),
template_name = 'app/thingie-list.html',
extra_context = { 'form': form },
paginate_by = 10)
You can:
Pass the data (either full data or just id to object) in request.session
Redirect with something like ?id=[id] in URL - where [id] points to your object.
Update:
Regarding pt. 1 above, I meant that you could do (in POST handler):
my_object = MyModel.objects.create(...)
request.session['my_object_id'] = my_object.id
Or you could try passing the whole object (it should work but I'm not 100% certain):
my_object = MyModel.objects.create(...)
request.session['my_object'] = my_object
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.