# Commits

committed f8987e5 Draft

• Participants
• Parent commits f87c5c0

# File shortener/baseconv.py

`-"""`
`-Convert numbers from base 10 integers to base X strings and back again.`
`-`
`-Original: http://www.djangosnippets.org/snippets/1431/`
`-`
`-Sample usage:`
`-`
`->>> base20 = BaseConverter('0123456789abcdefghij')`
`->>> base20.from_decimal(1234)`
`-'31e'`
`->>> base20.to_decimal('31e')`
`-1234`
`-"""`
`-`
`-class BaseConverter(object):`
`-    decimal_digits = "0123456789"`
`-`
`-    def __init__(self, digits):`
`-        self.digits = digits`
`-`
`-    def from_decimal(self, i):`
`-        return self.convert(i, self.decimal_digits, self.digits)`
`-`
`-    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`
`-        else:`
`-            neg = 0`
`-`
`-        # make an integer out of the number`
`-        x = 0`
`-        for digit in str(number):`
`-           x = x * len(fromdigits) + fromdigits.index(digit)`
`-`
`-        # 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'`
`-)`
`-`
`-`
`-if __name__ == '__main__':`
`-    nums = [-10 ** 10, 10 ** 10] + range(-100, 100)`
`-    for convertor in [bin, hexconv, base62]:`
`-        for i in nums:`
`-            assert i == bin.to_decimal(bin.from_decimal(i)), '%s failed' % i`

# File shortener/mixins.py

`+#!/usr/bin/env python`
`+# encoding: utf-8`
`+`
`+from django.db import models`
`+from django.contrib.auth.models import User`
`+`
`+from shortener.settings import LOGIN_REQUIRED`
`+`
`+`
`+class EnhancedLink(models.Model):`
`+`
`+    user = models.ForeignKey(User, blank=not LOGIN_REQUIRED, null= not LOGIN_REQUIRED)`
`+    created = models.DateTimeField(auto_now=True)`
`+`
`+    class Meta:`
`+        abstract = True`
`+`
`+    @classmethod`
`+    def create(cls, url, user):`
`+        instance = super(EnhancedLink, cls).create(url, commit=False)`
`+        instance.user = user`
`+        instance.save()`
`+        return instance`

# File shortener/models.py

` from django.core.validators import URLValidator`
` from django.core.exceptions import ValidationError`
` `
`-from shortener.settings import LINK_UNIQUENESS, HASH_SEED_LENGTH, SITE_BASE_URL, HASH_STRATEGY`
`-from shortener.baseconv import base62`
`+from shortener.settings import LINK_UNIQUENESS, HASH_SEED_LENGTH, SITE_BASE_URL, HASH_STRATEGY, LINK_MIXIN`
`+from shortener.utils.baseconv import base62`
`+from shortener.utils import get_basemodel_mixin`
` `
` log = logging.getLogger(__name__)`
` `
`-class Link(models.Model):`
`+class BaseLink(models.Model):`
`     _hash = models.CharField(max_length=8) # n-char unique random string`
`     url = models.CharField(max_length=2083) # http://www.boutell.com/newfaq/misc/urllength.html`
`     created = models.DateTimeField(auto_now=False, auto_now_add=True)`
` `
`+    class Meta:`
`+        abstract = True`
`+`
`     def __unicode__(self):`
`         return u'%s' % self.url`
` `
`     def get_short_link(self):`
`         return u'%s/%s' % (SITE_BASE_URL, self._hash)`
`-`
`+    """`
`     @classmethod`
`     def get_or_create(cls, url):`
`         try:`
`-            return Link.objects.get(url=url)`
`+            return cls.objects.get(url=url)`
`         except cls.DoesNotExist:`
`             return cls.create(url)`
`-`
`+    """`
`     @classmethod`
`     def create(cls, url, commit=True):`
`         log.debug("Link::create(url='%s')" % url)`
` `
`     @classmethod`
`     def hash_exists(cls, hash_):`
`-        return Link.objects.filter(_hash=hash_).exists()`
`+        return cls.objects.filter(_hash=hash_).exists()`
` `
`     @classmethod`
`     def link_exists(cls, url):`
`-        return Link.objects.filter(url=url).exists()`
`+        return cls.objects.filter(url=url).exists()`
` `
`     @classmethod`
`     def url_is_valid(cls, url):`
`             return True`
`         except ValidationError:`
`             return False`
`+`
`+`
`+if LINK_MIXIN:`
`+    Mixin = get_basemodel_mixin(LINK_MIXIN)`
`+    class Link(Mixin, BaseLink):`
`+        class Meta(Mixin.Meta):`
`+            abstract = False`
`+`
`+else:`
`+    class Link(BaseLink):`
`+        class Meta:`
`+            abstract = False`

# File shortener/settings.py

` `
` from django.conf import settings`
` `
`+LINK_MIXIN = getattr(settings, 'SHORTENER_LINK_MIXIN', None)`
`+LOGIN_REQUIRED = getattr(settings, 'SHORTENER_LOGIN_REQUIRED', False)`
`+`
` LINK_UNIQUENESS = getattr(settings, 'SHORTENER_LINK_UNIQUENESS', False)`
` `
` HASH_STRATEGY = getattr(settings, 'SHORTENER_HASH_STRATEGY', None)`

# File shortener/utils/__init__.py

`+#!/usr/bin/env python`
`+# encoding: utf-8`
`+`
`+from importer import load_class, get_basemodel_mixin`

# File shortener/utils/baseconv.py

`+"""`
`+Convert numbers from base 10 integers to base X strings and back again.`
`+`
`+Original: http://www.djangosnippets.org/snippets/1431/`
`+`
`+Sample usage:`
`+`
`+>>> base20 = BaseConverter('0123456789abcdefghij')`
`+>>> base20.from_decimal(1234)`
`+'31e'`
`+>>> base20.to_decimal('31e')`
`+1234`
`+"""`
`+`
`+class BaseConverter(object):`
`+    decimal_digits = "0123456789"`
`+`
`+    def __init__(self, digits):`
`+        self.digits = digits`
`+`
`+    def from_decimal(self, i):`
`+        return self.convert(i, self.decimal_digits, self.digits)`
`+`
`+    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`
`+        else:`
`+            neg = 0`
`+`
`+        # make an integer out of the number`
`+        x = 0`
`+        for digit in str(number):`
`+           x = x * len(fromdigits) + fromdigits.index(digit)`
`+`
`+        # 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'`
`+)`
`+`
`+`
`+if __name__ == '__main__':`
`+    nums = [-10 ** 10, 10 ** 10] + range(-100, 100)`
`+    for convertor in [bin, hexconv, base62]:`
`+        for i in nums:`
`+            assert i == bin.to_decimal(bin.from_decimal(i)), '%s failed' % i`

# File shortener/utils/importer.py

`+#!/usr/bin/env python`
`+# encoding: utf-8`
`+`
`+from django.db import models`
`+from django.core.exceptions import  ImproperlyConfigured`
`+`
`+def load_class(class_str):`
`+    if isinstance(class_str, basestring):`
`+        components = class_str.split('.')`
`+        mod = __import__('.'.join(components[:-1]), fromlist=components[-1:])`
`+        as_class = getattr(mod, components[-1:][0])`
`+        return as_class`
`+    else:`
`+        raise ValueError("Argument must be a string")`
`+`
`+def get_basemodel_mixin(basemodel_mixin):`
`+    if isinstance(basemodel_mixin, basestring):`
`+        as_model = load_class(basemodel_mixin)`
`+        return get_basemodel_mixin(as_model)`
`+`
`+    if issubclass(basemodel_mixin, models.Model) and basemodel_mixin._meta.abstract:`
`+        return basemodel_mixin`
`+    else:`
`+        raise ImproperlyConfigured("'%s' must be an abstract django model." % basemodel_mixin)`