Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support to request.url_for for query parameters #560

Closed
cjw296 opened this issue Jun 21, 2019 · 17 comments
Closed

add support to request.url_for for query parameters #560

cjw296 opened this issue Jun 21, 2019 · 17 comments

Comments

@cjw296
Copy link
Contributor

cjw296 commented Jun 21, 2019

I totally misunderstood url_for's API, I was expecting url_for('some_route', foo='bar') to return 'http://testserver/my_route/?foo=bar'.

I understand why that is now, but what is the best way to generate urls like this?
(for pagination in a rest list api in this case).

Could url_for grow support for query parameters in addition to path parameters?

@alex-oleshkevich
Copy link
Member

Totally agree with you, it would be very convenient.

I propose to add all unmatched path params to query string.

@tomchristie
Copy link
Member

I think what we'll probably do here is have url_for return a URL instance, and add a .with_query_params method to URLs, which would allow for this kind of usage...

url = request.url_for('users', username="tom").with_query_params(...)

@cjw296
Copy link
Contributor Author

cjw296 commented Nov 10, 2019

Sounds great, but would there be a less wordy alternative?

@tomchristie
Copy link
Member

Perhaps just this...

  • request.url_for('users', username="tom").with_query(...)

Or an explicitly shorthand notation...

  • request.url_for('users', username="tom").q(...)

@cjw296
Copy link
Contributor Author

cjw296 commented Nov 11, 2019

.params() maybe?

@tomchristie
Copy link
Member

tomchristie commented Nov 11, 2019

We probably want to differentiate it more clearly from the properties on the URL class. (.params might just as well indicate a property on the class. .with_query() makes it kinda clear that it's going to return a new URL.)

I think we probably want to end up with...

  • .query_params - A QueryParams datastructure.
  • .query_string - A raw string.
  • .with_query(...) - A new URL.

@ricardogsilva
Copy link

@cjw296 @tomchristie

I see you're taking some care to think this through, so I'll be closing my open PR on this soon (been open for more than a month now) - the approach I took in the PR was more akin to the existing implementation. But I guess you want to refactor in order to provide a more proper support for the URL's query. And I'm defintely 👍 on that

@wasdee
Copy link

wasdee commented Jul 31, 2020

are there any updates?

@natanlao
Copy link

natanlao commented Jan 29, 2021

I implemented a naive workaround to this:

def url_for_query(request: Request, name: str, **params: str) -> str:
    url = request.url_for(name)
    parsed = list(urllib.parse.urlparse(url))
    parsed[4] = urllib.parse.urlencode(params)
    return urllib.parse.urlparse(parsed)

templates = Jinja2Templates(directory='templates')
templates.env.globals['url_for_query'] = url_for_query

Then, in template

{ url_for_query('foo', bar='baz') }

This solution doesn't work for routes that accept a parameter.

@setmao
Copy link

setmao commented Apr 19, 2021

I implemented a naive workaround to this:

def url_for_query(request: Request, name: str, **params: str) -> str:
    url = request.url_for(name)
    parsed = list(urllib.parse.urlparse(url))
    parsed[4] = urllib.parse.urlencode(params)
    return urllib.parse.urlparse(parsed)

templates = Jinja2Templates(directory='templates')
templates.env.globals['url_for_query'] = url_for_query

Then, in template

{ url_for_query('foo', bar='baz') }

This solution doesn't work for routes that accept a parameter.

@natanlao
Thanks for your code, it's a good solution for me!
But I found some typo:

return urllib.parse.urlparse(parsed) -> return urllib.parse.urlunparse(parsed)
{ url_for_query('foo', bar='baz') } -> {{ url_for_query(request, 'foo', bar='baz') }}

@natanlao
Copy link

natanlao commented May 6, 2021

@gopalsingh462 Are you referring to the Flask documentation?

@goodmami
Copy link

goodmami commented May 17, 2021

I think what we'll probably do here is have url_for return a URL instance, and add a .with_query_params method to URLs

I like this idea but I'm concerned that this will change the type signature of url_for in a backwards-incompatible way, as URL is not a subtype of str, the current return value type. It would be easier to place this method on URLPath, but then it would be cumbersome to write url_path_for(...).with_query(...).make_absolute_url(...).

Like @cjw296 I'm coming to this issue for pagination purposes. I want to take the request's URL and replace some offset and limit query parameters while leaving other parameters (e.g., for filtering) intact. Something like this works in the meantime (disclaimer: I haven't tested it much):

from urllib.parse import urlsplit, parse_qs, urlencode

def replace_query_params(url: str, query: dict) -> str:
    _url = urlsplit(url)
    _query = parse_qs(_url.query)
    _query.update(query)
    querystr = urlencode(_query, doseq=True)
    return _url._replace(query=querystr).geturl()

e.g.,

>>> replace_query_params('https://example.com/foo?filter=10&offset=0&limit=50', {'offset': 50})
'https://example.com/foo?filter=10&offset=50&limit=50'

@Sagaryal
Copy link

Do we have this feature yet?

@Kludex
Copy link
Member

Kludex commented Mar 10, 2023

Can we close this @aminalaee ?

@Kludex
Copy link
Member

Kludex commented Jun 20, 2023

Closing this, since we implemented #1385. Let me know if that's not enough, and we can reopen.

@Kludex Kludex closed this as completed Jun 20, 2023
@aminalaee
Copy link
Member

It is enough. Thank you

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

12 participants