diff --git a/zerver/migrations/0429_user_topic_case_insensitive_unique_toghether.py b/zerver/migrations/0429_user_topic_case_insensitive_unique_toghether.py new file mode 100644 index 0000000000..2d5c361219 --- /dev/null +++ b/zerver/migrations/0429_user_topic_case_insensitive_unique_toghether.py @@ -0,0 +1,32 @@ +# Generated by Django 4.1.6 on 2023-02-11 05:16 + +import django.db.models.functions.text +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("zerver", "0428_remove_realm_email_address_visibility"), + ] + + operations = [ + migrations.AlterUniqueTogether( + name="usertopic", + unique_together=set(), + ), + # Before adding the constraint, remove any pre-existing duplicates from the table + migrations.RunSQL( + """ + DELETE FROM zerver_usertopic WHERE id NOT IN (SELECT max(id) FROM zerver_usertopic GROUP BY (user_profile_id, stream_id, upper(topic_name::text))); + """ + ), + migrations.AddConstraint( + model_name="usertopic", + constraint=models.UniqueConstraint( + models.F("user_profile"), + models.F("stream"), + django.db.models.functions.text.Lower("topic_name"), + name="usertopic_case_insensitive_topic_uniq", + ), + ), + ] diff --git a/zerver/models.py b/zerver/models.py index cccfcdfdb8..a86919c396 100644 --- a/zerver/models.py +++ b/zerver/models.py @@ -43,7 +43,7 @@ from django.core.validators import MinLengthValidator, RegexValidator, URLValida from django.db import models, transaction from django.db.backends.base.base import BaseDatabaseWrapper from django.db.models import CASCADE, Exists, F, OuterRef, Q, Sum -from django.db.models.functions import Upper +from django.db.models.functions import Lower, Upper from django.db.models.query import QuerySet from django.db.models.signals import post_delete, post_save, pre_delete from django.db.models.sql.compiler import SQLCompiler @@ -2648,7 +2648,14 @@ class UserTopic(models.Model): visibility_policy = models.SmallIntegerField(choices=visibility_policy_choices, default=MUTED) class Meta: - unique_together = ("user_profile", "stream", "topic_name") + constraints = [ + models.UniqueConstraint( + "user_profile", + "stream", + Lower("topic_name"), + name="usertopic_case_insensitive_topic_uniq", + ), + ] indexes = [ models.Index("stream", Upper("topic_name"), name="zerver_mutedtopic_stream_topic"),