0

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.

Nils
  • 1,936
  • 3
  • 27
  • 42
  • If those variables are guaranteed to be ints, you can specify that in the route and that should help you. Otherwise, I'm not sure why this would be happening. – Ian Stapleton Cordasco Jan 15 '13 at 19:46
  • They are not ints unfortunately. I have just added a sample, if you insert that into the usual Flask skeleton the URL to the CSS should no longer work. – Nils Jan 15 '13 at 20:13
  • 2
    are you literally capturing `///`? what's the point of using routing if you have one function do everything? – Eevee Jan 15 '13 at 21:33
  • 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. – Nils Jan 15 '13 at 22:07

2 Answers2

5

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.

Eevee
  • 47,412
  • 11
  • 95
  • 127
  • I think I got this to work, thank you! I don't understand why I need to get the session at all though. Why can't I just do `db = SQLAlchemy(app)` and then `for page in Page.query.all(): ...`? – Nils Jan 16 '13 at 23:51
  • you don't, really; `Page.query` is a shortcut for `session.query(Page)`. i just like to use the vanilla SQLA approach as much as possible; it doesn't make sense to me that a table could query itself :) – Eevee Jan 16 '13 at 23:54
1

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'
Ian Stapleton Cordasco
  • 26,944
  • 4
  • 67
  • 72
  • Yes, as I mentioned in my original post I could do this if I knew what the default handler for static resource requests was, so that I could forward the request correcly. Do you know? – Nils Jan 16 '13 at 19:41