Is it possible for me to create a reusable case-insensitive unique CharField in this way?

With my current code I know it is not working, I can still save duplicate values in a model that uses my custom field. So I’m wondering if what I want to do is impossible, or if parts of it are impossible (like automatically assigning the model and app name in the name of the new UniqueConstraint).

I know I could make a custom field that automatically converts everything to lowercase before saving, but then that does not help me preserve the casing of, for example, a name such as Charles de Gaulle, so it’s not a solution.

If it is not impossible, what am I doing wrong?

# fields.py

from django.db import models
from django.db.models import UniqueConstraint
from django.db.models.functions import Lower


class CIUniqueChar255Field(models.CharField):
    def __init__(self, *args, **kwargs):
        kwargs['max_length'] = kwargs.get('max_length', 255)
        kwargs['unique'] = kwargs.get('unique', True)
        super().__init__(*args, **kwargs)

    def contribute_to_class(self, cls, name, **kwargs):
        super().contribute_to_class(cls, name, **kwargs)
        if not cls._meta.abstract:
            meta = getattr(cls, 'Meta', None)
            if not hasattr(meta, 'constraints'):
                setattr(cls, 'Meta', type('Meta', (), {'constraints': []}))
                meta = cls.Meta

            self.model = cls
            app_name = self.model._meta.app_label
            model_name = self.model._meta.model_name

            meta.constraints.append(
                UniqueConstraint(
                    Lower([name]),
                    name=f'ci_unique_{name}_{model_name}_{app_name}'
                )
            )


# models.py

from .fields import CIUniqueChar255Field

class MyModel(models.Model):
    identifier = CIUniqueChar255Field()

I don’t think that should be a list in the Lower function. See the example at Constraints reference | Django documentation | Django.

It seems to be concerned about text filtering.

Without having to store case-sensitively, you can search case-insensitively in filtering.

Match
{field}__exact={string}
Include
{field}__icontains={string}