Ticket #7153: 7153.3.diff

File 7153.3.diff, 14.3 KB (added by Chris Beaven, 14 years ago)
  • django/contrib/auth/__init__.py

    diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
    index 3d76a42..cca1b70 100644
    a b import datetime  
    22from warnings import warn
    33from django.core.exceptions import ImproperlyConfigured
    44from django.utils.importlib import import_module
     5from django.contrib.auth import signals
    56
    67SESSION_KEY = '_auth_user_id'
    78BACKEND_SESSION_KEY = '_auth_user_backend'
    def login(request, user):  
    6263    if user is None:
    6364        user = request.user
    6465    # TODO: It would be nice to support different login methods, like signed cookies.
    65     user.last_login = datetime.datetime.now()
    66     user.save()
     66    signals.user_logged_in.send(sender=user.__class__, request=request,
     67                                user=user)
    6768
    6869    if SESSION_KEY in request.session:
    6970        if request.session[SESSION_KEY] != user.id:
    def logout(request):  
    8384    Removes the authenticated user's ID from the request and flushes their
    8485    session data.
    8586    """
     87    # Dispatch the signal before the user is logged out so the receivers have a
     88    # chance to find out *who* logged out.
     89    user = getattr(request, 'user', None)
     90    if hasattr(user, 'is_authenticated') and not user.is_authenticated():
     91        user = None
     92    signals.user_logged_out.send(sender=user.__class__, request=request,
     93                                 user=user)
     94
    8695    request.session.flush()
    8796    if hasattr(request, 'user'):
    8897        from django.contrib.auth.models import AnonymousUser
  • django/contrib/auth/models.py

    diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py
    index 16a8b99..e37c5b3 100644
    a b import datetime  
    22import urllib
    33
    44from django.contrib import auth
     5# Import the signals module to register the signal listener.
     6from django.contrib.auth import signals
    57from django.core.exceptions import ImproperlyConfigured
    68from django.db import models
    79from django.db.models.manager import EmptyManager
  • new file django/contrib/auth/signals.py

    diff --git a/django/contrib/auth/signals.py b/django/contrib/auth/signals.py
    new file mode 100644
    index 0000000..ae66b6d
    - +  
     1import datetime
     2import django.dispatch
     3
     4user_logged_in = django.dispatch.Signal(providing_args=['request', 'user'])
     5user_logged_out = django.dispatch.Signal(providing_args=['request', 'user'])
     6
     7
     8def update_last_login(sender, user, **kwargs):
     9    """
     10    A signal receiver which updates the last_login date for the user logging
     11    in.
     12
     13    """
     14    user.last_login = datetime.datetime.now()
     15    user.save()
     16
     17
     18user_logged_in.connect(update_last_login)
  • django/contrib/auth/tests/__init__.py

    diff --git a/django/contrib/auth/tests/__init__.py b/django/contrib/auth/tests/__init__.py
    index 98061a1..9f8b49f 100644
    a b from django.contrib.auth.tests.forms import UserCreationFormTest, Authentication  
    55from django.contrib.auth.tests.remote_user \
    66        import RemoteUserTest, RemoteUserNoCreateTest, RemoteUserCustomTest
    77from django.contrib.auth.tests.models import ProfileTestCase
     8from django.contrib.auth.tests.signals import SignalTestCase
    89from django.contrib.auth.tests.tokens import TokenGeneratorTest
    910from django.contrib.auth.tests.views \
    1011        import PasswordResetTest, ChangePasswordTest, LoginTest, LogoutTest
  • new file django/contrib/auth/tests/signals.py

    diff --git a/django/contrib/auth/tests/signals.py b/django/contrib/auth/tests/signals.py
    new file mode 100644
    index 0000000..3806021
    - +  
     1from django.test import TestCase
     2from django.contrib.auth import signals
     3
     4
     5class 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')
  • django/template/__init__.py

    diff --git a/django/template/__init__.py b/django/template/__init__.py
    index c316786..fc6ce07 100644
    a b class Variable(object):  
    717717        instead.
    718718        """
    719719        current = context
    720         for bit in self.lookups:
    721             try: # dictionary lookup
    722                 current = current[bit]
    723             except (TypeError, AttributeError, KeyError):
    724                 try: # attribute lookup
    725                     current = getattr(current, bit)
    726                     if callable(current):
    727                         if getattr(current, 'alters_data', False):
    728                             current = settings.TEMPLATE_STRING_IF_INVALID
    729                         else:
    730                             try: # method call (assuming no args required)
    731                                 current = current()
    732                             except TypeError: # arguments *were* required
    733                                 # GOTCHA: This will also catch any TypeError
    734                                 # raised in the function itself.
    735                                 current = settings.TEMPLATE_STRING_IF_INVALID # invalid method call
    736                             except Exception, e:
    737                                 if getattr(e, 'silent_variable_failure', False):
    738                                     current = settings.TEMPLATE_STRING_IF_INVALID
    739                                 else:
    740                                     raise
    741                 except (TypeError, AttributeError):
    742                     try: # list-index lookup
    743                         current = current[int(bit)]
    744                     except (IndexError, # list index out of range
    745                             ValueError, # invalid literal for int()
    746                             KeyError,   # current is a dict without `int(bit)` key
    747                             TypeError,  # unsubscriptable object
    748                             ):
    749                         raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
    750                 except Exception, e:
    751                     if getattr(e, 'silent_variable_failure', False):
    752                         current = settings.TEMPLATE_STRING_IF_INVALID
    753                     else:
    754                         raise
    755             except Exception, e:
    756                 if getattr(e, 'silent_variable_failure', False):
    757                     current = settings.TEMPLATE_STRING_IF_INVALID
    758                 else:
    759                     raise
     720        try: # catch-all for silent variable failures
     721            for bit in self.lookups:
     722                try: # dictionary lookup
     723                    current = current[bit]
     724                except (TypeError, AttributeError, KeyError):
     725                    try: # attribute lookup
     726                        current = getattr(current, bit)
     727                    except (TypeError, AttributeError):
     728                        try: # list-index lookup
     729                            current = current[int(bit)]
     730                        except (IndexError, # list index out of range
     731                                ValueError, # invalid literal for int()
     732                                KeyError,   # current is a dict without `int(bit)` key
     733                                TypeError,  # unsubscriptable object
     734                                ):
     735                            raise VariableDoesNotExist("Failed lookup for key [%s] in %r", (bit, current)) # missing attribute
     736                if callable(current):
     737                    if getattr(current, 'alters_data', False):
     738                        return settings.TEMPLATE_STRING_IF_INVALID
     739                        try: # method call (assuming no args required)
     740                            current = current()
     741                        except TypeError: # arguments *were* required
     742                            # GOTCHA: This will also catch any TypeError
     743                            # raised in the function itself.
     744                            return settings.TEMPLATE_STRING_IF_INVALID # invalid method call
     745        except Exception, e:
     746            if getattr(e, 'silent_variable_failure', False):
     747                return settings.TEMPLATE_STRING_IF_INVALID
     748            else:
     749                raise
    760750
    761751        return current
    762752
  • docs/ref/signals.txt

    diff --git a/docs/ref/signals.txt b/docs/ref/signals.txt
    index 4c1db33..36ad071 100644
    a b A list of all the signals that Django sends.  
    1212    The :doc:`comment framework </ref/contrib/comments/index>` sends a :doc:`set
    1313    of comment-related signals </ref/contrib/comments/signals>`.
    1414
     15    The :ref:`authentication framework <topics-auth>` sends :ref:`signals when
     16    a user is logged in / out <topics-auth-signals>`.
     17
    1518Model signals
    1619=============
    1720
  • docs/topics/auth.txt

    diff --git a/docs/topics/auth.txt b/docs/topics/auth.txt
    index e0856e8..d69fd91 100644
    a b How to log a user out  
    664664    immediately after logging out, do that *after* calling
    665665    :func:`django.contrib.auth.logout()`.
    666666
     667.. _topics-auth-signals:
     668
     669Login and logout signals
     670------------------------
     671
     672The auth framework uses two :ref:`signals <topic-signals>` that can be used for
     673notification when a user logs in or out.
     674
     675**:data:`django.contrib.auth.signals.user_logged_in`**
     676
     677Sent when a user logs in successfully.
     678
     679Arguments sent with this signal:
     680
     681    ``sender``
     682        As above: the class of the user that just logged in.
     683
     684    ``request``
     685        The current :class:`~django.http.HttpRequest` instance.
     686
     687    ``user``
     688        The user instance that just logged in.
     689
     690**:data:`django.contrib.auth.signals.user_logged_out`**   
     691
     692Sent when the logout method is called.
     693
     694    ``sender``
     695        As above: the class of the user that just logged out or ``None``
     696        if the user was not authenticated.
     697
     698    ``request``
     699        The current :class:`~django.http.HttpRequest` instance.
     700
     701    ``user``
     702        The user instance that just logged out or ``None`` if the
     703        user was not authenticated.
     704
    667705Limiting access to logged-in users
    668706----------------------------------
    669707
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index 6683ebb..fba5c02 100644
    a b class SomeClass:  
    9494    def method4(self):
    9595        raise SomeOtherException
    9696
     97    def __getitem__(self, key):
     98        if key == 'silent_fail_key':
     99            raise SomeException
     100        elif key == 'noisy_fail_key':
     101            raise SomeOtherException
     102        raise KeyError
     103   
     104    def silent_fail_attribute(self):
     105        raise SomeException
     106    silent_fail_attribute = property(silent_fail_attribute)
     107
     108    def noisy_fail_attribute(self):
     109        raise SomeOtherException
     110    noisy_fail_attribute = property(noisy_fail_attribute)
     111
    97112class OtherClass:
    98113    def method(self):
    99114        return "OtherClass.method"
    class Templates(unittest.TestCase):  
    499514            'basic-syntax25': ('{{ "fred" }}', {}, "fred"),
    500515            'basic-syntax26': (r'{{ "\"fred\"" }}', {}, "\"fred\""),
    501516            'basic-syntax27': (r'{{ _("\"fred\"") }}', {}, "\"fred\""),
     517           
     518            # Call methods in the top level of the context
     519            'basic-syntax28': (r'{{ callable }}', {"callable": lambda: "foo bar"}, "foo bar"),
     520           
     521            # Call methods returned from dictionary lookups
     522            'basic-syntax29': (r'{{ var.callable }}', {"var": {"callable": lambda: "foo bar"}}, "foo bar"),
    502523
    503524            # regression test for ticket #12554
    504525            # make sure a silent_variable_failure Exception is supressed
    class Templates(unittest.TestCase):  
    604625            #filters should accept empty string constants
    605626            'filter-syntax20': ('{{ ""|default_if_none:"was none" }}', {}, ""),
    606627
     628            # Fail silently for non-callable attribute and dict lookups which
     629            # raise an exception with a "silent_variable_failure" attribute
     630            'filter-syntax21': (r'1{{ var.silent_fail_key }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
     631            'filter-syntax22': (r'1{{ var.silent_fail_attribute }}2', {"var": SomeClass()}, ("12", "1INVALID2")),
     632
     633            # In attribute and dict lookups that raise an unexpected exception
     634            # without a "silent_variable_attribute" set to True, the exception
     635            # propagates
     636            'filter-syntax23': (r'1{{ var.noisy_fail_key }}2', {"var": SomeClass()}, SomeOtherException),
     637            'filter-syntax24': (r'1{{ var.noisy_fail_attribute }}2', {"var": SomeClass()}, SomeOtherException),
     638
    607639            ### COMMENT SYNTAX ########################################################
    608640            'comment-syntax01': ("{# this is hidden #}hello", {}, "hello"),
    609641            'comment-syntax02': ("{# this is hidden #}hello{# foo #}", {}, "hello"),
Back to Top