From f8d299563e640f7f8bc178d2d5de9a9b0ca7e703 Mon Sep 17 00:00:00 2001 From: Jori Lindell Date: Fri, 20 Sep 2024 11:13:42 +0300 Subject: [PATCH] Allow duplicate email addresses Unique constraint are dropped from account_emailaddress table because it's already previously added in the account migrations. Instead add unique together constraint for email and user_id fields. See 0002_email_max_length.py migration in allauth/account. ALTER TABLE account_emailaddress DROP CONSTRAINT account_emailaddress_email_key; ALTER TABLE account_emailaddress ADD CONSTRAINT account_emailaddress_email_key UNIQUE (user_id, email); --- respa/settings.py | 1 + users/api.py | 3 +- ..._allow_duplicate_account_emailaddresses.py | 37 +++++++++++++++++++ users/tests/test_api.py | 15 ++++++++ 4 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 users/migrations/0012_allow_duplicate_account_emailaddresses.py diff --git a/respa/settings.py b/respa/settings.py index 9e3ad5c44..4660be8b2 100644 --- a/respa/settings.py +++ b/respa/settings.py @@ -284,6 +284,7 @@ def get_git_revision_hash(): SOCIALACCOUNT_PROVIDERS = {"tampere": {"VERIFIED_EMAIL": True}} LOGIN_REDIRECT_URL = "/" +ACCOUNT_UNIQUE_EMAIL = False ACCOUNT_LOGOUT_ON_GET = True SOCIALACCOUNT_ADAPTER = "tamusers.adapter.SocialAccountAdapter" diff --git a/users/api.py b/users/api.py index d97de6551..b3b1f8cb5 100644 --- a/users/api.py +++ b/users/api.py @@ -1,4 +1,5 @@ from allauth.socialaccount.models import EmailAddress +from django.conf import settings from django.contrib.auth import get_user_model from rest_framework import permissions, serializers, generics, viewsets, status from rest_framework.decorators import action @@ -97,7 +98,7 @@ def set_email(self, request, pk=None): status=status.HTTP_400_BAD_REQUEST ) - if EmailAddress.objects.filter(email=email): + if settings.ACCOUNT_UNIQUE_EMAIL and EmailAddress.objects.filter(email=email): return Response( {"detail": "The email address is already in use."}, status=status.HTTP_400_BAD_REQUEST diff --git a/users/migrations/0012_allow_duplicate_account_emailaddresses.py b/users/migrations/0012_allow_duplicate_account_emailaddresses.py new file mode 100644 index 000000000..4d5f24b9c --- /dev/null +++ b/users/migrations/0012_allow_duplicate_account_emailaddresses.py @@ -0,0 +1,37 @@ +from django.db import migrations + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0002_email_max_length'), + ('users', '0011_change_last_name_length'), + ] + + operations = [ + migrations.RunSQL( + sql=[ + """ + DO $$ + DECLARE + r RECORD; + BEGIN + FOR r IN + SELECT conname + FROM pg_constraint + JOIN pg_class ON conrelid = pg_class.oid + WHERE pg_class.relname = 'account_emailaddress' + AND conname LIKE '%email%' + AND contype = 'u' + LOOP + EXECUTE 'ALTER TABLE account_emailaddress DROP CONSTRAINT ' || r.conname; + END LOOP; + END $$; + """, + "ALTER TABLE account_emailaddress ADD CONSTRAINT account_emailaddress_email_key UNIQUE (user_id, email);", + ], + reverse_sql=[ + "ALTER TABLE account_emailaddress DROP CONSTRAINT account_emailaddress_email_key;", + "ALTER TABLE account_emailaddress ADD CONSTRAINT account_emailaddress_email_key UNIQUE (email);", + ], + ), + ] diff --git a/users/tests/test_api.py b/users/tests/test_api.py index 8e8cffb84..b26165054 100644 --- a/users/tests/test_api.py +++ b/users/tests/test_api.py @@ -1,4 +1,5 @@ from allauth.account.models import EmailAddress +from django.conf import settings from django.contrib.auth import get_user_model from django.urls import reverse from rest_framework import status @@ -64,6 +65,7 @@ def test_set_email_not_provided(self): self.assertEqual(self.user.email, "") def test_set_email_already_in_use(self): + settings.ACCOUNT_UNIQUE_EMAIL = True url = reverse("user-set-email", kwargs={"pk": self.user.pk}) data = {"email": "other@example.com"} # Email already used by other_user @@ -74,3 +76,16 @@ def test_set_email_already_in_use(self): response.data, {"detail": "The email address is already in use."} ) self.assertEqual(self.user.email, "") + + def test_allow_to_set_duplicate_email(self): + settings.ACCOUNT_UNIQUE_EMAIL = False + url = reverse("user-set-email", kwargs={"pk": self.user.pk}) + data = {"email": "other@example.com"} # Email already used by other_user + + response = self.client.post(url, data, format="json") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual( + response.data, {"detail": "Email set successfully."} + ) + self.assertEqual(self.user.email, "other@example.com")