Skip to content

Commit cb7bbf9

Browse files
aaugustinMarkusH
authored andcommitted
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.
1 parent eb42d8d commit cb7bbf9

File tree

10 files changed

+63
-20
lines changed

10 files changed

+63
-20
lines changed

django/contrib/auth/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ def get_user_model():
172172
Returns the User model that is active in this project.
173173
"""
174174
try:
175-
return django_apps.get_model(settings.AUTH_USER_MODEL)
175+
return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False)
176176
except ValueError:
177177
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
178178
except LookupError:

django/contrib/auth/backends.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@
33
from django.contrib.auth import get_user_model
44
from django.contrib.auth.models import Permission
55

6+
UserModel = get_user_model()
7+
68

79
class ModelBackend(object):
810
"""
911
Authenticates against settings.AUTH_USER_MODEL.
1012
"""
1113

1214
def authenticate(self, request, username=None, password=None, **kwargs):
13-
UserModel = get_user_model()
1415
if username is None:
1516
username = kwargs.get(UserModel.USERNAME_FIELD)
1617
try:
@@ -97,7 +98,6 @@ def has_module_perms(self, user_obj, app_label):
9798
return False
9899

99100
def get_user(self, user_id):
100-
UserModel = get_user_model()
101101
try:
102102
user = UserModel._default_manager.get(pk=user_id)
103103
except UserModel.DoesNotExist:
@@ -139,8 +139,6 @@ def authenticate(self, request, remote_user):
139139
user = None
140140
username = self.clean_username(remote_user)
141141

142-
UserModel = get_user_model()
143-
144142
# Note that this could be accomplished in one try-except clause, but
145143
# instead we use get_or_create when creating unknown users since it has
146144
# built-in safeguards for multiple threads.

django/contrib/auth/forms.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from django.utils.text import capfirst
2323
from django.utils.translation import ugettext, ugettext_lazy as _
2424

25+
UserModel = get_user_model()
26+
2527

2628
class ReadOnlyPasswordHashWidget(forms.Widget):
2729
def render(self, name, value, attrs):
@@ -179,7 +181,6 @@ def __init__(self, request=None, *args, **kwargs):
179181
super(AuthenticationForm, self).__init__(*args, **kwargs)
180182

181183
# Set the label for the "username" field.
182-
UserModel = get_user_model()
183184
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
184185
if self.fields['username'].label is None:
185186
self.fields['username'].label = capfirst(self.username_field.verbose_name)
@@ -254,7 +255,6 @@ def get_users(self, email):
254255
that prevent inactive users and users with unusable passwords from
255256
resetting their password.
256257
"""
257-
UserModel = get_user_model()
258258
active_users = UserModel._default_manager.filter(**{
259259
'%s__iexact' % UserModel.get_email_field_name(): email,
260260
'is_active': True,

django/contrib/auth/handlers/modwsgi.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from django.contrib import auth
33
from django.utils.encoding import force_bytes
44

5+
UserModel = auth.get_user_model()
6+
57

68
def check_password(environ, username, password):
79
"""
@@ -11,7 +13,6 @@ def check_password(environ, username, password):
1113
on whether the user exists and authenticates.
1214
"""
1315

14-
UserModel = auth.get_user_model()
1516
# db connection state is managed similarly to the wsgi handler
1617
# as mod_wsgi may call these functions outside of a request/response cycle
1718
db.reset_queries()
@@ -33,7 +34,6 @@ def groups_for_user(environ, username):
3334
Authorizes a user based on groups
3435
"""
3536

36-
UserModel = auth.get_user_model()
3737
db.reset_queries()
3838

3939
try:

django/contrib/auth/management/commands/changepassword.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
from django.db import DEFAULT_DB_ALIAS
1010
from django.utils.encoding import force_str
1111

12+
UserModel = get_user_model()
13+
1214

1315
class Command(BaseCommand):
1416
help = "Change a user's password for django.contrib.auth."
@@ -38,8 +40,6 @@ def handle(self, *args, **options):
3840
else:
3941
username = getpass.getuser()
4042

41-
UserModel = get_user_model()
42-
4343
try:
4444
u = UserModel._default_manager.using(options['database']).get(**{
4545
UserModel.USERNAME_FIELD: username

django/contrib/auth/views.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
from django.views.generic.base import TemplateView
3232
from django.views.generic.edit import FormView
3333

34+
UserModel = get_user_model()
35+
3436

3537
def deprecate_current_app(func):
3638
"""
@@ -320,7 +322,6 @@ def password_reset_confirm(request, uidb64=None, token=None,
320322
warnings.warn("The password_reset_confirm() view is superseded by the "
321323
"class-based PasswordResetConfirmView().",
322324
RemovedInDjango21Warning, stacklevel=2)
323-
UserModel = get_user_model()
324325
assert uidb64 is not None and token is not None # checked by URLconf
325326
if post_reset_redirect is None:
326327
post_reset_redirect = reverse('password_reset_complete')
@@ -453,7 +454,6 @@ def dispatch(self, *args, **kwargs):
453454
return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)
454455

455456
def get_user(self, uidb64):
456-
UserModel = get_user_model()
457457
try:
458458
# urlsafe_base64_decode() decodes to bytestring on Python 3
459459
uid = force_text(urlsafe_base64_decode(uidb64))

django/test/signals.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import warnings
55

66
from django.apps import apps
7+
from django.core.exceptions import ImproperlyConfigured
78
from django.core.signals import setting_changed
89
from django.db import connections, router
910
from django.db.utils import ConnectionRouter
@@ -172,3 +173,24 @@ def auth_password_validators_changed(**kwargs):
172173
def user_model_swapped(**kwargs):
173174
if kwargs['setting'] == 'AUTH_USER_MODEL':
174175
apps.clear_cache()
176+
try:
177+
from django.contrib.auth import get_user_model
178+
UserModel = get_user_model()
179+
except ImproperlyConfigured:
180+
# Some tests set an invalid AUTH_USER_MODEL.
181+
pass
182+
else:
183+
from django.contrib.auth import backends
184+
backends.UserModel = UserModel
185+
186+
from django.contrib.auth import forms
187+
forms.UserModel = UserModel
188+
189+
from django.contrib.auth.handlers import modwsgi
190+
modwsgi.UserModel = UserModel
191+
192+
from django.contrib.auth.management.commands import changepassword
193+
changepassword.UserModel = UserModel
194+
195+
from django.contrib.auth import views
196+
views.UserModel = UserModel

docs/ref/applications.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -473,10 +473,6 @@ Here are some common problems that you may encounter during initialization:
473473
will also trigger this exception. The ORM cannot function properly until all
474474
models are available.
475475

476-
Another common culprit is :func:`django.contrib.auth.get_user_model()`. Use
477-
the :setting:`AUTH_USER_MODEL` setting to reference the User model at import
478-
time.
479-
480476
This exception also happens if you forget to call :func:`django.setup()` in
481477
a standalone Python script.
482478

docs/releases/1.11.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ Minor features
125125
Set :attr:`CustomUser.EMAIL_FIELD
126126
<django.contrib.auth.models.CustomUser.EMAIL_FIELD>` to the name of the field.
127127

128+
* :func:`~django.contrib.auth.get_user_model` can now be called at import time,
129+
even in modules that define models.
130+
128131
:mod:`django.contrib.contenttypes`
129132
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
130133

docs/topics/auth/customizing.txt

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -487,9 +487,33 @@ different user model.
487487

488488
post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)
489489

490-
Generally speaking, you should reference the user model with the
491-
:setting:`AUTH_USER_MODEL` setting in code that is executed at import
492-
time. ``get_user_model()`` only works once Django has imported all models.
490+
Generally speaking, it's easiest to refer to the user model with the
491+
:setting:`AUTH_USER_MODEL` setting in code that's executed at import time,
492+
however, it's also possible to call ``get_user_model()`` while Django
493+
is importing models, so you could use
494+
``models.ForeignKey(get_user_model(), ...)``.
495+
496+
If your app is tested with multiple user models, using
497+
``@override_settings(AUTH_USER_MODEL=...)`` for example, and you cache the
498+
result of ``get_user_model()`` in a module-level variable, you may need to
499+
listen to the :data:`~django.test.signals.setting_changed` signal to clear
500+
the cache. For example::
501+
502+
from django.apps import apps
503+
from django.contrib.auth import get_user_model
504+
from django.core.signals import setting_changed
505+
from django.dispatch import receiver
506+
507+
@receiver(setting_changed)
508+
def user_model_swapped(**kwargs):
509+
if kwargs['setting'] == 'AUTH_USER_MODEL':
510+
apps.clear_cache()
511+
from myapp import some_module
512+
some_module.UserModel = get_user_model()
513+
514+
.. versionchanged:: 1.11
515+
516+
The ability to call ``get_user_model()`` at import time was added.
493517

494518
.. _specifying-custom-user-model:
495519

0 commit comments

Comments
 (0)