python/Flask: Route with dynamic first component

2020-06-30 03:21发布

问题:

I am writing a Flask site for which I would like to have routes like this:

@app.route('/')
@app.route('/<page_id>')
@app.route('/<page_id>/<subpage_id>')
def page(page_id=None, subpage_id=None):
    ...

While it seems like this should work in theory, it looks like this actually breaks static resources located in the root static/ directory. I assume the reason for this is that my dynamic route actually matches 'static/style.css' and thus overrides the normal handler for static files.

Is there any way around this? Is there a 'static' handler I can forward the request to if I detect that page_id=='static'?

Edit: Here is a working sample

@app.route('/<page_id>/<subpage_id>/<subsubpage_id>')
def xxx(page_id=None, subpage_id=None, subsubpage_id=None):
    return 'hello'

If you open http://127.0.0.1:5000/static/css/style.css now you should get a 'hello' instead of the file.

回答1:

Regarding the root of your problem:

Yes. I have Page objects in my database, which I load and display based on the page_id/subpage_id/subsubpage_id when the method is called. Is there a better way to do this? I was thinking of adding separate routes for each of the pages when the app is initialized, but I could not find a good way of making that work in conjunction with url_for.

You can register route handlers directly by using app.add_url_rule. It will use the function's name for url_for by default, yes, but you can override that by passing an endpoint argument.

So maybe you'd have something like this:

from functools import partial

def actual_handler(page):
    return u'hello'

for page in session.query(Page):
    route = u'/{0}/{1}/{2}'.format(page.id1, page.id2, page.id3)
    app.add_url_rule(
        route,
        page.name,  # this is the name used for url_for
        partial(actual_handler, page=page),
    )

Getting the session may or may not be tricky, and may or may not involve work like manually calling session.remove(); I haven't tried using SQLAlchemy outside a Flask handler before. Assuming you're using SQLA in the first place.

Also see the documentation on route handling.

As for the original question of routes taking priority over static files, I genuinely don't know; based on my reading of the Flask and Werkzeug docs, that shouldn't happen. If you still wish to solve this by manually serving static files, perhaps send_from_directory will help. Presumably your web server will serve static files directly in production, anyway, so it might not be worth the metaprogramming gunk above.

PS: An afterthought; Pyramid's traversal might be a better fit if your entire site lives in a database. It examines path components one at a time dynamically, rather than having a fixed list of static routes.



回答2:

This is a horrible hack but you could probably just do something akin to:

@app.route('/<page_id>/<subpage_id>/<subsubpage_id>')
def xxx(page_id=None, subpage_id=None, subsubpage_id=None):
    if page_id == 'static':  # or whatever the path is to your assets
       # make a response where you've filled the request yourself
    return 'hello'