Fixed #29125 -- Made Q.deconstruct() deterministic with multiple keyword arguments.

This commit is contained in:
Tim Graham 2018-02-12 14:00:29 -05:00
parent a6fb81750a
commit b95c49c954
3 changed files with 14 additions and 1 deletions

View File

@ -58,7 +58,7 @@ class Q(tree.Node):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
connector = kwargs.pop('_connector', None) connector = kwargs.pop('_connector', None)
negated = kwargs.pop('_negated', False) negated = kwargs.pop('_negated', False)
super().__init__(children=list(args) + list(kwargs.items()), connector=connector, negated=negated) super().__init__(children=list(args) + sorted(kwargs.items()), connector=connector, negated=negated)
def _combine(self, other, conn): def _combine(self, other, conn):
if not isinstance(other, Q): if not isinstance(other, Q):

View File

@ -17,3 +17,7 @@ Bugfixes
(:ticket:`29109`). (:ticket:`29109`).
* Fixed crash with ``QuerySet.order_by(Exists(...))`` (:ticket:`29118`). * Fixed crash with ``QuerySet.order_by(Exists(...))`` (:ticket:`29118`).
* Made ``Q.deconstruct()`` deterministic with multiple keyword arguments
(:ticket:`29125`). You may need to modify ``Q``'s in existing migrations, or
accept an autogenerated migration.

View File

@ -60,6 +60,15 @@ class QTests(SimpleTestCase):
)) ))
self.assertEqual(kwargs, {'_connector': 'AND'}) self.assertEqual(kwargs, {'_connector': 'AND'})
def test_deconstruct_multiple_kwargs(self):
q = Q(price__gt=F('discounted_price'), price=F('discounted_price'))
path, args, kwargs = q.deconstruct()
self.assertEqual(args, (
('price', F('discounted_price')),
('price__gt', F('discounted_price')),
))
self.assertEqual(kwargs, {'_connector': 'AND'})
def test_deconstruct_nested(self): def test_deconstruct_nested(self):
q = Q(Q(price__gt=F('discounted_price'))) q = Q(Q(price__gt=F('discounted_price')))
path, args, kwargs = q.deconstruct() path, args, kwargs = q.deconstruct()