Updating vote counts in Google App Engine - python

I am trying to create a voting button for posts using Google App Engine.
Currently I am implementing it like this:
class Latest(Handler):
def get(self):
posts = recent_posts()
qt = memcache.get('recent_posts_qt')
if qt:
qt = time.time() - qt
self.render('latest.html', articles = posts, qt = qt)
def post(self):
post_id = int(self.request.get("id"))
q = AllPost.get_by_id(post_id)
q.votes += 1
q.put()
time.sleep(0.5)
update = recent_posts(update=True) # for memcache
posts = db.GqlQuery("SELECT * FROM AllPost ORDER BY created DESC LIMIT 10")
posts = list(posts)
self.render('latest.html', articles=posts)
The html I am using is this:
<div class="article-votes">Votes: {{item.votes}}
<form action="/latest" method="post">
<input name="id" value={{item.key().id()}}>
<button>Vote+</button>
</form>
</div>
If I try to refresh the page after voting on a post, I get the "confirm form submission alert". There must be a better way to implement this without this happening. Is it possible to update the vote count and datastore without rendering the page again?

Refreshing a POST request will always trigger that confirmation window, it is a common behavior in most browsers. You can control the form POST request closely via Javascript using AJAX (XMLHTTPRequest) and that wouldn't cause another page render.

Related

How can I pass a client-side parameter to a server-side route without using forms?

I have a simple Flask web app. My index template has various ways of interacting with clients using javascript and HTML. I am also have a form that, upon submission, routes to another flask process and uses the request.form command to retrieve user-submitted data.
However, I want to do something a little different. I would like to initiate a Flask redirection upon javascript event but include a parameter, and not use form.
For example, my index.html file would display something like this after template rendering:
function startRedirect(parameter) {
window.location.pathname = '/myRedirect';
}
<input type="checkbox" id="sample" name="sample" onChange="startRedirect(parameter);">
And part of my Flask script would have:
#app.route('/myRedirect')
def myRedirectFunction():
# do something with the parameter here
return render_template('index.html')
I realize this can be done with using a form, but I am interested in accomplishing this task without having a form. I was thinking about somehow using request.args, but don't quite understand what to do.
You can use a dynamic route to capture a simple input and pass it to the route's function.
app.route('/myRedirect/<param>')
def myRedirectFunction(param='hello world'):
return render_template('index.html', param=param)
Using this route as a redirect, you can pass a single param (or multiple if you serialize them) that you can use to do something. From there, you can either display or you can redirect again to a common endpoint so the user does not see the param in the url.
There's no need for a form or an explicit redirect, just attach a route and some parameter to the dynamic route.
Let's say you have a model to list the departments in your company:
class Departments(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True)
Now you have a department selection page:
#app.route('/departments_home', methods=['GET'])
def departments_home():
departments = Departments.query.all()
return render_template('departments_home.html',
departments=departments)
On the frontend you might have a variety of selections, each giving a link to the same route but with a different department_id:
{% for department in departments %}
Click to go to {{ department.name }}
{% endfor %}
Now you just need another route to handle this, taking the variable department_id that was passed in the GET request:
#app.route('/load_department/<department_id>', methods=['GET'])
def load_department(department_id):
department = Departments.query.get(int(department_id))
department_data = # do stuff here with the specific department

Using flask form result to generate a URL?

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.

In Flask, how do I prevent a route from being accessed unless another route has been visited first?

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'])

How to Distinguish Button Presses in Tornado?

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!

Creating forms with Pyramid web framework and Jinja2 templates

I am a newbie trying to build a simple web framework to support a SQLAlchemy DB. I am using Pyramid and Jinja2 templates. I am planning on using server-side rendering of static HTML instead of JSON. I have gone over the Pyramid tutorials repeatedly, spent days on JINJA's website, looked up questions here on good ol' Stack, but haven't been able to gather a clear understanding of how to achieve the following:
My issue at the moment is how to create a template that adds created objects using the get() method. I am having a hard time figuring out how to add these objects using the JINJA2 template syntax, e.g. {{ name }} in a form.
Here is a snippet example of the route, view and template:
CODE:
__init__.py
Each entity that makes up an assessment in the db, e.g. user, elements, video, categories, etc. have routes like this:
config.add_route('assessments', '/assessments')
config.add_route('assessment', '/assessments/{id}')
views.py
The design for the RESTful API is the same for EVERY entity mentioned above:
#view_defaults(route_name='assessments', renderer='templates/editor_view.jinja2') # editor view for creating assessment
class AssessmentsViews(object):
API = api
def __init__(self, request):
self.request = request
#view_config(request_method='GET')
def get(self):
assessments = self.API.retrieve_assessments()
return {}
#view_config(request_method='POST')
def post_assessment(self):
name = self.request.params['name']
text = self.request.params['text']
user = self.request.params['username']
video = self.request.params['videoname']
catogories = self.request.params['category_names']
assessment = self.API.create_assessment(name, text, user, video, categories)
return HTTPAccepted(location = request.route_url('assessments/{id}'))
#view_defaults(route_name='assessment', renderer='templates/user_view.jinja2') #user_view for the user to take the assessment
class AssessmentViews(object):
API = api
def __init__(self, request):
self.request = request
def get_assessment(self):
assessment_id = int(self.request.matchdict['id'])
assessment = self.API.retrieve_assessment(assessment_id)
return {'name': assessment.name, 'text': assessment.text, 'user': assessment.user, 'video':assessment.video, 'categories': assessment.categories}
#view_config(request_method='GET')
def get(self):
assessment = self.get_assessment()
if assessment is None:
raise HTTPNotFound()
return HTTPFound(location=request.route_url('/assessments/{id}'))
#view_config(request_method='PUT')
#code
#view_config(request_method='DELETE')
#code
template
A template meant for a user to enter their ratings (take an assessment), but how does one add the categories to the form? Is it by a redirect? or by JINJA code?
# add {{categories}} from Assessment where in this form?
<form method='POST' action ='/category_ratings'>
<dl>
<dt><label for = 'category_rating_int'> {{category}} </label></dt>
<dd>{{ forms.input('category_rating_int', type=category_rating_int, '') }}</dd>
<dt><label for = 'category_rating_int'> Rating </label></dt>
<dd>{{ forms.input('category_rating_int', type=category_rating_int) }}</dd>
<dt><label for = 'category_rating_int'> Rating </label></dt>
<dd>{{ forms.input('category_rating_int', type=category_rating_int) }}</dd>
<dt><label for = 'category_rating_int'> Rating </label></dt>
<dd>{{ forms.input('category_rating_int', type=category_rating_int) }}</dd>
<dd><input type="submit" name="submit" value="Submit" /></dd>
</dl>
</form>

Categories

Resources