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:
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/.