diff --git a/accounts/forms.py b/accounts/forms.py index ed4f32923..8a5cf7817 100644 --- a/accounts/forms.py +++ b/accounts/forms.py @@ -152,12 +152,24 @@ def get_user_by_email(email): class UsernameField(forms.CharField): """ Username field, 3~30 characters, allows only alphanumeric chars, required by default """ + def __init__(self, required=True): + """Validates the username field for a form. Validation for brand new usernames must have strong validation (see Regex). + For profile modifications, the username validation is done in ProfileForm cleaning methods, as antique usernames can + contain spaces but new modified ones cannot. + + Args: + required (bool, optional): True for RegistrationForms, false for ProfileForms + """ + if required: + validators = [RegexValidator(r'^[\w.+-]+$')] # is the same as Django UsernameValidator except for '@' symbol + else: + validators = [] super().__init__( label="Username", min_length=3, max_length=30, - validators=[RegexValidator(r'^[\w.+-]+$')], # is the same as Django UsernameValidator except for '@' symbol + validators=validators, help_text="30 characters or fewer. Can contain: letters, digits, underscores, dots, dashes and plus signs.", error_messages={'invalid': "The username field must contain only letters, digits, underscores, dots, dashes and " "plus signs."}, @@ -389,9 +401,16 @@ def clean_username(self): if not username: username = self.request.user.username - # If username was not changed, consider it valid + # If username was not changed, consider it valid. If it has, validate it to check it does not contain space characters. if username.lower() == self.request.user.username.lower(): return username + else: + validator = RegexValidator(regex=r'^[\w.+-]+$', + message="The username field must contain only letters, digits, underscores, dots, dashes and plus signs.", + code='invalid') + if validator(username): + return username + # Check that username is not used by another user. Note that because when the maximum number of username # changes is reached, the "username" field of the ProfileForm is disabled and its contents won't change. diff --git a/accounts/tests/test_user.py b/accounts/tests/test_user.py index 7c682ad45..3be72b7f3 100644 --- a/accounts/tests/test_user.py +++ b/accounts/tests/test_user.py @@ -1085,7 +1085,30 @@ def test_oldusername_username_unique_case_insensitiveness(self): OldUsername.objects.create(user=userA, username='newUserAUsername') with self.assertRaises(IntegrityError): OldUsername.objects.create(user=userA, username='NewUserAUsername') + + def test_username_whitespace(self): + """Test that for usernames created before stronger validation was applied, whitespaces are a valid character + but for new edited ones they are not.""" + userA = User.objects.create_user('user A', email='userA@freesound.org', password='testpass') + self.client.force_login(userA) + + # Test save profile without changing username with whitespaces + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['user A'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) + self.assertEqual(OldUsername.objects.filter(user=userA).count(), 0) + # Test save profile changing username (no whitespaces) + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userANewName'], 'profile-ui_theme_preference': 'f'}) + self.assertRedirects(resp, reverse('accounts-edit')) + userA.refresh_from_db() + self.assertEqual(OldUsername.objects.filter(user=userA).count(), 1) + + # Test save profile changing username (whitespaces -> fail) + resp = self.client.post(reverse('accounts-edit'), data={'profile-username': ['userA SpaceName'], 'profile-ui_theme_preference': 'f'}) + self.assertEqual(resp.status_code, 200) + userA.refresh_from_db() + self.assertEqual(userA.username, 'userANewName') + self.assertEqual(OldUsername.objects.filter(user=userA).count(), 1) class ChangeEmailViaAdminTestCase(TestCase):