Posted on December 12, 2008 at 4:59 am

Playing with Django Querysets

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()

Tags:,

4 Responses to “Playing with Django Querysets”

  1. 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).

  2. 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.

  3. 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

  4. 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.

Leave a Reply