From 6eea38cb8041c0f2abe79823dc18d28a6b240144 Mon Sep 17 00:00:00 2001 From: asajjad2 Date: Wed, 3 Dec 2025 19:03:51 +0500 Subject: [PATCH 1/2] fix: handle numbers-only username generation fails --- src/common/mitol/common/utils/user.py | 40 +++++++++++++++++++++------ tests/common/utils/test_user.py | 8 ++++-- 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/src/common/mitol/common/utils/user.py b/src/common/mitol/common/utils/user.py index cef10b9e..3dd7389d 100644 --- a/src/common/mitol/common/utils/user.py +++ b/src/common/mitol/common/utils/user.py @@ -2,6 +2,8 @@ import logging import re +import secrets +import string from django.contrib.auth import get_user_model from django.db import IntegrityError @@ -149,7 +151,8 @@ def _find_available_username( # noqa: RET503 def usernameify(full_name, email="", max_length=USERNAME_MAX_LEN): """Public API for username generation Generate a username based on a - full name, or an email address as a fallback. + full name, or an email address as a fallback. If both fail, generates + a random username. Args: full_name (str): A full name (i.e.: User.name) @@ -159,9 +162,6 @@ def usernameify(full_name, email="", max_length=USERNAME_MAX_LEN): (defaults to USERNAME_MAX_LEN) Returns: str: A generated username - Raises: - ValueError: Raised if generated username was blank after trying both - the full name and email """ username = _reformat_for_username(full_name) @@ -174,14 +174,38 @@ def usernameify(full_name, email="", max_length=USERNAME_MAX_LEN): ) username = _reformat_for_username(email.split("@")[0]) if not username: - raise ValueError( - "Username could not be generated (full_name: '{}', email: '{}')".format( # noqa: EM103, UP032 - full_name, email - ) + log.warning( + "Username could not be generated from full name or email " + "(full_name: '%s', email: '%s'). Generating random username...", + full_name, + email, ) + username = _generate_random_username(max_length) return username[0:max_length] +def _generate_random_username(max_length=USERNAME_MAX_LEN): + """Generate a random username with letters and numbers. + + Args: + max_length (int): The maximum allowed username length + (defaults to USERNAME_MAX_LEN) + + Returns: + str: A randomly generated username starting with a letter + """ + min_length = min(8, max_length) + length_range = max_length - min_length + 1 + random_length = min_length if length_range <= 1 else secrets.randbelow(length_range) + min_length + + first_char = secrets.choice(string.ascii_lowercase) + remaining_chars = "".join( + secrets.choice(string.ascii_lowercase + string.digits) + for _ in range(random_length - 1) + ) + return first_char + remaining_chars + + def create_user_with_generated_username( # noqa: PLR0913 serializer, initial_username, diff --git a/tests/common/utils/test_user.py b/tests/common/utils/test_user.py index d97df925..a84ab897 100644 --- a/tests/common/utils/test_user.py +++ b/tests/common/utils/test_user.py @@ -56,11 +56,13 @@ def test_usernameify(mocker, full_name, email, expected_username): def test_usernameify_fail(): - """Usernameify should raise an exception if the full name and email both + """Usernameify should generate a random username if the full name and email both fail to produce a username """ - with pytest.raises(ValueError): # noqa: PT011 - assert usernameify("!!!", email="???@example.com") + result = usernameify("!!!", email="???@example.com") + assert result is not None + assert len(result) > 0 + assert result[0].isalpha() @pytest.mark.parametrize( From 37e8af5a993a5448a3bc62400ca455535e37537f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 14:10:06 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/common/mitol/common/utils/user.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/mitol/common/utils/user.py b/src/common/mitol/common/utils/user.py index 3dd7389d..987c67ef 100644 --- a/src/common/mitol/common/utils/user.py +++ b/src/common/mitol/common/utils/user.py @@ -196,7 +196,11 @@ def _generate_random_username(max_length=USERNAME_MAX_LEN): """ min_length = min(8, max_length) length_range = max_length - min_length + 1 - random_length = min_length if length_range <= 1 else secrets.randbelow(length_range) + min_length + random_length = ( + min_length + if length_range <= 1 + else secrets.randbelow(length_range) + min_length + ) first_char = secrets.choice(string.ascii_lowercase) remaining_chars = "".join(