How to read in user input on a webpage in Flask [duplicate] - python

This question already has answers here:
Sending data from HTML form to a Python script in Flask
(2 answers)
How to use variables in SQL statement in Python?
(5 answers)
Closed 3 years ago.
I'm trying to create a basic web app that has an HTML form on the root landing page, and then after submission, run a postgresql query with the desired input and redirect the user to a page with a generated matplotlib chart of their input. In my main function, I have the following:
#app.route('/', methods=['POST'])
def main():
return render_template("main.html")
So let's say I have my main html file being rendered by the flask app. I have another route below:
#app.route('/query', methods=['POST'])
def queryPage():
# code to execute query with information
# passed from the main.html template
# then generate chart via matplotlib via returned information
return render_template("query.html")
I'm confused as to how to get my input from the form in main.html to send information back to the application for rendering at the /query endpoint. If someone could elaborate on this, I'd appreciate it. Front end is not my strong suit. Thanks!

You need a form on main.html... maybe like this (note the form action):
<form action = /query method="POST">
<label for="username">USERNAME:</label>
<input type="text" name="username" id="username" size="15">
<input type="submit" name="submit" id="submit"/>
</form>
When that form gets sent (after a user clicks a button lets say), the route in your flask code that matches the action (/query in this case) will get called and execute. Also the name= variables in any of your form elements will be available in your request on the back end (I'm using the variable username as an example). You can get them like this: request.form['username']. Other form variables (like a check box) will be slightly different.
Anyway in your case you need a /query action in your html somewhere in main.html.... It could be called by a button or timed javascript etc...
When this /query action is called on your main.html, you need to
return render_template('query.html, username=username)
and then the username variable will be available on the query.html page.
Keep in mind I only passed a single variable. You can pass a multiple variables, lists, dictionaries etc...
Also keep in mind any variable that you return to query.html can be made extremely dynamic using Jinja templating. You can loop through lists and print different html tags etc and use logic within your html... possible depending on what the values are that get returned to the page.

If I understand your question correctly then you are having difficulty passing the form information from your main function to the separate queryPage function for rendering. This can easily be achieved by providing the values you wish to pass as keyword arguments to the url_for function. These can then be retrieved from request.args within the queryPage function. Given the fact that you are returning query.html from this function and not an image, I assume that you are intending on displaying your chart within an img tag in query.html. In this case you will need another view function to generate and return the image itself. You may also need to disable browser caching for this endpoint to prevent browsers treating your dynamic image as if it were a static image https://stackoverflow.com/a/2068407/10548137.
#app.route('/', methods=['GET', 'POST'])
def main():
form = MyForm(request.form)
if request.method == "POST" and form.validate():
return redirect(url_for("queryPage", **form.data))
return render_template("main.html", form=form)
#app.route('/query', methods=['GET'])
def queryPage():
arguments = request.args.to_dict()
image_url = url_for("make_chart", **arguments)
return render_template("query.html", image_url=image_url)
#app.route('/make_chart', methods=['GET'])
def make_chart():
arguments = request.args.to_dict()
# perform postgres query here using arguments
# generate matplotlib chart here using query results
# ? save chart in BytesIO buffer in png format
response = send_file(file_pointer, mimetype="image/png")
# just return response here if don't need to alter headers
response = make_response(response)
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
response.headers["Pragma"] = "no-cache"
response.headers["Expires"] = "0"
return response

Related

Python: Sending to a certain URL based on form submission

I'm trying to create a search bar where it will send users to certain URLs based on the query they typed on the "result" page, e.g. "/results?<form_search>". I've successfully made the version where the result page URL is /results but this isn't really what I want.
Here's the HTML script:
<!--index.html-->
<form action="{{ url_for('search') }}" method="post">
<input type="text" id="search" name="form_search" placeholder="Type here">
</form>
Here's the Python script where I direct the result to /results URL:
#app.py
#app.route("/")
def index():
return render_template("index.html")
...
# I want to direct this to "/results?<form_search>"
# I think I need to put the line below somewhere but I'm not sure where
# form_search = request.form.get("form_search")
#app.route("/results", methods=["POST"]) # Want to change it to "/results?<form_search>"
def search(form_search):
...
return render_template("results.html", form_search=form_search, results=results)
Anyone can help?
I barely worked with flask but if you want to have the dynamic URL you need to add it in your #app.route decorator, e.g.: If I want a username to be posted in the URL this is what it would look like:
#app.route("/<username>") # str,int,uuid,float,path also works
def user_name(username=None, post_id=None):
return render_template("index.html", name=username)
When it comes to getting the data from the form I can show you a similar example as I did in django (I didnt work with flask a while so you might need to experiment a bit yourself) - This is a method as it is created in a class:
def get_queryset(self):
query = self.request.GET.get(
"searchrecipe") # searchrecipe is the name of our input form, means: the value we enter in the form -> This might also work for FLASK, get the data with request.get and FORM NAME
object_list = Recipe.objects.filter(name__icontains=query) #This filters the data in my database (aftger name) so not relevant for you
return object_list

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

Flask - Routing with multiple optional parameters

I have a Route named search: #app.route('/search')
Is it possible to add multiple optional parameters to it? Example:
#app.route('/search/pg/<int:pg>')
#app.route('/search/types/<types>')
#app.route('/search/number/<int:number>')
#app.route('/search/subject/<subject>')
The order in the URL shouldnt matter, so I could call /search/pg/2, or /search/subject/MySubject/pg/2 or /search/types/posts/subject/MySubject/pg/2
I tried this, but it only works with the full paths and all the parameters:
#app.route('/search/pg/<int:pg>/types/<types>/subject/<subject>', methods=['GET', 'POST'])
#app.route('/search/subject', defaults={'subject', None})
#app.route('/search/pg/<int:pg>/types/<types>', methods=['GET', 'POST'])
#app.route('/search/types', defaults={'types', None})
#app.route('/search', defaults={'pg': 1}, methods=['GET', 'POST'])
#app.route('/search/pg/<int:pg>', methods=['GET', 'POST'])
def search(pg, types=None, subject=None):
pass
You can use filter in the URL instead of "sub-resources".
Then you can put search arguments in any order in your request:
/search?pg=<pg>&types=<types>
Inside the flask view function you can retrieve parameters from the request object:
#app.route('/search/')
def search():
pg = request.args.get('pg')
...
#David, I worked on a package that does this called flask_optional_routes. The code is located at: https://github.com/sudouser2010/flask_optional_routes.
from flask import Flask
from flask_optional_routes import OptionalRoutes
app = Flask(__name__)
optional = OptionalRoutes(app)
#optional.routes('/<user_id>/<user_name>?/')
def foobar(user_id, user_name=None):
return 'it worked!'
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
If you are trying to route a user based on multiple, optional form values as route parameters, then I found a useful workaround.
First, create an intermediate route that will create the query string. This route will only allow for POST methods (since the 1 or more of the form values would be submitted by the form).
#app.route('/example', methods=['POST']
def createQueryParams():
form = ExampleForm()
# Retrieve/cleanse/validate form data
example1 = form.example_field1.data
example2 = form.example_field2.data
return redirect(url_for('app.route', example1=example1, example2=example2))
Note that each keyword argument in the redirect(url_for()) needs to either be a parameter used in app.route or something you expect to add as a query parameter.
Next, alter your app.route() to have a GET method and extract the query parameters like #lee-pai-long mentioned
#app.route('/search', methods=['GET'])
def search():
if len(request.args) > 0:
example1 = request.args.get('example1')
example2 = request.args.get('example2')
# Do stuff
Finally, in the .html template where the form is submitted, make sure the form action directs to the createQueryParams route, not the search route. Such as
<form action="{{ url_for('app.createQueryParams') }}" method="POST">
<!-- Flask form stuff -->
</form>
I used this structure to create a page where users could filter posts based on up to X different criteria (title search, date sort, tags, etc.). I wanted to be able to have an optimized query parameter, but without the intermediary createQueryParams route, I was not able to determine what form values were selected until after the url was created, since the single route owned both methods.

POSTing to a URL in a controller does not trigger render_template in Flask

I suppose this should be very simple but I think I'm missing something.
Simple description: I have a page that lists specific words. Each word has an ID. I'm passing this ID to a function, which then posts it to a URL. What I'm attempting to do is to pass the ID, query it in the backend, and get transferred to the edit page with the query result.
Here's my code:
AngularJS function
$scope.editDefinition = function (searchItem) {
var param = { id: searchItem.id };
var url = "/table-based";
$http.post(url, param).success(function (data) {
console.log(data);
});
};
Flask/Python route function
#app.route("/table-based", methods=["GET", "POST"])
def edit_definition():
if request.method == "POST":
j = json.loads(request.data)
uid = j["id"]
cdef = db.getDefinitionById(uid)
return render_template("edit.html", definition=cdef)
return render_template("edit.html")
HTML
<div ng-init="init('{{ definition |tojson|safe }}')" ng-controller="editCtrl">
<ng-include src="'/_partial_edit_form'"></ng-include>
</div>
EditCtrl has the relevant $scope.init function to receive definition. Basically, it returns a response that contains the HTML of the edit.html template, but it does not redirect to the URL.
What gives?
You haven't implemented anything that will cause the browser to redirect to your new template page. In the data returned you should just see the html generated by render_template.
You have two options that I can see. The first would be to store the cdef in flask's g or session object and then reload the page with angular $route.reload() or $window.location.reload() and have flask pick up the cdef from g or session on GET.
The second option would be to refactor your code so that you have angular doing the page rendering, then you just update your definition object on POST. In this case, flask would return json.dumps(cdef) instead of the render template and you would use the angular templating to render it.
The second option is much better, and is pretty much exactly what Angular is designed to do. When I create Angular-Flask apps, I very rarely use render_template, instead I have Angular doing all the rendering after retrieving the data as JSON from a flask-based API.

Save GET URI while doing POST in Flask

I need to save GET URI to use it later after POSTing form in Flask. The problem is, that it is overwritten when doing POST. Here is the code (schematically):
#app.route('/test', methods=['get', 'post'])
def test_view():
url_query = request.url.replace(request.base_url,'/')
form = Form()
if form.validate_on_submit():
# at this point url_query is already overriten with '/'
yadayada(url_query)
So, for example, if user requests https://host/test?kekeke=nenene
I expecting that the string "/test?kekeke=nenene" would be passed to yadayada(), but at practice it would be overwritten by '/'. How to solve that properly? Thanks.
Set your Form action to include the query parameters:
<form method="POST" action="{{ url_for('test_view', **request.args) }}">
where the request.args object gives you access to the query parameters, and the url_for() function generates a new URL with every key-value pair from request.args as query parameters.
Now when the form is POSTed the exact same query parameters are sent along as where used for the original GET request that rendered the form.

Categories

Resources