[Django]-How to catch A 'UNIQUE constraint failed' 404 in django

77👍

from django.db import IntegrityError

except IntegrityError:

This is what you need.

EDITED for @mbrochh:

from django.db import IntegrityError

except IntegrityError as e: 
    if 'unique constraint' in e.message: # or e.args[0] from Django 1.10
        #do something

Yes, you can be more precise but in question case UNIQUE failed is highly likely.

👤torm

27👍

IMHO, I would recommend to resolve this situation by get_or_create().

new_obj, created = AnswerModel.objects.get_or_create(user=user, yes_question=question_model)
if created:
    do_something_for_new_object(new_obj)
else:
    logging.error("Duplicated item.")
    return

10👍

Usually the “ask for forgiveness” principle is a good practice in programming but in this special case, I would not recommend it.

The exception you are looking for is IntegrityError. You could have easily figured that out yourself by simply removing the try-catch block and forcing that exception. The traceback shows the exception class.

The problem is, there are several different kinds of integrity errors, so inside your try-catch block you would have to check for something like if ex.pgcode == 23505 to see if this is actually a UNIQUE constraint error. This has been answered before here: IntegrityError: distinguish between unique constraint and not null violations

It gets worse: Each ORM has different error codes, the field name will not be pgcode but something else and some ORMs don’t throw UNIQUE constraints at all. So if you are building a reusable app or if you are using a ORM that sucks (such as MySQL) or if you are not sure if you will change the database of your project at some time in the future, you should not do this!

The better way is simply removing the try-catch block and check if the object is already in the database before saving.

I don’t know which field is UNIQUE in your case, so I will just assume that it is the user field. Your code would look something like this:

answers = AnswerModel.objects.filter(user=user)
if answers:
   return HttpResponseRedirect('/user/already_exists')
obj = AnswerModel.objects.create(user=user, yes_question=question_model)
...

If you are dealing with a combined unique constraint, the first line would be this:

answers = AnswerModel.objects.filter(user=user, yes_question=question_model)

4👍

For Python > 3.5

You need:

except IntegrityError as e: 
   if 'unique constraint' in str(e.args).lower():       
   

Example:

from django.db import IntegrityError

 for column in csv.reader(io_string, delimiter=',', quotechar="|"):
     try:
         _, created = Brand.objects.update_or_create(
            system_id=column[0],
            name=column[1],
            slug=column[2],
            description=column[3],
            image=column[4]
           )
     except IntegrityError as e: 
           if 'unique constraint' in str(e.args).lower():
               continue 
           else:
               raise e # Not the unique error we were expecting

2👍

Found in django.db.models.query.QuerySet._create_object_from_params. And with some change:

from typing import Any, Dict, Type  
from django.db import transaction, IntegrityError, Model


def get_or_create_obj(model: Type[Model], defaults: Dict[str, Any] = None, **kwargs: Any):
    defaults = defaults or {}
    try:
        with transaction.atomic():
            return model.objects.create(**kwargs, **defaults), True
    except IntegrityError as e:
        try:
            return model.objects.using("default").get(**kwargs), False
        except model.DoesNotExist:
            pass
        raise e

1👍

Unique constraint fail return :”(‘UNIQUE constraint failed: notepad_notepadform.title’)” which is basically a tuple,So we can use below code to catch it and do whatever required:

from django.db import IntegrityError
try:
        if form.is_valid():
            form.save()

    except IntegrityError as e:
        if 'UNIQUE constraint' in str(e.args):
            #your code here

1👍

After a brief search, I see that this is a problem, and hopefully, it will be addressed by the Django team. See: https://code.djangoproject.com/ticket/34610

I have a workaround and it involves the use of the save method of Model.
First I wanted to use the clean method however somehow it did not work. Bu save is good enough.

Problem

Django raises an IntegrityError and redirects the error message of the database management system when a unique constraint happens. However, an IntegrityError can be risen for other errors too. So one needs to check the message of the error to see if the error is due to a unique constraint.

But different database management systems use different messages for unique constraints such as:

  • Sqlite: Error while executing SQL query on database '[The databse]': UNIQUE constraint failed: [The field]
  • MariaDB: SQL Error (1062): Duplicate entry '[The value]' for key '[The field]'

as you see finding a pattern for some common text looks like a challenge.

Solution

Override the save method of the Model and check if any other entry already exist on save.

Please notice save can be invoked when one creates a new entry or updates an entry.

Here I have a Countries model that would save list of available countries.

class Countries(models.Model):
    created_at = models.DateTimeField(auto_created=True, auto_now_add=True)
    name = models.CharField(max_length=128, null=False, blank=False)
    code = models.CharField(max_length=128, null=False, blank=False, unique=True)
    timezone = models.CharField(max_length=128, null=False, blank=False)

    def __str__(self):
        return self.name

    def save(
            self, force_insert=False, force_update=False, using=None, update_fields=None
    ):
        # To check unique constraint.

        try:
            country_with_the_same_code = Countries.objects.get(code=self.code)
            # Get a country that has the code (must be unique)
            # If there is no such country, DoesNotExist exception will be risen,
            # and the next lines will be skipped.

            # Check if self.pk is not equal to the existing country's pk with the
            # same code
            # If it is not equal, It means someone is trying to create a new country
            # or update a country, and the code is equal to some other entries code.
            # (Raise an error)

            # if it is equal, it means someone is trying to update a country with the same
            # code of itself. (No error)
            if self.pk != country_with_the_same_code.pk:
                raise ValidationError("Unique Constraint failed for code")
        except Countries.DoesNotExist:
            # If no country is available with the same code (must be unique)
            # do nothing
            pass

        # Just execute save method of super.
        super(Countries, self).save(force_insert=force_insert, force_update=force_update, using=using,
                                    update_fields=update_fields)

I over-commented the save method, just because I am lazy to explain it here…

And here how one can use it:

try:
    the_country = Countries(name=name, code=code, timezone=timezone)
    the_country.save()
except ValidationError as e:
        if "Unique Constraint failed" in str(e):
            print("Not unique")

Update

Creating a custom Exception such as Unique instead of ValidationError would be a better approach. That way you do not need to check the message and the usage would change to be as such:

try:
    the_country = Countries(name=name, code=code, timezone=timezone)
    the_country.save()
except Unique:
        print("Not unique")
            
👤MSH

Leave a comment