Skip to content

Instantly share code, notes, and snippets.

@Sepero
Forked from gregplaysguitar/slash_middleware.py
Created March 26, 2012 09:17
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Sepero/2204099 to your computer and use it in GitHub Desktop.
Save Sepero/2204099 to your computer and use it in GitHub Desktop.
Remove slashs in django - like APPEND_SLASH but opposite.
""" Author contact: sepero 111 @ gmail . com """
from django import http
from django.utils.http import urlquote
from django.core import urlresolvers
class RemoveSlashMiddleware(object):
"""
This middleware works like django's built in APPEND_SLASH, but in reverse. Eg
It removes all ending slashes from a URL, and if that doesn't resolve, it will add one slash and try again.
Set APPEND_SLASH to False when using this middleware.
Forked from the original code at http://gregbrown.co.nz/code/append-or-remove-slash/
"""
def process_request(self, request):
# check if the url is valid
path = new_path = request.path_info
# Remove all trailing slashes from new_path.
while new_path.endswith('/'):
new_path = new_path[:-1]
urlconf = getattr(request, 'urlconf', None)
if not _is_valid_path(new_path, urlconf):
# If removing slashes made new_path invalid, add one slash and try again.
new_path = new_path + '/'
if path != new_path and _is_valid_path(new_path, urlconf):
return self.adjust_path(request, new_path)
elif path != new_path:
# If new_path is valid and not eq to path, send a permanent redirect.
return self.adjust_path(request, new_path)
def adjust_path(self, request, new_path):
"""
Redirect the clients browser to new_path, and tell it that all future requests to the desired URL should be sent to new_path. (This method looks like it may be able to be made more efficient, but I'm not familiar enough with request.path_info and other django variables to know how.)
"""
if request.get_host():
new_url = "%s://%s%s" % (request.is_secure() and 'https' or 'http', request.get_host(), urlquote(new_path))
else:
new_url = urlquote(new_path)
if request.GET:
new_url += '?' + request.META['QUERY_STRING']
return http.HttpResponseRedirect(new_url)
def _is_valid_path(path, urlconf=None):
"""
Returns True if the given path resolves against the default URL resolver,
False otherwise.
"""
try:
urlresolvers.resolve(path, urlconf)
return True
except urlresolvers.Resolver404:
return False
""" Author contact: sepero 111 @ gmail . com """
@Sepero
Copy link
Author

Sepero commented Mar 29, 2012

Minor efficiency increase

@jterrace
Copy link

I had to change line 18 to: while len(new_path) > 0 and new_path[-1] == '/': since it was broken for /

@Sepero
Copy link
Author

Sepero commented May 14, 2012 via email

@jterrace
Copy link

Um, I don't think that works? If new_path == '/', it wouldn't strip off the / like it's supposed to.

@Sepero
Copy link
Author

Sepero commented May 14, 2012 via email

@Sepero
Copy link
Author

Sepero commented May 14, 2012 via email

@jterrace
Copy link

Yes, exactly :) Actually, I bet you could change it to while new_path.endswith('/'):

@jterrace
Copy link

Also, maybe it could be more efficient if you return right away if the path doesn't end with a /?

@Sepero
Copy link
Author

Sepero commented May 15, 2012

Good call with the while new_path.endswith('/'):
Edited that.

As for returning right away when there is no slash at the end- the middleware still needs to verify the url resolves and if it doesn't, then add a slash.

Your input has been excellent on this. Very much appreciated.

@jterrace
Copy link

Oh, I see. The original was called "AppendOrRemoveSlashMiddleware", but you changed it to just RemoveSlashMiddleware but it still has the functionality of the original

@Sepero
Copy link
Author

Sepero commented May 15, 2012

Yeah, "AppendOrRemoveSlashMiddleware" tests if a url will resolve, and if the url doesn't resolve, then adds (or removes) the ending slash and tries again.

This "RemoveSlashMiddleware" is different, as it removes the ending slash(es) immediately, then if the url doesn't resolve, it adds a slash and tries again. So basically it works like the opposite of Django's APPEND_SLASH setting. (I prefer this way of handling url's because it's more easily readable by users, as well as being the method most commonly by other websites.)

@jterrace
Copy link

Ah, I see. So it still can add a slash though, it doesn't always only remove slashes. If URL http://example.com/foo came in and didn't resolve, it would try http://example.com/foo/ and if that resolved, it would redirect to it. I changed mine to just return right away if the URL doesn't end in a slash. I have no cases where I actually want to add a slash.

@Sepero
Copy link
Author

Sepero commented May 16, 2012

That is correct. I edited the explanatory comment block to try to clarify the how the middleware works. Any ideas on how can I make it more clear to say what this middleware does?

@jterrace
Copy link

Maybe call it RemoveOrAppendMiddleware instead of AppendOrRemoveMiddleware? :)

@Sepero
Copy link
Author

Sepero commented May 16, 2012

That's definitely a thought, but the predecessor might be better off named "ToggleSlash", as it just toggles slash on/off if the requested url doesn't work.

This middleware doesn't operate as the reverse of it's predecessor, since it doesn't toggle the slash. The predecessor was just a good code base to build from. This middleware is meant to operate as the reverse/opposite of APPEND_SLASH in django settings (like a REMOVE_SLASH setting). Hopefully that explains things. lol

@jterrace
Copy link

Yes, that makes sense. It's not exactly the opposite of APPEND_SLASH though, because APPEND_SLASH will never return a redirect to a URL that removes a slash.

@davidrenne
Copy link

It's easier to:

rewrite ^(.+)/+$ $1 permanent;

In nginx.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment