Fixed #33613 -- Made createsuperuser detect uniqueness of USERNAME_FIELD when using Meta.constraints.
This commit is contained in:
parent
ae506181f7
commit
13a9cde133
|
@ -11,6 +11,7 @@ from django.contrib.auth.password_validation import validate_password
|
||||||
from django.core import exceptions
|
from django.core import exceptions
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
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.functional import cached_property
|
||||||
from django.utils.text import capfirst
|
from django.utils.text import capfirst
|
||||||
|
|
||||||
|
|
||||||
|
@ -277,9 +278,21 @@ class Command(BaseCommand):
|
||||||
else "",
|
else "",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@cached_property
|
||||||
|
def username_is_unique(self):
|
||||||
|
if self.username_field.unique:
|
||||||
|
return True
|
||||||
|
for unique_constraint in self.UserModel._meta.total_unique_constraints:
|
||||||
|
if (
|
||||||
|
len(unique_constraint.fields) == 1
|
||||||
|
and unique_constraint.fields[0] == self.username_field.name
|
||||||
|
):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _validate_username(self, username, verbose_field_name, database):
|
def _validate_username(self, username, verbose_field_name, database):
|
||||||
"""Validate username. If invalid, return a string error message."""
|
"""Validate username. If invalid, return a string error message."""
|
||||||
if self.username_field.unique:
|
if self.username_is_unique:
|
||||||
try:
|
try:
|
||||||
self.UserModel._default_manager.db_manager(database).get_by_natural_key(
|
self.UserModel._default_manager.db_manager(database).get_by_natural_key(
|
||||||
username
|
username
|
||||||
|
|
|
@ -548,7 +548,7 @@ password resets. You must then provide some key implementation details:
|
||||||
A string describing the name of the field on the user model that is
|
A string describing the name of the field on the user model that is
|
||||||
used as the unique identifier. This will usually be a username of some
|
used as the unique identifier. This will usually be a username of some
|
||||||
kind, but it can also be an email address, or any other unique
|
kind, but it can also be an email address, or any other unique
|
||||||
identifier. The field *must* be unique (i.e., have ``unique=True`` set
|
identifier. The field *must* be unique (e.g. have ``unique=True`` set
|
||||||
in its definition), unless you use a custom authentication backend that
|
in its definition), unless you use a custom authentication backend that
|
||||||
can support non-unique usernames.
|
can support non-unique usernames.
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ from .with_foreign_key import CustomUserWithFK, Email
|
||||||
from .with_integer_username import IntegerUsernameUser
|
from .with_integer_username import IntegerUsernameUser
|
||||||
from .with_last_login_attr import UserWithDisabledLastLoginField
|
from .with_last_login_attr import UserWithDisabledLastLoginField
|
||||||
from .with_many_to_many import CustomUserWithM2M, CustomUserWithM2MThrough, Organization
|
from .with_many_to_many import CustomUserWithM2M, CustomUserWithM2MThrough, Organization
|
||||||
|
from .with_unique_constraint import CustomUserWithUniqueConstraint
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
"CustomEmailField",
|
"CustomEmailField",
|
||||||
|
@ -20,6 +21,7 @@ __all__ = (
|
||||||
"CustomUserWithFK",
|
"CustomUserWithFK",
|
||||||
"CustomUserWithM2M",
|
"CustomUserWithM2M",
|
||||||
"CustomUserWithM2MThrough",
|
"CustomUserWithM2MThrough",
|
||||||
|
"CustomUserWithUniqueConstraint",
|
||||||
"CustomUserWithoutIsActiveField",
|
"CustomUserWithoutIsActiveField",
|
||||||
"Email",
|
"Email",
|
||||||
"ExtensionUser",
|
"ExtensionUser",
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
|
||||||
|
from django.db import models
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserWithUniqueConstraintManager(BaseUserManager):
|
||||||
|
def create_superuser(self, username, password):
|
||||||
|
user = self.model(username=username)
|
||||||
|
user.set_password(password)
|
||||||
|
user.save(using=self._db)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
class CustomUserWithUniqueConstraint(AbstractBaseUser):
|
||||||
|
username = models.CharField(max_length=150)
|
||||||
|
|
||||||
|
objects = CustomUserWithUniqueConstraintManager()
|
||||||
|
USERNAME_FIELD = "username"
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=["username"], name="unique_custom_username"),
|
||||||
|
]
|
|
@ -23,6 +23,7 @@ from .models import (
|
||||||
CustomUserNonUniqueUsername,
|
CustomUserNonUniqueUsername,
|
||||||
CustomUserWithFK,
|
CustomUserWithFK,
|
||||||
CustomUserWithM2M,
|
CustomUserWithM2M,
|
||||||
|
CustomUserWithUniqueConstraint,
|
||||||
Email,
|
Email,
|
||||||
Organization,
|
Organization,
|
||||||
UserProxy,
|
UserProxy,
|
||||||
|
@ -1065,6 +1066,41 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
||||||
|
|
||||||
test(self)
|
test(self)
|
||||||
|
|
||||||
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUserWithUniqueConstraint")
|
||||||
|
def test_existing_username_meta_unique_constraint(self):
|
||||||
|
"""
|
||||||
|
Creation fails if the username already exists and a custom user model
|
||||||
|
has UniqueConstraint.
|
||||||
|
"""
|
||||||
|
user = CustomUserWithUniqueConstraint.objects.create(username="janet")
|
||||||
|
new_io = StringIO()
|
||||||
|
entered_passwords = ["password", "password"]
|
||||||
|
# Enter the existing username first and then a new one.
|
||||||
|
entered_usernames = [user.username, "joe"]
|
||||||
|
|
||||||
|
def return_passwords():
|
||||||
|
return entered_passwords.pop(0)
|
||||||
|
|
||||||
|
def return_usernames():
|
||||||
|
return entered_usernames.pop(0)
|
||||||
|
|
||||||
|
@mock_inputs({"password": return_passwords, "username": return_usernames})
|
||||||
|
def test(self):
|
||||||
|
call_command(
|
||||||
|
"createsuperuser",
|
||||||
|
interactive=True,
|
||||||
|
stdin=MockTTY(),
|
||||||
|
stdout=new_io,
|
||||||
|
stderr=new_io,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
new_io.getvalue().strip(),
|
||||||
|
"Error: That username is already taken.\n"
|
||||||
|
"Superuser created successfully.",
|
||||||
|
)
|
||||||
|
|
||||||
|
test(self)
|
||||||
|
|
||||||
def test_existing_username_non_interactive(self):
|
def test_existing_username_non_interactive(self):
|
||||||
"""Creation fails if the username already exists."""
|
"""Creation fails if the username already exists."""
|
||||||
User.objects.create(username="janet")
|
User.objects.create(username="janet")
|
||||||
|
|
Loading…
Reference in New Issue