Browse Source

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.
Aymeric Augustin 8 years ago
parent
commit
cb7bbf97a7

+ 1 - 1
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:

+ 2 - 4
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.

+ 2 - 2
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,

+ 2 - 2
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:

+ 2 - 2
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

+ 2 - 2
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))

+ 22 - 0
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

+ 0 - 4
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.
 

+ 3 - 0
docs/releases/1.11.txt

@@ -125,6 +125,9 @@ Minor features
   Set :attr:`CustomUser.EMAIL_FIELD
   <django.contrib.auth.models.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`
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

+ 27 - 3
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: