How do is pass dictionaries to url_for in Flask - python

I want to pass an endpoint to url_for where the endpoint contains name=value parameters where the name is a variable.
Basically i have multiple templates which could be collapsed down to one template if I could pass parameter variable names. A solution could be to pass a dictionary, but there may be other ways. I could definitely do it by post-processing the html jinja generates before it gets rendered.
I have tried all the obvious tricks I can think of including nested {{ }}.
So at the end of my route code I have
# routes.py
...
#bp.route('/customer/add/prompt/<customer_id>',methods = ['POST', 'GET'])
#login_required
def customer_add_prompt(customer_id):
#code code code
return render_template('customer_add.html',
customer_id = customer_id)
Indeed I have lots of routes for different subjects (customer, product, invoice) that all end this way. So for each subject I need a jinja template that looks like this.
# customer_add.html
...
<form action = "{{ url_for(customer_add, customer_id = customer_id) }}" method = "POST">
<-- html htlm htlm -->
</form>
When the user submits the form the endpoint for customer_add is followed and customer_add expects and is passed customer_id = 1234 (or whatever the value is).
Here is the problem. I should be able to combine all the templates like so.
# subject_add.html
...
<form action = "{{ url_for(subject_target, subject = subject_id) }}" method = "POST">
<-- html htlm htlm -->
</form>
Then render it like so.
# routes.py
...
#bp.route('/customer/add/<customer_id>',methods = ['POST', 'GET'])
#login_required
def customer_add_prompt(customer_id):
#code code code
subject_target = 'customer_add.html'
subject = 'customer_id'
subject_id = customer_id
return render_template(subject_target,
subject = subject_id)
When I do this I get an error saying Could not build url for endpoint 'customer_add' with values ['subject']. Did you forget to specify values ['entity_id']?
Testing shows that subject_target and subject_id get substituted for fine. But subject is not substituted for because in the url_for syntax it is a parameter name and the endpoint is expecting a value for a variable named customer_id not subject.
I am hoping there is a way to say what the parameter name will be in jinja. Generalising I may want to pass a dictionary of parameters.
Ideally I could pass d = {'subject': x, ...} like this url_for(subject_action, d) and Jinja would regard this as equivalent to url_for(subject_action, subject=1234, ... when x=1234.

You can add arguments to url_for(). As per the docs:
Variable arguments that are unknown to the target endpoint are
appended to the generated URL as query arguments
so you can do
<form action = "{{ url_for(action_target,
subject_type=subject_id,
2nd_variable=2nd_variable,
etc...) }}" method = "POST">
But as wonka said you'd probably be better off sending data as POST params.
If I misunderstood and your trying to pass data from your render_template call in your view then you can do that too.
return render_template('template_name.html',
data={
"action_type": "action",
"subject_type": subject_value
})
which you can then access in your template using {{data["subject_type"]}}

The answer turns out to be obvious. Instead of evaluating url_for in the template using jinja, evaluate it in the route and pass the result to jinja via render_template.

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.

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.

Python, Flask, and jinja templates - How to iterate over a dictionary created server side

I am using flask.
On the server server when a page loads I create a dictionary.
#app.route('/edit_creative', methods=["GET", "POST"])
#login_required
def edit_creative():
if request.method == "POST":
pass
query = """select * from mystable"""
print query
rows = execute_query(query,select=True)
creative_handler={}
for row in rows:
j = row[2].strip("'")
j = json.loads(j)
creative_handler[row[1]]=j
return render_template("edit_creatives.html",title = 'Edit Creative')
On the client side I want to iterate over the hash:
{% for crid, object in creative_handler.iteritems() %}
{{ crid }}<br>
{% endfor %}
On the page I get this error
UndefinedError: 'creative_handler' is undefined
So..how do I use jinja templates to iterate over a hash creates server side?
You need to pass creative_handler to the template:
return render_template("edit_creatives.html", title='Edit Creative', creative_handler=creative_handler)
Well you need to pass in the variable(s) you want to use, in the template.
>>> from flask import render_template
>>> help(render_template)
render_template(template_name, **context)
Renders a template from the template folder with the given
context.
:param template_name: the name of the template to be rendered
:param context: the variables that should be available in the
context of the template.
so return render_template("edit_creatives.html",title = 'Edit Creative', creative_handler = creative_handler)
Try
return render_template("edit_creatives.html",title = 'Edit Creative', creative_handler = creative_handler)
If creative_handler contains your data anyway.
You have to actually pass the object to the template so it can be seen and give it a name which you then use in the template. Also FYI the code is not executed client side, it's built inside your app then sent to the client. They just see the HTML that results from the loop etc.

Categories

Resources