Override default values with Flask's url_for

Issue

I have a route that has two variable parts, and used defaults to specify default values for each part.

@app.route("/api/<a>/<b>", defaults={"a": 0, "b": 0})
def api(a, b):
    return f"{a=}, {b=}"

I want to build a URL that overrides b while leaving a with the default value, /api/0/.

I tried excluding a, url_for("api", b=1), but got werkzeug.routing.BuildError: Could not build url for endpoint 'api' with values ['b']. Did you forget to specify values ['a']?. It seems that I can’t exclude a variable, the default isn’t substituted in.

Given that, I then tried calling with both variables, but got werkzeug.routing.BuildError: Could not build url for endpoint 'index.API.event_average_time_in_seconds' with values ['a', 'b'].. It seems I can’t override both variables either.

How can I use defaults in my route to allow not passing values for all variables?

Solution

defaults works differently than you’re expecting. From the docs for werkzeug.routing.Rule:

`defaults`
    An optional dict with defaults for other rules with the same endpoint.
    This is a bit tricky but useful if you want to have unique URLs::

        url_map = Map([
            Rule('/all/', defaults={'page': 1}, endpoint='all_entries'),
            Rule('/all/page/<int:page>', endpoint='all_entries')
        ])

    If a user now visits ``http://example.com/all/page/1`` they will be
    redirected to ``http://example.com/all/``.  If `redirect_defaults` is
    disabled on the `Map` instance this will only affect the URL
    generation.

You use defaults to create a default URL that does not have those corresponding variables, and it would be routed to the correct endpoint with those defaults. It will not fill in or override values for a single rule. It requires both a Rule with the defaults (and no variables), and a Rule with the variables.

Here’s how you could use four rules to allow the behavior you want, overriding any combination of the variables. Typically though, you wouldn’t write the default values in the URL, you’d do what the example in the docs above show and have a default rule with no values in the URL.

@app.route("/api/0/0", defaults={"a": 0, "b": 0})
@app.route("/api/<a>/0", defaults={"b": 0})
@app.route("/api/0/<b>", defaults={"a": 0})
@app.route("/api/<a>/<b>")
def api_items(a, b):
    return f"{a=}, {b=}"
url_for("api_items")
/api/0/0

url_for("api_items", a=1)
/api/1/0

url_for("api_items", b=1)
/api/0/1

url_for("api_items", a=1, b=1)
/api/1/1

Answered By – davidism

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published