import re import operator def q_to_function(q): """ Takes a Django Q object (which represents a query) and converts it to a lambda expression that takes an object and returns True or False based on whether the object passes the query. Limitations: - Doesn't support the 'search' field lookup. - Aggregations are not supported. - the regex field lookup uses the syntax of python's re module, not whatever DB backend you are using. - Doesn't do any of Django's type magic - ie 1230 != "1230", unlike in Django. Example usage: >>> class Article(Model): ... title = CharField(max_length=60) ... body = TextField() ... created = DateTimeField(auto_now_add=True) ... num_words = IntField() >>> article1 = Article(title="A simple sample", ... body="It's not a real article, you know.", ... created=datetime.datetime(2012, 7, 1, 12, 30), ... num_words=7) >>> article2 = Article(title="Another article", ... body="This one is shorter.", ... created=datetime.datetime(2012, 5, 15, 12, 00), ... num_words=4) >>> query = Q(title__icontains="sample", num_words__lt=10) >>> query_as_function = q_to_function(query) >>> query_as_function(article1) True >>> query_as_function(article2) False >>> complicated_query = (Q(title__istartswith("a")) | Q(title__startswith="b")) & Q(num_words__gt=3) & Q(created_at__gt=datetime.datetime(2012, 6, 1)) >>> complicated_query_as_function = q_to_function(complicated_query) >>> complicated_query_as_function(article1) True >>> complicated_query_as_function(article2) False """ field_lookup_map = { "exact": operator.eq, "iexact": lambda str1, str2: str1.lower() == str2.lower(), "contains": operator.contains, "icontains": lambda str1, str2: str2.lower() in str1.lower(), "startswith": lambda str1, str2: str1.startswith(str2), "istartswith": lambda str1, str2: str1.lower().startswith(str2.lower()), "endswith": lambda str1, str2: str1.endwith(str2), "iendswith": lambda str1, str2: str1.lower().endswith(str2.lower()), "in": lambda obj, iterator: obj in iterator, "gt": operator.gt, "gte": operator.ge, "lt": operator.lt, "lte": operator.le, "range": lambda val, given_range: val >= given_range[0] and val <= given_range[1], "year": lambda date, year: date.year == year, "month": lambda date, month: date.month == month, "day": lambda date, day: date.day == day, "week_day": lambda date, week_day: (date.isoweekday() + 1) % 7 == week_day, "isnull": lambda obj, boolean: (obj is None) == boolean, "regex": lambda string, pattern: re.match(pattern, string), } field_lookup_map if isinstance(q, tuple): field, target_value = q def apply_query(obj): """ Checks whether obj.value satisfies field. field is specified using django's syntax.""" split_fields = field.split("__") last_field = split_fields[-1] # Last field is the operator. If it isn't specified, then # exact matching is implied. if last_field in field_lookup_map: field_operator = field_lookup_map[last_field] split_fields = split_fields[:-1] else: field_operator = operator.eq # All remaning are related-field lookups. Pop these till we # get the field which we actually apply the operator to. split_fields.reverse() while len(split_fields) > 0: field_name = split_fields.pop() field_value = getattr(obj, field_name) try: return field_operator(field_value, target_value) except AttributeError: raise TypeError("Field '%s' does not support the '%s' lookup." % (field_name, last_field)) except TypeError: raise TypeError("Field '%s' does not support the '%s' lookup." % (field_name, last_field)) return apply_query else: children = q.children if q.connector == 'AND': return lambda obj: all(q_to_function(child)(obj) for child in children) elif q.connector == "OR": return lambda obj: any(q_to_function(child)(obj) for child in children)