Commit cf37e462 authored by Russell Keith-Magee's avatar Russell Keith-Magee
Browse files

Fixed #7210 -- Added F() expressions to query language. See the documentation for details on usage.

Many thanks to:
    * Nicolas Lara, who worked on this feature during the 2008 Google Summer of Code.
    * Alex Gaynor for his help debugging and fixing a number of issues.
    * Malcolm Tredinnick for his invaluable review notes.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@9792 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 08dd4176
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.db import connection
from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
from django.db.models.query import Q
from django.db.models.expressions import F
from django.db.models.manager import Manager
from django.db.models.base import Model
from django.db.models.aggregates import *
+110 −0
Original line number Diff line number Diff line
from copy import deepcopy
from datetime import datetime

from django.utils import tree

class ExpressionNode(tree.Node):
    """
    Base class for all query expressions.
    """
    # Arithmetic connectors
    ADD = '+'
    SUB = '-'
    MUL = '*'
    DIV = '/'
    MOD = '%%'  # This is a quoted % operator - it is quoted
                # because it can be used in strings that also
                # have parameter substitution.

    # Bitwise operators
    AND = '&'
    OR = '|'

    def __init__(self, children=None, connector=None, negated=False):
        if children is not None and len(children) > 1 and connector is None:
            raise TypeError('You have to specify a connector.')
        super(ExpressionNode, self).__init__(children, connector, negated)

    def _combine(self, other, connector, reversed, node=None):
        if reversed:
            obj = ExpressionNode([other], connector)
            obj.add(node or self, connector)
        else:
            obj = node or ExpressionNode([self], connector)
            obj.add(other, connector)
        return obj

    ###################
    # VISITOR METHODS #
    ###################

    def prepare(self, evaluator, query, allow_joins):
        return evaluator.prepare_node(self, query, allow_joins)

    def evaluate(self, evaluator, qn):
        return evaluator.evaluate_node(self, qn)

    #############
    # OPERATORS #
    #############

    def __add__(self, other):
        return self._combine(other, self.ADD, False)

    def __sub__(self, other):
        return self._combine(other, self.SUB, False)

    def __mul__(self, other):
        return self._combine(other, self.MUL, False)

    def __div__(self, other):
        return self._combine(other, self.DIV, False)

    def __mod__(self, other):
        return self._combine(other, self.MOD, False)

    def __and__(self, other):
        return self._combine(other, self.AND, False)

    def __or__(self, other):
        return self._combine(other, self.OR, False)

    def __radd__(self, other):
        return self._combine(other, self.ADD, True)

    def __rsub__(self, other):
        return self._combine(other, self.SUB, True)

    def __rmul__(self, other):
        return self._combine(other, self.MUL, True)

    def __rdiv__(self, other):
        return self._combine(other, self.DIV, True)

    def __rmod__(self, other):
        return self._combine(other, self.MOD, True)

    def __rand__(self, other):
        return self._combine(other, self.AND, True)

    def __ror__(self, other):
        return self._combine(other, self.OR, True)

class F(ExpressionNode):
    """
    An expression representing the value of the given field.
    """
    def __init__(self, name):
        super(F, self).__init__(None, None, False)
        self.name = name

    def __deepcopy__(self, memodict):
        obj = super(F, self).__deepcopy__(memodict)
        obj.name = self.name
        return obj

    def prepare(self, evaluator, query, allow_joins):
        return evaluator.prepare_leaf(self, query, allow_joins)

    def evaluate(self, evaluator, qn):
        return evaluator.evaluate_leaf(self, qn)
+6 −1
Original line number Diff line number Diff line
@@ -194,8 +194,13 @@ class Field(object):
    def get_db_prep_lookup(self, lookup_type, value):
        "Returns field's value prepared for database lookup."
        if hasattr(value, 'as_sql'):
            # If the value has a relabel_aliases method, it will need to
            # be invoked before the final SQL is evaluated
            if hasattr(value, 'relabel_aliases'):
                return value
            sql, params = value.as_sql()
            return QueryWrapper(('(%s)' % sql), params)

        if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'):
            return [value]
        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
+4 −0
Original line number Diff line number Diff line
@@ -141,6 +141,10 @@ class RelatedField(object):
            return v

        if hasattr(value, 'as_sql'):
            # If the value has a relabel_aliases method, it will need to
            # be invoked before the final SQL is evaluated
            if hasattr(value, 'relabel_aliases'):
                return value
            sql, params = value.as_sql()
            return QueryWrapper(('(%s)' % sql), params)

+3 −0
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@ class QueryWrapper(object):
    def __init__(self, sql, params):
        self.data = sql, params

    def as_sql(self, qn=None):
        return self.data

class Q(tree.Node):
    """
    Encapsulates filters as objects that can then be combined logically (using
Loading