I am creating an app that does some analysis, given a user enters in some IDs into the form. For example, if a user types 12345, 23456 into the TextField form, the app will run some analysis on these IDs and then display the results. My problem is that currently, when the user clicks "Submit" and the data analysis completes, it always redirects the user to www.website.com/results. I need to create unique url's like www.website.com/results/12345+23456 so that 1) I can have multiple users and 2) users can send this link to people to re-generate the analysis.
Now, there are some questions on StackOverflow that are similar to my question but they are not the same and did not help me. So first, let me show some code before discussing that.
I have a home page which contains the the form:
<div>
<form action="https://website.com/results/" class="form-inline" method="post">
<div class="form-group">
<label for="PubmedID">Pubmed ID(s)</label>
<input type="text" class="form-control" id="PubmedID" name="pmid" value="{{request.form.pmid}}">
</div>
<button type="submit" id= "myButton" class="btn btn-default" data-toggle="modal" data-target="#myModal">Submit</button>
</form>
</div>
As you can see, the value for the form is request.form.pmid. My Flask-Wtform for this is here:
class pmidForm(Form):
pmid = TextField('PubmedID')
Since the action of this form points towards website.com/results that triggers my Flask function to be called:
#app.route('/results/', methods=["POST"])
def results():
form = pmidForm()
try:
if request.method == 'POST':
#entry = request.form or request.data doesn't help me...
entry = form.pmid.data #This is the user input from the form!
# DO LOTS OF STUFF WITH THE ENTRY
return render_template('results.html')
except Exception as e:
return(str(e))
As you can see I am using POST and form.pmid.data to get the data from the textfield form.
Again, I don't want to just redirect to /results, I'd like to expand on that. I tried to modify my form so that the form action pointed to https://website.com/results/{{request.form.pmid}}/ and then update the results function to be
#app.route('/results/<form_stuff>', methods=["POST"])
def results(form_stuff):
But this never worked and would re-direct me to a 404 not found page. Which I believe makes sense because there is no form data in the action when the HTML is first rendered anyway.
Now, the other post that mine is similar to is: Keeping forms data in url with flask, but it quite doesn't answer or solve my problem. For tthis post, the key point that people made was to use POST (which I already do), and to obtain and return the data with return request.args['query']. For me, I'm already processing the form data as I need to, and I have my return render_template() exactly how I want it. I just need to add something to the results URL so that it can be unique for whatever the user put into the form.
What do I need to add to my form in the html and to my Flask /results function in order to have the form data added into the URL? Please let me know if there's any other information I can provide to make my problem more clear. I appreciate the help! Thanks
This isn't really a question about Flask.
If you want the data to show in the URL when you submit the form, you should use method="get" rather than "post". Then the URL will be in the form https://website.com/results/?pmid=12345.
Related
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.
Is there a way to prevent users from accessing some (or all) URLs in application? For example, I am following Django tutorial and one of the examples has a URL:
#music/album/<pk>/delete
url(r'image/(?P<pk>[0-9]+)/delete/$', views.ImageDelete.as_view(), name='image-delete'),
that deletes database entry give pk as a parameter. Of course, now it is possible to delete this entry with just copy-pasting the URL with any existing primary-key, so what is the best practice to avoid it? Thanks
EDIT. Based on the replies and comments, I decided to elaborate a bit more. I am actually using DeleteView and forms with POST request as #solarissmoke suggested in answer.
<form action="{% url 'album:image-delete' image.id%}" method="post" style="display: inline;">
{% csrf_token %}
<input type="hidden" name="image_id" value="{{ image.id }}"/>
<button type="submit" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-trash"></span>
</button>
</form>
and in my views.py:
class ImageDelete(DeleteView):
model = Album
# if you successfully delete the object, page redirects to <homepage>
success_url = reverse_lazy('album:index')
So, there were few suggestions on checkin whether the user is verified to delete URL entry (e.x. the image) and to add pop up/notification to verify if the user indeed wants to delete the entry. However, it does not feel like a complete solution. In the comments I brought example of Facebook, where you can not delete imeage/post by just copy-pasting the delete URL. Surely I'm not asking for Facebook-like security, however, I'm really curious how can secure URLs so that it's nearly impossible for regular user to delete entry with simple copy-pasting. Thanks again!
Best practice is that you should not be allowing modification of data like this through HTTP GET requests, which are intended (as the name suggests) for getting data rather than updating it.
You should use forms and POST requests to perform actions like deleting objects etc. Django provides lots of helper views for doing this. For example DeleteView:
A view that displays a confirmation page and deletes an existing object. The given object will only be deleted if the request method is POST. If this view is fetched via GET, it will display a confirmation page that should contain a form that POSTs to the same URL.
The advantages of using these views are:
You can make sure the user has permissions to edit an object before making any changes. Django will perform the basic checks (e.g., CSRF) for you. You can augment the views to perform additional checks like making sure a user is logged in or checking any other permission.
You can enforce Cross-Site Request Forgery Protection.
It is not possible to accidentally delete an object by visiting a URL a second time (as the documentation above explains).
there are many ways.. e.g:
user = request.user
if user.is_authenticated() and user.profile.can_delete_image(image_pk):
# only then, image can be deleted by this user
# can_delete_image(image_pk) is defined by you
else:
raise DeletePermissionDenied # you can define your own Exception, just for fun
I'm building a website using Flask, and on one page I've got two forms. If there's a POST, I need to decide which form is being posted. I can of course deduct it from the fields that are present in request.form, but I would rather make it explicit by getting the name (defined by <form name="my_form">) of the form that is submitted. I tried several things, such as:
#app.route('/myforms', methods=['GET', 'POST'])
def myForms():
if request.method == 'POST':
print request.form.name
print request.form.['name']
but unfortunately, nothing works. Does anybody know where I can get the name of the form submitted? All tips are welcome!
There is no 'name of the form'. That information is not sent by the browser; the name attribute on <form> tags is meant to be used solely on the browser side (and deprecated to boot, use id instead).
You could add that information by using a hidden field, but the most common way to distinguish between forms posting to the same form handler is to give the submit button a name:
<submit name="form1" value="Submit!"/>
and
if 'form1' in request.form:
but you could also use a <input type="hidden"> field to include the means to distinguish between forms.
This is a function which (in a GET request) receives a case_url and case_key and serves the corresponding case (using mongoDB) to a html template called detail_case.
Im trying to add a feature where when a form is filled(on this same page detail_case) and it is submitted, it should submit a POST request to the same function and the code under 'if request.method=="POST"' should get executed.
#app.route('/case/<case_url>/<case_key>', methods=["GET","POST"])
def serve_case(case_url,case_key):
"""for saving a comment in db.comments"""
if request.method == "POST":
text=request.form['comment_text']
#code which inserts it in the database
return redirect(url_for('serve_case', \
case_url=case_url,\
case_key="Highlights"))
"""
Function serves the case as per the key indicated in the URL
"""
#corresponding code here which fills values of variables and sends it to another page
return render_template('detail_case.html')
The problem is that I don't think the POST request is ever executed. This is the html code on the template page detail_case-
<textarea placeholder="Please enter your comments here" action="{{ url_for('serve_case',case_url=case_url,case_key=case_key)}}" method="POST" name="comment_text" rows="6"></textarea><br />
The problem i think is the action field. I don't know how should I send the variable comment_text to my function. Infact, the code under POST does not get executed when I submit.
Basically the issue is that during a GET request, it sends 2 variables which are needed in the parameters of the function serve_case. During my POST request, well, I don't know how to exactly frame the action field. If I send no parameters, its an error. If I don't send it to the same function, then how will it execute the POST code? Could someone please suggest sumthing?
i'm pretty new to flask, i'm editing someone else's code
You need to submit the POST request (for example through form) like below:
<form action="{{ url_for('serve_case',case_url=case_url,case_key=case_key)}}" method="POST">
<input type="text" placeholder="Please enter your comments here">
<input type="submit" name="comment_text" rows="6"><br />
</form>