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!
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, http://nedbatchelder.com