Updated .only() behaviour - now like exclude it is chainable (#202)
This commit is contained in:
parent
81c7007f80
commit
e2f3406e89
@ -4,6 +4,7 @@ Changelog
|
|||||||
|
|
||||||
Changes in 0.8.X
|
Changes in 0.8.X
|
||||||
================
|
================
|
||||||
|
- Updated .only() behaviour - now like exclude it is chainable (#202)
|
||||||
- Added with_limit_and_skip support to count() (#235)
|
- Added with_limit_and_skip support to count() (#235)
|
||||||
- Removed __len__ from queryset (#247)
|
- Removed __len__ from queryset (#247)
|
||||||
- Objects queryset manager now inherited (#256)
|
- Objects queryset manager now inherited (#256)
|
||||||
|
@ -114,6 +114,21 @@ explicit `queryset.count()` to update::
|
|||||||
# New code
|
# New code
|
||||||
Animal.objects(type="mammal").count())
|
Animal.objects(type="mammal").count())
|
||||||
|
|
||||||
|
|
||||||
|
.only() now inline with .exclude()
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
The behaviour of `.only()` was highly ambious, now it works in the mirror fashion
|
||||||
|
to `.exclude()`. Chaining `.only()` calls will increase the fields required::
|
||||||
|
|
||||||
|
# Old code
|
||||||
|
Animal.objects().only(['type', 'name']).only('name', 'order') # Would have returned just `name`
|
||||||
|
|
||||||
|
# New code
|
||||||
|
Animal.objects().only('name')
|
||||||
|
Animal.objects().only(['name']).only('order') # Would return `name` and `order`
|
||||||
|
|
||||||
|
|
||||||
Client
|
Client
|
||||||
======
|
======
|
||||||
PyMongo 2.4 came with a new connection client; MongoClient_ and started the
|
PyMongo 2.4 came with a new connection client; MongoClient_ and started the
|
||||||
|
@ -7,11 +7,20 @@ class QueryFieldList(object):
|
|||||||
ONLY = 1
|
ONLY = 1
|
||||||
EXCLUDE = 0
|
EXCLUDE = 0
|
||||||
|
|
||||||
def __init__(self, fields=[], value=ONLY, always_include=[]):
|
def __init__(self, fields=None, value=ONLY, always_include=None, _only_called=False):
|
||||||
|
"""The QueryFieldList builder
|
||||||
|
|
||||||
|
:param fields: A list of fields used in `.only()` or `.exclude()`
|
||||||
|
:param value: How to handle the fields; either `ONLY` or `EXCLUDE`
|
||||||
|
:param always_include: Any fields to always_include eg `_cls`
|
||||||
|
:param _only_called: Has `.only()` been called? If so its a set of fields
|
||||||
|
otherwise it performs a union.
|
||||||
|
"""
|
||||||
self.value = value
|
self.value = value
|
||||||
self.fields = set(fields)
|
self.fields = set(fields or [])
|
||||||
self.always_include = set(always_include)
|
self.always_include = set(always_include or [])
|
||||||
self._id = None
|
self._id = None
|
||||||
|
self._only_called = _only_called
|
||||||
self.slice = {}
|
self.slice = {}
|
||||||
|
|
||||||
def __add__(self, f):
|
def __add__(self, f):
|
||||||
@ -26,7 +35,10 @@ class QueryFieldList(object):
|
|||||||
self.slice = {}
|
self.slice = {}
|
||||||
elif self.value is self.ONLY and f.value is self.ONLY:
|
elif self.value is self.ONLY and f.value is self.ONLY:
|
||||||
self._clean_slice()
|
self._clean_slice()
|
||||||
self.fields = self.fields.intersection(f.fields)
|
if self._only_called:
|
||||||
|
self.fields = self.fields.union(f.fields)
|
||||||
|
else:
|
||||||
|
self.fields = f.fields
|
||||||
elif self.value is self.EXCLUDE and f.value is self.EXCLUDE:
|
elif self.value is self.EXCLUDE and f.value is self.EXCLUDE:
|
||||||
self.fields = self.fields.union(f.fields)
|
self.fields = self.fields.union(f.fields)
|
||||||
self._clean_slice()
|
self._clean_slice()
|
||||||
@ -46,6 +58,9 @@ class QueryFieldList(object):
|
|||||||
self.fields = self.fields.union(self.always_include)
|
self.fields = self.fields.union(self.always_include)
|
||||||
else:
|
else:
|
||||||
self.fields -= self.always_include
|
self.fields -= self.always_include
|
||||||
|
|
||||||
|
if getattr(f, '_only_called', False):
|
||||||
|
self._only_called = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __nonzero__(self):
|
def __nonzero__(self):
|
||||||
|
@ -624,19 +624,35 @@ class QuerySet(object):
|
|||||||
|
|
||||||
post = BlogPost.objects(...).only("title", "author.name")
|
post = BlogPost.objects(...).only("title", "author.name")
|
||||||
|
|
||||||
|
.. note :: `only()` is chainable and will perform a union ::
|
||||||
|
So with the following it will fetch both: `title` and `author.name`::
|
||||||
|
|
||||||
|
post = BlogPost.objects.only("title").only("author.name")
|
||||||
|
|
||||||
|
:func:`~mongoengine.queryset.QuerySet.all_fields` will reset any
|
||||||
|
field filters.
|
||||||
|
|
||||||
:param fields: fields to include
|
:param fields: fields to include
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
.. versionchanged:: 0.5 - Added subfield support
|
.. versionchanged:: 0.5 - Added subfield support
|
||||||
"""
|
"""
|
||||||
fields = dict([(f, QueryFieldList.ONLY) for f in fields])
|
fields = dict([(f, QueryFieldList.ONLY) for f in fields])
|
||||||
return self.fields(**fields)
|
return self.fields(True, **fields)
|
||||||
|
|
||||||
def exclude(self, *fields):
|
def exclude(self, *fields):
|
||||||
"""Opposite to .only(), exclude some document's fields. ::
|
"""Opposite to .only(), exclude some document's fields. ::
|
||||||
|
|
||||||
post = BlogPost.objects(...).exclude("comments")
|
post = BlogPost.objects(...).exclude("comments")
|
||||||
|
|
||||||
|
.. note :: `exclude()` is chainable and will perform a union ::
|
||||||
|
So with the following it will exclude both: `title` and `author.name`::
|
||||||
|
|
||||||
|
post = BlogPost.objects.exclude("title").exclude("author.name")
|
||||||
|
|
||||||
|
:func:`~mongoengine.queryset.QuerySet.all_fields` will reset any
|
||||||
|
field filters.
|
||||||
|
|
||||||
:param fields: fields to exclude
|
:param fields: fields to exclude
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
@ -644,7 +660,7 @@ class QuerySet(object):
|
|||||||
fields = dict([(f, QueryFieldList.EXCLUDE) for f in fields])
|
fields = dict([(f, QueryFieldList.EXCLUDE) for f in fields])
|
||||||
return self.fields(**fields)
|
return self.fields(**fields)
|
||||||
|
|
||||||
def fields(self, **kwargs):
|
def fields(self, _only_called=False, **kwargs):
|
||||||
"""Manipulate how you load this document's fields. Used by `.only()`
|
"""Manipulate how you load this document's fields. Used by `.only()`
|
||||||
and `.exclude()` to manipulate which fields to retrieve. Fields also
|
and `.exclude()` to manipulate which fields to retrieve. Fields also
|
||||||
allows for a greater level of control for example:
|
allows for a greater level of control for example:
|
||||||
@ -678,7 +694,8 @@ class QuerySet(object):
|
|||||||
for value, group in itertools.groupby(fields, lambda x: x[1]):
|
for value, group in itertools.groupby(fields, lambda x: x[1]):
|
||||||
fields = [field for field, value in group]
|
fields = [field for field, value in group]
|
||||||
fields = queryset._fields_to_dbfields(fields)
|
fields = queryset._fields_to_dbfields(fields)
|
||||||
queryset._loaded_fields += QueryFieldList(fields, value=value)
|
queryset._loaded_fields += QueryFieldList(fields, value=value, _only_called=_only_called)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def all_fields(self):
|
def all_fields(self):
|
||||||
|
@ -20,47 +20,47 @@ class QueryFieldListTest(unittest.TestCase):
|
|||||||
|
|
||||||
def test_include_include(self):
|
def test_include_include(self):
|
||||||
q = QueryFieldList()
|
q = QueryFieldList()
|
||||||
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY, _only_called=True)
|
||||||
self.assertEqual(q.as_dict(), {'a': True, 'b': True})
|
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1})
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
||||||
self.assertEqual(q.as_dict(), {'b': True})
|
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1, 'c': 1})
|
||||||
|
|
||||||
def test_include_exclude(self):
|
def test_include_exclude(self):
|
||||||
q = QueryFieldList()
|
q = QueryFieldList()
|
||||||
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY)
|
||||||
self.assertEqual(q.as_dict(), {'a': True, 'b': True})
|
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1})
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
|
||||||
self.assertEqual(q.as_dict(), {'a': True})
|
self.assertEqual(q.as_dict(), {'a': 1})
|
||||||
|
|
||||||
def test_exclude_exclude(self):
|
def test_exclude_exclude(self):
|
||||||
q = QueryFieldList()
|
q = QueryFieldList()
|
||||||
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
|
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
|
||||||
self.assertEqual(q.as_dict(), {'a': False, 'b': False})
|
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0})
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
|
||||||
self.assertEqual(q.as_dict(), {'a': False, 'b': False, 'c': False})
|
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0, 'c': 0})
|
||||||
|
|
||||||
def test_exclude_include(self):
|
def test_exclude_include(self):
|
||||||
q = QueryFieldList()
|
q = QueryFieldList()
|
||||||
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
|
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
|
||||||
self.assertEqual(q.as_dict(), {'a': False, 'b': False})
|
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0})
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
||||||
self.assertEqual(q.as_dict(), {'c': True})
|
self.assertEqual(q.as_dict(), {'c': 1})
|
||||||
|
|
||||||
def test_always_include(self):
|
def test_always_include(self):
|
||||||
q = QueryFieldList(always_include=['x', 'y'])
|
q = QueryFieldList(always_include=['x', 'y'])
|
||||||
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
|
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
||||||
self.assertEqual(q.as_dict(), {'x': True, 'y': True, 'c': True})
|
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'c': 1})
|
||||||
|
|
||||||
def test_reset(self):
|
def test_reset(self):
|
||||||
q = QueryFieldList(always_include=['x', 'y'])
|
q = QueryFieldList(always_include=['x', 'y'])
|
||||||
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
|
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
||||||
self.assertEqual(q.as_dict(), {'x': True, 'y': True, 'c': True})
|
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'c': 1})
|
||||||
q.reset()
|
q.reset()
|
||||||
self.assertFalse(q)
|
self.assertFalse(q)
|
||||||
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
|
||||||
self.assertEqual(q.as_dict(), {'x': True, 'y': True, 'b': True, 'c': True})
|
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'b': 1, 'c': 1})
|
||||||
|
|
||||||
def test_using_a_slice(self):
|
def test_using_a_slice(self):
|
||||||
q = QueryFieldList()
|
q = QueryFieldList()
|
||||||
@ -328,7 +328,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
|
|
||||||
Numbers.drop_collection()
|
Numbers.drop_collection()
|
||||||
|
|
||||||
numbers = Numbers(n=[0,1,2,3,4,5,-5,-4,-3,-2,-1])
|
numbers = Numbers(n=[0, 1, 2, 3, 4, 5, -5, -4, -3, -2, -1])
|
||||||
numbers.save()
|
numbers.save()
|
||||||
|
|
||||||
# first three
|
# first three
|
||||||
@ -368,7 +368,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
Numbers.drop_collection()
|
Numbers.drop_collection()
|
||||||
|
|
||||||
numbers = Numbers()
|
numbers = Numbers()
|
||||||
numbers.embedded = EmbeddedNumber(n=[0,1,2,3,4,5,-5,-4,-3,-2,-1])
|
numbers.embedded = EmbeddedNumber(n=[0, 1, 2, 3, 4, 5, -5, -4, -3, -2, -1])
|
||||||
numbers.save()
|
numbers.save()
|
||||||
|
|
||||||
# first three
|
# first three
|
||||||
|
Loading…
x
Reference in New Issue
Block a user