Blog

The Digital Agency for International Development

Code Kitchen: Overriding and keyword parameters

By Mark Skipper on 27 September 2010

I hinted, in the first Code Kitchen that there was a bit more to tell of the story of or recent Django refactoring.

Recall that we created a family of filter classes with the superclass doing all the work of returning what we need for our object tree-view, leaving the subclasses to redefine the filter_x() methods to define selection criteria for X, Y and Z objects.

Now, all the objects in our hierarchy have a Boolean deleted attribute, and we never want to display deleted objects. We could specify this easily using Django's filter() method by saying X.objects.filter(deleted=False), but we've chosen to do something slightly more subtle. Class Filter defines our default criteria like this:

class Filter(object):
    not_deleted = {'deleted':False} 
    ...

def filter_Xs(self, **criteria ): criteria.update(self.not_deleted) return X.objects.filter(**criteria)

Python functions take two kinds of parameters: positional parameters and keyword parameters. Passing a dictionary, preceded by two asterisks tells Python that the contents of that dictionary should be used as the keyword parameters for that function. Similarly specifying a formal parameter preceded by two asterisks tells Python that we want to be given all our keyword parameters in one dictionary instead of specifying them individually. This way our function always receives a dictionary of its keyword parameters; if no actual keyword parameters are specified in the function call, the dictionary will be empty.

Filter's other methods call filter_Xs() (also filter_Ys() and filter_Zs()) with no actual parameters. The method uses the dictionary's update() method to add the 'not deleted' criterion to the empty dictionary which then becomes the actual keyword parameter list for the call to Django's filter() method.

Filter's sub-classes override filter_Xs() (also filter_Ys() and filter_Zs()) to define more specific selection criteria. Though these may be arbitrarily complex, one simple solution is to add more selection criteria to be used by the filter method. Some of our tree filters look like this:

class CountryFilter(Filter):
   country = None

def __init__(self,country): self.country = country

def filter_Xs(self,**criteria): return super(CountryFilter,self).filter_Xs(country=self.country)

...

A CountryFilter is instantiated with a Country object to match on. Its filter methods use super to call Filter's implementations of filter_Xs(), etc., specifying their additional criteria with conventional keyword parameters. The superclass implementations of these functions work the same as I described above except now they are adding 'not deleted' to to the 'matches country' criterion to from the final query.

Filter's methods look slightly odd at firs sight. But we write them once and, in return, the implementations of many specific filter sub-classes become simple and uncluttered.