from django.conf import settings from django.contrib.sites.models import Site from django.contrib.redirects.models import Redirect from random import choice, seed from os import urandom SHORTURL_CHARS = getattr(settings, "SHORTURL_CHARS", "bcdfghjklmnpqrstvwxyz2346789") SHORTURL_CHAR_NO = getattr(settings, "SHORTURL_CHAR_NO", 5) SHORTURL_APPEND_SLASH = getattr(settings, "SHORTURL_APPEND_SLASH", True) class ShortURLException: pass class ShortURL(object): """ A mixin that sets up short url redirects for models that have a get_absolute_url method. Requires django.contrib.redirects to be installed to create redirects, and django.contrib.redirects.middleware.RedirectFallbackMiddleware to use them. """ def __init__(self, *args, **kwargs): """ Seeds randomiser """ seed(urandom(256)) super(ShortURL, self, *args, **kwargs) def get_short_url(self, *args, **kwargs): """ Finds the short url for the object's absolute url in the Redirects model objects. If it doesn't exist, generate a short url and create a new Redirect object. """ if not hasattr(self, 'get_absolute_url'): return None else: currenturl = self.get_absolute_url() site = Site.objects.get(id=settings.SITE_ID) redirects = Redirect.objects.filter(site=site, new_path=currenturl) for url in redirects: if len(url.old_path) <= SHORTURL_CHAR_NO + 1: #allow for leading slash shorturl = url.old_path break else: shorturl = None if not shorturl: # Check we've got at least a 9 in ten chance of not colliding or throw an exception if Redirect.objects.count() > (len(SHORTURL_CHARS) ** SHORTURL_CHAR_NO) / 10: raise ShortURLException while True: shorturl = '/'+''.join([choice(SHORTURL_CHARS) for char in range(SHORTURL_CHAR_NO)]) if not Redirect.objects.filter(site=site, old_path=shorturl): # save shorturl without trailing slash so redirect middleware will find both forms r = Redirect(site=site, old_path=shorturl, new_path=currenturl) r.save() break shorturl += '/' if SHORTURL_APPEND_SLASH else '' return shorturl