I have a Django form entry where I want the model saved to the database to have a unique title, or rather a unique "slug" (derived from the title). I do this by appending a counter to the slug. So something like "this-is-a-title-1" and possibly then later "this-is-a-title-2".
But if two users at the same time submit an entry with the same title, only one entry will make it to the database (the slug field is unique); saving the other will throw an IntegrityError. To create a unique counter, I now loop upon encountering such a a duplicate entry, as follows:
class MyClass(models.Model):
self.title = models.CharField()
self.slug = models.SlugField(unique=True, blank=False)
def save(*args, **kwargs):
baseslug = slugify(self.title)
self.slug = baseslug + "-1"
count = 1
while count < MAXCOUNT: # MAXCOUNT something like 1000
try:
super(MyClass, self).save(*args, **kwargs)
except IntegrityError as exc:
count += 1
self.slug = baseslug + "-{:d}".format(count)
else:
break # we're fine; break out of the loop
I do this through the admin, where the slug is not entered explicitly (but is created in the save()
method).
My question is: is this a good idea? Somehow it feels like a work-around that has a better solution.
Another solution I can think of is to pop-up a message to the unfortunate second user, but that would only say that "your entry can't be saved at this time. Try again in a few seconds." I would then also first need to get the current count
from existing (identical-except-for-the-counter) slugs. The above method automatically avoids that, although it will run into problems if ever there are more than MAXCOUNT
identical slugs.
Are there any other suggestions, or does this seems like a viable solution?
(Note: I came across this similar question suggested by SO while entering my question, but there, a get()
is first performed before saving the entry. This potentially allows enough time between the get()
and save()
to still attempt to save duplicate entries, thus causing an IntegrityError. Otherwise it appears to suggest pretty much the same solution as I have now.)