Abdullah Diab’s Blog

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:

private void Application_AuthenticateRequest(Object source, EventArgs e) {
    //Your Code Goes Here
}

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

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

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

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

a = User.objects.get(username__exact='mpcabd')
print a.useractivity.last_activity_ip

b = UserActivity.objects.get(user=a)
print b.user.username

The Middleware

from models import UserActivity
from datetime import datetime
from django.conf import settings
from django.contrib.sites.models import Site
import re

compiledLists = {}

class LastActivityMiddleware(object):
    def process_request(self, request):
        if not request.user.is_authenticated():
            return
        urlsModule = __import__(settings.ROOT_URLCONF, {}, {}, [''])
        skipList = getattr(urlsModule, 'skip_last_activity_date', None)
        skippedPath = request.path
        if skippedPath.startswith('/'):
            skippedPath = skippedPath[1:]
        if skipList is not None:
            for expression in skipList:
                compiledVersion = None
                if not compiledLists.has_key(expression):
                    compiledLists[expression] = re.compile(expression)
                compiledVersion = compiledLists[expression]
                if compiledVersion.search(skippedPath):
                    return
        activity = None
        try:
            activity = request.user.useractivity
        except:
            activity = UserActivity()
            activity.user = request.user
            activity.last_activity_date = datetime.now()
            activity.last_activity_ip = request.META['REMOTE_ADDR']
            activity.save()
            return
        activity.last_activity_date = datetime.now()
        activity.last_activity_ip = request.META['REMOTE_ADDR']
        activity.save()

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:

skip_last_activity_date = [
    #Your expressions go here
]

Modifying the settings module

In the settings.py file insert the following code:

#Your settings code
MIDDLEWARE_CLASSES = (
    #Your middleware classes go here
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'lastActivityDate.LastActivityMiddleware.LastActivityMiddleware',
    #Your middleware classes go here
)
#Your settings code
INSTALLED_APPS = (
    #Your installed apps
    'YOUR_APP_FOLDER.lastActivityDate',
    #Your installed apps
)
#Your settings code

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

YourAppFolder:
--> appFolder
--> …
--> __init__.py
--> views.py
--> models.py
--> …
--> lastActivityDate
--> __init__.py
--> LastActivityMiddleware.py
--> models.py
--> urls.py
--> settings.py
--> __init__.py
--> manage.py

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:

from django.contrib.auth.models import User
from datetime import datetime, timedelta
from lastActivityDate.models import UserActivity

def get_online_users(num):
    fifteen_minutes = datetime.now() - timedelta(minutes=15)
    sql_datetime = datetime.strftime(fifteen_minutes, '%Y-%m-%d %H:%M:%S')
    users = UserActivity.objects.filter(last_activity_date__gte=sql_datetime, user__is_active__exact=1).order_by('-last_activity_date')[:num]
    return [u.user for u in 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/.