Using Django, an example of how to auto populate a slug field based on the name or title of whatever the thing is. Correctly handles duplicate values (slugs are unique), and truncates slug if value too long. Includes sample unit tests!
The built in Django model.SlugField() is basically the same thing as a model.CharField(). It leaves the work of populating the slug up to you. Do not override the save() method of your concrete model and copy and paste that code to every model in your project that needs the behavior (that causes dementia).
Django makes it possible to elegantly abstract the ‘fill in my slug’ behavior into an abstract base model. Once you setup the model the slug generation is entirely automatic. Just leave the slug field blank in your app code, call .save(), and the slug will get populated.
This is compatible with Django 1.8 and 1.9 for me on Python 2.7, and probably earlier versions Django too.
What is a slug?
A slug is a unique, URL friendly label for something, usually based on the name of whatever that thing is. For example, the book Fluent Python’s slug might be ‘fluent-python’. This blog, powered by WordPress, makes extensive use of slugs, every post gets one as you can see in the URL bar.
What about corner cases?
If the title is too long for the slug field, the value will be truncated. In the case of a duplicate name (which may be okay depending on the model), the slug will get suffixed with a number, eg ‘fluent-python-2’. There are some unit tests below so you can carry these into your project too and be confident.
My field names are not ‘name’ and ‘slug’!
That is okay. It is setup so you can customize the source field name and the slug field name on a per model basis. See the LegacyArticle example in the Gist.
Example usage in a TestCase:
row = Article() row.name = 'The Computer Is Talking Sense' row.save() self.assertEqual(row.slug, 'the-computer-is-talking-sense') # create another one with the same name, should get -2 added row2 = Article() row2.name = 'The Computer Is Talking Sense' row2.save() self.assertEqual(row2.slug, 'the-computer-is-talking-sense-2') # change the name, the slug should change too row2.name = 'Change is Good' row2.save() self.assertEqual(row2.slug, 'change-is-good') # slug gets truncated row = Article() row.name = '0123456789' * 25 row.save() self.assertEqual(row.slug, ('0123456789' * 10)) # slug gets truncated, accounts for suffix row = Article() row.name = '0123456789' * 25 row.save() self.assertEqual(row.slug, ('0123456789' * 9) + '01234567-2') # loop to trigger integrity error for i in range(1, 10): row = Article() row.name = 'loop' row.save() row = Article() row.name = 'loop' # hijack the local attribute just this once setattr(row, 'slug_max_iterations', 10) try: row.save() self.fail('Integrity exception should have been fired') except IntegrityError as e: self.assertEqual(e.message, 'Unable to locate unique slug')