Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Fixed #5612 -- Added login and logout signals to contrib auth app. Th…

…anks SmileyChris and pterk.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14710 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 132afbf8eee837b6fe2d051f7eced4889e19de88 1 parent 81323cb
@jezdez jezdez authored
View
11 django/contrib/auth/__init__.py
@@ -2,6 +2,7 @@
from warnings import warn
from django.core.exceptions import ImproperlyConfigured
from django.utils.importlib import import_module
+from django.contrib.auth.signals import user_logged_in, user_logged_out
SESSION_KEY = '_auth_user_id'
BACKEND_SESSION_KEY = '_auth_user_backend'
@@ -62,8 +63,7 @@ def login(request, user):
if user is None:
user = request.user
# TODO: It would be nice to support different login methods, like signed cookies.
- user.last_login = datetime.datetime.now()
- user.save()
+ user_logged_in.send(sender=user.__class__, request=request, user=user)
if SESSION_KEY in request.session:
if request.session[SESSION_KEY] != user.id:
@@ -83,6 +83,13 @@ def logout(request):
Removes the authenticated user's ID from the request and flushes their
session data.
"""
+ # Dispatch the signal before the user is logged out so the receivers have a
+ # chance to find out *who* logged out.
+ user = getattr(request, 'user', None)
+ if hasattr(user, 'is_authenticated') and not user.is_authenticated():
+ user = None
+ user_logged_out.send(sender=user.__class__, request=request, user=user)
+
request.session.flush()
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
View
10 django/contrib/auth/models.py
@@ -2,6 +2,7 @@
import urllib
from django.contrib import auth
+from django.contrib.auth.signals import user_logged_in
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.db.models.manager import EmptyManager
@@ -40,6 +41,15 @@ def check_password(raw_password, enc_password):
algo, salt, hsh = enc_password.split('$')
return hsh == get_hexdigest(algo, salt, raw_password)
+def update_last_login(sender, user, **kwargs):
+ """
+ A signal receiver which updates the last_login date for
+ the user logging in.
+ """
+ user.last_login = datetime.datetime.now()
+ user.save()
+user_logged_in.connect(update_last_login)
+
class SiteProfileNotAvailable(Exception):
pass
View
4 django/contrib/auth/signals.py
@@ -0,0 +1,4 @@
+from django.dispatch import Signal
+
+user_logged_in = Signal(providing_args=['request', 'user'])
+user_logged_out = Signal(providing_args=['request', 'user'])
View
1  django/contrib/auth/tests/__init__.py
@@ -5,6 +5,7 @@
from django.contrib.auth.tests.remote_user \
import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
from django.contrib.auth.tests.models import ProfileTestCase
+from django.contrib.auth.tests.signals import SignalTestCase
from django.contrib.auth.tests.tokens import TokenGeneratorTest
from django.contrib.auth.tests.views \
import PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest
View
47 django/contrib/auth/tests/signals.py
@@ -0,0 +1,47 @@
+from django.test import TestCase
+from django.contrib.auth import signals
+
+
+class SignalTestCase(TestCase):
+ urls = 'django.contrib.auth.tests.urls'
+ fixtures = ['authtestdata.json']
+
+ def listener_login(self, user, **kwargs):
+ self.logged_in.append(user)
+
+ def listener_logout(self, user, **kwargs):
+ self.logged_out.append(user)
+
+ def setUp(self):
+ """Set up the listeners and reset the logged in/logged out counters"""
+ self.logged_in = []
+ self.logged_out = []
+ signals.user_logged_in.connect(self.listener_login)
+ signals.user_logged_out.connect(self.listener_logout)
+
+ def tearDown(self):
+ """Disconnect the listeners"""
+ signals.user_logged_in.disconnect(self.listener_login)
+ signals.user_logged_out.disconnect(self.listener_logout)
+
+ def test_login(self):
+ # Only a successful login will trigger the signal.
+ self.client.login(username='testclient', password='bad')
+ self.assertEqual(len(self.logged_in), 0)
+ # Like this:
+ self.client.login(username='testclient', password='password')
+ self.assertEqual(len(self.logged_in), 1)
+ self.assertEqual(self.logged_in[0].username, 'testclient')
+
+ def test_logout_anonymous(self):
+ # The log_out function will still trigger the signal for anonymous
+ # users.
+ self.client.get('/logout/next_page/')
+ self.assertEqual(len(self.logged_out), 1)
+ self.assertEqual(self.logged_out[0], None)
+
+ def test_logout(self):
+ self.client.login(username='testclient', password='password')
+ self.client.get('/logout/next_page/')
+ self.assertEqual(len(self.logged_out), 1)
+ self.assertEqual(self.logged_out[0].username, 'testclient')
View
3  docs/ref/signals.txt
@@ -12,6 +12,9 @@ A list of all the signals that Django sends.
The :doc:`comment framework </ref/contrib/comments/index>` sends a :doc:`set
of comment-related signals </ref/contrib/comments/signals>`.
+ The :ref:`authentication framework <topics-auth>` sends :ref:`signals when
+ a user is logged in / out <topics-auth-signals>`.
+
Model signals
=============
View
38 docs/topics/auth.txt
@@ -665,6 +665,44 @@ How to log a user out
immediately after logging out, do that *after* calling
:func:`django.contrib.auth.logout()`.
+.. _topics-auth-signals:
+
+Login and logout signals
+------------------------
+
+The auth framework uses two :ref:`signals <topic-signals>` that can be used for
+notification when a user logs in or out.
+
+**:data:`django.contrib.auth.signals.user_logged_in`**
+
+Sent when a user logs in successfully.
+
+Arguments sent with this signal:
+
+ ``sender``
+ As above: the class of the user that just logged in.
+
+ ``request``
+ The current :class:`~django.http.HttpRequest` instance.
+
+ ``user``
+ The user instance that just logged in.
+
+**:data:`django.contrib.auth.signals.user_logged_out`**
+
+Sent when the logout method is called.
+
+ ``sender``
+ As above: the class of the user that just logged out or ``None``
+ if the user was not authenticated.
+
+ ``request``
+ The current :class:`~django.http.HttpRequest` instance.
+
+ ``user``
+ The user instance that just logged out or ``None`` if the
+ user was not authenticated.
+
Limiting access to logged-in users
----------------------------------
Please sign in to comment.
Something went wrong with that request. Please try again.