If you want unique values for a slug field, but don't want to bother the user with error messages, this function can be put into a model's save function to automate unique slugs. It works 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.
Comments appreciated!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 4 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
Cool. To make it more generic you could have the
slug
andtitle
attribute 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.
#
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
...?#
Please login first before commenting.