Repository: pennersr/django-allauth

Issue:
ACCOUNT_USERNAME_BLACKLIST and populate_username
I've noticed that populate_username function is not working as desired.

It doesn't check ACCOUNT_USERNAME_BLACKLIST while populating account username.

https://github.com/pennersr/django-allauth/blob/1b59ba0f622bc7fc151509cd9cef8f76d28d8f83/allauth/account/adapter.py#L158



Discussion:

Resolving patch:
diff --git a/allauth/account/adapter.py b/allauth/account/adapter.py
--- a/allauth/account/adapter.py
+++ b/allauth/account/adapter.py
@@ -214,7 +214,7 @@ def save_user(self, request, user, form, commit=True):
             user.save()
         return user
 
-    def clean_username(self, username):
+    def clean_username(self, username, shallow=False):
         """
         Validates the username. You can hook into this if you want to
         (dynamically) restrict what usernames can be chosen.
@@ -229,16 +229,19 @@ def clean_username(self, username):
         if username.lower() in username_blacklist_lower:
             raise forms.ValidationError(_("Username can not be used. "
                                           "Please use other username."))
-        username_field = app_settings.USER_MODEL_USERNAME_FIELD
-        assert username_field
-        user_model = get_user_model()
-        try:
-            query = {username_field + '__iexact': username}
-            user_model.objects.get(**query)
-        except user_model.DoesNotExist:
-            return username
-        raise forms.ValidationError(_("This username is already taken. Please "
-                                      "choose another."))
+        # Skipping database lookups when shallow is True, needed for unique username generation.
+        if not shallow:
+            username_field = app_settings.USER_MODEL_USERNAME_FIELD
+            assert username_field
+            user_model = get_user_model()
+            try:
+                query = {username_field + '__iexact': username}
+                user_model.objects.get(**query)
+            except user_model.DoesNotExist:
+                return username
+            raise forms.ValidationError(_("This username is already taken. Please "
+                                          "choose another."))
+        return username
 
     def clean_email(self, email):
         """
diff --git a/allauth/utils.py b/allauth/utils.py
--- a/allauth/utils.py
+++ b/allauth/utils.py
@@ -27,6 +27,8 @@
 
 
 def _generate_unique_username_base(txts, regex=None):
+    from .account.adapter import get_adapter
+    adapter = get_adapter()
     username = None
     regex = regex or '[^\w\s@+.-]'
     for txt in txts:
@@ -43,8 +45,12 @@ def _generate_unique_username_base(txts, regex=None):
         username = username.split('@')[0]
         username = username.strip()
         username = re.sub('\s+', '_', username)
-        if username:
+        # Finally, validating base username without database lookups etc.
+        try:
+            username = adapter.clean_username(username, shallow=True)
             break
+        except ValidationError:
+            pass
     return username or 'user'
 
 
@@ -59,9 +65,9 @@ def get_username_max_length():
 
 
 def generate_unique_username(txts, regex=None):
-    from .account.app_settings import USER_MODEL_USERNAME_FIELD
+    from .account.adapter import get_adapter
+    adapter = get_adapter()
     username = _generate_unique_username_base(txts, regex)
-    User = get_user_model()
     max_length = get_username_max_length()
     i = 0
     while True:
@@ -71,11 +77,9 @@ def generate_unique_username(txts, regex=None):
             else:
                 pfx = ''
             ret = username[0:max_length - len(pfx)] + pfx
-            query = {USER_MODEL_USERNAME_FIELD + '__iexact': ret}
-            User.objects.get(**query)
+            return adapter.clean_username(ret)
+        except ValidationError:
             i += 1
-        except User.DoesNotExist:
-            return ret
 
 
 def valid_email_or_none(email):


Test patch:
diff --git a/allauth/socialaccount/tests.py b/allauth/socialaccount/tests.py
--- a/allauth/socialaccount/tests.py
+++ b/allauth/socialaccount/tests.py
@@ -334,6 +334,31 @@ def test_email_address_clash_username_auto_signup(self):
                'other@test.com'})
         self.assertNotEqual(user_username(user), 'test')
 
+    @override_settings(
+        ACCOUNT_EMAIL_REQUIRED=True,
+        ACCOUNT_USERNAME_BLACKLIST=['username', 'username1', 'username2'],
+        ACCOUNT_UNIQUE_EMAIL=True,
+        ACCOUNT_USERNAME_REQUIRED=True,
+        ACCOUNT_AUTHENTICATION_METHOD='email',
+        SOCIALACCOUNT_AUTO_SIGNUP=True)
+    def test_populate_username_in_blacklist(self):
+        factory = RequestFactory()
+        request = factory.get('/accounts/twitter/login/callback/')
+        request.user = AnonymousUser()
+        SessionMiddleware().process_request(request)
+        MessageMiddleware().process_request(request)
+
+        User = get_user_model()
+        user = User()
+        setattr(user, account_settings.USER_MODEL_USERNAME_FIELD, 'username')
+        setattr(user, account_settings.USER_MODEL_EMAIL_FIELD, 'username@doe.com')
+
+        account = SocialAccount(provider='twitter', uid='123')
+        sociallogin = SocialLogin(user=user, account=account)
+        complete_social_login(request, sociallogin)
+
+        self.assertNotIn(request.user.username, account_settings.USERNAME_BLACKLIST)
+
     def _email_address_clash(self, username, email):
         User = get_user_model()
         # Some existig user