Ticket #16211: issue16211+14029-query-expression-extra-operators4.diff

File issue16211+14029-query-expression-extra-operators4.diff, 12.3 KB (added by twoolie, 3 years ago)

koenb's patch, but documentation and compat added.

  • django/db/backends/__init__.py

    diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
    index 9b0f495..8b50ce0 100644
    a b class BaseDatabaseOperations(object): 
    912912        can vary between backends (e.g., Oracle with %% and &) and between
    913913        subexpression types (e.g., date expressions)
    914914        """
     915        if connector == 'NOT':
     916            assert len(sub_expressions) == 1
     917            return 'NOT (%s)' % sub_expressions[0]
    915918        conn = ' %s ' % connector
    916919        return conn.join(sub_expressions)
    917920
  • django/db/models/expressions.py

    diff --git a/django/db/models/expressions.py b/django/db/models/expressions.py
    index 639ef6e..972440b 100644
    a b class ExpressionNode(tree.Node): 
    1818    AND = '&'
    1919    OR = '|'
    2020
     21    # Unary operator (needs special attention in combine_expression)
     22    NOT = 'NOT'
     23
     24    # Comparison operators
     25    EQ = '='
     26    GE = '>='
     27    GT = '>'
     28    LE = '<='
     29    LT = '<'
     30    NE = '<>'
     31
    2132    def __init__(self, children=None, connector=None, negated=False):
    2233        if children is not None and len(children) > 1 and connector is None:
    2334            raise TypeError('You have to specify a connector.')
    class ExpressionNode(tree.Node): 
    93104    def __ror__(self, other):
    94105        return self._combine(other, self.OR, True)
    95106
     107    def __invert__(self):
     108        obj = ExpressionNode([self], connector=self.NOT, negated=True)
     109        return obj
     110
     111    def __eq__(self, other):
     112        return self._combine(other, self.EQ, False)
     113
     114    def __ge__(self, other):
     115        return self._combine(other, self.GE, False)
     116
     117    def __gt__(self, other):
     118        return self._combine(other, self.GT, False)
     119
     120    def __le__(self, other):
     121        return self._combine(other, self.LE, False)
     122
     123    def __lt__(self, other):
     124        return self._combine(other, self.LT, False)
     125
     126    def __ne__(self, other):
     127        return self._combine(other, self.NE, False)
     128
     129    def __bool__(self):
     130        raise TypeError('Boolean operators should be avoided. Use bitwise operators.')
     131    __nonzero__ = __bool__
     132
    96133    def prepare_database_save(self, unused):
    97134        return self
    98135
  • django/utils/tree.py

    diff --git a/django/utils/tree.py b/django/utils/tree.py
    index 717181d..090e5c9 100644
    a b class Node(object): 
    8888        Otherwise, the whole tree is pushed down one level and a new root
    8989        connector is created, connecting the existing tree and the new node.
    9090        """
    91         if node in self.children and conn_type == self.connector:
    92             return
     91        # Using for loop with 'is' instead of 'if node in children' so node
     92        # __eq__ method doesn't get called
     93        for child in self.children:
     94            if node is child and conn_type == self.connector:
     95                return
    9396        if len(self.children) < 2:
    9497            self.connector = conn_type
    9598        if self.connector == conn_type:
  • docs/releases/1.5.txt

    diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
    index 528a44c..6d37620 100644
    a b Django 1.5 also includes several smaller improvements worth noting: 
    129129  configuration duplication. More information can be found in the
    130130  :func:`~django.contrib.auth.decorators.login_required` documentation.
    131131
     132* The :class:`~django.db.models.expressions.ExpressionNode` object now supports
     133  comparison operations and inversion, expanding the types of expressions that
     134  can be passed to the database.
     135
    132136Backwards incompatible changes in 1.5
    133137=====================================
    134138
  • docs/topics/db/queries.txt

    diff --git a/docs/topics/db/queries.txt b/docs/topics/db/queries.txt
    index dd16065..9a2544b 100644
    a b a join with an ``F()`` object, a ``FieldError`` will be raised:: 
    990990    # THIS WILL RAISE A FieldError
    991991    >>> Entry.objects.update(headline=F('blog__name'))
    992992
     993.. versionadded:: 1.5
     994
     995Django also supports the comparison operators ``==``, ``!=``, ``<=``, ``<``,
     996``>``, ``>=`` and the bitwise negation operator ``~`` (boolean ``not`` operator
     997will raise ``TypeError``)::
     998
     999    >>> Entry.objects.update(is_heavily_quoted=~(F('n_pingbacks') < 100))
     1000
    9931001.. _topics-db-queries-related:
    9941002
    9951003Related objects
  • tests/modeltests/expressions/models.py

    diff --git a/tests/modeltests/expressions/models.py b/tests/modeltests/expressions/models.py
    index f592a0e..15f0d24 100644
    a b class Company(models.Model): 
    2727        Employee,
    2828        related_name='company_point_of_contact_set',
    2929        null=True)
     30    is_large = models.BooleanField(
     31        blank=True)
    3032
    3133    def __str__(self):
    3234        return self.name
  • tests/modeltests/expressions/tests.py

    diff --git a/tests/modeltests/expressions/tests.py b/tests/modeltests/expressions/tests.py
    index 99eb07e..66407c7 100644
    a b from .models import Company, Employee 
    1111class ExpressionsTests(TestCase):
    1212    def test_filter(self):
    1313        Company.objects.create(
    14             name="Example Inc.", num_employees=2300, num_chairs=5,
     14            name="Example Inc.", num_employees=2300, num_chairs=5, is_large=False,
    1515            ceo=Employee.objects.create(firstname="Joe", lastname="Smith")
    1616        )
    1717        Company.objects.create(
    18             name="Foobar Ltd.", num_employees=3, num_chairs=4,
     18            name="Foobar Ltd.", num_employees=3, num_chairs=4, is_large=False,
    1919            ceo=Employee.objects.create(firstname="Frank", lastname="Meyer")
    2020        )
    2121        Company.objects.create(
    22             name="Test GmbH", num_employees=32, num_chairs=1,
     22            name="Test GmbH", num_employees=32, num_chairs=1, is_large=False,
    2323            ceo=Employee.objects.create(firstname="Max", lastname="Mustermann")
    2424        )
    2525
    2626        company_query = Company.objects.values(
    27             "name", "num_employees", "num_chairs"
     27            "name", "num_employees", "num_chairs", "is_large"
    2828        ).order_by(
    29             "name", "num_employees", "num_chairs"
     29            "name", "num_employees", "num_chairs", "is_large"
    3030        )
    3131
    3232        # We can filter for companies where the number of employees is greater
    class ExpressionsTests(TestCase): 
    3737                    "num_chairs": 5,
    3838                    "name": "Example Inc.",
    3939                    "num_employees": 2300,
     40                    "is_large": False
    4041                },
    4142                {
    4243                    "num_chairs": 1,
    4344                    "name": "Test GmbH",
    44                     "num_employees": 32
     45                    "num_employees": 32,
     46                    "is_large": False
    4547                },
    4648            ],
    4749            lambda o: o
    class ExpressionsTests(TestCase): 
    5557                {
    5658                    "num_chairs": 2300,
    5759                    "name": "Example Inc.",
    58                     "num_employees": 2300
     60                    "num_employees": 2300,
     61                    "is_large": False
    5962                },
    6063                {
    6164                    "num_chairs": 3,
    6265                    "name": "Foobar Ltd.",
    63                     "num_employees": 3
     66                    "num_employees": 3,
     67                    "is_large": False
    6468                },
    6569                {
    6670                    "num_chairs": 32,
    6771                    "name": "Test GmbH",
    68                     "num_employees": 32
     72                    "num_employees": 32,
     73                    "is_large": False
    6974                }
    7075            ],
    7176            lambda o: o
    class ExpressionsTests(TestCase): 
    7984                {
    8085                    'num_chairs': 2302,
    8186                    'name': 'Example Inc.',
    82                     'num_employees': 2300
     87                    'num_employees': 2300,
     88                    'is_large': False
    8389                },
    8490                {
    8591                    'num_chairs': 5,
    8692                    'name': 'Foobar Ltd.',
    87                     'num_employees': 3
     93                    'num_employees': 3,
     94                    'is_large': False
    8895                },
    8996                {
    9097                    'num_chairs': 34,
    9198                    'name': 'Test GmbH',
    92                     'num_employees': 32
     99                    'num_employees': 32,
     100                    'is_large': False
    93101                }
    94102            ],
    95103            lambda o: o,
    class ExpressionsTests(TestCase): 
    104112                {
    105113                    'num_chairs': 6900,
    106114                    'name': 'Example Inc.',
    107                     'num_employees': 2300
     115                    'num_employees': 2300,
     116                    'is_large': False
    108117                },
    109118                {
    110119                    'num_chairs': 9,
    111120                    'name': 'Foobar Ltd.',
    112                     'num_employees': 3
     121                    'num_employees': 3,
     122                    'is_large': False
    113123                },
    114124                {
    115125                    'num_chairs': 96,
    116126                    'name': 'Test GmbH',
    117                     'num_employees': 32
     127                    'num_employees': 32,
     128                    'is_large': False
    118129                }
    119130            ],
    120131            lambda o: o,
    class ExpressionsTests(TestCase): 
    129140                {
    130141                    'num_chairs': 5294600,
    131142                    'name': 'Example Inc.',
    132                     'num_employees': 2300
     143                    'num_employees': 2300,
     144                    'is_large': False
    133145                },
    134146                {
    135147                    'num_chairs': 15,
    136148                    'name': 'Foobar Ltd.',
    137                     'num_employees': 3
     149                    'num_employees': 3,
     150                    'is_large': False
    138151                },
    139152                {
    140153                    'num_chairs': 1088,
    141154                    'name': 'Test GmbH',
    142                     'num_employees': 32
     155                    'num_employees': 32,
     156                    'is_large': False
    143157                }
    144158            ],
    145159            lambda o: o,
    146160        )
     161        # The comparison operators and the bitwise unary not can be used
     162        # to assign to boolean fields
     163        for expression in (
     164            # Check boundaries
     165            ~(F('num_employees') < 33),
     166            ~(F('num_employees') <= 32),
     167            (F('num_employees') > 2299),
     168            (F('num_employees') >= 2300),
     169            (F('num_employees') == 2300),
     170            ((F('num_employees') + 1 != 4) & (32 != F('num_employees'))),
     171            # Inverted argument order works too
     172            (2299 < F('num_employees')),
     173            (2300 <= F('num_employees'))
     174        ):
     175            # Test update by F-expression
     176            company_query.update(
     177                is_large=expression
     178            )
     179            # Compare results
     180            self.assertQuerysetEqual(
     181                company_query, [
     182                    {
     183                        'num_chairs': 5294600,
     184                        'name': u'Example Inc.',
     185                        'num_employees': 2300,
     186                        'is_large': True
     187                    },
     188                    {
     189                        'num_chairs': 15,
     190                        'name': u'Foobar Ltd.',
     191                        'num_employees': 3,
     192                        'is_large': False
     193                    },
     194                    {
     195                        'num_chairs': 1088,
     196                        'name': u'Test GmbH',
     197                        'num_employees': 32,
     198                        'is_large': False
     199                    }
     200                ],
     201                lambda o: o,
     202            )
     203            # Reset values
     204            company_query.update(
     205                is_large=False
     206            )
     207
     208        # The python boolean operators should be avoided as they yield
     209        # unexpected results
     210        test_gmbh = Company.objects.get(name="Test GmbH")
     211        def test():
     212            test_gmbh.is_large = not F('is_large')
     213        self.assertRaises(TypeError, test)
     214        def test():
     215            test_gmbh.is_large = F('is_large') and F('is_large')
     216        self.assertRaises(TypeError, test)
     217        def test():
     218            test_gmbh.is_large = F('is_large') or F('is_large')
     219        self.assertRaises(TypeError, test)
    147220
    148221        # The relation of a foreign key can become copied over to an other
    149222        # foreign key.
Back to Top