mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Fixed #33613 -- Made createsuperuser detect uniqueness of USERNAME_FIELD when using Meta.constraints.
This commit is contained in:
		
				
					committed by
					
						 Mariusz Felisiak
						Mariusz Felisiak
					
				
			
			
				
	
			
			
			
						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.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 | ||||
|   | ||||
| @@ -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. | ||||
|  | ||||
|   | ||||
| @@ -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", | ||||
|   | ||||
							
								
								
									
										22
									
								
								tests/auth_tests/models/with_unique_constraint.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/auth_tests/models/with_unique_constraint.py
									
									
									
									
									
										Normal 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"), | ||||
|         ] | ||||
| @@ -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") | ||||
|   | ||||
		Reference in New Issue
	
	Block a user