7

I'm currently working on a toy project in Django.

Part of my app allows users to leave reviews. I'd like to take the title of the review and slugify it to create a url.

So, if a user writes a review called "The best thing ever!", the url would be something like: www.example.com/reviews/the-best-thing-ever.

That's all well and good, but what is the best way to handle case where two users pick the same title? I don't want to make the title required to be unique.

I've thought about adding the review id in the url somewhere, but I'd like to avoid that extra info for any urls that don't collide.

Any ideas?

TM.
  • 108,298
  • 33
  • 122
  • 127

4 Answers4

6

I would recommend something like AutoSlugField. It has a few options available with respect to configuring uniqueness (unique and unique_with), and has the added benefit of being able to automatically generate slugs based on another field on your model, if you so choose.

Ryan Duffield
  • 18,497
  • 6
  • 40
  • 40
6

One thing I never liked about the unique slug fields/methods is that if you have a lot of clashes for a single title, you'll end up running several queries to try and determine an available slug. I know you mentioned you don't want to show the id for non-clashing slugs, but, as far as performance, I think it's the better route to take. To make the URL a little nicer looking, I prefer also to embed the id before the slug, so that a URL takes the form of www.example.com/reviews/1/the-best-thing-ever.

Adam
  • 7,067
  • 2
  • 27
  • 24
  • This is the solution I was considering, although in this case, slug turns out to be meaningless eye candy. It's fast though and frees you from this problem entirely. In a "real" app i'd probably take this route, but I'm still interested in how one would handle this and still allow non-colliding urls to be totally free of any ids. – TM. Sep 29 '09 at 16:34
  • 1
    I should also note that this seems to be the same approach that SO uses, if you glance up at the url bar :) – TM. Sep 29 '09 at 16:36
  • Well, it looks like I'm in good company then. As far as eliminating all ids, the two other answers are definitely the way to go. When I originally started slugging things, I used a unique slug method I found somewhere (it's nearly identical to the one Zalew posted). – Adam Sep 29 '09 at 16:53
  • I wouldn't call it meaningless eye candy, it still serves its purpose for SEO. – Shinhan Feb 26 '10 at 07:30
  • @Shinhan: (puts on flame coat) isn't SEO meaningless? – Lie Ryan May 13 '11 at 16:31
  • 1
    I know I'm way too late for any comment, but still wanted to point this out: Using id's in url and relying on them for uniqueness could get you into some SEO hazards, since the following urls would contain the exact same content: `example.com/reviews/1/some-cool-thing example.com/reviews/1/other-cool-thing example.com/reviews/1/it-is-still-cool ` some bots are known for probing different urls on their own and they would 'find' these urls and screw up your SEO work – Hussam May 10 '12 at 01:00
  • The performance argument depends on how often you are expecting collissions. Furthermore you could try something like: ``my_title = my_title + '_' + str(MyModel.objects.filter(slug__istartswith=my_title).count() + 1)`` – Izz ad-Din Ruhulessin Jul 24 '12 at 14:30
2
from django.template.defaultfilters import slugify

def slugify_unique(value, model, slugfield="slug"):
        suffix = 0
        potential = base = slugify(value)
        while True:
            if suffix:
                potential = "-".join([base, str(suffix)])
            if not model.objects.filter(**{slugfield: potential}).count():
                return potential
            suffix += 1      
"""
above function is not my code, but i don't remember exactly where it comes from
you can find many snippets with such solutions searching for 'unique slug' and so
"""


class ReviewForm(forms.ModelForm):

    def save(self, user, commit=True):    
        self.instance.slug = slugify_unique(self.cleaned_data['title'], self.Meta.model)                       
        review = super(ReviewForm, self).save(commit)
        review.save()
        return review

    class Meta:
        model = Review

of course change the appropriate names and form definition, but you get the idea :)

zalew
  • 10,171
  • 3
  • 29
  • 32
0

I would (in the form validation) just check to see if the slug is used, and then add something to it, either a number "my-cool-idea_2" or the actual id