how to use Flask Jinja2 url_for with multiple parameters - python

I have a problem while using jinja2 url_for() function.
I have a route like this:
#app.route('/article/<int:article_id>/<url_title>/', methods=['GET'])
def article_page(article_id, url_title):
article = Article.query.get(article_id)
if article == None:
abort(404)
return render_template('article.html', article=article)
in jinja template file,i want to create a url which links to article_page,so i write like this:
<h5>
{{ article.title }}
</h5>
but when I run this page, I get a error:
raise BuildError(endpoint, values, method)
BuildError: ('article_page', {'article_id': 1}, None)
It seems like that the second parameter url_title missing.
How can I use url_for() with multiple parameters correctly?

According to url_for documentation:
If the value of a query argument is None, the whole pair is skipped.
Make sure that url_title is not None.
Or specify default value for url_title in the article_page function.

You missed a dot, try that:
url_for('.article_page', article_id=article.id, url_title=article.url_title)

Related

How can I use url_for in jinja?

I have a issue during using url_for in jinja template. I use like :
href="{{ url_for('user',user_id=current_user.id)}}"
when I write href="user/{{current_user.id}}" it works perfectly ::
and my view function is :
#app.route('/user/<user_id>')
def get_user(user_id):
user = Users.query.filter_by(id = user_id).first()
return render_template("profile.html", user = user)
but my jinja cant see this url and has an errors
werkzeug.routing.exceptions.BuildError: Could not build url for endpoint 'user' with values ['user_id']. Did you mean 'get_user' instead
As the error message suggested, you have to provide the name of the view function to url_for():
href="{{ url_for('get_user', user_id=current_user.id)}}"

How to correctly call url_for and specify path parameters?

This is the function in views.py:
#application.route("/students/<int:id>", methods=["GET", "POST"])
def students(id):
return render_template("students.html")
I have used this code on my HTML page:
{{ student.id }}
But url_for is generating this:
http://127.0.0.1:5000/students?id=1
I want to generate this:
http://127.0.0.1:5000/students/1
Note that I have another function which was written before that first function:
#application.route("/students")
def students():
return render_template("students.html", students=Student.query.all())
I think url_for is using this route.
The problem is that you have 2 endpoints that have almost the same pattern:
/students
/students/<int:id>
It then becomes a matter of in what order are those endpoints defined, because the call url_for('students', id=student.id) can correctly match on both:
If it matches /students first, as explained in url_for docs: "Variable arguments that are unknown to the target endpoint are appended to the generated URL as query arguments.". Since id=1234 is not a known parameter on this endpoint, it becomes a query parameter ?id=1234.
If it matches /students/<int:id> first, then you get your expected behavior of /students/1234
Relying on the order of the endpoints is a brittle and error-prone strategy, so if you think you should just reorder the endpoint definitions, no, that's not the way to go.
What I recommend instead are these options:
Rename the functions to help differentiate url_for (and readers of your code) that one endpoint is for getting all the students, and the other one is for accesing just one student by id. Then explicitly call the correct one in url_for:
#application.route("/students/<int:id>", methods=["GET", "POST"])
def student_by_id(id):
return {"student": id}
#application.route("/students")
def all_students():
return {"students": "all"}
{{ student.id }}
Similar to option 1, but instead of renaming the functions, pass-in an endpoint=... keyword parameter to the route(...) decorator to specify different endpoint names. See the docs for the route decorator and the section on URL Route Registrations for more details. By default, "The endpoint name for the route defaults to the name of the view function if the endpoint parameter isn’t passed.". So here, name the endpoint that accepts an id as "student_by_id" and explicitly use that in url_for.
#application.route(
"/students/<int:id>",
methods=["GET", "POST"],
endpoint="student_by_id", # <---------
)
def students(id):
return {"student": id}
#application.route("/students")
def students():
return {"students": "all"}
{{ student.id }}
Combine both into 1 endpoint. Then the endpoint function should first check if id was specified. If it was, then handle processing for just 1 student. Else, handle processing for all students. The url_for call doesn't need to be changed.
#application.route("/students")
#application.route("/students/<int:id>", methods=["GET", "POST"])
def students(id=None):
if id:
return {"student": id}
else:
return {"students": "all"}
{{ student.id }}
Pick one which works best for you.
As a side note, as mentioned in the comments, I also recommend not using id as a parameter or variable name, because you might accidentally shadow the built-in id() function. It might be actually be more readable to change it to student_id.

Django template tag to insert or replace URL parameter

Is anyone aware of a Django template tag that takes the current path and query string and inserts or replaces a query string value?
e.g. given a request to /some/custom/path?q=how+now+brown+cow&page=3&filter=person
The call {% urlparam 'page' 4 %} would generate /some/custom/path?q=how+now+brown+cow&page=4&filter=person.
This wouldn't be too difficult to write from scratch, but as this seems like a very common task, I would expect a tag like this to be built-in. However, after reading through the docs and googling, I can't seem to find anyone's who's publicized such a tag.
Since I haven't used these tools by my own, I'll just refer you:
django-url-tools has url_params template tag that seems to do what you want
django-more-template-tags-and-filters has some helper tags for manipulating urls and parameters
FYI, I've personally used jsurl library for this kind of url manipulations in javascript.
Hope that helps.
Here's how I did it on a Django 1.3 project. Like you I expected to find this built in, but ended up coding it in the view:
def remove_url_param(url, params):
if not isinstance(params, list):
params = [params,]
if isinstance(url, unicode):
# urlencode doesn't like unicode
url = url.encode('utf8')
(scheme, netloc, path, query, fragment) = urlparse.urlsplit(url)
param_dict = parse_qs(query)
for p in params:
try:
del(param_dict[p])
except KeyError:
pass
query = urllib.urlencode(param_dict, True)
return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
Then I used this to create base URLs:
page_url_unordered = putils.remove_url_param( request.get_full_path(), ['order', 'ajax'] )
Then in the template:
Price
Size
I want to render a series of pagination links on a search page. The
URL contains several query parameters (like in my example). The
pagination code that renders the pagination links shouldn't have to be
explicitly given all these parameters. Django's admin seems to have
this behavior everywhere.
This is enabled by adding django.core.context_processors.request to TEMPLATE_CONTEXT_PROCESSORS (its not enabled by default). This will add a request variable to your templates, which is the HttpRequest object.
From there, you can use {{ request.get_full_path }} to get the current URL with the complete query string, and then append your custom query to it.
If your page is /search?q=foo+bar, and you want a new link to be /search?q=foo+bar&page=4, page 4.

Pass variables to Flask's render_template

I want to pass multiple variables from my Flask view to my Jinja template. Right now, I can only pass one. How do I pass multiple variable when rendering a template?
#app.route("/user/<user_id>/post/<post_id>", methods=["GET", "POST"])
def im_research(user_id, post_id):
user = mongo.db.Users.find_one_or_404({'ticker': user_id})
return render_template('post.html', user=user)
The render_template function takes any number of keyword arguments. Query for each of the things you need in the template, then pass the results of each query as another argument to render_template.
#app.route("/user/<user_id>/post/<post_id>")
def im_research(user_id, post_id):
user = get_user_by_id(id)
post = get_user_post_by_id(user, id)
return render_template("post.html", user=user, post=post)
Python also has a built-in locals() function that will return a dict of all locally defined variables. This is not recommended as it may pass too much and obscures what specifically is being passed.
#app.route("/user/<user_id>/post/<post_id>")
def im_research(user_id, post_id):
user = get_user_by_id(id)
post = get_user_post_by_id(user, id)
return render_template("post.html", **locals())
return render_template('im.html', user= None, content = xxx, timestamp = xxx)
You can pass as many variables as you need.
The api
excerpt:
flask.render_template(template_name_or_list, **context)
Renders a
template from the template folder with the given context.
Parameters: template_name_or_list – the name of the template to be
rendered, or an iterable with template names the first one existing
will be rendered context – the variables that should be available in
the context of the template.
It is also possible to pass a list to render_template's context variables, and refer to its elements with Jinja's syntax in HTML.
example.py
mylist = [user, content, timestamp]
return render_template('exemple.html', mylist=l)
exemple.html
...
<body>
{% for e in mylist %}
{{e}}
{% endfor %}
</body>
...

how to pass id from url to post_save_redirect in urls.py

I have several objects in the database. Url to edit an object using the generic view looks like site.com/cases/edit/123/ where 123 is an id of the particular object. Consider the cases/url.py contents:
url(r'edit/(?P<object_id>\d{1,5})/$', update_object, { ... 'post_save_redirect': ???}, name = 'cases_edit'),
where update_object is a generic view. How to construct the post_save_redirect to point to site.com/cases/edit/123/. My problem is, that I don't know how to pass the id of the object to redirect function. I tried something like:
'post_save_redirect': 'edit/(?P<object_id>\d{1,5})/'
'post_save_redirect': 'edit/' + str(object_id) + '/'
but obviously none of these work. reverse function was suggested, but how to pass the particular id?
'post_save_redirect': reverse('cases_edit', kwargs = {'object_id': ???})
{% url %} in the temple also requires passing the id of the particular object. The id can be passed via extra_context:
extra_context = {'object_id': ???}
In all the cases the problem is to get object_id from the url.
regards
chriss
In short what you need to do is wrap the update_object function.
def update_object_wrapper(request, object_id, *args, **kwargs):
redirect_to = reverse('your object edit url name', object_id)
return update_object(request, object_id, post_save_redirect=redirect_to, *args, **kwargs)
First, read up on the reverse function.
Second, read up on the {% url %} tag.
You use the reverse function in a view to generate the expected redirect location.
Also, you should be using the {% url %} tag in your templates.
Right from the docs at: https://docs.djangoproject.com/en/dev/ref/generic-views/#django-views-generic-create-update-create-object
post_save_redirect may contain dictionary string formatting, which will be interpolated against the object's field attributes. For example, you could use post_save_redirect="/polls/%(slug)s/".

Categories