Piotr Wasilewicz
Piotr Wasilewicz

Reputation: 1821

UniqueConstraint and ignore case sensitive

I would like to use this code:

constraints = [
    models.UniqueConstraint(fields=['name', 'app'], name='unique_booking'),
]

but name and app (both of them) should not check case sensitive so "FOo" and "fOO" should be marked as the same app. How to do it?

Upvotes: 0

Views: 1885

Answers (4)

Bhaumik Brahmbhatt
Bhaumik Brahmbhatt

Reputation: 81

For Django version >= 4.0, this will be work https://docs.djangoproject.com/en/4.0/releases/4.0/#functional-unique-constraints.

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


class MyModel(models.Model):
    first_name = models.CharField(max_length=255)
    last_name = models.CharField(max_length=255)

    class Meta:
        constraints = [
            UniqueConstraint(
                Lower('first_name'),
                Lower('last_name').desc(),
                name='first_last_name_unique',
            ),
        ]

I tried and it is working for me.

Upvotes: 2

Alireza Farahani
Alireza Farahani

Reputation: 2539

For anyone using Django 4.0 and later, with the help of UniqueConstraint expressions you could add a Meta class to your model like this:

class Meta:
    constraints = [
        models.UniqueConstraint(
            'book',
            Lower('app'),
            name='unique_booking'
        ),
    ]

Upvotes: 2

Adi Gabaie
Adi Gabaie

Reputation: 146

There are 2 options:

First Option:

In case you are using postgresql and Django version which is later than 2.0 - you can use CICharField or CITextField fields.

Your model will look like that:

class DemoModelCI(models.Model):

    class Meta(object):
        constraints = [
            UniqueConstraint(fields=['app', 'name'], name='unique_booking_ci')
        ]

    name = CICharField(null=False, default=None, max_length=550, blank=False)
    app = CICharField(null=False, default=None, max_length=550, blank=False)

Let's check using the shell:

enter image description here

Note the following:

  • Working with the CI fields has some limitation if you work with multiple languages. Read documentation before using it.

  • To work with CIText you need to create the extension on your DB. You can do it in a Django migration:

from django.db import migrations
from django.contrib.postgres.operations import CITextExtension

class Migration(migrations.Migration):

    dependencies = [
        ('storage', '0037_previous_migration'),
    ]

    operations = [
        CITextExtension(),
    ]

Second Option:

Create 2 fields for each of the case insensitive fields. The first will be for display and to maintain the original case of the text. The second will be saved lowercased and will be used for checking the uniqueness.

Your model will look like:

class DemoModel(models.Model):


    class Meta(object):
        constraints = [
            UniqueConstraint(fields=['name_compare', 'app_compare'], name='unique_booking')
        ]

    name_display = models.TextField(null=False, default=None, blank=False, max_length=550)
    name_compare = models.TextField(null=False, default=None, blank=False, max_length=550)

    app_display = models.TextField(null=False, default=None, blank=False, max_length=550)
    app_compare = models.TextField(null=False, default=None, blank=False, max_length=550)

    def save(self, *args, **kwargs):
        # If explicitly received "compare" name - make sure it is casefolded
        if self.name_compare:
            self.name_compare = self.name_compare.casefold()
        # If didn't get "compare" name - use the display name and casefold it
        elif self.name_display:
            self.name_compare = self.name_display.casefold()

        # If explicitly received "compare" name - make sure it is casefolded
        if self.app_compare:
            self.app_compare = self.app_compare.casefold()
        # If didn't get "compare" name - use the display name and casefold it
        elif self.app_display:
            self.app_compare = self.app_display.casefold()

        return super(DemoModel, self).save(*args, **kwargs)

Let's check using the shell:

enter image description here

Note the following:

  • Read about "casefold" vs "lower" to decide which is better for your case.

Upvotes: 3

ruddra
ruddra

Reputation: 51988

As per this ticket, these changes which are needed to implement case insensitive unique constaints have already been implemented in the Django codebase(found the codes in GitHub) and the code is currently(as of 29/04/2021) is in master branch. But the changes are not in latest Django version 3.2. Probably you should be able to use them in Django >= 3.3. Then the code should be like this:

# not implemented in Django <= 3.2
models.UniqueConstraint(fields=[Lower('name'), Lower('app')], name='unique_booking', expression='')

In the mean time, you can either try to store them by lowercasing the values on save:

class YourModel():
    def save(self, *args, **kwargs):
        self.name = self.name.lower()
        self.app = self.app.lower()
        super().save(*args, **kwargs)

Or consider using CIText field if you are using postgres database.

Upvotes: 3

Related Questions