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( |