Flask - Routing with multiple optional parameters - python

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.

Related

Can we embed param in URL in Django app from url.py?

I want to embed param in URL by default like I have end point abc.com/abc with call type GET I call from postman like this abc.com/abc?isActive=1 now I want Django to embed key=1 in URL
Like This
abc.com/abc?isActive=1&key=1 in url.py file
I now it looks no sense in it but I need this for some purpose
In django you can't capture GET or POST parameters by default. From the Docs:
The URLconf searches against the requested URL, as a normal Python string. This does not include GET or POST parameters, or the domain name.
What you can do in your view is to check the presence of the GET-parameter you want, like:
# views.py
def my_view(request):
if request.method == 'GET':
my_param = request.GET.get('isActive', None)
if my_param is not None:
# do something
else:
# do something, e.g. exception

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

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

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.

Access attributes of object in multiple routes

#app.route('/a', methods=['GET', 'POST'])
def a():
form = data(request.form)
if form.validate_on_submit():
job = Job()
job.title = form.title.data
return redirect(url_for('users.b'))
#app.route('/b', methods=['GET', 'POST'])
def b():
#access here the job.title previous defined
I know that I can pass parameters to url_for, but isn't what i am looking for. Basically access the previous defined object attributes. How can I do that?
HTTP is a stateless connection, meaning a user being redirected to /b from /a will only be able to pass information via HTTP headers, cookies and URL parameters.
To solve this particular problem, you'll want to save the information set in job.title into a database on the host (i.e. Using SQLAlchemy in Flask). Then, you can pass the database ID of the new entry into the url_for parameter in /a so that it's redirected to some URL with this parameter set. (i.e. /b?user_id=123). Be aware there are multiple security issues you'll need to be aware of, since the user can just change the ID themselves.
There are lots of ways to do this. You may want to use the Flask-WTF addon to handle some of this for you.
Here's some snippets on handling sessions in Flask.

Do I need to name the url when using app.add_url_rule?

From the Flask docs:
def index():
pass
app.add_url_rule('/', 'index', index)
It also says:
endpoint – the endpoint for the registered URL rule. Flask itself assumes the name of the view function as endpoint
Indeed, if I do app.add_url_rule('/', None, index) it all seems to work fine. (I'm not sure of the terminology and what an "endpoint" actually is.)
Some further questions:
Is there any reason to specify the second argument?
Is the second argument indeed the "endpoint"?
What are the benefits/drawbacks of (not) specifying the second argument?
What if the same name is used in another url rule? Is the first overwritten? Is this intended?
In this case, an endpoint is simply a term for a valid URL of your application. From Wikipedia:
In service-oriented architecture, an endpoint is the entry point to a service, a process, or a queue or topic destination
As for naming your URLs - yes, it is optional. There are some benefits to defining names. When using *url_for*() or redirect() for example, you can specify the url's name as a shortcut. This comes with the added benefit of allowing your to change your url structure without changing every line of code that interacts with that url, since it only references it by name.
So in your example, you could reference your index url like this:
return redirect('index')
For your last question, I'm not sure what would happen. It likely would error when trying to resolve the url with that name. Either way, there's no reason to define two different urls with the same name.
The reason for the second argument is the following:
Let's say you're designing a website and you have /login be the page where your users enter their username, password, OpenID, whatever. Upon successfully logging in, you might want to send them to /. In Flask, the canonical way to do that is:
return redirect(url_for('index'))
Where 'index' is the name of the function you defined to be the handler for /, e.g.,
#app.route('/')
def index():
return render_template('index.html')
If you instead do:
def index():
return render_template('index.html')
app.add_url_rule('/', None, index)
It will work just fine when your user requests / explicitly, but when your users successfully login they'll be faced with a horrible error message and have to visit / manually.
In short, it's the proper thing to do.
Also, you should note that
#app.route('/')
def index():
return render_template('index.html')
And
def index():
return render_template('index.html')
app.add_url_rule('/', 'index', index)
Are exactly the same and in the both of these last two snippets redirect(url_for('index')) will work perfectly.

Categories

Resources