doniyor
doniyor

Reputation: 37904

django - detect IntegrityError without "save()"

ok, i need a little help here.

I have a model which has a field called slug = models.SlugField(unique=True), and i am trying to set this field on save() by appending 1 to slug if slug already exists and so on.

I want to consider race conditions.

def set_uniqslug(self, slug, i=0):
    new_slug = u"{}{}".format(slug, str(i) if i else '')
    try:
        with transaction.atomic():
            self.slug = slugify(new_slug.lower())
            self.save()
            return self
        return self
    except IntegrityError as e:
        i += 1
        return set_uniqslug(self, slug, i)

def save(self, *args, **kwargs):
    if not self.pk:
        set_uniqslug(self.name.lower()) # <--- but it does "save" above. 
        # i want something like:
        # self.slug = self.get_uniqslug(self.name.lower())
        super(Company, self).save(*args, **kwargs)

my problem is, if i call the set_uniqslug(), it needs to try to save, just to know if there is IntegrityError. in my code, it goes into infinite loop.

how can I know without saving if there is IntegrityError and then just return the unique slug back to save() method?

update:

i tried this:

with transaction.atomic():
    if Company.objects.filter(slug=new_slug).exists():
        i += 1
        return self.set_uniqslug(slug, i)
    return new_slug

it is working, but i have a stomachache by locking READ-action. am I not blocking other queries or doing any other bad stuff by doing this?

Upvotes: 1

Views: 301

Answers (1)

Kevin Christopher Henry
Kevin Christopher Henry

Reputation: 49012

Your check-and-set version will probably not work. That will depend on your database and its implementation of the transaction isolation levels; but taking PostgreSQL as an example, the default READ COMMITTED isolation level will not prevent another transaction from inserting a row with the same slug in between your check and set.

So use your original, optimistic locking idea. As Hugo Rodger-Brown pointed out, you can avoid the infinite loop by calling the superclass's save().

Finally, you might want to consider an alternative slug format. Many times the slug will incorporate the database id (similar to StackOverflow itself, actually), which eliminates the possibility of duplicate slugs.

Upvotes: 1

Related Questions