views:

658

answers:

4

In my Django app, I need to start running a few periodic background jobs when a user logs in and stop running them when the user logs out, so I am looking for an elegant way to

  1. get notified of a user login/logout
  2. query user login status

From my perspective, the ideal solution would be

  1. a signal sent by each django.contrib.auth.views.login and ... views.logout
  2. a method django.contrib.auth.models.User.is_logged_in(), analogous to ... User.is_active() or ... User.is_authenticated()

Django 1.1.1 does not have that and I am reluctant to patch the source and add it (not sure how to do that, anyway).

As a temporary solution, I have added an is_logged_in boolean field to the UserProfile model which is cleared by default, is set the first time the user hits the landing page (defined by LOGIN_REDIRECT_URL = '/') and is queried in subsequent requests. I added it to UserProfile, so I don't have to derive from and customize the builtin User model for that purpose only.

I don't like this solution. If the user explicitely clicks the logout button, I can clear the flag, but most of the time, users just leave the page or close the browser; clearing the flag in these cases does not seem straight forward to me. Besides (that's rather data model clarity nitpicking, though), is_logged_in does not belong in the UserProfile, but in the User model.

Can anyone think of alternate approaches ?

+9  A: 

One option might be to wrap Django's login/logout views with your own. For example:

from django.contib.auth.views import login, logout

def my_login(request):
    response = login(request)
    #fire a signal, or equivalent
    return response

def my_logout(request):
    #fire a signal, or equivalent
    return logout(request)

You then use these views in your code rather than Django's, and voila.

With regards to querying login status, it's pretty simple if you have access to the request object; simply check request's user attribute to see if they're a registered user or the anonymous user, and bingo. To quote the Django documentation:

if request.user.is_authenticated():
    # Do something for logged-in users.
else:
    # Do something for anonymous users.

If you don't have access to the request object, then determining if the current user is logged in is going to be difficult.

Edit:

Unfortunately, you'll never be able to get User.is_logged_in() functionality - it's a limitation of the HTTP protocol. If you make a few assumptions, however, you might be able to get close to what you want.

First, why can't you get that functionality? Well, you can't tell the difference between someone closing the browser, or someone spending a while on a page before fetching a new one. There's no way to tell over HTTP when someone actually leaves the site or closes the browser.

So you have two options here that aren't perfect:

  1. Use Javascript's unload event to catch when a user is leaving a page. You'd have to write some careful logic to make sure you aren't logging out a user when they're still navigating your site, however.
  2. Fire the logout signal whenever a user logs in, just to be sure. Also create a cron job that runs fairly often to flush out expired sessions -- when an expired session is deleted, check that the session's user (if it's not anonymous) has no more active sessions, in which case you fire the logout signal.

These solutions are messy and not ideal, but they're the best you can do, unfortunately.

ShZ
This still doesn't address the "most of the time, users just leave the page or close the browser" situation.
Joel L
Thanks for your answer, of course wrapping login/logout saves me from patching the source, should have though of that myself.A little correction, though: If the signal is sent in my_login before login is called, the user is still anonymous in the signal handler. Better (don't think this will be formatted properly):def my_login(request): response = login(request) #fire a signal, or equivalent return responseAlso, I am using is_authenticated already, I just had a feeling I would need more than that. So far, though, my new is_logged_in piece in the data model sits there unused.
ssc
Good points all around. I guess I didn't really address the `is_logged_in` stuff very well at all (apologies there, I guess I didn't do a great job of reading the post), but I've updated the answer to offer what help I can in that area. It's a bit of an impossible problem, unfortunately.
ShZ
A: 

Rough idea - you could use middleware for this. This middleware could process requests and fire signal when relevant URL is requested. It could also process responses and fire signal when given action actually succeded.

Tomasz Zielinski
A: 

The only reliable way (that also detects when the user has closed the browser) is to update some last_request field every time the user loads a page.

You could also have a periodic AJAX request that pings the server every x minutes if the user has a page open.

Then have a single background job that gets a list of recent users, create jobs for them, and clear the jobs for users not present in that list.

Joel L
+1  A: 

Inferring logout, as opposed to having them explicitly click a button (which nobody does), means picking an amount of idle time that equates to "logged out". phpMyAdmin uses a default of 15 minutes, some banking sites use as little as 5 minutes.

The simplest way of implementing this would be to change the cookie-lifetime. You can do this for your entire site by specifying settings.SESSION_COOKIE_AGE. Alternatively, you could change it on a per-user basis (based on some arbitrary set of criteria) by using HttpResponse.setcookie(). You can centralize this code by creating your own version of render_to_response() and having it set the lifetime on each response.

Peter Rowell