Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions src/common/mitol/common/utils/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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)

Expand All @@ -174,14 +174,42 @@ 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,
Expand Down
8 changes: 5 additions & 3 deletions tests/common/utils/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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="[email protected]")
result = usernameify("!!!", email="[email protected]")
assert result is not None
assert len(result) > 0
assert result[0].isalpha()


@pytest.mark.parametrize(
Expand Down