diff options
| author | Ben Firshman | 2014-09-22 02:18:09 +0200 |
|---|---|---|
| committer | Ben Firshman | 2014-09-22 02:18:09 +0200 |
| commit | 47dcc785c2d6bcbbcf0cbaabdd5d8c825bc97e9c (patch) | |
| tree | f184d0ff8da4d2644ae4faed3bddcb9e5790b596 | |
| parent | 967f4ddab7bb68bc1cbd10a30658c65fd67c9acb (diff) | |
| parent | ab5312e7c035e76521140382b47113bcf50acbcf (diff) | |
| download | django-shorturls-47dcc785c2d6bcbbcf0cbaabdd5d8c825bc97e9c.tar.bz2 | |
Merge pull request #8 from bfirsh/crockford-base32
Add Crockford's base32 and performance improvements
| -rw-r--r-- | README.md | 13 | ||||
| -rw-r--r-- | src/shorturls/__init__.py | 19 | ||||
| -rw-r--r-- | src/shorturls/baseconv.py | 86 | ||||
| -rw-r--r-- | src/shorturls/templatetags/shorturl.py | 4 | ||||
| -rw-r--r-- | src/shorturls/tests/test_baseconv.py | 17 | ||||
| -rw-r--r-- | src/shorturls/tests/test_views.py | 1 | ||||
| -rw-r--r-- | src/shorturls/views.py | 25 |
7 files changed, 121 insertions, 44 deletions
@@ -87,3 +87,16 @@ differ. If not defined, the redirect view will try to guess the proper domain by consulting the ``django.contrib.sites`` framework, if installed, or the requested domain, if not. + +#### ``SHORTURLS_DEFAULT_CONVERTER`` + +The converter that is used to translate between short URLs and model IDs. +Defaults to the built in base 62 conversion. + +Available converters: + + - `shorturls.baseconv.base62` Base 62 encoding. + - `shorturls.baseconv.base32` [Douglas Crockford's base 32.](http://www.crockford.com/wrmg/base32.html) + - `shorturls.baseconv.hexconv` Hex encoding. + - `shorturls.baseconv.bin` Binary encoding, because why not. + diff --git a/src/shorturls/__init__.py b/src/shorturls/__init__.py index e69de29..528e838 100644 --- a/src/shorturls/__init__.py +++ b/src/shorturls/__init__.py @@ -0,0 +1,19 @@ +from django.conf import settings +from django.core.exceptions import ImproperlyConfigured +from django.utils.importlib import import_module +from shorturls import baseconv + +default_converter = baseconv.base62 + +if hasattr(settings, 'SHORTURLS_DEFAULT_CONVERTER'): + mod_name, conv_name = settings.SHORTURLS_DEFAULT_CONVERTER.rsplit('.', 1) + try: + mod = import_module(mod_name) + except ImportError, e: + raise ImproperlyConfigured('Could not load converter specified by SHORTURLS_DEFAULT_CONVERTER. Error was: %s' % e) + try: + default_converter = getattr(mod, conv_name) + except AttributeError: + raise ImproperlyConfigured('Could not load converter specified by SHORTURLS_DEFAULT_CONVERTER. %s is not in %s.' % (conv_name, mod)) + +
\ No newline at end of file diff --git a/src/shorturls/baseconv.py b/src/shorturls/baseconv.py index 1e22ed1..1b4fd15 100644 --- a/src/shorturls/baseconv.py +++ b/src/shorturls/baseconv.py @@ -13,46 +13,74 @@ Sample usage: """ class BaseConverter(object): - decimal_digits = "0123456789" + decode_mapping = {} def __init__(self, digits): self.digits = digits + self.length = len(digits) def from_decimal(self, i): - return self.convert(i, self.decimal_digits, self.digits) + if i < 0: + i, neg = -i, 1 + else: + neg = 0 + enc = '' + while i >= self.length: + i, mod = divmod(i, self.length) + enc = self.digits[mod] + enc + enc = self.digits[i] + enc + if neg: + enc = '-' + enc + return enc def to_decimal(self, s): - return int(self.convert(s, self.digits, self.decimal_digits)) - - def convert(number, fromdigits, todigits): - # Based on http://code.activestate.com/recipes/111286/ - if str(number)[0] == '-': - number = str(number)[1:] - neg = 1 + if self.decode_mapping: + new = '' + for digit in s: + if digit in self.decode_mapping: + new += self.decode_mapping[digit] + else: + new += digit + s = new + if str(s)[0] == '-': + s, neg = str(s)[1:], 1 else: neg = 0 - - # make an integer out of the number - x = 0 - for digit in str(number): - x = x * len(fromdigits) + fromdigits.index(digit) + decoded = 0 + multi = 1 + while len(s) > 0: + decoded += multi * self.digits.index(s[-1:]) + multi = multi * self.length + s = s[:-1] + if neg: + decoded = -decoded + return decoded - # create the result in base 'len(todigits)' - if x == 0: - res = todigits[0] - else: - res = "" - while x > 0: - digit = x % len(todigits) - res = todigits[digit] + res - x = int(x / len(todigits)) - if neg: - res = '-' + res - return res - convert = staticmethod(convert) - bin = BaseConverter('01') hexconv = BaseConverter('0123456789ABCDEF') base62 = BaseConverter( 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz' -)
\ No newline at end of file +) + +class Base32Converter(BaseConverter): + """ + http://www.crockford.com/wrmg/base32.html + """ + decode_mapping = { + 'o': '0', + 'i': '1', + 'l': '1', + } + + def __init__(self): + super(Base32Converter, self).__init__('0123456789abcdefghjkmnpqrstvwxyz') + + def to_decimal(self, s): + return super(Base32Converter, self).to_decimal(s.lower()) + +base32 = Base32Converter() + +if __name__ == '__main__': + import doctest + doctest.testmod() + diff --git a/src/shorturls/templatetags/shorturl.py b/src/shorturls/templatetags/shorturl.py index e1781cc..4c98d87 100644 --- a/src/shorturls/templatetags/shorturl.py +++ b/src/shorturls/templatetags/shorturl.py @@ -3,7 +3,7 @@ from django import template from django.conf import settings from django.core import urlresolvers from django.utils.safestring import mark_safe -from shorturls.baseconv import base62 +from shorturls import default_converter as converter class ShortURL(template.Node): @classmethod @@ -27,7 +27,7 @@ class ShortURL(template.Node): except (AttributeError, KeyError): return '' - tinyid = base62.from_decimal(obj.pk) + tinyid = converter.from_decimal(obj.pk) if hasattr(settings, 'SHORT_BASE_URL') and settings.SHORT_BASE_URL: return urlparse.urljoin(settings.SHORT_BASE_URL, prefix+tinyid) diff --git a/src/shorturls/tests/test_baseconv.py b/src/shorturls/tests/test_baseconv.py index 5e203b7..24f944f 100644 --- a/src/shorturls/tests/test_baseconv.py +++ b/src/shorturls/tests/test_baseconv.py @@ -16,4 +16,19 @@ class BaseConvTests(unittest.TestCase): self._test_converter(baseconv.hexconv) def test_base62(self): - self._test_converter(baseconv.base62)
\ No newline at end of file + self._test_converter(baseconv.base62) + + def test_base32(self): + self._test_converter(baseconv.base32) + + def test_base32_mappings(self): + converter = baseconv.base32 + self.assertEqual( + converter.from_decimal(converter.to_decimal('Fo9')), + 'f09' + ) + self.assertEqual( + converter.from_decimal(converter.to_decimal('43FG5i2L')), + '43fg5121' + ) + diff --git a/src/shorturls/tests/test_views.py b/src/shorturls/tests/test_views.py index a2813f2..e2c7b63 100644 --- a/src/shorturls/tests/test_views.py +++ b/src/shorturls/tests/test_views.py @@ -1,5 +1,4 @@ from django.conf import settings -from django.http import Http404 from django.test import TestCase from shorturls.baseconv import base62 diff --git a/src/shorturls/views.py b/src/shorturls/views.py index d811fde..2d0a47b 100644 --- a/src/shorturls/views.py +++ b/src/shorturls/views.py @@ -4,9 +4,9 @@ from django.contrib.sites.models import Site, RequestSite from django.db import models from django.http import HttpResponsePermanentRedirect, Http404 from django.shortcuts import get_object_or_404 -from shorturls.baseconv import base62 +from shorturls import default_converter -def redirect(request, prefix, tiny): +def redirect(request, prefix, tiny, converter=default_converter): """ Redirect to a given object from a short URL. """ @@ -16,15 +16,18 @@ def redirect(request, prefix, tiny): # any of that stuff goes wrong. try: app_label, model_name = settings.SHORTEN_MODELS[prefix].split('.') - try: - model = models.get_model(app_label, model_name) - except LookupError: - raise Http404('Bad model.') - if not model: - raise ValueError - id = base62.to_decimal(tiny) - except (AttributeError, ValueError, KeyError): - raise Http404('Bad prefix, model, SHORTEN_MODELS, or encoded ID.') + except KeyError: + raise Http404('Bad prefix.') + try: + model = models.get_model(app_label, model_name) + except LookupError: + model = False + if not model: + raise Http404('Bad model specified in SHORTEN_MODELS.') + try: + id = converter.to_decimal(tiny) + except ValueError: + raise Http404('Bad encoded ID.') # Try to look up the object. If it's not a valid object, or if it doesn't # have an absolute url, bail again. |
