Using routes.py as a dispatcher for URL shortener app

251 views
Skip to first unread message

Simon Bushell

unread,
Mar 29, 2012, 12:40:16 PM3/29/12
to web...@googlegroups.com
hi all,

So I am coding a little app that acts as a sort of url shortener (like bit.ly). 

Visiting myapp.com/tcgata will search my database for an URL linked to 'tcgata'

My reading of the documentation suggests that this can be achieved using pattern-based URL rewriting in routes.py. As such I made the following file:

routes_in = (
   
('.*[a-zA-Z0-9]{6}', '/catchAll'),
)

#in default.py
def catchAll():pass


where catchAll is a controller function that will glean the search string ('tcgata') from the request object and redirect to the relevant URL (or 404 if not found). Currently it is a passed function.

However, the redirection doesn't seem to be working for me. When I visit myapp.com/tcgata i get an error page saying 'invalid function (default/tcgata)'

Where am I going wrong? Is it a routing issue? a regex issue? I am sure it is something simple I am missing, but I think I need some extra eyes to find it.

Furthermore, if there is an easier way to do what I am seeing to achieve, I am all ears!

thanks in advance

Simon



rdodev

unread,
Mar 29, 2012, 1:25:48 PM3/29/12
to web...@googlegroups.com
Having recently completed my own personal URL shortener app on web2py, the trick was using pattern based routes at the root level. Actually, even for a fairly complex work-related app we had to use the root-level routes.py as well. Specially with an URL shortener, you want to have the URL as small as possible (else it sorta defeats the purpose), so you are going to need something along the lines of:

routes_in = (
             (r'^/?$', r'/app_name/default/index'),
             (r'^/(?P<url>[^/]*)/?$', r'/app_name/default/index/\g<url>'),           
             )


in your root-level routes.py The drawback is that you will lose access to all other apps (including admin) but that can be a good thing for public deployments.

Anthony

unread,
Mar 29, 2012, 1:40:09 PM3/29/12
to web...@googlegroups.com
routes_in = (
             (r'^/?$', r'/app_name/default/index'),
             (r'^/(?P<url>[^/]*)/?$', r'/app_name/default/index/\g<url>'),           
             )


in your root-level routes.py The drawback is that you will lose access to all other apps (including admin) but that can be a good thing for public deployments.

You can catch the other apps by adding route patterns that match them before your catch-all pattern (the patterns are processed in order, and the first match is used). Anyway, using the parametric router and specifying the url-shortening app as the default app might be simpler:

routers = dict(
    BASE
= dict(
        default_application
= 'shortener',
        default_controller
= 'default',
        default_function
= 'index',
   
),
)

Then http://myapp.com/tcgata will get routed to http://myapp.com/shortener/default/index/tcgata, and "tcgata" will be available to the index() function in request.args(0).

Anthony

Ruben Orduz

unread,
Mar 29, 2012, 1:53:02 PM3/29/12
to web...@googlegroups.com
Good point, Anthony. I forgot they are evaluated in order. So you can
declare them explicitly before the generic pattern and achieve the
same result. For bonus points, OP, you should make it RESTful like I
did with mine and then heavily leverage the default layout :) Mine's
currently up at the super cliché url http://rdzr.co :)

Simon Bushell

unread,
Mar 29, 2012, 2:33:01 PM3/29/12
to web...@googlegroups.com
This is a neat solution Anthony (actually, it was my original idea for solving this). however I seem to be getting the same error: invalid function (default/tcgata)

Forgive me, is this code in the root-level routes.py? or a routes.py in applications/shortener

Should anything else be in routes.py? 

S

Anthony

unread,
Mar 29, 2012, 3:38:05 PM3/29/12
to web...@googlegroups.com
This is a neat solution Anthony (actually, it was my original idea for solving this). however I seem to be getting the same error: invalid function (default/tcgata)

Forgive me, is this code in the root-level routes.py? or a routes.py in applications/shortener

Should anything else be in routes.py?

It should be in the root level routes.py, and nothing else is required in that file. Note, whenever you change routes.py, you have to reload routes, either by restarting the server or by clicking the "Reload routes" button on the home page of the admin app (or by visiting /admin/default/reload_routes).

Anthony
 

Simon Bushell

unread,
Mar 29, 2012, 6:05:10 PM3/29/12
to web...@googlegroups.com
Hmm, this is bizarre. I was aware of the reloading routes and it is still not working. I even made a blank 'shortener' scaffold app and added the routes.py from above at root level.

http://127.0.0.1/jujuju gives the same errors as above. 

I'll muck about a bit more and see whats going wrong. I must be missing something daft. 

Jonathan Lundell

unread,
Mar 29, 2012, 8:34:19 PM3/29/12
to web...@googlegroups.com
On Mar 29, 2012, at 11:33 AM, Simon Bushell wrote:
> This is a neat solution Anthony (actually, it was my original idea for solving this). however I seem to be getting the same error: invalid function (default/tcgata).
>
> Forgive me, is this code in the root-level routes.py? or a routes.py in applications/shortener?
>
> Should anything else be in routes.py?

If I'm remembering this correctly, you want something like this (root level is fine):

routers = dict(
BASE = dict(
default_application = 'shortener',

),
shortener = dict(


default_controller = 'default',
default_function = 'index',

functions = ['index', 'user', 'download', 'call'],
),
)

...where the functions list is a complete list of the visible functions in the default controller (that is, any function that can appear in a URL).

The router needs that list so it can distinguish function names from args, and can then omit 'index'. Since in your example tcgata is not in the functions list, it can be safely treated as args[0].

Anthony

unread,
Mar 29, 2012, 10:01:58 PM3/29/12
to web...@googlegroups.com
Thanks for the correction. I guess my version allows you to eliminate the app, controller, and function, but doesn't allow anything else in the url in that case.

Anthony

Simon Bushell

unread,
Mar 30, 2012, 4:58:59 AM3/30/12
to web...@googlegroups.com
Many thanks Jonathan. That did the trick.

Thank you  to everyone who helped out

S

Jonathan Lundell

unread,
Mar 30, 2012, 9:54:03 AM3/30/12
to web...@googlegroups.com
On Mar 30, 2012, at 1:58 AM, Simon Bushell wrote:
> Many thanks Jonathan. That did the trick.

Glad to hear it. Now that you've tested it, some color commentary for router users.

> routers = dict(
> BASE = dict(
> default_application = 'shortener',
> ),

BASE might better be named GLOBAL. It takes the default routing dict (see router.example.py) and modifies them as specified. In this case, we want to specify the default application, which needs to be done globally, at the base level.

> shortener = dict(

Following BASE, we specify routing parameters for specific apps. The effective router for any give app is the default router dictionary, updated by the BASE dictionary, updated by the app's dictionary (if any).

> default_controller = 'default',
> default_function = 'index',

These are actually the default values; they're only here for documentation, and aren't strictly necessary.

> functions = ['index', 'user', 'download', 'call'],

By default, the router scans the app's controllers directory to get the controller names, but it relies on the router dict for the function names. It wants them because it wants to map:

http://domain.com/foobar -> /shortener/default/index/foobar
http://domain.com/user -> /shortener/default/user

It does that by looking at the function candidates (foobar and user, above) and seeing whether they're in the functions list.

In outgoing URL() mapping, it uses the list to safely omit elements. So if args[0] is 'foobar', it can omit a/c/f safely. But suppose the function is 'index', but args[0] is 'user', which collides with a known function name. Now it knows that it can't safely omit the function name, and the outgoing URL becomes '/index/user'.

If you *don't* specify the functions list, then the outgoing URL drops the default function name (here 'index') only if args is empty.

> ),
> )

Reply all
Reply to author
Forward
0 new messages