How do I use Flask's url_for in a template?

Viewed 3486

I am building a Flask application to manage character sheets for an RPG using an SQL database.

Currently, I have the following script that displays the list of a user's characters currently in the database.

@app.route("/<cha_name>")
@login_required
def character_sheet():
    characters = db.execute(
        """SELECT
          *
        FROM
            characters
        WHERE
            user_id = :user_id AND
            name = :cha_name
        """,
        user_id=session["user_id"],
        cha_name="???",
    )

    if not characters:
        return render_template("add_char.html")

I would like to include a button that navigates to the character sheet for the specic chosen character. So the page below would detail some of the stats for the character, and then a button would take the user to a populated character sheet on another page.

This is what I have so far for displaying a specific user's characters.

{% extends "main.html" %}

{% block title %}
    Characters
{% endblock %}

{% block main %}
      <table border = 1>
         <thead>
            <td>Player</td>
            <td>Name</td>
            <td>Race</td>
            <td>Class</td>
            <td>Level</td>
            <td>Status</td>
            <td>View</td>
         </thead>

         {% for row in rows %}
            <tr>
               <td>{{ character["user"] }}</td>
               <td>{{ character["name"] }}</td>
               <td>{{ character["race"] }}</td>
               <td>{{ character["cha_class"] }}</td>
               <td>{{ character["level"] }}</td>
               <td>{{ character["status"] }}</td>
               <td><a href={{ url_for('cha_name') }}>View Sheet</a></td> <!-- HERE -->
            </tr>
         {% endfor %}

      </table>
      <a href = "/add_char">Add a new Character</a>
      <a href = "/">Go back to home page</a>

   </body>
{% endblock %}

What do I use on the line with <!-- HERE --> to make a link to the character sheet URL?

1 Answers

Whew, there's a lot to unpack here!

Your example is/was invalid, e.g. cha_name= on the end of the db.execute() call, and there are no calls to render_template if a character is found, so it'll never produce a response even with a valid request.

Next, you've told Flask to pass a cha_name parameter to your character_sheet function, but haven't defined a parameter on the function itself.

Finally, in your template you're passing cha_name to the url_for function, which (so far as we can see from the sample code) isn't a route that you've defined, so can't work.

You need to include more information for us to help, such as telling us what error you're seeing. Right now, I imagine the Flask service won't even start due to the syntax error. Once that's fixed, I'd expect to see something like the following:

werkzeug.routing.BuildError: Could not build url for endpoint 'cha_name'. Did you mean 'character_sheet' instead?

I'd suggest you head back to the documentation on URL building and also look at the docs for the route decorator. You'll see on the latter that "Flask itself assumes the name of the view function as endpoint". That means that in order to get Flask to generate a URL for your character_sheet function, you'll need to pass that name to url_for, and then the parameters, like this:

url_for('character_sheet', cha_name=character.name)

If a user were to rename their character, all of the URLs would change, which is a bad user experience — what would happen if they'd bookmarked a particular character, then fixed a typo in the name?

Putting this all together, here's a hopefully-better example:

# app.py
@login_required
@app.route("/<character_id>")
def character_sheet(character_id):
    characters = db.execute(
        """SELECT
          *
        FROM
            characters
        WHERE
            id = :id AND
            user_id = :user_id
        """,
        id=character_id,
        user_id=session["user_id"],
    )

    if not characters:
        return abort(404)

    return render_template(
        "characters/sheet.html",
        character=characters[0],
    )
<!-- templates/characters/list.html -->
{% extends "main.html" %}

{% block title %}
    Characters
{% endblock %}

{% block main %}
    <table>
         {% for character in characters %}
            <tr>
               <td>{{ character.name }}</td>
               <td><a href={{ url_for('character_sheet', character_id=character.id) }}>View Sheet</a></td>
            </tr>
         {% endfor %}
    </table>
    <a href = "{{ url_for('add_char') }}">Add a new Character</a>
{% endblock %}
Related