Although Harold's answer works in this specific case, I can see at least two important issues with it:
- This solution can only be used with a database session engine. In other situations (cache, file, cookie) the
Session
model would not be used.
- When the number of sessions and users in database grows, this becomes quite inefficient.
To solve those issues, I suggest you take another approach at the problem. The idea is to store somewhere the date when the user was logged in for a given session, and the last time you requested a user to be logged out.
Then whenever someone access your site, if the logged in date is lower than the log out date, you can force-logout the user. As dan said, there's no practical difference between logging out a user immediately or on his next request to your site.
Now, let's see a possible implementation of this solution, for django 1.3b1. In three steps:
1. store in the session the last login date
Fortunately, Django auth system exposes a signal called user_logged_in
. You just have to register that signals, and save the current date in the session. At the bottom of your models.py
:
from django.contrib.auth.signals import user_logged_in
from datetime import datetime
def update_session_last_login(sender, user=user, request=request, **kwargs):
if request:
request.session['LAST_LOGIN_DATE'] = datetime.now()
user_logged_in.connect(update_session_last_login)
2. request a force logout for a user
We just need to add a field and a method to the User
model. There's multiple ways to achieve that (user profiles, model inheritance, etc.) each with pros and cons.
For the sake of simplicity, I'm gonna use model inheritance here, if you go for this solution, don't forget to write a custom authentication backend.
from django.contrib.auth.models import User
from django.db import models
from datetime import datetime
class MyUser(User):
force_logout_date = models.DateTimeField(null=True, blank=True)
def force_logout(self):
self.force_logout_date = datetime.now()
self.save()
Then, if you want to force logout for user johndoe
, you just have to:
from myapp.models import MyUser
MyUser.objects.get(username='johndoe').force_logout()
3. implement the check on access
Best way here is to use a middleware as dan suggested. This middleware will access request.user
, so you need to put it after 'django.contrib.auth.middleware.AuthenticationMiddleware'
in your MIDDLEWARE_CLASSES
setting.
from django.contrib.auth import logout
class ForceLogoutMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated() and request.user.force_logout_date and \
request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date:
logout(request)
That should do it.
Notes
Be aware of the performance implication of storing an extra field for your users. Using model inheritance will add an extra JOIN
. Using user profiles will add an extra query. Modifying directly the User
is the best way performance wise, but it is still a hairy topic.
If you deploy that solution on an existing site, you will probably have some trouble with existing sessions, which won't have the 'LAST_LOGIN_DATE'
key. You can adapt a bit the middleware code to deal with that case :
from django.contrib.auth import logout
class ForceLogoutMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated() and request.user.force_logout_date and \
( 'LAST_LOGIN_DATE' not in request.session or \
request.session['LAST_LOGIN_DATE'] < request.user.force_logout_date ):
logout(request)
In django 1.2.x, there is no user_logged_in
signal. Fall back to overriding the login
function:
from django.contrib.auth import login as dj_login
from datetime import datetime
def login(request, user):
dj_login(request, user)
request.session['LAST_LOGIN_DATE'] = datetime.now()