I use base36 for all most object ids in tagz. I previously used to manually convert all base36 values into integers before doing the lookups. Yesterday it dawned to me that I could subclass QuerySets and remove a lot of boilerplate code. The idea here is that I can do something like
Model.objects.get(pk_base36='2kk')
instead of something like
Model.objects.get(pk=_decode('2kk'))
The Queryset wrapper would internally decode the base36 string into an integer and then lookup by that id. Code follows.
import string
from django.db import models
DIGITS = string.digits + string.ascii_lowercase
BASE = len(DIGITS)
def _encode(n) :
l = []
base = len(DIGITS)
while True :
n, rem = divmod(n, BASE)
l.append(DIGITS[rem])
if n < 1 :
break
l.reverse()
return ''.join(l)
def _decode(s) :
num = 0
base = len(DIGITS)
for c in s.lower() :
pos = DIGITS.index(c)
num = (num * base) + pos
class Base36KeyedQuerySet(QuerySet) :
def _handle_pk_base36(self, kwargs) :
if 'pk_base36' in kwargs :
pk_base36 = kwargs['pk_base36']
assert ('id' not in kwargs and 'pk' not in kwargs)
kwargs['pk'] = _decode(pk_base36)
del kwargs['pk_base36']
return kwargs
def get(self, *args, **kwargs) :
kwargs = self._handle_pk_base36(kwargs)
return super(self.__class__,self).get(*args, **kwargs)
def filter(self, *args, **kwargs) :
kwargs = self._handle_pk_base36(kwargs)
return super(self.__class__,self).filter(*args, **kwargs)
def exclude(self, *args, **kwargs) :
kwargs = self._handle_pk_base36(kwargs)
return super(self.__class__,self).exclude(*args, **kwargs)
def get_or_create(self, *args, **kwargs) :
kwargs = self._handle_pk_base36(kwargs)
return super(self.__class__,self).get_or_create(*args, **kwargs)
class Base36KeyedManager(models.Manager) :
def get_query_set(self) :
return Base36KeyedQuerySet(self.model)
class Base36KeyedModel(models.Model) :
def get_base36_id(self) :
return _encode(self.id)
class Meta :
abstract = True
class ExampleModel(Base36KeyedModel) :
field1 = models.CharField(max_length=255)
field2 = models.CharField(max_length=255)
objects = Base36KeyedManager()
James Bennett on December 12th, 2008 at 7:12 AM says:
You probably want to have a look at the base36_to_int() and int_to_base36() functions in django.utils.http (which are there because a couple things in Django need to do base36 conversions).
admin on December 12th, 2008 at 8:10 AM says:
Thanks for the tip James, I should have searched (ack’d) the django source before writing the encode and decode functions.
Gurminder on February 26th, 2009 at 10:55 AM says:
Hi Jeethu,
Its interesting to see your posts about tagz.in. Apparently I am also working on a similar project.
But there is one simple thing I am not able to figure out. If you can help.
When user enters tags in search box example – “css html tutorial … ”
how do you handle it?
I have a search form on my Index page. when user submits the form, I can’t figure out how to pass the ‘search string’ using HttpResponseRedirect, which redirects to a new view with a completely different template.
I am using generic views.
Thanks
Guri
Jeethu on March 6th, 2009 at 6:40 PM says:
Well, its just a matter of doing inner joins in sql. Its a bit of a pain to do it with the Django ORM, but its not too hard to do.