From cb7bbf97a74fa7800865e3615f196ad65dc4f281 Mon Sep 17 00:00:00 2001 From: Aymeric Augustin Date: Fri, 30 Sep 2016 22:06:02 +0200 Subject: [PATCH] Fixed #25966 -- Made get_user_model() work at import time. This makes it equivalent to: `from django.contrib.auth.models import User`. Thanks Aymeric Augustin for the initial patch and Tim Graham for the review. --- django/contrib/auth/__init__.py | 2 +- django/contrib/auth/backends.py | 6 ++-- django/contrib/auth/forms.py | 4 +-- django/contrib/auth/handlers/modwsgi.py | 4 +-- .../management/commands/changepassword.py | 4 +-- django/contrib/auth/views.py | 4 +-- django/test/signals.py | 22 ++++++++++++++ docs/ref/applications.txt | 4 --- docs/releases/1.11.txt | 3 ++ docs/topics/auth/customizing.txt | 30 +++++++++++++++++-- 10 files changed, 63 insertions(+), 20 deletions(-) diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py index 7adb8a022f..04a7f7ddcb 100644 --- a/django/contrib/auth/__init__.py +++ b/django/contrib/auth/__init__.py @@ -172,7 +172,7 @@ def get_user_model(): Returns the User model that is active in this project. """ try: - return django_apps.get_model(settings.AUTH_USER_MODEL) + return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False) except ValueError: raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'") except LookupError: diff --git a/django/contrib/auth/backends.py b/django/contrib/auth/backends.py index 379f1c4450..8df95db5ee 100644 --- a/django/contrib/auth/backends.py +++ b/django/contrib/auth/backends.py @@ -3,6 +3,8 @@ from __future__ import unicode_literals from django.contrib.auth import get_user_model from django.contrib.auth.models import Permission +UserModel = get_user_model() + class ModelBackend(object): """ @@ -10,7 +12,6 @@ class ModelBackend(object): """ def authenticate(self, request, username=None, password=None, **kwargs): - UserModel = get_user_model() if username is None: username = kwargs.get(UserModel.USERNAME_FIELD) try: @@ -97,7 +98,6 @@ class ModelBackend(object): return False def get_user(self, user_id): - UserModel = get_user_model() try: user = UserModel._default_manager.get(pk=user_id) except UserModel.DoesNotExist: @@ -139,8 +139,6 @@ class RemoteUserBackend(ModelBackend): user = None username = self.clean_username(remote_user) - UserModel = get_user_model() - # Note that this could be accomplished in one try-except clause, but # instead we use get_or_create when creating unknown users since it has # built-in safeguards for multiple threads. diff --git a/django/contrib/auth/forms.py b/django/contrib/auth/forms.py index dbdc08db64..a5d0375f58 100644 --- a/django/contrib/auth/forms.py +++ b/django/contrib/auth/forms.py @@ -22,6 +22,8 @@ from django.utils.safestring import mark_safe from django.utils.text import capfirst from django.utils.translation import ugettext, ugettext_lazy as _ +UserModel = get_user_model() + class ReadOnlyPasswordHashWidget(forms.Widget): def render(self, name, value, attrs): @@ -179,7 +181,6 @@ class AuthenticationForm(forms.Form): super(AuthenticationForm, self).__init__(*args, **kwargs) # Set the label for the "username" field. - UserModel = get_user_model() self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD) if self.fields['username'].label is None: self.fields['username'].label = capfirst(self.username_field.verbose_name) @@ -254,7 +255,6 @@ class PasswordResetForm(forms.Form): that prevent inactive users and users with unusable passwords from resetting their password. """ - UserModel = get_user_model() active_users = UserModel._default_manager.filter(**{ '%s__iexact' % UserModel.get_email_field_name(): email, 'is_active': True, diff --git a/django/contrib/auth/handlers/modwsgi.py b/django/contrib/auth/handlers/modwsgi.py index 6b91ed9c6e..6bd29e79b1 100644 --- a/django/contrib/auth/handlers/modwsgi.py +++ b/django/contrib/auth/handlers/modwsgi.py @@ -2,6 +2,8 @@ from django import db from django.contrib import auth from django.utils.encoding import force_bytes +UserModel = auth.get_user_model() + def check_password(environ, username, password): """ @@ -11,7 +13,6 @@ def check_password(environ, username, password): on whether the user exists and authenticates. """ - UserModel = auth.get_user_model() # db connection state is managed similarly to the wsgi handler # as mod_wsgi may call these functions outside of a request/response cycle db.reset_queries() @@ -33,7 +34,6 @@ def groups_for_user(environ, username): Authorizes a user based on groups """ - UserModel = auth.get_user_model() db.reset_queries() try: diff --git a/django/contrib/auth/management/commands/changepassword.py b/django/contrib/auth/management/commands/changepassword.py index 173ea2ae27..1c4eee3b91 100644 --- a/django/contrib/auth/management/commands/changepassword.py +++ b/django/contrib/auth/management/commands/changepassword.py @@ -9,6 +9,8 @@ from django.core.management.base import BaseCommand, CommandError from django.db import DEFAULT_DB_ALIAS from django.utils.encoding import force_str +UserModel = get_user_model() + class Command(BaseCommand): help = "Change a user's password for django.contrib.auth." @@ -38,8 +40,6 @@ class Command(BaseCommand): else: username = getpass.getuser() - UserModel = get_user_model() - try: u = UserModel._default_manager.using(options['database']).get(**{ UserModel.USERNAME_FIELD: username diff --git a/django/contrib/auth/views.py b/django/contrib/auth/views.py index 08c3510988..8edf02a7d1 100644 --- a/django/contrib/auth/views.py +++ b/django/contrib/auth/views.py @@ -31,6 +31,8 @@ from django.views.decorators.debug import sensitive_post_parameters from django.views.generic.base import TemplateView from django.views.generic.edit import FormView +UserModel = get_user_model() + def deprecate_current_app(func): """ @@ -320,7 +322,6 @@ def password_reset_confirm(request, uidb64=None, token=None, warnings.warn("The password_reset_confirm() view is superseded by the " "class-based PasswordResetConfirmView().", RemovedInDjango21Warning, stacklevel=2) - UserModel = get_user_model() assert uidb64 is not None and token is not None # checked by URLconf if post_reset_redirect is None: post_reset_redirect = reverse('password_reset_complete') @@ -453,7 +454,6 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView): return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs) def get_user(self, uidb64): - UserModel = get_user_model() try: # urlsafe_base64_decode() decodes to bytestring on Python 3 uid = force_text(urlsafe_base64_decode(uidb64)) diff --git a/django/test/signals.py b/django/test/signals.py index 98506609e1..371b90c9ee 100644 --- a/django/test/signals.py +++ b/django/test/signals.py @@ -4,6 +4,7 @@ import time import warnings from django.apps import apps +from django.core.exceptions import ImproperlyConfigured from django.core.signals import setting_changed from django.db import connections, router from django.db.utils import ConnectionRouter @@ -172,3 +173,24 @@ def auth_password_validators_changed(**kwargs): def user_model_swapped(**kwargs): if kwargs['setting'] == 'AUTH_USER_MODEL': apps.clear_cache() + try: + from django.contrib.auth import get_user_model + UserModel = get_user_model() + except ImproperlyConfigured: + # Some tests set an invalid AUTH_USER_MODEL. + pass + else: + from django.contrib.auth import backends + backends.UserModel = UserModel + + from django.contrib.auth import forms + forms.UserModel = UserModel + + from django.contrib.auth.handlers import modwsgi + modwsgi.UserModel = UserModel + + from django.contrib.auth.management.commands import changepassword + changepassword.UserModel = UserModel + + from django.contrib.auth import views + views.UserModel = UserModel diff --git a/docs/ref/applications.txt b/docs/ref/applications.txt index 149312aafd..c3a06ab855 100644 --- a/docs/ref/applications.txt +++ b/docs/ref/applications.txt @@ -473,10 +473,6 @@ Here are some common problems that you may encounter during initialization: will also trigger this exception. The ORM cannot function properly until all models are available. - Another common culprit is :func:`django.contrib.auth.get_user_model()`. Use - the :setting:`AUTH_USER_MODEL` setting to reference the User model at import - time. - This exception also happens if you forget to call :func:`django.setup()` in a standalone Python script. diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt index 53265272ca..c8c64ab9c9 100644 --- a/docs/releases/1.11.txt +++ b/docs/releases/1.11.txt @@ -125,6 +125,9 @@ Minor features Set :attr:`CustomUser.EMAIL_FIELD ` to the name of the field. +* :func:`~django.contrib.auth.get_user_model` can now be called at import time, + even in modules that define models. + :mod:`django.contrib.contenttypes` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/auth/customizing.txt b/docs/topics/auth/customizing.txt index 1b9dc6539e..79ffa0a88b 100644 --- a/docs/topics/auth/customizing.txt +++ b/docs/topics/auth/customizing.txt @@ -487,9 +487,33 @@ different user model. post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL) - Generally speaking, you should reference the user model with the - :setting:`AUTH_USER_MODEL` setting in code that is executed at import - time. ``get_user_model()`` only works once Django has imported all models. + Generally speaking, it's easiest to refer to the user model with the + :setting:`AUTH_USER_MODEL` setting in code that's executed at import time, + however, it's also possible to call ``get_user_model()`` while Django + is importing models, so you could use + ``models.ForeignKey(get_user_model(), ...)``. + + If your app is tested with multiple user models, using + ``@override_settings(AUTH_USER_MODEL=...)`` for example, and you cache the + result of ``get_user_model()`` in a module-level variable, you may need to + listen to the :data:`~django.test.signals.setting_changed` signal to clear + the cache. For example:: + + from django.apps import apps + from django.contrib.auth import get_user_model + from django.core.signals import setting_changed + from django.dispatch import receiver + + @receiver(setting_changed) + def user_model_swapped(**kwargs): + if kwargs['setting'] == 'AUTH_USER_MODEL': + apps.clear_cache() + from myapp import some_module + some_module.UserModel = get_user_model() + + .. versionchanged:: 1.11 + + The ability to call ``get_user_model()`` at import time was added. .. _specifying-custom-user-model: