Revert "Fixed #16211 -- Added comparison and negation ops to F() expressions"
This reverts commit 28abf5f0eb
.
Conflicts:
docs/releases/1.5.txt
This commit is contained in:
parent
7f4dbdc036
commit
a8b1861fc4
|
@ -913,9 +913,6 @@ class BaseDatabaseOperations(object):
|
||||||
can vary between backends (e.g., Oracle with %% and &) and between
|
can vary between backends (e.g., Oracle with %% and &) and between
|
||||||
subexpression types (e.g., date expressions)
|
subexpression types (e.g., date expressions)
|
||||||
"""
|
"""
|
||||||
if connector == 'NOT':
|
|
||||||
assert len(sub_expressions) == 1
|
|
||||||
return 'NOT (%s)' % sub_expressions[0]
|
|
||||||
conn = ' %s ' % connector
|
conn = ' %s ' % connector
|
||||||
return conn.join(sub_expressions)
|
return conn.join(sub_expressions)
|
||||||
|
|
||||||
|
|
|
@ -18,17 +18,6 @@ class ExpressionNode(tree.Node):
|
||||||
AND = '&'
|
AND = '&'
|
||||||
OR = '|'
|
OR = '|'
|
||||||
|
|
||||||
# Unary operator (needs special attention in combine_expression)
|
|
||||||
NOT = 'NOT'
|
|
||||||
|
|
||||||
# Comparison operators
|
|
||||||
EQ = '='
|
|
||||||
GE = '>='
|
|
||||||
GT = '>'
|
|
||||||
LE = '<='
|
|
||||||
LT = '<'
|
|
||||||
NE = '<>'
|
|
||||||
|
|
||||||
def __init__(self, children=None, connector=None, negated=False):
|
def __init__(self, children=None, connector=None, negated=False):
|
||||||
if children is not None and len(children) > 1 and connector is None:
|
if children is not None and len(children) > 1 and connector is None:
|
||||||
raise TypeError('You have to specify a connector.')
|
raise TypeError('You have to specify a connector.')
|
||||||
|
@ -104,32 +93,6 @@ class ExpressionNode(tree.Node):
|
||||||
def __ror__(self, other):
|
def __ror__(self, other):
|
||||||
return self._combine(other, self.OR, True)
|
return self._combine(other, self.OR, True)
|
||||||
|
|
||||||
def __invert__(self):
|
|
||||||
obj = ExpressionNode([self], connector=self.NOT, negated=True)
|
|
||||||
return obj
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
return self._combine(other, self.EQ, False)
|
|
||||||
|
|
||||||
def __ge__(self, other):
|
|
||||||
return self._combine(other, self.GE, False)
|
|
||||||
|
|
||||||
def __gt__(self, other):
|
|
||||||
return self._combine(other, self.GT, False)
|
|
||||||
|
|
||||||
def __le__(self, other):
|
|
||||||
return self._combine(other, self.LE, False)
|
|
||||||
|
|
||||||
def __lt__(self, other):
|
|
||||||
return self._combine(other, self.LT, False)
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return self._combine(other, self.NE, False)
|
|
||||||
|
|
||||||
def __bool__(self):
|
|
||||||
raise TypeError('Boolean operators should be avoided. Use bitwise operators.')
|
|
||||||
__nonzero__ = __bool__
|
|
||||||
|
|
||||||
def prepare_database_save(self, unused):
|
def prepare_database_save(self, unused):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
|
@ -88,11 +88,7 @@ class Node(object):
|
||||||
Otherwise, the whole tree is pushed down one level and a new root
|
Otherwise, the whole tree is pushed down one level and a new root
|
||||||
connector is created, connecting the existing tree and the new node.
|
connector is created, connecting the existing tree and the new node.
|
||||||
"""
|
"""
|
||||||
# Using for loop with 'is' instead of 'if node in children' so node
|
if node in self.children and conn_type == self.connector:
|
||||||
# __eq__ method doesn't get called. The __eq__ method can be overriden
|
|
||||||
# by subtypes, for example the F-expression.
|
|
||||||
for child in self.children:
|
|
||||||
if node is child and conn_type == self.connector:
|
|
||||||
return
|
return
|
||||||
if len(self.children) < 2:
|
if len(self.children) < 2:
|
||||||
self.connector = conn_type
|
self.connector = conn_type
|
||||||
|
|
|
@ -177,10 +177,6 @@ Django 1.5 also includes several smaller improvements worth noting:
|
||||||
:setting:`DEBUG` is `True` are sent to the console (unless you redefine the
|
:setting:`DEBUG` is `True` are sent to the console (unless you redefine the
|
||||||
logger in your :setting:`LOGGING` setting).
|
logger in your :setting:`LOGGING` setting).
|
||||||
|
|
||||||
* :ref:`F() expressions <query-expressions>` now support comparison operations
|
|
||||||
and inversion, expanding the types of expressions that can be passed to the
|
|
||||||
database.
|
|
||||||
|
|
||||||
* When using :class:`~django.template.RequestContext`, it is now possible to
|
* When using :class:`~django.template.RequestContext`, it is now possible to
|
||||||
look up permissions by using ``{% if 'someapp.someperm' in perms %}``
|
look up permissions by using ``{% if 'someapp.someperm' in perms %}``
|
||||||
in templates.
|
in templates.
|
||||||
|
|
|
@ -640,15 +640,6 @@ that were modified more than 3 days after they were published::
|
||||||
>>> from datetime import timedelta
|
>>> from datetime import timedelta
|
||||||
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3))
|
||||||
|
|
||||||
.. versionadded:: 1.5
|
|
||||||
Comparisons and negation operators for ``F()`` expressions
|
|
||||||
|
|
||||||
Django also supports the comparison operators ``==``, ``!=``, ``<=``, ``<``,
|
|
||||||
``>``, ``>=`` and the bitwise negation operator ``~`` (boolean ``not`` operator
|
|
||||||
will raise ``TypeError``)::
|
|
||||||
|
|
||||||
>>> Entry.objects.filter(is_heavily_quoted=~(F('n_pingbacks') < 100))
|
|
||||||
|
|
||||||
The pk lookup shortcut
|
The pk lookup shortcut
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ class Company(models.Model):
|
||||||
Employee,
|
Employee,
|
||||||
related_name='company_point_of_contact_set',
|
related_name='company_point_of_contact_set',
|
||||||
null=True)
|
null=True)
|
||||||
is_large = models.BooleanField(
|
|
||||||
blank=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
|
@ -11,22 +11,22 @@ from .models import Company, Employee
|
||||||
class ExpressionsTests(TestCase):
|
class ExpressionsTests(TestCase):
|
||||||
def test_filter(self):
|
def test_filter(self):
|
||||||
Company.objects.create(
|
Company.objects.create(
|
||||||
name="Example Inc.", num_employees=2300, num_chairs=5, is_large=False,
|
name="Example Inc.", num_employees=2300, num_chairs=5,
|
||||||
ceo=Employee.objects.create(firstname="Joe", lastname="Smith")
|
ceo=Employee.objects.create(firstname="Joe", lastname="Smith")
|
||||||
)
|
)
|
||||||
Company.objects.create(
|
Company.objects.create(
|
||||||
name="Foobar Ltd.", num_employees=3, num_chairs=4, is_large=False,
|
name="Foobar Ltd.", num_employees=3, num_chairs=4,
|
||||||
ceo=Employee.objects.create(firstname="Frank", lastname="Meyer")
|
ceo=Employee.objects.create(firstname="Frank", lastname="Meyer")
|
||||||
)
|
)
|
||||||
Company.objects.create(
|
Company.objects.create(
|
||||||
name="Test GmbH", num_employees=32, num_chairs=1, is_large=False,
|
name="Test GmbH", num_employees=32, num_chairs=1,
|
||||||
ceo=Employee.objects.create(firstname="Max", lastname="Mustermann")
|
ceo=Employee.objects.create(firstname="Max", lastname="Mustermann")
|
||||||
)
|
)
|
||||||
|
|
||||||
company_query = Company.objects.values(
|
company_query = Company.objects.values(
|
||||||
"name", "num_employees", "num_chairs", "is_large"
|
"name", "num_employees", "num_chairs"
|
||||||
).order_by(
|
).order_by(
|
||||||
"name", "num_employees", "num_chairs", "is_large"
|
"name", "num_employees", "num_chairs"
|
||||||
)
|
)
|
||||||
|
|
||||||
# We can filter for companies where the number of employees is greater
|
# We can filter for companies where the number of employees is greater
|
||||||
|
@ -37,13 +37,11 @@ class ExpressionsTests(TestCase):
|
||||||
"num_chairs": 5,
|
"num_chairs": 5,
|
||||||
"name": "Example Inc.",
|
"name": "Example Inc.",
|
||||||
"num_employees": 2300,
|
"num_employees": 2300,
|
||||||
"is_large": False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"num_chairs": 1,
|
"num_chairs": 1,
|
||||||
"name": "Test GmbH",
|
"name": "Test GmbH",
|
||||||
"num_employees": 32,
|
"num_employees": 32
|
||||||
"is_large": False
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
lambda o: o
|
lambda o: o
|
||||||
|
@ -57,20 +55,17 @@ class ExpressionsTests(TestCase):
|
||||||
{
|
{
|
||||||
"num_chairs": 2300,
|
"num_chairs": 2300,
|
||||||
"name": "Example Inc.",
|
"name": "Example Inc.",
|
||||||
"num_employees": 2300,
|
"num_employees": 2300
|
||||||
"is_large": False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"num_chairs": 3,
|
"num_chairs": 3,
|
||||||
"name": "Foobar Ltd.",
|
"name": "Foobar Ltd.",
|
||||||
"num_employees": 3,
|
"num_employees": 3
|
||||||
"is_large": False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"num_chairs": 32,
|
"num_chairs": 32,
|
||||||
"name": "Test GmbH",
|
"name": "Test GmbH",
|
||||||
"num_employees": 32,
|
"num_employees": 32
|
||||||
"is_large": False
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
lambda o: o
|
lambda o: o
|
||||||
|
@ -84,20 +79,17 @@ class ExpressionsTests(TestCase):
|
||||||
{
|
{
|
||||||
'num_chairs': 2302,
|
'num_chairs': 2302,
|
||||||
'name': 'Example Inc.',
|
'name': 'Example Inc.',
|
||||||
'num_employees': 2300,
|
'num_employees': 2300
|
||||||
'is_large': False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'num_chairs': 5,
|
'num_chairs': 5,
|
||||||
'name': 'Foobar Ltd.',
|
'name': 'Foobar Ltd.',
|
||||||
'num_employees': 3,
|
'num_employees': 3
|
||||||
'is_large': False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'num_chairs': 34,
|
'num_chairs': 34,
|
||||||
'name': 'Test GmbH',
|
'name': 'Test GmbH',
|
||||||
'num_employees': 32,
|
'num_employees': 32
|
||||||
'is_large': False
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
lambda o: o,
|
lambda o: o,
|
||||||
|
@ -112,20 +104,17 @@ class ExpressionsTests(TestCase):
|
||||||
{
|
{
|
||||||
'num_chairs': 6900,
|
'num_chairs': 6900,
|
||||||
'name': 'Example Inc.',
|
'name': 'Example Inc.',
|
||||||
'num_employees': 2300,
|
'num_employees': 2300
|
||||||
'is_large': False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'num_chairs': 9,
|
'num_chairs': 9,
|
||||||
'name': 'Foobar Ltd.',
|
'name': 'Foobar Ltd.',
|
||||||
'num_employees': 3,
|
'num_employees': 3
|
||||||
'is_large': False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'num_chairs': 96,
|
'num_chairs': 96,
|
||||||
'name': 'Test GmbH',
|
'name': 'Test GmbH',
|
||||||
'num_employees': 32,
|
'num_employees': 32
|
||||||
'is_large': False
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
lambda o: o,
|
lambda o: o,
|
||||||
|
@ -140,80 +129,21 @@ class ExpressionsTests(TestCase):
|
||||||
{
|
{
|
||||||
'num_chairs': 5294600,
|
'num_chairs': 5294600,
|
||||||
'name': 'Example Inc.',
|
'name': 'Example Inc.',
|
||||||
'num_employees': 2300,
|
'num_employees': 2300
|
||||||
'is_large': False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'num_chairs': 15,
|
'num_chairs': 15,
|
||||||
'name': 'Foobar Ltd.',
|
'name': 'Foobar Ltd.',
|
||||||
'num_employees': 3,
|
'num_employees': 3
|
||||||
'is_large': False
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'num_chairs': 1088,
|
'num_chairs': 1088,
|
||||||
'name': 'Test GmbH',
|
'name': 'Test GmbH',
|
||||||
'num_employees': 32,
|
'num_employees': 32
|
||||||
'is_large': False
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
lambda o: o,
|
lambda o: o,
|
||||||
)
|
)
|
||||||
# The comparison operators and the bitwise unary not can be used
|
|
||||||
# to assign to boolean fields
|
|
||||||
for expression in (
|
|
||||||
# Check boundaries
|
|
||||||
~(F('num_employees') < 33),
|
|
||||||
~(F('num_employees') <= 32),
|
|
||||||
(F('num_employees') > 2299),
|
|
||||||
(F('num_employees') >= 2300),
|
|
||||||
(F('num_employees') == 2300),
|
|
||||||
((F('num_employees') + 1 != 4) & (32 != F('num_employees'))),
|
|
||||||
# Inverted argument order works too
|
|
||||||
(2299 < F('num_employees')),
|
|
||||||
(2300 <= F('num_employees'))
|
|
||||||
):
|
|
||||||
# Test update by F-expression
|
|
||||||
company_query.update(
|
|
||||||
is_large=expression
|
|
||||||
)
|
|
||||||
# Compare results
|
|
||||||
self.assertQuerysetEqual(
|
|
||||||
company_query, [
|
|
||||||
{
|
|
||||||
'num_chairs': 5294600,
|
|
||||||
'name': 'Example Inc.',
|
|
||||||
'num_employees': 2300,
|
|
||||||
'is_large': True
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'num_chairs': 15,
|
|
||||||
'name': 'Foobar Ltd.',
|
|
||||||
'num_employees': 3,
|
|
||||||
'is_large': False
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'num_chairs': 1088,
|
|
||||||
'name': 'Test GmbH',
|
|
||||||
'num_employees': 32,
|
|
||||||
'is_large': False
|
|
||||||
}
|
|
||||||
],
|
|
||||||
lambda o: o,
|
|
||||||
)
|
|
||||||
# Reset values
|
|
||||||
company_query.update(
|
|
||||||
is_large=False
|
|
||||||
)
|
|
||||||
|
|
||||||
# The python boolean operators should be avoided as they yield
|
|
||||||
# unexpected results
|
|
||||||
test_gmbh = Company.objects.get(name="Test GmbH")
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
test_gmbh.is_large = not F('is_large')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
test_gmbh.is_large = F('is_large') and F('is_large')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
test_gmbh.is_large = F('is_large') or F('is_large')
|
|
||||||
|
|
||||||
# The relation of a foreign key can become copied over to an other
|
# The relation of a foreign key can become copied over to an other
|
||||||
# foreign key.
|
# foreign key.
|
||||||
|
@ -272,8 +202,9 @@ class ExpressionsTests(TestCase):
|
||||||
test_gmbh.point_of_contact = None
|
test_gmbh.point_of_contact = None
|
||||||
test_gmbh.save()
|
test_gmbh.save()
|
||||||
self.assertTrue(test_gmbh.point_of_contact is None)
|
self.assertTrue(test_gmbh.point_of_contact is None)
|
||||||
with self.assertRaises(ValueError):
|
def test():
|
||||||
test_gmbh.point_of_contact = F("ceo")
|
test_gmbh.point_of_contact = F("ceo")
|
||||||
|
self.assertRaises(ValueError, test)
|
||||||
|
|
||||||
test_gmbh.point_of_contact = test_gmbh.ceo
|
test_gmbh.point_of_contact = test_gmbh.ceo
|
||||||
test_gmbh.save()
|
test_gmbh.save()
|
||||||
|
|
Loading…
Reference in New Issue