Sunday, June 2, 2013

Django change the trailing slash url convention to no trailing slash

URL Specs and search engines state that urls with and without trailing can affect your SEO rankings (and a user's ability to type in a link).

eg. example.com/page and example.com/page/

This affects some web frameworks, such as Django, as the regular expression for routing these URLs will be defined for only one of these cases. By convention Django will add a trailing slash, if a non trailing slash url is typed into the browser.

I prefer to not have a trailing slash as it is one less character to type in. I should note that some sites, like stackoverflow.com or twitter.com does not give a crap either way.

We can add this feature to Django by creating a custom middleware class that intercepts the request.path and rewrites it to the non trailing slash url.

What is middleware?

Middleware is a framework of hooks into Django’s request/response processing. It’s a light, low-level “plugin” system for globally altering Django’s input or output.

Or in layman's terms, it processes values/urls before it is sent to Django's views. You can find Django's middleware that redirects to the trailing slash url in:

django/middleware/common.py

Which was added to your project in
settings.py

MIDDLEWARE_CLASSES = (     'django.middleware.common.CommonMiddleware',


How to get Django to redirect trailing slashes to the non trailing slash url if it exists.

Step 1:

Add this line in settings.py

APPEND_SLASH = False

Step 2:

Create these paths and files

./common/
./common/__init__.py
./common/redirect.py

Step 3: Add the class to the MIDDLEWARE definition

MIDDLEWARE_CLASSES = (
    'common.redirect.RedirectTrailingSlashMiddleware',
    'django.middleware.common.CommonMiddleware',

# It must be the first class as we want to catch it before django does.

Step 4: Add this code to redirect.py

For /admin urls, I have kept the trailing slash as I don't want to break Django's admin methods

from django.conf import settings
from django.core import urlresolvers
from django import http

'''
Based on django/middleware/common.py

Django convention is to add trailing slashes to most urls
This method does the opposite and redirects trailing slashes to the
no trailing slash url if it exists
'''
class RedirectTrailingSlashMiddleware(object):

    def process_request(self, request):
        if settings.APPEND_SLASH:
           return 
        
        if '/admin' in request.path:
            settings.APPEND_SLASH = True         
            return

        new_url = old_url = request.path

        if (old_url.endswith('/')):
            urlconf = getattr(request, 'urlconf', None)
            if (not urlresolvers.is_valid_path(request.path_info, urlconf) and
                urlresolvers.is_valid_path(request.path_info[:-1], urlconf)):
                new_url = new_url[:-1]
                if settings.DEBUG and request.method == 'POST':
                    raise RuntimeError((""
                    "You called this URL via POST, but the URL ends "
                    "in a slash and you have APPEND_SLASH set. Django can't "
                    "redirect to the non-slash URL while maintaining POST data. "
                    "Change your form to point to %s (note no trailing "
                    "slash), or set APPEND_SLASH=True in your Django "
                    "settings.") % (new_url))

        if new_url == old_url:
            # No redirects required.
            return

        return http.HttpResponsePermanentRedirect(new_url)