1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #33613 -- Made createsuperuser detect uniqueness of USERNAME_FIELD when using Meta.constraints.

This commit is contained in:
Lucidiot 2022-03-31 14:39:28 +02:00 committed by Mariusz Felisiak
parent ae506181f7
commit 13a9cde133
5 changed files with 75 additions and 2 deletions

View File

@ -11,6 +11,7 @@ from django.contrib.auth.password_validation import validate_password
from django.core import exceptions
from django.core.management.base import BaseCommand, CommandError
from django.db import DEFAULT_DB_ALIAS
from django.utils.functional import cached_property
from django.utils.text import capfirst
@ -277,9 +278,21 @@ class Command(BaseCommand):
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):
"""Validate username. If invalid, return a string error message."""
if self.username_field.unique:
if self.username_is_unique:
try:
self.UserModel._default_manager.db_manager(database).get_by_natural_key(
username

View File

@ -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
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
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
can support non-unique usernames.

View File

@ -11,6 +11,7 @@ from .with_foreign_key import CustomUserWithFK, Email
from .with_integer_username import IntegerUsernameUser
from .with_last_login_attr import UserWithDisabledLastLoginField
from .with_many_to_many import CustomUserWithM2M, CustomUserWithM2MThrough, Organization
from .with_unique_constraint import CustomUserWithUniqueConstraint
__all__ = (
"CustomEmailField",
@ -20,6 +21,7 @@ __all__ = (
"CustomUserWithFK",
"CustomUserWithM2M",
"CustomUserWithM2MThrough",
"CustomUserWithUniqueConstraint",
"CustomUserWithoutIsActiveField",
"Email",
"ExtensionUser",

View File

@ -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"),
]

View File

@ -23,6 +23,7 @@ from .models import (
CustomUserNonUniqueUsername,
CustomUserWithFK,
CustomUserWithM2M,
CustomUserWithUniqueConstraint,
Email,
Organization,
UserProxy,
@ -1065,6 +1066,41 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
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):
"""Creation fails if the username already exists."""
User.objects.create(username="janet")