diff --git a/django/contrib/auth/models.py b/django/contrib/auth/models.py index 84962b9d0e..5f092f0ae8 100644 --- a/django/contrib/auth/models.py +++ b/django/contrib/auth/models.py @@ -1,5 +1,7 @@ +from django.apps import apps from django.contrib import auth from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager +from django.contrib.auth.hashers import make_password from django.contrib.contenttypes.models import ContentType from django.core.exceptions import PermissionDenied from django.core.mail import send_mail @@ -134,9 +136,13 @@ class UserManager(BaseUserManager): if not username: raise ValueError('The given username must be set') email = self.normalize_email(email) - username = self.model.normalize_username(username) + # Lookup the real model class from the global app registry so this + # manager method can be used in migrations. This is fine because + # managers are by definition working on the real model. + GlobalUserModel = apps.get_model(self.model._meta.app_label, self.model._meta.object_name) + username = GlobalUserModel.normalize_username(username) user = self.model(username=username, email=email, **extra_fields) - user.set_password(password) + user.password = make_password(password) user.save(using=self._db) return user diff --git a/tests/auth_tests/test_models.py b/tests/auth_tests/test_models.py index c60b66c993..2379f68b83 100644 --- a/tests/auth_tests/test_models.py +++ b/tests/auth_tests/test_models.py @@ -10,8 +10,12 @@ from django.contrib.auth.models import ( ) from django.contrib.contenttypes.models import ContentType from django.core import mail +from django.db import connection, migrations +from django.db.migrations.state import ModelState, ProjectState from django.db.models.signals import post_save -from django.test import SimpleTestCase, TestCase, override_settings +from django.test import ( + SimpleTestCase, TestCase, TransactionTestCase, override_settings, +) from .models import IntegerUsernameUser from .models.with_custom_email_field import CustomEmailField @@ -101,7 +105,12 @@ class LoadDataWithNaturalKeysAndMultipleDatabasesTestCase(TestCase): self.assertEqual(perm_other.content_type_id, other_objects[0].id) -class UserManagerTestCase(TestCase): +class UserManagerTestCase(TransactionTestCase): + available_apps = [ + 'auth_tests', + 'django.contrib.auth', + 'django.contrib.contenttypes', + ] def test_create_user(self): email_lowercase = 'normal@normal.com' @@ -156,6 +165,30 @@ class UserManagerTestCase(TestCase): for char in password: self.assertIn(char, allowed_chars) + def test_runpython_manager_methods(self): + def forwards(apps, schema_editor): + UserModel = apps.get_model('auth', 'User') + user = UserModel.objects.create_user('user1', password='secure') + self.assertIsInstance(user, UserModel) + + operation = migrations.RunPython(forwards, migrations.RunPython.noop) + project_state = ProjectState() + project_state.add_model(ModelState.from_model(User)) + project_state.add_model(ModelState.from_model(Group)) + project_state.add_model(ModelState.from_model(Permission)) + project_state.add_model(ModelState.from_model(ContentType)) + new_state = project_state.clone() + with connection.schema_editor() as editor: + operation.state_forwards('test_manager_methods', new_state) + operation.database_forwards( + 'test_manager_methods', + editor, + project_state, + new_state, + ) + user = User.objects.get(username='user1') + self.assertTrue(user.check_password('secure')) + class AbstractBaseUserTests(SimpleTestCase):