Skip to content
Merged
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
18 changes: 18 additions & 0 deletions ami/users/managers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import UserManager as DjangoUserManager
from django.db.models import Q


class UserManager(DjangoUserManager):
Expand Down Expand Up @@ -32,3 +33,20 @@ def create_superuser(self, email: str, password: str | None = None, **extra_fiel
raise ValueError("Superuser must have is_superuser=True.")

return self._create_user(email, password, **extra_fields)

def get_by_natural_key(self, email: str):
"""
Enable case-insensitive username lookup
"""
return self.get(Q(email__iexact=email))

@classmethod
def normalize_email(cls, email: str | None) -> str:
"""
Lowercase the entire email address.

For justification see
https://docs.allauth.org/en/latest/account/email.html#case-sensitivity
"""
email = super().normalize_email(email)
return email.lower()
22 changes: 22 additions & 0 deletions ami/users/migrations/0003_lowercase_existing_emails.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 4.2.10 on 2024-09-19 17:54
from django.db import migrations


def lowercase_emails(apps, schema_editor):
User = apps.get_model("users", "User")
for user in User.objects.all():
lowercase_email = user.email.lower()
if user.email != lowercase_email:
user.email = lowercase_email
user.save(update_fields=["email"])


class Migration(migrations.Migration):

dependencies = [
("users", "0002_user_image"),
]

operations = [
migrations.RunPython(lowercase_emails),
]
6 changes: 6 additions & 0 deletions ami/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class User(AbstractUser):

objects = UserManager()

def save(self, *args, **kwargs):
if not self.email:
raise ValueError("The Email field must be set")
self.email = UserManager.normalize_email(self.email)
super().save(*args, **kwargs)

def get_absolute_url(self) -> str:
"""Get URL for user's detail view.

Expand Down
6 changes: 3 additions & 3 deletions ami/users/tests/test_managers.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def test_create_user(self):
user = User.objects.create_user(
email="john@example.com",
password="something-r@nd0m!",
)
) # type: ignore
assert user.email == "john@example.com"
assert not user.is_staff
assert not user.is_superuser
Expand All @@ -23,7 +23,7 @@ def test_create_superuser(self):
user = User.objects.create_superuser(
email="admin@example.com",
password="something-r@nd0m!",
)
) # type: ignore
assert user.email == "admin@example.com"
assert user.is_staff
assert user.is_superuser
Expand All @@ -33,7 +33,7 @@ def test_create_superuser_username_ignored(self):
user = User.objects.create_superuser(
email="test@example.com",
password="something-r@nd0m!",
)
) # type: ignore
assert user.username is None


Expand Down
50 changes: 50 additions & 0 deletions ami/users/tests/test_user_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from django.contrib.auth import get_user_model
from django.db.utils import IntegrityError
from django.urls import reverse
from rest_framework.test import APIRequestFactory, APITestCase

User = get_user_model()


class UserAuthTestCase(APITestCase):
def setUp(self):
self.factory = APIRequestFactory()
# self.client.force_authenticate(user=self.user)
self.user = User.objects.create_user(email="TEST@example.com", password="testpassword") # type: ignore

def test_case_insensitive_login(self):
login_url = reverse("api:login") # Assuming you're using django-allauth

# Test lowercase email
response = self.client.post(login_url, {"email": "test@example.com", "password": "testpassword"})
self.assertEqual(response.status_code, 200)

self.client.logout()

# Test mixed case email
response = self.client.post(login_url, {"email": "TeSt@ExAmPlE.cOm", "password": "testpassword"})
self.assertEqual(response.status_code, 200)

self.client.logout()

def test_email_stored_lowercase(self):
user = User.objects.create_user(email="UPPER@EXAMPLE.COM", password="testpassword") # type: ignore
self.assertEqual(user.email, "upper@example.com")

user.email = "MiXeD@ExAmPlE.cOm"
user.save()
user.refresh_from_db()
self.assertEqual(user.email, "mixed@example.com")

def test_change_user_email(self):
user = User.objects.create_user(email="testEXAMPL@example.com", password="testpassword") # type: ignore
user.email = "TESTexample@example.com"
user.save()
user.refresh_from_db()
self.assertEqual(user.email, "testexample@example.com")

def test_uniqueness(self):
with self.assertRaises(IntegrityError):
User.objects.create_user(email="testemail@example.com") # type: ignore
User.objects.create_user(email="testEMAIL@example.com") # type: ignore
User.objects.create_user(email="TESTemail@example.com") # type: ignore