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

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
Thanks Sam for coming by, that is my pleasure
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.
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.
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.
Any chance you could give us a peek at the source code that demonstrates your approach to this scenario?
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