Bitwise operator queryset filter

 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
from copy import deepcopy

from django.db import models
from django.db.models.sql.expressions import SQLEvaluator
from django.utils import tree

class FExpression(object):
    '''
    FExpression is the simplest way to reach the code path we need
    and should not inherit from tree.Node or any subclass thereof.

    FExpression currently only supports the integer comparison.
    Partial string comparison and such do not work because of the
    way these are defined in connection.operators. For those you
    should use the regular filter methods django provides.
    '''
    def __init__(self, left_expression, lookup_type, right_expression):
        self.left_expression, self.lookup_type, self.right_expression = \
                left_expression, lookup_type, right_expression

    def as_sql(self, qn, connection):
        cast_sql = connection.ops.lookup_cast(self.lookup_type)
        field = models.Field()
        # expression is either a smart expression or a user parameter
        if hasattr(self.left_expression, 'as_sql'):
            left_sql, left_params = self.left_expression.as_sql(qn, connection)
            left_sql = cast_sql % left_sql
        else:
            left_sql, left_params = cast_sql, field.get_db_prep_lookup(self.lookup_type, self.left_expression, connection=connection)
        # right hand side is cast by the operator sql
        if hasattr(self.right_expression, 'as_sql'):
            right_sql, right_params = self.right_expression.as_sql(qn, connection)
        else:
            right_sql, right_params = '%s', field.get_db_prep_lookup(self.lookup_type, self.right_expression, connection=connection)

        format = '%%s %s' % connection.operators[self.lookup_type]
        return format % (left_sql, right_sql), list(left_params) + list(right_params)


class FNode(tree.Node):
    '''
    FNode inherits tree.Node to pass tests for the code path we need
    This allows FNode to work without using complex_filter
    '''
    def __init__(self, left_expression, lookup_type, right_expression):
        if not lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
            raise ValueError('Invalid operator, operator %r is not supported.' % lookup_type)
        super(FNode, self).__init__()
        self.left_expression, self.lookup_type, self.right_expression = \
                left_expression, lookup_type, right_expression

    def __deepcopy__(self, memodict):
        obj = super(FNode, self).__deepcopy__(memodict)
        obj.left_expression = deepcopy(self.left_expression, memodict)
        obj.right_expression = deepcopy(self.right_expression, memodict)
        obj.lookup_type = self.lookup_type
        return obj

    def add_to_query(self, query, aliases):
        # evaluate if it is a query expression
        if hasattr(self.left_expression, 'evaluate'):
            self.left_expression = SQLEvaluator(self.left_expression, query)
        if hasattr(self.right_expression, 'evaluate'):
            self.right_expression = SQLEvaluator(self.right_expression, query)
        query.where.add(FExpression(self.left_expression, self.lookup_type, self.right_expression), self.connector)


def FQ(*args, **kwargs):
    '''
    Wrap FNode in a Q object for it's logical operators
    '''
    return models.Q(FNode(*args, **kwargs))

More like this

  1. JSONField by Jasber 4 years, 11 months ago
  2. Generic model filter from request GET data by genbit 2 years, 8 months ago
  3. SuperChoices by willhardy 5 years, 5 months ago
  4. caching parsed templates by forgems 6 years, 4 months ago
  5. Hidden Forms by insin 6 years, 9 months ago

Comments

mikesperanza (on August 16, 2013):

Very nice and useful for anyone stuck on pre-1.5

However, I found you also need to implement the relabel_aliases on the FExpression, otherwise, in subqueries, the aliases will not be used and the query will not run correctly. Something like this:

def relabel_aliases(self, change_map):
    if hasattr(self.left_expression, "relabel_aliases"):
        self.left_expression.relabel_aliases(change_map)
    if hasattr(self.right_expression, "relabel_aliases"):
        self.right_expression.relabel_aliases(change_map)

#

(Forgotten your password?)