Skip to content

Commit 3d1ec09

Browse files
committed
[#3087] Add DB constraint that primary and alternative phonenumber differ
1 parent 788a90a commit 3d1ec09

File tree

5 files changed

+90
-2
lines changed

5 files changed

+90
-2
lines changed

src/open_inwoner/accounts/forms.py

+4
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,10 @@ def clean_phonenumber_alternative(self):
194194
"A primary phone number is required for setting an alternative phone number"
195195
)
196196
)
197+
if phonenumber_alt and phonenumber_alt == phonenumber:
198+
raise ValidationError(
199+
_("Primary and secondary phone numbers cannot be the same")
200+
)
197201

198202
return phonenumber_alt
199203

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Generated by Django 4.2.18 on 2025-03-11 15:35
2+
3+
from django.db import migrations, models
4+
from django.db.models import F
5+
6+
7+
def dedupe_phone_numbers(apps, schema_editor):
8+
User = apps.get_model("accounts", "User")
9+
10+
for user in User.objects.filter(phonenumber=F("phonenumber_alternative")):
11+
user.phonenumber_alternative = ""
12+
user.save()
13+
14+
15+
class Migration(migrations.Migration):
16+
17+
dependencies = [
18+
("accounts", "0080_user_phonenumber_alternative"),
19+
]
20+
21+
operations = [
22+
migrations.RunPython(
23+
code=dedupe_phone_numbers,
24+
reverse_code=migrations.RunPython.noop,
25+
),
26+
migrations.AddConstraint(
27+
model_name="user",
28+
constraint=models.CheckConstraint(
29+
check=models.Q(
30+
models.Q(
31+
("phonenumber", models.F("phonenumber_alternative")),
32+
_negated=True,
33+
),
34+
("phonenumber__exact", ""),
35+
_connector="OR",
36+
),
37+
name="check_alternative_phonenumber_differs_from_primary_phonenumber",
38+
),
39+
),
40+
]

src/open_inwoner/accounts/models.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from django.contrib.contenttypes.fields import GenericRelation
88
from django.core.exceptions import ValidationError
99
from django.db import models
10-
from django.db.models import Q, UniqueConstraint
10+
from django.db.models import F, Q, UniqueConstraint
1111
from django.urls import reverse
1212
from django.utils import timezone
1313
from django.utils.crypto import get_random_string
@@ -335,6 +335,11 @@ class Meta:
335335
check=~Q(phonenumber__exact="") | Q(phonenumber_alternative__exact=""),
336336
name="phonenumber_alt_requires_phonenumber_primary",
337337
),
338+
models.CheckConstraint(
339+
check=~Q(phonenumber=F("phonenumber_alternative"))
340+
| Q(phonenumber__exact=""),
341+
name="check_alternative_phonenumber_differs_from_primary_phonenumber",
342+
),
338343
]
339344

340345
def __init__(self, *args, **kwargs):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
from django.db.models import F
2+
from django.db.utils import IntegrityError
3+
4+
from open_inwoner.utils.tests.test_migrations import TestSuccessfulMigrations
5+
6+
7+
class UserMigrationTest(TestSuccessfulMigrations):
8+
migrate_from = "0080_user_phonenumber_alternative"
9+
migrate_to = (
10+
"0081_user_check_alternative_phonenumber_differs_from_primary_phonenumber"
11+
)
12+
app = "accounts"
13+
14+
def setUpBeforeMigration(self, apps):
15+
User = apps.get_model("accounts", "User")
16+
17+
user = User.objects.create(phonenumber="0612345678")
18+
19+
# no IntegrityError before migration
20+
user.phonenumber_alternative = user.phonenumber
21+
user.save()
22+
23+
def test_migrate_phonenumber_constraint(self):
24+
User = self.apps.get_model("accounts", "User")
25+
26+
self.assertFalse(User.objects.filter(phonenumber=F("phonenumber_alternative")))
27+
28+
user = User.objects.first()
29+
30+
with self.assertRaises(IntegrityError):
31+
user.phonenumber_alternative = user.phonenumber
32+
user.save()

src/open_inwoner/accounts/tests/test_user.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,16 @@ def test_plan_contact_new_count_methods(self):
112112
user.clear_plan_contact_new_count()
113113
self.assertEqual(0, user.get_plan_contact_new_count())
114114

115-
def test_constraint_phonenumber_alternative(self):
115+
def test_phonenumber_alternative_requires_primary_phonenumber(self):
116116
user = UserFactory(phonenumber="")
117117

118118
with self.assertRaises(IntegrityError):
119119
user.phonenumber_alternative = "0612345678"
120120
user.save()
121+
122+
def test_phonenumber_alternative_differs_from_non_empty_primary_phonenumber(self):
123+
user = UserFactory(phonenumber="612345678")
124+
125+
with self.assertRaises(IntegrityError):
126+
user.phonenumber_alternative = user.phonenumber
127+
user.save()

0 commit comments

Comments
 (0)