In the process of teaching myself how to use python's tornado web framework, I am trying to create a simple web server and some web pages. On one of the web pages, I have two buttons: one to log users out and redirect them back to the login page and one to submit a blog post. They are both "post" requests and have their name values in html set to "logout" and "new_post".
My questions is, how can I tell which button was pressed so that the post() method for the page's RequestHandler can perform the correct actions in each case? Is there a way to grab the "name" of the button pressed?
When you submit a form with a button click, a parameter with the name of the clicked button gets added to the request.
You can check, if the parameter exists and then do your stuff.
def post(self):
if self.get_argument("logout", None) != None:
# do logout stuff
if self.get_argument("new_post", None) != None:
# do submit a blog post stuff
On Python3 using Tornado, the setup looks as follows with two submit buttons (which is very similar to the above answer).
First the HTML and note both buttons are similar with differing display values. The name parameter is what will be passed to Tornado's POST handler:
<p><input type="submit" class="button" name="basic" value="Basic Query"></p>
<p><input type="submit" class="button" name="advanced" value="Advanced Query"></p>
Next, the POST handler. Using get_argument, you can specify the name and select it if the input is not null. Remember to include the ", None" in get_argument so that it defaults to None should the option not be selected:
def post(self):
if self.get_argument('basic', None) is not None:
self.write('Basic Query')
elif self.get_argument('advanced', None) is not None:
self.write('Advanced Query')
That's it! 2 forms and the ability to differentiate between. Happy coding!
Related
I'm trying to update a database value called ''favorites'' for the logged in user of a Flask web app using a button click. Essentially, the favorites column is a single string that looks like this: Apples, Bananas, Oranges where on the button click, I would want to append a value (say Cherries) by breaking apart the string into a list in my #app.routes(), appending the value, and rejoining it back into a string before committing the changes. I'm not sure what the proper way is to do this, here's what I have:
HTML snippet
<button action="{{ url_for('add') }}" method="post" type="submit">Favorite</button>
#app.routes()
#app.route('/add', methods=['POST'])
def add():
star_faves = current_user.favorites
star_faves_list = star_faves.split(', ')
star_faves_list.append('Cherries')
', '.join(star_faves_list)
current_user.favorites = star_faves_list
db.session.commit()
return render_template('add.html')
The problem is that I don't really understand how the HTML is communicating with Python/Jinja, if anybody can help clear that up I would greatly appreciate it.
It looks like you have some elements confused.
If you want to submit a POST request to the /add page, the easiest way is to create a form. (Buttons do not have an action or method attribute, forms do.) When you create the form, you also specify the HTTP method to use when submitting the form. So in your case, it should look something like this:
<form action="{{ url_for('add') }}" method="POST">
<input type="submit" value="Favorite">
</form>
You can use a button instead of an input with type submit, they are interchangeable.
If you don't want the page to reload while submitting the request, a more advanced technique you can use with JavaScript is something called AJAX.
This example code sends the same POST request to the /add page:
var request = new XMLHttpRequest();
request.onreadystatechange = function () {
// this method gets called if the state of the request changes (it succeeds of fails)
// you will probably want to update your page accordingly here
};
request.open('POST', '/add');
request.send();
PROBLEM STATEMENT
I'm working on a Flask web app that displays a list of items in a table. The user can select a row and hit a Delete button to delete the item. However, before the item is deleted from the database, the user is first routed to a confirmation screen where some item details are displayed as well as a Confirm button. The url for the confirmation page follows this pattern: dashboard/confirm-delete/<id> and the url for the actual delete page follows this pattern: dashboard/delete/<id>. See admin/views.py below for more details.
While the system works, the problem I have is that a user can simply skip the confirmation page by typing dashboard/delete/<id>, where <id> is substituted by an actual item id, into the address bar.
QUESTIONS
Is there a way to prevent users from accessing dashboard/delete/<id> unless they first go to dashboard/confirm-delete/<id> (the confirmation screen)? Alternatively, is my approach wrong and is there a better one available?
CURRENT CODE:
Function in my dashboard.html page called when a row is selected and the delete button is pressed:
$('#remove').click(function () {
var id = getId();
window.location.href="/dashboard/confirm-delete" + $.trim(id);
});
Confirm button in confirm-delete.html (the delete confirmation page):
<a class="btn btn-default" href="{{ url_for('admin.delete_item', id=item.id) }}" role="button">Confirm Delete</a>
My admins/views.py:
#admin_blueprint.route('dashboard/confirm-delete/<id>')
#login_required
#groups_required(['admin'})
def confirm_delete_item(id)
item = Item.query.get_or_404(id)
return render_template('admin/confirm-delete.html', item=item, title="Delete Item")
#admin_blueprint.route('dashboard/delete/<id>', methods=['GET', 'POST'])
#login_required
#groups_required(['admin'})
def delete_item(id)
item = Item.query.get_or_404(id)
db.session.delete(item)
db.commit()
return redirect(url_for('home.homepage'))
SOLUTION
Based on the answer marked as accepted I solved the problem as follows:
First, I created a new form to handle the Submit button in the confirm-delete.html page:
admin/forms.py:
from flask_wtf import FlaskForm
from wtforms import SubmitField
class DeleteForm(FlaskForm):
submit = SubmitField('Confirm')
I substituted the Confirm Button code with the following to confirm-delete.html:
<form method="post">
{{ form.csrf_token }}
{{ form.submit }}
</form>
Finally, I merged both of the functions in app/views.py as follows:
#admin_blueprint.route('dashboard/confirm-delete/<id>', methods=['GET', 'POST'])
#login_required
#groups_required(['admin'})
def confirm_delete_item(id)
form = DeleteForm()
item = Item.query.get_or_404(id)
if form.validate_on_submit():
if form.submit.data:
db.session.delete(item)
db.commit()
return redirect(url_for('home.homepage'))
return render_template('admin/confirm-delete.html', item=item, form=form, title="Delete Item")
This way, a user can't bypass the delete confirmation screen by typing a specific link in the address bar, plus it simplifies the code.
As already mentioned in comments, one way of solving your problem is checking for a certain cookie as the user sends a request. But personally I would not recommend this method, because such cookies can very likely be compromised unless you come up with some sort of hashing algorithm to hash the cookie values and check them in some way.
To my mind, the most easy, secure and natural way of doing it is protecting /delete route with CSRF-token. You can implement it with Flask_WTF extension.
In a word, you have to create something like DeleteForm, then you put {{form.csrf_token}} in your confirm-delete.htmland validate it in delete_view() with form.validate_on_submit()
Check out their docs:
http://flask-wtf.readthedocs.io/en/stable/form.html
http://flask-wtf.readthedocs.io/en/stable/csrf.html
I would make the delete page POST-only. The browser may skip a GET request or try it many times, you have no control over it. A crawler could follow an anonymous delete link and delete all your wiki articles. A browser prefetcher could prefetch a logout link.
REST purists would insist you use GET, POST, DELETE and PUT methods for their intended purposes.
https://softwareengineering.stackexchange.com/questions/188860/why-shouldnt-a-get-request-change-data-on-the-server
So,
In HTML
<form action='/dashboard/delete/{{id}}' method='post'>
In Flask
#app.route('/dashboard/delete/<int:id>', methods=['POST'])
def delete(id):
I think there's a mistake in parenthesis.
#groups_required(['admin'})
Shouldn't it be ??
#groups_required(['admin'])
I have a Flask site that has a 'search bar' where you type in the location ID of a particular location and then click Submit to be taken to the page for that location, if it exists. Here's the current form action:
<form id="locationinfo" action="{{ url_for('location') }}">
When you click Submit you are taken to /location?info=SITEID and that works just fine. What I want to do is change this behavior slightly so that when a user clicks Submit they are taken to /location/SITEID/ instead. I have the decorator set up in my main Flask routes file, but I'm struggling to put the pieces together to get this simple form together.
#app.route("/location/<locationid>/")
def locations(locationid):
...
return locationid
Any direction would be greatly appreciated!
[Edit with current full form code]
#app.route("/location")
def location():
location_id = request.args.get("info")
<form id="info" action="{{ url_for('location') }}">
<input type="text" name="info" id="locationfield">
<button type="submit">Go!</button>
</form>
You can't change how HTML forms submit their fields, they will always be in the query string or body (POST). One option is to use JavaScript to override the submit event to perform your own submit and re-render with the results.
A simpler solution is to redirect to the nice url after submit. This keeps the "search" action separate from the "show" action (even if they are handled by the same view).
#app.route('/location/')
#app.route('/location/<int:id>/')
def location(id=None):
# redirect to the second form if the id wasn't in the path
# raises 400 error if id wasn't in the query
if id is None:
return redirect(url_for('location', id=request.args['info']))
# id is in the path, continue
...
You can expand this later if you want to search by something besides id. Perform the search then redirect to the found id.
Hello I am trying to use the .GET form method on a check box, select tag and radio button in Python2.7 utilising google app engine and thats it.
here is the code so far
HTML
<form method='GET'>
<input type="checkbox" name="name" value="checkbox">
<select name='select'>
</form>
Python Code
select = self.request.GET['select']
checkbox = self.request.GET['name']
This works for my standard input fields (such as text) but not on any other type of input I use. I have looked everywhere for the documentation on this but have come up empty, any help would be appreciated.
Thanks in advance!
When a checkbox is not checked, the browser doesn't send the field.
GET is a MultiDict object, which acts just like a dictionary. As such you can use containment testing with in to see if the checkbox is present in the request:
checkbox = 'name' in self.request.GET
This sets a boolean True or False.
Alternatively, test for it instead with the .get() method:
checkbox = bool(self.request.GET.get('name'))
GET.get('name') returns None (a false value) if the checkbox is missing from the request, otherwise it'll return 'checkbox' (a true value).
For <select> elements, if none of the options are selected, it too is omitted from the request. If there is a default value you'd pick, then you can do so with:
select = self.request.GET.get('select', 'default value')
For the precise rules as to what a browser will include in the form, see the Successful controls specification; only controls that are successful are included.
I'm trying to integrate Mozilla Persona (browserid) into a Pyramid application. The login process is:
user can login on any page by clicking on the login button
a popup then shows a login form
when the users enters correct login/password, an ajax call is made by the popup to a Pyramid view that checks users credentials, and calls Pyramid remember function if the check succeeded
the browserid javascript code then reloads the current page
Now I want to handle the case of a new user subscribing to the web app and present a new view asking for a few more details (desired username, etc).
Since the "remember" function is called by an ajax call from the popup, I cannot redirect the user the the "/newuser" page.
So every view needs to redirect new users to the "/newuser" url whenever the remembered browserid has no corresponding user in the database.
Is there a way to intercept user requests before calling a view to call the "new_user" view instead? Or maybe my authentication approach is fundamentally incorrect and I should rely on another approach?
One way to do this would be to create an exception that should be raised when the user is created, and use this exception as the context of a view that would redirect to the new page.
class NewUser(Exception):
pass
#view_config(context=NewUser)
def new_user_exception(request):
return HTTPFound(request.route_path('new_user'))
Make sure the exception is raised during the first request after the first login (after having created the user object, for example), and the user will be redirected to the right page.
You could also put the code for the welcome page directly in new_user_exception, but without redirection, this page would have the url asked by the user, whatever it was.
Another solution would be to tweak how the persona part is done. For that, I'm going to guess you are using pyramid_persona (I'm the writer :) ). If not, what I'm saying will still apply, and will be even simpler to do.
What you could do is :
Change the login view so that it includes in the response a boolean saying whether this is the first login or not.
Change the javascript to check this boolean, reload the page if it's not the first time, and redirect to the welcome page if it.
The code for the login view can use pyramid_persona's login view like this :
from pyramid_persona.views import login
#view_config(route_name='login')
def new_login(request):
response = login(request)
if response.status == 200: #the login worked
# tweak the response
return response
EDIT : There's now a part about this in pyramid_persona's documentation : Do extra work or verification at login.