migrations: Register (UPPER(email), realm) indexes in UserProfile.Meta.

It's nicer to have these indexes properly registered, rather than hidden
in RunSQL operations. Now that Django has had support for unique
functional indexes for a while, let's clean this up.
This commit is contained in:
Mateusz Mandera 2024-09-29 22:29:02 +02:00 committed by Tim Abbott
parent a8217aee36
commit e655a7b251
3 changed files with 47 additions and 27 deletions

View File

@ -3312,6 +3312,23 @@ class Migration(migrations.Migration):
name="zerver_userpresence_realm_last_update_id_idx",
),
),
# Ensure users have unique email addresses, case-insensitive, within their realm.
migrations.AddConstraint(
model_name="userprofile",
constraint=models.UniqueConstraint(
models.F("realm"),
django.db.models.functions.text.Upper(models.F("email")),
name="zerver_userprofile_realm_id_email_uniq",
),
),
migrations.AddConstraint(
model_name="userprofile",
constraint=models.UniqueConstraint(
models.F("realm"),
django.db.models.functions.text.Upper(models.F("delivery_email")),
name="zerver_userprofile_realm_id_delivery_email_uniq",
),
),
migrations.AddIndex(
model_name="usertopic",
index=models.Index(
@ -3403,13 +3420,6 @@ class Migration(migrations.Migration):
CREATE UNIQUE INDEX zerver_stream_realm_id_name_uniq ON zerver_stream (realm_id, upper(name::text));
"""
),
# Ensure users have unique email addresses, case-insensitive, within their realm.
migrations.RunSQL(
"""
CREATE UNIQUE INDEX zerver_userprofile_realm_id_email_uniq ON zerver_userprofile (realm_id, upper(email::text));
CREATE UNIQUE INDEX zerver_userprofile_realm_id_delivery_email_uniq ON zerver_userprofile (realm_id, upper(delivery_email::text));
"""
),
# Set up full-text search indexes.
migrations.RunSQL(
sql=get_fts_sql(),

View File

@ -1,4 +1,5 @@
from django.db import migrations
from django.db import migrations, models
from django.db.models.functions import Upper
class Migration(migrations.Migration):
@ -7,24 +8,21 @@ class Migration(migrations.Migration):
]
operations = [
# Zulip has always had case-insensitive matching for email
# addresses on UserProfile objects. But Django's
# unique_together feature only supports case-sensitive
# indexes. So we reply the old unique_together index with a
# new case-insensitive index.
#
# Further, when we created the delivery_email field, we
# neglected to create a unique index on (realm_id,
# delivery_email), which meant race conditions or logic bugs
# could allow duplicate user accounts being created in
# organizations with EMAIL_ADDRESS_VISIBILITY_ADMINS. We
# correct this by adding the appropriate unique index there as
# well.
migrations.RunSQL(
"""
CREATE UNIQUE INDEX zerver_userprofile_realm_id_email_uniq ON zerver_userprofile (realm_id, upper(email::text));
CREATE UNIQUE INDEX zerver_userprofile_realm_id_delivery_email_uniq ON zerver_userprofile (realm_id, upper(delivery_email::text));
"""
migrations.AddConstraint(
model_name="userprofile",
constraint=models.UniqueConstraint(
models.F("realm"),
Upper(models.F("email")),
name="zerver_userprofile_realm_id_email_uniq",
),
),
migrations.AddConstraint(
model_name="userprofile",
constraint=models.UniqueConstraint(
models.F("realm"),
Upper(models.F("delivery_email")),
name="zerver_userprofile_realm_id_delivery_email_uniq",
),
),
migrations.AlterUniqueTogether(
name="userprofile",

View File

@ -5,7 +5,7 @@ from uuid import uuid4
from django.conf import settings
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin, UserManager
from django.db import models
from django.db.models import CASCADE, Q, QuerySet
from django.db.models import CASCADE, F, Q, QuerySet
from django.db.models.functions import Upper
from django.db.models.signals import post_save
from django.utils.timezone import now as timezone_now
@ -617,6 +617,18 @@ class UserProfile(AbstractBaseUser, PermissionsMixin, UserBaseSettings):
ROLE_API_NAME_TO_ID = {v: k for k, v in ROLE_ID_TO_API_NAME.items()}
class Meta:
constraints = [
models.UniqueConstraint(
"realm",
Upper(F("email")),
name="zerver_userprofile_realm_id_email_uniq",
),
models.UniqueConstraint(
"realm",
Upper(F("delivery_email")),
name="zerver_userprofile_realm_id_delivery_email_uniq",
),
]
indexes = [
models.Index(Upper("email"), name="upper_userprofile_email_idx"),
]