Monday, April 4, 2011

Django howto: Non-conflicting slugs

A while ago I was testing a Django project at work. In the project we had a Django app called Groups. To create a group, you should point the browser to www.domain.tld/group/create/ and to view a group, you had to point your browser to www.domain.tld/group/<group_slug>/. Of course a group slug is unique, so we should never have any conflicts. That is, until I decided to create a group with the slug 'create'.

As expected, I was faced by the 'Create a new group'-page when I tried to view my pretty new group. When I swapped some URLs in de Groups app, everybody who tried to create a new group, was served the page of my beautiful group. Everything worked as expected, but still it was considered a bug. This was not the kind of functionality the customer was looking for. For the time being we just let it be (what are the chances the customer would create a group with the slug 'create'?), but the case kept whining in the back of my head.
Until today. I decided to tackle the problem. And of course, it was easier than I thought.

After entering some smart queries, Google told me that there is something called the Django test client. Django says: 'The test client is a Python class that acts as a dummy Web browser, allowing you to test your views and interact with your Django-powered application programmatically.' (source) In other words: The test client can be used to send a request to and get a response from your server. This could come in handy!

Long story short: I decided to create a SaveSlugField that uses the Django test client to check if a certain slug (or URL) already exists. If the slug already exists, it throws a standard validation error. Here's the code you've been craving for:

from django import forms
from django.test.client import Client

class SaveSlugField(forms.SlugField):

    def slug_url_exists(self, slug):
        This function uses the Django test Client to determine whether or
        not a given URL (or slug) is already being used in this website.

        c = Client()
        if not slug[0] == '/':
            slug = '/' + slug # Add a slash to create a valid URL

        response = c.get(slug)
        if response.status_code == 404:
            return False

        return True

    def clean(self, *args, **kwargs):
        data = super(SaveSlugField, self).clean(*args, **kwargs)

        if self.slug_url_exists(data):
            raise forms.ValidationError('This slug is not available')

        return data

I think the code is pretty self explaining. This SaveSlugField checks if the URL www.domain.tld/<slug>/ is still available. Of course you can use subclassing (like I subclassed SlugField) to check for your custom slugs/URLs like www.domain.tld/group/<group_slug>/.

If there are any better, prettier, easier or faster way to solve this problem, do not hesitate to let me know in the comments!

Thanks @ polichism. I was afraid I might be kind of confusing ;-)
For the sake of clarity: The URL config in the Group app probably looked a little like this:
url(r'^group/create/$', 'create', name = 'groups.create'),
url(r'^group/(?P<slug>[-\w]+)/$', 'view', name = 'groups.view'),