1

I am using postgreSQL with Django 1.10 and python 3.4. I have a Course model defined like this:

class Course(models.Model):

    title = models.CharField(max_length=200, unique=True)
    content = models.TextField(max_length=200)

Then i manually added unique index to the title column using the following command.

CREATE UNIQUE INDEX title_unique on wiki_course (LOWER(title));

Lets say database already has "Programming Basics" as title. When I add "programming basics" to title and hit save , it shows me the following error.

IntegrityError at /admin/wiki/course/add/
duplicate key value violates unique constraint "title_unique"
DETAIL:  Key (lower(title::text))=(programming basics) already exists.
Request Method: POST
Request URL:    http://127.0.0.1:8000/admin/wiki/course/add/
Django Version: 1.10.5
Exception Type: IntegrityError
Exception Value:    
duplicate key value violates unique constraint "title_unique"

On the otherhand if i switch the database from MySQL and try again it would tell me Course already exists.

enter image description here

Is there any way to achieve this behavior in PostgreSQL?


Update: One solution is to use to use citext type field for the title. To do this first you have to enable citext extension, using the following command.

CREATE EXTENSION IF NOT EXISTS citext;

Then use alter statement to change the type of the desired column.

ALTER TABLE course ALTER COLUMN title TYPE citext;

After executing these two queries. Django shows error in the form instead of throwing an IntegrityError exception.

If someone knows a better solution to it pls post it.

3
  • it because unique=True inside your field of title, so if you has duplicates title with same name, it should return error.. Commented Mar 2, 2017 at 15:18
  • Yes i know but i want to display an error in the form not an exception. MySQL does this right out of the box. Is there any way i can accomplish this thing in PostgreSQL. I read somewhere on Google groups about this issue, they recommend creating a function index or something in in the PostgreSQL. Is that how to solve this issue ? Commented Mar 2, 2017 at 15:22
  • @SancaKembang I updated the question pls take a look Commented Mar 2, 2017 at 17:31

1 Answer 1

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)
Sign up to request clarification or add additional context in comments.

4 Comments

I know what migrations are. I want to show an error in the form not exception
Moreover if i don't issue CREATE UNIQUE INDEX title_unique on wiki_course (LOWER(title)); command manually, postgresql won't even detect the difference between "Programming Basics" and "programming basics". It will simply add the later to the database.
Then, the "django" way to handle that would be to catch on the save method. Or, if you're talking about on a form on the validation. I will try and post the code I have in my head when I am back at a computer,and update my answer.
Yes pls post your answer. Thanks

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.