def unique_slug(item,slug_source,slug_field):
"""Ensures a unique slug field by appending an integer counter to duplicate slugs.
The item's slug field is first prepopulated by slugify-ing the source field. If that value already exists, a counter is appended to the slug, and the counter incremented upward until the value is unique.
For instance, if you save an object titled Daily Roundup, and the slug daily-roundup is already taken, this function will try daily-roundup-2, daily-roundup-3, daily-roundup-4, etc, until a unique value is found.
Call from within a model's custom save() method like so:
unique_slug(item, slug_source='field1', slug_field='field2')
where the value of field slug_source will be used to prepopulate the value of slug_field.
"""
if getattr(item, slug_field): # if it's already got a slug, do nothing.
from django.template.defaultfilters import slugify
slug = slugify(getattr(item,slug_source))
itemModel = item.__class__
# the following gets all existing slug values
allSlugs = [sl.values()[0] for sl in itemModel.objects.values(slug_field)]
if slug in allSlugs:
import re
counterFinder = re.compile(r'-\d+$')
counter = 2
slug = "%s-%i" % (slug, counter)
while slug in allSlugs:
slug = re.sub(counterFinder,"-%i" % counter, slug)
counter += 1
setattr(item,slug_field,slug)
Comments
Cool. To make it more generic you could have the
slugandtitleattribute names passed in as keyword arguments, and then use getattr and setattr to perform the logic, allowing:#
I thought of that briefly, before being overcome by an attack of the lazies. But it should be done in the interest of portability and completeness; I'll work it out in the next few days.
Thanks
#
That was simpler than I'd thought.
#
But then if you resave a model you will get a new slug in every case. I have a similar snippet here: http://www.djangosnippets.org/snippets/369/
#
The outer 'if' loop tests for an existing slug value, and leaves it alone if it's got one. Or rather, that's what it does now – when I made it possible to specify the name of the slug and source fields, I forgot to alter the test statement to use getattr().
Won't your function keep adding additional integer counters to the end of the slug, rather than incrementing a single counter?
I do wish this site had more search abilities. It's awfully hard to tell if you're posting duplicate snippets.
#
Um, the first line of the function body isn't supposed to be
if not getattr...?#