django message with redirect not displaying - python

Why messages framework only work with render with context. But in my case i want redirect because the form validation is failed i want here message to display in frontend ..
def user(request):
if form.is_valid():
#do action
else:
messages.error(request, "Your Message")
#return render(request, 'pages/user-account.html') # working fine
return redirect(reverse('frontend.views.user')) # not working
return render(request, 'pages/user.html')
What am missing here ?

Something is not right in your code. !
return redirect(reverse('frontend.views.user')) # not working
You told that you redirect. But redirecting to where ? 'frontend.views.user' will get you to the same page but again with different request object. Make your view redirect to another function or use the working one render with request.

Related

How to rewrite this Flask view function to follow the post/redirect/get pattern?

I have a small log browser. It retrieves and displays a list of previously logged records depending on user's input. It does not update anything.
The code is very simple and is working fine. This is a simplified version:
#app.route('/log', methods=['GET', 'POST'])
def log():
form = LogForm()
if form.validate_on_submit():
args = parse(form)
return render_template('log.html', form=form, log=getlog(*args))
return render_template('log.html', form=form)
However it does not follow the post/redirect/get pattern and I want to fix this.
Where should I store the posted data (i.e. the args) between post and get? What is the standard or recommended approach? Should I set a cookie? Should I use flask.session object, create a cache there? Could you please point me in the right direction? Most of the time I'm writing backends...
UPDATE:
I'm posting the resulting code.
#app.route('/log', methods=['POST'])
def log_post():
form = LogForm()
if form.validate_on_submit():
session['logformdata'] = form.data
return redirect(url_for('log'))
# either flash errors here or display them in the template
return render_template('log.html', form=form)
#app.route('/log', methods=['GET'])
def log():
try:
formdata = session.pop('logformdata')
except KeyError:
return render_template('log.html', form=LogForm())
args = parse(formdata)
log = getlog(args)
return render_template('log.html', form=LogForm(data=formdata), log=log)
So, ultimately the post/redirect/get pattern protects against submitting form data more than once. Since your POST here is not actually making any database changes the approach you're using seems fine. Typically in the pattern the POST makes a change to underlying data structure (e.g. UPDATE/INSERT/DELETE), then on the redirect you query the updated data (SELECT) so typically you don't need to "store" anything in between the redirect and get.
With all the being said my approach for this would be to use the Flask session object, which is a cookie that Flask manages for you. You could do something like this:
#app.route('/log', methods=['GET', 'POST'])
def log():
form = LogForm()
if form.validate_on_submit():
args = parse(form)
session['log'] = getlog(*args)
return redirect(url_for('log'))
saved = session.pop('log', None)
return render_template('log.html', form=form, log=saved)
Also, to use session, you must have a secret_key set as part of you application configuration.
Flask Session API
UPDATE 1/9/16
Per ThiefMaster's comment, re-arranged the order of logic here to allow use of WTForms validation methods for invalid form submissions so invalid form submissions are not lost.
The common way to do P/R/G in Flask is this:
#app.route('/log', methods=('GET', 'POST'))
def log():
form = LogForm()
if form.validate_on_submit():
# process the form data
# you can flash() a message here or add something to the session
return redirect(url_for('log'))
# this code is reached when the form was not submitted or failed to validate
# if you add something to the session in case of successful submission, try
# popping it here and pass it to the template
return render_template('log.html', form=form)
By staying on the POSTed page in case the form failed to validate WTForms prefills the fields with the data entered by the user and you can show the errors of each field during form rendering (usually people write some Jinja macros to render a WTForm easily)

How to do login at end of other form submission in Flask?

I'm writing a web app where you can make a product request through a form before logging in (or signing up). It's kind of a "try it" mode to get the abandon rate lower. When we were putting the signup first -- we were way too many people abandoning the service because they had to make an account first. This way we're drawing people committing some effort before they have to signup.
In any case, I am having trouble getting this pattern working. I'm using the flask-login extension. I have a view that renders my product request form and redirects to the login view if the user is not logged in:
#app.route('/customer/gift_request', methods=['GET', 'POST'])
# Note login is NOT required for this.
def gift_request():
form = GiftRequestForm()
error = None
if form.validate_on_submit():
if current_user.get_id():
new_gift_request = GiftRequest(
current_user.id,
form.giftee_name.data,
form.giftee_age.data,
int(form.giftee_sex.data),
form.event.data,
form.event_date.data,
float(form.budget.data))
try:
db.session.add(new_gift_request)
db.session.commit()
flash("You have successfully submitted a gift request! ",'success')
return redirect(url_for('customer_home'))
except:
error = "Error creating gift request"
else:
return redirect(url_for('login', next=request.url)
return render_template('customer_portal/gift_request.html', form=form, error=error)
My login view is the following:
#app.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
error = None
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user:
if bcrypt.hashpw(form.password.data.encode('utf-8'), user.password.encode('utf-8')) == user.password:
login_user(user)
flash('User {} logged in'.format(current_user.email), 'success')
# Handle the next URL
next_url = request.args.get('next')
if next_url is not url_for('gift_request'):
return abort(400)
return redirect(next_url or url_for('customer_home'))
else:
error = "Incorrect password"
else:
error = "User not found"
return render_template("user/landing_page.html",
login_form=form,
register_form=RegisterForm(),
error=error,
show_login_modal=True,
show_register_modal=False)
Note that my login is rendered as a modal window on my landing page. In any case, I have no idea how to keep my gift request form data around after the login. How do I pass that around? I've tried a bunch of stuff -- like putting it in the request -- but that feels hacky (and potentially insecure). Also, upon login submission -- that form data is gone. It's never passed to the login template, so i guess it just disappears. I've searched the internet, but I can't seem to find a pattern to do this.
Also, the login is just a piece of this -- if the person that's filling out the gift request doesn't have a login, then I want to take them to the registration page to make an account. But I figure if I figure this out -- I can take the same pattern and extend it to that.
I think you may need to use a session. It would look something like this:
if form.validate_on_submit():
if current_user.get_id():
session['new_gift_request'] = GiftRequest(
current_user.id,
form.giftee_name.data,
form.giftee_age.data,
int(form.giftee_sex.data),
form.event.data,
form.event_date.data,
float(form.budget.data))
I have used this many times, and I've never had an issue. If you want the form data to hang around without a session, then you have to keep track of it and pass it to every subsequent template.
UPDATE
After re-reading your code it appears that if the current user is not logged in, you aren't saving the form data at all. Here is a slight change (obviously make sure you import session from flask):
#app.route('/customer/gift_request', methods=['GET', 'POST'])
# Note login is NOT required for this.
def gift_request():
form = GiftRequestForm()
error = None
if form.validate_on_submit():
if current_user.get_id():
new_gift_request = GiftRequest(
current_user.id,
form.giftee_name.data,
form.giftee_age.data,
int(form.giftee_sex.data),
form.event.data,
form.event_date.data,
float(form.budget.data))
try:
db.session.add(new_gift_request)
db.session.commit()
flash("You have successfully submitted a gift request! ",'success')
return redirect(url_for('customer_home'))
except:
error = "Error creating gift request"
else:
session['giftee_name'] = form.giftee_name.data
session['giftee_age'] = form.giftee_age.data
session['giftee_age'] = form.giftee_age.data
session['giftee_sex'] = int(form.giftee_sex.data)
session['event'] = form.event.data
session['event_date'] = form.event_date.data
session['budget'] = float(form.budget.data)
return redirect(url_for('login', next=request.url)
return render_template('customer_portal/gift_request.html', form=form, error=error)

Clarification on the statement if request.Post: in django

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

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.

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