Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #7210 -- Added F() expressions to query language. See the docum…

…entation 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
  • Loading branch information...
commit cf37e4624a967f936ecbb5a4eefc9d38ed9d7892 1 parent 08dd417
Russell Keith-Magee authored January 29, 2009
1  django/db/models/__init__.py
@@ -3,6 +3,7 @@
3 3
 from django.db import connection
4 4
 from django.db.models.loading import get_apps, get_app, get_models, get_model, register_models
5 5
 from django.db.models.query import Q
  6
+from django.db.models.expressions import F
6 7
 from django.db.models.manager import Manager
7 8
 from django.db.models.base import Model
8 9
 from django.db.models.aggregates import *
110  django/db/models/expressions.py
... ...
@@ -0,0 +1,110 @@
  1
+from copy import deepcopy
  2
+from datetime import datetime
  3
+
  4
+from django.utils import tree
  5
+
  6
+class ExpressionNode(tree.Node):
  7
+    """
  8
+    Base class for all query expressions.
  9
+    """
  10
+    # Arithmetic connectors
  11
+    ADD = '+'
  12
+    SUB = '-'
  13
+    MUL = '*'
  14
+    DIV = '/'
  15
+    MOD = '%%'  # This is a quoted % operator - it is quoted
  16
+                # because it can be used in strings that also
  17
+                # have parameter substitution.
  18
+
  19
+    # Bitwise operators
  20
+    AND = '&'
  21
+    OR = '|'
  22
+
  23
+    def __init__(self, children=None, connector=None, negated=False):
  24
+        if children is not None and len(children) > 1 and connector is None:
  25
+            raise TypeError('You have to specify a connector.')
  26
+        super(ExpressionNode, self).__init__(children, connector, negated)
  27
+
  28
+    def _combine(self, other, connector, reversed, node=None):
  29
+        if reversed:
  30
+            obj = ExpressionNode([other], connector)
  31
+            obj.add(node or self, connector)
  32
+        else:
  33
+            obj = node or ExpressionNode([self], connector)
  34
+            obj.add(other, connector)
  35
+        return obj
  36
+
  37
+    ###################
  38
+    # VISITOR METHODS #
  39
+    ###################
  40
+
  41
+    def prepare(self, evaluator, query, allow_joins):
  42
+        return evaluator.prepare_node(self, query, allow_joins)
  43
+
  44
+    def evaluate(self, evaluator, qn):
  45
+        return evaluator.evaluate_node(self, qn)
  46
+
  47
+    #############
  48
+    # OPERATORS #
  49
+    #############
  50
+
  51
+    def __add__(self, other):
  52
+        return self._combine(other, self.ADD, False)
  53
+
  54
+    def __sub__(self, other):
  55
+        return self._combine(other, self.SUB, False)
  56
+
  57
+    def __mul__(self, other):
  58
+        return self._combine(other, self.MUL, False)
  59
+
  60
+    def __div__(self, other):
  61
+        return self._combine(other, self.DIV, False)
  62
+
  63
+    def __mod__(self, other):
  64
+        return self._combine(other, self.MOD, False)
  65
+
  66
+    def __and__(self, other):
  67
+        return self._combine(other, self.AND, False)
  68
+
  69
+    def __or__(self, other):
  70
+        return self._combine(other, self.OR, False)
  71
+
  72
+    def __radd__(self, other):
  73
+        return self._combine(other, self.ADD, True)
  74
+
  75
+    def __rsub__(self, other):
  76
+        return self._combine(other, self.SUB, True)
  77
+
  78
+    def __rmul__(self, other):
  79
+        return self._combine(other, self.MUL, True)
  80
+
  81
+    def __rdiv__(self, other):
  82
+        return self._combine(other, self.DIV, True)
  83
+
  84
+    def __rmod__(self, other):
  85
+        return self._combine(other, self.MOD, True)
  86
+
  87
+    def __rand__(self, other):
  88
+        return self._combine(other, self.AND, True)
  89
+
  90
+    def __ror__(self, other):
  91
+        return self._combine(other, self.OR, True)
  92
+
  93
+class F(ExpressionNode):
  94
+    """
  95
+    An expression representing the value of the given field.
  96
+    """
  97
+    def __init__(self, name):
  98
+        super(F, self).__init__(None, None, False)
  99
+        self.name = name
  100
+
  101
+    def __deepcopy__(self, memodict):
  102
+        obj = super(F, self).__deepcopy__(memodict)
  103
+        obj.name = self.name
  104
+        return obj
  105
+
  106
+    def prepare(self, evaluator, query, allow_joins):
  107
+        return evaluator.prepare_leaf(self, query, allow_joins)
  108
+
  109
+    def evaluate(self, evaluator, qn):
  110
+        return evaluator.evaluate_leaf(self, qn)
7  django/db/models/fields/__init__.py
@@ -194,8 +194,13 @@ def get_db_prep_save(self, value):
194 194
     def get_db_prep_lookup(self, lookup_type, value):
195 195
         "Returns field's value prepared for database lookup."
196 196
         if hasattr(value, 'as_sql'):
  197
+            # If the value has a relabel_aliases method, it will need to
  198
+            # be invoked before the final SQL is evaluated
  199
+            if hasattr(value, 'relabel_aliases'):
  200
+                return value
197 201
             sql, params = value.as_sql()
198 202
             return QueryWrapper(('(%s)' % sql), params)
  203
+
199 204
         if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'):
200 205
             return [value]
201 206
         elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
@@ -309,7 +314,7 @@ def formfield(self, form_class=forms.CharField, **kwargs):
309 314
             if callable(self.default):
310 315
                 defaults['show_hidden_initial'] = True
311 316
         if self.choices:
312  
-            # Fields with choices get special treatment. 
  317
+            # Fields with choices get special treatment.
313 318
             include_blank = self.blank or not (self.has_default() or 'initial' in kwargs)
314 319
             defaults['choices'] = self.get_choices(include_blank=include_blank)
315 320
             defaults['coerce'] = self.to_python
4  django/db/models/fields/related.py
@@ -141,6 +141,10 @@ def pk_trace(value):
3  django/db/models/query_utils.py
@@ -17,6 +17,9 @@ class QueryWrapper(object):
17 17
     def __init__(self, sql, params):
18 18
         self.data = sql, params
19 19
 
  20
+    def as_sql(self, qn=None):
  21
+        return self.data
  22
+
20 23
 class Q(tree.Node):
21 24
     """
22 25
     Encapsulates filters as objects that can then be combined logically (using
92  django/db/models/sql/expressions.py
... ...
@@ -0,0 +1,92 @@
  1
+from django.core.exceptions import FieldError
  2
+from django.db import connection
  3
+from django.db.models.fields import FieldDoesNotExist
  4
+from django.db.models.sql.constants import LOOKUP_SEP
  5
+
  6
+class SQLEvaluator(object):
  7
+    def __init__(self, expression, query, allow_joins=True):
  8
+        self.expression = expression
  9
+        self.opts = query.get_meta()
  10
+        self.cols = {}
  11
+
  12
+        self.contains_aggregate = False
  13
+        self.expression.prepare(self, query, allow_joins)
  14
+
  15
+    def as_sql(self, qn=None):
  16
+        return self.expression.evaluate(self, qn)
  17
+
  18
+    def relabel_aliases(self, change_map):
  19
+        for node, col in self.cols.items():
  20
+            self.cols[node] = (change_map.get(col[0], col[0]), col[1])
  21
+
  22
+    #####################################################
  23
+    # Vistor methods for initial expression preparation #
  24
+    #####################################################
  25
+
  26
+    def prepare_node(self, node, query, allow_joins):
  27
+        for child in node.children:
  28
+            if hasattr(child, 'prepare'):
  29
+                child.prepare(self, query, allow_joins)
  30
+
  31
+    def prepare_leaf(self, node, query, allow_joins):
  32
+        if not allow_joins and LOOKUP_SEP in node.name:
  33
+            raise FieldError("Joined field references are not permitted in this query")
  34
+
  35
+        field_list = node.name.split(LOOKUP_SEP)
  36
+        if (len(field_list) == 1 and
  37
+            node.name in query.aggregate_select.keys()):
  38
+            self.contains_aggregate = True
  39
+            self.cols[node] = query.aggregate_select[node.name]
  40
+        else:
  41
+            try:
  42
+                field, source, opts, join_list, last, _ = query.setup_joins(
  43
+                    field_list, query.get_meta(),
  44
+                    query.get_initial_alias(), False)
  45
+                _, _, col, _, join_list = query.trim_joins(source, join_list, last, False)
  46
+
  47
+                self.cols[node] = (join_list[-1], col)
  48
+            except FieldDoesNotExist:
  49
+                raise FieldError("Cannot resolve keyword %r into field. "
  50
+                                 "Choices are: %s" % (self.name,
  51
+                                                      [f.name for f in self.opts.fields]))
  52
+
  53
+    ##################################################
  54
+    # Vistor methods for final expression evaluation #
  55
+    ##################################################
  56
+
  57
+    def evaluate_node(self, node, qn):
  58
+        if not qn:
  59
+            qn = connection.ops.quote_name
  60
+
  61
+        expressions = []
  62
+        expression_params = []
  63
+        for child in node.children:
  64
+            if hasattr(child, 'evaluate'):
  65
+                sql, params = child.evaluate(self, qn)
  66
+            else:
  67
+                try:
  68
+                    sql, params = qn(child), ()
  69
+                except:
  70
+                    sql, params = str(child), ()
  71
+
  72
+            if hasattr(child, 'children') > 1:
  73
+                format = '(%s)'
  74
+            else:
  75
+                format = '%s'
  76
+
  77
+            if sql:
  78
+                expressions.append(format % sql)
  79
+                expression_params.extend(params)
  80
+        conn = ' %s ' % node.connector
  81
+
  82
+        return conn.join(expressions), expression_params
  83
+
  84
+    def evaluate_leaf(self, node, qn):
  85
+        if not qn:
  86
+            qn = connection.ops.quote_name
  87
+
  88
+        col = self.cols[node]
  89
+        if hasattr(col, 'as_sql'):
  90
+            return col.as_sql(qn), ()
  91
+        else:
  92
+            return '%s.%s' % (qn(col[0]), qn(col[1])), ()
18  django/db/models/sql/query.py
@@ -18,6 +18,7 @@
18 18
 from django.db.models.fields import FieldDoesNotExist
19 19
 from django.db.models.query_utils import select_related_descend
20 20
 from django.db.models.sql import aggregates as base_aggregates_module
  21
+from django.db.models.sql.expressions import SQLEvaluator
21 22
 from django.db.models.sql.where import WhereNode, Constraint, EverythingNode, AND, OR
22 23
 from django.core.exceptions import FieldError
23 24
 from datastructures import EmptyResultSet, Empty, MultiJoin
@@ -1271,6 +1272,10 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
1271 1272
         else:
1272 1273
             lookup_type = parts.pop()
1273 1274
 
  1275
+        # By default, this is a WHERE clause. If an aggregate is referenced
  1276
+        # in the value, the filter will be promoted to a HAVING
  1277
+        having_clause = False
  1278
+
1274 1279
         # Interpret '__exact=None' as the sql 'is NULL'; otherwise, reject all
1275 1280
         # uses of None as a query value.
1276 1281
         if value is None:
@@ -1284,6 +1289,10 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
1284 1289
             value = True
1285 1290
         elif callable(value):
1286 1291
             value = value()
  1292
+        elif hasattr(value, 'evaluate'):
  1293
+            # If value is a query expression, evaluate it
  1294
+            value = SQLEvaluator(value, self)
  1295
+            having_clause = value.contains_aggregate
1287 1296
 
1288 1297
         for alias, aggregate in self.aggregate_select.items():
1289 1298
             if alias == parts[0]:
@@ -1340,8 +1349,13 @@ def add_filter(self, filter_expr, connector=AND, negate=False, trim=False,
1340 1349
             self.promote_alias_chain(join_it, join_promote)
1341 1350
             self.promote_alias_chain(table_it, table_promote)
1342 1351
 
1343  
-        self.where.add((Constraint(alias, col, field), lookup_type, value),
1344  
-            connector)
  1352
+
  1353
+        if having_clause:
  1354
+            self.having.add((Constraint(alias, col, field), lookup_type, value),
  1355
+                connector)
  1356
+        else:
  1357
+            self.where.add((Constraint(alias, col, field), lookup_type, value),
  1358
+                connector)
1345 1359
 
1346 1360
         if negate:
1347 1361
             self.promote_alias_chain(join_list)
9  django/db/models/sql/subqueries.py
@@ -5,6 +5,7 @@
5 5
 from django.core.exceptions import FieldError
6 6
 from django.db.models.sql.constants import *
7 7
 from django.db.models.sql.datastructures import Date
  8
+from django.db.models.sql.expressions import SQLEvaluator
8 9
 from django.db.models.sql.query import Query
9 10
 from django.db.models.sql.where import AND, Constraint
10 11
 
@@ -136,7 +137,11 @@ def as_sql(self):
136 137
         result.append('SET')
137 138
         values, update_params = [], []
138 139
         for name, val, placeholder in self.values:
139  
-            if val is not None:
  140
+            if hasattr(val, 'as_sql'):
  141
+                sql, params = val.as_sql(qn)
  142
+                values.append('%s = %s' % (qn(name), sql))
  143
+                update_params.extend(params)
  144
+            elif val is not None:
140 145
                 values.append('%s = %s' % (qn(name), placeholder))
141 146
                 update_params.append(val)
142 147
             else:
@@ -251,6 +256,8 @@ def add_update_fields(self, values_seq):
251 256
             else:
252 257
                 placeholder = '%s'
253 258
 
  259
+            if hasattr(val, 'evaluate'):
  260
+                val = SQLEvaluator(val, self, allow_joins=False)
254 261
             if model:
255 262
                 self.add_related_update(model, field.column, val, placeholder)
256 263
             else:
10  django/db/models/sql/where.py
@@ -97,6 +97,7 @@ def as_sql(self, qn=None):
97 97
                 else:
98 98
                     # A leaf node in the tree.
99 99
                     sql, params = self.make_atom(child, qn)
  100
+
100 101
             except EmptyResultSet:
101 102
                 if self.connector == AND and not self.negated:
102 103
                     # We can bail out early in this particular case (only).
@@ -114,6 +115,7 @@ def as_sql(self, qn=None):
114 115
                 if self.negated:
115 116
                     empty = True
116 117
                 continue
  118
+
117 119
             empty = False
118 120
             if sql:
119 121
                 result.append(sql)
@@ -151,8 +153,9 @@ def make_atom(self, child, qn):
151 153
         else:
152 154
             cast_sql = '%s'
153 155
 
154  
-        if isinstance(params, QueryWrapper):
155  
-            extra, params = params.data
  156
+        if hasattr(params, 'as_sql'):
  157
+            extra, params = params.as_sql(qn)
  158
+            cast_sql = ''
156 159
         else:
157 160
             extra = ''
158 161
 
@@ -214,6 +217,9 @@ def relabel_aliases(self, change_map, node=None):
214 217
                 if elt[0] in change_map:
215 218
                     elt[0] = change_map[elt[0]]
216 219
                     node.children[pos] = (tuple(elt),) + child[1:]
  220
+                # Check if the query value also requires relabelling
  221
+                if hasattr(child[3], 'relabel_aliases'):
  222
+                    child[3].relabel_aliases(change_map)
217 223
 
218 224
 class EverythingNode(object):
219 225
     """
59  docs/ref/databases.txt
@@ -163,7 +163,7 @@ table (usually called ``django_session`` and the table
163 163
 Connecting to the database
164 164
 --------------------------
165 165
 
166  
-Refer to the :ref:`settings documentation <ref-settings>`. 
  166
+Refer to the :ref:`settings documentation <ref-settings>`.
167 167
 
168 168
 Connection settings are used in this order:
169 169
 
@@ -262,9 +262,9 @@ of whether ``unique=True`` is specified or not.
262 262
 
263 263
 .. _sqlite-notes:
264 264
 
265  
-SQLite notes 
266  
-============ 
267  
- 
  265
+SQLite notes
  266
+============
  267
+
268 268
 SQLite_ provides an excellent development alternative for applications that
269 269
 are predominantly read-only or require a smaller installation footprint. As
270 270
 with all database servers, though, there are some differences that are
@@ -294,21 +294,21 @@ the ``extra()`` QuerySet method. The bug can be identified by the error message
294 294
 ``OperationalError: ORDER BY terms must not be non-integer constants``. The
295 295
 problem can be solved updating SQLite to version 3.3.6 or newer, possibly also
296 296
 updating the ``pysqlite2`` Python module in the process.
297  
- 
298  
-.. _contain a bug: http://www.sqlite.org/cvstrac/tktview?tn=1768 
299  
- 
300  
-This has a very low impact because 3.3.6 was released in April 2006, so most 
301  
-current binary distributions for different platforms include newer version of 
302  
-SQLite usable from Python through either the ``pysqlite2`` or the ``sqlite3`` 
303  
-modules. 
304  
- 
305  
-However, in the case of Windows, the official binary distribution of the stable 
306  
-release of Python 2.5 (2.5.2, as of this writing) includes SQLite 3.3.4, so the bug can 
307  
-make itself evident in that platform. There are (as of Django 1.0) even three 
308  
-tests in the Django test suite that will fail when run under this setup.  As 
309  
-described above, this can be solved by downloading and installing a newer 
310  
-version of ``pysqlite2`` (``pysqlite-2.x.x.win32-py2.5.exe``) that includes and 
311  
-uses a newer version of SQLite. Python 2.6 ships with a newer version of 
  297
+
  298
+.. _contain a bug: http://www.sqlite.org/cvstrac/tktview?tn=1768
  299
+
  300
+This has a very low impact because 3.3.6 was released in April 2006, so most
  301
+current binary distributions for different platforms include newer version of
  302
+SQLite usable from Python through either the ``pysqlite2`` or the ``sqlite3``
  303
+modules.
  304
+
  305
+However, in the case of Windows, the official binary distribution of the stable
  306
+release of Python 2.5 (2.5.2, as of this writing) includes SQLite 3.3.4, so the bug can
  307
+make itself evident in that platform. There are (as of Django 1.0) even three
  308
+tests in the Django test suite that will fail when run under this setup.  As
  309
+described above, this can be solved by downloading and installing a newer
  310
+version of ``pysqlite2`` (``pysqlite-2.x.x.win32-py2.5.exe``) that includes and
  311
+uses a newer version of SQLite. Python 2.6 ships with a newer version of
312 312
 SQLite and is not affected by this issue.
313 313
 
314 314
 If you are in such platform and find yourself in the need to update
@@ -317,6 +317,23 @@ If you are in such platform and find yourself in the need to update
317 317
 attempts to import ``pysqlite2`` before than ``sqlite3`` and so it can take
318 318
 advantage of the new ``pysqlite2``/SQLite versions.
319 319
 
  320
+Version 3.5.9
  321
+-------------
  322
+
  323
+The Ubuntu "Intrepid Ibex" SQLite 3.5.9-3 package contains a bug that causes
  324
+problems with the evaluation of query expressions. If you are using Ubuntu
  325
+"Intrepid Ibex", you will need to find an alternate source for SQLite
  326
+packages, or install SQLite from source.
  327
+
  328
+At one time, Debian Lenny shipped with the same malfunctioning SQLite 3.5.9-3
  329
+package. However the Debian project has subsequently issued updated versions
  330
+of the SQLite package that correct these bugs. If you find you are getting
  331
+unexpected results under Debian, ensure you have updated your SQLite package
  332
+to 3.5.9-5 or later.
  333
+
  334
+The problem does not appear to exist with other versions of SQLite packaged
  335
+with other operating systems.
  336
+
320 337
 Version 3.6.2
321 338
 --------------
322 339
 
@@ -348,14 +365,14 @@ database user must have privileges to run the following commands:
348 365
     * CREATE SEQUENCE
349 366
     * CREATE PROCEDURE
350 367
     * CREATE TRIGGER
351  
-    
  368
+
352 369
 To run Django's test suite, the user needs these *additional* privileges:
353 370
 
354 371
     * CREATE USER
355 372
     * DROP USER
356 373
     * CREATE TABLESPACE
357 374
     * DROP TABLESPACE
358  
-    
  375
+
359 376
 Connecting to the database
360 377
 --------------------------
361 378
 
100  docs/topics/db/queries.txt
@@ -8,8 +8,8 @@ Making queries
8 8
 
9 9
 Once you've created your :ref:`data models <topics-db-models>`, Django
10 10
 automatically gives you a database-abstraction API that lets you create,
11  
-retrieve, update and delete objects. This document explains how to use this 
12  
-API. Refer to the :ref:`data model reference <ref-models-index>` for full 
  11
+retrieve, update and delete objects. This document explains how to use this
  12
+API. Refer to the :ref:`data model reference <ref-models-index>` for full
13 13
 details of all the various model lookup options.
14 14
 
15 15
 Throughout this guide (and in the reference), we'll refer to the following
@@ -39,6 +39,9 @@ models, which comprise a weblog application:
39 39
         body_text = models.TextField()
40 40
         pub_date = models.DateTimeField()
41 41
         authors = models.ManyToManyField(Author)
  42
+        n_comments = models.IntegerField()
  43
+        n_pingbacks = models.IntegerField()
  44
+        rating = models.IntegerField()
42 45
 
43 46
         def __unicode__(self):
44 47
             return self.headline
@@ -94,11 +97,11 @@ Saving ``ForeignKey`` and ``ManyToManyField`` fields
94 97
 ----------------------------------------------------
95 98
 
96 99
 Updating ``ForeignKey`` fields works exactly the same way as saving a normal
97  
-field; simply assign an object of the right type to the field in question:: 
  100
+field; simply assign an object of the right type to the field in question::
98 101
 
99  
-    >>> cheese_blog = Blog.objects.get(name="Cheddar Talk") 
100  
-    >>> entry.blog = cheese_blog 
101  
-    >>> entry.save() 
  102
+    >>> cheese_blog = Blog.objects.get(name="Cheddar Talk")
  103
+    >>> entry.blog = cheese_blog
  104
+    >>> entry.save()
102 105
 
103 106
 Updating a ``ManyToManyField`` works a little differently; use the ``add()``
104 107
 method on the field to add a record to the relation::
@@ -245,7 +248,7 @@ this example::
245 248
     >>> q = q.filter(pub_date__lte=datetime.now())
246 249
     >>> q = q.exclude(body_text__icontains="food")
247 250
     >>> print q
248  
-    
  251
+
249 252
 Though this looks like three database hits, in fact it hits the database only
250 253
 once, at the last line (``print q``). In general, the results of a ``QuerySet``
251 254
 aren't fetched from the database until you "ask" for them. When you do, the
@@ -333,15 +336,15 @@ you'll probably use:
333 336
 
334 337
     :lookup:`exact`
335 338
         An "exact" match. For example::
336  
-        
  339
+
337 340
             >>> Entry.objects.get(headline__exact="Man bites dog")
338 341
 
339 342
         Would generate SQL along these lines:
340  
-        
  343
+
341 344
         .. code-block:: sql
342 345
 
343 346
             SELECT ... WHERE headline = 'Man bites dog';
344  
-            
  347
+
345 348
         If you don't provide a lookup type -- that is, if your keyword argument
346 349
         doesn't contain a double underscore -- the lookup type is assumed to be
347 350
         ``exact``.
@@ -352,36 +355,36 @@ you'll probably use:
352 355
             >>> Blog.objects.get(id=14)         # __exact is implied
353 356
 
354 357
         This is for convenience, because ``exact`` lookups are the common case.
355  
-        
  358
+
356 359
     :lookup:`iexact`
357 360
         A case-insensitive match. So, the query::
358  
-        
  361
+
359 362
             >>> Blog.objects.get(name__iexact="beatles blog")
360  
-            
  363
+
361 364
         Would match a ``Blog`` titled "Beatles Blog", "beatles blog", or even
362 365
         "BeAtlES blOG".
363  
-    
  366
+
364 367
     :lookup:`contains`
365 368
         Case-sensitive containment test. For example::
366 369
 
367 370
             Entry.objects.get(headline__contains='Lennon')
368 371
 
369 372
         Roughly translates to this SQL:
370  
-        
  373
+
371 374
         .. code-block:: sql
372 375
 
373 376
             SELECT ... WHERE headline LIKE '%Lennon%';
374 377
 
375 378
         Note this will match the headline ``'Today Lennon honored'`` but not
376 379
         ``'today lennon honored'``.
377  
-        
  380
+
378 381
         There's also a case-insensitive version, :lookup:`icontains`.
379  
-        
  382
+
380 383
     :lookup:`startswith`, :lookup:`endswith`
381 384
         Starts-with and ends-with search, respectively. There are also
382 385
         case-insensitive versions called :lookup:`istartswith` and
383 386
         :lookup:`iendswith`.
384  
-    
  387
+
385 388
 Again, this only scratches the surface. A complete reference can be found in the
386 389
 :ref:`field lookup reference <field-lookups>`.
387 390
 
@@ -485,6 +488,48 @@ are talking about the same multi-valued relation). Conditions in subsequent
485 488
 ``filter()`` or ``exclude()`` calls that refer to the same relation may end up
486 489
 filtering on different linked objects.
487 490
 
  491
+.. _query-expressions:
  492
+
  493
+Filters can reference fields on the model
  494
+-----------------------------------------
  495
+
  496
+.. versionadded:: 1.1
  497
+
  498
+In the examples given so far, we have constructed filters that compare
  499
+the value of a model field with a constant. But what if you want to compare
  500
+the value of a model field with another field on the same model?
  501
+
  502
+Django provides the ``F()`` object to allow such comparisons. Instances
  503
+of ``F()`` act as a reference to a model field within a query. These
  504
+references can then be used in query filters to compare the values of two
  505
+different fields on the same model instance.
  506
+
  507
+For example, to find a list of all blog entries that have had more comments
  508
+than pingbacks, we construct an ``F()`` object to reference the comment count,
  509
+and use that ``F()`` object in the query::
  510
+
  511
+    >>> Entry.objects.filter(n_pingbacks__lt=F('n_comments'))
  512
+
  513
+Django supports the use of addition, subtraction, multiplication,
  514
+division and modulo arithmetic with ``F()`` objects, both with constants
  515
+and with other ``F()`` objects. To find all the blog entries with *twice* as
  516
+many comments as pingbacks, we modify the query::
  517
+
  518
+    >>> Entry.objects.filter(n_pingbacks__lt=F('n_comments') * 2)
  519
+
  520
+To find all the entries where the sum of the pingback count and comment count
  521
+is greater than the rating of the entry, we would issue the query::
  522
+
  523
+    >>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks'))
  524
+
  525
+You can also use the double underscore notation to span relationships in
  526
+an ``F()`` object. An ``F()`` object with a double underscore will introduce
  527
+any joins needed to access the related object. For example, to retrieve all
  528
+the entries where the author's name is the same as the blog name, we could
  529
+issue the query:
  530
+
  531
+    >>> Entry.objects.filter(author__name=F('blog__name'))
  532
+
488 533
 The pk lookup shortcut
489 534
 ----------------------
490 535
 
@@ -503,7 +548,7 @@ can be combined with ``pk`` to perform a query on the primary key of a model::
503 548
 
504 549
     # Get blogs entries with id 1, 4 and 7
505 550
     >>> Blog.objects.filter(pk__in=[1,4,7])
506  
-    
  551
+
507 552
     # Get all blog entries with id > 14
508 553
     >>> Blog.objects.filter(pk__gt=14)
509 554
 
@@ -728,7 +773,7 @@ To update ``ForeignKey`` fields, set the new value to be the new model
728 773
 instance you want to point to. Example::
729 774
 
730 775
     >>> b = Blog.objects.get(pk=1)
731  
-    
  776
+
732 777
     # Change every Entry so that it belongs to this Blog.
733 778
     >>> Entry.objects.all().update(blog=b)
734 779
 
@@ -749,6 +794,21 @@ Just loop over them and call ``save()``::
749 794
     for item in my_queryset:
750 795
         item.save()
751 796
 
  797
+Calls to update can also use :ref:`F() objects <query-expressions>` to update
  798
+one field based on the value of another field in the model. This is especially
  799
+useful for incrementing counters based upon their current value. For example, to
  800
+increment the pingback count for every entry in the blog::
  801
+
  802
+    >>> Entry.objects.all().update(n_pingbacks=F('n_pingbacks') + 1)
  803
+
  804
+However, unlike ``F()`` objects in filter and exclude clauses, you can't
  805
+introduce joins when you use ``F()`` objects in an update -- you can only
  806
+reference fields local to the model being updated. If you attempt to introduce
  807
+a join with an ``F()`` object, a ``FieldError`` will be raised::
  808
+
  809
+    # THIS WILL RAISE A FieldError
  810
+    >>> Entry.objects.update(headline=F('blog__name'))
  811
+
752 812
 Related objects
753 813
 ===============
754 814
 
0  tests/modeltests/expressions/__init__.py
No changes.
71  tests/modeltests/expressions/models.py
... ...
@@ -0,0 +1,71 @@
  1
+"""
  2
+Tests for F() query expression syntax.
  3
+"""
  4
+
  5
+from django.db import models
  6
+
  7
+class Employee(models.Model):
  8
+    firstname = models.CharField(max_length=50)
  9
+    lastname = models.CharField(max_length=50)
  10
+
  11
+    def __unicode__(self):
  12
+        return u'%s %s' % (self.firstname, self.lastname)
  13
+
  14
+class Company(models.Model):
  15
+    name = models.CharField(max_length=100)
  16
+    num_employees = models.PositiveIntegerField()
  17
+    num_chairs = models.PositiveIntegerField()
  18
+    ceo = models.ForeignKey(
  19
+        Employee,
  20
+        related_name='company_ceo_set')
  21
+    point_of_contact = models.ForeignKey(
  22
+        Employee,
  23
+        related_name='company_point_of_contact_set',
  24
+        null=True)
  25
+
  26
+    def __unicode__(self):
  27
+        return self.name
  28
+
  29
+
  30
+__test__ = {'API_TESTS': """
  31
+>>> from django.db.models import F
  32
+
  33
+>>> Company(name='Example Inc.', num_employees=2300, num_chairs=5,
  34
+...     ceo=Employee.objects.create(firstname='Joe', lastname='Smith')).save()
  35
+>>> Company(name='Foobar Ltd.', num_employees=3, num_chairs=3,
  36
+...     ceo=Employee.objects.create(firstname='Frank', lastname='Meyer')).save()
  37
+>>> Company(name='Test GmbH', num_employees=32, num_chairs=1,
  38
+...     ceo=Employee.objects.create(firstname='Max', lastname='Mustermann')).save()
  39
+
  40
+# We can filter for companies where the number of employees is greater than the
  41
+# number of chairs.
  42
+
  43
+>>> Company.objects.filter(num_employees__gt=F('num_chairs'))
  44
+[<Company: Example Inc.>, <Company: Test GmbH>]
  45
+
  46
+# The relation of a foreign key can become copied over to an other foreign key.
  47
+
  48
+>>> Company.objects.update(point_of_contact=F('ceo'))
  49
+3
  50
+
  51
+>>> [c.point_of_contact for c in Company.objects.all()]
  52
+[<Employee: Joe Smith>, <Employee: Frank Meyer>, <Employee: Max Mustermann>]
  53
+
  54
+>>> c = Company.objects.all()[0]
  55
+>>> c.point_of_contact = Employee.objects.create(firstname="Guido", lastname="van Rossum")
  56
+>>> c.save()
  57
+
  58
+# F Expressions can also span joins
  59
+>>> Company.objects.filter(ceo__firstname=F('point_of_contact__firstname')).distinct()
  60
+[<Company: Foobar Ltd.>, <Company: Test GmbH>]
  61
+
  62
+>>> _ = Company.objects.exclude(ceo__firstname=F('point_of_contact__firstname')).update(name='foo')
  63
+>>> Company.objects.exclude(ceo__firstname=F('point_of_contact__firstname')).get().name
  64
+u'foo'
  65
+
  66
+>>> _ = Company.objects.exclude(ceo__firstname=F('point_of_contact__firstname')).update(name=F('point_of_contact__lastname'))
  67
+Traceback (most recent call last):
  68
+...
  69
+FieldError: Joined field references are not permitted in this query
  70
+
  71
+"""}
17  tests/regressiontests/aggregation_regress/models.py
@@ -50,7 +50,7 @@ def __unicode__(self):
50 50
 #Extra does not play well with values. Modify the tests if/when this is fixed.
51 51
 __test__ = {'API_TESTS': """
52 52
 >>> from django.core import management
53  
->>> from django.db.models import get_app
  53
+>>> from django.db.models import get_app, F
54 54
 
55 55
 # Reset the database representation of this app.
56 56
 # This will return the database to a clean initial state.
@@ -164,6 +164,21 @@ def __unicode__(self):
164 164
 >>> len(Book.objects.annotate(num_authors=Count('authors')).exclude(num_authors__lt=2).filter(num_authors__lt=3))
165 165
 2
166 166
 
  167
+# Aggregates can be used with F() expressions
  168
+# ... where the F() is pushed into the HAVING clause
  169
+>>> Publisher.objects.annotate(num_books=Count('book')).filter(num_books__lt=F('num_awards')/2).values('name','num_books','num_awards')
  170
+[{'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}, {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}]
  171
+
  172
+>>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).values('name','num_books','num_awards')
  173
+[{'num_books': 2, 'name': u'Apress', 'num_awards': 3}, {'num_books': 1, 'name': u'Sams', 'num_awards': 1}, {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}]
  174
+
  175
+# ... and where the F() references an aggregate
  176
+>>> Publisher.objects.annotate(num_books=Count('book')).filter(num_awards__gt=2*F('num_books')).values('name','num_books','num_awards')
  177
+[{'num_books': 2, 'name': u'Prentice Hall', 'num_awards': 7}, {'num_books': 1, 'name': u'Morgan Kaufmann', 'num_awards': 9}]
  178
+
  179
+>>> Publisher.objects.annotate(num_books=Count('book')).exclude(num_books__lt=F('num_awards')/2).values('name','num_books','num_awards')
  180
+[{'num_books': 2, 'name': u'Apress', 'num_awards': 3}, {'num_books': 1, 'name': u'Sams', 'num_awards': 1}, {'num_books': 0, 'name': u"Jonno's House of Books", 'num_awards': 0}]
  181
+
167 182
 # Regression for #10089: Check handling of empty result sets with aggregates
168 183
 >>> Book.objects.filter(id__in=[]).count()
169 184
 0
0  tests/regressiontests/expressions_regress/__init__.py
No changes.
133  tests/regressiontests/expressions_regress/models.py
... ...
@@ -0,0 +1,133 @@
  1
+"""
  2
+Spanning tests for all the operations that F() expressions can perform.
  3
+"""
  4
+
  5
+from django.db import models
  6
+
  7
+#
  8
+# Model for testing arithmetic expressions.
  9
+#
  10
+
  11
+class Number(models.Model):
  12
+    integer = models.IntegerField()
  13
+    float = models.FloatField(null=True)
  14
+
  15
+    def __unicode__(self):
  16
+        return u'%i, %.3f' % (self.integer, self.float)
  17
+
  18
+
  19
+__test__ = {'API_TESTS': """
  20
+>>> from django.db.models import F
  21
+
  22
+>>> Number(integer=-1).save()
  23
+>>> Number(integer=42).save()
  24
+>>> Number(integer=1337).save()
  25
+
  26
+We can fill a value in all objects with an other value of the same object.
  27
+
  28
+>>> Number.objects.update(float=F('integer'))
  29
+3
  30
+>>> Number.objects.all()
  31
+[<Number: -1, -1.000>, <Number: 42, 42.000>, <Number: 1337, 1337.000>]
  32
+
  33
+We can increment a value of all objects in a query set.
  34
+
  35
+>>> Number.objects.filter(integer__gt=0).update(integer=F('integer') + 1)
  36
+2
  37
+>>> Number.objects.all()
  38
+[<Number: -1, -1.000>, <Number: 43, 42.000>, <Number: 1338, 1337.000>]
  39
+
  40
+We can filter for objects, where a value is not equals the value of an other field.
  41
+
  42
+>>> Number.objects.exclude(float=F('integer'))
  43
+[<Number: 43, 42.000>, <Number: 1338, 1337.000>]
  44
+
  45
+Complex expressions of different connection types are possible.
  46
+
  47
+>>> n = Number.objects.create(integer=10, float=123.45)
  48
+
  49
+>>> Number.objects.filter(pk=n.pk).update(float=F('integer') + F('float') * 2)
  50
+1
  51
+>>> Number.objects.get(pk=n.pk)
  52
+<Number: 10, 256.900>
  53
+
  54
+# All supported operators work as expected.
  55
+
  56
+>>> n = Number.objects.create(integer=42, float=15.5)
  57
+
  58
+# Left hand operators
  59
+
  60
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  61
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') + 15, float=F('float') + 42.7)
  62
+>>> Number.objects.get(pk=n.pk) # LH Addition of floats and integers
  63
+<Number: 57, 58.200>
  64
+
  65
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  66
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') - 15, float=F('float') - 42.7)
  67
+>>> Number.objects.get(pk=n.pk) # LH Subtraction of floats and integers
  68
+<Number: 27, -27.200>
  69
+
  70
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  71
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') * 15, float=F('float') * 42.7)
  72
+>>> Number.objects.get(pk=n.pk) # Multiplication of floats and integers
  73
+<Number: 630, 661.850>
  74
+
  75
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  76
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') / 2, float=F('float') / 42.7)
  77
+>>> Number.objects.get(pk=n.pk) # LH Division of floats and integers
  78
+<Number: 21, 0.363>
  79
+
  80
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  81
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') % 20)
  82
+>>> Number.objects.get(pk=n.pk) # LH Modulo arithmetic on integers
  83
+<Number: 2, 15.500>
  84
+
  85
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  86
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') & 56)
  87
+>>> Number.objects.get(pk=n.pk) # LH Bitwise ands on integers
  88
+<Number: 40, 15.500>
  89
+
  90
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  91
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=F('integer') | 48)
  92
+>>> Number.objects.get(pk=n.pk) # LH Bitwise or on integers
  93
+<Number: 58, 15.500>
  94
+
  95
+# Right hand operators
  96
+
  97
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  98
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=15 + F('integer'), float=42.7 + F('float'))
  99
+>>> Number.objects.get(pk=n.pk) # RH Addition of floats and integers
  100
+<Number: 57, 58.200>
  101
+
  102
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  103
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=15 - F('integer'), float=42.7 - F('float'))
  104
+>>> Number.objects.get(pk=n.pk) # RH Subtraction of floats and integers
  105
+<Number: -27, 27.200>
  106
+
  107
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  108
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=15 * F('integer'), float=42.7 * F('float'))
  109
+>>> Number.objects.get(pk=n.pk) # RH Multiplication of floats and integers
  110
+<Number: 630, 661.850>
  111
+
  112
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  113
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=640 / F('integer'), float=42.7 / F('float'))
  114
+>>> Number.objects.get(pk=n.pk) # RH Division of floats and integers
  115
+<Number: 15, 2.755>
  116
+
  117
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  118
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=69 % F('integer'))
  119
+>>> Number.objects.get(pk=n.pk) # RH Modulo arithmetic on integers
  120
+<Number: 27, 15.500>
  121
+
  122
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  123
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=15 & F('integer'))
  124
+>>> Number.objects.get(pk=n.pk) # RH Bitwise ands on integers
  125
+<Number: 10, 15.500>
  126
+
  127
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=42, float=15.5)
  128
+>>> _ = Number.objects.filter(pk=n.pk).update(integer=15 | F('integer'))
  129
+>>> Number.objects.get(pk=n.pk) # RH Bitwise or on integers
  130
+<Number: 47, 15.500>
  131
+
  132
+
  133
+"""}

0 notes on commit cf37e46

Please sign in to comment.
Something went wrong with that request. Please try again.