Loading django/contrib/auth/base_user.py +7 −4 Original line number Diff line number Diff line Loading @@ -33,10 +33,6 @@ class BaseUserManager(models.Manager): email = '@'.join([email_name, domain_part.lower()]) return email @classmethod def normalize_username(cls, username): return unicodedata.normalize('NFKC', force_text(username)) def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyz' 'ABCDEFGHJKLMNPQRSTUVWXYZ' Loading Loading @@ -77,6 +73,9 @@ class AbstractBaseUser(models.Model): def __str__(self): return self.get_username() def clean(self): setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username())) def save(self, *args, **kwargs): super(AbstractBaseUser, self).save(*args, **kwargs) if self._password is not None: Loading Loading @@ -137,3 +136,7 @@ class AbstractBaseUser(models.Model): """ key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" return salted_hmac(key_salt, self.password).hexdigest() @classmethod def normalize_username(cls, username): return unicodedata.normalize('NFKC', force_text(username)) django/contrib/auth/models.py +1 −1 Original line number Diff line number Diff line Loading @@ -145,7 +145,7 @@ class UserManager(BaseUserManager): if not username: raise ValueError('The given username must be set') email = self.normalize_email(email) username = self.normalize_username(username) username = self.model.normalize_username(username) user = self.model(username=username, email=email, **extra_fields) user.set_password(password) user.save(using=self._db) Loading docs/releases/1.10.txt +4 −0 Original line number Diff line number Diff line Loading @@ -887,6 +887,10 @@ Miscellaneous * Accessing a deleted field on a model instance, e.g. after ``del obj.field``, reloads the field's value instead of raising ``AttributeError``. * If you subclass ``AbstractBaseUser`` and override ``clean()``, be sure it calls ``super()``. :meth:`.AbstractBaseUser.normalize_username` is called in a new :meth:`.AbstractBaseUser.clean` method. .. _deprecated-features-1.10: Features deprecated in 1.10 Loading docs/topics/auth/customizing.txt +16 −8 Original line number Diff line number Diff line Loading @@ -612,6 +612,22 @@ The following attributes and methods are available on any subclass of Returns the value of the field nominated by ``USERNAME_FIELD``. .. method:: clean() .. versionadded:: 1.10 Normalizes the username by calling :meth:`normalize_username`. If you override this method, be sure to call ``super()`` to retain the normalization. .. classmethod:: normalize_username(username) .. versionadded:: 1.10 Applies NFKC Unicode normalization to usernames so that visually identical characters with different Unicode code points are considered identical. .. attribute:: models.AbstractBaseUser.is_authenticated Read-only attribute which is always ``True`` (as opposed to Loading Loading @@ -726,14 +742,6 @@ utility methods: Normalizes email addresses by lowercasing the domain portion of the email address. .. classmethod:: models.BaseUserManager.normalize_username(email) .. versionadded:: 1.10 Applies NFKC Unicode normalization to usernames so that visually identical characters with different Unicode code points are considered identical. .. method:: models.BaseUserManager.get_by_natural_key(username) Retrieves a user instance using the contents of the field Loading tests/auth_tests/test_forms.py +16 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,22 @@ class UserCreationFormTest(TestDataMixin, TestCase): else: self.assertFalse(form.is_valid()) @skipIf(six.PY2, "Python 2 doesn't support unicode usernames by default.") def test_normalize_username(self): # The normalization happens in AbstractBaseUser.clean() and ModelForm # validation calls Model.clean(). ohm_username = 'testΩ' # U+2126 OHM SIGN data = { 'username': ohm_username, 'password1': 'pwd2', 'password2': 'pwd2', } form = UserCreationForm(data) self.assertTrue(form.is_valid()) user = form.save() self.assertNotEqual(user.username, ohm_username) self.assertEqual(user.username, 'testΩ') # U+03A9 GREEK CAPITAL LETTER OMEGA @skipIf(six.PY2, "Python 2 doesn't support unicode usernames by default.") def test_duplicate_normalized_unicode(self): """ Loading Loading
django/contrib/auth/base_user.py +7 −4 Original line number Diff line number Diff line Loading @@ -33,10 +33,6 @@ class BaseUserManager(models.Manager): email = '@'.join([email_name, domain_part.lower()]) return email @classmethod def normalize_username(cls, username): return unicodedata.normalize('NFKC', force_text(username)) def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyz' 'ABCDEFGHJKLMNPQRSTUVWXYZ' Loading Loading @@ -77,6 +73,9 @@ class AbstractBaseUser(models.Model): def __str__(self): return self.get_username() def clean(self): setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username())) def save(self, *args, **kwargs): super(AbstractBaseUser, self).save(*args, **kwargs) if self._password is not None: Loading Loading @@ -137,3 +136,7 @@ class AbstractBaseUser(models.Model): """ key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash" return salted_hmac(key_salt, self.password).hexdigest() @classmethod def normalize_username(cls, username): return unicodedata.normalize('NFKC', force_text(username))
django/contrib/auth/models.py +1 −1 Original line number Diff line number Diff line Loading @@ -145,7 +145,7 @@ class UserManager(BaseUserManager): if not username: raise ValueError('The given username must be set') email = self.normalize_email(email) username = self.normalize_username(username) username = self.model.normalize_username(username) user = self.model(username=username, email=email, **extra_fields) user.set_password(password) user.save(using=self._db) Loading
docs/releases/1.10.txt +4 −0 Original line number Diff line number Diff line Loading @@ -887,6 +887,10 @@ Miscellaneous * Accessing a deleted field on a model instance, e.g. after ``del obj.field``, reloads the field's value instead of raising ``AttributeError``. * If you subclass ``AbstractBaseUser`` and override ``clean()``, be sure it calls ``super()``. :meth:`.AbstractBaseUser.normalize_username` is called in a new :meth:`.AbstractBaseUser.clean` method. .. _deprecated-features-1.10: Features deprecated in 1.10 Loading
docs/topics/auth/customizing.txt +16 −8 Original line number Diff line number Diff line Loading @@ -612,6 +612,22 @@ The following attributes and methods are available on any subclass of Returns the value of the field nominated by ``USERNAME_FIELD``. .. method:: clean() .. versionadded:: 1.10 Normalizes the username by calling :meth:`normalize_username`. If you override this method, be sure to call ``super()`` to retain the normalization. .. classmethod:: normalize_username(username) .. versionadded:: 1.10 Applies NFKC Unicode normalization to usernames so that visually identical characters with different Unicode code points are considered identical. .. attribute:: models.AbstractBaseUser.is_authenticated Read-only attribute which is always ``True`` (as opposed to Loading Loading @@ -726,14 +742,6 @@ utility methods: Normalizes email addresses by lowercasing the domain portion of the email address. .. classmethod:: models.BaseUserManager.normalize_username(email) .. versionadded:: 1.10 Applies NFKC Unicode normalization to usernames so that visually identical characters with different Unicode code points are considered identical. .. method:: models.BaseUserManager.get_by_natural_key(username) Retrieves a user instance using the contents of the field Loading
tests/auth_tests/test_forms.py +16 −0 Original line number Diff line number Diff line Loading @@ -119,6 +119,22 @@ class UserCreationFormTest(TestDataMixin, TestCase): else: self.assertFalse(form.is_valid()) @skipIf(six.PY2, "Python 2 doesn't support unicode usernames by default.") def test_normalize_username(self): # The normalization happens in AbstractBaseUser.clean() and ModelForm # validation calls Model.clean(). ohm_username = 'testΩ' # U+2126 OHM SIGN data = { 'username': ohm_username, 'password1': 'pwd2', 'password2': 'pwd2', } form = UserCreationForm(data) self.assertTrue(form.is_valid()) user = form.save() self.assertNotEqual(user.username, ohm_username) self.assertEqual(user.username, 'testΩ') # U+03A9 GREEK CAPITAL LETTER OMEGA @skipIf(six.PY2, "Python 2 doesn't support unicode usernames by default.") def test_duplicate_normalized_unicode(self): """ Loading