From c1aec0feda73ede09503192a66f973598aef901d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeremy=20Lain=C3=A9?= Date: Sat, 2 Apr 2016 13:18:26 +0200 Subject: [PATCH] Fixed #25847 -- Made User.is_(anonymous|authenticated) properties. --- AUTHORS | 1 + django/contrib/auth/__init__.py | 2 +- django/contrib/auth/backends.py | 4 +- django/contrib/auth/base_user.py | 7 +- django/contrib/auth/decorators.py | 2 +- django/contrib/auth/middleware.py | 4 +- django/contrib/auth/mixins.py | 2 +- django/contrib/auth/models.py | 7 +- django/contrib/auth/views.py | 2 +- .../flatpages/templatetags/flatpages.py | 2 +- django/contrib/flatpages/views.py | 2 +- django/utils/deprecation.py | 30 ++++++++ docs/howto/error-reporting.txt | 2 +- docs/internals/deprecation.txt | 3 + docs/ref/contrib/auth.txt | 73 +++++++++++-------- docs/ref/request-response.txt | 4 +- docs/releases/1.10.txt | 35 +++++++++ docs/topics/auth/customizing.txt | 38 +++++++--- docs/topics/auth/default.txt | 12 +-- docs/topics/cache.txt | 2 +- docs/topics/class-based-views/mixins.txt | 6 +- tests/auth_tests/test_auth_backends.py | 19 +++-- tests/auth_tests/test_basic.py | 48 +++++++++++- tests/auth_tests/test_middleware.py | 4 +- tests/auth_tests/test_mixins.py | 4 +- tests/auth_tests/test_remote_user.py | 14 ++-- tests/urlpatterns_reverse/views.py | 2 +- 27 files changed, 238 insertions(+), 93 deletions(-) diff --git a/AUTHORS b/AUTHORS index 537d11212d..c7ed5afd6f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -345,6 +345,7 @@ answer newbie questions, and generally made Django that much better: Jérémie Blaser Jeremy Carbaugh Jeremy Dunck + Jeremy Lainé Jesse Young jhenry Jim Dalton diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 734c03731a..4f4f08987a 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -138,7 +138,7 @@ def logout(request): # 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(): + if hasattr(user, 'is_authenticated') and not user.is_authenticated: user = None user_logged_out.send(sender=user.__class__, request=request, user=user) diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index 43d8627d0c..f145a1929a 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -45,7 +45,7 @@ class ModelBackend(object): be either "group" or "user" to return permissions from `_get_group_permissions` or `_get_user_permissions` respectively. """ - if not user_obj.is_active or user_obj.is_anonymous() or obj is not None: + if not user_obj.is_active or user_obj.is_anonymous or obj is not None: return set() perm_cache_name = '_%s_perm_cache' % from_name @@ -73,7 +73,7 @@ class ModelBackend(object): return self._get_permissions(user_obj, obj, 'group') def get_all_permissions(self, user_obj, obj=None): - if not user_obj.is_active or user_obj.is_anonymous() or obj is not None: + if not user_obj.is_active or user_obj.is_anonymous or obj is not None: return set() if not hasattr(user_obj, '_perm_cache'): user_obj._perm_cache = self.get_user_permissions(user_obj) diff --git a/django/contrib/auth/base_user.py b/django/contrib/auth/base_user.py index 48ab950c43..c8f748cf02 100644 --- a/django/contrib/auth/base_user.py +++ b/django/contrib/auth/base_user.py @@ -10,6 +10,7 @@ from django.contrib.auth.hashers import ( ) from django.db import models from django.utils.crypto import get_random_string, salted_hmac +from django.utils.deprecation import CallableFalse, CallableTrue from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -79,19 +80,21 @@ class AbstractBaseUser(models.Model): def natural_key(self): return (self.get_username(),) + @property def is_anonymous(self): """ Always return False. This is a way of comparing User objects to anonymous users. """ - return False + return CallableFalse + @property def is_authenticated(self): """ Always return True. This is a way to tell if the user has been authenticated in templates. """ - return True + return CallableTrue def set_password(self, raw_password): self.password = make_password(raw_password) diff --git a/django/contrib/auth/decorators.py b/django/contrib/auth/decorators.py index 6ab428e9cd..9c44108c89 100644 --- a/django/contrib/auth/decorators.py +++ b/django/contrib/auth/decorators.py @@ -43,7 +43,7 @@ def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login to the log-in page if necessary. """ actual_decorator = user_passes_test( - lambda u: u.is_authenticated(), + lambda u: u.is_authenticated, login_url=login_url, redirect_field_name=redirect_field_name ) diff --git a/django/contrib/auth/middleware.py b/django/contrib/auth/middleware.py index c829038f89..476729c147 100644 --- a/django/contrib/auth/middleware.py +++ b/django/contrib/auth/middleware.py @@ -70,13 +70,13 @@ class RemoteUserMiddleware(object): # If specified header doesn't exist then remove any existing # authenticated remote-user, or return (leaving request.user set to # AnonymousUser by the AuthenticationMiddleware). - if self.force_logout_if_no_header and request.user.is_authenticated(): + if self.force_logout_if_no_header and request.user.is_authenticated: self._remove_invalid_user(request) return # If the user is already authenticated and that user is the user we are # getting passed in the headers, then the correct user is already # persisted in the session and we don't need to continue. - if request.user.is_authenticated(): + if request.user.is_authenticated: if request.user.get_username() == self.clean_username(username, request): return else: diff --git a/django/contrib/auth/mixins.py b/django/contrib/auth/mixins.py index d98c909112..4a7759435b 100644 --- a/django/contrib/auth/mixins.py +++ b/django/contrib/auth/mixins.py @@ -51,7 +51,7 @@ class LoginRequiredMixin(AccessMixin): CBV mixin which verifies that the current user is authenticated. """ def dispatch(self, request, *args, **kwargs): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return self.handle_no_permission() return super(LoginRequiredMixin, self).dispatch(request, *args, **kwargs) diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 0600b96774..2f3e297ba6 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -10,6 +10,7 @@ from django.core.mail import send_mail from django.db import models from django.db.models.manager import EmptyManager from django.utils import six, timezone +from django.utils.deprecation import CallableFalse, CallableTrue from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ @@ -438,11 +439,13 @@ class AnonymousUser(object): def has_module_perms(self, module): return _user_has_module_perms(self, module) + @property def is_anonymous(self): - return True + return CallableTrue + @property def is_authenticated(self): - return False + return CallableFalse def get_username(self): return self.username diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index ff6643b035..3cf7c36b8b 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -68,7 +68,7 @@ def login(request, template_name='registration/login.html', """ redirect_to = request.POST.get(redirect_field_name, request.GET.get(redirect_field_name, '')) - if redirect_authenticated_user and request.user.is_authenticated(): + if redirect_authenticated_user and request.user.is_authenticated: redirect_to = _get_login_redirect_url(request, redirect_to) if redirect_to == request.path: raise ValueError( diff --git a/django/contrib/flatpages/templatetags/flatpages.py b/django/contrib/flatpages/templatetags/flatpages.py index db82c4aa2c..3abe6f5433 100644 --- a/django/contrib/flatpages/templatetags/flatpages.py +++ b/django/contrib/flatpages/templatetags/flatpages.py @@ -33,7 +33,7 @@ class FlatpageNode(template.Node): # was provided, filter the list to only public flatpages. if self.user: user = self.user.resolve(context) - if not user.is_authenticated(): + if not user.is_authenticated: flatpages = flatpages.filter(registration_required=False) else: flatpages = flatpages.filter(registration_required=False) diff --git a/django/contrib/flatpages/views.py b/django/contrib/flatpages/views.py index 10e13a73be..6df0791837 100644 --- a/django/contrib/flatpages/views.py +++ b/django/contrib/flatpages/views.py @@ -52,7 +52,7 @@ def render_flatpage(request, f): """ # If registration is required for accessing this page, and the user isn't # logged in, redirect to the login page. - if f.registration_required and not request.user.is_authenticated(): + if f.registration_required and not request.user.is_authenticated: from django.contrib.auth.views import redirect_to_login return redirect_to_login(request.path) if f.template_name: diff --git a/django/utils/deprecation.py b/django/utils/deprecation.py index 905bbc4f6d..70216e8c5b 100644 --- a/django/utils/deprecation.py +++ b/django/utils/deprecation.py @@ -79,3 +79,33 @@ class DeprecationInstanceCheck(type): self.deprecation_warning, 2 ) return super(DeprecationInstanceCheck, self).__instancecheck__(instance) + + +class CallableBool: + """ + An boolean-like object that is also callable for backwards compatibility. + """ + do_not_call_in_templates = True + + def __init__(self, value): + self.value = value + + def __bool__(self): + return self.value + + def __call__(self): + warnings.warn( + "Using user.is_authenticated() and user.is_anonymous() as a method " + "is deprecated. Remove the parentheses to use it as an attribute.", + RemovedInDjango20Warning, stacklevel=2 + ) + return self.value + + def __nonzero__(self): # Python 2 compatibility + return self.value + + def __repr__(self): + return 'CallableBool(%r)' % self.value + +CallableFalse = CallableBool(False) +CallableTrue = CallableBool(True) diff --git a/docs/howto/error-reporting.txt b/docs/howto/error-reporting.txt index 9ae194d4e0..d07404a1e9 100644 --- a/docs/howto/error-reporting.txt +++ b/docs/howto/error-reporting.txt @@ -256,7 +256,7 @@ given view by setting the ``HttpRequest``’s ``exception_reporter_filter`` attribute:: def my_view(request): - if request.user.is_authenticated(): + if request.user.is_authenticated: request.exception_reporter_filter = CustomExceptionReporterFilter() ... diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 810f7d1a2b..d428f414f7 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -147,6 +147,9 @@ details on these changes. * The shim for supporting custom related manager classes without a ``_apply_rel_filters()`` method will be removed. +* Using ``User.is_authenticated()`` and ``User.is_anonymous()`` as methods + will no longer be supported. + .. _deprecation-removed-in-1.10: 1.10 diff --git a/docs/ref/contrib/auth.txt b/docs/ref/contrib/auth.txt index 831de3b5a6..6e3b8892cf 100644 --- a/docs/ref/contrib/auth.txt +++ b/docs/ref/contrib/auth.txt @@ -111,6 +111,41 @@ Fields A datetime designating when the account was created. Is set to the current date/time by default when the account is created. +Attributes +---------- + +.. class:: models.User + + .. attribute:: is_authenticated + + Read-only attribute which is always ``True`` (as opposed to + ``AnonymousUser.is_authenticated`` which is always ``False``). This is + a way to tell if the user has been authenticated. This does not imply + any permissions and doesn't check if the user is active or has a valid + session. Even though normally you will check this attribute on + ``request.user`` to find out whether it has been populated by the + :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` + (representing the currently logged-in user), you should know this + attribute is ``True`` for any :class:`~models.User` instance. + + .. versionchanged:: 1.10 + + In older versions, this was a method. Backwards-compatibility + support for using it as a method will be removed in Django 2.0. + + .. attribute:: is_anonymous + + Read-only attribute which is always ``False``. This is a way of + differentiating :class:`~models.User` and :class:`~models.AnonymousUser` + objects. Generally, you should prefer using + :attr:`~django.contrib.auth.models.User.is_authenticated` to this + attribute. + + .. versionchanged:: 1.10 + + In older versions, this was a method. Backwards-compatibility + support for using it as a method will be removed in Django 2.0. + Methods ------- @@ -119,31 +154,9 @@ Methods .. method:: get_username() Returns the username for the user. Since the User model can be swapped - out, you should use this method instead of referencing the username + out, you should use this method instead of referencing the username attribute directly. - .. method:: is_anonymous() - - Always returns ``False``. This is a way of differentiating - :class:`~django.contrib.auth.models.User` and - :class:`~django.contrib.auth.models.AnonymousUser` objects. - Generally, you should prefer using - :meth:`~django.contrib.auth.models.User.is_authenticated()` to this - method. - - .. method:: is_authenticated() - - Always returns ``True`` (as opposed to - ``AnonymousUser.is_authenticated()`` which always returns ``False``). - This is a way to tell if the user has been authenticated. This does not - imply any permissions, and doesn't check if the user is active or has - a valid session. Even though normally you will call this method on - ``request.user`` to find out whether it has been populated by the - :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` - (representing the currently logged-in user), you should know this method - returns ``True`` for any :class:`~django.contrib.auth.models.User` - instance. - .. method:: get_full_name() Returns the :attr:`~django.contrib.auth.models.User.first_name` plus @@ -287,6 +300,10 @@ Manager methods string. * :meth:`~django.contrib.auth.models.User.get_username()` always returns the empty string. + * :attr:`~django.contrib.auth.models.User.is_anonymous` is ``True`` + instead of ``False``. + * :attr:`~django.contrib.auth.models.User.is_authenticated` is + ``False`` instead of ``True``. * :attr:`~django.contrib.auth.models.User.is_staff` and :attr:`~django.contrib.auth.models.User.is_superuser` are always ``False``. @@ -294,10 +311,6 @@ Manager methods * :attr:`~django.contrib.auth.models.User.groups` and :attr:`~django.contrib.auth.models.User.user_permissions` are always empty. - * :meth:`~django.contrib.auth.models.User.is_anonymous()` returns ``True`` - instead of ``False``. - * :meth:`~django.contrib.auth.models.User.is_authenticated()` returns - ``False`` instead of ``True``. * :meth:`~django.contrib.auth.models.User.set_password()`, :meth:`~django.contrib.auth.models.User.check_password()`, :meth:`~django.db.models.Model.save` and @@ -471,21 +484,21 @@ The following backends are available in :mod:`django.contrib.auth.backends`: Returns the set of permission strings the ``user_obj`` has from their own user permissions. Returns an empty set if - :meth:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or + :attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or :attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``. .. method:: get_group_permissions(user_obj, obj=None) Returns the set of permission strings the ``user_obj`` has from the permissions of the groups they belong. Returns an empty set if - :meth:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or + :attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or :attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``. .. method:: get_all_permissions(user_obj, obj=None) Returns the set of permission strings the ``user_obj`` has, including both user permissions and group permissions. Returns an empty set if - :meth:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or + :attr:`~django.contrib.auth.models.AbstractBaseUser.is_anonymous` or :attr:`~django.contrib.auth.models.CustomUser.is_active` is ``False``. .. method:: has_perm(user_obj, perm, obj=None) diff --git a/docs/ref/request-response.txt b/docs/ref/request-response.txt index acf195803b..a1be4d64b9 100644 --- a/docs/ref/request-response.txt +++ b/docs/ref/request-response.txt @@ -233,9 +233,9 @@ middleware class is listed in :setting:`MIDDLEWARE_CLASSES`. logged-in user. If the user isn't currently logged in, ``user`` will be set to an instance of :class:`~django.contrib.auth.models.AnonymousUser`. You can tell them apart with - :meth:`~django.contrib.auth.models.User.is_authenticated`, like so:: + :attr:`~django.contrib.auth.models.User.is_authenticated`, like so:: - if request.user.is_authenticated(): + if request.user.is_authenticated: ... # Do something for logged-in users. else: ... # Do something for anonymous users. diff --git a/docs/releases/1.10.txt b/docs/releases/1.10.txt index 2002905b2d..463cd4706e 100644 --- a/docs/releases/1.10.txt +++ b/docs/releases/1.10.txt @@ -730,6 +730,10 @@ Miscellaneous * Middleware classes are now initialized when the server starts rather than during the first request. +* If you override ``is_authenticated()`` or ``is_anonymous()`` in a custom user + model, you must convert them to attributes or properties as described in + :ref:`the deprecation note `. + .. _deprecated-features-1.10: Features deprecated in 1.10 @@ -857,6 +861,37 @@ features, is deprecated. Replace it with a custom lookup:: models.CharField.register_lookup(Search) models.TextField.register_lookup(Search) +.. _user-is-auth-anon-deprecation: + +Using ``User.is_authenticated()`` and ``User.is_anonymous()`` as methods +------------------------------------------------------------------------ + +The ``is_authenticated()`` and ``is_anonymous()`` methods of +:class:`~django.contrib.auth.models.AbstractBaseUser` and +:class:`~django.contrib.auth.models.AnonymousUser` classes are now +properties. They will still work as methods until Django 2.0, but all usage +in Django now uses attribute access. + +For example, if you use +:class:`~django.contrib.auth.middleware.AuthenticationMiddleware` and want +to know whether the user is currently logged-in you would use:: + + if request.user.is_authenticated: + ... # Do something for logged-in users. + else: + ... # Do something for anonymous users. + +instead of ``request.user.is_authenticated()``. + +This change avoids accidental information leakage if you forget to call the +method, e.g.:: + + if request.user.is_authenticated: + return sensitive_information + +If you override these methods in a custom user model, you must change them to +properties or attributes. + Custom manager classes available through ``prefetch_related`` must define a ``_apply_rel_filters()`` method ----------------------------------------------------------------------------------------------------------- diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index 271d7f52f7..68ca0e7f55 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -603,7 +603,7 @@ password resets. You must then provide some key implementation details: raised a deprecation warning in older versions and is no longer supported in Django 1.9). -The following methods are available on any subclass of +The following attributes and methods are available on any subclass of :class:`~django.contrib.auth.models.AbstractBaseUser`: .. class:: models.AbstractBaseUser @@ -612,20 +612,34 @@ The following methods are available on any subclass of Returns the value of the field nominated by ``USERNAME_FIELD``. - .. method:: models.AbstractBaseUser.is_anonymous() + .. attribute:: models.AbstractBaseUser.is_authenticated - Always returns ``False``. This is a way of differentiating - from :class:`~django.contrib.auth.models.AnonymousUser` objects. - Generally, you should prefer using - :meth:`~django.contrib.auth.models.AbstractBaseUser.is_authenticated()` to this - method. + Read-only attribute which is always ``True`` (as opposed to + ``AnonymousUser.is_authenticated`` which is always ``False``). + This is a way to tell if the user has been authenticated. This does not + imply any permissions and doesn't check if the user is active or has + a valid session. Even though normally you will check this attribute on + ``request.user`` to find out whether it has been populated by the + :class:`~django.contrib.auth.middleware.AuthenticationMiddleware` + (representing the currently logged-in user), you should know this + attribute is ``True`` for any :class:`~models.User` instance. - .. method:: models.AbstractBaseUser.is_authenticated() + .. versionchanged:: 1.10 - Always returns ``True``. This is a way to tell if the user has been - authenticated. This does not imply any permissions, and doesn't check - if the user is active - it only indicates that the user has provided a - valid username and password. + In older versions, this was a method. Backwards-compatibility + support for using it as a method will be removed in Django 2.0. + + .. attribute:: models.AbstractBaseUser.is_anonymous + + Read-only attribute which is always ``False``. This is a way of + differentiating :class:`~models.User` and :class:`~models.AnonymousUser` + objects. Generally, you should prefer using + :attr:`~models.User.is_authenticated` to this attribute. + + .. versionchanged:: 1.10 + + In older versions, this was a method. Backwards-compatibility + support for using it as a method will be removed in Django 2.0. .. method:: models.AbstractBaseUser.set_password(raw_password) diff --git a/docs/topics/auth/default.txt b/docs/topics/auth/default.txt index 72205740d5..4c8d44a0cd 100644 --- a/docs/topics/auth/default.txt +++ b/docs/topics/auth/default.txt @@ -306,9 +306,9 @@ of :class:`~django.contrib.auth.models.AnonymousUser`, otherwise it will be an instance of :class:`~django.contrib.auth.models.User`. You can tell them apart with -:meth:`~django.contrib.auth.models.User.is_authenticated()`, like so:: +:attr:`~django.contrib.auth.models.User.is_authenticated`, like so:: - if request.user.is_authenticated(): + if request.user.is_authenticated: # Do something for authenticated users. ... else: @@ -421,15 +421,15 @@ The raw way ~~~~~~~~~~~ The simple, raw way to limit access to pages is to check -:meth:`request.user.is_authenticated() -` and either redirect to a +:attr:`request.user.is_authenticated +` and either redirect to a login page:: from django.conf import settings from django.shortcuts import redirect def my_view(request): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return redirect('%s?next=%s' % (settings.LOGIN_URL, request.path)) # ... @@ -438,7 +438,7 @@ login page:: from django.shortcuts import render def my_view(request): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return render(request, 'myapp/login_error.html') # ... diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 4cfffeeecf..b0bf37c94c 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -1170,7 +1170,7 @@ decorator):: @vary_on_cookie def list_blog_entries_view(request): - if request.user.is_anonymous(): + if request.user.is_anonymous: response = render_only_public_entries() patch_cache_control(response, public=True) else: diff --git a/docs/topics/class-based-views/mixins.txt b/docs/topics/class-based-views/mixins.txt index e53a8599e4..a5bb290d2b 100644 --- a/docs/topics/class-based-views/mixins.txt +++ b/docs/topics/class-based-views/mixins.txt @@ -235,7 +235,7 @@ We'll demonstrate this with the ``Author`` model we used in the model = Author def post(self, request, *args, **kwargs): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return HttpResponseForbidden() # Look up the author we're interested in. @@ -466,7 +466,7 @@ Our new ``AuthorDetail`` looks like this:: return context def post(self, request, *args, **kwargs): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return HttpResponseForbidden() self.object = self.get_object() form = self.get_form() @@ -552,7 +552,7 @@ template as ``AuthorDisplay`` is using on ``GET``:: model = Author def post(self, request, *args, **kwargs): - if not request.user.is_authenticated(): + if not request.user.is_authenticated: return HttpResponseForbidden() self.object = self.get_object() return super(AuthorInterest, self).post(request, *args, **kwargs) diff --git a/tests/auth_tests/test_auth_backends.py b/tests/auth_tests/test_auth_backends.py index e3c0109c96..91f92397a2 100644 --- a/tests/auth_tests/test_auth_backends.py +++ b/tests/auth_tests/test_auth_backends.py @@ -12,7 +12,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.http import HttpRequest from django.test import ( - SimpleTestCase, TestCase, modify_settings, override_settings, + SimpleTestCase, TestCase, mock, modify_settings, override_settings, ) from .models import ( @@ -142,11 +142,10 @@ class BaseModelBackendTest(object): self.assertEqual(backend.get_user_permissions(user), {'auth.test_user', 'auth.test_group'}) self.assertEqual(backend.get_group_permissions(user), {'auth.test_group'}) - user.is_anonymous = lambda: True - - self.assertEqual(backend.get_all_permissions(user), set()) - self.assertEqual(backend.get_user_permissions(user), set()) - self.assertEqual(backend.get_group_permissions(user), set()) + with mock.patch.object(self.UserModel, 'is_anonymous', True): + self.assertEqual(backend.get_all_permissions(user), set()) + self.assertEqual(backend.get_user_permissions(user), set()) + self.assertEqual(backend.get_group_permissions(user), set()) def test_inactive_has_no_permissions(self): """ @@ -334,14 +333,14 @@ class SimpleRowlevelBackend(object): if isinstance(obj, TestObj): if user.username == 'test2': return True - elif user.is_anonymous() and perm == 'anon': + elif user.is_anonymous and perm == 'anon': return True elif not user.is_active and perm == 'inactive': return True return False def has_module_perms(self, user, app_label): - if not user.is_anonymous() and not user.is_active: + if not user.is_anonymous and not user.is_active: return False return app_label == "app1" @@ -352,7 +351,7 @@ class SimpleRowlevelBackend(object): if not isinstance(obj, TestObj): return ['none'] - if user.is_anonymous(): + if user.is_anonymous: return ['anon'] if user.username == 'test2': return ['simple', 'advanced'] @@ -578,7 +577,7 @@ class ChangedBackendSettingsTest(TestCase): # Assert that the user retrieval is successful and the user is # anonymous as the backend is not longer available. self.assertIsNotNone(user) - self.assertTrue(user.is_anonymous()) + self.assertTrue(user.is_anonymous) class TypeErrorBackend(object): diff --git a/tests/auth_tests/test_basic.py b/tests/auth_tests/test_basic.py index 81b7ac9c56..818f6a6d53 100644 --- a/tests/auth_tests/test_basic.py +++ b/tests/auth_tests/test_basic.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +import warnings + from django.apps import apps from django.contrib.auth import get_user_model from django.contrib.auth.models import AnonymousUser, User @@ -44,7 +46,8 @@ class BasicTestCase(TestCase): self.assertEqual(u.get_username(), 'testuser') # Check authentication/permissions - self.assertTrue(u.is_authenticated()) + self.assertFalse(u.is_anonymous) + self.assertTrue(u.is_authenticated) self.assertFalse(u.is_staff) self.assertTrue(u.is_active) self.assertFalse(u.is_superuser) @@ -53,6 +56,26 @@ class BasicTestCase(TestCase): u2 = User.objects.create_user('testuser2', 'test2@example.com') self.assertFalse(u2.has_usable_password()) + def test_is_anonymous_authenticated_method_deprecation(self): + deprecation_message = ( + 'Using user.is_authenticated() and user.is_anonymous() as a ' + 'method is deprecated. Remove the parentheses to use it as an ' + 'attribute.' + ) + u = User.objects.create_user('testuser', 'test@example.com', 'testpw') + # Backwards-compatibility callables + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + self.assertFalse(u.is_anonymous()) + self.assertEqual(len(warns), 1) + self.assertEqual(str(warns[0].message), deprecation_message) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') + self.assertTrue(u.is_authenticated()) + self.assertEqual(len(warns), 1) + self.assertEqual(str(warns[0].message), deprecation_message) + def test_user_no_email(self): "Check that users can be created without an email" u = User.objects.create_user('testuser1') @@ -70,13 +93,34 @@ class BasicTestCase(TestCase): self.assertEqual(a.pk, None) self.assertEqual(a.username, '') self.assertEqual(a.get_username(), '') - self.assertFalse(a.is_authenticated()) + self.assertTrue(a.is_anonymous) + self.assertFalse(a.is_authenticated) self.assertFalse(a.is_staff) self.assertFalse(a.is_active) self.assertFalse(a.is_superuser) self.assertEqual(a.groups.all().count(), 0) self.assertEqual(a.user_permissions.all().count(), 0) + def test_anonymous_user_is_anonymous_authenticated_method_deprecation(self): + a = AnonymousUser() + deprecation_message = ( + 'Using user.is_authenticated() and user.is_anonymous() as a ' + 'method is deprecated. Remove the parentheses to use it as an ' + 'attribute.' + ) + # Backwards-compatibility callables + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') # prevent warnings from appearing as errors + self.assertTrue(a.is_anonymous()) + self.assertEqual(len(warns), 1) + self.assertEqual(str(warns[0].message), deprecation_message) + + with warnings.catch_warnings(record=True) as warns: + warnings.simplefilter('always') # prevent warnings from appearing as errors + self.assertFalse(a.is_authenticated()) + self.assertEqual(len(warns), 1) + self.assertEqual(str(warns[0].message), deprecation_message) + def test_superuser(self): "Check the creation and properties of a superuser" super = User.objects.create_superuser('super', 'super@example.com', 'super') diff --git a/tests/auth_tests/test_middleware.py b/tests/auth_tests/test_middleware.py index 9ebb1e46ee..635c43beb0 100644 --- a/tests/auth_tests/test_middleware.py +++ b/tests/auth_tests/test_middleware.py @@ -16,7 +16,7 @@ class TestAuthenticationMiddleware(TestCase): self.request.session = self.client.session self.middleware.process_request(self.request) self.assertIsNotNone(self.request.user) - self.assertFalse(self.request.user.is_anonymous()) + self.assertFalse(self.request.user.is_anonymous) def test_changed_password_invalidates_session(self): # After password change, user should be anonymous @@ -24,6 +24,6 @@ class TestAuthenticationMiddleware(TestCase): self.user.save() self.middleware.process_request(self.request) self.assertIsNotNone(self.request.user) - self.assertTrue(self.request.user.is_anonymous()) + self.assertTrue(self.request.user.is_anonymous) # session should be flushed self.assertIsNone(self.request.session.session_key) diff --git a/tests/auth_tests/test_mixins.py b/tests/auth_tests/test_mixins.py index 22e5709bc3..5df5ee3c4e 100644 --- a/tests/auth_tests/test_mixins.py +++ b/tests/auth_tests/test_mixins.py @@ -5,7 +5,7 @@ from django.contrib.auth.mixins import ( from django.contrib.auth.models import AnonymousUser from django.core.exceptions import PermissionDenied from django.http import HttpResponse -from django.test import RequestFactory, TestCase +from django.test import RequestFactory, TestCase, mock from django.views.generic import View @@ -78,9 +78,9 @@ class AccessMixinTests(TestCase): with self.assertRaises(PermissionDenied): view(request) + @mock.patch.object(models.User, 'is_authenticated', False) def test_stacked_mixins_not_logged_in(self): user = models.User.objects.create(username='joe', password='qwerty') - user.is_authenticated = lambda: False perms = models.Permission.objects.filter(codename__in=('add_customuser', 'change_customuser')) user.user_permissions.add(*perms) request = self.factory.get('/rand') diff --git a/tests/auth_tests/test_remote_user.py b/tests/auth_tests/test_remote_user.py index 4e916d80ec..3d77ea3b89 100644 --- a/tests/auth_tests/test_remote_user.py +++ b/tests/auth_tests/test_remote_user.py @@ -38,15 +38,15 @@ class RemoteUserTest(TestCase): num_users = User.objects.count() response = self.client.get('/remote_user/') - self.assertTrue(response.context['user'].is_anonymous()) + self.assertTrue(response.context['user'].is_anonymous) self.assertEqual(User.objects.count(), num_users) response = self.client.get('/remote_user/', **{self.header: None}) - self.assertTrue(response.context['user'].is_anonymous()) + self.assertTrue(response.context['user'].is_anonymous) self.assertEqual(User.objects.count(), num_users) response = self.client.get('/remote_user/', **{self.header: ''}) - self.assertTrue(response.context['user'].is_anonymous()) + self.assertTrue(response.context['user'].is_anonymous) self.assertEqual(User.objects.count(), num_users) def test_unknown_user(self): @@ -118,7 +118,7 @@ class RemoteUserTest(TestCase): self.assertEqual(response.context['user'].username, 'knownuser') # During the session, the REMOTE_USER header disappears. Should trigger logout. response = self.client.get('/remote_user/') - self.assertEqual(response.context['user'].is_anonymous(), True) + self.assertTrue(response.context['user'].is_anonymous) # verify the remoteuser middleware will not remove a user # authenticated via another backend User.objects.create_user(username='modeluser', password='foo') @@ -148,7 +148,7 @@ class RemoteUserTest(TestCase): def test_inactive_user(self): User.objects.create(username='knownuser', is_active=False) response = self.client.get('/remote_user/', **{self.header: 'knownuser'}) - self.assertTrue(response.context['user'].is_anonymous()) + self.assertTrue(response.context['user'].is_anonymous) class RemoteUserNoCreateBackend(RemoteUserBackend): @@ -167,7 +167,7 @@ class RemoteUserNoCreateTest(RemoteUserTest): def test_unknown_user(self): num_users = User.objects.count() response = self.client.get('/remote_user/', **{self.header: 'newuser'}) - self.assertTrue(response.context['user'].is_anonymous()) + self.assertTrue(response.context['user'].is_anonymous) self.assertEqual(User.objects.count(), num_users) @@ -268,5 +268,5 @@ class PersistentRemoteUserTest(RemoteUserTest): self.assertEqual(response.context['user'].username, 'knownuser') # Should stay logged in if the REMOTE_USER header disappears. response = self.client.get('/remote_user/') - self.assertEqual(response.context['user'].is_anonymous(), False) + self.assertFalse(response.context['user'].is_anonymous) self.assertEqual(response.context['user'].username, 'knownuser') diff --git a/tests/urlpatterns_reverse/views.py b/tests/urlpatterns_reverse/views.py index acfa530b4c..1d1d3d557a 100644 --- a/tests/urlpatterns_reverse/views.py +++ b/tests/urlpatterns_reverse/views.py @@ -45,7 +45,7 @@ class LazyRedirectView(RedirectView): url = reverse_lazy('named-lazy-url-redirected-to') -@user_passes_test(lambda u: u.is_authenticated(), login_url=reverse_lazy('some-login-page')) +@user_passes_test(lambda u: u.is_authenticated, login_url=reverse_lazy('some-login-page')) def login_required_view(request): return HttpResponse('Hello you')