Skip to content

Commit

Permalink
Update username validator (#1865)
Browse files Browse the repository at this point in the history
* update username validator

* lint fix
  • Loading branch information
sainak authored Feb 10, 2024
1 parent c7842be commit 5a087e1
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 18 deletions.
26 changes: 26 additions & 0 deletions care/users/migrations/0014_alter_user_username.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Generated by Django 4.2.8 on 2024-01-31 14:00

from django.db import migrations, models

import care.utils.models.validators


class Migration(migrations.Migration):
dependencies = [
("users", "0013_staff_to_nurse"),
]

operations = [
migrations.AlterField(
model_name="user",
name="username",
field=models.CharField(
error_messages={"unique": "A user with that username already exists."},
help_text="Username must be 4 to 16 characters long. It may only contain lowercase alphabets, numbers, underscores, hyphens and dots. It shouldn't start or end with underscores, hyphens or dots. It shouldn't contain consecutive underscores, hyphens or dots.",
max_length=150,
unique=True,
validators=[care.utils.models.validators.UsernameValidator()],
verbose_name="username",
),
),
]
11 changes: 2 additions & 9 deletions care/users/models.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import uuid

from django.contrib.auth.models import AbstractUser, UserManager
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.core.validators import MaxValueValidator, MinValueValidator
from django.db import models
from django.urls import reverse
Expand All @@ -10,6 +9,7 @@

from care.utils.models.base import BaseModel
from care.utils.models.validators import (
UsernameValidator,
mobile_or_landline_number_validator,
mobile_validator,
)
Expand Down Expand Up @@ -147,13 +147,6 @@ def __str__(self):
return self.name


class UsernameValidator(UnicodeUsernameValidator):
regex = r"^[\w.@+-]+[^.@+_-]$"
message = _(
"Please enter letters, digits and @ . + - _ only and username should not end with @ . + - or _"
)


class UserSkill(BaseModel):
user = models.ForeignKey("User", on_delete=models.CASCADE, null=True, blank=True)
skill = models.ForeignKey("Skill", on_delete=models.CASCADE, null=True, blank=True)
Expand All @@ -175,7 +168,7 @@ class User(AbstractUser):
_("username"),
max_length=150,
unique=True,
help_text=_("150 characters or fewer. Letters, digits and @/./+/-/_ only."),
help_text=username_validator.message,
validators=[username_validator],
error_messages={"unique": _("A user with that username already exists.")},
)
Expand Down
15 changes: 15 additions & 0 deletions care/utils/models/validators.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import re
from typing import Iterable, List

import jsonschema
from django.core import validators
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.utils.deconstruct import deconstructible
from django.utils.translation import gettext_lazy as _


@deconstructible
Expand Down Expand Up @@ -44,6 +47,18 @@ def _extract_errors(
container.append(ValidationError(message))


@deconstructible
class UsernameValidator(validators.RegexValidator):
regex = r"^(?!.*[._-]{2})[a-z0-9](?:[a-z0-9._-]{2,14}[a-z0-9])$"
message = _(
"Username must be 4 to 16 characters long. "
"It may only contain lowercase alphabets, numbers, underscores, hyphens and dots. "
"It shouldn't start or end with underscores, hyphens or dots. "
"It shouldn't contain consecutive underscores, hyphens or dots."
)
flags = re.ASCII


@deconstructible
class PhoneNumberValidator(RegexValidator):
"""
Expand Down
49 changes: 49 additions & 0 deletions care/utils/tests/test_username_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
from django.core.exceptions import ValidationError
from django.test import TestCase

from care.utils.models.validators import UsernameValidator


class UsernameValidatorTests(TestCase):
username_validator = UsernameValidator()

valid_usernames = [
"user",
"user123",
"user_123",
"user_123-456",
"useruseruseruser",
"11useruseruser11",
]

invalid_characters = ["user@123", "user#123", "user?123", "user!123"]

consecutive_characters = ["user__123", "user--123", "user..123", "user__" "..user"]

invalid_case = ["User", "USER", "uSeR"]

invalid_length = ["usr", "user12345678901234567890", "useruseruseruseru"]

def test_valid_usernames(self):
for username in self.valid_usernames:
self.assertIsNone(self.username_validator(username))

def test_invalid_characters(self):
for username in self.invalid_characters:
with self.assertRaises(ValidationError):
self.username_validator(username)

def test_consecutive_characters(self):
for username in self.consecutive_characters:
with self.assertRaises(ValidationError):
self.username_validator(username)

def test_invalid_case(self):
for username in self.invalid_case:
with self.assertRaises(ValidationError):
self.username_validator(username)

def test_invalid_length(self):
for username in self.invalid_length:
with self.assertRaises(ValidationError):
self.username_validator(username)
9 changes: 0 additions & 9 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,3 @@ known_third_party = [
"sentry_sdk",
"simple_history"
]
extend_skip=["migrations"]

[tool.black]
extend-exclude = """
/(
| migrations
)/
"""

0 comments on commit 5a087e1

Please sign in to comment.