Repository: WeblateOrg/weblate

Issue:
Utilize expression index for auth
### Describe the problem

Weblate does manually create index using `CREATE UNIQUE INDEX` during the `weblate.auth` model migration.

### Describe the solution you'd like

This should be changed to use Django standard expression index `UniqueConstraint(...Upper(...))` instead for better readability and portability.

### Describe alternatives you've considered

_No response_

### Screenshots

_No response_

### Additional context

Need Django 4.0+ (Weblate already requires 4.2).

See also https://stackoverflow.com/a/69752763/225718 and https://stackoverflow.com/a/66966391/225718


Discussion:
This issue seems to be a good fit for newbie contributors. You are welcome to contribute to Weblate! Don't hesitate to ask any questions you would have while implementing this.

You can learn about how to get started in our [contributors documentation](https://docs.weblate.org/en/latest/contributing/index.html).
Hi @nijel I would like to take this one for my first hacktoberfest contribution
You're welcome 
Is this still open? I am looking to contribute to it I am newbie as well. 
@nishitiwari22 i'm working on it. Apologies.

Resolving patch:
diff --git a/weblate/auth/migrations/0002_squashed_weblate_5.py b/weblate/auth/migrations/0002_squashed_weblate_5.py
--- a/weblate/auth/migrations/0002_squashed_weblate_5.py
+++ b/weblate/auth/migrations/0002_squashed_weblate_5.py
@@ -15,21 +15,6 @@
 import weblate.utils.fields
 import weblate.utils.validators
 
-CREATE = "CREATE UNIQUE INDEX weblate_auth_user_{0}_ci ON weblate_auth_user(UPPER({0}))"
-DROP = "DROP INDEX weblate_auth_user_{0}_ci"
-
-
-def create_index(apps, schema_editor):
-    if schema_editor.connection.vendor == "postgresql":
-        schema_editor.execute(CREATE.format("username"))
-        schema_editor.execute(CREATE.format("email"))
-
-
-def drop_index(apps, schema_editor):
-    if schema_editor.connection.vendor == "postgresql":
-        schema_editor.execute(DROP.format("username"))
-        schema_editor.execute(DROP.format("email"))
-
 
 class Migration(migrations.Migration):
     replaces = [
@@ -459,6 +444,18 @@ class Migration(migrations.Migration):
                 "unique_together": {("user", "project")},
             },
         ),
-        # TODO: replace with UniqueConstraint(...Upper(...)) see https://github.com/WeblateOrg/weblate/issues/9973
-        migrations.RunPython(create_index, drop_index, elidable=False, atomic=False),
+        migrations.AddConstraint(
+            model_name="user",
+            constraint=models.UniqueConstraint(
+                django.db.models.functions.text.Upper("username"),
+                name="weblate_auth_user_username_ci",
+            ),
+        ),
+        migrations.AddConstraint(
+            model_name="user",
+            constraint=models.UniqueConstraint(
+                django.db.models.functions.text.Upper("email"),
+                name="weblate_auth_user_email_ci",
+            ),
+        ),
     ]
diff --git a/weblate/auth/models.py b/weblate/auth/models.py
--- a/weblate/auth/models.py
+++ b/weblate/auth/models.py
@@ -16,7 +16,8 @@
 from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
 from django.contrib.auth.models import Group as DjangoGroup
 from django.db import models
-from django.db.models import Prefetch, Q
+from django.db.models import Prefetch, Q, UniqueConstraint
+from django.db.models.functions import Upper
 from django.db.models.signals import m2m_changed, post_save
 from django.dispatch import receiver
 from django.http import Http404
@@ -387,6 +388,10 @@ class User(AbstractBaseUser):
     class Meta:
         verbose_name = "User"
         verbose_name_plural = "Users"
+        constraints = [
+            UniqueConstraint(Upper("username"), name="weblate_auth_user_username_ci"),
+            UniqueConstraint(Upper("email"), name="weblate_auth_user_email_ci"),
+        ]
 
     def __str__(self):
         return self.full_name


Test patch: