Refs #26445 -- Allowed using UserManager.create_user()/create_superuser() in migrations.

Used app config to lookup user model in _create_user().

Thanks Markus Holtermann for the review and initial patch.
Thanks Simon Charette for the implementation idea.
This commit is contained in:
Hasan Ramezani 2020-03-10 22:22:23 +01:00 committed by Mariusz Felisiak
parent d1409f51ff
commit 7af8f41273
2 changed files with 43 additions and 4 deletions

View File

@ -1,5 +1,7 @@
from django.apps import apps
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager 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.contrib.contenttypes.models import ContentType
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.core.mail import send_mail from django.core.mail import send_mail
@ -134,9 +136,13 @@ class UserManager(BaseUserManager):
if not username: if not username:
raise ValueError('The given username must be set') raise ValueError('The given username must be set')
email = self.normalize_email(email) 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 = self.model(username=username, email=email, **extra_fields)
user.set_password(password) user.password = make_password(password)
user.save(using=self._db) user.save(using=self._db)
return user return user

View File

@ -10,8 +10,12 @@ from django.contrib.auth.models import (
) )
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core import mail 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.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 import IntegerUsernameUser
from .models.with_custom_email_field import CustomEmailField 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) 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): def test_create_user(self):
email_lowercase = 'normal@normal.com' email_lowercase = 'normal@normal.com'
@ -156,6 +165,30 @@ class UserManagerTestCase(TestCase):
for char in password: for char in password:
self.assertIn(char, allowed_chars) 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): class AbstractBaseUserTests(SimpleTestCase):