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.
This commit is contained in:
Aymeric Augustin 2016-09-30 22:06:02 +02:00 committed by Markus Holtermann
parent eb42d8d5d9
commit cb7bbf97a7
No known key found for this signature in database
GPG Key ID: AFE79D68D41C7E39
10 changed files with 63 additions and 20 deletions

View File

@ -172,7 +172,7 @@ def get_user_model():
Returns the User model that is active in this project. Returns the User model that is active in this project.
""" """
try: try:
return django_apps.get_model(settings.AUTH_USER_MODEL) return django_apps.get_model(settings.AUTH_USER_MODEL, require_ready=False)
except ValueError: except ValueError:
raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'") raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
except LookupError: except LookupError:

View File

@ -3,6 +3,8 @@ from __future__ import unicode_literals
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission from django.contrib.auth.models import Permission
UserModel = get_user_model()
class ModelBackend(object): class ModelBackend(object):
""" """
@ -10,7 +12,6 @@ class ModelBackend(object):
""" """
def authenticate(self, request, username=None, password=None, **kwargs): def authenticate(self, request, username=None, password=None, **kwargs):
UserModel = get_user_model()
if username is None: if username is None:
username = kwargs.get(UserModel.USERNAME_FIELD) username = kwargs.get(UserModel.USERNAME_FIELD)
try: try:
@ -97,7 +98,6 @@ class ModelBackend(object):
return False return False
def get_user(self, user_id): def get_user(self, user_id):
UserModel = get_user_model()
try: try:
user = UserModel._default_manager.get(pk=user_id) user = UserModel._default_manager.get(pk=user_id)
except UserModel.DoesNotExist: except UserModel.DoesNotExist:
@ -139,8 +139,6 @@ class RemoteUserBackend(ModelBackend):
user = None user = None
username = self.clean_username(remote_user) username = self.clean_username(remote_user)
UserModel = get_user_model()
# Note that this could be accomplished in one try-except clause, but # 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 # instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads. # built-in safeguards for multiple threads.

View File

@ -22,6 +22,8 @@ from django.utils.safestring import mark_safe
from django.utils.text import capfirst from django.utils.text import capfirst
from django.utils.translation import ugettext, ugettext_lazy as _ from django.utils.translation import ugettext, ugettext_lazy as _
UserModel = get_user_model()
class ReadOnlyPasswordHashWidget(forms.Widget): class ReadOnlyPasswordHashWidget(forms.Widget):
def render(self, name, value, attrs): def render(self, name, value, attrs):
@ -179,7 +181,6 @@ class AuthenticationForm(forms.Form):
super(AuthenticationForm, self).__init__(*args, **kwargs) super(AuthenticationForm, self).__init__(*args, **kwargs)
# Set the label for the "username" field. # Set the label for the "username" field.
UserModel = get_user_model()
self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD) self.username_field = UserModel._meta.get_field(UserModel.USERNAME_FIELD)
if self.fields['username'].label is None: if self.fields['username'].label is None:
self.fields['username'].label = capfirst(self.username_field.verbose_name) 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 that prevent inactive users and users with unusable passwords from
resetting their password. resetting their password.
""" """
UserModel = get_user_model()
active_users = UserModel._default_manager.filter(**{ active_users = UserModel._default_manager.filter(**{
'%s__iexact' % UserModel.get_email_field_name(): email, '%s__iexact' % UserModel.get_email_field_name(): email,
'is_active': True, 'is_active': True,

View File

@ -2,6 +2,8 @@ from django import db
from django.contrib import auth from django.contrib import auth
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
UserModel = auth.get_user_model()
def check_password(environ, username, password): def check_password(environ, username, password):
""" """
@ -11,7 +13,6 @@ def check_password(environ, username, password):
on whether the user exists and authenticates. on whether the user exists and authenticates.
""" """
UserModel = auth.get_user_model()
# db connection state is managed similarly to the wsgi handler # db connection state is managed similarly to the wsgi handler
# as mod_wsgi may call these functions outside of a request/response cycle # as mod_wsgi may call these functions outside of a request/response cycle
db.reset_queries() db.reset_queries()
@ -33,7 +34,6 @@ def groups_for_user(environ, username):
Authorizes a user based on groups Authorizes a user based on groups
""" """
UserModel = auth.get_user_model()
db.reset_queries() db.reset_queries()
try: try:

View File

@ -9,6 +9,8 @@ from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS from django.db import DEFAULT_DB_ALIAS
from django.utils.encoding import force_str from django.utils.encoding import force_str
UserModel = get_user_model()
class Command(BaseCommand): class Command(BaseCommand):
help = "Change a user's password for django.contrib.auth." help = "Change a user's password for django.contrib.auth."
@ -38,8 +40,6 @@ class Command(BaseCommand):
else: else:
username = getpass.getuser() username = getpass.getuser()
UserModel = get_user_model()
try: try:
u = UserModel._default_manager.using(options['database']).get(**{ u = UserModel._default_manager.using(options['database']).get(**{
UserModel.USERNAME_FIELD: username UserModel.USERNAME_FIELD: username

View File

@ -31,6 +31,8 @@ from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic.base import TemplateView from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView from django.views.generic.edit import FormView
UserModel = get_user_model()
def deprecate_current_app(func): 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 " warnings.warn("The password_reset_confirm() view is superseded by the "
"class-based PasswordResetConfirmView().", "class-based PasswordResetConfirmView().",
RemovedInDjango21Warning, stacklevel=2) RemovedInDjango21Warning, stacklevel=2)
UserModel = get_user_model()
assert uidb64 is not None and token is not None # checked by URLconf assert uidb64 is not None and token is not None # checked by URLconf
if post_reset_redirect is None: if post_reset_redirect is None:
post_reset_redirect = reverse('password_reset_complete') post_reset_redirect = reverse('password_reset_complete')
@ -453,7 +454,6 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView):
return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs) return super(PasswordResetConfirmView, self).dispatch(*args, **kwargs)
def get_user(self, uidb64): def get_user(self, uidb64):
UserModel = get_user_model()
try: try:
# urlsafe_base64_decode() decodes to bytestring on Python 3 # urlsafe_base64_decode() decodes to bytestring on Python 3
uid = force_text(urlsafe_base64_decode(uidb64)) uid = force_text(urlsafe_base64_decode(uidb64))

View File

@ -4,6 +4,7 @@ import time
import warnings import warnings
from django.apps import apps from django.apps import apps
from django.core.exceptions import ImproperlyConfigured
from django.core.signals import setting_changed from django.core.signals import setting_changed
from django.db import connections, router from django.db import connections, router
from django.db.utils import ConnectionRouter from django.db.utils import ConnectionRouter
@ -172,3 +173,24 @@ def auth_password_validators_changed(**kwargs):
def user_model_swapped(**kwargs): def user_model_swapped(**kwargs):
if kwargs['setting'] == 'AUTH_USER_MODEL': if kwargs['setting'] == 'AUTH_USER_MODEL':
apps.clear_cache() 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

View File

@ -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 will also trigger this exception. The ORM cannot function properly until all
models are available. 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 This exception also happens if you forget to call :func:`django.setup()` in
a standalone Python script. a standalone Python script.

View File

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

View File

@ -487,9 +487,33 @@ different user model.
post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL) post_save.connect(post_save_receiver, sender=settings.AUTH_USER_MODEL)
Generally speaking, you should reference the user model with the Generally speaking, it's easiest to refer to the user model with the
:setting:`AUTH_USER_MODEL` setting in code that is executed at import :setting:`AUTH_USER_MODEL` setting in code that's executed at import time,
time. ``get_user_model()`` only works once Django has imported all models. 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: .. _specifying-custom-user-model: