feat(User): PRWLR-4988 Make users' email case insensitive (#56)

* feat(User): PRWLR-4988 make User.email case insensitive

* test(User): PRWLR-4988 update unit tests

* feat(User): PRWLR-4988 include email validation in serializer
This commit is contained in:
Víctor Fernández Poyatos
2024-10-17 19:22:20 +02:00
committed by GitHub
parent 6d69a192f3
commit a8825c385b
5 changed files with 33 additions and 19 deletions
+3
View File
@@ -58,6 +58,9 @@ class CustomUserManager(BaseUserManager):
user.save(using=self._db)
return user
def get_by_natural_key(self, email):
return self.get(email__iexact=email)
def enum_to_choices(enum_class):
"""
+6 -1
View File
@@ -156,7 +156,12 @@ class Migration(migrations.Migration):
blank=True, null=True, verbose_name="last login"
),
),
("email", models.EmailField(max_length=254, unique=True)),
(
"email",
models.EmailField(
max_length=254, unique=True, help_text="Case insensitive"
),
),
("company_name", models.CharField(max_length=150, blank=True)),
("is_active", models.BooleanField(default=True)),
("date_joined", models.DateTimeField(auto_now_add=True)),
+6 -1
View File
@@ -69,7 +69,7 @@ class StateChoices(models.TextChoices):
class User(AbstractBaseUser):
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
name = models.CharField(max_length=150, validators=[MinLengthValidator(3)])
email = models.EmailField(max_length=254, unique=True)
email = models.EmailField(max_length=254, unique=True, help_text="Case insensitive")
company_name = models.CharField(max_length=150, blank=True)
is_active = models.BooleanField(default=True)
date_joined = models.DateTimeField(auto_now_add=True, editable=False)
@@ -82,6 +82,11 @@ class User(AbstractBaseUser):
def is_member_of_tenant(self, tenant_id):
return self.memberships.filter(tenant_id=tenant_id).exists()
def save(self, *args, **kwargs):
if self.email:
self.email = self.email.strip().lower()
super().save(*args, **kwargs)
class Meta:
db_table = "users"
+12 -17
View File
@@ -38,32 +38,27 @@ class TestUserViewSet:
valid_user_payload = {
"name": "test",
"password": "newpassword123",
"email": "newuser@example.com",
"email": "NeWuSeR@example.com",
}
response = client.post(
reverse("user-list"), data=valid_user_payload, format="json"
)
assert response.status_code == status.HTTP_201_CREATED
assert User.objects.filter(email=valid_user_payload["email"]).exists()
assert User.objects.filter(email__iexact=valid_user_payload["email"]).exists()
assert (
response.json()["data"]["attributes"]["email"]
== valid_user_payload["email"]
== valid_user_payload["email"].lower()
)
def test_users_invalid_create(self, client):
invalid_user_payload = {
"name": "test",
"password": "thepasswordisfine123",
"email": "invalidemail",
}
response = client.post(
reverse("user-list"), data=invalid_user_payload, format="json"
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert (
response.json()["errors"][0]["source"]["pointer"]
== "/data/attributes/email"
)
def test_users_create_duplicated_email(self, client):
# Create a user
self.test_users_create(client)
# Try to create it again and expect a 400
with pytest.raises(AssertionError) as assertion_error:
self.test_users_create(client)
assert "Response status_code=400" in str(assertion_error)
@pytest.mark.parametrize(
"password",
+6
View File
@@ -177,6 +177,12 @@ class UserCreateSerializer(BaseWriteSerializer):
validate_password(value, user=user)
return value
def validate_email(self, value):
normalized_email = value.strip().lower()
if User.objects.filter(email__iexact=normalized_email).exists():
raise ValidationError("User with this email already exists.")
return value
def create(self, validated_data):
password = validated_data.pop("password")
user = User(**validated_data)