Clarification on the statement if request.Post: in django - python

Suppose I have a view like this:
def home(request, redir_url, template = 'example/home.html')
if request.session["profile_name"] and request.session["token"]:
return HttpResponseRedirect(redir_url)
if request.POST:
driver = Facebook()
res = driver.RetLoginUrl(redir_url)
return HttpResponseRedirect(res)
return render(request, template)
In this view, first I check the session variables, if the user has already logged in, I redirect to the welcome page, and if not I have a login button with which the user can authorize my app with Facebook.
And the template has a form method=POST with the login button as input.
My question is, how does the if statement (if request.POST) get executed, when there is a statement return render(request, template) at the end of the view. After the page is rendered, i.e. return render() statement has been executed, shouldn't the view function terminate, hence without a form response being submitted to the view?
Basically, I just would like to understand the execution flow of a django view. Is the return render() statement executed first, and then waits for user input?

It should be
if request.method == 'POST'
This sentence is executed every time you access the url configured for this view. If the method of the request was POST, when the user presses the button, then the code inside if is executed and a HttpResponse is returned
In the example the line render(request, template) is only executed when the method was'nt POST (maybe GET, PUT, DELETE, etc).
Finally, you could use Django Login Decorator to avoid the session variables checking

Related

Python - Flask - Stop page from re-submitting a post request

How can I stop Flask from re-adding form data to database on refresh?
My form.html sends data to approved.html like so:
if request.method == 'POST':
element = db_name(request.form.get('element'))
db.session.add(element)
db.session.commit()
else:
return redirect(url_for('home'))
return render_template('approved.html', current=element)
This so I can display the data from the form and let the user know entry has been added. But the problem is whenever I refresh approved.html which displays the form data, another copy of this entry is added to the database.
This happens because the browser stores the state of the last request and refreshing will re-submit the form, leading to another entry in your database. This is also why it's normal practice to redirect on the server-side after successful form submission. See Preventing POST on reloading a form and here
What you need to do is to successfully handle a successful form request and then redirect the user to a fresh state.
So something like this
return redirect(url_for('success'))
can be added instead of the render_template function which will then be redirected to the assigned function; there you can call the render_template of your choice.

Why doesn't Django allow redirect with context

As is clear from this question and the accepted answer, the only way to redirect with a context is to save it in the session. Why doesn't Django have a simple redirect method that takes a context object like render does?
For e.g., let us say, I have a login view that accepts both GET and POST requests. GET request simply renders the form in which a user can enter credentials, which will trigger a POST call to the same view.
def login(request, *args, **kwargs):
if request.method == 'GET':
context = {}
context.update(csrf(request))
return render(request, 'login.html', context=context)
elif request.method == 'POST':
username = request.POST.get('username', '')
password = request.POST.get('password', '')
user = auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request, user)
return redirect('loggedin')
else:
# Redirect with a context object that has 'error':True,
# so that the login template can display error message:
# 'Username/password is incorrect'
redirect('login')
return redirect('invalid')
Please notice that upon the entry of wrong combination of username/password, I would like to redirect the user to the same login page with error variable set in the context so that the template can draw out a warning.
I know, a possible answer would be to invoke render with custom context than redirect, but then isn't is a good practice to always use redirect after a post request?
Thanks.
This is not in any way a limitation of Django, and you don't seem to have thought through how this would even work.
A redirect is a simple HTTP response that tells your browser to go and request another page. How would Django, or any framework, pass a context through that redirect? Where would it go? And the receiving URL would have its own view, which would generate its own context - what would it do with the "redirect" context? How would it know?
But in any case I don't understand why you would want to redirect back to the same page on an error. Simply redisplay it. The redirect-after-POST principle is for successful posts, not unsuccessful ones.

Django - Simple redirect to requested URL

I'd like to do that, but this
return HttpResponseRedirect(request.path_info)
yields a redirect loop. I suspect it's because the request object is the website you're on at that time.
Whole function
def permit(request, pk):
if int(request.user.id) == int(pk):
return HttpResponseRedirect(request.path_info)
else:
return render_to_response('forbidden.html')
The user has clicked a link on a webpage. pk is the regx search term in urls.py. I want this to redirect to the page the user clicked on if the user is authorized, not the page he is currently on.
The problem is that as soon as the user is "redirected", the function permit is called. The if statement is true (since it is the authenticated user) and the redirect happens again... To prevent this loop, only redirect, if there was a post request:
def permit(request, pk):
if request.POST:
if int(request.user.id) == int(pk):
return HttpResponseRedirect(request.path_info)
else:
return render_to_response('forbidden.html')
## Return your login page here.

Flask login_required + next url params

I have a protected view in my app which just accepts POST requests.
#app.route("/booking", methods=("POST", ))
#login_required
def booking():
arg1 = request.form.get("arg1")
arg2 = request.form.get("arg2")
When an unauthorized user tries to access this view, I want them to
login and then be redirected here.
Right now, my login view looks like this:
#app.route("/login", methods=("GET", "POST"))
#login_required
def login():
do_login()
return redirect(request.args.get('next') or url_for('home'))
So what ends up happening is a POST request to /booking (which is the
"next" parameter) and I get a NOT ALLOWED error.
The problem is that login() makes a GET request to booking(). I can
get around that, but I am not sure how to retrieve the original POST
form arguments from /booking? Any ideas to get round that?
I would solve this by pulling the data and putting it in the session. You can remove the #login_required decorator and check this in the function using current_user.is_authorized. See Flask Sessions and Flask Login.
Something like this might work for you, I didn't test it:
from flask import session
from flask_login import current_user
#app.route("/booking", methods=("POST", ))
def booking():
if not 'arg1' in session.keys() and not 'arg2' in session.keys():
session['arg1'] = request.form.get("arg1")
session['arg2'] = request.form.get("arg2")
# Now the data will persist in the session
if current_user.is_authorized:
# Do what you need...
else:
# Redirect to login, session will persist
Why would you only use POST in the booking view ? You are probably rendering a form which should also allow GET.
#app.route("/booking", methods=['GET','POST'])
#login_required
def booking():
# render the form. something like
form = BookingForm()
# Check if POST
if request.method == 'POST':
# process the form now and do whatever you need.
return redirect(url_for('index'))
# code below will run if not POST. You should render the template here
return render_templte('booking.html')

How can I redirect after POST in Pyramid?

I'm trying to have my form submit to a route which will validate the data then redirect back to the original route.
For example:
User loads the page website.com/post
Form POSTs the data to website.com/post-save
User gets redirected back to website.com/post
Pyramid is giving me some troubles doing this.
Here's my slimmed down views.py
def _get_link_form(post_data):
""" Returns the initialised form object """
return LinkForm(post_data)
def home_page(request):
form = _get_link_form(request.POST)
return {'form' : form}
def save_post(request):
""" form data is submitted here """"
form = _get_link_form(request.POST)
if not form.validate():
return home_page(request, form)
This is the code I've been playing around with. Not only does it not work, it also feels messy and hacked up. Surely there's a simpler way to 'redirect after-POST' in Pyramid?
Your problem is most easily solved by simply POSTing to the same URL that your form is shown at, and simply redirecting the user away from the page when the POST is successful. That way until the form is successfully submitted you do not change URLs.
If you're just dying to POST to a different URL, then you need to save the data using sessions, since you're obviously handling the form data between requests.
Typically if you want to be able to handle errors in your forms you would use a session and flash messages. To do this you simply add a location for flash messages to appear in your base template and setup session support using something like pyramid_beaker.
Assuming your home page is setup at the 'home' named-route:
from pyramid.httpexceptions import HTTPFound
def myview(request):
user = '<default user field value>'
if 'submit' in request.POST:
user = request.POST.get('user')
# validate your form data
if <form validates successfully>:
request.session.flash('Form was submitted successfully.')
url = request.route_url('home')
return HTTPFound(location=url)
return {
# globals for rendering your form
'user': user,
}
Notice how if the form fails to validate you use the same code you did to render the form originally, and only if it is successful do you redirect. This format can also handle populating the form with the values used in the submission, and default values.
You can loop through the flash messages in your template of choice using request.session.peek_flash() and request.session.pop_flash().
route_url supports mutating the query string on the generated url as well, if you want to flag your home page view to check the session data.
You can obviously just pass everything in the query string back to the home page, but that's a pretty big security vulnerability that sessions can help protect against.
The Pyramid documentation has a particularly on-point section with the following example:
from pyramid.httpexceptions import HTTPFound
def myview(request):
return HTTPFound(location='http://example.com')
I do this like so:
from pyramid.httpexceptions import HTTPCreated
response = HTTPCreated()
response.location = self.request.resource_url( newResource )
return response
This sends the HTTP Created code , 201
The Pyramid documentation has content about Redirect, you can see more information in below link :
Pyramid documentation
import pyramid.httpexceptions as exc
raise exc.HTTPFound(request.route_url("section1")) # Redirect
Edited:
Actually you can do that on client side with Javascript, first you should send particular response to client side(either with flashes some data or return Response object):
window.location = '{{ request.route_path("route_name") }}';
Assuming your homepage is the default view of your pyramid web app, you can do:
def _get_link_form(post_data):
""" Returns the initialised form object """
return LinkForm(post_data)
def home_page(request):
form = _get_link_form(request.POST)
return {'form' : form}
def save_post(request):
form = _get_link_form(request.POST)
if not form.validate():
from pyramid.httpexceptions import HTTPFound
return HTTPFound(location=request.application_url)
Basically you need to know how the home_page view was "added" to your Configurator. If your homepage actually lives at /few/levels/deep/homepage then a redirect might look like this:
return HTTPFound(location=request.application_url + '/few/levels/deep/homepage')
A clean way is using the "overload" provided by pyramid for different request types, por example, you can decorate your methods this way:
#action(request_method='GET',
renderer='mypackage:/templates/save.mako',
name='save')
def save(request):
''' Fill the template with default values or leave it blank'''
return {}
#action(request_method='POST',
renderer='mypackage:/templates/save.mako',
name='save')
def save_post(request):
""" form data is submitted here """"
# process form
In the HTML, you must call the action form, like
<form method="POST" id="tform" action="${request.route_url('home', action='save')}">
This way, one method is processed when the method POST is used, and the other when the GET is used. The same name, but two implementations.

Categories

Resources