From 395bc2aa3fede50646f78a07b4035824339a1e44 Mon Sep 17 00:00:00 2001
From: Nate Bragg <jonathan.bragg@alum.rpi.edu>
Date: Wed, 18 Jan 2012 22:02:12 -0500
Subject: [PATCH] Implemented a fix to #17186 using a negation node; Updated
docs and tests
---
django/db/models/expressions.py | 14 ++++++++++++++
docs/topics/db/queries.txt | 23 +++++++++++++++++++++++
tests/modeltests/expressions/models.py | 3 +++
tests/modeltests/expressions/tests.py | 13 +++++++++++++
4 files changed, 53 insertions(+), 0 deletions(-)
diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
index a71f4a3..7c978dc 100644
a
|
b
|
class ExpressionNode(tree.Node):
|
70 | 70 | def __or__(self, other): |
71 | 71 | return self._combine(other, self.OR, False) |
72 | 72 | |
| 73 | def __invert__(self): |
| 74 | return NotNode([self]) |
| 75 | |
73 | 76 | def __radd__(self, other): |
74 | 77 | return self._combine(other, self.ADD, True) |
75 | 78 | |
… |
… |
class F(ExpressionNode):
|
113 | 116 | def evaluate(self, evaluator, qn, connection): |
114 | 117 | return evaluator.evaluate_leaf(self, qn, connection) |
115 | 118 | |
| 119 | class NotNode(ExpressionNode): |
| 120 | """ |
| 121 | Node that represents a negation. |
| 122 | """ |
| 123 | def __init__(self, children=None): |
| 124 | super(NotNode, self).__init__(children, None, True) |
| 125 | |
| 126 | def evaluate(self, evaluator, qn, connection): |
| 127 | sql, params = evaluator.evaluate_node(self, qn, connection) |
| 128 | return ("NOT %s" % sql, params) |
| 129 | |
116 | 130 | class DateModifierNode(ExpressionNode): |
117 | 131 | """ |
118 | 132 | Node that implements the following syntax: |
diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
index 345687e..5059f0e 100644
a
|
b
|
after they were published::
|
594 | 594 | >>> from datetime import timedelta |
595 | 595 | >>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3)) |
596 | 596 | |
| 597 | .. versionadded:: 1.4 |
| 598 | |
| 599 | Additionally, ``F()`` objects can be negated using the ``~`` operator. |
| 600 | Note that for ``NULL`` values, this performs no changes:: |
| 601 | |
| 602 | >>> class A(Model): |
| 603 | ... f = NullBooleanField() |
| 604 | ... |
| 605 | >>> A.objects.create() |
| 606 | <A: A object> |
| 607 | >>> for m in A.objects.all(): |
| 608 | ... m.f = not m.f; m.save() # all NULL values are now True |
| 609 | ... |
| 610 | >>> A.objects.values('f') |
| 611 | [{'f': True}] |
| 612 | >>> A.objects.all().delete() |
| 613 | >>> A.objects.create() |
| 614 | <A: A object> |
| 615 | >>> A.objects.update(f=~F('f')) # this leaves NULL values unchanged as NOT NULL is NULL |
| 616 | 1 |
| 617 | >>> A.objects.values('f') |
| 618 | [{'f': None}] |
| 619 | |
597 | 620 | The pk lookup shortcut |
598 | 621 | ---------------------- |
599 | 622 | |
diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py
index dd50499..da6f1df 100644
a
|
b
|
from django.db import models
|
8 | 8 | class Employee(models.Model): |
9 | 9 | firstname = models.CharField(max_length=50) |
10 | 10 | lastname = models.CharField(max_length=50) |
| 11 | is_active = models.BooleanField(default=False) |
11 | 12 | |
12 | 13 | def __unicode__(self): |
13 | 14 | return u'%s %s' % (self.firstname, self.lastname) |
… |
… |
class Company(models.Model):
|
26 | 27 | |
27 | 28 | def __unicode__(self): |
28 | 29 | return self.name |
| 30 | |
| 31 | |
diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py
index 8f4f546..c1096c0 100644
a
|
b
|
class ExpressionsTests(TestCase):
|
22 | 22 | ceo=Employee.objects.create(firstname="Max", lastname="Mustermann") |
23 | 23 | ) |
24 | 24 | |
| 25 | # mark active employees when their companies have num_chairs greater than 3 |
| 26 | employee_qs = Employee.objects.filter(is_active=False) |
| 27 | self.assertEqual(employee_qs.count(), 3) |
| 28 | |
| 29 | Employee.objects.filter(company_ceo_set__num_chairs__gte=3)\ |
| 30 | .update(is_active=~F("is_active")) |
| 31 | |
| 32 | employee_qs = Employee.objects.filter(is_active=False) |
| 33 | self.assertEqual(employee_qs.count(), 1) |
| 34 | |
| 35 | employee_qs = Employee.objects.filter(is_active=~(~F('is_active'))) |
| 36 | self.assertEqual(employee_qs.count(), Employee.objects.count()) |
| 37 | |
25 | 38 | company_query = Company.objects.values( |
26 | 39 | "name", "num_employees", "num_chairs" |
27 | 40 | ).order_by( |