Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
Jannis Leidel authored November 26, 2010
11  django/contrib/auth/__init__.py
@@ -2,6 +2,7 @@
2 2
 from warnings import warn
3 3
 from django.core.exceptions import ImproperlyConfigured
4 4
 from django.utils.importlib import import_module
  5
+from django.contrib.auth.signals import user_logged_in, user_logged_out
5 6
 
6 7
 SESSION_KEY = '_auth_user_id'
7 8
 BACKEND_SESSION_KEY = '_auth_user_backend'
@@ -62,8 +63,7 @@ def login(request, user):
62 63
     if user is None:
63 64
         user = request.user
64 65
     # TODO: It would be nice to support different login methods, like signed cookies.
65  
-    user.last_login = datetime.datetime.now()
66  
-    user.save()
  66
+    user_logged_in.send(sender=user.__class__, request=request, user=user)
67 67
 
68 68
     if SESSION_KEY in request.session:
69 69
         if request.session[SESSION_KEY] != user.id:
@@ -83,6 +83,13 @@ def logout(request):
83 83
     Removes the authenticated user's ID from the request and flushes their
84 84
     session data.
85 85
     """
  86
+    # Dispatch the signal before the user is logged out so the receivers have a
  87
+    # chance to find out *who* logged out.
  88
+    user = getattr(request, 'user', None)
  89
+    if hasattr(user, 'is_authenticated') and not user.is_authenticated():
  90
+        user = None
  91
+    user_logged_out.send(sender=user.__class__, request=request, user=user)
  92
+
86 93
     request.session.flush()
87 94
     if hasattr(request, 'user'):
88 95
         from django.contrib.auth.models import AnonymousUser
10  django/contrib/auth/models.py
@@ -2,6 +2,7 @@
2 2
 import urllib
3 3
 
4 4
 from django.contrib import auth
  5
+from django.contrib.auth.signals import user_logged_in
5 6
 from django.core.exceptions import ImproperlyConfigured
6 7
 from django.db import models
7 8
 from django.db.models.manager import EmptyManager
@@ -40,6 +41,15 @@ def check_password(raw_password, enc_password):
40 41
     algo, salt, hsh = enc_password.split('$')
41 42
     return hsh == get_hexdigest(algo, salt, raw_password)
42 43
 
  44
+def update_last_login(sender, user, **kwargs):
  45
+    """
  46
+    A signal receiver which updates the last_login date for
  47
+    the user logging in.
  48
+    """
  49
+    user.last_login = datetime.datetime.now()
  50
+    user.save()
  51
+user_logged_in.connect(update_last_login)
  52
+
43 53
 class SiteProfileNotAvailable(Exception):
44 54
     pass
45 55
 
4  django/contrib/auth/signals.py
... ...
@@ -0,0 +1,4 @@
  1
+from django.dispatch import Signal
  2
+
  3
+user_logged_in = Signal(providing_args=['request', 'user'])
  4
+user_logged_out = Signal(providing_args=['request', 'user'])
1  django/contrib/auth/tests/__init__.py
@@ -5,6 +5,7 @@
5 5
 from django.contrib.auth.tests.remote_user \
6 6
         import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
7 7
 from django.contrib.auth.tests.models import ProfileTestCase
  8
+from django.contrib.auth.tests.signals import SignalTestCase
8 9
 from django.contrib.auth.tests.tokens import TokenGeneratorTest
9 10
 from django.contrib.auth.tests.views \
10 11
         import PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest
47  django/contrib/auth/tests/signals.py
... ...
@@ -0,0 +1,47 @@
  1
+from django.test import TestCase
  2
+from django.contrib.auth import signals
  3
+
  4
+
  5
+class SignalTestCase(TestCase):
  6
+    urls = 'django.contrib.auth.tests.urls'
  7
+    fixtures = ['authtestdata.json']
  8
+
  9
+    def listener_login(self, user, **kwargs):
  10
+        self.logged_in.append(user)
  11
+
  12
+    def listener_logout(self, user, **kwargs):
  13
+        self.logged_out.append(user)
  14
+
  15
+    def setUp(self):
  16
+        """Set up the listeners and reset the logged in/logged out counters"""
  17
+        self.logged_in = []
  18
+        self.logged_out = []
  19
+        signals.user_logged_in.connect(self.listener_login)
  20
+        signals.user_logged_out.connect(self.listener_logout)
  21
+
  22
+    def tearDown(self):
  23
+        """Disconnect the listeners"""
  24
+        signals.user_logged_in.disconnect(self.listener_login)
  25
+        signals.user_logged_out.disconnect(self.listener_logout)
  26
+
  27
+    def test_login(self):
  28
+        # Only a successful login will trigger the signal.
  29
+        self.client.login(username='testclient', password='bad')
  30
+        self.assertEqual(len(self.logged_in), 0)
  31
+        # Like this:
  32
+        self.client.login(username='testclient', password='password')
  33
+        self.assertEqual(len(self.logged_in), 1)
  34
+        self.assertEqual(self.logged_in[0].username, 'testclient')
  35
+
  36
+    def test_logout_anonymous(self):
  37
+        # The log_out function will still trigger the signal for anonymous
  38
+        # users.
  39
+        self.client.get('/logout/next_page/')
  40
+        self.assertEqual(len(self.logged_out), 1)
  41
+        self.assertEqual(self.logged_out[0], None)
  42
+
  43
+    def test_logout(self):
  44
+        self.client.login(username='testclient', password='password')
  45
+        self.client.get('/logout/next_page/')
  46
+        self.assertEqual(len(self.logged_out), 1)
  47
+        self.assertEqual(self.logged_out[0].username, 'testclient')
3  docs/ref/signals.txt
@@ -12,6 +12,9 @@ A list of all the signals that Django sends.
12 12
     The :doc:`comment framework </ref/contrib/comments/index>` sends a :doc:`set
13 13
     of comment-related signals </ref/contrib/comments/signals>`.
14 14
 
  15
+    The :ref:`authentication framework <topics-auth>` sends :ref:`signals when
  16
+    a user is logged in / out <topics-auth-signals>`.
  17
+
15 18
 Model signals
16 19
 =============
17 20
 
38  docs/topics/auth.txt
@@ -665,6 +665,44 @@ How to log a user out
665 665
     immediately after logging out, do that *after* calling
666 666
     :func:`django.contrib.auth.logout()`.
667 667
 
  668
+.. _topics-auth-signals:
  669
+
  670
+Login and logout signals
  671
+------------------------
  672
+
  673
+The auth framework uses two :ref:`signals <topic-signals>` that can be used for
  674
+notification when a user logs in or out.
  675
+
  676
+**:data:`django.contrib.auth.signals.user_logged_in`**
  677
+
  678
+Sent when a user logs in successfully.
  679
+
  680
+Arguments sent with this signal:
  681
+
  682
+    ``sender``
  683
+        As above: the class of the user that just logged in.
  684
+
  685
+    ``request``
  686
+        The current :class:`~django.http.HttpRequest` instance.
  687
+
  688
+    ``user``
  689
+        The user instance that just logged in.
  690
+
  691
+**:data:`django.contrib.auth.signals.user_logged_out`**
  692
+
  693
+Sent when the logout method is called.
  694
+
  695
+    ``sender``
  696
+        As above: the class of the user that just logged out or ``None``
  697
+        if the user was not authenticated.
  698
+
  699
+    ``request``
  700
+        The current :class:`~django.http.HttpRequest` instance.
  701
+
  702
+    ``user``
  703
+        The user instance that just logged out or ``None`` if the
  704
+        user was not authenticated.
  705
+
668 706
 Limiting access to logged-in users
669 707
 ----------------------------------
670 708
 

0 notes on commit 132afbf

Please sign in to comment.
Something went wrong with that request. Please try again.