Create automatic redirects upon changing an URL dependent field in your Django Models

If you have a model in which the get_absolute_url() result depends on a field that is easily edited (such a title or a slug that is editable in the django admin for example), you can easily get yourself into trouble with broken links on Google.

An easy way to offset this risk is to write a simple signal that creates a redirect using django.contrib.redirects every time that editable field changes.

For example, changing the slug on a BlogPost model would result in a new url:

class BlogPost(models.Model):
  # ...
  slug = models.CharField(unique=true, max_length=128)

  @models.permalink
  def get_absolute_url(def):
    return ('blogpost_detail', (), { 'slug' : self.slug })

from django.db.models.signals import pre_save
from signals import create_redirect
pre_save.connect(create_redirect, sender=BlogPost, dispatch_uid="001")

so we create a new signal handler in signals.py

from django.contrib.redirects.models import Redirect
from django.contrib.sites.models import Site

def create_redirect(sender, instance, **kwargs):
  if sender == BlogPost:
    try:
      o = sender.objects.get(id=instance.id)
      if o.slug != instance.slug:
        old_path = o.get_absolute_url()
        new_path = instance.get_absolute_url()
        # Update any existing redirects that are pointing to the old url
        for redirect in Redirect.objects.filter(new_path=old_path):
          redirect.new_path = new_path
          # If the updated redirect now points to itself, delete it
          # (i.e. slug = A -> slug = B -> slug = A again)
          if redirect.new_path == redirect.old_path:
            redirect.delete()
          else:
            redirect.save()
        # Now add the new redirect
        Redirect.objects.create(site=Site.objects.get_current(), old_path=old_path, new_path=new_path)
    except sender.DoesNotExist:
        pass

so now every time you edit the slug field in your model a redirect will be automatically created.