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!

EDIT:
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'),

Wednesday, October 20, 2010

[UPDATED] Python: Don't touch the list you're iterating

If you are a developer that likes safe code, there's probably nothing in this post you don't know. However, if you have read my previous post, you already know that I'm a lazy developer who likes his programming languages to tell him he's a jerk. I stumbled across a Python feature that didn't tell me I am a jerk. Let's call it a quirk.

Enough for the poetry. The problem: I made a list and I wanted to remove objects from that list, when they met a certain condition. The thing I did, looked a little bit like the following snippet of code.

roll = [1, 2, 3, 4]
for segment in roll:
    if segment == 2:
        roll.remove(segment)
    else:
        print segment

When you try to run this snippet, it will print out '1, 4'. Indeed, '3' is missing. Without the code telling anybody you are a jerk. The list will eventually be [1, 3, 4], but things can be really messed up. What if we try to remove the segments that are equal to two as well as three? Python will just skip the third segment, due to an internal pointer, and the jerk in this story will end up really confused, with a list like [1, 3, 4], while he thought it would be [1, 4].

Of course you are now craving for the solution of this problem. And of course it is really simple. Just make a copy of the list you try to alter and iterate through that one. Then alter the list you want to alter. Do I make myself sound confusing again? This will probably enlighten you:

roll = [1, 2, 3, 4]
copy = list(roll) # list() makes a copy
for segment in copy:
    if segment == 2 or segment == 3:
        roll.remove(segment)

print roll

See, '[1, 4]'. Just the way you like it.

*** UPDATE ***

The example above works with removing a segment from a list. If you really want to mess things up, try appending segments. Every time you iterate through the list, you can add a segment. When you add a segment, the loop needs one more iteration. One more iteration, in which one more segment will be added and so on.

Try the following snippet AT YOUR OWN RISK!
roll = [1]
for segment in roll:
    roll.append(segment+1)
    print segment

If you want, leave a comment with the segment at which the program died.

Thursday, September 23, 2010

PHP: What equals zero, but is not empty?

In a project we did recently, we needed to check if a SHA1 hash was correctly being set. We at first tried it with the PHP empty() function. If the hash turned out to be empty, we threw an exception. However, we found out that somewhere along the way, the hash sometimes was 0 (zero). So we added another check to see if the hash was 0. If it was, we threw another exception, so the code looked something like this:
if (empty($hash)) {
    throw new Exception('Hash is empty');
}

if ($hash == 0) {
    throw new Exception('Hash equals 0 (zero)');
}

Nothing really fancy, right? Well, as it turned out, we got a lot of exceptions, saying the hash equals 0 (zero). But why? What was going on? What obvious mistake laughed us in the face, while we looked over it?

My astonishment only grew, when I took a look at the PHP manual about the empty() function. It clearly says that 0 as an integer and "0" as a string are considered to be empty. How on Earth can a hash be equal to 0, but not be empty? That's pretty impossible, according to the rules of logic. That is, until you realize that you are developing in PHP.

Some further research learned that, in PHP, if you parse a string to an integer and if "the string starts with valid numeric data, this will be the value used. Otherwise, the value will be 0 (zero)." source Yes, indeed. That means that if you have a string and that string starts with any non-numeric character or with a zero followed by any non-numeric character, and you compare that string to 0, it will return true. In short:
echo ("a9993e364706816aba3e25717850c26c9cd0d89d" == 0); //true
echo ("0e226ad77382bda133797db656efd5e8d1099014" == 0); //true
echo ("47425e4490d1548713efea3b8a6f5d778e4b1766" == 0); //finally, false!

I'm starting to believe that the first P in PHP stands for something that has to do with Pain...

Tuesday, August 31, 2010

Django Admin: Making fields read-only for editing

Sometimes, when you are developing a Django website, you want to use the Django admin interface to create and edit your custom, homemade models. How to do this, is well documented on this page on the Django website. However, sometimes you want the edit page to differ from the create page. For example, you might want users to be able to fill in a slug field for a model, but because you are afraid of broken links, you don't want them to be able to edit that slug.

Of course you can override the model's save method to raise an exception when somebody tries to edit the slug, but that is both ugly and confusing for the user. What you actually want is a standard text field when you create an object, but a read-only field if you edit an object. From the previously mentioned page, we know that Django supports something called 'readonly_fields' and that the ModelAdmin model has a method called 'get_readonly_fields'. How can we use this information to make a field read-only in edit mode? Simply override the get_readonly_fields method.

A silly example:
Let's say we have a model called Message, for which we create an admin model called MessageAdmin, following the guide from the Django website. In the admin interface, we want to show the fields 'author', 'title', 'slug' and 'text'. As you might have guessed, we want the slug to be read-only in edit mode. The MessageAdmin class will then look like this:
class MessageAdmin(admin.ModelAdmin):
    fieldsets = (
        (None, {
            'fields': ('author', 'title', 'slug', 'text',)
        }),
    )
    
    def get_readonly_fields(self, request, obj = None):
        if obj: #In edit mode
            return ('slug',) + self.readonly_fields
        return self.readonly_fields

The interesting part is the overridden get_readonly_fields method. From the Django website we learn that, if there is no obj, we are in create mode. Therefor, if obj exists, we are in edit mode. In this case, if we are in edit mode, we add 'slug' to the other read-only fields we may or may not have already defined and return all the read-only fields. If we are not in edit mode, we return only the fields we may or may not have defined as read-only.

That's all there is to it. Nothing really exciting, but it can be really helpful to know.