Login

Convert Q object to function

Author:
spenczar
Posted:
July 30, 2012
Language:
Python
Version:
1.2
Score:
2 (after 2 ratings)

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

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

Please login first before commenting.