Get Online Users in Django

I was upgrading my Django Bookmarks site, and I wanted to list the online users. I searched the internet and I found a simple method, but it doesn’t work properly. So I developed my own method.

Why the simple method didn’t work?

The problem with it is that it depends on the last login date in the user model. This field is updated on every login for the user.

Imagine the following scenario:
* The user X logs in @ 1:00 pm.
* The user X posts a new post @ 1:30 pm.
* The user X comments on a previous post @ 1:35 pm.
* The user X leaves the website @ 1:40 pm.
* The user X comes back to the website @ 3:00 pm.
* The user X is logged in since the session cookie has not expired yet.
* The user X posts a new post @ 3:15 pm.
* A guest enters the website, and looks at the online users corner.

The guest won’t see X’s name in the list of online users, simply because X’s last login date is @ 1 pm, even though user X is now online and he has just posted a new post.

UPDATE 8th May 2013

Please note the solution provided in this post is an old solution, it is coded in a weird way maybe, and it is not the best way to achieve what is required. I’ll write soon a new solution that in my opinion will be far better than this one.

So what is missing here?

The last login date is not enough to tell if the user online or not, we must have another field that tells us when is the last activity date for the user.
In ASP .NET there is a field called LastActivityDate in the users table, and it is used for this specific mission.

When to update last activity date?

In ASP .NET you have to update the last activity manually, and there is a good way to do it; in global.asax:

In this method you can access the request, get the current user, update his/her last activity date.

So basically updating last activity date is done on the request, when the user makes a request to your application and he/she is authenticated.

But there is no last activity date in Django’s user model?

Yeah I know, but you can extend the User module in Django. And to do this there are many ways, one of them is by making a new model with a foreign key to the User. Then you can do whatever you want.

And where am I going to write the code that will update the last activity date?

In Django we have Middleware; a Middleware is a layer that handles something on each request, its input is the request and it can process it or modify it.

So I simply created a new Middleware that takes the request and finds the user, then updates his/her last activity.

I installed the middleware in the settings module, I appended its name after ‘django.contrib.auth.middleware.AuthenticationMiddleware’ in MIDDLEWARE_CLASSES.

There was a problem in doing that, because the user might send many request per page, each image will send a request, and imagine how many updates there is in each page!

The solution is to skip a list of URLs, so if the user requests a URL within this list his/her last activity date won’t be updated. The skip list is stored within the urls.py file.

The skip list will contain regular expressions just like the url_patterns dictionary.

Making the solution generic

I wanted to publish this solution, and it had to be generic so you can use it without editing its code.

Making it generic required small modifications:
* Making a new application called lastActivityDate.
* Pasting the extended user model inside models.py.
* Modifying the settings module and add lastActivityDate to the INSTALLED_APPS list.

Extending the user module

Since the User and UserActivity models are now related one-to-one we can now type:

The Middleware

The compiled list is for enhancing performance, each regular expression is compiled one time; when it is accessed the first time.

You can see that I used the settings module to get a the URLs module name. And I used the __import__ function to import the module dynamically.

In line 12 I check if there is a list called ‘skip_last_activity_date’ using the getattr function, passing a third parameter to this functions specifies the default value if the list was not found so I don’t get an exception.

In lines 25 to 37 I get or create the UserActivity object, I update the last activity date using datetime.now(), I update the last activity IP using request.META[‘REMOTE_ADDR’] which gives me the client IP address.

Modifying the URLs module

In the urls.py file insert the following code:

Modifying the settings module

In the settings.py file insert the following code:

Replace YOUR_APP_FOLDER with the folder of your application, such that the directory tree is like this:

P.S.

Do not forget to synchronize the database using “manage.py syncdb” so that Django creates the lastactivitydate_useractivity table in the database.

Getting online users

After this long trip here is how to get online users:

This function returns a list of users whose last activity was in the last 15 minutes. Of course you can modify it and expand the duration.

Download source code

Here is the link to download source code of the lastActivityDate application:

lastActivityDate.tar.gz

Notice that the code is written using Python 2.6 and Django 1.0. If you have different version(s) you might encounter a problem.

License

This work is licensed under the Creative Commons Attribution-Share Alike 3.0 License.

To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/.

14 thoughts on “Get Online Users in Django

  1. Thanks for taking out time & putting in the effort to make this available to all as a generic solution.

    This is what makes the Django/Python community so great 🙂

  2. Hi,
    I’ve been looking for solutions before to know which users were online.
    At first I looked at your technique but I found that using a new model for the activity and using a middleware to actually hit the database (write operation) costs too much.
    Then I used a technique using an access log. To find the online users, you query the access log and group access entries by user.
    The query in this case is quite slow but you don’t have to add another middleware just to update the last activity.

    I’ve actually found a better technique which is fast and does not hit the database, also you do not need an access log.
    You create memcached entries to know if a user is online.
    Everytime a user logs in, you update another entry in the cache which holds a list of online users (this list can be up to 1MB long, the max size of a memcached entry). Whenever you need the list of online users, you remove offline users from the list and return the resulting list.
    Every page view, you can update the user online status in the cache. If the status goes from offline to online, you add the user to the online list.
    It’s a bit unclear but preserves the app from database hits.

    1. Yeah that’s right, the solution I introduced uses the database every time, your solution is better since it uses the memcache.
      But I created a middleware so I can be able to update the last activity date on every request which is less complex than updating the state on every page view. I mean in your solution you’ll have to update the status manually every time on every page call, while in mine you won’t have to do that manually. Also when you decide for example to disable this logic or remove it, in your case you’ll have to edit many methods and page calls, while in my solution all you have to do is to disable the middleware.

      So I think that the best solution we can come up with is to merge your memcache method with my middleware 🙂

      Thanks.

  3. Hi Abd Allah,
    I forgot to tell that my app actually combines this with a middleware, so you can disable the behaviour just by removing the middleware from the list.
    The only (slight) problem is in the login view : each user login hits the cache, even when the middleware is off. The performance cost is very minimal so it’s just a matter of just enabling or disabling the middleware.
    It would be a perfect solution if the custom login function could tell if the middleware is active.

  4. made little update, now i can check users status:

    from django.db import models
    from django.contrib.auth.models import User
    from datetime import datetime, timedelta

    class UserActivity(models.Model):
    last_activity_ip = models.IPAddressField()
    last_activity_date = models.DateTimeField(default = datetime(1941, 1, 1))
    user = models.OneToOneField(User, primary_key=True)

    def is_online(self):
    no_active_delta = datetime.now() – self.last_activity_date
    if no_active_delta > timedelta(minutes=21):
    return False
    else:
    return True

  5. Great post!

    Little modification. Model can be:

    class UserActivity(models.Model):
    last_activity_ip = models.IPAddressField()
    last_activity_date = models.DateTimeField(auto_now=True)
    user = models.OneToOneField(User, primary_key=True)

    1. Thanks for the modification,

      Actually this is an old post, now I think it shouldn’t behave like this, it should use caching instead of database access on each request, it will become better.

    1. Thanks for your comment, I’ll try soon to squeeze some time and write a new solution that would be better, currently this solution works but not the best way.

      As for the skip_last_activity_date it is simply a list of regular expressions that if the request path matches any one of them the activity date won’t be updated, it was intended to make less calls to the DB per page when serving static files through Django which is not the thing you might do on a deployment server. Hope this helps.

Leave a Reply