Compare commits
1 Commits
container-
...
insert-con
Author | SHA1 | Date | |
---|---|---|---|
|
98e1df0c45 |
54
.travis.yml
54
.travis.yml
@@ -1,6 +1,3 @@
|
|||||||
# Use a container-based environment
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
language: python
|
language: python
|
||||||
|
|
||||||
python:
|
python:
|
||||||
@@ -11,41 +8,26 @@ python:
|
|||||||
- pypy
|
- pypy
|
||||||
- pypy3
|
- pypy3
|
||||||
|
|
||||||
# Test on PyMongo v2.7.x, v2.8.x, and v3.x
|
|
||||||
env:
|
env:
|
||||||
- PYMONGO=2.7
|
- PYMONGO=2.7
|
||||||
- PYMONGO=2.8
|
- PYMONGO=2.8
|
||||||
- PYMONGO=3.0
|
- PYMONGO=3.0
|
||||||
|
- PYMONGO=dev
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
fast_finish: true
|
fast_finish: true
|
||||||
|
|
||||||
services:
|
before_install:
|
||||||
- mongodb
|
- travis_retry sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
||||||
|
- echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' |
|
||||||
addons:
|
sudo tee /etc/apt/sources.list.d/mongodb.list
|
||||||
apt:
|
- travis_retry sudo apt-get update
|
||||||
sources:
|
- travis_retry sudo apt-get install mongodb-org-server
|
||||||
- mongodb-upstart
|
|
||||||
packages:
|
|
||||||
- mongodb-org-server=2.6.9
|
|
||||||
|
|
||||||
# Optional dependencies for the ImageField and others
|
|
||||||
- python-dev
|
|
||||||
- python3-dev
|
|
||||||
- libopenjpeg-dev
|
|
||||||
- zlib1g-dev
|
|
||||||
- libjpeg-turbo8-dev
|
|
||||||
- libtiff4-dev
|
|
||||||
- libjpeg8-dev
|
|
||||||
- libfreetype6-dev
|
|
||||||
- liblcms2-dev
|
|
||||||
- libwebp-dev
|
|
||||||
- tcl8.5-dev
|
|
||||||
- tk8.5-dev
|
|
||||||
- python-tk
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
|
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
||||||
|
libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev
|
||||||
|
python-tk
|
||||||
- travis_retry pip install --upgrade pip
|
- travis_retry pip install --upgrade pip
|
||||||
- travis_retry pip install coveralls
|
- travis_retry pip install coveralls
|
||||||
- travis_retry pip install flake8
|
- travis_retry pip install flake8
|
||||||
@@ -53,9 +35,6 @@ install:
|
|||||||
- travis_retry pip install "virtualenv<14.0.0" # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
|
- travis_retry pip install "virtualenv<14.0.0" # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
|
||||||
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
|
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
|
||||||
|
|
||||||
# Cache dependencies installed via pip
|
|
||||||
cache: pip
|
|
||||||
|
|
||||||
# Run flake8 for py27
|
# Run flake8 for py27
|
||||||
before_script:
|
before_script:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then tox -e flake8; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then tox -e flake8; fi
|
||||||
@@ -66,7 +45,7 @@ script:
|
|||||||
# For now only submit coveralls for Python v2.7. Python v3.x currently shows
|
# For now only submit coveralls for Python v2.7. Python v3.x currently shows
|
||||||
# 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible
|
# 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible
|
||||||
# code in a separate dir and runs tests on that.
|
# code in a separate dir and runs tests on that.
|
||||||
after_success:
|
after_script:
|
||||||
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi
|
||||||
|
|
||||||
notifications:
|
notifications:
|
||||||
@@ -77,22 +56,11 @@ branches:
|
|||||||
- master
|
- master
|
||||||
- /^v.*$/
|
- /^v.*$/
|
||||||
|
|
||||||
# Whenever a new release is created via GitHub, publish it on PyPI.
|
|
||||||
deploy:
|
deploy:
|
||||||
provider: pypi
|
provider: pypi
|
||||||
user: the_drow
|
user: the_drow
|
||||||
password:
|
password:
|
||||||
secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
|
secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
|
||||||
|
|
||||||
# create a source distribution and a pure python wheel for faster installs
|
|
||||||
distributions: "sdist bdist_wheel"
|
|
||||||
|
|
||||||
# only deploy on tagged commits (aka GitHub releases) and only for the
|
|
||||||
# parent repo's builds running Python 2.7 along with dev PyMongo (we run
|
|
||||||
# Travis against many different Python and PyMongo versions and we don't
|
|
||||||
# want the deploy to occur multiple times).
|
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
repo: MongoEngine/mongoengine
|
repo: MongoEngine/mongoengine
|
||||||
condition: "$PYMONGO = 3.0"
|
|
||||||
python: 2.7
|
|
||||||
|
18
README.rst
18
README.rst
@@ -35,22 +35,16 @@ setup.py install``.
|
|||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
============
|
============
|
||||||
All of the dependencies can easily be installed via `pip <https://pip.pypa.io/>`_. At the very least, you'll need these two packages to use MongoEngine:
|
|
||||||
|
|
||||||
- pymongo>=2.7.1
|
- pymongo>=2.7.1
|
||||||
- six>=1.10.0
|
- sphinx (optional - for documentation generation)
|
||||||
|
|
||||||
If you utilize a ``DateTimeField``, you might also use a more flexible date parser:
|
|
||||||
|
|
||||||
|
Optional Dependencies
|
||||||
|
---------------------
|
||||||
|
- **Image Fields**: Pillow>=2.0.0
|
||||||
- dateutil>=2.1.0
|
- dateutil>=2.1.0
|
||||||
|
|
||||||
If you need to use an ``ImageField`` or ``ImageGridFsProxy``:
|
.. note
|
||||||
|
MongoEngine always runs it's test suite against the latest patch version of each dependecy. e.g.: PyMongo 3.0.1
|
||||||
- Pillow>=2.0.0
|
|
||||||
|
|
||||||
If you want to generate the documentation (e.g. to contribute to it):
|
|
||||||
|
|
||||||
- sphinx
|
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
========
|
========
|
||||||
|
@@ -5,8 +5,6 @@ Changelog
|
|||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
- (Fill this out as you fix issues and develop you features).
|
- (Fill this out as you fix issues and develop you features).
|
||||||
- POTENTIAL BREAKING CHANGE: Fixed limit/skip/hint/batch_size chaining #1476
|
|
||||||
- POTENTIAL BREAKING CHANGE: Changed a public `QuerySet.clone_into` method to a private `QuerySet._clone_into` #1476
|
|
||||||
- Fixed connecting to a replica set with PyMongo 2.x #1436
|
- Fixed connecting to a replica set with PyMongo 2.x #1436
|
||||||
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
|
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
|
||||||
|
|
||||||
|
@@ -361,6 +361,11 @@ Its value can take any of the following constants:
|
|||||||
In Django, be sure to put all apps that have such delete rule declarations in
|
In Django, be sure to put all apps that have such delete rule declarations in
|
||||||
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
|
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
|
||||||
|
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Signals are not triggered when doing cascading updates / deletes - if this
|
||||||
|
is required you must manually handle the update / delete.
|
||||||
|
|
||||||
Generic reference fields
|
Generic reference fields
|
||||||
''''''''''''''''''''''''
|
''''''''''''''''''''''''
|
||||||
A second kind of reference field also exists,
|
A second kind of reference field also exists,
|
||||||
|
@@ -142,4 +142,11 @@ cleaner looking while still allowing manual execution of the callback::
|
|||||||
modified = DateTimeField()
|
modified = DateTimeField()
|
||||||
|
|
||||||
|
|
||||||
|
ReferenceFields and Signals
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Currently `reverse_delete_rule` does not trigger signals on the other part of
|
||||||
|
the relationship. If this is required you must manually handle the
|
||||||
|
reverse deletion.
|
||||||
|
|
||||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||||
|
@@ -2,20 +2,6 @@
|
|||||||
Upgrading
|
Upgrading
|
||||||
#########
|
#########
|
||||||
|
|
||||||
Development
|
|
||||||
***********
|
|
||||||
(Fill this out whenever you introduce breaking changes to MongoEngine)
|
|
||||||
|
|
||||||
This release includes various fixes for the `BaseQuerySet` methods and how they
|
|
||||||
are chained together. Since version 0.10.1 applying limit/skip/hint/batch_size
|
|
||||||
to an already-existing queryset wouldn't modify the underlying PyMongo cursor.
|
|
||||||
This has been fixed now, so you'll need to make sure that your code didn't rely
|
|
||||||
on the broken implementation.
|
|
||||||
|
|
||||||
Additionally, a public `BaseQuerySet.clone_into` has been renamed to a private
|
|
||||||
`_clone_into`. If you directly used that method in your code, you'll need to
|
|
||||||
rename its occurrences.
|
|
||||||
|
|
||||||
0.11.0
|
0.11.0
|
||||||
******
|
******
|
||||||
This release includes a major rehaul of MongoEngine's code quality and
|
This release includes a major rehaul of MongoEngine's code quality and
|
||||||
|
@@ -888,6 +888,10 @@ class ReferenceField(BaseField):
|
|||||||
|
|
||||||
Foo.register_delete_rule(Bar, 'foo', NULLIFY)
|
Foo.register_delete_rule(Bar, 'foo', NULLIFY)
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
`reverse_delete_rule` does not trigger pre / post delete signals to be
|
||||||
|
triggered.
|
||||||
|
|
||||||
.. versionchanged:: 0.5 added `reverse_delete_rule`
|
.. versionchanged:: 0.5 added `reverse_delete_rule`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@@ -86,7 +86,6 @@ class BaseQuerySet(object):
|
|||||||
self._batch_size = None
|
self._batch_size = None
|
||||||
self.only_fields = []
|
self.only_fields = []
|
||||||
self._max_time_ms = None
|
self._max_time_ms = None
|
||||||
self._comment = None
|
|
||||||
|
|
||||||
def __call__(self, q_obj=None, class_check=True, read_preference=None,
|
def __call__(self, q_obj=None, class_check=True, read_preference=None,
|
||||||
**query):
|
**query):
|
||||||
@@ -297,22 +296,25 @@ class BaseQuerySet(object):
|
|||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def insert(self, doc_or_docs, load_bulk=True,
|
def insert(self, doc_or_docs, load_bulk=True, write_concern=None,
|
||||||
write_concern=None, signal_kwargs=None):
|
signal_kwargs=None, continue_on_error=None):
|
||||||
"""bulk insert documents
|
"""bulk insert documents
|
||||||
|
|
||||||
:param doc_or_docs: a document or list of documents to be inserted
|
:param doc_or_docs: a document or list of documents to be inserted
|
||||||
:param load_bulk (optional): If True returns the list of document
|
:param load_bulk (optional): If True returns the list of document
|
||||||
instances
|
instances
|
||||||
:param write_concern: Extra keyword arguments are passed down to
|
:param write_concern: Optional keyword argument passed down to
|
||||||
:meth:`~pymongo.collection.Collection.insert`
|
:meth:`~pymongo.collection.Collection.insert`, representing
|
||||||
which will be used as options for the resultant
|
the write concern. For example,
|
||||||
``getLastError`` command. For example,
|
``insert(..., write_concert={w: 2, fsync: True})`` will
|
||||||
``insert(..., {w: 2, fsync: True})`` will wait until at least
|
wait until at least two servers have recorded the write
|
||||||
two servers have recorded the write and will force an fsync on
|
and will force an fsync on each server being written to.
|
||||||
each server being written to.
|
|
||||||
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
|
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
|
||||||
the signal calls.
|
the signal calls.
|
||||||
|
:param continue_on_error: Optional keyword argument passed down to
|
||||||
|
:meth:`~pymongo.collection.Collection.insert`. Defines what
|
||||||
|
to do when a document cannot be inserted (e.g. due to
|
||||||
|
duplicate IDs). Read PyMongo's docs for more info.
|
||||||
|
|
||||||
By default returns document instances, set ``load_bulk`` to False to
|
By default returns document instances, set ``load_bulk`` to False to
|
||||||
return just ``ObjectIds``
|
return just ``ObjectIds``
|
||||||
@@ -323,12 +325,10 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
Document = _import_class('Document')
|
Document = _import_class('Document')
|
||||||
|
|
||||||
if write_concern is None:
|
# Determine if we're inserting one doc or more
|
||||||
write_concern = {}
|
|
||||||
|
|
||||||
docs = doc_or_docs
|
docs = doc_or_docs
|
||||||
return_one = False
|
return_one = False
|
||||||
if isinstance(docs, Document) or issubclass(docs.__class__, Document):
|
if isinstance(docs, Document):
|
||||||
return_one = True
|
return_one = True
|
||||||
docs = [docs]
|
docs = [docs]
|
||||||
|
|
||||||
@@ -345,9 +345,16 @@ class BaseQuerySet(object):
|
|||||||
signals.pre_bulk_insert.send(self._document,
|
signals.pre_bulk_insert.send(self._document,
|
||||||
documents=docs, **signal_kwargs)
|
documents=docs, **signal_kwargs)
|
||||||
|
|
||||||
|
# Resolve optional insert kwargs
|
||||||
|
insert_kwargs = {}
|
||||||
|
if write_concern is not None:
|
||||||
|
insert_kwargs.update(write_concern)
|
||||||
|
if continue_on_error is not None:
|
||||||
|
insert_kwargs['continue_on_error'] = continue_on_error
|
||||||
|
|
||||||
raw = [doc.to_mongo() for doc in docs]
|
raw = [doc.to_mongo() for doc in docs]
|
||||||
try:
|
try:
|
||||||
ids = self._collection.insert(raw, **write_concern)
|
ids = self._collection.insert(raw, **insert_kwargs)
|
||||||
except pymongo.errors.DuplicateKeyError as err:
|
except pymongo.errors.DuplicateKeyError as err:
|
||||||
message = 'Could not save document (%s)'
|
message = 'Could not save document (%s)'
|
||||||
raise NotUniqueError(message % six.text_type(err))
|
raise NotUniqueError(message % six.text_type(err))
|
||||||
@@ -707,36 +714,39 @@ class BaseQuerySet(object):
|
|||||||
with switch_db(self._document, alias) as cls:
|
with switch_db(self._document, alias) as cls:
|
||||||
collection = cls._get_collection()
|
collection = cls._get_collection()
|
||||||
|
|
||||||
return self._clone_into(self.__class__(self._document, collection))
|
return self.clone_into(self.__class__(self._document, collection))
|
||||||
|
|
||||||
def clone(self):
|
def clone(self):
|
||||||
"""Create a copy of the current queryset."""
|
"""Creates a copy of the current
|
||||||
return self._clone_into(self.__class__(self._document, self._collection_obj))
|
:class:`~mongoengine.queryset.QuerySet`
|
||||||
|
|
||||||
def _clone_into(self, new_qs):
|
.. versionadded:: 0.5
|
||||||
"""Copy all of the relevant properties of this queryset to
|
|
||||||
a new queryset (which has to be an instance of
|
|
||||||
:class:`~mongoengine.queryset.base.BaseQuerySet`).
|
|
||||||
"""
|
"""
|
||||||
if not isinstance(new_qs, BaseQuerySet):
|
return self.clone_into(self.__class__(self._document, self._collection_obj))
|
||||||
|
|
||||||
|
def clone_into(self, cls):
|
||||||
|
"""Creates a copy of the current
|
||||||
|
:class:`~mongoengine.queryset.base.BaseQuerySet` into another child class
|
||||||
|
"""
|
||||||
|
if not isinstance(cls, BaseQuerySet):
|
||||||
raise OperationError(
|
raise OperationError(
|
||||||
'%s is not a subclass of BaseQuerySet' % new_qs.__name__)
|
'%s is not a subclass of BaseQuerySet' % cls.__name__)
|
||||||
|
|
||||||
copy_props = ('_mongo_query', '_initial_query', '_none', '_query_obj',
|
copy_props = ('_mongo_query', '_initial_query', '_none', '_query_obj',
|
||||||
'_where_clause', '_loaded_fields', '_ordering', '_snapshot',
|
'_where_clause', '_loaded_fields', '_ordering', '_snapshot',
|
||||||
'_timeout', '_class_check', '_slave_okay', '_read_preference',
|
'_timeout', '_class_check', '_slave_okay', '_read_preference',
|
||||||
'_iter', '_scalar', '_as_pymongo', '_as_pymongo_coerce',
|
'_iter', '_scalar', '_as_pymongo', '_as_pymongo_coerce',
|
||||||
'_limit', '_skip', '_hint', '_auto_dereference',
|
'_limit', '_skip', '_hint', '_auto_dereference',
|
||||||
'_search_text', 'only_fields', '_max_time_ms', '_comment')
|
'_search_text', 'only_fields', '_max_time_ms')
|
||||||
|
|
||||||
for prop in copy_props:
|
for prop in copy_props:
|
||||||
val = getattr(self, prop)
|
val = getattr(self, prop)
|
||||||
setattr(new_qs, prop, copy.copy(val))
|
setattr(cls, prop, copy.copy(val))
|
||||||
|
|
||||||
if self._cursor_obj:
|
if self._cursor_obj:
|
||||||
new_qs._cursor_obj = self._cursor_obj.clone()
|
cls._cursor_obj = self._cursor_obj.clone()
|
||||||
|
|
||||||
return new_qs
|
return cls
|
||||||
|
|
||||||
def select_related(self, max_depth=1):
|
def select_related(self, max_depth=1):
|
||||||
"""Handles dereferencing of :class:`~bson.dbref.DBRef` objects or
|
"""Handles dereferencing of :class:`~bson.dbref.DBRef` objects or
|
||||||
@@ -758,11 +768,7 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._limit = n if n != 0 else 1
|
queryset._limit = n if n != 0 else 1
|
||||||
|
# Return self to allow chaining
|
||||||
# If a cursor object has already been created, apply the limit to it.
|
|
||||||
if queryset._cursor_obj:
|
|
||||||
queryset._cursor_obj.limit(queryset._limit)
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def skip(self, n):
|
def skip(self, n):
|
||||||
@@ -773,11 +779,6 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._skip = n
|
queryset._skip = n
|
||||||
|
|
||||||
# If a cursor object has already been created, apply the skip to it.
|
|
||||||
if queryset._cursor_obj:
|
|
||||||
queryset._cursor_obj.skip(queryset._skip)
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def hint(self, index=None):
|
def hint(self, index=None):
|
||||||
@@ -795,11 +796,6 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._hint = index
|
queryset._hint = index
|
||||||
|
|
||||||
# If a cursor object has already been created, apply the hint to it.
|
|
||||||
if queryset._cursor_obj:
|
|
||||||
queryset._cursor_obj.hint(queryset._hint)
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def batch_size(self, size):
|
def batch_size(self, size):
|
||||||
@@ -813,11 +809,6 @@ class BaseQuerySet(object):
|
|||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._batch_size = size
|
queryset._batch_size = size
|
||||||
|
|
||||||
# If a cursor object has already been created, apply the batch size to it.
|
|
||||||
if queryset._cursor_obj:
|
|
||||||
queryset._cursor_obj.batch_size(queryset._batch_size)
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def distinct(self, field):
|
def distinct(self, field):
|
||||||
@@ -989,31 +980,13 @@ class BaseQuerySet(object):
|
|||||||
def order_by(self, *keys):
|
def order_by(self, *keys):
|
||||||
"""Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The
|
"""Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The
|
||||||
order may be specified by prepending each of the keys by a + or a -.
|
order may be specified by prepending each of the keys by a + or a -.
|
||||||
Ascending order is assumed. If no keys are passed, existing ordering
|
Ascending order is assumed.
|
||||||
is cleared instead.
|
|
||||||
|
|
||||||
:param keys: fields to order the query results by; keys may be
|
:param keys: fields to order the query results by; keys may be
|
||||||
prefixed with **+** or **-** to determine the ordering direction
|
prefixed with **+** or **-** to determine the ordering direction
|
||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
|
queryset._ordering = queryset._get_order_by(keys)
|
||||||
old_ordering = queryset._ordering
|
|
||||||
new_ordering = queryset._get_order_by(keys)
|
|
||||||
|
|
||||||
if queryset._cursor_obj:
|
|
||||||
|
|
||||||
# If a cursor object has already been created, apply the sort to it
|
|
||||||
if new_ordering:
|
|
||||||
queryset._cursor_obj.sort(new_ordering)
|
|
||||||
|
|
||||||
# If we're trying to clear a previous explicit ordering, we need
|
|
||||||
# to clear the cursor entirely (because PyMongo doesn't allow
|
|
||||||
# clearing an existing sort on a cursor).
|
|
||||||
elif old_ordering:
|
|
||||||
queryset._cursor_obj = None
|
|
||||||
|
|
||||||
queryset._ordering = new_ordering
|
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def comment(self, text):
|
def comment(self, text):
|
||||||
@@ -1459,13 +1432,10 @@ class BaseQuerySet(object):
|
|||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
raw_doc = self._cursor.next()
|
raw_doc = self._cursor.next()
|
||||||
|
|
||||||
if self._as_pymongo:
|
if self._as_pymongo:
|
||||||
return self._get_as_pymongo(raw_doc)
|
return self._get_as_pymongo(raw_doc)
|
||||||
|
doc = self._document._from_son(raw_doc,
|
||||||
doc = self._document._from_son(
|
_auto_dereference=self._auto_dereference, only_fields=self.only_fields)
|
||||||
raw_doc, _auto_dereference=self._auto_dereference,
|
|
||||||
only_fields=self.only_fields)
|
|
||||||
|
|
||||||
if self._scalar:
|
if self._scalar:
|
||||||
return self._get_scalar(doc)
|
return self._get_scalar(doc)
|
||||||
@@ -1475,6 +1445,7 @@ class BaseQuerySet(object):
|
|||||||
def rewind(self):
|
def rewind(self):
|
||||||
"""Rewind the cursor to its unevaluated state.
|
"""Rewind the cursor to its unevaluated state.
|
||||||
|
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
self._iter = False
|
self._iter = False
|
||||||
@@ -1524,54 +1495,43 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def _cursor(self):
|
def _cursor(self):
|
||||||
"""Return a PyMongo cursor object corresponding to this queryset."""
|
if self._cursor_obj is None:
|
||||||
|
|
||||||
# If _cursor_obj already exists, return it immediately.
|
# In PyMongo 3+, we define the read preference on a collection
|
||||||
if self._cursor_obj is not None:
|
# level, not a cursor level. Thus, we need to get a cloned
|
||||||
return self._cursor_obj
|
# collection object using `with_options` first.
|
||||||
|
if IS_PYMONGO_3 and self._read_preference is not None:
|
||||||
|
self._cursor_obj = self._collection\
|
||||||
|
.with_options(read_preference=self._read_preference)\
|
||||||
|
.find(self._query, **self._cursor_args)
|
||||||
|
else:
|
||||||
|
self._cursor_obj = self._collection.find(self._query,
|
||||||
|
**self._cursor_args)
|
||||||
|
# Apply where clauses to cursor
|
||||||
|
if self._where_clause:
|
||||||
|
where_clause = self._sub_js_fields(self._where_clause)
|
||||||
|
self._cursor_obj.where(where_clause)
|
||||||
|
|
||||||
# Create a new PyMongo cursor.
|
if self._ordering:
|
||||||
# XXX In PyMongo 3+, we define the read preference on a collection
|
# Apply query ordering
|
||||||
# level, not a cursor level. Thus, we need to get a cloned collection
|
self._cursor_obj.sort(self._ordering)
|
||||||
# object using `with_options` first.
|
elif self._ordering is None and self._document._meta['ordering']:
|
||||||
if IS_PYMONGO_3 and self._read_preference is not None:
|
# Otherwise, apply the ordering from the document model, unless
|
||||||
self._cursor_obj = self._collection\
|
# it's been explicitly cleared via order_by with no arguments
|
||||||
.with_options(read_preference=self._read_preference)\
|
order = self._get_order_by(self._document._meta['ordering'])
|
||||||
.find(self._query, **self._cursor_args)
|
self._cursor_obj.sort(order)
|
||||||
else:
|
|
||||||
self._cursor_obj = self._collection.find(self._query,
|
|
||||||
**self._cursor_args)
|
|
||||||
# Apply "where" clauses to cursor
|
|
||||||
if self._where_clause:
|
|
||||||
where_clause = self._sub_js_fields(self._where_clause)
|
|
||||||
self._cursor_obj.where(where_clause)
|
|
||||||
|
|
||||||
# Apply ordering to the cursor.
|
if self._limit is not None:
|
||||||
# XXX self._ordering can be equal to:
|
self._cursor_obj.limit(self._limit)
|
||||||
# * None if we didn't explicitly call order_by on this queryset.
|
|
||||||
# * A list of PyMongo-style sorting tuples.
|
|
||||||
# * An empty list if we explicitly called order_by() without any
|
|
||||||
# arguments. This indicates that we want to clear the default
|
|
||||||
# ordering.
|
|
||||||
if self._ordering:
|
|
||||||
# explicit ordering
|
|
||||||
self._cursor_obj.sort(self._ordering)
|
|
||||||
elif self._ordering is None and self._document._meta['ordering']:
|
|
||||||
# default ordering
|
|
||||||
order = self._get_order_by(self._document._meta['ordering'])
|
|
||||||
self._cursor_obj.sort(order)
|
|
||||||
|
|
||||||
if self._limit is not None:
|
if self._skip is not None:
|
||||||
self._cursor_obj.limit(self._limit)
|
self._cursor_obj.skip(self._skip)
|
||||||
|
|
||||||
if self._skip is not None:
|
if self._hint != -1:
|
||||||
self._cursor_obj.skip(self._skip)
|
self._cursor_obj.hint(self._hint)
|
||||||
|
|
||||||
if self._hint != -1:
|
if self._batch_size is not None:
|
||||||
self._cursor_obj.hint(self._hint)
|
self._cursor_obj.batch_size(self._batch_size)
|
||||||
|
|
||||||
if self._batch_size is not None:
|
|
||||||
self._cursor_obj.batch_size(self._batch_size)
|
|
||||||
|
|
||||||
return self._cursor_obj
|
return self._cursor_obj
|
||||||
|
|
||||||
@@ -1746,13 +1706,7 @@ class BaseQuerySet(object):
|
|||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _get_order_by(self, keys):
|
def _get_order_by(self, keys):
|
||||||
"""Given a list of MongoEngine-style sort keys, return a list
|
"""Creates a list of order by fields"""
|
||||||
of sorting tuples that can be applied to a PyMongo cursor. For
|
|
||||||
example:
|
|
||||||
|
|
||||||
>>> qs._get_order_by(['-last_name', 'first_name'])
|
|
||||||
[('last_name', -1), ('first_name', 1)]
|
|
||||||
"""
|
|
||||||
key_list = []
|
key_list = []
|
||||||
for key in keys:
|
for key in keys:
|
||||||
if not key:
|
if not key:
|
||||||
@@ -1765,19 +1719,17 @@ class BaseQuerySet(object):
|
|||||||
direction = pymongo.ASCENDING
|
direction = pymongo.ASCENDING
|
||||||
if key[0] == '-':
|
if key[0] == '-':
|
||||||
direction = pymongo.DESCENDING
|
direction = pymongo.DESCENDING
|
||||||
|
|
||||||
if key[0] in ('-', '+'):
|
if key[0] in ('-', '+'):
|
||||||
key = key[1:]
|
key = key[1:]
|
||||||
|
|
||||||
key = key.replace('__', '.')
|
key = key.replace('__', '.')
|
||||||
try:
|
try:
|
||||||
key = self._document._translate_field_name(key)
|
key = self._document._translate_field_name(key)
|
||||||
except Exception:
|
except Exception:
|
||||||
# TODO this exception should be more specific
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
key_list.append((key, direction))
|
key_list.append((key, direction))
|
||||||
|
|
||||||
|
if self._cursor_obj and key_list:
|
||||||
|
self._cursor_obj.sort(key_list)
|
||||||
return key_list
|
return key_list
|
||||||
|
|
||||||
def _get_scalar(self, doc):
|
def _get_scalar(self, doc):
|
||||||
@@ -1875,21 +1827,10 @@ class BaseQuerySet(object):
|
|||||||
return code
|
return code
|
||||||
|
|
||||||
def _chainable_method(self, method_name, val):
|
def _chainable_method(self, method_name, val):
|
||||||
"""Call a particular method on the PyMongo cursor call a particular chainable method
|
|
||||||
with the provided value.
|
|
||||||
"""
|
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
|
method = getattr(queryset._cursor, method_name)
|
||||||
# Get an existing cursor object or create a new one
|
method(val)
|
||||||
cursor = queryset._cursor
|
|
||||||
|
|
||||||
# Find the requested method on the cursor and call it with the
|
|
||||||
# provided value
|
|
||||||
getattr(cursor, method_name)(val)
|
|
||||||
|
|
||||||
# Cache the value on the queryset._{method_name}
|
|
||||||
setattr(queryset, '_' + method_name, val)
|
setattr(queryset, '_' + method_name, val)
|
||||||
|
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
# Deprecated
|
# Deprecated
|
||||||
|
@@ -136,15 +136,13 @@ class QuerySet(BaseQuerySet):
|
|||||||
return self._len
|
return self._len
|
||||||
|
|
||||||
def no_cache(self):
|
def no_cache(self):
|
||||||
"""Convert to a non-caching queryset
|
"""Convert to a non_caching queryset
|
||||||
|
|
||||||
.. versionadded:: 0.8.3 Convert to non caching queryset
|
.. versionadded:: 0.8.3 Convert to non caching queryset
|
||||||
"""
|
"""
|
||||||
if self._result_cache is not None:
|
if self._result_cache is not None:
|
||||||
raise OperationError('QuerySet already cached')
|
raise OperationError('QuerySet already cached')
|
||||||
|
return self.clone_into(QuerySetNoCache(self._document, self._collection))
|
||||||
return self._clone_into(QuerySetNoCache(self._document,
|
|
||||||
self._collection))
|
|
||||||
|
|
||||||
|
|
||||||
class QuerySetNoCache(BaseQuerySet):
|
class QuerySetNoCache(BaseQuerySet):
|
||||||
@@ -155,7 +153,7 @@ class QuerySetNoCache(BaseQuerySet):
|
|||||||
|
|
||||||
.. versionadded:: 0.8.3 Convert to caching queryset
|
.. versionadded:: 0.8.3 Convert to caching queryset
|
||||||
"""
|
"""
|
||||||
return self._clone_into(QuerySet(self._document, self._collection))
|
return self.clone_into(QuerySet(self._document, self._collection))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
"""Provides the string representation of the QuerySet
|
"""Provides the string representation of the QuerySet
|
||||||
|
@@ -106,111 +106,58 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
list(BlogPost.objects(author2__name="test"))
|
list(BlogPost.objects(author2__name="test"))
|
||||||
|
|
||||||
def test_find(self):
|
def test_find(self):
|
||||||
"""Ensure that a query returns a valid set of results."""
|
"""Ensure that a query returns a valid set of results.
|
||||||
user_a = self.Person.objects.create(name='User A', age=20)
|
"""
|
||||||
user_b = self.Person.objects.create(name='User B', age=30)
|
self.Person(name="User A", age=20).save()
|
||||||
|
self.Person(name="User B", age=30).save()
|
||||||
|
|
||||||
# Find all people in the collection
|
# Find all people in the collection
|
||||||
people = self.Person.objects
|
people = self.Person.objects
|
||||||
self.assertEqual(people.count(), 2)
|
self.assertEqual(people.count(), 2)
|
||||||
results = list(people)
|
results = list(people)
|
||||||
|
|
||||||
self.assertTrue(isinstance(results[0], self.Person))
|
self.assertTrue(isinstance(results[0], self.Person))
|
||||||
self.assertTrue(isinstance(results[0].id, (ObjectId, str, unicode)))
|
self.assertTrue(isinstance(results[0].id, (ObjectId, str, unicode)))
|
||||||
|
self.assertEqual(results[0].name, "User A")
|
||||||
self.assertEqual(results[0], user_a)
|
|
||||||
self.assertEqual(results[0].name, 'User A')
|
|
||||||
self.assertEqual(results[0].age, 20)
|
self.assertEqual(results[0].age, 20)
|
||||||
|
self.assertEqual(results[1].name, "User B")
|
||||||
self.assertEqual(results[1], user_b)
|
|
||||||
self.assertEqual(results[1].name, 'User B')
|
|
||||||
self.assertEqual(results[1].age, 30)
|
self.assertEqual(results[1].age, 30)
|
||||||
|
|
||||||
# Filter people by age
|
# Use a query to filter the people found to just person1
|
||||||
people = self.Person.objects(age=20)
|
people = self.Person.objects(age=20)
|
||||||
self.assertEqual(people.count(), 1)
|
self.assertEqual(people.count(), 1)
|
||||||
person = people.next()
|
person = people.next()
|
||||||
self.assertEqual(person, user_a)
|
|
||||||
self.assertEqual(person.name, "User A")
|
self.assertEqual(person.name, "User A")
|
||||||
self.assertEqual(person.age, 20)
|
self.assertEqual(person.age, 20)
|
||||||
|
|
||||||
def test_limit(self):
|
# Test limit
|
||||||
"""Ensure that QuerySet.limit works as expected."""
|
|
||||||
user_a = self.Person.objects.create(name='User A', age=20)
|
|
||||||
user_b = self.Person.objects.create(name='User B', age=30)
|
|
||||||
|
|
||||||
# Test limit on a new queryset
|
|
||||||
people = list(self.Person.objects.limit(1))
|
people = list(self.Person.objects.limit(1))
|
||||||
self.assertEqual(len(people), 1)
|
self.assertEqual(len(people), 1)
|
||||||
self.assertEqual(people[0], user_a)
|
self.assertEqual(people[0].name, 'User A')
|
||||||
|
|
||||||
# Test limit on an existing queryset
|
# Test skip
|
||||||
people = self.Person.objects
|
|
||||||
self.assertEqual(len(people), 2)
|
|
||||||
people2 = people.limit(1)
|
|
||||||
self.assertEqual(len(people), 2)
|
|
||||||
self.assertEqual(len(people2), 1)
|
|
||||||
self.assertEqual(people2[0], user_a)
|
|
||||||
|
|
||||||
# Test chaining of only after limit
|
|
||||||
person = self.Person.objects().limit(1).only('name').first()
|
|
||||||
self.assertEqual(person, user_a)
|
|
||||||
self.assertEqual(person.name, 'User A')
|
|
||||||
self.assertEqual(person.age, None)
|
|
||||||
|
|
||||||
def test_skip(self):
|
|
||||||
"""Ensure that QuerySet.skip works as expected."""
|
|
||||||
user_a = self.Person.objects.create(name='User A', age=20)
|
|
||||||
user_b = self.Person.objects.create(name='User B', age=30)
|
|
||||||
|
|
||||||
# Test skip on a new queryset
|
|
||||||
people = list(self.Person.objects.skip(1))
|
people = list(self.Person.objects.skip(1))
|
||||||
self.assertEqual(len(people), 1)
|
self.assertEqual(len(people), 1)
|
||||||
self.assertEqual(people[0], user_b)
|
self.assertEqual(people[0].name, 'User B')
|
||||||
|
|
||||||
# Test skip on an existing queryset
|
person3 = self.Person(name="User C", age=40)
|
||||||
people = self.Person.objects
|
person3.save()
|
||||||
self.assertEqual(len(people), 2)
|
|
||||||
people2 = people.skip(1)
|
|
||||||
self.assertEqual(len(people), 2)
|
|
||||||
self.assertEqual(len(people2), 1)
|
|
||||||
self.assertEqual(people2[0], user_b)
|
|
||||||
|
|
||||||
# Test chaining of only after skip
|
|
||||||
person = self.Person.objects().skip(1).only('name').first()
|
|
||||||
self.assertEqual(person, user_b)
|
|
||||||
self.assertEqual(person.name, 'User B')
|
|
||||||
self.assertEqual(person.age, None)
|
|
||||||
|
|
||||||
def test_slice(self):
|
|
||||||
"""Ensure slicing a queryset works as expected."""
|
|
||||||
user_a = self.Person.objects.create(name='User A', age=20)
|
|
||||||
user_b = self.Person.objects.create(name='User B', age=30)
|
|
||||||
user_c = self.Person.objects.create(name="User C", age=40)
|
|
||||||
|
|
||||||
# Test slice limit
|
# Test slice limit
|
||||||
people = list(self.Person.objects[:2])
|
people = list(self.Person.objects[:2])
|
||||||
self.assertEqual(len(people), 2)
|
self.assertEqual(len(people), 2)
|
||||||
self.assertEqual(people[0], user_a)
|
self.assertEqual(people[0].name, 'User A')
|
||||||
self.assertEqual(people[1], user_b)
|
self.assertEqual(people[1].name, 'User B')
|
||||||
|
|
||||||
# Test slice skip
|
# Test slice skip
|
||||||
people = list(self.Person.objects[1:])
|
people = list(self.Person.objects[1:])
|
||||||
self.assertEqual(len(people), 2)
|
self.assertEqual(len(people), 2)
|
||||||
self.assertEqual(people[0], user_b)
|
self.assertEqual(people[0].name, 'User B')
|
||||||
self.assertEqual(people[1], user_c)
|
self.assertEqual(people[1].name, 'User C')
|
||||||
|
|
||||||
# Test slice limit and skip
|
# Test slice limit and skip
|
||||||
people = list(self.Person.objects[1:2])
|
people = list(self.Person.objects[1:2])
|
||||||
self.assertEqual(len(people), 1)
|
self.assertEqual(len(people), 1)
|
||||||
self.assertEqual(people[0], user_b)
|
self.assertEqual(people[0].name, 'User B')
|
||||||
|
|
||||||
# Test slice limit and skip on an existing queryset
|
|
||||||
people = self.Person.objects
|
|
||||||
self.assertEqual(len(people), 3)
|
|
||||||
people2 = people[1:2]
|
|
||||||
self.assertEqual(len(people2), 1)
|
|
||||||
self.assertEqual(people2[0], user_b)
|
|
||||||
|
|
||||||
# Test slice limit and skip cursor reset
|
# Test slice limit and skip cursor reset
|
||||||
qs = self.Person.objects[1:2]
|
qs = self.Person.objects[1:2]
|
||||||
@@ -221,7 +168,6 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(people), 1)
|
self.assertEqual(len(people), 1)
|
||||||
self.assertEqual(people[0].name, 'User B')
|
self.assertEqual(people[0].name, 'User B')
|
||||||
|
|
||||||
# Test empty slice
|
|
||||||
people = list(self.Person.objects[1:1])
|
people = list(self.Person.objects[1:1])
|
||||||
self.assertEqual(len(people), 0)
|
self.assertEqual(len(people), 0)
|
||||||
|
|
||||||
@@ -241,6 +187,12 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual("[<Person: Person object>, <Person: Person object>]",
|
self.assertEqual("[<Person: Person object>, <Person: Person object>]",
|
||||||
"%s" % self.Person.objects[51:53])
|
"%s" % self.Person.objects[51:53])
|
||||||
|
|
||||||
|
# Test only after limit
|
||||||
|
self.assertEqual(self.Person.objects().limit(2).only('name')[0].age, None)
|
||||||
|
|
||||||
|
# Test only after skip
|
||||||
|
self.assertEqual(self.Person.objects().skip(2).only('name')[0].age, None)
|
||||||
|
|
||||||
def test_find_one(self):
|
def test_find_one(self):
|
||||||
"""Ensure that a query using find_one returns a valid result.
|
"""Ensure that a query using find_one returns a valid result.
|
||||||
"""
|
"""
|
||||||
@@ -814,8 +766,7 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
self.assertEqual(record.embed.field, 2)
|
self.assertEqual(record.embed.field, 2)
|
||||||
|
|
||||||
def test_bulk_insert(self):
|
def test_bulk_insert(self):
|
||||||
"""Ensure that bulk insert works
|
"""Ensure that bulk insert works."""
|
||||||
"""
|
|
||||||
|
|
||||||
class Comment(EmbeddedDocument):
|
class Comment(EmbeddedDocument):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@@ -933,9 +884,37 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(Blog.objects.count(), 2)
|
self.assertEqual(Blog.objects.count(), 2)
|
||||||
|
|
||||||
Blog.objects.insert([blog2, blog3],
|
def test_bulk_insert_continue_on_error(self):
|
||||||
write_concern={"w": 0, 'continue_on_error': True})
|
"""Ensure that bulk insert works with the continue_on_error option."""
|
||||||
self.assertEqual(Blog.objects.count(), 3)
|
|
||||||
|
class Person(Document):
|
||||||
|
email = EmailField(unique=True)
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
Person.objects.insert([
|
||||||
|
Person(email='alice@example.com'),
|
||||||
|
Person(email='bob@example.com')
|
||||||
|
])
|
||||||
|
self.assertEqual(Person.objects.count(), 2)
|
||||||
|
|
||||||
|
new_docs = [
|
||||||
|
Person(email='alice@example.com'), # dupe
|
||||||
|
Person(email='bob@example.com'), # dupe
|
||||||
|
Person(email='steve@example.com') # new one
|
||||||
|
]
|
||||||
|
|
||||||
|
# By default inserting dupe docs should fail and no new docs should
|
||||||
|
# be inserted.
|
||||||
|
with self.assertRaises(NotUniqueError):
|
||||||
|
Person.objects.insert(new_docs)
|
||||||
|
self.assertEqual(Person.objects.count(), 2)
|
||||||
|
|
||||||
|
# With continue_on_error, new doc should be inserted, even though we
|
||||||
|
# still get a NotUniqueError caused by the other 2 dupes.
|
||||||
|
with self.assertRaises(NotUniqueError):
|
||||||
|
Person.objects.insert(new_docs, continue_on_error=True)
|
||||||
|
self.assertEqual(Person.objects.count(), 3)
|
||||||
|
|
||||||
def test_get_changed_fields_query_count(self):
|
def test_get_changed_fields_query_count(self):
|
||||||
|
|
||||||
@@ -1274,7 +1253,6 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
# default ordering should be used by default
|
|
||||||
with db_ops_tracker() as q:
|
with db_ops_tracker() as q:
|
||||||
BlogPost.objects.filter(title='whatever').first()
|
BlogPost.objects.filter(title='whatever').first()
|
||||||
self.assertEqual(len(q.get_ops()), 1)
|
self.assertEqual(len(q.get_ops()), 1)
|
||||||
@@ -1283,28 +1261,11 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
{'published_date': -1}
|
{'published_date': -1}
|
||||||
)
|
)
|
||||||
|
|
||||||
# calling order_by() should clear the default ordering
|
|
||||||
with db_ops_tracker() as q:
|
with db_ops_tracker() as q:
|
||||||
BlogPost.objects.filter(title='whatever').order_by().first()
|
BlogPost.objects.filter(title='whatever').order_by().first()
|
||||||
self.assertEqual(len(q.get_ops()), 1)
|
self.assertEqual(len(q.get_ops()), 1)
|
||||||
self.assertFalse('$orderby' in q.get_ops()[0]['query'])
|
self.assertFalse('$orderby' in q.get_ops()[0]['query'])
|
||||||
|
|
||||||
# calling an explicit order_by should use a specified sort
|
|
||||||
with db_ops_tracker() as q:
|
|
||||||
BlogPost.objects.filter(title='whatever').order_by('published_date').first()
|
|
||||||
self.assertEqual(len(q.get_ops()), 1)
|
|
||||||
self.assertEqual(
|
|
||||||
q.get_ops()[0]['query']['$orderby'],
|
|
||||||
{'published_date': 1}
|
|
||||||
)
|
|
||||||
|
|
||||||
# calling order_by() after an explicit sort should clear it
|
|
||||||
with db_ops_tracker() as q:
|
|
||||||
qs = BlogPost.objects.filter(title='whatever').order_by('published_date')
|
|
||||||
qs.order_by().first()
|
|
||||||
self.assertEqual(len(q.get_ops()), 1)
|
|
||||||
self.assertFalse('$orderby' in q.get_ops()[0]['query'])
|
|
||||||
|
|
||||||
def test_no_ordering_for_get(self):
|
def test_no_ordering_for_get(self):
|
||||||
""" Ensure that Doc.objects.get doesn't use any ordering.
|
""" Ensure that Doc.objects.get doesn't use any ordering.
|
||||||
"""
|
"""
|
||||||
|
6
tox.ini
6
tox.ini
@@ -1,5 +1,5 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = {py27,py33,py34,py35,pypy,pypy3}-{mg27,mg28,mg30},flake8
|
envlist = {py26,py27,py33,py34,py35,pypy,pypy3}-{mg27,mg28},flake8
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
@@ -7,10 +7,12 @@ commands =
|
|||||||
deps =
|
deps =
|
||||||
nose
|
nose
|
||||||
mg27: PyMongo<2.8
|
mg27: PyMongo<2.8
|
||||||
mg28: PyMongo>=2.8,<2.9
|
mg28: PyMongo>=2.8,<3.0
|
||||||
mg30: PyMongo>=3.0
|
mg30: PyMongo>=3.0
|
||||||
|
mgdev: https://github.com/mongodb/mongo-python-driver/tarball/master
|
||||||
setenv =
|
setenv =
|
||||||
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
||||||
|
passenv = windir
|
||||||
|
|
||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
deps =
|
deps =
|
||||||
|
Reference in New Issue
Block a user