[Answered ]-Django unexpected IntegrityError with PostgreSQL

2👍

MIGRATIONS

Your best bet is to not alter constraints on the database itself and instead allow Django to handle changes to your models.

Let’s say you had this already existing.

class Course(models.Model):
    title = models.CharField(max_length=200)

Then, you decide to make the title unique.

class Course(models.Model):
    title = models.CharField(max_length=200, unique=True)

To enforce this, you do not go into the database and call the command directly. Instead we allow Django to handle the migrations for us.

$ ./manage.py makemigrations
$ ./manage.py migrate

SOLUTION #1 – ON SAVE

class Course(models.Model):
    title = models.CharField(max_length=200, unique=True)

    def save(self, *args, **kwargs):
        try:
            Course.objects.get(title__iexact=self.title)
            raise models.ValidationError
        except Course.DoesNotExist:
            pass
        super().save(*args, **kwargs)

The problem with this method is that it makes another call to your database for each save. Not ideal.

SOLUTION #2 – Two fields

You could also have a sort of dummy field that always stores the lower case string and provide the unique on that.

class Course(models.Model):
    title = models.CharField(max_length=200)
    lower_title = models.CharField(max_length=200, unique=True, blank=True)

    def save(self, *args, **kwargs):
        self.lower_title = self.title.lower()
        super().save(*args, **kwargs)

Here, we set blank=True so that the lower_title does not throw an error for being empty.

One drawback of this is that it creates slightly more overhead in your database because the title is stored in two locations. The idea here though being that we run the unique constraint against a field that we ALWAYS know will be lowercase. Then, just use title where you need to.

Similarly, we could use django’s slugify to achieve a similar result.

from django.template.defaultfilters import slugify


class Course(models.Model):
    title = models.CharField(max_length=200)
    slug = models.SlugField(max_length=200, unique=True)

    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super().save(*args, **kwargs)

Leave a comment