Runtime Modification of Class Attributes in Python

Man, I love setattr. Today I had the pleasure on working on something really cool. A friend of mine recently wrote this amazing piece of boiler plate code that is very modular for a Django project. The intention was to create a drop in search form which would allow us to write queries with ease.

He basically has two data structures defined within the form and a super class that looks for those structures and creates queries from them. So if say you have something like this:

class MySearchForm(SearchFormMixin, ModelForm):  
    class Meta:
        model = ModelClass
        fields = ('field1', 'field2', 'field3',)
        field_lookup = (
            ('field1', 'field1__related_field'),
            ('field2', 'field2__related_field'),)

    def __init__(self, *args, **kwargs):
        super(MySearchForm, self).__init__(*args, **kwargs)

And the SearchFormMixin implements the field lookup by creating Q objects from field_lookup tuple and kwargs from fields. Then essentially it boils down to doing this

queryset.filter(**kwargs)  
queryset.filter(q_object_list)  

However, here's the kicker. Say, if you want to implement a Global search, i.e. a search on EVERY field on the form, you would invariably choose Q objects.

queryset.filter(Q(first_name__icontains='foo') | Q(last_name__contains='foo') | Q(date__gte='foo'))  

You'll notice that this will throw an exception since date is expecting a DateTime object. Similarly an IntegerField is always expecting a int() base 10 object when searching. So how do you implement this programmatically when your form search is hardwired?

Well, that's when setattr comes in. It allows you to set objects on class instances and classes in general and use them. So if I had to replace the methods in SearchFormMixin, all I had to really do is, this

setattr(MySearchForm, 'filter', MyNewMixin().filter)  
setattr(MySearchForm, 'search', MyNewMixin().search)  

And in MyNewMixin I just redefine the nature of the query lookup.

class MyNewMixin(object):  
    def filter(self):
        """
        Code to search objects independently
        """
        ...

    def search(self):
        """
         Code to trigger the filter
        """
        ...

At this point the instance of class MySearchForm is going to trigger MyNewMixin().search() instead of SearchFormMixin().search().

Pretty smart method I'd say.