URL dispatcher with filter

2 views
Skip to first unread message

tt

unread,
Aug 26, 2007, 2:25:05 PM8/26/07
to Django users
I have a unique situation that the traditional urls.py config does not
handle very well.

Imagine I have URL patterns like this:
/c/(?P<cid>\d+)/foo
/c/(?P<cid>\d+)/bar
/c/(?P<cid>\d+)/abc
...

I'm using Django authentication to ensure the user is logged in. But I
have to do a custom check to ensure if that each logged-in user has
access to a specific cid object (each user can see exactly one cid,
and not allowed to see another cid).

What I currently have to do is to define each view (such as to handle
"foo" above) as:

@login_required
def foo(request, cid):
c = get_object_or_404(C, pk=cid)
if user_can_access_c(request.user, c):
# do something that's allowed
# ...
else:
# not allowed; act as if user got the wrong url
raise Http404

... and then I have to repeat this above codes for "bar" and "abc"
views. This feels rather excessive for me. Going with the don't-repeat-
yourself principle, I'd rather have a single place that does this
instead of sprinkling this codes for every view under /c/(?P<cid>\d
+)/...

Any pointer you can give me would be greatly appreciated.
Thanks!

Tim Chase

unread,
Aug 26, 2007, 5:34:40 PM8/26/07
to django...@googlegroups.com
> What I currently have to do is to define each view (such as to handle
> "foo" above) as:
>
> @login_required
> def foo(request, cid):
> c = get_object_or_404(C, pk=cid)
> if user_can_access_c(request.user, c):
> # do something that's allowed
> # ...
> else:
> # not allowed; act as if user got the wrong url
> raise Http404
>
> ... and then I have to repeat this above codes for "bar" and "abc"
> views. This feels rather excessive for me. Going with the don't-repeat-
> yourself principle, I'd rather have a single place that does this
> instead of sprinkling this codes for every view under /c/(?P<cid>\d
> +)/...
>
> Any pointer you can give me would be greatly appreciated.

There are at least two suggestions that I can think of:

In a similar situation, I found that the easiest way for me was
to add a "get_allowable_objects_for_user" sort of method to the
class in question:

class C(Model):
# class definition
def get_allowed(self, user):
# complex logic here
if problems:
raise SomeException
return C.objects.filter(...)

This may be overkill as it still requires some checking in each
view function. However, for more complex access-control schemes,
this can be useful to prevent awkward syntax from propagating
across your views.py.

Alternatively, you could use a double-dispatch sort of pattern.
Your urls.py could pass in that second part

/c/(?P<cid>\d+)/(P<whatever>foo|bar|abc) -> dispatch

and then your view(s) would look something like:


def foo(request, cid, c):
# do something foo'ish with c
def bar(request, cid, c):
# do something bar'ish with c
def abc(request, cid, c):
# do something abc'ish with c

action_mapping = {
'foo': foo,
'bar': bar,
'abc': abc,
}

@login_required
def dispatch(request, cid, whatever):
c = get_object_or_404(C, pk=cid)

if user_can_access_c(request.user, c):
action_mapping[whatever](request, cid, c)


else:
# not allowed; act as if user got the wrong url
raise Http404


That action_mapping bit can be cleaned up some if you keep a
tight reign on your regexp for the definition of "whatever". In
the above case, because we only explicitly allow "foo|bar|abc",
it can be rewritten simply as

if user_can_access_c(request.user, c):
eval(whatever)(request, cid, c)
else:
raise Http404

without the need for the action_mapping.

Just a couple ideas for DRY'ing the code.

-tim


Ned Batchelder

unread,
Aug 26, 2007, 8:11:02 PM8/26/07
to django...@googlegroups.com
I would think the simplest thing to do is combine the get_object_or_404 and the user_can_access_c calls:

def get_allowed_c(request, cid):

    c = get_object_or_404(C, pk=cid)
    if user_can_access_c(request.user, c):
        return c

    else:
        # not allowed; act as if user got the wrong url
        raise Http404

@login_required
def foo(request, cid):
    c = get_allowed_c(request, cid)
    # Do whatever you want with c...

This means that each view function has a single line in common with the others, just as it used to before your access checking.

--Ned.
-- 
Ned Batchelder, http://nedbatchelder.com
Reply all
Reply to author
Forward
0 new messages