Repository: learningequality/kolibri

Issue:
Cannot create non-ascii usernames
### Observed behavior

I tried to create a user with username `иван` (ivan in cyrillic) but was not able to.


### Expected behavior

Create a new user  OR  show validation error.


### User-facing consequences

Cannot create usernames in other locales.


### Errors and logs

UI doesn't show a validation error, but JS console has this:

<img width="880" alt="screen shot 2018-08-30 at 5 01 06 pm" src="https://user-images.githubusercontent.com/163966/44857106-c4f0c800-ac77-11e8-8976-bfbf9f6924fd.png">


### Steps to reproduce

  - Go to localhost:8080/facility/#/users
  - Choose ADD USER
  - Enter a non-ascii username (and valid password etc)
  - Click ADD

Actual: nothing

Expected: user created or validation error "username ... invalid; use ascii characters"


### Context

 * Kolibri version: 0.10.3.dev
 * Operating system: Linux
 * Browser: Firefox



Discussion:
Our validation logic is inadequate for this, we should improve and allow it! 
let's address this at the same time as we include bulgarian (see #4201)

removing milestone for now
Also, when I try to do this on develop, it tells me that an account with this username already exists, which is not the error that the API returns.

Resolving patch:
diff --git a/kolibri/core/auth/migrations/0013_auto_20180917_1213.py b/kolibri/core/auth/migrations/0013_auto_20180917_1213.py
new file mode 100644
--- /dev/null
+++ b/kolibri/core/auth/migrations/0013_auto_20180917_1213.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.11.15 on 2018-09-17 16:13
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations
+from django.db import models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('kolibriauth', '0012_facilitydataset_allow_guest_access'),
+    ]
+
+    operations = [
+        migrations.AlterField(
+            model_name='facilityuser',
+            name='username',
+            field=models.CharField(help_text='Required. 30 characters or fewer. Letters and digits only', max_length=30, validators=[django.core.validators.RegexValidator('[\\s`~!@#$%^&*()\\-+={}\\[\\]\\|\\\\\\/:;"\\\'<>,\\.\\?]', 'Enter a valid username. This value can contain only letters, numbers, and underscores.', inverse_match=True)], verbose_name='username'),
+        ),
+    ]
diff --git a/kolibri/core/auth/models.py b/kolibri/core/auth/models.py
--- a/kolibri/core/auth/models.py
+++ b/kolibri/core/auth/models.py
@@ -35,7 +35,6 @@
 from django.db.models.query import F
 from django.db.utils import IntegrityError
 from django.utils.encoding import python_2_unicode_compatible
-from django.utils.translation import ugettext_lazy as _
 from morango.certificates import Certificate
 from morango.manager import SyncableModelManager
 from morango.models import SyncableModel
@@ -225,18 +224,19 @@ class Meta:
     USERNAME_FIELD = "username"
 
     username = models.CharField(
-        _('username'),
+        'username',
         max_length=30,
-        help_text=_('Required. 30 characters or fewer. Letters and digits only'),
+        help_text='Required. 30 characters or fewer. Letters and digits only',
         validators=[
             validators.RegexValidator(
-                r'^\w+$',
-                _('Enter a valid username. This value can contain only letters, numbers, and underscores.')
+                r'[\s`~!@#$%^&*()\-+={}\[\]\|\\\/:;"\'<>,\.\?]',
+                'Enter a valid username. This value can contain only letters, numbers, and underscores.',
+                inverse_match=True,
             ),
         ],
     )
-    full_name = models.CharField(_('full name'), max_length=120, blank=True)
-    date_joined = DateTimeTzField(_('date joined'), default=local_now, editable=False)
+    full_name = models.CharField('full name', max_length=120, blank=True)
+    date_joined = DateTimeTzField('date joined', default=local_now, editable=False)
 
     is_staff = False
     is_superuser = False
@@ -478,7 +478,7 @@ def create_user(self, username, email=None, password=None, **extra_fields):
         if 'facility' not in extra_fields:
             extra_fields['facility'] = Facility.get_default_facility()
         if self.filter(username__iexact=username, facility=extra_fields['facility']).exists():
-            raise ValidationError(_('An account with that username already exists'))
+            raise ValidationError('An account with that username already exists')
         user = self.model(username=username, password=password, **extra_fields)
         user.full_clean()
         user.set_password(password)
@@ -494,7 +494,7 @@ def create_superuser(self, username, password):
         facility = Facility.get_default_facility()
 
         if self.filter(username__iexact=username, facility=facility).exists():
-            raise ValidationError(_('An account with that username already exists'))
+            raise ValidationError('An account with that username already exists')
 
         # create the new account in that facility
         superuser = FacilityUser(username=username, password=password, facility=facility)


Test patch: