Django Su
It's always a sign of good design when adding a new feature turns out to be easy.
In the
Django
authentication system, I
wanted a way for an administrator to view the site as if they were
a particular user; effectively an equivalent to
su
in UNIX-land.
The first easy step was to invent a URL to correspond to this action, which gets
encoded in
urls.py
:
(r'^su/(?P<username>.*)/$', 'qlockweb.accounts.views.su', {'redirect_url': '/qlockdata/'}),
That done, the second and final step is to write some view code.
@user_passes_test(lambda u: u.is_staff) def su(request, username, redirect_url='/'): su_user = get_object_or_404(User, username=username) if su_user.is_active: request.session[SESSION_KEY] = su_user.id return HttpResponseRedirect(redirect_url)
Seven lines of code and we're done (modulo a bunch of import
statements).
Expanding what's going on here:
(r'^su/(?P<username>.*)/$', 'qlockweb.accounts.views.su', {'redirect_url': '/qlockdata/'}),
urls.py
line above; the /(?P<username>.*)/
part of the regexp pulls out "fred" and this gets
passed as a parameter named username
into the function su
in qlockweb/accounts/views.py
. This function also gets passed a parameter
called redirect_url
with value '/qlockdata/'
.@user_passes_test(lambda u: u.is_staff)
su
function. The
line before the function definition is a Python
decorator: some extra code wrapping the function that gets
executed just before the function itself is executed. This decorator needs some expansion of its own:
@user_passes_test(lambda u: u.is_staff)
@user_passes_test(lambda u: u.is_staff)
contrib/auth/decorators.py
); its first
argument test_func
is a function that does the test. This test function is given
a single parameter: the current User
. If the test function returns
true, the wrapped view code is called; if not, then the user gets redirected to a login page.
@user_passes_test(lambda u: u.is_staff)
is_this_a_staff_user(u)
that
checks whether its argument u
is an administrator. However, as this is the
only place that the function is used, we don't bother to give it a name—we just use
a lambda expression
to give the definition right here and now.@user_passes_test(lambda u: u.is_staff)
User
class
that indicates whether the user is an administrator or not.def su(request, username, redirect_url='/'):
su
function itself, and if we've got this far we're guaranteed that the person viewing the
page is logged in as an is_staff
user. The function has the username
and redirect_url
parameters mentioned earlier; it also has a request
parameter that holds all of the information about the original web request (in a
HttpRequest
object).su_user = get_object_or_404(User, username=username)
User
object for the username that was
specified—fred
in other words. If there isn't a user called fred
, then
a Http404
exception gets raised, which will percolate up the stack and display a (surprise,
surprise) 404 page.if su_user.is_active:
is_active
field in the standard User
model.request.session[SESSION_KEY] = su_user.id
return HttpResponseRedirect(redirect_url)
redirect_url
page.(Statutory disclaimer: I am not a security expert, nor do I play one on TV. Adding this to a production system is probably not a good idea.)
Another Django snippet: I finally
discovered
that the follow
argument to the standard
Manipulators
allows you to list fields in the model that the form should leave untouched. Very helpful: the
end result is more compact
and less brittle than the code I'd put together to manually override all of the hidden fields.
[A:37385 B:3278 C:346 D:9187 E:62544 Total:112740]
3 comments:
You could probably add another view to switch back to the original user keeping the original user name in the session.
def su(...):
...
if user.is_active:
# add the current user name to the session
...
def su_exit(request):
# if original user name in the session, log in as that user
Just an idea :)
I have just wrapped all of this up into an egg here:
https://github.com/continuous/django-su
I have implemented the above comment, and removed the need for an authentication backend. I hope it helps someone :)
While I click a user in admin, sometimes I got the following "bad character in group name" error, but not always.
Django Version: 1.4
Exception Type: error
Exception Value:
bad character in group name
Exception Location: /usr/lib/python2.6/re.py in _compile, line 245
Python Executable: /usr/bin/python
Python Version: 2.6.4
Is this something related to django-su?
Thank you if you can help.
Post a Comment