This is a function to take a Q object and construct a function which returns a boolean. This lets you use the exact same filter syntax that Django's managers use and apply it inside list comprehensions, or to non-persistent objects, or to objects of different types with the same attribute names.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 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)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 9 months ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 9 months, 1 week ago
- Serializer factory with Django Rest Framework by julio 1 year, 3 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 4 months ago
- Help text hyperlinks by sa2812 1 year, 5 months ago
Comments
Please login first before commenting.