Compare commits
1 Commits
v0.16.0
...
better-db-
Author | SHA1 | Date | |
---|---|---|---|
|
7195236a3b |
@@ -1,28 +1,23 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
sudo apt-get remove mongodb-org-server
|
|
||||||
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
|
||||||
|
|
||||||
if [ "$MONGODB" = "2.6" ]; then
|
if [ "$MONGODB" = "2.4" ]; then
|
||||||
|
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install mongodb-10gen=2.4.14
|
||||||
|
sudo service mongodb start
|
||||||
|
elif [ "$MONGODB" = "2.6" ]; then
|
||||||
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install mongodb-org-server=2.6.12
|
sudo apt-get install mongodb-org-server=2.6.12
|
||||||
# service should be started automatically
|
# service should be started automatically
|
||||||
elif [ "$MONGODB" = "3.0" ]; then
|
elif [ "$MONGODB" = "3.0" ]; then
|
||||||
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
echo "deb http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb.list
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install mongodb-org-server=3.0.14
|
sudo apt-get install mongodb-org-server=3.0.14
|
||||||
# service should be started automatically
|
# service should be started automatically
|
||||||
elif [ "$MONGODB" = "3.2" ]; then
|
|
||||||
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv EA312927
|
|
||||||
echo "deb http://repo.mongodb.org/apt/ubuntu trusty/mongodb-org/3.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.2.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install mongodb-org-server=3.2.20
|
|
||||||
# service should be started automatically
|
|
||||||
else
|
else
|
||||||
echo "Invalid MongoDB version, expected 2.6, 3.0, or 3.2"
|
echo "Invalid MongoDB version, expected 2.4, 2.6, or 3.0."
|
||||||
exit 1
|
exit 1
|
||||||
fi;
|
fi;
|
||||||
|
|
||||||
mkdir db
|
|
||||||
1>db/logs mongod --dbpath=db &
|
|
||||||
|
37
.travis.yml
37
.travis.yml
@@ -2,10 +2,12 @@
|
|||||||
# PyMongo combinations. However, that would result in an overly long build
|
# PyMongo combinations. However, that would result in an overly long build
|
||||||
# with a very large number of jobs, hence we only test a subset of all the
|
# with a very large number of jobs, hence we only test a subset of all the
|
||||||
# combinations:
|
# combinations:
|
||||||
|
# * MongoDB v2.4 & v3.0 are only tested against Python v2.7 & v3.5.
|
||||||
|
# * MongoDB v2.4 is tested against PyMongo v2.7 & v3.x.
|
||||||
|
# * MongoDB v3.0 is tested against PyMongo v3.x.
|
||||||
# * MongoDB v2.6 is currently the "main" version tested against Python v2.7,
|
# * MongoDB v2.6 is currently the "main" version tested against Python v2.7,
|
||||||
# v3.5, v3.6, PyPy, and PyMongo v3.x.
|
# v3.5, PyPy & PyPy3, and PyMongo v2.7, v2.8 & v3.x.
|
||||||
# * MongoDB v3.0 & v3.2 are tested against Python v2.7, v3.5 & v3.6
|
#
|
||||||
# and Pymongo v3.5 & v3.x
|
|
||||||
# Reminder: Update README.rst if you change MongoDB versions we test.
|
# Reminder: Update README.rst if you change MongoDB versions we test.
|
||||||
|
|
||||||
language: python
|
language: python
|
||||||
@@ -13,11 +15,14 @@ language: python
|
|||||||
python:
|
python:
|
||||||
- 2.7
|
- 2.7
|
||||||
- 3.5
|
- 3.5
|
||||||
- 3.6
|
|
||||||
- pypy
|
- pypy
|
||||||
|
- pypy3.3-5.2-alpha1
|
||||||
|
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- MONGODB=2.6 PYMONGO=3.x
|
- MONGODB=2.6 PYMONGO=2.7
|
||||||
|
- MONGODB=2.6 PYMONGO=2.8
|
||||||
|
- MONGODB=2.6 PYMONGO=3.0
|
||||||
|
|
||||||
matrix:
|
matrix:
|
||||||
# Finish the build as soon as one job fails
|
# Finish the build as soon as one job fails
|
||||||
@@ -25,22 +30,20 @@ matrix:
|
|||||||
|
|
||||||
include:
|
include:
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: MONGODB=3.0 PYMONGO=3.5
|
env: MONGODB=2.4 PYMONGO=2.7
|
||||||
- python: 2.7
|
- python: 2.7
|
||||||
env: MONGODB=3.2 PYMONGO=3.x
|
env: MONGODB=2.4 PYMONGO=3.0
|
||||||
|
- python: 2.7
|
||||||
|
env: MONGODB=3.0 PYMONGO=3.0
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
env: MONGODB=3.0 PYMONGO=3.5
|
env: MONGODB=2.4 PYMONGO=2.7
|
||||||
- python: 3.5
|
- python: 3.5
|
||||||
env: MONGODB=3.2 PYMONGO=3.x
|
env: MONGODB=2.4 PYMONGO=3.0
|
||||||
- python: 3.6
|
- python: 3.5
|
||||||
env: MONGODB=3.0 PYMONGO=3.5
|
env: MONGODB=3.0 PYMONGO=3.0
|
||||||
- python: 3.6
|
|
||||||
env: MONGODB=3.2 PYMONGO=3.x
|
|
||||||
|
|
||||||
before_install:
|
before_install:
|
||||||
- bash .install_mongodb_on_travis.sh
|
- bash .install_mongodb_on_travis.sh
|
||||||
- sleep 15 # https://docs.travis-ci.com/user/database-setup/#MongoDB-does-not-immediately-accept-connections
|
|
||||||
- mongo --eval 'db.version();'
|
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
|
||||||
@@ -89,11 +92,11 @@ deploy:
|
|||||||
distributions: "sdist bdist_wheel"
|
distributions: "sdist bdist_wheel"
|
||||||
|
|
||||||
# only deploy on tagged commits (aka GitHub releases) and only for the
|
# only deploy on tagged commits (aka GitHub releases) and only for the
|
||||||
# parent repo's builds running Python 2.7 along with PyMongo v3.x (we run
|
# 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
|
# Travis against many different Python and PyMongo versions and we don't
|
||||||
# want the deploy to occur multiple times).
|
# want the deploy to occur multiple times).
|
||||||
on:
|
on:
|
||||||
tags: true
|
tags: true
|
||||||
repo: MongoEngine/mongoengine
|
repo: MongoEngine/mongoengine
|
||||||
condition: "$PYMONGO = 3.x"
|
condition: "$PYMONGO = 3.0"
|
||||||
python: 2.7
|
python: 2.7
|
||||||
|
4
AUTHORS
4
AUTHORS
@@ -243,7 +243,3 @@ that much better:
|
|||||||
* Victor Varvaryuk
|
* Victor Varvaryuk
|
||||||
* Stanislav Kaledin (https://github.com/sallyruthstruik)
|
* Stanislav Kaledin (https://github.com/sallyruthstruik)
|
||||||
* Dmitry Yantsen (https://github.com/mrTable)
|
* Dmitry Yantsen (https://github.com/mrTable)
|
||||||
* Renjianxin (https://github.com/Davidrjx)
|
|
||||||
* Erdenezul Batmunkh (https://github.com/erdenezul)
|
|
||||||
* Andy Yankovsky (https://github.com/werat)
|
|
||||||
* Bastien Gérard (https://github.com/bagerard)
|
|
||||||
|
@@ -22,11 +22,8 @@ Supported Interpreters
|
|||||||
|
|
||||||
MongoEngine supports CPython 2.7 and newer. Language
|
MongoEngine supports CPython 2.7 and newer. Language
|
||||||
features not supported by all interpreters can not be used.
|
features not supported by all interpreters can not be used.
|
||||||
The codebase is written in python 2 so you must be using python 2
|
Please also ensure that your code is properly converted by
|
||||||
when developing new features. Compatibility of the library with Python 3
|
`2to3 <http://docs.python.org/library/2to3.html>`_ for Python 3 support.
|
||||||
relies on the 2to3 package that gets executed as part of the installation
|
|
||||||
build. You should ensure that your code is properly converted by
|
|
||||||
`2to3 <http://docs.python.org/library/2to3.html>`_.
|
|
||||||
|
|
||||||
Style Guide
|
Style Guide
|
||||||
-----------
|
-----------
|
||||||
|
12
README.rst
12
README.rst
@@ -26,21 +26,19 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
|
|||||||
|
|
||||||
Supported MongoDB Versions
|
Supported MongoDB Versions
|
||||||
==========================
|
==========================
|
||||||
MongoEngine is currently tested against MongoDB v2.6, v3.0 and v3.2. Future
|
MongoEngine is currently tested against MongoDB v2.4, v2.6, and v3.0. Future
|
||||||
versions should be supported as well, but aren't actively tested at the moment.
|
versions should be supported as well, but aren't actively tested at the moment.
|
||||||
Make sure to open an issue or submit a pull request if you experience any
|
Make sure to open an issue or submit a pull request if you experience any
|
||||||
problems with MongoDB v3.4+.
|
problems with MongoDB v3.2+.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
============
|
============
|
||||||
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
|
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
|
||||||
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
|
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
|
||||||
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
|
||||||
and thus you can use ``easy_install -U mongoengine``. Another option is
|
and thus you can use ``easy_install -U mongoengine``. Otherwise, you can download the
|
||||||
`pipenv <https://docs.pipenv.org/>`_. You can then use ``pipenv install mongoengine``
|
source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and run ``python
|
||||||
to both create the virtual environment and install the package. Otherwise, you can
|
setup.py install``.
|
||||||
download the source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
|
|
||||||
run ``python setup.py install``.
|
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
============
|
============
|
||||||
|
@@ -87,9 +87,7 @@ Fields
|
|||||||
.. autoclass:: mongoengine.fields.DictField
|
.. autoclass:: mongoengine.fields.DictField
|
||||||
.. autoclass:: mongoengine.fields.MapField
|
.. autoclass:: mongoengine.fields.MapField
|
||||||
.. autoclass:: mongoengine.fields.ReferenceField
|
.. autoclass:: mongoengine.fields.ReferenceField
|
||||||
.. autoclass:: mongoengine.fields.LazyReferenceField
|
|
||||||
.. autoclass:: mongoengine.fields.GenericReferenceField
|
.. autoclass:: mongoengine.fields.GenericReferenceField
|
||||||
.. autoclass:: mongoengine.fields.GenericLazyReferenceField
|
|
||||||
.. autoclass:: mongoengine.fields.CachedReferenceField
|
.. autoclass:: mongoengine.fields.CachedReferenceField
|
||||||
.. autoclass:: mongoengine.fields.BinaryField
|
.. autoclass:: mongoengine.fields.BinaryField
|
||||||
.. autoclass:: mongoengine.fields.FileField
|
.. autoclass:: mongoengine.fields.FileField
|
||||||
|
@@ -5,74 +5,6 @@ Changelog
|
|||||||
Development
|
Development
|
||||||
===========
|
===========
|
||||||
- (Fill this out as you fix issues and develop your features).
|
- (Fill this out as you fix issues and develop your features).
|
||||||
=======
|
|
||||||
Changes in 0.16.0
|
|
||||||
=================
|
|
||||||
- Various improvements to the doc
|
|
||||||
- Improvement to code quality
|
|
||||||
- POTENTIAL BREAKING CHANGES:
|
|
||||||
- EmbeddedDocumentField will no longer accept references to Document classes in its constructor #1661
|
|
||||||
- Get rid of the `basecls` parameter from the DictField constructor (dead code) #1876
|
|
||||||
- default value of ComplexDateTime is now None (and no longer the current datetime) #1368
|
|
||||||
- Fix unhashable TypeError when referencing a Document with a compound key in an EmbeddedDocument #1685
|
|
||||||
- Fix bug where an EmbeddedDocument with the same id as its parent would not be tracked for changes #1768
|
|
||||||
- Fix the fact that bulk `insert()` was not setting primary keys of inserted documents instances #1919
|
|
||||||
- Fix bug when referencing the abstract class in a ReferenceField #1920
|
|
||||||
- Allow modification to the document made in pre_save_post_validation to be taken into account #1202
|
|
||||||
- Replaced MongoDB 2.4 tests in CI by MongoDB 3.2 #1903
|
|
||||||
- Fix side effects of using queryset.`no_dereference` on other documents #1677
|
|
||||||
- Fix TypeError when using lazy django translation objects as translated choices #1879
|
|
||||||
- Improve 2-3 codebase compatibility #1889
|
|
||||||
- Fix the support for changing the default value of ComplexDateTime #1368
|
|
||||||
- Improves error message in case an EmbeddedDocumentListField receives an EmbeddedDocument instance
|
|
||||||
instead of a list #1877
|
|
||||||
- Fix the Decimal operator inc/dec #1517 #1320
|
|
||||||
- Ignore killcursors queries in `query_counter` context manager #1869
|
|
||||||
- Fix the fact that `query_counter` was modifying the initial profiling_level in case it was != 0 #1870
|
|
||||||
- Repaired the `no_sub_classes` context manager + fix the fact that it was swallowing exceptions #1865
|
|
||||||
- Fix index creation error that was swallowed by hasattr under python2 #1688
|
|
||||||
- QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611
|
|
||||||
- bulk insert updates the ids of the input documents instances #1919
|
|
||||||
- Fix an harmless bug related to GenericReferenceField where modifications in the generic-referenced document
|
|
||||||
were tracked in the parent #1934
|
|
||||||
- Improve validator of BinaryField #273
|
|
||||||
- Implemented lazy regex compiling in Field classes to improve 'import mongoengine' performance #1806
|
|
||||||
- Updated GridFSProxy.__str__ so that it would always print both the filename and grid_id #710
|
|
||||||
- Add __repr__ to Q and QCombination #1843
|
|
||||||
- fix bug in BaseList.__iter__ operator (was occuring when modifying a BaseList while iterating over it) #1676
|
|
||||||
- Added field `DateField`#513
|
|
||||||
|
|
||||||
Changes in 0.15.3
|
|
||||||
=================
|
|
||||||
- Subfield resolve error in generic_emdedded_document query #1651 #1652
|
|
||||||
- use each modifier only with $position #1673 #1675
|
|
||||||
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
|
|
||||||
- Fix validation error instance in GenericEmbeddedDocumentField #1067
|
|
||||||
- Update cached fields when fields argument is given #1712
|
|
||||||
- Add a db parameter to register_connection for compatibility with connect
|
|
||||||
- Use insert_one, insert_many in Document.insert #1491
|
|
||||||
- Use new update_one, update_many on document/queryset update #1491
|
|
||||||
- Use insert_one, insert_many in Document.insert #1491
|
|
||||||
- Fix reload(fields) affect changed fields #1371
|
|
||||||
- Fix Read-only access to database fails when trying to create indexes #1338
|
|
||||||
|
|
||||||
Changes in 0.15.0
|
|
||||||
=================
|
|
||||||
- Add LazyReferenceField and GenericLazyReferenceField to address #1230
|
|
||||||
|
|
||||||
Changes in 0.14.1
|
|
||||||
=================
|
|
||||||
- Removed SemiStrictDict and started using a regular dict for `BaseDocument._data` #1630
|
|
||||||
- Added support for the `$position` param in the `$push` operator #1566
|
|
||||||
- Fixed `DateTimeField` interpreting an empty string as today #1533
|
|
||||||
- Added a missing `__ne__` method to the `GridFSProxy` class #1632
|
|
||||||
- Fixed `BaseQuerySet._fields_to_db_fields` #1553
|
|
||||||
|
|
||||||
Changes in 0.14.0
|
|
||||||
=================
|
|
||||||
- BREAKING CHANGE: Removed the `coerce_types` param from `QuerySet.as_pymongo` #1549
|
|
||||||
- POTENTIAL BREAKING CHANGE: Made EmbeddedDocument not hashable by default #1528
|
|
||||||
- Improved code quality #1531, #1540, #1541, #1547
|
|
||||||
|
|
||||||
Changes in 0.13.0
|
Changes in 0.13.0
|
||||||
=================
|
=================
|
||||||
|
@@ -45,27 +45,27 @@ post2.link_url = 'http://tractiondigital.com/labs/mongoengine/docs'
|
|||||||
post2.tags = ['mongoengine']
|
post2.tags = ['mongoengine']
|
||||||
post2.save()
|
post2.save()
|
||||||
|
|
||||||
print('ALL POSTS')
|
print 'ALL POSTS'
|
||||||
print()
|
print
|
||||||
for post in Post.objects:
|
for post in Post.objects:
|
||||||
print(post.title)
|
print post.title
|
||||||
#print '=' * post.title.count()
|
#print '=' * post.title.count()
|
||||||
print("=" * 20)
|
print "=" * 20
|
||||||
|
|
||||||
if isinstance(post, TextPost):
|
if isinstance(post, TextPost):
|
||||||
print(post.content)
|
print post.content
|
||||||
|
|
||||||
if isinstance(post, LinkPost):
|
if isinstance(post, LinkPost):
|
||||||
print('Link:', post.link_url)
|
print 'Link:', post.link_url
|
||||||
|
|
||||||
print()
|
print
|
||||||
print()
|
print
|
||||||
|
|
||||||
print('POSTS TAGGED \'MONGODB\'')
|
print 'POSTS TAGGED \'MONGODB\''
|
||||||
print()
|
print
|
||||||
for post in Post.objects(tags='mongodb'):
|
for post in Post.objects(tags='mongodb'):
|
||||||
print(post.title)
|
print post.title
|
||||||
print()
|
print
|
||||||
|
|
||||||
num_posts = Post.objects(tags='mongodb').count()
|
num_posts = Post.objects(tags='mongodb').count()
|
||||||
print('Found %d posts with tag "mongodb"' % num_posts)
|
print 'Found %d posts with tag "mongodb"' % num_posts
|
||||||
|
@@ -18,10 +18,10 @@ provide the :attr:`host` and :attr:`port` arguments to
|
|||||||
|
|
||||||
connect('project1', host='192.168.1.35', port=12345)
|
connect('project1', host='192.168.1.35', port=12345)
|
||||||
|
|
||||||
If the database requires authentication, :attr:`username`, :attr:`password`
|
If the database requires authentication, :attr:`username` and :attr:`password`
|
||||||
and :attr:`authentication_source` arguments should be provided::
|
arguments should be provided::
|
||||||
|
|
||||||
connect('project1', username='webapp', password='pwd123', authentication_source='admin')
|
connect('project1', username='webapp', password='pwd123')
|
||||||
|
|
||||||
URI style connections are also supported -- just supply the URI as
|
URI style connections are also supported -- just supply the URI as
|
||||||
the :attr:`host` to
|
the :attr:`host` to
|
||||||
|
@@ -22,7 +22,7 @@ objects** as class attributes to the document class::
|
|||||||
|
|
||||||
class Page(Document):
|
class Page(Document):
|
||||||
title = StringField(max_length=200, required=True)
|
title = StringField(max_length=200, required=True)
|
||||||
date_modified = DateTimeField(default=datetime.datetime.utcnow)
|
date_modified = DateTimeField(default=datetime.datetime.now)
|
||||||
|
|
||||||
As BSON (the binary format for storing data in mongodb) is order dependent,
|
As BSON (the binary format for storing data in mongodb) is order dependent,
|
||||||
documents are serialized based on their field order.
|
documents are serialized based on their field order.
|
||||||
@@ -80,7 +80,6 @@ are as follows:
|
|||||||
* :class:`~mongoengine.fields.FloatField`
|
* :class:`~mongoengine.fields.FloatField`
|
||||||
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
|
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
|
||||||
* :class:`~mongoengine.fields.GenericReferenceField`
|
* :class:`~mongoengine.fields.GenericReferenceField`
|
||||||
* :class:`~mongoengine.fields.GenericLazyReferenceField`
|
|
||||||
* :class:`~mongoengine.fields.GeoPointField`
|
* :class:`~mongoengine.fields.GeoPointField`
|
||||||
* :class:`~mongoengine.fields.ImageField`
|
* :class:`~mongoengine.fields.ImageField`
|
||||||
* :class:`~mongoengine.fields.IntField`
|
* :class:`~mongoengine.fields.IntField`
|
||||||
@@ -88,7 +87,6 @@ are as follows:
|
|||||||
* :class:`~mongoengine.fields.MapField`
|
* :class:`~mongoengine.fields.MapField`
|
||||||
* :class:`~mongoengine.fields.ObjectIdField`
|
* :class:`~mongoengine.fields.ObjectIdField`
|
||||||
* :class:`~mongoengine.fields.ReferenceField`
|
* :class:`~mongoengine.fields.ReferenceField`
|
||||||
* :class:`~mongoengine.fields.LazyReferenceField`
|
|
||||||
* :class:`~mongoengine.fields.SequenceField`
|
* :class:`~mongoengine.fields.SequenceField`
|
||||||
* :class:`~mongoengine.fields.SortedListField`
|
* :class:`~mongoengine.fields.SortedListField`
|
||||||
* :class:`~mongoengine.fields.StringField`
|
* :class:`~mongoengine.fields.StringField`
|
||||||
@@ -226,7 +224,7 @@ store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate
|
|||||||
user = ReferenceField(User)
|
user = ReferenceField(User)
|
||||||
answers = DictField()
|
answers = DictField()
|
||||||
|
|
||||||
survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
|
survey_response = SurveyResponse(date=datetime.now(), user=request.user)
|
||||||
response_form = ResponseForm(request.POST)
|
response_form = ResponseForm(request.POST)
|
||||||
survey_response.answers = response_form.cleaned_data()
|
survey_response.answers = response_form.cleaned_data()
|
||||||
survey_response.save()
|
survey_response.save()
|
||||||
@@ -492,9 +490,7 @@ the field name with a **#**::
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
If a dictionary is passed then additional options become available. Valid options include,
|
If a dictionary is passed then the following options are available:
|
||||||
but are not limited to:
|
|
||||||
|
|
||||||
|
|
||||||
:attr:`fields` (Default: None)
|
:attr:`fields` (Default: None)
|
||||||
The fields to index. Specified in the same format as described above.
|
The fields to index. Specified in the same format as described above.
|
||||||
@@ -515,15 +511,8 @@ but are not limited to:
|
|||||||
Allows you to automatically expire data from a collection by setting the
|
Allows you to automatically expire data from a collection by setting the
|
||||||
time in seconds to expire the a field.
|
time in seconds to expire the a field.
|
||||||
|
|
||||||
:attr:`name` (Optional)
|
|
||||||
Allows you to specify a name for the index
|
|
||||||
|
|
||||||
:attr:`collation` (Optional)
|
|
||||||
Allows to create case insensitive indexes (MongoDB v3.4+ only)
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Additional options are forwarded as **kwargs to pymongo's create_index method.
|
|
||||||
Inheritance adds extra fields indices see: :ref:`document-inheritance`.
|
Inheritance adds extra fields indices see: :ref:`document-inheritance`.
|
||||||
|
|
||||||
Global index default options
|
Global index default options
|
||||||
@@ -535,16 +524,15 @@ There are a few top level defaults for all indexes that can be set::
|
|||||||
title = StringField()
|
title = StringField()
|
||||||
rating = StringField()
|
rating = StringField()
|
||||||
meta = {
|
meta = {
|
||||||
'index_opts': {},
|
'index_options': {},
|
||||||
'index_background': True,
|
'index_background': True,
|
||||||
'index_cls': False,
|
|
||||||
'auto_create_index': True,
|
|
||||||
'index_drop_dups': True,
|
'index_drop_dups': True,
|
||||||
|
'index_cls': False
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
:attr:`index_opts` (Optional)
|
:attr:`index_options` (Optional)
|
||||||
Set any default index options - see the `full options list <https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex>`_
|
Set any default index options - see the `full options list <http://docs.mongodb.org/manual/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex>`_
|
||||||
|
|
||||||
:attr:`index_background` (Optional)
|
:attr:`index_background` (Optional)
|
||||||
Set the default value for if an index should be indexed in the background
|
Set the default value for if an index should be indexed in the background
|
||||||
@@ -552,15 +540,10 @@ There are a few top level defaults for all indexes that can be set::
|
|||||||
:attr:`index_cls` (Optional)
|
:attr:`index_cls` (Optional)
|
||||||
A way to turn off a specific index for _cls.
|
A way to turn off a specific index for _cls.
|
||||||
|
|
||||||
:attr:`auto_create_index` (Optional)
|
|
||||||
When this is True (default), MongoEngine will ensure that the correct
|
|
||||||
indexes exist in MongoDB each time a command is run. This can be disabled
|
|
||||||
in systems where indexes are managed separately. Disabling this will improve
|
|
||||||
performance.
|
|
||||||
|
|
||||||
:attr:`index_drop_dups` (Optional)
|
:attr:`index_drop_dups` (Optional)
|
||||||
Set the default value for if an index should drop duplicates
|
Set the default value for if an index should drop duplicates
|
||||||
Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
|
|
||||||
|
.. note:: Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
|
||||||
and has no effect
|
and has no effect
|
||||||
|
|
||||||
|
|
||||||
@@ -635,7 +618,7 @@ collection after a given period. See the official
|
|||||||
documentation for more information. A common usecase might be session data::
|
documentation for more information. A common usecase might be session data::
|
||||||
|
|
||||||
class Session(Document):
|
class Session(Document):
|
||||||
created = DateTimeField(default=datetime.utcnow)
|
created = DateTimeField(default=datetime.now)
|
||||||
meta = {
|
meta = {
|
||||||
'indexes': [
|
'indexes': [
|
||||||
{'fields': ['created'], 'expireAfterSeconds': 3600}
|
{'fields': ['created'], 'expireAfterSeconds': 3600}
|
||||||
@@ -742,9 +725,6 @@ document.::
|
|||||||
.. note:: From 0.8 onwards :attr:`allow_inheritance` defaults
|
.. note:: From 0.8 onwards :attr:`allow_inheritance` defaults
|
||||||
to False, meaning you must set it to True to use inheritance.
|
to False, meaning you must set it to True to use inheritance.
|
||||||
|
|
||||||
Setting :attr:`allow_inheritance` to True should also be used in
|
|
||||||
:class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it
|
|
||||||
|
|
||||||
Working with existing data
|
Working with existing data
|
||||||
--------------------------
|
--------------------------
|
||||||
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and
|
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and
|
||||||
|
@@ -57,8 +57,7 @@ document values for example::
|
|||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""Ensures that only published essays have a `pub_date` and
|
"""Ensures that only published essays have a `pub_date` and
|
||||||
automatically sets `pub_date` if essay is published and `pub_date`
|
automatically sets the pub_date if published and not set"""
|
||||||
is not set"""
|
|
||||||
if self.status == 'Draft' and self.pub_date is not None:
|
if self.status == 'Draft' and self.pub_date is not None:
|
||||||
msg = 'Draft entries should not have a publication date.'
|
msg = 'Draft entries should not have a publication date.'
|
||||||
raise ValidationError(msg)
|
raise ValidationError(msg)
|
||||||
|
@@ -53,8 +53,7 @@ Deletion
|
|||||||
|
|
||||||
Deleting stored files is achieved with the :func:`delete` method::
|
Deleting stored files is achieved with the :func:`delete` method::
|
||||||
|
|
||||||
marmot.photo.delete() # Deletes the GridFS document
|
marmot.photo.delete()
|
||||||
marmot.save() # Saves the GridFS reference (being None) contained in the marmot instance
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
@@ -72,5 +71,4 @@ Files can be replaced with the :func:`replace` method. This works just like
|
|||||||
the :func:`put` method so even metadata can (and should) be replaced::
|
the :func:`put` method so even metadata can (and should) be replaced::
|
||||||
|
|
||||||
another_marmot = open('another_marmot.png', 'rb')
|
another_marmot = open('another_marmot.png', 'rb')
|
||||||
marmot.photo.replace(another_marmot, content_type='image/png') # Replaces the GridFS document
|
marmot.photo.replace(another_marmot, content_type='image/png')
|
||||||
marmot.save() # Replaces the GridFS reference contained in marmot instance
|
|
||||||
|
@@ -456,14 +456,14 @@ data. To turn off dereferencing of the results of a query use
|
|||||||
:func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so::
|
:func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so::
|
||||||
|
|
||||||
post = Post.objects.no_dereference().first()
|
post = Post.objects.no_dereference().first()
|
||||||
assert(isinstance(post.author, DBRef))
|
assert(isinstance(post.author, ObjectId))
|
||||||
|
|
||||||
You can also turn off all dereferencing for a fixed period by using the
|
You can also turn off all dereferencing for a fixed period by using the
|
||||||
:class:`~mongoengine.context_managers.no_dereference` context manager::
|
:class:`~mongoengine.context_managers.no_dereference` context manager::
|
||||||
|
|
||||||
with no_dereference(Post) as Post:
|
with no_dereference(Post) as Post:
|
||||||
post = Post.objects.first()
|
post = Post.objects.first()
|
||||||
assert(isinstance(post.author, DBRef))
|
assert(isinstance(post.author, ObjectId))
|
||||||
|
|
||||||
# Outside the context manager dereferencing occurs.
|
# Outside the context manager dereferencing occurs.
|
||||||
assert(isinstance(post.author, User))
|
assert(isinstance(post.author, User))
|
||||||
@@ -565,15 +565,6 @@ cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
|
|||||||
>>> post.tags
|
>>> post.tags
|
||||||
['database', 'mongodb']
|
['database', 'mongodb']
|
||||||
|
|
||||||
From MongoDB version 2.6, push operator supports $position value which allows
|
|
||||||
to push values with index.
|
|
||||||
>>> post = BlogPost(title="Test", tags=["mongo"])
|
|
||||||
>>> post.save()
|
|
||||||
>>> post.update(push__tags__0=["database", "code"])
|
|
||||||
>>> post.reload()
|
|
||||||
>>> post.tags
|
|
||||||
['database', 'code', 'mongo']
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Currently only top level lists are handled, future versions of mongodb /
|
Currently only top level lists are handled, future versions of mongodb /
|
||||||
pymongo plan to support nested positional operators. See `The $ positional
|
pymongo plan to support nested positional operators. See `The $ positional
|
||||||
|
@@ -43,10 +43,10 @@ Available signals include:
|
|||||||
has taken place but before saving.
|
has taken place but before saving.
|
||||||
|
|
||||||
`post_save`
|
`post_save`
|
||||||
Called within :meth:`~mongoengine.Document.save` after most actions
|
Called within :meth:`~mongoengine.Document.save` after all actions
|
||||||
(validation, insert/update, and cascades, but not clearing dirty flags) have
|
(validation, insert/update, cascades, clearing dirty flags) have completed
|
||||||
completed successfully. Passed the additional boolean keyword argument
|
successfully. Passed the additional boolean keyword argument `created` to
|
||||||
`created` to indicate if the save was an insert or an update.
|
indicate if the save was an insert or an update.
|
||||||
|
|
||||||
`pre_delete`
|
`pre_delete`
|
||||||
Called within :meth:`~mongoengine.Document.delete` prior to
|
Called within :meth:`~mongoengine.Document.delete` prior to
|
||||||
@@ -113,10 +113,6 @@ handlers within your subclass::
|
|||||||
signals.pre_save.connect(Author.pre_save, sender=Author)
|
signals.pre_save.connect(Author.pre_save, sender=Author)
|
||||||
signals.post_save.connect(Author.post_save, sender=Author)
|
signals.post_save.connect(Author.post_save, sender=Author)
|
||||||
|
|
||||||
.. warning::
|
|
||||||
|
|
||||||
Note that EmbeddedDocument only supports pre/post_init signals. pre/post_save, etc should be attached to Document's class only. Attaching pre_save to an EmbeddedDocument is ignored silently.
|
|
||||||
|
|
||||||
Finally, you can also use this small decorator to quickly create a number of
|
Finally, you can also use this small decorator to quickly create a number of
|
||||||
signals and attach them to your :class:`~mongoengine.Document` or
|
signals and attach them to your :class:`~mongoengine.Document` or
|
||||||
:class:`~mongoengine.EmbeddedDocument` subclasses as class decorators::
|
:class:`~mongoengine.EmbeddedDocument` subclasses as class decorators::
|
||||||
|
@@ -48,4 +48,4 @@ Ordering by text score
|
|||||||
|
|
||||||
::
|
::
|
||||||
|
|
||||||
objects = News.objects.search_text('mongo').order_by('$text_score')
|
objects = News.objects.search('mongo').order_by('$text_score')
|
||||||
|
@@ -86,7 +86,7 @@ of them stand out as particularly intuitive solutions.
|
|||||||
Posts
|
Posts
|
||||||
^^^^^
|
^^^^^
|
||||||
|
|
||||||
Happily MongoDB *isn't* a relational database, so we're not going to do it that
|
Happily mongoDB *isn't* a relational database, so we're not going to do it that
|
||||||
way. As it turns out, we can use MongoDB's schemaless nature to provide us with
|
way. As it turns out, we can use MongoDB's schemaless nature to provide us with
|
||||||
a much nicer solution. We will store all of the posts in *one collection* and
|
a much nicer solution. We will store all of the posts in *one collection* and
|
||||||
each post type will only store the fields it needs. If we later want to add
|
each post type will only store the fields it needs. If we later want to add
|
||||||
@@ -153,7 +153,7 @@ post. This works, but there is no real reason to be storing the comments
|
|||||||
separately from their associated posts, other than to work around the
|
separately from their associated posts, other than to work around the
|
||||||
relational model. Using MongoDB we can store the comments as a list of
|
relational model. Using MongoDB we can store the comments as a list of
|
||||||
*embedded documents* directly on a post document. An embedded document should
|
*embedded documents* directly on a post document. An embedded document should
|
||||||
be treated no differently than a regular document; it just doesn't have its own
|
be treated no differently that a regular document; it just doesn't have its own
|
||||||
collection in the database. Using MongoEngine, we can define the structure of
|
collection in the database. Using MongoEngine, we can define the structure of
|
||||||
embedded documents, along with utility methods, in exactly the same way we do
|
embedded documents, along with utility methods, in exactly the same way we do
|
||||||
with regular documents::
|
with regular documents::
|
||||||
|
@@ -6,23 +6,6 @@ Development
|
|||||||
***********
|
***********
|
||||||
(Fill this out whenever you introduce breaking changes to MongoEngine)
|
(Fill this out whenever you introduce breaking changes to MongoEngine)
|
||||||
|
|
||||||
URLField's constructor no longer takes `verify_exists`
|
|
||||||
|
|
||||||
0.15.0
|
|
||||||
******
|
|
||||||
|
|
||||||
0.14.0
|
|
||||||
******
|
|
||||||
This release includes a few bug fixes and a significant code cleanup. The most
|
|
||||||
important change is that `QuerySet.as_pymongo` no longer supports a
|
|
||||||
`coerce_types` mode. If you used it in the past, a) please let us know of your
|
|
||||||
use case, b) you'll need to override `as_pymongo` to get the desired outcome.
|
|
||||||
|
|
||||||
This release also makes the EmbeddedDocument not hashable by default. If you
|
|
||||||
use embedded documents in sets or dictionaries, you might have to override
|
|
||||||
`__hash__` and implement a hashing logic specific to your use case. See #1528
|
|
||||||
for the reason behind this change.
|
|
||||||
|
|
||||||
0.13.0
|
0.13.0
|
||||||
******
|
******
|
||||||
This release adds Unicode support to the `EmailField` and changes its
|
This release adds Unicode support to the `EmailField` and changes its
|
||||||
|
@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
|
|||||||
list(signals.__all__) + list(errors.__all__))
|
list(signals.__all__) + list(errors.__all__))
|
||||||
|
|
||||||
|
|
||||||
VERSION = (0, 16, 0)
|
VERSION = (0, 13, 0)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
|
@@ -15,7 +15,7 @@ __all__ = (
|
|||||||
'UPDATE_OPERATORS', '_document_registry', 'get_document',
|
'UPDATE_OPERATORS', '_document_registry', 'get_document',
|
||||||
|
|
||||||
# datastructures
|
# datastructures
|
||||||
'BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference',
|
'BaseDict', 'BaseList', 'EmbeddedDocumentList',
|
||||||
|
|
||||||
# document
|
# document
|
||||||
'BaseDocument',
|
'BaseDocument',
|
||||||
|
@@ -3,10 +3,9 @@ from mongoengine.errors import NotRegistered
|
|||||||
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
|
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
|
||||||
|
|
||||||
|
|
||||||
UPDATE_OPERATORS = {'set', 'unset', 'inc', 'dec', 'mul',
|
UPDATE_OPERATORS = set(['set', 'unset', 'inc', 'dec', 'pop', 'push',
|
||||||
'pop', 'push', 'push_all', 'pull',
|
'push_all', 'pull', 'pull_all', 'add_to_set',
|
||||||
'pull_all', 'add_to_set', 'set_on_insert',
|
'set_on_insert', 'min', 'max', 'rename'])
|
||||||
'min', 'max', 'rename'}
|
|
||||||
|
|
||||||
|
|
||||||
_document_registry = {}
|
_document_registry = {}
|
||||||
@@ -19,7 +18,7 @@ def get_document(name):
|
|||||||
# Possible old style name
|
# Possible old style name
|
||||||
single_end = name.split('.')[-1]
|
single_end = name.split('.')[-1]
|
||||||
compound_end = '.%s' % single_end
|
compound_end = '.%s' % single_end
|
||||||
possible_match = [k for k in _document_registry
|
possible_match = [k for k in _document_registry.keys()
|
||||||
if k.endswith(compound_end) or k == single_end]
|
if k.endswith(compound_end) or k == single_end]
|
||||||
if len(possible_match) == 1:
|
if len(possible_match) == 1:
|
||||||
doc = _document_registry.get(possible_match.pop(), None)
|
doc = _document_registry.get(possible_match.pop(), None)
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
|
import itertools
|
||||||
import weakref
|
import weakref
|
||||||
|
|
||||||
from bson import DBRef
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
|
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
|
||||||
|
|
||||||
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
|
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList')
|
||||||
|
|
||||||
|
|
||||||
class BaseDict(dict):
|
class BaseDict(dict):
|
||||||
@@ -17,9 +17,10 @@ class BaseDict(dict):
|
|||||||
_name = None
|
_name = None
|
||||||
|
|
||||||
def __init__(self, dict_items, instance, name):
|
def __init__(self, dict_items, instance, name):
|
||||||
BaseDocument = _import_class('BaseDocument')
|
Document = _import_class('Document')
|
||||||
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
|
|
||||||
if isinstance(instance, BaseDocument):
|
if isinstance(instance, (Document, EmbeddedDocument)):
|
||||||
self._instance = weakref.proxy(instance)
|
self._instance = weakref.proxy(instance)
|
||||||
self._name = name
|
self._name = name
|
||||||
super(BaseDict, self).__init__(dict_items)
|
super(BaseDict, self).__init__(dict_items)
|
||||||
@@ -30,11 +31,11 @@ class BaseDict(dict):
|
|||||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
elif isinstance(value, dict) and not isinstance(value, BaseDict):
|
elif not isinstance(value, BaseDict) and isinstance(value, dict):
|
||||||
value = BaseDict(value, None, '%s.%s' % (self._name, key))
|
value = BaseDict(value, None, '%s.%s' % (self._name, key))
|
||||||
super(BaseDict, self).__setitem__(key, value)
|
super(BaseDict, self).__setitem__(key, value)
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
elif isinstance(value, list) and not isinstance(value, BaseList):
|
elif not isinstance(value, BaseList) and isinstance(value, list):
|
||||||
value = BaseList(value, None, '%s.%s' % (self._name, key))
|
value = BaseList(value, None, '%s.%s' % (self._name, key))
|
||||||
super(BaseDict, self).__setitem__(key, value)
|
super(BaseDict, self).__setitem__(key, value)
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
@@ -101,9 +102,10 @@ class BaseList(list):
|
|||||||
_name = None
|
_name = None
|
||||||
|
|
||||||
def __init__(self, list_items, instance, name):
|
def __init__(self, list_items, instance, name):
|
||||||
BaseDocument = _import_class('BaseDocument')
|
Document = _import_class('Document')
|
||||||
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
|
|
||||||
if isinstance(instance, BaseDocument):
|
if isinstance(instance, (Document, EmbeddedDocument)):
|
||||||
self._instance = weakref.proxy(instance)
|
self._instance = weakref.proxy(instance)
|
||||||
self._name = name
|
self._name = name
|
||||||
super(BaseList, self).__init__(list_items)
|
super(BaseList, self).__init__(list_items)
|
||||||
@@ -114,19 +116,19 @@ class BaseList(list):
|
|||||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
if isinstance(value, EmbeddedDocument) and value._instance is None:
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
elif isinstance(value, dict) and not isinstance(value, BaseDict):
|
elif not isinstance(value, BaseDict) and isinstance(value, dict):
|
||||||
value = BaseDict(value, None, '%s.%s' % (self._name, key))
|
value = BaseDict(value, None, '%s.%s' % (self._name, key))
|
||||||
super(BaseList, self).__setitem__(key, value)
|
super(BaseList, self).__setitem__(key, value)
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
elif isinstance(value, list) and not isinstance(value, BaseList):
|
elif not isinstance(value, BaseList) and isinstance(value, list):
|
||||||
value = BaseList(value, None, '%s.%s' % (self._name, key))
|
value = BaseList(value, None, '%s.%s' % (self._name, key))
|
||||||
super(BaseList, self).__setitem__(key, value)
|
super(BaseList, self).__setitem__(key, value)
|
||||||
value._instance = self._instance
|
value._instance = self._instance
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
for v in super(BaseList, self).__iter__():
|
for i in xrange(self.__len__()):
|
||||||
yield v
|
yield self[i]
|
||||||
|
|
||||||
def __setitem__(self, key, value, *args, **kwargs):
|
def __setitem__(self, key, value, *args, **kwargs):
|
||||||
if isinstance(key, slice):
|
if isinstance(key, slice):
|
||||||
@@ -135,7 +137,7 @@ class BaseList(list):
|
|||||||
self._mark_as_changed(key)
|
self._mark_as_changed(key)
|
||||||
return super(BaseList, self).__setitem__(key, value)
|
return super(BaseList, self).__setitem__(key, value)
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key, *args, **kwargs):
|
||||||
self._mark_as_changed()
|
self._mark_as_changed()
|
||||||
return super(BaseList, self).__delitem__(key)
|
return super(BaseList, self).__delitem__(key)
|
||||||
|
|
||||||
@@ -184,7 +186,7 @@ class BaseList(list):
|
|||||||
self._mark_as_changed()
|
self._mark_as_changed()
|
||||||
return super(BaseList, self).remove(*args, **kwargs)
|
return super(BaseList, self).remove(*args, **kwargs)
|
||||||
|
|
||||||
def reverse(self):
|
def reverse(self, *args, **kwargs):
|
||||||
self._mark_as_changed()
|
self._mark_as_changed()
|
||||||
return super(BaseList, self).reverse()
|
return super(BaseList, self).reverse()
|
||||||
|
|
||||||
@@ -231,9 +233,6 @@ class EmbeddedDocumentList(BaseList):
|
|||||||
Filters the list by only including embedded documents with the
|
Filters the list by only including embedded documents with the
|
||||||
given keyword arguments.
|
given keyword arguments.
|
||||||
|
|
||||||
This method only supports simple comparison (e.g: .filter(name='John Doe'))
|
|
||||||
and does not support operators like __gte, __lte, __icontains like queryset.filter does
|
|
||||||
|
|
||||||
:param kwargs: The keyword arguments corresponding to the fields to
|
:param kwargs: The keyword arguments corresponding to the fields to
|
||||||
filter on. *Multiple arguments are treated as if they are ANDed
|
filter on. *Multiple arguments are treated as if they are ANDed
|
||||||
together.*
|
together.*
|
||||||
@@ -351,8 +350,7 @@ class EmbeddedDocumentList(BaseList):
|
|||||||
|
|
||||||
def update(self, **update):
|
def update(self, **update):
|
||||||
"""
|
"""
|
||||||
Updates the embedded documents with the given replacement values. This
|
Updates the embedded documents with the given update values.
|
||||||
function does not support mongoDB update operators such as ``inc__``.
|
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
The embedded document changes are not automatically saved
|
The embedded document changes are not automatically saved
|
||||||
@@ -374,7 +372,7 @@ class EmbeddedDocumentList(BaseList):
|
|||||||
|
|
||||||
class StrictDict(object):
|
class StrictDict(object):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
_special_fields = {'get', 'pop', 'iteritems', 'items', 'keys', 'create'}
|
_special_fields = set(['get', 'pop', 'iteritems', 'items', 'keys', 'create'])
|
||||||
_classes = {}
|
_classes = {}
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -449,40 +447,40 @@ class StrictDict(object):
|
|||||||
return cls._classes[allowed_keys]
|
return cls._classes[allowed_keys]
|
||||||
|
|
||||||
|
|
||||||
class LazyReference(DBRef):
|
class SemiStrictDict(StrictDict):
|
||||||
__slots__ = ('_cached_doc', 'passthrough', 'document_type')
|
__slots__ = ('_extras', )
|
||||||
|
_classes = {}
|
||||||
|
|
||||||
def fetch(self, force=False):
|
def __getattr__(self, attr):
|
||||||
if not self._cached_doc or force:
|
|
||||||
self._cached_doc = self.document_type.objects.get(pk=self.pk)
|
|
||||||
if not self._cached_doc:
|
|
||||||
raise DoesNotExist('Trying to dereference unknown document %s' % (self))
|
|
||||||
return self._cached_doc
|
|
||||||
|
|
||||||
@property
|
|
||||||
def pk(self):
|
|
||||||
return self.id
|
|
||||||
|
|
||||||
def __init__(self, document_type, pk, cached_doc=None, passthrough=False):
|
|
||||||
self.document_type = document_type
|
|
||||||
self._cached_doc = cached_doc
|
|
||||||
self.passthrough = passthrough
|
|
||||||
super(LazyReference, self).__init__(self.document_type._get_collection_name(), pk)
|
|
||||||
|
|
||||||
def __getitem__(self, name):
|
|
||||||
if not self.passthrough:
|
|
||||||
raise KeyError()
|
|
||||||
document = self.fetch()
|
|
||||||
return document[name]
|
|
||||||
|
|
||||||
def __getattr__(self, name):
|
|
||||||
if not object.__getattribute__(self, 'passthrough'):
|
|
||||||
raise AttributeError()
|
|
||||||
document = self.fetch()
|
|
||||||
try:
|
try:
|
||||||
return document[name]
|
super(SemiStrictDict, self).__getattr__(attr)
|
||||||
except KeyError:
|
except AttributeError:
|
||||||
raise AttributeError()
|
try:
|
||||||
|
return self.__getattribute__('_extras')[attr]
|
||||||
|
except KeyError as e:
|
||||||
|
raise AttributeError(e)
|
||||||
|
|
||||||
def __repr__(self):
|
def __setattr__(self, attr, value):
|
||||||
return "<LazyReference(%s, %r)>" % (self.document_type, self.pk)
|
try:
|
||||||
|
super(SemiStrictDict, self).__setattr__(attr, value)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
self._extras[attr] = value
|
||||||
|
except AttributeError:
|
||||||
|
self._extras = {attr: value}
|
||||||
|
|
||||||
|
def __delattr__(self, attr):
|
||||||
|
try:
|
||||||
|
super(SemiStrictDict, self).__delattr__(attr)
|
||||||
|
except AttributeError:
|
||||||
|
try:
|
||||||
|
del self._extras[attr]
|
||||||
|
except KeyError as e:
|
||||||
|
raise AttributeError(e)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
try:
|
||||||
|
extras_iter = iter(self.__getattribute__('_extras'))
|
||||||
|
except AttributeError:
|
||||||
|
extras_iter = ()
|
||||||
|
return itertools.chain(super(SemiStrictDict, self).__iter__(), extras_iter)
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import numbers
|
import numbers
|
||||||
|
from collections import Hashable
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from bson import ObjectId, json_util
|
from bson import ObjectId, json_util
|
||||||
@@ -12,15 +13,13 @@ from mongoengine import signals
|
|||||||
from mongoengine.base.common import get_document
|
from mongoengine.base.common import get_document
|
||||||
from mongoengine.base.datastructures import (BaseDict, BaseList,
|
from mongoengine.base.datastructures import (BaseDict, BaseList,
|
||||||
EmbeddedDocumentList,
|
EmbeddedDocumentList,
|
||||||
LazyReference,
|
SemiStrictDict, StrictDict)
|
||||||
StrictDict)
|
|
||||||
from mongoengine.base.fields import ComplexBaseField
|
from mongoengine.base.fields import ComplexBaseField
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.errors import (FieldDoesNotExist, InvalidDocumentError,
|
from mongoengine.errors import (FieldDoesNotExist, InvalidDocumentError,
|
||||||
LookUpError, OperationError, ValidationError)
|
LookUpError, OperationError, ValidationError)
|
||||||
from mongoengine.python_support import Hashable
|
|
||||||
|
|
||||||
__all__ = ('BaseDocument', 'NON_FIELD_ERRORS')
|
__all__ = ('BaseDocument',)
|
||||||
|
|
||||||
NON_FIELD_ERRORS = '__all__'
|
NON_FIELD_ERRORS = '__all__'
|
||||||
|
|
||||||
@@ -80,7 +79,8 @@ class BaseDocument(object):
|
|||||||
if self.STRICT and not self._dynamic:
|
if self.STRICT and not self._dynamic:
|
||||||
self._data = StrictDict.create(allowed_keys=self._fields_ordered)()
|
self._data = StrictDict.create(allowed_keys=self._fields_ordered)()
|
||||||
else:
|
else:
|
||||||
self._data = {}
|
self._data = SemiStrictDict.create(
|
||||||
|
allowed_keys=self._fields_ordered)()
|
||||||
|
|
||||||
self._dynamic_fields = SON()
|
self._dynamic_fields = SON()
|
||||||
|
|
||||||
@@ -91,17 +91,22 @@ class BaseDocument(object):
|
|||||||
value = getattr(self, key, None)
|
value = getattr(self, key, None)
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
if '_cls' not in values:
|
||||||
|
self._cls = self._class_name
|
||||||
|
|
||||||
# Set passed values after initialisation
|
# Set passed values after initialisation
|
||||||
if self._dynamic:
|
if self._dynamic:
|
||||||
dynamic_data = {}
|
dynamic_data = {}
|
||||||
for key, value in values.iteritems():
|
for key, value in values.iteritems():
|
||||||
if key in self._fields or key == '_id':
|
if key in self._fields or key == '_id':
|
||||||
setattr(self, key, value)
|
setattr(self, key, value)
|
||||||
else:
|
elif self._dynamic:
|
||||||
dynamic_data[key] = value
|
dynamic_data[key] = value
|
||||||
else:
|
else:
|
||||||
FileField = _import_class('FileField')
|
FileField = _import_class('FileField')
|
||||||
for key, value in values.iteritems():
|
for key, value in values.iteritems():
|
||||||
|
if key == '__auto_convert':
|
||||||
|
continue
|
||||||
key = self._reverse_db_field_map.get(key, key)
|
key = self._reverse_db_field_map.get(key, key)
|
||||||
if key in self._fields or key in ('id', 'pk', '_cls'):
|
if key in self._fields or key in ('id', 'pk', '_cls'):
|
||||||
if __auto_convert and value is not None:
|
if __auto_convert and value is not None:
|
||||||
@@ -142,7 +147,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
if not hasattr(self, name) and not name.startswith('_'):
|
if not hasattr(self, name) and not name.startswith('_'):
|
||||||
DynamicField = _import_class('DynamicField')
|
DynamicField = _import_class('DynamicField')
|
||||||
field = DynamicField(db_field=name, null=True)
|
field = DynamicField(db_field=name)
|
||||||
field.name = name
|
field.name = name
|
||||||
self._dynamic_fields[name] = field
|
self._dynamic_fields[name] = field
|
||||||
self._fields_ordered += (name,)
|
self._fields_ordered += (name,)
|
||||||
@@ -299,7 +304,7 @@ class BaseDocument(object):
|
|||||||
data['_cls'] = self._class_name
|
data['_cls'] = self._class_name
|
||||||
|
|
||||||
# only root fields ['test1.a', 'test2'] => ['test1', 'test2']
|
# only root fields ['test1.a', 'test2'] => ['test1', 'test2']
|
||||||
root_fields = {f.split('.')[0] for f in fields}
|
root_fields = set([f.split('.')[0] for f in fields])
|
||||||
|
|
||||||
for field_name in self:
|
for field_name in self:
|
||||||
if root_fields and field_name not in root_fields:
|
if root_fields and field_name not in root_fields:
|
||||||
@@ -332,7 +337,7 @@ class BaseDocument(object):
|
|||||||
value = field.generate()
|
value = field.generate()
|
||||||
self._data[field_name] = value
|
self._data[field_name] = value
|
||||||
|
|
||||||
if (value is not None) or (field.null):
|
if value is not None:
|
||||||
if use_db_field:
|
if use_db_field:
|
||||||
data[field.db_field] = value
|
data[field.db_field] = value
|
||||||
else:
|
else:
|
||||||
@@ -401,15 +406,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_json(cls, json_data, created=False):
|
def from_json(cls, json_data, created=False):
|
||||||
"""Converts json data to a Document instance
|
"""Converts json data to an unsaved document instance"""
|
||||||
|
|
||||||
:param json_data: The json data to load into the Document
|
|
||||||
:param created: If True, the document will be considered as a brand new document
|
|
||||||
If False and an id is provided, it will consider that the data being
|
|
||||||
loaded corresponds to what's already in the database (This has an impact of subsequent call to .save())
|
|
||||||
If False and no id is provided, it will consider the data as a new document
|
|
||||||
(default ``False``)
|
|
||||||
"""
|
|
||||||
return cls._from_son(json_util.loads(json_data), created=created)
|
return cls._from_son(json_util.loads(json_data), created=created)
|
||||||
|
|
||||||
def __expand_dynamic_values(self, name, value):
|
def __expand_dynamic_values(self, name, value):
|
||||||
@@ -492,7 +489,7 @@ class BaseDocument(object):
|
|||||||
else:
|
else:
|
||||||
data = getattr(data, part, None)
|
data = getattr(data, part, None)
|
||||||
|
|
||||||
if not isinstance(data, LazyReference) and hasattr(data, '_changed_fields'):
|
if hasattr(data, '_changed_fields'):
|
||||||
if getattr(data, '_is_document', False):
|
if getattr(data, '_is_document', False):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -500,13 +497,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
self._changed_fields = []
|
self._changed_fields = []
|
||||||
|
|
||||||
def _nestable_types_changed_fields(self, changed_fields, base_key, data):
|
def _nestable_types_changed_fields(self, changed_fields, key, data, inspected):
|
||||||
"""Inspect nested data for changed fields
|
|
||||||
|
|
||||||
:param changed_fields: Previously collected changed fields
|
|
||||||
:param base_key: The base key that must be used to prepend changes to this data
|
|
||||||
:param data: data to inspect for changes
|
|
||||||
"""
|
|
||||||
# Loop list / dict fields as they contain documents
|
# Loop list / dict fields as they contain documents
|
||||||
# Determine the iterator to use
|
# Determine the iterator to use
|
||||||
if not hasattr(data, 'items'):
|
if not hasattr(data, 'items'):
|
||||||
@@ -514,60 +505,68 @@ class BaseDocument(object):
|
|||||||
else:
|
else:
|
||||||
iterator = data.iteritems()
|
iterator = data.iteritems()
|
||||||
|
|
||||||
for index_or_key, value in iterator:
|
for index, value in iterator:
|
||||||
item_key = '%s%s.' % (base_key, index_or_key)
|
list_key = '%s%s.' % (key, index)
|
||||||
# don't check anything lower if this key is already marked
|
# don't check anything lower if this key is already marked
|
||||||
# as changed.
|
# as changed.
|
||||||
if item_key[:-1] in changed_fields:
|
if list_key[:-1] in changed_fields:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if hasattr(value, '_get_changed_fields'):
|
if hasattr(value, '_get_changed_fields'):
|
||||||
changed = value._get_changed_fields()
|
changed = value._get_changed_fields(inspected)
|
||||||
changed_fields += ['%s%s' % (item_key, k) for k in changed if k]
|
changed_fields += ['%s%s' % (list_key, k)
|
||||||
|
for k in changed if k]
|
||||||
elif isinstance(value, (list, tuple, dict)):
|
elif isinstance(value, (list, tuple, dict)):
|
||||||
self._nestable_types_changed_fields(
|
self._nestable_types_changed_fields(
|
||||||
changed_fields, item_key, value)
|
changed_fields, list_key, value, inspected)
|
||||||
|
|
||||||
def _get_changed_fields(self):
|
def _get_changed_fields(self, inspected=None):
|
||||||
"""Return a list of all fields that have explicitly been changed.
|
"""Return a list of all fields that have explicitly been changed.
|
||||||
"""
|
"""
|
||||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
|
DynamicEmbeddedDocument = _import_class('DynamicEmbeddedDocument')
|
||||||
ReferenceField = _import_class('ReferenceField')
|
ReferenceField = _import_class('ReferenceField')
|
||||||
GenericReferenceField = _import_class('GenericReferenceField')
|
|
||||||
SortedListField = _import_class('SortedListField')
|
SortedListField = _import_class('SortedListField')
|
||||||
|
|
||||||
changed_fields = []
|
changed_fields = []
|
||||||
changed_fields += getattr(self, '_changed_fields', [])
|
changed_fields += getattr(self, '_changed_fields', [])
|
||||||
|
|
||||||
|
inspected = inspected or set()
|
||||||
|
if hasattr(self, 'id') and isinstance(self.id, Hashable):
|
||||||
|
if self.id in inspected:
|
||||||
|
return changed_fields
|
||||||
|
inspected.add(self.id)
|
||||||
|
|
||||||
for field_name in self._fields_ordered:
|
for field_name in self._fields_ordered:
|
||||||
db_field_name = self._db_field_map.get(field_name, field_name)
|
db_field_name = self._db_field_map.get(field_name, field_name)
|
||||||
key = '%s.' % db_field_name
|
key = '%s.' % db_field_name
|
||||||
data = self._data.get(field_name, None)
|
data = self._data.get(field_name, None)
|
||||||
field = self._fields.get(field_name)
|
field = self._fields.get(field_name)
|
||||||
|
|
||||||
if db_field_name in changed_fields:
|
if hasattr(data, 'id'):
|
||||||
# Whole field already marked as changed, no need to go further
|
if data.id in inspected:
|
||||||
|
continue
|
||||||
|
if isinstance(field, ReferenceField):
|
||||||
continue
|
continue
|
||||||
|
elif (
|
||||||
if isinstance(field, ReferenceField): # Don't follow referenced documents
|
isinstance(data, (EmbeddedDocument, DynamicEmbeddedDocument)) and
|
||||||
continue
|
db_field_name not in changed_fields
|
||||||
|
):
|
||||||
if isinstance(data, EmbeddedDocument):
|
|
||||||
# Find all embedded fields that have been changed
|
# Find all embedded fields that have been changed
|
||||||
changed = data._get_changed_fields()
|
changed = data._get_changed_fields(inspected)
|
||||||
changed_fields += ['%s%s' % (key, k) for k in changed if k]
|
changed_fields += ['%s%s' % (key, k) for k in changed if k]
|
||||||
elif isinstance(data, (list, tuple, dict)):
|
elif (isinstance(data, (list, tuple, dict)) and
|
||||||
|
db_field_name not in changed_fields):
|
||||||
if (hasattr(field, 'field') and
|
if (hasattr(field, 'field') and
|
||||||
isinstance(field.field, (ReferenceField, GenericReferenceField))):
|
isinstance(field.field, ReferenceField)):
|
||||||
continue
|
continue
|
||||||
elif isinstance(field, SortedListField) and field._ordering:
|
elif isinstance(field, SortedListField) and field._ordering:
|
||||||
# if ordering is affected whole list is changed
|
# if ordering is affected whole list is changed
|
||||||
if any(field._ordering in d._changed_fields for d in data):
|
if any(map(lambda d: field._ordering in d._changed_fields, data)):
|
||||||
changed_fields.append(db_field_name)
|
changed_fields.append(db_field_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
self._nestable_types_changed_fields(
|
self._nestable_types_changed_fields(
|
||||||
changed_fields, key, data)
|
changed_fields, key, data, inspected)
|
||||||
return changed_fields
|
return changed_fields
|
||||||
|
|
||||||
def _delta(self):
|
def _delta(self):
|
||||||
@@ -695,7 +694,7 @@ class BaseDocument(object):
|
|||||||
|
|
||||||
fields = cls._fields
|
fields = cls._fields
|
||||||
if not _auto_dereference:
|
if not _auto_dereference:
|
||||||
fields = copy.deepcopy(fields)
|
fields = copy.copy(fields)
|
||||||
|
|
||||||
for field_name, field in fields.iteritems():
|
for field_name, field in fields.iteritems():
|
||||||
field._auto_dereference = _auto_dereference
|
field._auto_dereference = _auto_dereference
|
||||||
@@ -1081,11 +1080,5 @@ class BaseDocument(object):
|
|||||||
"""Return the display value for a choice field"""
|
"""Return the display value for a choice field"""
|
||||||
value = getattr(self, field.name)
|
value = getattr(self, field.name)
|
||||||
if field.choices and isinstance(field.choices[0], (list, tuple)):
|
if field.choices and isinstance(field.choices[0], (list, tuple)):
|
||||||
if value is None:
|
return dict(field.choices).get(value, value)
|
||||||
return None
|
|
||||||
sep = getattr(field, 'display_sep', ' ')
|
|
||||||
values = value if field.__class__.__name__ in ('ListField', 'SortedListField') else [value]
|
|
||||||
return sep.join([
|
|
||||||
six.text_type(dict(field.choices).get(val, val))
|
|
||||||
for val in values or []])
|
|
||||||
return value
|
return value
|
||||||
|
@@ -55,7 +55,7 @@ class BaseField(object):
|
|||||||
field. Generally this is deprecated in favour of the
|
field. Generally this is deprecated in favour of the
|
||||||
`FIELD.validate` method
|
`FIELD.validate` method
|
||||||
:param choices: (optional) The valid choices
|
:param choices: (optional) The valid choices
|
||||||
:param null: (optional) If the field value can be null. If no and there is a default value
|
:param null: (optional) Is the field value can be null. If no and there is a default value
|
||||||
then the default value is set
|
then the default value is set
|
||||||
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
|
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
|
||||||
means that uniqueness won't be enforced for `None` values
|
means that uniqueness won't be enforced for `None` values
|
||||||
@@ -130,6 +130,7 @@ class BaseField(object):
|
|||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
"""Descriptor for assigning a value to a field in a document.
|
"""Descriptor for assigning a value to a field in a document.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# If setting to None and there is a default
|
# If setting to None and there is a default
|
||||||
# Then set the value to the default value
|
# Then set the value to the default value
|
||||||
if value is None:
|
if value is None:
|
||||||
@@ -212,10 +213,8 @@ class BaseField(object):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Choices which are types other than Documents
|
# Choices which are types other than Documents
|
||||||
else:
|
elif value not in choice_list:
|
||||||
values = value if isinstance(value, (list, tuple)) else [value]
|
self.error('Value must be one of %s' % six.text_type(choice_list))
|
||||||
if len(set(values) - set(choice_list)):
|
|
||||||
self.error('Value must be one of %s' % six.text_type(choice_list))
|
|
||||||
|
|
||||||
def _validate(self, value, **kwargs):
|
def _validate(self, value, **kwargs):
|
||||||
# Check the Choices Constraint
|
# Check the Choices Constraint
|
||||||
@@ -266,15 +265,13 @@ class ComplexBaseField(BaseField):
|
|||||||
ReferenceField = _import_class('ReferenceField')
|
ReferenceField = _import_class('ReferenceField')
|
||||||
GenericReferenceField = _import_class('GenericReferenceField')
|
GenericReferenceField = _import_class('GenericReferenceField')
|
||||||
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
|
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
|
||||||
|
dereference = (self._auto_dereference and
|
||||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
|
||||||
|
|
||||||
dereference = (auto_dereference and
|
|
||||||
(self.field is None or isinstance(self.field,
|
(self.field is None or isinstance(self.field,
|
||||||
(GenericReferenceField, ReferenceField))))
|
(GenericReferenceField, ReferenceField))))
|
||||||
|
|
||||||
_dereference = _import_class('DeReference')()
|
_dereference = _import_class('DeReference')()
|
||||||
|
|
||||||
|
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
if instance._initialised and dereference and instance._data.get(self.name):
|
if instance._initialised and dereference and instance._data.get(self.name):
|
||||||
instance._data[self.name] = _dereference(
|
instance._data[self.name] = _dereference(
|
||||||
instance._data.get(self.name), max_depth=1, instance=instance,
|
instance._data.get(self.name), max_depth=1, instance=instance,
|
||||||
@@ -295,7 +292,7 @@ class ComplexBaseField(BaseField):
|
|||||||
value = BaseDict(value, instance, self.name)
|
value = BaseDict(value, instance, self.name)
|
||||||
instance._data[self.name] = value
|
instance._data[self.name] = value
|
||||||
|
|
||||||
if (auto_dereference and instance._initialised and
|
if (self._auto_dereference and instance._initialised and
|
||||||
isinstance(value, (BaseList, BaseDict)) and
|
isinstance(value, (BaseList, BaseDict)) and
|
||||||
not value._dereferenced):
|
not value._dereferenced):
|
||||||
value = _dereference(
|
value = _dereference(
|
||||||
@@ -314,16 +311,11 @@ class ComplexBaseField(BaseField):
|
|||||||
if hasattr(value, 'to_python'):
|
if hasattr(value, 'to_python'):
|
||||||
return value.to_python()
|
return value.to_python()
|
||||||
|
|
||||||
BaseDocument = _import_class('BaseDocument')
|
|
||||||
if isinstance(value, BaseDocument):
|
|
||||||
# Something is wrong, return the value as it is
|
|
||||||
return value
|
|
||||||
|
|
||||||
is_list = False
|
is_list = False
|
||||||
if not hasattr(value, 'items'):
|
if not hasattr(value, 'items'):
|
||||||
try:
|
try:
|
||||||
is_list = True
|
is_list = True
|
||||||
value = {idx: v for idx, v in enumerate(value)}
|
value = {k: v for k, v in enumerate(value)}
|
||||||
except TypeError: # Not iterable return the value
|
except TypeError: # Not iterable return the value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -508,7 +500,7 @@ class GeoJsonBaseField(BaseField):
|
|||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""Validate the GeoJson object based on its type."""
|
"""Validate the GeoJson object based on its type."""
|
||||||
if isinstance(value, dict):
|
if isinstance(value, dict):
|
||||||
if set(value.keys()) == {'type', 'coordinates'}:
|
if set(value.keys()) == set(['type', 'coordinates']):
|
||||||
if value['type'] != self._type:
|
if value['type'] != self._type:
|
||||||
self.error('%s type must be "%s"' %
|
self.error('%s type must be "%s"' %
|
||||||
(self._name, self._type))
|
(self._name, self._type))
|
||||||
|
@@ -18,14 +18,14 @@ class DocumentMetaclass(type):
|
|||||||
"""Metaclass for all documents."""
|
"""Metaclass for all documents."""
|
||||||
|
|
||||||
# TODO lower complexity of this method
|
# TODO lower complexity of this method
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
flattened_bases = mcs._get_bases(bases)
|
flattened_bases = cls._get_bases(bases)
|
||||||
super_new = super(DocumentMetaclass, mcs).__new__
|
super_new = super(DocumentMetaclass, cls).__new__
|
||||||
|
|
||||||
# If a base class just call super
|
# If a base class just call super
|
||||||
metaclass = attrs.get('my_metaclass')
|
metaclass = attrs.get('my_metaclass')
|
||||||
if metaclass and issubclass(metaclass, DocumentMetaclass):
|
if metaclass and issubclass(metaclass, DocumentMetaclass):
|
||||||
return super_new(mcs, name, bases, attrs)
|
return super_new(cls, name, bases, attrs)
|
||||||
|
|
||||||
attrs['_is_document'] = attrs.get('_is_document', False)
|
attrs['_is_document'] = attrs.get('_is_document', False)
|
||||||
attrs['_cached_reference_fields'] = []
|
attrs['_cached_reference_fields'] = []
|
||||||
@@ -121,8 +121,7 @@ class DocumentMetaclass(type):
|
|||||||
# inheritance of classes where inheritance is set to False
|
# inheritance of classes where inheritance is set to False
|
||||||
allow_inheritance = base._meta.get('allow_inheritance')
|
allow_inheritance = base._meta.get('allow_inheritance')
|
||||||
if not allow_inheritance and not base._meta.get('abstract'):
|
if not allow_inheritance and not base._meta.get('abstract'):
|
||||||
raise ValueError('Document %s may not be subclassed. '
|
raise ValueError('Document %s may not be subclassed' %
|
||||||
'To enable inheritance, use the "allow_inheritance" meta attribute.' %
|
|
||||||
base.__name__)
|
base.__name__)
|
||||||
|
|
||||||
# Get superclasses from last base superclass
|
# Get superclasses from last base superclass
|
||||||
@@ -139,7 +138,7 @@ class DocumentMetaclass(type):
|
|||||||
attrs['_types'] = attrs['_subclasses'] # TODO depreciate _types
|
attrs['_types'] = attrs['_subclasses'] # TODO depreciate _types
|
||||||
|
|
||||||
# Create the new_class
|
# Create the new_class
|
||||||
new_class = super_new(mcs, name, bases, attrs)
|
new_class = super_new(cls, name, bases, attrs)
|
||||||
|
|
||||||
# Set _subclasses
|
# Set _subclasses
|
||||||
for base in document_bases:
|
for base in document_bases:
|
||||||
@@ -148,7 +147,7 @@ class DocumentMetaclass(type):
|
|||||||
base._types = base._subclasses # TODO depreciate _types
|
base._types = base._subclasses # TODO depreciate _types
|
||||||
|
|
||||||
(Document, EmbeddedDocument, DictField,
|
(Document, EmbeddedDocument, DictField,
|
||||||
CachedReferenceField) = mcs._import_classes()
|
CachedReferenceField) = cls._import_classes()
|
||||||
|
|
||||||
if issubclass(new_class, Document):
|
if issubclass(new_class, Document):
|
||||||
new_class._collection = None
|
new_class._collection = None
|
||||||
@@ -220,26 +219,29 @@ class DocumentMetaclass(type):
|
|||||||
|
|
||||||
return new_class
|
return new_class
|
||||||
|
|
||||||
|
def add_to_class(self, name, value):
|
||||||
|
setattr(self, name, value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_bases(mcs, bases):
|
def _get_bases(cls, bases):
|
||||||
if isinstance(bases, BasesTuple):
|
if isinstance(bases, BasesTuple):
|
||||||
return bases
|
return bases
|
||||||
seen = []
|
seen = []
|
||||||
bases = mcs.__get_bases(bases)
|
bases = cls.__get_bases(bases)
|
||||||
unique_bases = (b for b in bases if not (b in seen or seen.append(b)))
|
unique_bases = (b for b in bases if not (b in seen or seen.append(b)))
|
||||||
return BasesTuple(unique_bases)
|
return BasesTuple(unique_bases)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def __get_bases(mcs, bases):
|
def __get_bases(cls, bases):
|
||||||
for base in bases:
|
for base in bases:
|
||||||
if base is object:
|
if base is object:
|
||||||
continue
|
continue
|
||||||
yield base
|
yield base
|
||||||
for child_base in mcs.__get_bases(base.__bases__):
|
for child_base in cls.__get_bases(base.__bases__):
|
||||||
yield child_base
|
yield child_base
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _import_classes(mcs):
|
def _import_classes(cls):
|
||||||
Document = _import_class('Document')
|
Document = _import_class('Document')
|
||||||
EmbeddedDocument = _import_class('EmbeddedDocument')
|
EmbeddedDocument = _import_class('EmbeddedDocument')
|
||||||
DictField = _import_class('DictField')
|
DictField = _import_class('DictField')
|
||||||
@@ -252,9 +254,9 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
collection in the database.
|
collection in the database.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __new__(mcs, name, bases, attrs):
|
def __new__(cls, name, bases, attrs):
|
||||||
flattened_bases = mcs._get_bases(bases)
|
flattened_bases = cls._get_bases(bases)
|
||||||
super_new = super(TopLevelDocumentMetaclass, mcs).__new__
|
super_new = super(TopLevelDocumentMetaclass, cls).__new__
|
||||||
|
|
||||||
# Set default _meta data if base class, otherwise get user defined meta
|
# Set default _meta data if base class, otherwise get user defined meta
|
||||||
if attrs.get('my_metaclass') == TopLevelDocumentMetaclass:
|
if attrs.get('my_metaclass') == TopLevelDocumentMetaclass:
|
||||||
@@ -317,7 +319,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
not parent_doc_cls._meta.get('abstract', False)):
|
not parent_doc_cls._meta.get('abstract', False)):
|
||||||
msg = 'Abstract document cannot have non-abstract base'
|
msg = 'Abstract document cannot have non-abstract base'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
return super_new(mcs, name, bases, attrs)
|
return super_new(cls, name, bases, attrs)
|
||||||
|
|
||||||
# Merge base class metas.
|
# Merge base class metas.
|
||||||
# Uses a special MetaDict that handles various merging rules
|
# Uses a special MetaDict that handles various merging rules
|
||||||
@@ -358,7 +360,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
attrs['_meta'] = meta
|
attrs['_meta'] = meta
|
||||||
|
|
||||||
# Call super and get the new class
|
# Call super and get the new class
|
||||||
new_class = super_new(mcs, name, bases, attrs)
|
new_class = super_new(cls, name, bases, attrs)
|
||||||
|
|
||||||
meta = new_class._meta
|
meta = new_class._meta
|
||||||
|
|
||||||
@@ -392,7 +394,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
'_auto_id_field', False)
|
'_auto_id_field', False)
|
||||||
if not new_class._meta.get('id_field'):
|
if not new_class._meta.get('id_field'):
|
||||||
# After 0.10, find not existing names, instead of overwriting
|
# After 0.10, find not existing names, instead of overwriting
|
||||||
id_name, id_db_name = mcs.get_auto_id_names(new_class)
|
id_name, id_db_name = cls.get_auto_id_names(new_class)
|
||||||
new_class._auto_id_field = True
|
new_class._auto_id_field = True
|
||||||
new_class._meta['id_field'] = id_name
|
new_class._meta['id_field'] = id_name
|
||||||
new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
|
new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
|
||||||
@@ -417,7 +419,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
|
|||||||
return new_class
|
return new_class
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_auto_id_names(mcs, new_class):
|
def get_auto_id_names(cls, new_class):
|
||||||
id_name, id_db_name = ('id', '_id')
|
id_name, id_db_name = ('id', '_id')
|
||||||
if id_name not in new_class._fields and \
|
if id_name not in new_class._fields and \
|
||||||
id_db_name not in (v.db_field for v in new_class._fields.values()):
|
id_db_name not in (v.db_field for v in new_class._fields.values()):
|
||||||
|
@@ -1,22 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
|
|
||||||
class LazyRegexCompiler(object):
|
|
||||||
"""Descriptor to allow lazy compilation of regex"""
|
|
||||||
|
|
||||||
def __init__(self, pattern, flags=0):
|
|
||||||
self._pattern = pattern
|
|
||||||
self._flags = flags
|
|
||||||
self._compiled_regex = None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def compiled_regex(self):
|
|
||||||
if self._compiled_regex is None:
|
|
||||||
self._compiled_regex = re.compile(self._pattern, self._flags)
|
|
||||||
return self._compiled_regex
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
return self.compiled_regex
|
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
|
||||||
raise AttributeError("Can not set attribute LazyRegexCompiler")
|
|
@@ -28,7 +28,7 @@ _connections = {}
|
|||||||
_dbs = {}
|
_dbs = {}
|
||||||
|
|
||||||
|
|
||||||
def register_connection(alias, db=None, name=None, host=None, port=None,
|
def register_connection(alias, name=None, host=None, port=None,
|
||||||
read_preference=READ_PREFERENCE,
|
read_preference=READ_PREFERENCE,
|
||||||
username=None, password=None,
|
username=None, password=None,
|
||||||
authentication_source=None,
|
authentication_source=None,
|
||||||
@@ -39,7 +39,6 @@ def register_connection(alias, db=None, name=None, host=None, port=None,
|
|||||||
:param alias: the name that will be used to refer to this connection
|
:param alias: the name that will be used to refer to this connection
|
||||||
throughout MongoEngine
|
throughout MongoEngine
|
||||||
:param name: the name of the specific database to use
|
:param name: the name of the specific database to use
|
||||||
:param db: the name of the database to use, for compatibility with connect
|
|
||||||
:param host: the host name of the :program:`mongod` instance to connect to
|
:param host: the host name of the :program:`mongod` instance to connect to
|
||||||
:param port: the port that the :program:`mongod` instance is running on
|
:param port: the port that the :program:`mongod` instance is running on
|
||||||
:param read_preference: The read preference for the collection
|
:param read_preference: The read preference for the collection
|
||||||
@@ -59,7 +58,7 @@ def register_connection(alias, db=None, name=None, host=None, port=None,
|
|||||||
.. versionchanged:: 0.10.6 - added mongomock support
|
.. versionchanged:: 0.10.6 - added mongomock support
|
||||||
"""
|
"""
|
||||||
conn_settings = {
|
conn_settings = {
|
||||||
'name': name or db or 'test',
|
'name': name or 'test',
|
||||||
'host': host or 'localhost',
|
'host': host or 'localhost',
|
||||||
'port': port or 27017,
|
'port': port or 27017,
|
||||||
'read_preference': read_preference,
|
'read_preference': read_preference,
|
||||||
@@ -104,18 +103,6 @@ def register_connection(alias, db=None, name=None, host=None, port=None,
|
|||||||
conn_settings['authentication_source'] = uri_options['authsource']
|
conn_settings['authentication_source'] = uri_options['authsource']
|
||||||
if 'authmechanism' in uri_options:
|
if 'authmechanism' in uri_options:
|
||||||
conn_settings['authentication_mechanism'] = uri_options['authmechanism']
|
conn_settings['authentication_mechanism'] = uri_options['authmechanism']
|
||||||
if IS_PYMONGO_3 and 'readpreference' in uri_options:
|
|
||||||
read_preferences = (
|
|
||||||
ReadPreference.NEAREST,
|
|
||||||
ReadPreference.PRIMARY,
|
|
||||||
ReadPreference.PRIMARY_PREFERRED,
|
|
||||||
ReadPreference.SECONDARY,
|
|
||||||
ReadPreference.SECONDARY_PREFERRED)
|
|
||||||
read_pf_mode = uri_options['readpreference'].lower()
|
|
||||||
for preference in read_preferences:
|
|
||||||
if preference.name.lower() == read_pf_mode:
|
|
||||||
conn_settings['read_preference'] = preference
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
resolved_hosts.append(entity)
|
resolved_hosts.append(entity)
|
||||||
conn_settings['host'] = resolved_hosts
|
conn_settings['host'] = resolved_hosts
|
||||||
@@ -159,14 +146,13 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
|
|||||||
raise MongoEngineConnectionError(msg)
|
raise MongoEngineConnectionError(msg)
|
||||||
|
|
||||||
def _clean_settings(settings_dict):
|
def _clean_settings(settings_dict):
|
||||||
# set literal more efficient than calling set function
|
irrelevant_fields = set([
|
||||||
irrelevant_fields_set = {
|
'name', 'username', 'password', 'authentication_source',
|
||||||
'name', 'username', 'password',
|
'authentication_mechanism'
|
||||||
'authentication_source', 'authentication_mechanism'
|
])
|
||||||
}
|
|
||||||
return {
|
return {
|
||||||
k: v for k, v in settings_dict.items()
|
k: v for k, v in settings_dict.items()
|
||||||
if k not in irrelevant_fields_set
|
if k not in irrelevant_fields
|
||||||
}
|
}
|
||||||
|
|
||||||
# Retrieve a copy of the connection settings associated with the requested
|
# Retrieve a copy of the connection settings associated with the requested
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
from contextlib import contextmanager
|
|
||||||
from pymongo.write_concern import WriteConcern
|
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
|
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
|
||||||
'no_sub_classes', 'query_counter', 'set_write_concern')
|
'no_sub_classes', 'query_counter')
|
||||||
|
|
||||||
|
|
||||||
class switch_db(object):
|
class switch_db(object):
|
||||||
@@ -145,85 +143,66 @@ class no_sub_classes(object):
|
|||||||
:param cls: the class to turn querying sub classes on
|
:param cls: the class to turn querying sub classes on
|
||||||
"""
|
"""
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
self.cls_initial_subclasses = None
|
|
||||||
|
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
"""Change the objects default and _auto_dereference values."""
|
"""Change the objects default and _auto_dereference values."""
|
||||||
self.cls_initial_subclasses = self.cls._subclasses
|
self.cls._all_subclasses = self.cls._subclasses
|
||||||
self.cls._subclasses = (self.cls._class_name,)
|
self.cls._subclasses = (self.cls,)
|
||||||
return self.cls
|
return self.cls
|
||||||
|
|
||||||
def __exit__(self, t, value, traceback):
|
def __exit__(self, t, value, traceback):
|
||||||
"""Reset the default and _auto_dereference values."""
|
"""Reset the default and _auto_dereference values."""
|
||||||
self.cls._subclasses = self.cls_initial_subclasses
|
self.cls._subclasses = self.cls._all_subclasses
|
||||||
|
delattr(self.cls, '_all_subclasses')
|
||||||
|
return self.cls
|
||||||
|
|
||||||
|
|
||||||
class query_counter(object):
|
class query_counter(object):
|
||||||
"""Query_counter context manager to get the number of queries.
|
"""Query_counter context manager to get the number of queries."""
|
||||||
This works by updating the `profiling_level` of the database so that all queries get logged,
|
|
||||||
resetting the db.system.profile collection at the beginnig of the context and counting the new entries.
|
|
||||||
|
|
||||||
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
|
|
||||||
can interfere with it
|
|
||||||
|
|
||||||
Be aware that:
|
|
||||||
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of
|
|
||||||
documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches)
|
|
||||||
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
"""Construct the query_counter
|
"""Construct the query_counter."""
|
||||||
"""
|
self.counter = 0
|
||||||
self.db = get_db()
|
self.db = get_db()
|
||||||
self.initial_profiling_level = None
|
|
||||||
self._ctx_query_counter = 0 # number of queries issued by the context
|
|
||||||
|
|
||||||
self._ignored_query = {
|
def __enter__(self):
|
||||||
'ns':
|
"""On every with block we need to drop the profile collection."""
|
||||||
{'$ne': '%s.system.indexes' % self.db.name},
|
|
||||||
'op': # MONGODB < 3.2
|
|
||||||
{'$ne': 'killcursors'},
|
|
||||||
'command.killCursors': # MONGODB >= 3.2
|
|
||||||
{'$exists': False}
|
|
||||||
}
|
|
||||||
|
|
||||||
def _turn_on_profiling(self):
|
|
||||||
self.initial_profiling_level = self.db.profiling_level()
|
|
||||||
self.db.set_profiling_level(0)
|
self.db.set_profiling_level(0)
|
||||||
self.db.system.profile.drop()
|
self.db.system.profile.drop()
|
||||||
self.db.set_profiling_level(2)
|
self.db.set_profiling_level(2)
|
||||||
|
|
||||||
def _resets_profiling(self):
|
|
||||||
self.db.set_profiling_level(self.initial_profiling_level)
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self._turn_on_profiling()
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __exit__(self, t, value, traceback):
|
def __exit__(self, t, value, traceback):
|
||||||
self._resets_profiling()
|
"""Reset the profiling level."""
|
||||||
|
self.db.set_profiling_level(0)
|
||||||
|
|
||||||
def __eq__(self, value):
|
def __eq__(self, value):
|
||||||
|
"""== Compare querycounter."""
|
||||||
counter = self._get_count()
|
counter = self._get_count()
|
||||||
return value == counter
|
return value == counter
|
||||||
|
|
||||||
def __ne__(self, value):
|
def __ne__(self, value):
|
||||||
|
"""!= Compare querycounter."""
|
||||||
return not self.__eq__(value)
|
return not self.__eq__(value)
|
||||||
|
|
||||||
def __lt__(self, value):
|
def __lt__(self, value):
|
||||||
|
"""< Compare querycounter."""
|
||||||
return self._get_count() < value
|
return self._get_count() < value
|
||||||
|
|
||||||
def __le__(self, value):
|
def __le__(self, value):
|
||||||
|
"""<= Compare querycounter."""
|
||||||
return self._get_count() <= value
|
return self._get_count() <= value
|
||||||
|
|
||||||
def __gt__(self, value):
|
def __gt__(self, value):
|
||||||
|
"""> Compare querycounter."""
|
||||||
return self._get_count() > value
|
return self._get_count() > value
|
||||||
|
|
||||||
def __ge__(self, value):
|
def __ge__(self, value):
|
||||||
|
""">= Compare querycounter."""
|
||||||
return self._get_count() >= value
|
return self._get_count() >= value
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
|
"""int representation."""
|
||||||
return self._get_count()
|
return self._get_count()
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -231,17 +210,8 @@ class query_counter(object):
|
|||||||
return u"%s" % self._get_count()
|
return u"%s" % self._get_count()
|
||||||
|
|
||||||
def _get_count(self):
|
def _get_count(self):
|
||||||
"""Get the number of queries by counting the current number of entries in db.system.profile
|
"""Get the number of queries."""
|
||||||
and substracting the queries issued by this context. In fact everytime this is called, 1 query is
|
ignore_query = {'ns': {'$ne': '%s.system.indexes' % self.db.name}}
|
||||||
issued so we need to balance that
|
count = self.db.system.profile.find(ignore_query).count() - self.counter
|
||||||
"""
|
self.counter += 1
|
||||||
count = self.db.system.profile.find(self._ignored_query).count() - self._ctx_query_counter
|
|
||||||
self._ctx_query_counter += 1 # Account for the query we just issued to gather the information
|
|
||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def set_write_concern(collection, write_concerns):
|
|
||||||
combined_concerns = dict(collection.write_concern.document.items())
|
|
||||||
combined_concerns.update(write_concerns)
|
|
||||||
yield collection.with_options(write_concern=WriteConcern(**combined_concerns))
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
|
from collections import OrderedDict
|
||||||
from bson import DBRef, SON
|
from bson import DBRef, SON
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
|
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
|
||||||
TopLevelDocumentMetaclass, get_document)
|
TopLevelDocumentMetaclass, get_document)
|
||||||
from mongoengine.base.datastructures import LazyReference
|
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.document import Document, EmbeddedDocument
|
from mongoengine.document import Document, EmbeddedDocument
|
||||||
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
|
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
|
||||||
@@ -100,10 +100,7 @@ class DeReference(object):
|
|||||||
if isinstance(item, (Document, EmbeddedDocument)):
|
if isinstance(item, (Document, EmbeddedDocument)):
|
||||||
for field_name, field in item._fields.iteritems():
|
for field_name, field in item._fields.iteritems():
|
||||||
v = item._data.get(field_name, None)
|
v = item._data.get(field_name, None)
|
||||||
if isinstance(v, LazyReference):
|
if isinstance(v, DBRef):
|
||||||
# LazyReference inherits DBRef but should not be dereferenced here !
|
|
||||||
continue
|
|
||||||
elif isinstance(v, DBRef):
|
|
||||||
reference_map.setdefault(field.document_type, set()).add(v.id)
|
reference_map.setdefault(field.document_type, set()).add(v.id)
|
||||||
elif isinstance(v, (dict, SON)) and '_ref' in v:
|
elif isinstance(v, (dict, SON)) and '_ref' in v:
|
||||||
reference_map.setdefault(get_document(v['_cls']), set()).add(v['_ref'].id)
|
reference_map.setdefault(get_document(v['_cls']), set()).add(v['_ref'].id)
|
||||||
@@ -114,9 +111,6 @@ class DeReference(object):
|
|||||||
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
|
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
|
||||||
key = field_cls
|
key = field_cls
|
||||||
reference_map.setdefault(key, set()).update(refs)
|
reference_map.setdefault(key, set()).update(refs)
|
||||||
elif isinstance(item, LazyReference):
|
|
||||||
# LazyReference inherits DBRef but should not be dereferenced here !
|
|
||||||
continue
|
|
||||||
elif isinstance(item, DBRef):
|
elif isinstance(item, DBRef):
|
||||||
reference_map.setdefault(item.collection, set()).add(item.id)
|
reference_map.setdefault(item.collection, set()).add(item.id)
|
||||||
elif isinstance(item, (dict, SON)) and '_ref' in item:
|
elif isinstance(item, (dict, SON)) and '_ref' in item:
|
||||||
@@ -133,12 +127,7 @@ class DeReference(object):
|
|||||||
"""
|
"""
|
||||||
object_map = {}
|
object_map = {}
|
||||||
for collection, dbrefs in self.reference_map.iteritems():
|
for collection, dbrefs in self.reference_map.iteritems():
|
||||||
|
if hasattr(collection, 'objects'): # We have a document class for the refs
|
||||||
# we use getattr instead of hasattr because hasattr swallows any exception under python2
|
|
||||||
# so it could hide nasty things without raising exceptions (cfr bug #1688))
|
|
||||||
ref_document_cls_exists = (getattr(collection, 'objects', None) is not None)
|
|
||||||
|
|
||||||
if ref_document_cls_exists:
|
|
||||||
col_name = collection._get_collection_name()
|
col_name = collection._get_collection_name()
|
||||||
refs = [dbref for dbref in dbrefs
|
refs = [dbref for dbref in dbrefs
|
||||||
if (col_name, dbref) not in object_map]
|
if (col_name, dbref) not in object_map]
|
||||||
@@ -146,7 +135,7 @@ class DeReference(object):
|
|||||||
for key, doc in references.iteritems():
|
for key, doc in references.iteritems():
|
||||||
object_map[(col_name, key)] = doc
|
object_map[(col_name, key)] = doc
|
||||||
else: # Generic reference: use the refs data to convert to document
|
else: # Generic reference: use the refs data to convert to document
|
||||||
if isinstance(doc_type, (ListField, DictField, MapField)):
|
if isinstance(doc_type, (ListField, DictField, MapField,)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
refs = [dbref for dbref in dbrefs
|
refs = [dbref for dbref in dbrefs
|
||||||
@@ -213,6 +202,10 @@ class DeReference(object):
|
|||||||
as_tuple = isinstance(items, tuple)
|
as_tuple = isinstance(items, tuple)
|
||||||
iterator = enumerate(items)
|
iterator = enumerate(items)
|
||||||
data = []
|
data = []
|
||||||
|
elif isinstance(items, OrderedDict):
|
||||||
|
is_list = False
|
||||||
|
iterator = items.iteritems()
|
||||||
|
data = OrderedDict()
|
||||||
else:
|
else:
|
||||||
is_list = False
|
is_list = False
|
||||||
iterator = items.iteritems()
|
iterator = items.iteritems()
|
||||||
@@ -242,7 +235,7 @@ class DeReference(object):
|
|||||||
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
|
||||||
item_name = '%s.%s' % (name, k) if name else name
|
item_name = '%s.%s' % (name, k) if name else name
|
||||||
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
|
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
|
||||||
elif isinstance(v, DBRef) and hasattr(v, 'id'):
|
elif hasattr(v, 'id'):
|
||||||
data[k] = self.object_map.get((v.collection, v.id), v)
|
data[k] = self.object_map.get((v.collection, v.id), v)
|
||||||
|
|
||||||
if instance and name:
|
if instance and name:
|
||||||
|
@@ -39,7 +39,7 @@ class InvalidCollectionError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
|
class EmbeddedDocument(BaseDocument):
|
||||||
"""A :class:`~mongoengine.Document` that isn't stored in its own
|
"""A :class:`~mongoengine.Document` that isn't stored in its own
|
||||||
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
|
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
|
||||||
fields on :class:`~mongoengine.Document`\ s through the
|
fields on :class:`~mongoengine.Document`\ s through the
|
||||||
@@ -58,6 +58,7 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
|
|||||||
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
||||||
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
||||||
my_metaclass = DocumentMetaclass
|
my_metaclass = DocumentMetaclass
|
||||||
|
__metaclass__ = DocumentMetaclass
|
||||||
|
|
||||||
# A generic embedded document doesn't have any immutable properties
|
# A generic embedded document doesn't have any immutable properties
|
||||||
# that describe it uniquely, hence it shouldn't be hashable. You can
|
# that describe it uniquely, hence it shouldn't be hashable. You can
|
||||||
@@ -94,7 +95,7 @@ class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
|
|||||||
self._instance.reload(*args, **kwargs)
|
self._instance.reload(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
class Document(BaseDocument):
|
||||||
"""The base class used for defining the structure and properties of
|
"""The base class used for defining the structure and properties of
|
||||||
collections of documents stored in MongoDB. Inherit from this class, and
|
collections of documents stored in MongoDB. Inherit from this class, and
|
||||||
add fields as class attributes to define a document's structure.
|
add fields as class attributes to define a document's structure.
|
||||||
@@ -149,6 +150,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
||||||
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
||||||
my_metaclass = TopLevelDocumentMetaclass
|
my_metaclass = TopLevelDocumentMetaclass
|
||||||
|
__metaclass__ = TopLevelDocumentMetaclass
|
||||||
|
|
||||||
__slots__ = ('__objects',)
|
__slots__ = ('__objects',)
|
||||||
|
|
||||||
@@ -170,8 +172,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
"""
|
"""
|
||||||
if self.pk is None:
|
if self.pk is None:
|
||||||
return super(BaseDocument, self).__hash__()
|
return super(BaseDocument, self).__hash__()
|
||||||
|
else:
|
||||||
return hash(self.pk)
|
return hash(self.pk)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_db(cls):
|
def _get_db(cls):
|
||||||
@@ -193,10 +195,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
|
|
||||||
# Ensure indexes on the collection unless auto_create_index was
|
# Ensure indexes on the collection unless auto_create_index was
|
||||||
# set to False.
|
# set to False.
|
||||||
# Also there is no need to ensure indexes on slave.
|
if cls._meta.get('auto_create_index', True):
|
||||||
db = cls._get_db()
|
|
||||||
if cls._meta.get('auto_create_index', True) and\
|
|
||||||
db.client.is_primary:
|
|
||||||
cls.ensure_indexes()
|
cls.ensure_indexes()
|
||||||
|
|
||||||
return cls._collection
|
return cls._collection
|
||||||
@@ -281,9 +280,6 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
elif query[id_field] != self.pk:
|
elif query[id_field] != self.pk:
|
||||||
raise InvalidQueryError('Invalid document modify query: it must modify only this document.')
|
raise InvalidQueryError('Invalid document modify query: it must modify only this document.')
|
||||||
|
|
||||||
# Need to add shard key to query, or you get an error
|
|
||||||
query.update(self._object_key)
|
|
||||||
|
|
||||||
updated = self._qs(**query).modify(new=True, **update)
|
updated = self._qs(**query).modify(new=True, **update)
|
||||||
if updated is None:
|
if updated is None:
|
||||||
return False
|
return False
|
||||||
@@ -324,7 +320,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
:param save_condition: only perform save if matching record in db
|
:param save_condition: only perform save if matching record in db
|
||||||
satisfies condition(s) (e.g. version number).
|
satisfies condition(s) (e.g. version number).
|
||||||
Raises :class:`OperationError` if the conditions are not satisfied
|
Raises :class:`OperationError` if the conditions are not satisfied
|
||||||
:param 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.
|
||||||
|
|
||||||
.. versionchanged:: 0.5
|
.. versionchanged:: 0.5
|
||||||
@@ -368,8 +364,6 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
|
|
||||||
signals.pre_save_post_validation.send(self.__class__, document=self,
|
signals.pre_save_post_validation.send(self.__class__, document=self,
|
||||||
created=created, **signal_kwargs)
|
created=created, **signal_kwargs)
|
||||||
# it might be refreshed by the pre_save_post_validation hook, e.g., for etag generation
|
|
||||||
doc = self.to_mongo()
|
|
||||||
|
|
||||||
if self._meta.get('auto_create_index', True):
|
if self._meta.get('auto_create_index', True):
|
||||||
self.ensure_indexes()
|
self.ensure_indexes()
|
||||||
@@ -582,11 +576,12 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
"""Delete the :class:`~mongoengine.Document` from the database. This
|
"""Delete the :class:`~mongoengine.Document` from the database. This
|
||||||
will only take effect if the document has been previously saved.
|
will only take effect if the document has been previously saved.
|
||||||
|
|
||||||
:param 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 write_concern: Extra keyword arguments are passed down which
|
:param write_concern: Extra keyword arguments are passed down which
|
||||||
will be used as options for the resultant ``getLastError`` command.
|
will be used as options for the resultant
|
||||||
For example, ``save(..., w: 2, fsync: True)`` will
|
``getLastError`` command. For example,
|
||||||
|
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
||||||
wait until at least two servers have recorded the write and
|
wait until at least two servers have recorded the write and
|
||||||
will force an fsync on the primary server.
|
will force an fsync on the primary server.
|
||||||
|
|
||||||
@@ -707,6 +702,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
obj = obj[0]
|
obj = obj[0]
|
||||||
else:
|
else:
|
||||||
raise self.DoesNotExist('Document does not exist')
|
raise self.DoesNotExist('Document does not exist')
|
||||||
|
|
||||||
for field in obj._data:
|
for field in obj._data:
|
||||||
if not fields or field in fields:
|
if not fields or field in fields:
|
||||||
try:
|
try:
|
||||||
@@ -714,7 +710,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
except (KeyError, AttributeError):
|
except (KeyError, AttributeError):
|
||||||
try:
|
try:
|
||||||
# If field is a special field, e.g. items is stored as _reserved_items,
|
# If field is a special field, e.g. items is stored as _reserved_items,
|
||||||
# a KeyError is thrown. So try to retrieve the field from _data
|
# an KeyError is thrown. So try to retrieve the field from _data
|
||||||
setattr(self, field, self._reload(field, obj._data.get(field)))
|
setattr(self, field, self._reload(field, obj._data.get(field)))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# If field is removed from the database while the object
|
# If field is removed from the database while the object
|
||||||
@@ -722,9 +718,7 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
# i.e. obj.update(unset__field=1) followed by obj.reload()
|
# i.e. obj.update(unset__field=1) followed by obj.reload()
|
||||||
delattr(self, field)
|
delattr(self, field)
|
||||||
|
|
||||||
self._changed_fields = list(
|
self._changed_fields = obj._changed_fields
|
||||||
set(self._changed_fields) - set(fields)
|
|
||||||
) if fields else obj._changed_fields
|
|
||||||
self._created = False
|
self._created = False
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -970,16 +964,8 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
required = cls.list_indexes()
|
required = cls.list_indexes()
|
||||||
|
existing = [info['key']
|
||||||
existing = []
|
for info in cls._get_collection().index_information().values()]
|
||||||
for info in cls._get_collection().index_information().values():
|
|
||||||
if '_fts' in info['key'][0]:
|
|
||||||
index_type = info['key'][0][1]
|
|
||||||
text_index_fields = info.get('weights').keys()
|
|
||||||
existing.append(
|
|
||||||
[(key, index_type) for key in text_index_fields])
|
|
||||||
else:
|
|
||||||
existing.append(info['key'])
|
|
||||||
missing = [index for index in required if index not in existing]
|
missing = [index for index in required if index not in existing]
|
||||||
extra = [index for index in existing if index not in required]
|
extra = [index for index in existing if index not in required]
|
||||||
|
|
||||||
@@ -996,10 +982,10 @@ class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
|
|||||||
return {'missing': missing, 'extra': extra}
|
return {'missing': missing, 'extra': extra}
|
||||||
|
|
||||||
|
|
||||||
class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
|
class DynamicDocument(Document):
|
||||||
"""A Dynamic Document class allowing flexible, expandable and uncontrolled
|
"""A Dynamic Document class allowing flexible, expandable and uncontrolled
|
||||||
schemas. As a :class:`~mongoengine.Document` subclass, acts in the same
|
schemas. As a :class:`~mongoengine.Document` subclass, acts in the same
|
||||||
way as an ordinary document but has expanded style properties. Any data
|
way as an ordinary document but has expando style properties. Any data
|
||||||
passed or set against the :class:`~mongoengine.DynamicDocument` that is
|
passed or set against the :class:`~mongoengine.DynamicDocument` that is
|
||||||
not a field is automatically converted into a
|
not a field is automatically converted into a
|
||||||
:class:`~mongoengine.fields.DynamicField` and data can be attributed to that
|
:class:`~mongoengine.fields.DynamicField` and data can be attributed to that
|
||||||
@@ -1013,6 +999,7 @@ class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
|
|||||||
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
||||||
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
||||||
my_metaclass = TopLevelDocumentMetaclass
|
my_metaclass = TopLevelDocumentMetaclass
|
||||||
|
__metaclass__ = TopLevelDocumentMetaclass
|
||||||
|
|
||||||
_dynamic = True
|
_dynamic = True
|
||||||
|
|
||||||
@@ -1023,12 +1010,11 @@ class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
|
|||||||
field_name = args[0]
|
field_name = args[0]
|
||||||
if field_name in self._dynamic_fields:
|
if field_name in self._dynamic_fields:
|
||||||
setattr(self, field_name, None)
|
setattr(self, field_name, None)
|
||||||
self._dynamic_fields[field_name].null = False
|
|
||||||
else:
|
else:
|
||||||
super(DynamicDocument, self).__delattr__(*args, **kwargs)
|
super(DynamicDocument, self).__delattr__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class DynamicEmbeddedDocument(six.with_metaclass(DocumentMetaclass, EmbeddedDocument)):
|
class DynamicEmbeddedDocument(EmbeddedDocument):
|
||||||
"""A Dynamic Embedded Document class allowing flexible, expandable and
|
"""A Dynamic Embedded Document class allowing flexible, expandable and
|
||||||
uncontrolled schemas. See :class:`~mongoengine.DynamicDocument` for more
|
uncontrolled schemas. See :class:`~mongoengine.DynamicDocument` for more
|
||||||
information about dynamic documents.
|
information about dynamic documents.
|
||||||
@@ -1037,6 +1023,7 @@ class DynamicEmbeddedDocument(six.with_metaclass(DocumentMetaclass, EmbeddedDocu
|
|||||||
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
# The __metaclass__ attribute is removed by 2to3 when running with Python3
|
||||||
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
|
||||||
my_metaclass = DocumentMetaclass
|
my_metaclass = DocumentMetaclass
|
||||||
|
__metaclass__ = DocumentMetaclass
|
||||||
|
|
||||||
_dynamic = True
|
_dynamic = True
|
||||||
|
|
||||||
|
@@ -71,7 +71,6 @@ class ValidationError(AssertionError):
|
|||||||
_message = None
|
_message = None
|
||||||
|
|
||||||
def __init__(self, message='', **kwargs):
|
def __init__(self, message='', **kwargs):
|
||||||
super(ValidationError, self).__init__(message)
|
|
||||||
self.errors = kwargs.get('errors', {})
|
self.errors = kwargs.get('errors', {})
|
||||||
self.field_name = kwargs.get('field_name')
|
self.field_name = kwargs.get('field_name')
|
||||||
self.message = message
|
self.message = message
|
||||||
|
@@ -5,6 +5,8 @@ import re
|
|||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import uuid
|
import uuid
|
||||||
|
import warnings
|
||||||
|
from collections import Mapping
|
||||||
from operator import itemgetter
|
from operator import itemgetter
|
||||||
|
|
||||||
from bson import Binary, DBRef, ObjectId, SON
|
from bson import Binary, DBRef, ObjectId, SON
|
||||||
@@ -24,18 +26,13 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
Int64 = long
|
Int64 = long
|
||||||
|
|
||||||
|
|
||||||
from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField,
|
from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField,
|
||||||
GeoJsonBaseField, LazyReference, ObjectIdField,
|
GeoJsonBaseField, ObjectIdField, get_document)
|
||||||
get_document)
|
|
||||||
from mongoengine.base.utils import LazyRegexCompiler
|
|
||||||
from mongoengine.common import _import_class
|
|
||||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
|
||||||
from mongoengine.document import Document, EmbeddedDocument
|
from mongoengine.document import Document, EmbeddedDocument
|
||||||
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
|
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
|
||||||
from mongoengine.python_support import StringIO
|
from mongoengine.python_support import StringIO
|
||||||
from mongoengine.queryset import DO_NOTHING
|
from mongoengine.queryset import DO_NOTHING, QuerySet
|
||||||
from mongoengine.queryset.base import BaseQuerySet
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
@@ -43,20 +40,13 @@ except ImportError:
|
|||||||
Image = None
|
Image = None
|
||||||
ImageOps = None
|
ImageOps = None
|
||||||
|
|
||||||
if six.PY3:
|
|
||||||
# Useless as long as 2to3 gets executed
|
|
||||||
# as it turns `long` into `int` blindly
|
|
||||||
long = int
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'StringField', 'URLField', 'EmailField', 'IntField', 'LongField',
|
'StringField', 'URLField', 'EmailField', 'IntField', 'LongField',
|
||||||
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField', 'DateField',
|
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField',
|
||||||
'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField',
|
'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField',
|
||||||
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
|
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
|
||||||
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
|
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
|
||||||
'MapField', 'ReferenceField', 'CachedReferenceField',
|
'MapField', 'ReferenceField', 'CachedReferenceField',
|
||||||
'LazyReferenceField', 'GenericLazyReferenceField',
|
|
||||||
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy',
|
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy',
|
||||||
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
|
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
|
||||||
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
|
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
|
||||||
@@ -131,9 +121,9 @@ class URLField(StringField):
|
|||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_URL_REGEX = LazyRegexCompiler(
|
_URL_REGEX = re.compile(
|
||||||
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
|
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
|
||||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-_]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain...
|
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain...
|
||||||
r'localhost|' # localhost...
|
r'localhost|' # localhost...
|
||||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
|
||||||
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
|
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
|
||||||
@@ -141,7 +131,8 @@ class URLField(StringField):
|
|||||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||||
_URL_SCHEMES = ['http', 'https', 'ftp', 'ftps']
|
_URL_SCHEMES = ['http', 'https', 'ftp', 'ftps']
|
||||||
|
|
||||||
def __init__(self, url_regex=None, schemes=None, **kwargs):
|
def __init__(self, verify_exists=False, url_regex=None, schemes=None, **kwargs):
|
||||||
|
self.verify_exists = verify_exists
|
||||||
self.url_regex = url_regex or self._URL_REGEX
|
self.url_regex = url_regex or self._URL_REGEX
|
||||||
self.schemes = schemes or self._URL_SCHEMES
|
self.schemes = schemes or self._URL_SCHEMES
|
||||||
super(URLField, self).__init__(**kwargs)
|
super(URLField, self).__init__(**kwargs)
|
||||||
@@ -164,7 +155,7 @@ class EmailField(StringField):
|
|||||||
|
|
||||||
.. versionadded:: 0.4
|
.. versionadded:: 0.4
|
||||||
"""
|
"""
|
||||||
USER_REGEX = LazyRegexCompiler(
|
USER_REGEX = re.compile(
|
||||||
# `dot-atom` defined in RFC 5322 Section 3.2.3.
|
# `dot-atom` defined in RFC 5322 Section 3.2.3.
|
||||||
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"
|
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"
|
||||||
# `quoted-string` defined in RFC 5322 Section 3.2.4.
|
# `quoted-string` defined in RFC 5322 Section 3.2.4.
|
||||||
@@ -172,7 +163,7 @@ class EmailField(StringField):
|
|||||||
re.IGNORECASE
|
re.IGNORECASE
|
||||||
)
|
)
|
||||||
|
|
||||||
UTF8_USER_REGEX = LazyRegexCompiler(
|
UTF8_USER_REGEX = re.compile(
|
||||||
six.u(
|
six.u(
|
||||||
# RFC 6531 Section 3.3 extends `atext` (used by dot-atom) to
|
# RFC 6531 Section 3.3 extends `atext` (used by dot-atom) to
|
||||||
# include `UTF8-non-ascii`.
|
# include `UTF8-non-ascii`.
|
||||||
@@ -182,7 +173,7 @@ class EmailField(StringField):
|
|||||||
), re.IGNORECASE | re.UNICODE
|
), re.IGNORECASE | re.UNICODE
|
||||||
)
|
)
|
||||||
|
|
||||||
DOMAIN_REGEX = LazyRegexCompiler(
|
DOMAIN_REGEX = re.compile(
|
||||||
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z',
|
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z',
|
||||||
re.IGNORECASE
|
re.IGNORECASE
|
||||||
)
|
)
|
||||||
@@ -274,14 +265,14 @@ class IntField(BaseField):
|
|||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
except (TypeError, ValueError):
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
try:
|
try:
|
||||||
value = int(value)
|
value = int(value)
|
||||||
except (TypeError, ValueError):
|
except Exception:
|
||||||
self.error('%s could not be converted to int' % value)
|
self.error('%s could not be converted to int' % value)
|
||||||
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
if self.min_value is not None and value < self.min_value:
|
||||||
@@ -307,7 +298,7 @@ class LongField(BaseField):
|
|||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
try:
|
try:
|
||||||
value = long(value)
|
value = long(value)
|
||||||
except (TypeError, ValueError):
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -317,7 +308,7 @@ class LongField(BaseField):
|
|||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
try:
|
try:
|
||||||
value = long(value)
|
value = long(value)
|
||||||
except (TypeError, ValueError):
|
except Exception:
|
||||||
self.error('%s could not be converted to long' % value)
|
self.error('%s could not be converted to long' % value)
|
||||||
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
if self.min_value is not None and value < self.min_value:
|
||||||
@@ -371,8 +362,7 @@ class FloatField(BaseField):
|
|||||||
|
|
||||||
|
|
||||||
class DecimalField(BaseField):
|
class DecimalField(BaseField):
|
||||||
"""Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used.
|
"""Fixed-point decimal number field.
|
||||||
If using floats, beware of Decimal to float conversion (potential precision loss)
|
|
||||||
|
|
||||||
.. versionchanged:: 0.8
|
.. versionchanged:: 0.8
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
@@ -383,9 +373,7 @@ class DecimalField(BaseField):
|
|||||||
"""
|
"""
|
||||||
:param min_value: Validation rule for the minimum acceptable value.
|
:param min_value: Validation rule for the minimum acceptable value.
|
||||||
:param max_value: Validation rule for the maximum acceptable value.
|
:param max_value: Validation rule for the maximum acceptable value.
|
||||||
:param force_string: Store the value as a string (instead of a float).
|
:param force_string: Store as a string.
|
||||||
Be aware that this affects query sorting and operation like lte, gte (as string comparison is applied)
|
|
||||||
and some query operator won't work (e.g: inc, dec)
|
|
||||||
:param precision: Number of decimal places to store.
|
:param precision: Number of decimal places to store.
|
||||||
:param rounding: The rounding rule from the python decimal library:
|
:param rounding: The rounding rule from the python decimal library:
|
||||||
|
|
||||||
@@ -416,7 +404,7 @@ class DecimalField(BaseField):
|
|||||||
# Convert to string for python 2.6 before casting to Decimal
|
# Convert to string for python 2.6 before casting to Decimal
|
||||||
try:
|
try:
|
||||||
value = decimal.Decimal('%s' % value)
|
value = decimal.Decimal('%s' % value)
|
||||||
except (TypeError, ValueError, decimal.InvalidOperation):
|
except decimal.InvalidOperation:
|
||||||
return value
|
return value
|
||||||
return value.quantize(decimal.Decimal('.%s' % ('0' * self.precision)), rounding=self.rounding)
|
return value.quantize(decimal.Decimal('.%s' % ('0' * self.precision)), rounding=self.rounding)
|
||||||
|
|
||||||
@@ -433,7 +421,7 @@ class DecimalField(BaseField):
|
|||||||
value = six.text_type(value)
|
value = six.text_type(value)
|
||||||
try:
|
try:
|
||||||
value = decimal.Decimal(value)
|
value = decimal.Decimal(value)
|
||||||
except (TypeError, ValueError, decimal.InvalidOperation) as exc:
|
except Exception as exc:
|
||||||
self.error('Could not convert value to decimal: %s' % exc)
|
self.error('Could not convert value to decimal: %s' % exc)
|
||||||
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
if self.min_value is not None and value < self.min_value:
|
||||||
@@ -472,8 +460,6 @@ class DateTimeField(BaseField):
|
|||||||
installed you can utilise it to convert varying types of date formats into valid
|
installed you can utilise it to convert varying types of date formats into valid
|
||||||
python datetime objects.
|
python datetime objects.
|
||||||
|
|
||||||
Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
|
|
||||||
|
|
||||||
Note: Microseconds are rounded to the nearest millisecond.
|
Note: Microseconds are rounded to the nearest millisecond.
|
||||||
Pre UTC microsecond support is effectively broken.
|
Pre UTC microsecond support is effectively broken.
|
||||||
Use :class:`~mongoengine.fields.ComplexDateTimeField` if you
|
Use :class:`~mongoengine.fields.ComplexDateTimeField` if you
|
||||||
@@ -498,10 +484,6 @@ class DateTimeField(BaseField):
|
|||||||
if not isinstance(value, six.string_types):
|
if not isinstance(value, six.string_types):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
value = value.strip()
|
|
||||||
if not value:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Attempt to parse a datetime:
|
# Attempt to parse a datetime:
|
||||||
if dateutil:
|
if dateutil:
|
||||||
try:
|
try:
|
||||||
@@ -537,22 +519,6 @@ class DateTimeField(BaseField):
|
|||||||
return super(DateTimeField, self).prepare_query_value(op, self.to_mongo(value))
|
return super(DateTimeField, self).prepare_query_value(op, self.to_mongo(value))
|
||||||
|
|
||||||
|
|
||||||
class DateField(DateTimeField):
|
|
||||||
def to_mongo(self, value):
|
|
||||||
value = super(DateField, self).to_mongo(value)
|
|
||||||
# drop hours, minutes, seconds
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
value = datetime.datetime(value.year, value.month, value.day)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
value = super(DateField, self).to_python(value)
|
|
||||||
# convert datetime to date
|
|
||||||
if isinstance(value, datetime.datetime):
|
|
||||||
value = datetime.date(value.year, value.month, value.day)
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
class ComplexDateTimeField(StringField):
|
class ComplexDateTimeField(StringField):
|
||||||
"""
|
"""
|
||||||
ComplexDateTimeField handles microseconds exactly instead of rounding
|
ComplexDateTimeField handles microseconds exactly instead of rounding
|
||||||
@@ -569,15 +535,11 @@ class ComplexDateTimeField(StringField):
|
|||||||
The `,` as the separator can be easily modified by passing the `separator`
|
The `,` as the separator can be easily modified by passing the `separator`
|
||||||
keyword when initializing the field.
|
keyword when initializing the field.
|
||||||
|
|
||||||
Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, separator=',', **kwargs):
|
def __init__(self, separator=',', **kwargs):
|
||||||
"""
|
self.names = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond']
|
||||||
:param separator: Allows to customize the separator used for storage (default ``,``)
|
|
||||||
"""
|
|
||||||
self.separator = separator
|
self.separator = separator
|
||||||
self.format = separator.join(['%Y', '%m', '%d', '%H', '%M', '%S', '%f'])
|
self.format = separator.join(['%Y', '%m', '%d', '%H', '%M', '%S', '%f'])
|
||||||
super(ComplexDateTimeField, self).__init__(**kwargs)
|
super(ComplexDateTimeField, self).__init__(**kwargs)
|
||||||
@@ -604,24 +566,20 @@ class ComplexDateTimeField(StringField):
|
|||||||
>>> ComplexDateTimeField()._convert_from_string(a)
|
>>> ComplexDateTimeField()._convert_from_string(a)
|
||||||
datetime.datetime(2011, 6, 8, 20, 26, 24, 92284)
|
datetime.datetime(2011, 6, 8, 20, 26, 24, 92284)
|
||||||
"""
|
"""
|
||||||
values = [int(d) for d in data.split(self.separator)]
|
values = map(int, data.split(self.separator))
|
||||||
return datetime.datetime(*values)
|
return datetime.datetime(*values)
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
if instance is None:
|
|
||||||
return self
|
|
||||||
|
|
||||||
data = super(ComplexDateTimeField, self).__get__(instance, owner)
|
data = super(ComplexDateTimeField, self).__get__(instance, owner)
|
||||||
|
if data is None:
|
||||||
if isinstance(data, datetime.datetime) or data is None:
|
return None if self.null else datetime.datetime.now()
|
||||||
|
if isinstance(data, datetime.datetime):
|
||||||
return data
|
return data
|
||||||
return self._convert_from_string(data)
|
return self._convert_from_string(data)
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
super(ComplexDateTimeField, self).__set__(instance, value)
|
value = self._convert_from_datetime(value) if value else value
|
||||||
value = instance._data[self.name]
|
return super(ComplexDateTimeField, self).__set__(instance, value)
|
||||||
if value is not None:
|
|
||||||
instance._data[self.name] = self._convert_from_datetime(value)
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
value = self.to_python(value)
|
value = self.to_python(value)
|
||||||
@@ -650,7 +608,6 @@ class EmbeddedDocumentField(BaseField):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, document_type, **kwargs):
|
def __init__(self, document_type, **kwargs):
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if not (
|
if not (
|
||||||
isinstance(document_type, six.string_types) or
|
isinstance(document_type, six.string_types) or
|
||||||
issubclass(document_type, EmbeddedDocument)
|
issubclass(document_type, EmbeddedDocument)
|
||||||
@@ -665,17 +622,9 @@ class EmbeddedDocumentField(BaseField):
|
|||||||
def document_type(self):
|
def document_type(self):
|
||||||
if isinstance(self.document_type_obj, six.string_types):
|
if isinstance(self.document_type_obj, six.string_types):
|
||||||
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
|
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
|
||||||
resolved_document_type = self.owner_document
|
self.document_type_obj = self.owner_document
|
||||||
else:
|
else:
|
||||||
resolved_document_type = get_document(self.document_type_obj)
|
self.document_type_obj = get_document(self.document_type_obj)
|
||||||
|
|
||||||
if not issubclass(resolved_document_type, EmbeddedDocument):
|
|
||||||
# Due to the late resolution of the document_type
|
|
||||||
# There is a chance that it won't be an EmbeddedDocument (#1661)
|
|
||||||
self.error('Invalid embedded document class provided to an '
|
|
||||||
'EmbeddedDocumentField')
|
|
||||||
self.document_type_obj = resolved_document_type
|
|
||||||
|
|
||||||
return self.document_type_obj
|
return self.document_type_obj
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
@@ -734,28 +683,16 @@ class GenericEmbeddedDocumentField(BaseField):
|
|||||||
return value
|
return value
|
||||||
|
|
||||||
def validate(self, value, clean=True):
|
def validate(self, value, clean=True):
|
||||||
if self.choices and isinstance(value, SON):
|
|
||||||
for choice in self.choices:
|
|
||||||
if value['_cls'] == choice._class_name:
|
|
||||||
return True
|
|
||||||
|
|
||||||
if not isinstance(value, EmbeddedDocument):
|
if not isinstance(value, EmbeddedDocument):
|
||||||
self.error('Invalid embedded document instance provided to an '
|
self.error('Invalid embedded document instance provided to an '
|
||||||
'GenericEmbeddedDocumentField')
|
'GenericEmbeddedDocumentField')
|
||||||
|
|
||||||
value.validate(clean=clean)
|
value.validate(clean=clean)
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
|
||||||
if self.choices:
|
|
||||||
for choice in self.choices:
|
|
||||||
field = choice._fields.get(member_name)
|
|
||||||
if field:
|
|
||||||
return field
|
|
||||||
return None
|
|
||||||
|
|
||||||
def to_mongo(self, document, use_db_field=True, fields=None):
|
def to_mongo(self, document, use_db_field=True, fields=None):
|
||||||
if document is None:
|
if document is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
data = document.to_mongo(use_db_field, fields)
|
data = document.to_mongo(use_db_field, fields)
|
||||||
if '_cls' not in data:
|
if '_cls' not in data:
|
||||||
data['_cls'] = document._class_name
|
data['_cls'] = document._class_name
|
||||||
@@ -768,6 +705,14 @@ class DynamicField(BaseField):
|
|||||||
|
|
||||||
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
|
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
|
||||||
|
|
||||||
|
def __init__(self, container_class=dict, *args, **kwargs):
|
||||||
|
self._container_cls = container_class
|
||||||
|
if not issubclass(self._container_cls, Mapping):
|
||||||
|
self.error('The class that is specified in `container_class` parameter '
|
||||||
|
'must be a subclass of `dict`.')
|
||||||
|
|
||||||
|
super(DynamicField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def to_mongo(self, value, use_db_field=True, fields=None):
|
def to_mongo(self, value, use_db_field=True, fields=None):
|
||||||
"""Convert a Python type to a MongoDB compatible type.
|
"""Convert a Python type to a MongoDB compatible type.
|
||||||
"""
|
"""
|
||||||
@@ -793,7 +738,7 @@ class DynamicField(BaseField):
|
|||||||
is_list = True
|
is_list = True
|
||||||
value = {k: v for k, v in enumerate(value)}
|
value = {k: v for k, v in enumerate(value)}
|
||||||
|
|
||||||
data = {}
|
data = self._container_cls()
|
||||||
for k, v in value.iteritems():
|
for k, v in value.iteritems():
|
||||||
data[k] = self.to_mongo(v, use_db_field, fields)
|
data[k] = self.to_mongo(v, use_db_field, fields)
|
||||||
|
|
||||||
@@ -839,20 +784,10 @@ class ListField(ComplexBaseField):
|
|||||||
kwargs.setdefault('default', lambda: [])
|
kwargs.setdefault('default', lambda: [])
|
||||||
super(ListField, self).__init__(**kwargs)
|
super(ListField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
if instance is None:
|
|
||||||
# Document class being used rather than a document object
|
|
||||||
return self
|
|
||||||
value = instance._data.get(self.name)
|
|
||||||
LazyReferenceField = _import_class('LazyReferenceField')
|
|
||||||
GenericLazyReferenceField = _import_class('GenericLazyReferenceField')
|
|
||||||
if isinstance(self.field, (LazyReferenceField, GenericLazyReferenceField)) and value:
|
|
||||||
instance._data[self.name] = [self.field.build_lazyref(x) for x in value]
|
|
||||||
return super(ListField, self).__get__(instance, owner)
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
"""Make sure that a list of valid fields is being used."""
|
"""Make sure that a list of valid fields is being used."""
|
||||||
if not isinstance(value, (list, tuple, BaseQuerySet)):
|
if (not isinstance(value, (list, tuple, QuerySet)) or
|
||||||
|
isinstance(value, six.string_types)):
|
||||||
self.error('Only lists and tuples may be used in a list field')
|
self.error('Only lists and tuples may be used in a list field')
|
||||||
super(ListField, self).validate(value)
|
super(ListField, self).validate(value)
|
||||||
|
|
||||||
@@ -959,10 +894,12 @@ class DictField(ComplexBaseField):
|
|||||||
.. versionchanged:: 0.5 - Can now handle complex / varying types of data
|
.. versionchanged:: 0.5 - Can now handle complex / varying types of data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, field=None, *args, **kwargs):
|
def __init__(self, basecls=None, field=None, *args, **kwargs):
|
||||||
self.field = field
|
self.field = field
|
||||||
self._auto_dereference = False
|
self._auto_dereference = False
|
||||||
|
self.basecls = basecls or BaseField
|
||||||
|
if not issubclass(self.basecls, BaseField):
|
||||||
|
self.error('DictField only accepts dict values')
|
||||||
kwargs.setdefault('default', lambda: {})
|
kwargs.setdefault('default', lambda: {})
|
||||||
super(DictField, self).__init__(*args, **kwargs)
|
super(DictField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
@@ -981,7 +918,7 @@ class DictField(ComplexBaseField):
|
|||||||
super(DictField, self).validate(value)
|
super(DictField, self).validate(value)
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
return DictField(db_field=member_name)
|
return DictField(basecls=self.basecls, db_field=member_name)
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
def prepare_query_value(self, op, value):
|
||||||
match_operators = ['contains', 'icontains', 'startswith',
|
match_operators = ['contains', 'icontains', 'startswith',
|
||||||
@@ -991,7 +928,7 @@ class DictField(ComplexBaseField):
|
|||||||
if op in match_operators and isinstance(value, six.string_types):
|
if op in match_operators and isinstance(value, six.string_types):
|
||||||
return StringField().prepare_query_value(op, value)
|
return StringField().prepare_query_value(op, value)
|
||||||
|
|
||||||
if hasattr(self.field, 'field'): # Used for instance when using DictField(ListField(IntField()))
|
if hasattr(self.field, 'field'):
|
||||||
if op in ('set', 'unset') and isinstance(value, dict):
|
if op in ('set', 'unset') and isinstance(value, dict):
|
||||||
return {
|
return {
|
||||||
k: self.field.prepare_query_value(op, v)
|
k: self.field.prepare_query_value(op, v)
|
||||||
@@ -1011,7 +948,6 @@ class MapField(DictField):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, field=None, *args, **kwargs):
|
def __init__(self, field=None, *args, **kwargs):
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if not isinstance(field, BaseField):
|
if not isinstance(field, BaseField):
|
||||||
self.error('Argument to MapField constructor must be a valid '
|
self.error('Argument to MapField constructor must be a valid '
|
||||||
'field')
|
'field')
|
||||||
@@ -1022,15 +958,6 @@ class ReferenceField(BaseField):
|
|||||||
"""A reference to a document that will be automatically dereferenced on
|
"""A reference to a document that will be automatically dereferenced on
|
||||||
access (lazily).
|
access (lazily).
|
||||||
|
|
||||||
Note this means you will get a database I/O access everytime you access
|
|
||||||
this field. This is necessary because the field returns a :class:`~mongoengine.Document`
|
|
||||||
which precise type can depend of the value of the `_cls` field present in the
|
|
||||||
document in database.
|
|
||||||
In short, using this type of field can lead to poor performances (especially
|
|
||||||
if you access this field only to retrieve it `pk` field which is already
|
|
||||||
known before dereference). To solve this you should consider using the
|
|
||||||
:class:`~mongoengine.fields.LazyReferenceField`.
|
|
||||||
|
|
||||||
Use the `reverse_delete_rule` to handle what should happen if the document
|
Use the `reverse_delete_rule` to handle what should happen if the document
|
||||||
the field is referencing is deleted. EmbeddedDocuments, DictFields and
|
the field is referencing is deleted. EmbeddedDocuments, DictFields and
|
||||||
MapFields does not support reverse_delete_rule and an `InvalidDocumentError`
|
MapFields does not support reverse_delete_rule and an `InvalidDocumentError`
|
||||||
@@ -1049,13 +976,11 @@ class ReferenceField(BaseField):
|
|||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
class Org(Document):
|
class Bar(Document):
|
||||||
owner = ReferenceField('User')
|
content = StringField()
|
||||||
|
foo = ReferenceField('Foo')
|
||||||
|
|
||||||
class User(Document):
|
Foo.register_delete_rule(Bar, 'foo', NULLIFY)
|
||||||
org = ReferenceField('Org', reverse_delete_rule=CASCADE)
|
|
||||||
|
|
||||||
User.register_delete_rule(Org, 'owner', DENY)
|
|
||||||
|
|
||||||
.. versionchanged:: 0.5 added `reverse_delete_rule`
|
.. versionchanged:: 0.5 added `reverse_delete_rule`
|
||||||
"""
|
"""
|
||||||
@@ -1073,7 +998,6 @@ class ReferenceField(BaseField):
|
|||||||
A reference to an abstract document type is always stored as a
|
A reference to an abstract document type is always stored as a
|
||||||
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
|
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
|
||||||
"""
|
"""
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if (
|
if (
|
||||||
not isinstance(document_type, six.string_types) and
|
not isinstance(document_type, six.string_types) and
|
||||||
not issubclass(document_type, Document)
|
not issubclass(document_type, Document)
|
||||||
@@ -1103,9 +1027,9 @@ class ReferenceField(BaseField):
|
|||||||
|
|
||||||
# Get value from document instance if available
|
# Get value from document instance if available
|
||||||
value = instance._data.get(self.name)
|
value = instance._data.get(self.name)
|
||||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if auto_dereference and isinstance(value, DBRef):
|
if self._auto_dereference and isinstance(value, DBRef):
|
||||||
if hasattr(value, 'cls'):
|
if hasattr(value, 'cls'):
|
||||||
# Dereference using the class type specified in the reference
|
# Dereference using the class type specified in the reference
|
||||||
cls = get_document(value.cls)
|
cls = get_document(value.cls)
|
||||||
@@ -1128,8 +1052,6 @@ class ReferenceField(BaseField):
|
|||||||
if isinstance(document, Document):
|
if isinstance(document, Document):
|
||||||
# We need the id from the saved object to create the DBRef
|
# We need the id from the saved object to create the DBRef
|
||||||
id_ = document.pk
|
id_ = document.pk
|
||||||
|
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
self.error('You can only reference documents once they have'
|
self.error('You can only reference documents once they have'
|
||||||
' been saved to the database')
|
' been saved to the database')
|
||||||
@@ -1169,13 +1091,21 @@ class ReferenceField(BaseField):
|
|||||||
return self.to_mongo(value)
|
return self.to_mongo(value)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, (self.document_type, LazyReference, DBRef, ObjectId)):
|
|
||||||
self.error('A ReferenceField only accepts DBRef, LazyReference, ObjectId or documents')
|
if not isinstance(value, (self.document_type, DBRef, ObjectId)):
|
||||||
|
self.error('A ReferenceField only accepts DBRef, ObjectId or documents')
|
||||||
|
|
||||||
if isinstance(value, Document) and value.id is None:
|
if isinstance(value, Document) and value.id is None:
|
||||||
self.error('You can only reference documents once they have been '
|
self.error('You can only reference documents once they have been '
|
||||||
'saved to the database')
|
'saved to the database')
|
||||||
|
|
||||||
|
if self.document_type._meta.get('abstract') and \
|
||||||
|
not isinstance(value, self.document_type):
|
||||||
|
self.error(
|
||||||
|
'%s is not an instance of abstract reference type %s' % (
|
||||||
|
self.document_type._class_name)
|
||||||
|
)
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
def lookup_member(self, member_name):
|
||||||
return self.document_type._fields.get(member_name)
|
return self.document_type._fields.get(member_name)
|
||||||
|
|
||||||
@@ -1196,7 +1126,6 @@ class CachedReferenceField(BaseField):
|
|||||||
if fields is None:
|
if fields is None:
|
||||||
fields = []
|
fields = []
|
||||||
|
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if (
|
if (
|
||||||
not isinstance(document_type, six.string_types) and
|
not isinstance(document_type, six.string_types) and
|
||||||
not issubclass(document_type, Document)
|
not issubclass(document_type, Document)
|
||||||
@@ -1256,10 +1185,9 @@ class CachedReferenceField(BaseField):
|
|||||||
|
|
||||||
# Get value from document instance if available
|
# Get value from document instance if available
|
||||||
value = instance._data.get(self.name)
|
value = instance._data.get(self.name)
|
||||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
|
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if auto_dereference and isinstance(value, DBRef):
|
if self._auto_dereference and isinstance(value, DBRef):
|
||||||
dereferenced = self.document_type._get_db().dereference(value)
|
dereferenced = self.document_type._get_db().dereference(value)
|
||||||
if dereferenced is None:
|
if dereferenced is None:
|
||||||
raise DoesNotExist('Trying to dereference unknown document %s' % value)
|
raise DoesNotExist('Trying to dereference unknown document %s' % value)
|
||||||
@@ -1272,7 +1200,6 @@ class CachedReferenceField(BaseField):
|
|||||||
id_field_name = self.document_type._meta['id_field']
|
id_field_name = self.document_type._meta['id_field']
|
||||||
id_field = self.document_type._fields[id_field_name]
|
id_field = self.document_type._fields[id_field_name]
|
||||||
|
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if isinstance(document, Document):
|
if isinstance(document, Document):
|
||||||
# We need the id from the saved object to create the DBRef
|
# We need the id from the saved object to create the DBRef
|
||||||
id_ = document.pk
|
id_ = document.pk
|
||||||
@@ -1281,6 +1208,7 @@ class CachedReferenceField(BaseField):
|
|||||||
' been saved to the database')
|
' been saved to the database')
|
||||||
else:
|
else:
|
||||||
self.error('Only accept a document object')
|
self.error('Only accept a document object')
|
||||||
|
# TODO: should raise here or will fail next statement
|
||||||
|
|
||||||
value = SON((
|
value = SON((
|
||||||
('_id', id_field.to_mongo(id_)),
|
('_id', id_field.to_mongo(id_)),
|
||||||
@@ -1298,20 +1226,16 @@ class CachedReferenceField(BaseField):
|
|||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if isinstance(value, Document):
|
if isinstance(value, Document):
|
||||||
if value.pk is None:
|
if value.pk is None:
|
||||||
self.error('You can only reference documents once they have'
|
self.error('You can only reference documents once they have'
|
||||||
' been saved to the database')
|
' been saved to the database')
|
||||||
value_dict = {'_id': value.pk}
|
return {'_id': value.pk}
|
||||||
for field in self.fields:
|
|
||||||
value_dict.update({field: value[field]})
|
|
||||||
|
|
||||||
return value_dict
|
|
||||||
|
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
|
|
||||||
if not isinstance(value, self.document_type):
|
if not isinstance(value, self.document_type):
|
||||||
self.error('A CachedReferenceField only accepts documents')
|
self.error('A CachedReferenceField only accepts documents')
|
||||||
|
|
||||||
@@ -1344,12 +1268,6 @@ class GenericReferenceField(BaseField):
|
|||||||
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
|
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
|
||||||
that will be automatically dereferenced on access (lazily).
|
that will be automatically dereferenced on access (lazily).
|
||||||
|
|
||||||
Note this field works the same way as :class:`~mongoengine.document.ReferenceField`,
|
|
||||||
doing database I/O access the first time it is accessed (even if it's to access
|
|
||||||
it ``pk`` or ``id`` field).
|
|
||||||
To solve this you should consider using the
|
|
||||||
:class:`~mongoengine.fields.GenericLazyReferenceField`.
|
|
||||||
|
|
||||||
.. note ::
|
.. note ::
|
||||||
* Any documents used as a generic reference must be registered in the
|
* Any documents used as a generic reference must be registered in the
|
||||||
document registry. Importing the model will automatically register
|
document registry. Importing the model will automatically register
|
||||||
@@ -1372,8 +1290,6 @@ class GenericReferenceField(BaseField):
|
|||||||
elif isinstance(choice, type) and issubclass(choice, Document):
|
elif isinstance(choice, type) and issubclass(choice, Document):
|
||||||
self.choices.append(choice._class_name)
|
self.choices.append(choice._class_name)
|
||||||
else:
|
else:
|
||||||
# XXX ValidationError raised outside of the "validate"
|
|
||||||
# method.
|
|
||||||
self.error('Invalid choices provided: must be a list of'
|
self.error('Invalid choices provided: must be a list of'
|
||||||
'Document subclasses and/or six.string_typess')
|
'Document subclasses and/or six.string_typess')
|
||||||
|
|
||||||
@@ -1392,8 +1308,8 @@ class GenericReferenceField(BaseField):
|
|||||||
|
|
||||||
value = instance._data.get(self.name)
|
value = instance._data.get(self.name)
|
||||||
|
|
||||||
auto_dereference = instance._fields[self.name]._auto_dereference
|
self._auto_dereference = instance._fields[self.name]._auto_dereference
|
||||||
if auto_dereference and isinstance(value, (dict, SON)):
|
if self._auto_dereference and isinstance(value, (dict, SON)):
|
||||||
dereferenced = self.dereference(value)
|
dereferenced = self.dereference(value)
|
||||||
if dereferenced is None:
|
if dereferenced is None:
|
||||||
raise DoesNotExist('Trying to dereference unknown document %s' % value)
|
raise DoesNotExist('Trying to dereference unknown document %s' % value)
|
||||||
@@ -1437,7 +1353,6 @@ class GenericReferenceField(BaseField):
|
|||||||
# We need the id from the saved object to create the DBRef
|
# We need the id from the saved object to create the DBRef
|
||||||
id_ = document.id
|
id_ = document.id
|
||||||
if id_ is None:
|
if id_ is None:
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
self.error('You can only reference documents once they have'
|
self.error('You can only reference documents once they have'
|
||||||
' been saved to the database')
|
' been saved to the database')
|
||||||
else:
|
else:
|
||||||
@@ -1475,10 +1390,10 @@ class BinaryField(BaseField):
|
|||||||
return Binary(value)
|
return Binary(value)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, (six.binary_type, Binary)):
|
if not isinstance(value, (six.binary_type, six.text_type, Binary)):
|
||||||
self.error('BinaryField only accepts instances of '
|
self.error('BinaryField only accepts instances of '
|
||||||
'(%s, %s, Binary)' % (
|
'(%s, %s, Binary)' % (
|
||||||
six.binary_type.__name__, Binary.__name__))
|
six.binary_type.__name__, six.text_type.__name__))
|
||||||
|
|
||||||
if self.max_bytes is not None and len(value) > self.max_bytes:
|
if self.max_bytes is not None and len(value) > self.max_bytes:
|
||||||
self.error('Binary value is too long')
|
self.error('Binary value is too long')
|
||||||
@@ -1523,11 +1438,9 @@ class GridFSProxy(object):
|
|||||||
def __get__(self, instance, value):
|
def __get__(self, instance, value):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __bool__(self):
|
def __nonzero__(self):
|
||||||
return bool(self.grid_id)
|
return bool(self.grid_id)
|
||||||
|
|
||||||
__nonzero__ = __bool__ # For Py2 support
|
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
self_dict = self.__dict__
|
self_dict = self.__dict__
|
||||||
self_dict['_fs'] = None
|
self_dict['_fs'] = None
|
||||||
@@ -1545,9 +1458,9 @@ class GridFSProxy(object):
|
|||||||
return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
|
return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
gridout = self.get()
|
name = getattr(
|
||||||
filename = getattr(gridout, 'filename') if gridout else '<no file>'
|
self.get(), 'filename', self.grid_id) if self.get() else '(no file)'
|
||||||
return '<%s: %s (%s)>' % (self.__class__.__name__, filename, self.grid_id)
|
return '<%s: %s>' % (self.__class__.__name__, name)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if isinstance(other, GridFSProxy):
|
if isinstance(other, GridFSProxy):
|
||||||
@@ -1557,9 +1470,6 @@ class GridFSProxy(object):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fs(self):
|
def fs(self):
|
||||||
if not self._fs:
|
if not self._fs:
|
||||||
@@ -1867,9 +1777,12 @@ class ImageField(FileField):
|
|||||||
"""
|
"""
|
||||||
A Image File storage field.
|
A Image File storage field.
|
||||||
|
|
||||||
:param size: max size to store images, provided as (width, height, force)
|
@size (width, height, force):
|
||||||
if larger, it will be automatically resized (ex: size=(800, 600, True))
|
max size to store images, if larger will be automatically resized
|
||||||
:param thumbnail_size: size to generate a thumbnail, provided as (width, height, force)
|
ex: size=(800, 600, True)
|
||||||
|
|
||||||
|
@thumbnail (width, height, force):
|
||||||
|
size to generate a thumbnail
|
||||||
|
|
||||||
.. versionadded:: 0.6
|
.. versionadded:: 0.6
|
||||||
"""
|
"""
|
||||||
@@ -1940,7 +1853,8 @@ class SequenceField(BaseField):
|
|||||||
self.collection_name = collection_name or self.COLLECTION_NAME
|
self.collection_name = collection_name or self.COLLECTION_NAME
|
||||||
self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
|
self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
|
||||||
self.sequence_name = sequence_name
|
self.sequence_name = sequence_name
|
||||||
self.value_decorator = value_decorator if callable(value_decorator) else self.VALUE_DECORATOR
|
self.value_decorator = (callable(value_decorator) and
|
||||||
|
value_decorator or self.VALUE_DECORATOR)
|
||||||
super(SequenceField, self).__init__(*args, **kwargs)
|
super(SequenceField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def generate(self):
|
def generate(self):
|
||||||
@@ -2049,7 +1963,7 @@ class UUIDField(BaseField):
|
|||||||
if not isinstance(value, six.string_types):
|
if not isinstance(value, six.string_types):
|
||||||
value = six.text_type(value)
|
value = six.text_type(value)
|
||||||
return uuid.UUID(value)
|
return uuid.UUID(value)
|
||||||
except (ValueError, TypeError, AttributeError):
|
except Exception:
|
||||||
return original_value
|
return original_value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
@@ -2071,7 +1985,7 @@ class UUIDField(BaseField):
|
|||||||
value = str(value)
|
value = str(value)
|
||||||
try:
|
try:
|
||||||
uuid.UUID(value)
|
uuid.UUID(value)
|
||||||
except (ValueError, TypeError, AttributeError) as exc:
|
except Exception as exc:
|
||||||
self.error('Could not convert to UUID: %s' % exc)
|
self.error('Could not convert to UUID: %s' % exc)
|
||||||
|
|
||||||
|
|
||||||
@@ -2229,201 +2143,3 @@ class MultiPolygonField(GeoJsonBaseField):
|
|||||||
.. versionadded:: 0.9
|
.. versionadded:: 0.9
|
||||||
"""
|
"""
|
||||||
_type = 'MultiPolygon'
|
_type = 'MultiPolygon'
|
||||||
|
|
||||||
|
|
||||||
class LazyReferenceField(BaseField):
|
|
||||||
"""A really lazy reference to a document.
|
|
||||||
Unlike the :class:`~mongoengine.fields.ReferenceField` it will
|
|
||||||
**not** be automatically (lazily) dereferenced on access.
|
|
||||||
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
|
|
||||||
instance, allowing access to `pk` or manual dereference by using
|
|
||||||
``fetch()`` method.
|
|
||||||
|
|
||||||
.. versionadded:: 0.15
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, document_type, passthrough=False, dbref=False,
|
|
||||||
reverse_delete_rule=DO_NOTHING, **kwargs):
|
|
||||||
"""Initialises the Reference Field.
|
|
||||||
|
|
||||||
:param dbref: Store the reference as :class:`~pymongo.dbref.DBRef`
|
|
||||||
or as the :class:`~pymongo.objectid.ObjectId`.id .
|
|
||||||
:param reverse_delete_rule: Determines what to do when the referring
|
|
||||||
object is deleted
|
|
||||||
:param passthrough: When trying to access unknown fields, the
|
|
||||||
:class:`~mongoengine.base.datastructure.LazyReference` instance will
|
|
||||||
automatically call `fetch()` and try to retrive the field on the fetched
|
|
||||||
document. Note this only work getting field (not setting or deleting).
|
|
||||||
"""
|
|
||||||
# XXX ValidationError raised outside of the "validate" method.
|
|
||||||
if (
|
|
||||||
not isinstance(document_type, six.string_types) and
|
|
||||||
not issubclass(document_type, Document)
|
|
||||||
):
|
|
||||||
self.error('Argument to LazyReferenceField constructor must be a '
|
|
||||||
'document class or a string')
|
|
||||||
|
|
||||||
self.dbref = dbref
|
|
||||||
self.passthrough = passthrough
|
|
||||||
self.document_type_obj = document_type
|
|
||||||
self.reverse_delete_rule = reverse_delete_rule
|
|
||||||
super(LazyReferenceField, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def document_type(self):
|
|
||||||
if isinstance(self.document_type_obj, six.string_types):
|
|
||||||
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
|
|
||||||
self.document_type_obj = self.owner_document
|
|
||||||
else:
|
|
||||||
self.document_type_obj = get_document(self.document_type_obj)
|
|
||||||
return self.document_type_obj
|
|
||||||
|
|
||||||
def build_lazyref(self, value):
|
|
||||||
if isinstance(value, LazyReference):
|
|
||||||
if value.passthrough != self.passthrough:
|
|
||||||
value = LazyReference(value.document_type, value.pk, passthrough=self.passthrough)
|
|
||||||
elif value is not None:
|
|
||||||
if isinstance(value, self.document_type):
|
|
||||||
value = LazyReference(self.document_type, value.pk, passthrough=self.passthrough)
|
|
||||||
elif isinstance(value, DBRef):
|
|
||||||
value = LazyReference(self.document_type, value.id, passthrough=self.passthrough)
|
|
||||||
else:
|
|
||||||
# value is the primary key of the referenced document
|
|
||||||
value = LazyReference(self.document_type, value, passthrough=self.passthrough)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
"""Descriptor to allow lazy dereferencing."""
|
|
||||||
if instance is None:
|
|
||||||
# Document class being used rather than a document object
|
|
||||||
return self
|
|
||||||
|
|
||||||
value = self.build_lazyref(instance._data.get(self.name))
|
|
||||||
if value:
|
|
||||||
instance._data[self.name] = value
|
|
||||||
|
|
||||||
return super(LazyReferenceField, self).__get__(instance, owner)
|
|
||||||
|
|
||||||
def to_mongo(self, value):
|
|
||||||
if isinstance(value, LazyReference):
|
|
||||||
pk = value.pk
|
|
||||||
elif isinstance(value, self.document_type):
|
|
||||||
pk = value.pk
|
|
||||||
elif isinstance(value, DBRef):
|
|
||||||
pk = value.id
|
|
||||||
else:
|
|
||||||
# value is the primary key of the referenced document
|
|
||||||
pk = value
|
|
||||||
id_field_name = self.document_type._meta['id_field']
|
|
||||||
id_field = self.document_type._fields[id_field_name]
|
|
||||||
pk = id_field.to_mongo(pk)
|
|
||||||
if self.dbref:
|
|
||||||
return DBRef(self.document_type._get_collection_name(), pk)
|
|
||||||
else:
|
|
||||||
return pk
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
if isinstance(value, LazyReference):
|
|
||||||
if value.collection != self.document_type._get_collection_name():
|
|
||||||
self.error('Reference must be on a `%s` document.' % self.document_type)
|
|
||||||
pk = value.pk
|
|
||||||
elif isinstance(value, self.document_type):
|
|
||||||
pk = value.pk
|
|
||||||
elif isinstance(value, DBRef):
|
|
||||||
# TODO: check collection ?
|
|
||||||
collection = self.document_type._get_collection_name()
|
|
||||||
if value.collection != collection:
|
|
||||||
self.error("DBRef on bad collection (must be on `%s`)" % collection)
|
|
||||||
pk = value.id
|
|
||||||
else:
|
|
||||||
# value is the primary key of the referenced document
|
|
||||||
id_field_name = self.document_type._meta['id_field']
|
|
||||||
id_field = getattr(self.document_type, id_field_name)
|
|
||||||
pk = value
|
|
||||||
try:
|
|
||||||
id_field.validate(pk)
|
|
||||||
except ValidationError:
|
|
||||||
self.error(
|
|
||||||
"value should be `{0}` document, LazyReference or DBRef on `{0}` "
|
|
||||||
"or `{0}`'s primary key (i.e. `{1}`)".format(
|
|
||||||
self.document_type.__name__, type(id_field).__name__))
|
|
||||||
|
|
||||||
if pk is None:
|
|
||||||
self.error('You can only reference documents once they have been '
|
|
||||||
'saved to the database')
|
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
|
||||||
if value is None:
|
|
||||||
return None
|
|
||||||
super(LazyReferenceField, self).prepare_query_value(op, value)
|
|
||||||
return self.to_mongo(value)
|
|
||||||
|
|
||||||
def lookup_member(self, member_name):
|
|
||||||
return self.document_type._fields.get(member_name)
|
|
||||||
|
|
||||||
|
|
||||||
class GenericLazyReferenceField(GenericReferenceField):
|
|
||||||
"""A reference to *any* :class:`~mongoengine.document.Document` subclass.
|
|
||||||
Unlike the :class:`~mongoengine.fields.GenericReferenceField` it will
|
|
||||||
**not** be automatically (lazily) dereferenced on access.
|
|
||||||
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
|
|
||||||
instance, allowing access to `pk` or manual dereference by using
|
|
||||||
``fetch()`` method.
|
|
||||||
|
|
||||||
.. note ::
|
|
||||||
* Any documents used as a generic reference must be registered in the
|
|
||||||
document registry. Importing the model will automatically register
|
|
||||||
it.
|
|
||||||
|
|
||||||
* You can use the choices param to limit the acceptable Document types
|
|
||||||
|
|
||||||
.. versionadded:: 0.15
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
self.passthrough = kwargs.pop('passthrough', False)
|
|
||||||
super(GenericLazyReferenceField, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def _validate_choices(self, value):
|
|
||||||
if isinstance(value, LazyReference):
|
|
||||||
value = value.document_type._class_name
|
|
||||||
super(GenericLazyReferenceField, self)._validate_choices(value)
|
|
||||||
|
|
||||||
def build_lazyref(self, value):
|
|
||||||
if isinstance(value, LazyReference):
|
|
||||||
if value.passthrough != self.passthrough:
|
|
||||||
value = LazyReference(value.document_type, value.pk, passthrough=self.passthrough)
|
|
||||||
elif value is not None:
|
|
||||||
if isinstance(value, (dict, SON)):
|
|
||||||
value = LazyReference(get_document(value['_cls']), value['_ref'].id, passthrough=self.passthrough)
|
|
||||||
elif isinstance(value, Document):
|
|
||||||
value = LazyReference(type(value), value.pk, passthrough=self.passthrough)
|
|
||||||
return value
|
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
|
||||||
if instance is None:
|
|
||||||
return self
|
|
||||||
|
|
||||||
value = self.build_lazyref(instance._data.get(self.name))
|
|
||||||
if value:
|
|
||||||
instance._data[self.name] = value
|
|
||||||
|
|
||||||
return super(GenericLazyReferenceField, self).__get__(instance, owner)
|
|
||||||
|
|
||||||
def validate(self, value):
|
|
||||||
if isinstance(value, LazyReference) and value.pk is None:
|
|
||||||
self.error('You can only reference documents once they have been'
|
|
||||||
' saved to the database')
|
|
||||||
return super(GenericLazyReferenceField, self).validate(value)
|
|
||||||
|
|
||||||
def to_mongo(self, document):
|
|
||||||
if document is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
if isinstance(document, LazyReference):
|
|
||||||
return SON((
|
|
||||||
('_cls', document.document_type._class_name),
|
|
||||||
('_ref', DBRef(document.document_type._get_collection_name(), document.pk))
|
|
||||||
))
|
|
||||||
else:
|
|
||||||
return super(GenericLazyReferenceField, self).to_mongo(document)
|
|
||||||
|
@@ -6,7 +6,11 @@ import pymongo
|
|||||||
import six
|
import six
|
||||||
|
|
||||||
|
|
||||||
IS_PYMONGO_3 = pymongo.version_tuple[0] >= 3
|
if pymongo.version_tuple[0] < 3:
|
||||||
|
IS_PYMONGO_3 = False
|
||||||
|
else:
|
||||||
|
IS_PYMONGO_3 = True
|
||||||
|
|
||||||
|
|
||||||
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
|
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
|
||||||
StringIO = six.BytesIO
|
StringIO = six.BytesIO
|
||||||
@@ -19,10 +23,3 @@ if not six.PY3:
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
StringIO = cStringIO.StringIO
|
StringIO = cStringIO.StringIO
|
||||||
|
|
||||||
|
|
||||||
if six.PY3:
|
|
||||||
from collections.abc import Hashable
|
|
||||||
else:
|
|
||||||
# raises DeprecationWarnings in Python >=3.7
|
|
||||||
from collections import Hashable
|
|
||||||
|
@@ -2,6 +2,7 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import itertools
|
import itertools
|
||||||
|
import operator
|
||||||
import pprint
|
import pprint
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
@@ -17,7 +18,7 @@ from mongoengine import signals
|
|||||||
from mongoengine.base import get_document
|
from mongoengine.base import get_document
|
||||||
from mongoengine.common import _import_class
|
from mongoengine.common import _import_class
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.context_managers import set_write_concern, switch_db
|
from mongoengine.context_managers import switch_db
|
||||||
from mongoengine.errors import (InvalidQueryError, LookUpError,
|
from mongoengine.errors import (InvalidQueryError, LookUpError,
|
||||||
NotUniqueError, OperationError)
|
NotUniqueError, OperationError)
|
||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.python_support import IS_PYMONGO_3
|
||||||
@@ -38,6 +39,8 @@ CASCADE = 2
|
|||||||
DENY = 3
|
DENY = 3
|
||||||
PULL = 4
|
PULL = 4
|
||||||
|
|
||||||
|
RE_TYPE = type(re.compile(''))
|
||||||
|
|
||||||
|
|
||||||
class BaseQuerySet(object):
|
class BaseQuerySet(object):
|
||||||
"""A set of results returned from a query. Wraps a MongoDB cursor,
|
"""A set of results returned from a query. Wraps a MongoDB cursor,
|
||||||
@@ -64,6 +67,7 @@ class BaseQuerySet(object):
|
|||||||
self._scalar = []
|
self._scalar = []
|
||||||
self._none = False
|
self._none = False
|
||||||
self._as_pymongo = False
|
self._as_pymongo = False
|
||||||
|
self._as_pymongo_coerce = False
|
||||||
self._search_text = None
|
self._search_text = None
|
||||||
|
|
||||||
# If inheritance is allowed, only return instances and instances of
|
# If inheritance is allowed, only return instances and instances of
|
||||||
@@ -206,12 +210,14 @@ class BaseQuerySet(object):
|
|||||||
queryset = self.order_by()
|
queryset = self.order_by()
|
||||||
return False if queryset.first() is None else True
|
return False if queryset.first() is None else True
|
||||||
|
|
||||||
|
def __nonzero__(self):
|
||||||
|
"""Avoid to open all records in an if stmt in Py2."""
|
||||||
|
return self._has_data()
|
||||||
|
|
||||||
def __bool__(self):
|
def __bool__(self):
|
||||||
"""Avoid to open all records in an if stmt in Py3."""
|
"""Avoid to open all records in an if stmt in Py3."""
|
||||||
return self._has_data()
|
return self._has_data()
|
||||||
|
|
||||||
__nonzero__ = __bool__ # For Py2 support
|
|
||||||
|
|
||||||
# Core functions
|
# Core functions
|
||||||
|
|
||||||
def all(self):
|
def all(self):
|
||||||
@@ -264,13 +270,13 @@ class BaseQuerySet(object):
|
|||||||
queryset = queryset.filter(*q_objs, **query)
|
queryset = queryset.filter(*q_objs, **query)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = six.next(queryset)
|
result = queryset.next()
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
msg = ('%s matching query does not exist.'
|
msg = ('%s matching query does not exist.'
|
||||||
% queryset._document._class_name)
|
% queryset._document._class_name)
|
||||||
raise queryset._document.DoesNotExist(msg)
|
raise queryset._document.DoesNotExist(msg)
|
||||||
try:
|
try:
|
||||||
six.next(queryset)
|
queryset.next()
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -345,24 +351,11 @@ class BaseQuerySet(object):
|
|||||||
documents=docs, **signal_kwargs)
|
documents=docs, **signal_kwargs)
|
||||||
|
|
||||||
raw = [doc.to_mongo() for doc in docs]
|
raw = [doc.to_mongo() for doc in docs]
|
||||||
|
|
||||||
with set_write_concern(self._collection, write_concern) as collection:
|
|
||||||
insert_func = collection.insert_many
|
|
||||||
if return_one:
|
|
||||||
raw = raw[0]
|
|
||||||
insert_func = collection.insert_one
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
inserted_result = insert_func(raw)
|
ids = self._collection.insert(raw, **write_concern)
|
||||||
ids = [inserted_result.inserted_id] if return_one else inserted_result.inserted_ids
|
|
||||||
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))
|
||||||
except pymongo.errors.BulkWriteError as err:
|
|
||||||
# inserting documents that already have an _id field will
|
|
||||||
# give huge performance debt or raise
|
|
||||||
message = u'Document must not have _id value before bulk write (%s)'
|
|
||||||
raise NotUniqueError(message % six.text_type(err))
|
|
||||||
except pymongo.errors.OperationFailure as err:
|
except pymongo.errors.OperationFailure as err:
|
||||||
message = 'Could not save document (%s)'
|
message = 'Could not save document (%s)'
|
||||||
if re.match('^E1100[01] duplicate key', six.text_type(err)):
|
if re.match('^E1100[01] duplicate key', six.text_type(err)):
|
||||||
@@ -372,20 +365,18 @@ class BaseQuerySet(object):
|
|||||||
raise NotUniqueError(message % six.text_type(err))
|
raise NotUniqueError(message % six.text_type(err))
|
||||||
raise OperationError(message % six.text_type(err))
|
raise OperationError(message % six.text_type(err))
|
||||||
|
|
||||||
# Apply inserted_ids to documents
|
|
||||||
for doc, doc_id in zip(docs, ids):
|
|
||||||
doc.pk = doc_id
|
|
||||||
|
|
||||||
if not load_bulk:
|
if not load_bulk:
|
||||||
signals.post_bulk_insert.send(
|
signals.post_bulk_insert.send(
|
||||||
self._document, documents=docs, loaded=False, **signal_kwargs)
|
self._document, documents=docs, loaded=False, **signal_kwargs)
|
||||||
return ids[0] if return_one else ids
|
return return_one and ids[0] or ids
|
||||||
|
|
||||||
documents = self.in_bulk(ids)
|
documents = self.in_bulk(ids)
|
||||||
results = [documents.get(obj_id) for obj_id in ids]
|
results = []
|
||||||
|
for obj_id in ids:
|
||||||
|
results.append(documents.get(obj_id))
|
||||||
signals.post_bulk_insert.send(
|
signals.post_bulk_insert.send(
|
||||||
self._document, documents=results, loaded=True, **signal_kwargs)
|
self._document, documents=results, loaded=True, **signal_kwargs)
|
||||||
return results[0] if return_one else results
|
return return_one and results[0] or results
|
||||||
|
|
||||||
def count(self, with_limit_and_skip=False):
|
def count(self, with_limit_and_skip=False):
|
||||||
"""Count the selected elements in the query.
|
"""Count the selected elements in the query.
|
||||||
@@ -394,7 +385,7 @@ class BaseQuerySet(object):
|
|||||||
:meth:`skip` that has been applied to this cursor into account when
|
:meth:`skip` that has been applied to this cursor into account when
|
||||||
getting the count
|
getting the count
|
||||||
"""
|
"""
|
||||||
if self._limit == 0 and with_limit_and_skip is False or self._none:
|
if self._limit == 0 and with_limit_and_skip or self._none:
|
||||||
return 0
|
return 0
|
||||||
return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
|
return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
|
||||||
|
|
||||||
@@ -496,9 +487,8 @@ class BaseQuerySet(object):
|
|||||||
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
|
||||||
wait until at least two servers have recorded the write and
|
wait until at least two servers have recorded the write and
|
||||||
will force an fsync on the primary server.
|
will force an fsync on the primary server.
|
||||||
:param full_result: Return the full result dictionary rather than just the number
|
:param full_result: Return the full result rather than just the number
|
||||||
updated, e.g. return
|
updated.
|
||||||
``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
|
|
||||||
:param update: Django-style update keyword arguments
|
:param update: Django-style update keyword arguments
|
||||||
|
|
||||||
.. versionadded:: 0.2
|
.. versionadded:: 0.2
|
||||||
@@ -521,15 +511,12 @@ class BaseQuerySet(object):
|
|||||||
else:
|
else:
|
||||||
update['$set'] = {'_cls': queryset._document._class_name}
|
update['$set'] = {'_cls': queryset._document._class_name}
|
||||||
try:
|
try:
|
||||||
with set_write_concern(queryset._collection, write_concern) as collection:
|
result = queryset._collection.update(query, update, multi=multi,
|
||||||
update_func = collection.update_one
|
upsert=upsert, **write_concern)
|
||||||
if multi:
|
|
||||||
update_func = collection.update_many
|
|
||||||
result = update_func(query, update, upsert=upsert)
|
|
||||||
if full_result:
|
if full_result:
|
||||||
return result
|
return result
|
||||||
elif result.raw_result:
|
elif result:
|
||||||
return result.raw_result['n']
|
return result['n']
|
||||||
except pymongo.errors.DuplicateKeyError as err:
|
except pymongo.errors.DuplicateKeyError as err:
|
||||||
raise NotUniqueError(u'Update failed (%s)' % six.text_type(err))
|
raise NotUniqueError(u'Update failed (%s)' % six.text_type(err))
|
||||||
except pymongo.errors.OperationFailure as err:
|
except pymongo.errors.OperationFailure as err:
|
||||||
@@ -558,10 +545,10 @@ class BaseQuerySet(object):
|
|||||||
write_concern=write_concern,
|
write_concern=write_concern,
|
||||||
full_result=True, **update)
|
full_result=True, **update)
|
||||||
|
|
||||||
if atomic_update.raw_result['updatedExisting']:
|
if atomic_update['updatedExisting']:
|
||||||
document = self.get()
|
document = self.get()
|
||||||
else:
|
else:
|
||||||
document = self._document.objects.with_id(atomic_update.upserted_id)
|
document = self._document.objects.with_id(atomic_update['upserted'])
|
||||||
return document
|
return document
|
||||||
|
|
||||||
def update_one(self, upsert=False, write_concern=None, **update):
|
def update_one(self, upsert=False, write_concern=None, **update):
|
||||||
@@ -741,12 +728,11 @@ class BaseQuerySet(object):
|
|||||||
'%s is not a subclass of BaseQuerySet' % new_qs.__name__)
|
'%s is not a subclass of BaseQuerySet' % new_qs.__name__)
|
||||||
|
|
||||||
copy_props = ('_mongo_query', '_initial_query', '_none', '_query_obj',
|
copy_props = ('_mongo_query', '_initial_query', '_none', '_query_obj',
|
||||||
'_where_clause', '_loaded_fields', '_ordering',
|
'_where_clause', '_loaded_fields', '_ordering', '_snapshot',
|
||||||
'_snapshot', '_timeout', '_class_check', '_slave_okay',
|
'_timeout', '_class_check', '_slave_okay', '_read_preference',
|
||||||
'_read_preference', '_iter', '_scalar', '_as_pymongo',
|
'_iter', '_scalar', '_as_pymongo', '_as_pymongo_coerce',
|
||||||
'_limit', '_skip', '_hint', '_auto_dereference',
|
'_limit', '_skip', '_hint', '_auto_dereference',
|
||||||
'_search_text', 'only_fields', '_max_time_ms',
|
'_search_text', 'only_fields', '_max_time_ms', '_comment')
|
||||||
'_comment')
|
|
||||||
|
|
||||||
for prop in copy_props:
|
for prop in copy_props:
|
||||||
val = getattr(self, prop)
|
val = getattr(self, prop)
|
||||||
@@ -773,11 +759,10 @@ class BaseQuerySet(object):
|
|||||||
"""Limit the number of returned documents to `n`. This may also be
|
"""Limit the number of returned documents to `n`. This may also be
|
||||||
achieved using array-slicing syntax (e.g. ``User.objects[:5]``).
|
achieved using array-slicing syntax (e.g. ``User.objects[:5]``).
|
||||||
|
|
||||||
:param n: the maximum number of objects to return if n is greater than 0.
|
:param n: the maximum number of objects to return
|
||||||
When 0 is passed, returns all the documents in the cursor
|
|
||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._limit = n
|
queryset._limit = n if n != 0 else 1
|
||||||
|
|
||||||
# If a cursor object has already been created, apply the limit to it.
|
# If a cursor object has already been created, apply the limit to it.
|
||||||
if queryset._cursor_obj:
|
if queryset._cursor_obj:
|
||||||
@@ -954,8 +939,7 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
posts = BlogPost.objects(...).fields(slice__comments=5)
|
posts = BlogPost.objects(...).fields(slice__comments=5)
|
||||||
|
|
||||||
:param kwargs: A set of keyword arguments identifying what to
|
:param kwargs: A set keywors arguments identifying what to include.
|
||||||
include, exclude, or slice.
|
|
||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
@@ -975,10 +959,11 @@ class BaseQuerySet(object):
|
|||||||
# explicitly included, and then more complicated operators such as
|
# explicitly included, and then more complicated operators such as
|
||||||
# $slice.
|
# $slice.
|
||||||
def _sort_key(field_tuple):
|
def _sort_key(field_tuple):
|
||||||
_, value = field_tuple
|
key, value = field_tuple
|
||||||
if isinstance(value, int):
|
if isinstance(value, (int)):
|
||||||
return value # 0 for exclusion, 1 for inclusion
|
return value # 0 for exclusion, 1 for inclusion
|
||||||
return 2 # so that complex values appear last
|
else:
|
||||||
|
return 2 # so that complex values appear last
|
||||||
|
|
||||||
fields = sorted(cleaned_fields, key=_sort_key)
|
fields = sorted(cleaned_fields, key=_sort_key)
|
||||||
|
|
||||||
@@ -1143,15 +1128,16 @@ class BaseQuerySet(object):
|
|||||||
"""An alias for scalar"""
|
"""An alias for scalar"""
|
||||||
return self.scalar(*fields)
|
return self.scalar(*fields)
|
||||||
|
|
||||||
def as_pymongo(self):
|
def as_pymongo(self, coerce_types=False):
|
||||||
"""Instead of returning Document instances, return raw values from
|
"""Instead of returning Document instances, return raw values from
|
||||||
pymongo.
|
pymongo.
|
||||||
|
|
||||||
This method is particularly useful if you don't need dereferencing
|
:param coerce_types: Field types (if applicable) would be use to
|
||||||
and care primarily about the speed of data retrieval.
|
coerce types.
|
||||||
"""
|
"""
|
||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset._as_pymongo = True
|
queryset._as_pymongo = True
|
||||||
|
queryset._as_pymongo_coerce = coerce_types
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
def max_time_ms(self, ms):
|
def max_time_ms(self, ms):
|
||||||
@@ -1196,10 +1182,6 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
pipeline = initial_pipeline + list(pipeline)
|
pipeline = initial_pipeline + list(pipeline)
|
||||||
|
|
||||||
if IS_PYMONGO_3 and self._read_preference is not None:
|
|
||||||
return self._collection.with_options(read_preference=self._read_preference) \
|
|
||||||
.aggregate(pipeline, cursor={}, **kwargs)
|
|
||||||
|
|
||||||
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
|
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
|
||||||
|
|
||||||
# JS functionality
|
# JS functionality
|
||||||
@@ -1475,13 +1457,13 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
# Iterator helpers
|
# Iterator helpers
|
||||||
|
|
||||||
def __next__(self):
|
def next(self):
|
||||||
"""Wrap the result in a :class:`~mongoengine.Document` object.
|
"""Wrap the result in a :class:`~mongoengine.Document` object.
|
||||||
"""
|
"""
|
||||||
if self._limit == 0 or self._none:
|
if self._limit == 0 or self._none:
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
|
|
||||||
raw_doc = six.next(self._cursor)
|
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)
|
||||||
@@ -1495,8 +1477,6 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
next = __next__ # For Python2 support
|
|
||||||
|
|
||||||
def rewind(self):
|
def rewind(self):
|
||||||
"""Rewind the cursor to its unevaluated state.
|
"""Rewind the cursor to its unevaluated state.
|
||||||
|
|
||||||
@@ -1598,9 +1578,6 @@ class BaseQuerySet(object):
|
|||||||
if self._batch_size is not None:
|
if self._batch_size is not None:
|
||||||
self._cursor_obj.batch_size(self._batch_size)
|
self._cursor_obj.batch_size(self._batch_size)
|
||||||
|
|
||||||
if self._comment is not None:
|
|
||||||
self._cursor_obj.comment(self._comment)
|
|
||||||
|
|
||||||
return self._cursor_obj
|
return self._cursor_obj
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
@@ -1745,33 +1722,25 @@ class BaseQuerySet(object):
|
|||||||
return frequencies
|
return frequencies
|
||||||
|
|
||||||
def _fields_to_dbfields(self, fields):
|
def _fields_to_dbfields(self, fields):
|
||||||
"""Translate fields' paths to their db equivalents."""
|
"""Translate fields paths to its db equivalents"""
|
||||||
|
ret = []
|
||||||
subclasses = []
|
subclasses = []
|
||||||
if self._document._meta['allow_inheritance']:
|
document = self._document
|
||||||
|
if document._meta['allow_inheritance']:
|
||||||
subclasses = [get_document(x)
|
subclasses = [get_document(x)
|
||||||
for x in self._document._subclasses][1:]
|
for x in document._subclasses][1:]
|
||||||
|
|
||||||
db_field_paths = []
|
|
||||||
for field in fields:
|
for field in fields:
|
||||||
field_parts = field.split('.')
|
|
||||||
try:
|
try:
|
||||||
field = '.'.join(
|
field = '.'.join(f.db_field for f in
|
||||||
f if isinstance(f, six.string_types) else f.db_field
|
document._lookup_field(field.split('.')))
|
||||||
for f in self._document._lookup_field(field_parts)
|
ret.append(field)
|
||||||
)
|
|
||||||
db_field_paths.append(field)
|
|
||||||
except LookUpError as err:
|
except LookUpError as err:
|
||||||
found = False
|
found = False
|
||||||
|
|
||||||
# If a field path wasn't found on the main document, go
|
|
||||||
# through its subclasses and see if it exists on any of them.
|
|
||||||
for subdoc in subclasses:
|
for subdoc in subclasses:
|
||||||
try:
|
try:
|
||||||
subfield = '.'.join(
|
subfield = '.'.join(f.db_field for f in
|
||||||
f if isinstance(f, six.string_types) else f.db_field
|
subdoc._lookup_field(field.split('.')))
|
||||||
for f in subdoc._lookup_field(field_parts)
|
ret.append(subfield)
|
||||||
)
|
|
||||||
db_field_paths.append(subfield)
|
|
||||||
found = True
|
found = True
|
||||||
break
|
break
|
||||||
except LookUpError:
|
except LookUpError:
|
||||||
@@ -1779,8 +1748,7 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
if not found:
|
if not found:
|
||||||
raise err
|
raise err
|
||||||
|
return ret
|
||||||
return db_field_paths
|
|
||||||
|
|
||||||
def _get_order_by(self, keys):
|
def _get_order_by(self, keys):
|
||||||
"""Given a list of MongoEngine-style sort keys, return a list
|
"""Given a list of MongoEngine-style sort keys, return a list
|
||||||
@@ -1831,25 +1799,59 @@ class BaseQuerySet(object):
|
|||||||
|
|
||||||
return tuple(data)
|
return tuple(data)
|
||||||
|
|
||||||
def _get_as_pymongo(self, doc):
|
def _get_as_pymongo(self, row):
|
||||||
"""Clean up a PyMongo doc, removing fields that were only fetched
|
# Extract which fields paths we should follow if .fields(...) was
|
||||||
for the sake of MongoEngine's implementation, and return it.
|
# used. If not, handle all fields.
|
||||||
"""
|
if not getattr(self, '__as_pymongo_fields', None):
|
||||||
# Always remove _cls as a MongoEngine's implementation detail.
|
self.__as_pymongo_fields = []
|
||||||
if '_cls' in doc:
|
|
||||||
del doc['_cls']
|
|
||||||
|
|
||||||
# If the _id was not included in a .only or was excluded in a .exclude,
|
for field in self._loaded_fields.fields - set(['_cls']):
|
||||||
# remove it from the doc (we always fetch it so that we can properly
|
self.__as_pymongo_fields.append(field)
|
||||||
# construct documents).
|
while '.' in field:
|
||||||
fields = self._loaded_fields
|
field, _ = field.rsplit('.', 1)
|
||||||
if fields and '_id' in doc and (
|
self.__as_pymongo_fields.append(field)
|
||||||
(fields.value == QueryFieldList.ONLY and '_id' not in fields.fields) or
|
|
||||||
(fields.value == QueryFieldList.EXCLUDE and '_id' in fields.fields)
|
|
||||||
):
|
|
||||||
del doc['_id']
|
|
||||||
|
|
||||||
return doc
|
all_fields = not self.__as_pymongo_fields
|
||||||
|
|
||||||
|
def clean(data, path=None):
|
||||||
|
path = path or ''
|
||||||
|
|
||||||
|
if isinstance(data, dict):
|
||||||
|
new_data = {}
|
||||||
|
for key, value in data.iteritems():
|
||||||
|
new_path = '%s.%s' % (path, key) if path else key
|
||||||
|
|
||||||
|
if all_fields:
|
||||||
|
include_field = True
|
||||||
|
elif self._loaded_fields.value == QueryFieldList.ONLY:
|
||||||
|
include_field = new_path in self.__as_pymongo_fields
|
||||||
|
else:
|
||||||
|
include_field = new_path not in self.__as_pymongo_fields
|
||||||
|
|
||||||
|
if include_field:
|
||||||
|
new_data[key] = clean(value, path=new_path)
|
||||||
|
data = new_data
|
||||||
|
elif isinstance(data, list):
|
||||||
|
data = [clean(d, path=path) for d in data]
|
||||||
|
else:
|
||||||
|
if self._as_pymongo_coerce:
|
||||||
|
# If we need to coerce types, we need to determine the
|
||||||
|
# type of this field and use the corresponding
|
||||||
|
# .to_python(...)
|
||||||
|
EmbeddedDocumentField = _import_class('EmbeddedDocumentField')
|
||||||
|
|
||||||
|
obj = self._document
|
||||||
|
for chunk in path.split('.'):
|
||||||
|
obj = getattr(obj, chunk, None)
|
||||||
|
if obj is None:
|
||||||
|
break
|
||||||
|
elif isinstance(obj, EmbeddedDocumentField):
|
||||||
|
obj = obj.document_type
|
||||||
|
if obj and data is not None:
|
||||||
|
data = obj.to_python(data)
|
||||||
|
return data
|
||||||
|
|
||||||
|
return clean(row)
|
||||||
|
|
||||||
def _sub_js_fields(self, code):
|
def _sub_js_fields(self, code):
|
||||||
"""When fields are specified with [~fieldname] syntax, where
|
"""When fields are specified with [~fieldname] syntax, where
|
||||||
@@ -1872,8 +1874,8 @@ class BaseQuerySet(object):
|
|||||||
# Substitute the correct name for the field into the javascript
|
# Substitute the correct name for the field into the javascript
|
||||||
return '.'.join([f.db_field for f in fields])
|
return '.'.join([f.db_field for f in fields])
|
||||||
|
|
||||||
code = re.sub(r'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code)
|
code = re.sub(u'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code)
|
||||||
code = re.sub(r'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub,
|
code = re.sub(u'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub,
|
||||||
code)
|
code)
|
||||||
return code
|
return code
|
||||||
|
|
||||||
|
@@ -63,11 +63,9 @@ class QueryFieldList(object):
|
|||||||
self._only_called = True
|
self._only_called = True
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def __bool__(self):
|
def __nonzero__(self):
|
||||||
return bool(self.fields)
|
return bool(self.fields)
|
||||||
|
|
||||||
__nonzero__ = __bool__ # For Py2 support
|
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self):
|
||||||
field_list = {field: self.value for field in self.fields}
|
field_list = {field: self.value for field in self.fields}
|
||||||
if self.slice:
|
if self.slice:
|
||||||
|
@@ -36,7 +36,7 @@ class QuerySetManager(object):
|
|||||||
queryset_class = owner._meta.get('queryset_class', self.default)
|
queryset_class = owner._meta.get('queryset_class', self.default)
|
||||||
queryset = queryset_class(owner, owner._get_collection())
|
queryset = queryset_class(owner, owner._get_collection())
|
||||||
if self.get_queryset:
|
if self.get_queryset:
|
||||||
arg_count = self.get_queryset.__code__.co_argcount
|
arg_count = self.get_queryset.func_code.co_argcount
|
||||||
if arg_count == 1:
|
if arg_count == 1:
|
||||||
queryset = self.get_queryset(queryset)
|
queryset = self.get_queryset(queryset)
|
||||||
elif arg_count == 2:
|
elif arg_count == 2:
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
import six
|
|
||||||
|
|
||||||
from mongoengine.errors import OperationError
|
from mongoengine.errors import OperationError
|
||||||
from mongoengine.queryset.base import (BaseQuerySet, CASCADE, DENY, DO_NOTHING,
|
from mongoengine.queryset.base import (BaseQuerySet, CASCADE, DENY, DO_NOTHING,
|
||||||
NULLIFY, PULL)
|
NULLIFY, PULL)
|
||||||
@@ -89,10 +87,10 @@ class QuerySet(BaseQuerySet):
|
|||||||
yield self._result_cache[pos]
|
yield self._result_cache[pos]
|
||||||
pos += 1
|
pos += 1
|
||||||
|
|
||||||
# return if we already established there were no more
|
# Raise StopIteration if we already established there were no more
|
||||||
# docs in the db cursor.
|
# docs in the db cursor.
|
||||||
if not self._has_more:
|
if not self._has_more:
|
||||||
return
|
raise StopIteration
|
||||||
|
|
||||||
# Otherwise, populate more of the cache and repeat.
|
# Otherwise, populate more of the cache and repeat.
|
||||||
if len(self._result_cache) <= pos:
|
if len(self._result_cache) <= pos:
|
||||||
@@ -114,8 +112,8 @@ class QuerySet(BaseQuerySet):
|
|||||||
# Pull in ITER_CHUNK_SIZE docs from the database and store them in
|
# Pull in ITER_CHUNK_SIZE docs from the database and store them in
|
||||||
# the result cache.
|
# the result cache.
|
||||||
try:
|
try:
|
||||||
for _ in six.moves.range(ITER_CHUNK_SIZE):
|
for _ in xrange(ITER_CHUNK_SIZE):
|
||||||
self._result_cache.append(six.next(self))
|
self._result_cache.append(self.next())
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
# Getting this exception means there are no more docs in the
|
# Getting this exception means there are no more docs in the
|
||||||
# db cursor. Set _has_more to False so that we can use that
|
# db cursor. Set _has_more to False so that we can use that
|
||||||
@@ -168,9 +166,9 @@ class QuerySetNoCache(BaseQuerySet):
|
|||||||
return '.. queryset mid-iteration ..'
|
return '.. queryset mid-iteration ..'
|
||||||
|
|
||||||
data = []
|
data = []
|
||||||
for _ in six.moves.range(REPR_OUTPUT_SIZE + 1):
|
for _ in xrange(REPR_OUTPUT_SIZE + 1):
|
||||||
try:
|
try:
|
||||||
data.append(six.next(self))
|
data.append(self.next())
|
||||||
except StopIteration:
|
except StopIteration:
|
||||||
break
|
break
|
||||||
|
|
||||||
@@ -186,3 +184,10 @@ class QuerySetNoCache(BaseQuerySet):
|
|||||||
queryset = self.clone()
|
queryset = self.clone()
|
||||||
queryset.rewind()
|
queryset.rewind()
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
class QuerySetNoDeRef(QuerySet):
|
||||||
|
"""Special no_dereference QuerySet"""
|
||||||
|
|
||||||
|
def __dereference(items, max_depth=1, instance=None, name=None):
|
||||||
|
return items
|
||||||
|
@@ -101,8 +101,21 @@ def query(_doc_cls=None, **kwargs):
|
|||||||
value = value['_id']
|
value = value['_id']
|
||||||
|
|
||||||
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
|
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
|
||||||
# Raise an error if the in/nin/all/near param is not iterable.
|
# Raise an error if the in/nin/all/near param is not iterable. We need a
|
||||||
value = _prepare_query_for_iterable(field, op, value)
|
# special check for BaseDocument, because - although it's iterable - using
|
||||||
|
# it as such in the context of this method is most definitely a mistake.
|
||||||
|
BaseDocument = _import_class('BaseDocument')
|
||||||
|
if isinstance(value, BaseDocument):
|
||||||
|
raise TypeError("When using the `in`, `nin`, `all`, or "
|
||||||
|
"`near`-operators you can\'t use a "
|
||||||
|
"`Document`, you must wrap your object "
|
||||||
|
"in a list (object -> [object]).")
|
||||||
|
elif not hasattr(value, '__iter__'):
|
||||||
|
raise TypeError("The `in`, `nin`, `all`, or "
|
||||||
|
"`near`-operators must be applied to an "
|
||||||
|
"iterable (e.g. a list).")
|
||||||
|
else:
|
||||||
|
value = [field.prepare_query_value(op, v) for v in value]
|
||||||
|
|
||||||
# If we're querying a GenericReferenceField, we need to alter the
|
# If we're querying a GenericReferenceField, we need to alter the
|
||||||
# key depending on the value:
|
# key depending on the value:
|
||||||
@@ -147,7 +160,7 @@ def query(_doc_cls=None, **kwargs):
|
|||||||
if op is None or key not in mongo_query:
|
if op is None or key not in mongo_query:
|
||||||
mongo_query[key] = value
|
mongo_query[key] = value
|
||||||
elif key in mongo_query:
|
elif key in mongo_query:
|
||||||
if isinstance(mongo_query[key], dict) and isinstance(value, dict):
|
if isinstance(mongo_query[key], dict):
|
||||||
mongo_query[key].update(value)
|
mongo_query[key].update(value)
|
||||||
# $max/minDistance needs to come last - convert to SON
|
# $max/minDistance needs to come last - convert to SON
|
||||||
value_dict = mongo_query[key]
|
value_dict = mongo_query[key]
|
||||||
@@ -201,37 +214,30 @@ def update(_doc_cls=None, **update):
|
|||||||
format.
|
format.
|
||||||
"""
|
"""
|
||||||
mongo_update = {}
|
mongo_update = {}
|
||||||
|
|
||||||
for key, value in update.items():
|
for key, value in update.items():
|
||||||
if key == '__raw__':
|
if key == '__raw__':
|
||||||
mongo_update.update(value)
|
mongo_update.update(value)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
parts = key.split('__')
|
parts = key.split('__')
|
||||||
|
|
||||||
# if there is no operator, default to 'set'
|
# if there is no operator, default to 'set'
|
||||||
if len(parts) < 3 and parts[0] not in UPDATE_OPERATORS:
|
if len(parts) < 3 and parts[0] not in UPDATE_OPERATORS:
|
||||||
parts.insert(0, 'set')
|
parts.insert(0, 'set')
|
||||||
|
|
||||||
# Check for an operator and transform to mongo-style if there is
|
# Check for an operator and transform to mongo-style if there is
|
||||||
op = None
|
op = None
|
||||||
if parts[0] in UPDATE_OPERATORS:
|
if parts[0] in UPDATE_OPERATORS:
|
||||||
op = parts.pop(0)
|
op = parts.pop(0)
|
||||||
# Convert Pythonic names to Mongo equivalents
|
# Convert Pythonic names to Mongo equivalents
|
||||||
operator_map = {
|
if op in ('push_all', 'pull_all'):
|
||||||
'push_all': 'pushAll',
|
op = op.replace('_all', 'All')
|
||||||
'pull_all': 'pullAll',
|
elif op == 'dec':
|
||||||
'dec': 'inc',
|
|
||||||
'add_to_set': 'addToSet',
|
|
||||||
'set_on_insert': 'setOnInsert'
|
|
||||||
}
|
|
||||||
if op == 'dec':
|
|
||||||
# Support decrement by flipping a positive value's sign
|
# Support decrement by flipping a positive value's sign
|
||||||
# and using 'inc'
|
# and using 'inc'
|
||||||
|
op = 'inc'
|
||||||
value = -value
|
value = -value
|
||||||
# If the operator doesn't found from operator map, the op value
|
elif op == 'add_to_set':
|
||||||
# will stay unchanged
|
op = 'addToSet'
|
||||||
op = operator_map.get(op, op)
|
elif op == 'set_on_insert':
|
||||||
|
op = 'setOnInsert'
|
||||||
|
|
||||||
match = None
|
match = None
|
||||||
if parts[-1] in COMPARISON_OPERATORS:
|
if parts[-1] in COMPARISON_OPERATORS:
|
||||||
@@ -278,15 +284,7 @@ def update(_doc_cls=None, **update):
|
|||||||
if isinstance(field, GeoJsonBaseField):
|
if isinstance(field, GeoJsonBaseField):
|
||||||
value = field.to_mongo(value)
|
value = field.to_mongo(value)
|
||||||
|
|
||||||
if op == 'pull':
|
if op in (None, 'set', 'push', 'pull'):
|
||||||
if field.required or value is not None:
|
|
||||||
if match == 'in' and not isinstance(value, dict):
|
|
||||||
value = _prepare_query_for_iterable(field, op, value)
|
|
||||||
else:
|
|
||||||
value = field.prepare_query_value(op, value)
|
|
||||||
elif op == 'push' and isinstance(value, (list, tuple, set)):
|
|
||||||
value = [field.prepare_query_value(op, v) for v in value]
|
|
||||||
elif op in (None, 'set', 'push'):
|
|
||||||
if field.required or value is not None:
|
if field.required or value is not None:
|
||||||
value = field.prepare_query_value(op, value)
|
value = field.prepare_query_value(op, value)
|
||||||
elif op in ('pushAll', 'pullAll'):
|
elif op in ('pushAll', 'pullAll'):
|
||||||
@@ -298,8 +296,6 @@ def update(_doc_cls=None, **update):
|
|||||||
value = field.prepare_query_value(op, value)
|
value = field.prepare_query_value(op, value)
|
||||||
elif op == 'unset':
|
elif op == 'unset':
|
||||||
value = 1
|
value = 1
|
||||||
elif op == 'inc':
|
|
||||||
value = field.prepare_query_value(op, value)
|
|
||||||
|
|
||||||
if match:
|
if match:
|
||||||
match = '$' + match
|
match = '$' + match
|
||||||
@@ -323,17 +319,11 @@ def update(_doc_cls=None, **update):
|
|||||||
field_classes = [c.__class__ for c in cleaned_fields]
|
field_classes = [c.__class__ for c in cleaned_fields]
|
||||||
field_classes.reverse()
|
field_classes.reverse()
|
||||||
ListField = _import_class('ListField')
|
ListField = _import_class('ListField')
|
||||||
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
|
if ListField in field_classes:
|
||||||
if ListField in field_classes or EmbeddedDocumentListField in field_classes:
|
# Join all fields via dot notation to the last ListField
|
||||||
# Join all fields via dot notation to the last ListField or EmbeddedDocumentListField
|
|
||||||
# Then process as normal
|
# Then process as normal
|
||||||
if ListField in field_classes:
|
|
||||||
_check_field = ListField
|
|
||||||
else:
|
|
||||||
_check_field = EmbeddedDocumentListField
|
|
||||||
|
|
||||||
last_listField = len(
|
last_listField = len(
|
||||||
cleaned_fields) - field_classes.index(_check_field)
|
cleaned_fields) - field_classes.index(ListField)
|
||||||
key = '.'.join(parts[:last_listField])
|
key = '.'.join(parts[:last_listField])
|
||||||
parts = parts[last_listField:]
|
parts = parts[last_listField:]
|
||||||
parts.insert(0, key)
|
parts.insert(0, key)
|
||||||
@@ -343,26 +333,10 @@ def update(_doc_cls=None, **update):
|
|||||||
value = {key: value}
|
value = {key: value}
|
||||||
elif op == 'addToSet' and isinstance(value, list):
|
elif op == 'addToSet' and isinstance(value, list):
|
||||||
value = {key: {'$each': value}}
|
value = {key: {'$each': value}}
|
||||||
elif op in ('push', 'pushAll'):
|
|
||||||
if parts[-1].isdigit():
|
|
||||||
key = parts[0]
|
|
||||||
position = int(parts[-1])
|
|
||||||
# $position expects an iterable. If pushing a single value,
|
|
||||||
# wrap it in a list.
|
|
||||||
if not isinstance(value, (set, tuple, list)):
|
|
||||||
value = [value]
|
|
||||||
value = {key: {'$each': value, '$position': position}}
|
|
||||||
else:
|
|
||||||
if op == 'pushAll':
|
|
||||||
op = 'push' # convert to non-deprecated keyword
|
|
||||||
if not isinstance(value, (set, tuple, list)):
|
|
||||||
value = [value]
|
|
||||||
value = {key: {'$each': value}}
|
|
||||||
else:
|
|
||||||
value = {key: value}
|
|
||||||
else:
|
else:
|
||||||
value = {key: value}
|
value = {key: value}
|
||||||
key = '$' + op
|
key = '$' + op
|
||||||
|
|
||||||
if key not in mongo_update:
|
if key not in mongo_update:
|
||||||
mongo_update[key] = value
|
mongo_update[key] = value
|
||||||
elif key in mongo_update and isinstance(mongo_update[key], dict):
|
elif key in mongo_update and isinstance(mongo_update[key], dict):
|
||||||
@@ -429,6 +403,7 @@ def _infer_geometry(value):
|
|||||||
'type and coordinates keys')
|
'type and coordinates keys')
|
||||||
elif isinstance(value, (list, set)):
|
elif isinstance(value, (list, set)):
|
||||||
# TODO: shouldn't we test value[0][0][0][0] to see if it is MultiPolygon?
|
# TODO: shouldn't we test value[0][0][0][0] to see if it is MultiPolygon?
|
||||||
|
# TODO: should both TypeError and IndexError be alike interpreted?
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value[0][0][0]
|
value[0][0][0]
|
||||||
@@ -450,22 +425,3 @@ def _infer_geometry(value):
|
|||||||
|
|
||||||
raise InvalidQueryError('Invalid $geometry data. Can be either a '
|
raise InvalidQueryError('Invalid $geometry data. Can be either a '
|
||||||
'dictionary or (nested) lists of coordinate(s)')
|
'dictionary or (nested) lists of coordinate(s)')
|
||||||
|
|
||||||
|
|
||||||
def _prepare_query_for_iterable(field, op, value):
|
|
||||||
# We need a special check for BaseDocument, because - although it's iterable - using
|
|
||||||
# it as such in the context of this method is most definitely a mistake.
|
|
||||||
BaseDocument = _import_class('BaseDocument')
|
|
||||||
|
|
||||||
if isinstance(value, BaseDocument):
|
|
||||||
raise TypeError("When using the `in`, `nin`, `all`, or "
|
|
||||||
"`near`-operators you can\'t use a "
|
|
||||||
"`Document`, you must wrap your object "
|
|
||||||
"in a list (object -> [object]).")
|
|
||||||
|
|
||||||
if not hasattr(value, '__iter__'):
|
|
||||||
raise TypeError("The `in`, `nin`, `all`, or "
|
|
||||||
"`near`-operators must be applied to an "
|
|
||||||
"iterable (e.g. a list).")
|
|
||||||
|
|
||||||
return [field.prepare_query_value(op, v) for v in value]
|
|
||||||
|
@@ -3,7 +3,7 @@ import copy
|
|||||||
from mongoengine.errors import InvalidQueryError
|
from mongoengine.errors import InvalidQueryError
|
||||||
from mongoengine.queryset import transform
|
from mongoengine.queryset import transform
|
||||||
|
|
||||||
__all__ = ('Q', 'QNode')
|
__all__ = ('Q',)
|
||||||
|
|
||||||
|
|
||||||
class QNodeVisitor(object):
|
class QNodeVisitor(object):
|
||||||
@@ -131,10 +131,6 @@ class QCombination(QNode):
|
|||||||
else:
|
else:
|
||||||
self.children.append(node)
|
self.children.append(node)
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
op = ' & ' if self.operation is self.AND else ' | '
|
|
||||||
return '(%s)' % op.join([repr(node) for node in self.children])
|
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
for i in range(len(self.children)):
|
for i in range(len(self.children)):
|
||||||
if isinstance(self.children[i], QNode):
|
if isinstance(self.children[i], QNode):
|
||||||
@@ -155,9 +151,6 @@ class Q(QNode):
|
|||||||
def __init__(self, **query):
|
def __init__(self, **query):
|
||||||
self.query = query
|
self.query = query
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return 'Q(**%s)' % repr(self.query)
|
|
||||||
|
|
||||||
def accept(self, visitor):
|
def accept(self, visitor):
|
||||||
return visitor.visit_query(self)
|
return visitor.visit_query(self)
|
||||||
|
|
||||||
|
@@ -1,11 +1,11 @@
|
|||||||
[nosetests]
|
[nosetests]
|
||||||
verbosity=2
|
verbosity=2
|
||||||
detailed-errors=1
|
detailed-errors=1
|
||||||
#tests=tests
|
tests=tests
|
||||||
cover-package=mongoengine
|
cover-package=mongoengine
|
||||||
|
|
||||||
[flake8]
|
[flake8]
|
||||||
ignore=E501,F401,F403,F405,I201,I202,W504, W605
|
ignore=E501,F401,F403,F405,I201
|
||||||
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
|
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
|
||||||
max-complexity=47
|
max-complexity=47
|
||||||
application-import-names=mongoengine,tests
|
application-import-names=mongoengine,tests
|
||||||
|
9
setup.py
9
setup.py
@@ -44,8 +44,9 @@ CLASSIFIERS = [
|
|||||||
"Programming Language :: Python :: 2",
|
"Programming Language :: Python :: 2",
|
||||||
"Programming Language :: Python :: 2.7",
|
"Programming Language :: Python :: 2.7",
|
||||||
"Programming Language :: Python :: 3",
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.3",
|
||||||
|
"Programming Language :: Python :: 3.4",
|
||||||
"Programming Language :: Python :: 3.5",
|
"Programming Language :: Python :: 3.5",
|
||||||
"Programming Language :: Python :: 3.6",
|
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: Implementation :: PyPy",
|
"Programming Language :: Python :: Implementation :: PyPy",
|
||||||
'Topic :: Database',
|
'Topic :: Database',
|
||||||
@@ -69,9 +70,9 @@ setup(
|
|||||||
name='mongoengine',
|
name='mongoengine',
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
author='Harry Marr',
|
author='Harry Marr',
|
||||||
author_email='harry.marr@gmail.com',
|
author_email='harry.marr@{nospam}gmail.com',
|
||||||
maintainer="Stefan Wojcik",
|
maintainer="Ross Lawley",
|
||||||
maintainer_email="wojcikstefan@gmail.com",
|
maintainer_email="ross.lawley@{nospam}gmail.com",
|
||||||
url='http://mongoengine.org/',
|
url='http://mongoengine.org/',
|
||||||
download_url='https://github.com/MongoEngine/mongoengine/tarball/master',
|
download_url='https://github.com/MongoEngine/mongoengine/tarball/master',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
from .all_warnings import AllWarnings
|
from all_warnings import AllWarnings
|
||||||
from .document import *
|
from document import *
|
||||||
from .queryset import *
|
from queryset import *
|
||||||
from .fields import *
|
from fields import *
|
||||||
|
@@ -1,13 +1,13 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from .class_methods import *
|
from class_methods import *
|
||||||
from .delta import *
|
from delta import *
|
||||||
from .dynamic import *
|
from dynamic import *
|
||||||
from .indexes import *
|
from indexes import *
|
||||||
from .inheritance import *
|
from inheritance import *
|
||||||
from .instance import *
|
from instance import *
|
||||||
from .json_serialisation import *
|
from json_serialisation import *
|
||||||
from .validation import *
|
from validation import *
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -5,7 +5,6 @@ from mongoengine import *
|
|||||||
|
|
||||||
from mongoengine.queryset import NULLIFY, PULL
|
from mongoengine.queryset import NULLIFY, PULL
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from tests.utils import requires_mongodb_gte_26
|
|
||||||
|
|
||||||
__all__ = ("ClassMethodsTest", )
|
__all__ = ("ClassMethodsTest", )
|
||||||
|
|
||||||
@@ -66,10 +65,10 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
"""
|
"""
|
||||||
collection_name = 'person'
|
collection_name = 'person'
|
||||||
self.Person(name='Test').save()
|
self.Person(name='Test').save()
|
||||||
self.assertIn(collection_name, self.db.collection_names())
|
self.assertTrue(collection_name in self.db.collection_names())
|
||||||
|
|
||||||
self.Person.drop_collection()
|
self.Person.drop_collection()
|
||||||
self.assertNotIn(collection_name, self.db.collection_names())
|
self.assertFalse(collection_name in self.db.collection_names())
|
||||||
|
|
||||||
def test_register_delete_rule(self):
|
def test_register_delete_rule(self):
|
||||||
"""Ensure that register delete rule adds a delete rule to the document
|
"""Ensure that register delete rule adds a delete rule to the document
|
||||||
@@ -188,26 +187,6 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
|
||||||
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
|
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
|
||||||
def test_compare_indexes_for_text_indexes(self):
|
|
||||||
""" Ensure that compare_indexes behaves correctly for text indexes """
|
|
||||||
|
|
||||||
class Doc(Document):
|
|
||||||
a = StringField()
|
|
||||||
b = StringField()
|
|
||||||
meta = {'indexes': [
|
|
||||||
{'fields': ['$a', "$b"],
|
|
||||||
'default_language': 'english',
|
|
||||||
'weights': {'a': 10, 'b': 2}
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
|
|
||||||
Doc.drop_collection()
|
|
||||||
Doc.ensure_indexes()
|
|
||||||
actual = Doc.compare_indexes()
|
|
||||||
expected = {'missing': [], 'extra': []}
|
|
||||||
self.assertEqual(actual, expected)
|
|
||||||
|
|
||||||
def test_list_indexes_inheritance(self):
|
def test_list_indexes_inheritance(self):
|
||||||
""" ensure that all of the indexes are listed regardless of the super-
|
""" ensure that all of the indexes are listed regardless of the super-
|
||||||
or sub-class that we call it from
|
or sub-class that we call it from
|
||||||
@@ -340,7 +319,7 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
meta = {'collection': collection_name}
|
meta = {'collection': collection_name}
|
||||||
|
|
||||||
Person(name="Test User").save()
|
Person(name="Test User").save()
|
||||||
self.assertIn(collection_name, self.db.collection_names())
|
self.assertTrue(collection_name in self.db.collection_names())
|
||||||
|
|
||||||
user_obj = self.db[collection_name].find_one()
|
user_obj = self.db[collection_name].find_one()
|
||||||
self.assertEqual(user_obj['name'], "Test User")
|
self.assertEqual(user_obj['name'], "Test User")
|
||||||
@@ -349,7 +328,7 @@ class ClassMethodsTest(unittest.TestCase):
|
|||||||
self.assertEqual(user_obj.name, "Test User")
|
self.assertEqual(user_obj.name, "Test User")
|
||||||
|
|
||||||
Person.drop_collection()
|
Person.drop_collection()
|
||||||
self.assertNotIn(collection_name, self.db.collection_names())
|
self.assertFalse(collection_name in self.db.collection_names())
|
||||||
|
|
||||||
def test_collection_name_and_primary(self):
|
def test_collection_name_and_primary(self):
|
||||||
"""Ensure that a collection with a specified name may be used.
|
"""Ensure that a collection with a specified name may be used.
|
||||||
|
@@ -694,7 +694,7 @@ class DeltaTest(unittest.TestCase):
|
|||||||
organization.employees.append(person)
|
organization.employees.append(person)
|
||||||
updates, removals = organization._delta()
|
updates, removals = organization._delta()
|
||||||
self.assertEqual({}, removals)
|
self.assertEqual({}, removals)
|
||||||
self.assertIn('employees', updates)
|
self.assertTrue('employees' in updates)
|
||||||
|
|
||||||
def test_delta_with_dbref_false(self):
|
def test_delta_with_dbref_false(self):
|
||||||
person, organization, employee = self.circular_reference_deltas_2(Document, Document, False)
|
person, organization, employee = self.circular_reference_deltas_2(Document, Document, False)
|
||||||
@@ -709,7 +709,7 @@ class DeltaTest(unittest.TestCase):
|
|||||||
organization.employees.append(person)
|
organization.employees.append(person)
|
||||||
updates, removals = organization._delta()
|
updates, removals = organization._delta()
|
||||||
self.assertEqual({}, removals)
|
self.assertEqual({}, removals)
|
||||||
self.assertIn('employees', updates)
|
self.assertTrue('employees' in updates)
|
||||||
|
|
||||||
def test_nested_nested_fields_mark_as_changed(self):
|
def test_nested_nested_fields_mark_as_changed(self):
|
||||||
class EmbeddedDoc(EmbeddedDocument):
|
class EmbeddedDoc(EmbeddedDocument):
|
||||||
|
@@ -174,8 +174,8 @@ class DynamicTest(unittest.TestCase):
|
|||||||
|
|
||||||
Employee.drop_collection()
|
Employee.drop_collection()
|
||||||
|
|
||||||
self.assertIn('name', Employee._fields)
|
self.assertTrue('name' in Employee._fields)
|
||||||
self.assertIn('salary', Employee._fields)
|
self.assertTrue('salary' in Employee._fields)
|
||||||
self.assertEqual(Employee._get_collection_name(),
|
self.assertEqual(Employee._get_collection_name(),
|
||||||
self.Person._get_collection_name())
|
self.Person._get_collection_name())
|
||||||
|
|
||||||
@@ -189,7 +189,7 @@ class DynamicTest(unittest.TestCase):
|
|||||||
self.assertEqual(1, Employee.objects(age=20).count())
|
self.assertEqual(1, Employee.objects(age=20).count())
|
||||||
|
|
||||||
joe_bloggs = self.Person.objects.first()
|
joe_bloggs = self.Person.objects.first()
|
||||||
self.assertIsInstance(joe_bloggs, Employee)
|
self.assertTrue(isinstance(joe_bloggs, Employee))
|
||||||
|
|
||||||
def test_embedded_dynamic_document(self):
|
def test_embedded_dynamic_document(self):
|
||||||
"""Test dynamic embedded documents"""
|
"""Test dynamic embedded documents"""
|
||||||
|
@@ -1,14 +1,15 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import unittest
|
import unittest
|
||||||
from datetime import datetime
|
import sys
|
||||||
|
|
||||||
from nose.plugins.skip import SkipTest
|
from nose.plugins.skip import SkipTest
|
||||||
from pymongo.errors import OperationFailure
|
from datetime import datetime
|
||||||
import pymongo
|
import pymongo
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from tests.utils import get_mongodb_version, requires_mongodb_gte_26, MONGODB_32, MONGODB_3
|
|
||||||
|
from tests.utils import get_mongodb_version, needs_mongodb_v26
|
||||||
|
|
||||||
__all__ = ("IndexesTest", )
|
__all__ = ("IndexesTest", )
|
||||||
|
|
||||||
@@ -18,7 +19,6 @@ class IndexesTest(unittest.TestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.connection = connect(db='mongoenginetest')
|
self.connection = connect(db='mongoenginetest')
|
||||||
self.db = get_db()
|
self.db = get_db()
|
||||||
self.mongodb_version = get_mongodb_version()
|
|
||||||
|
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
@@ -70,7 +70,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(info), 4)
|
self.assertEqual(len(info), 4)
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
for expected in expected_specs:
|
for expected in expected_specs:
|
||||||
self.assertIn(expected['fields'], info)
|
self.assertTrue(expected['fields'] in info)
|
||||||
|
|
||||||
def _index_test_inheritance(self, InheritFrom):
|
def _index_test_inheritance(self, InheritFrom):
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
self.assertEqual(len(info), 4)
|
self.assertEqual(len(info), 4)
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
for expected in expected_specs:
|
for expected in expected_specs:
|
||||||
self.assertIn(expected['fields'], info)
|
self.assertTrue(expected['fields'] in info)
|
||||||
|
|
||||||
class ExtendedBlogPost(BlogPost):
|
class ExtendedBlogPost(BlogPost):
|
||||||
title = StringField()
|
title = StringField()
|
||||||
@@ -117,7 +117,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
info = ExtendedBlogPost.objects._collection.index_information()
|
info = ExtendedBlogPost.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
for expected in expected_specs:
|
for expected in expected_specs:
|
||||||
self.assertIn(expected['fields'], info)
|
self.assertTrue(expected['fields'] in info)
|
||||||
|
|
||||||
def test_indexes_document_inheritance(self):
|
def test_indexes_document_inheritance(self):
|
||||||
"""Ensure that indexes are used when meta[indexes] is specified for
|
"""Ensure that indexes are used when meta[indexes] is specified for
|
||||||
@@ -226,7 +226,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
list(Person.objects)
|
list(Person.objects)
|
||||||
info = Person.objects._collection.index_information()
|
info = Person.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('rank.title', 1)], info)
|
self.assertTrue([('rank.title', 1)] in info)
|
||||||
|
|
||||||
def test_explicit_geo2d_index(self):
|
def test_explicit_geo2d_index(self):
|
||||||
"""Ensure that geo2d indexes work when created via meta[indexes]
|
"""Ensure that geo2d indexes work when created via meta[indexes]
|
||||||
@@ -246,7 +246,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('location.point', '2d')], info)
|
self.assertTrue([('location.point', '2d')] in info)
|
||||||
|
|
||||||
def test_explicit_geo2d_index_embedded(self):
|
def test_explicit_geo2d_index_embedded(self):
|
||||||
"""Ensure that geo2d indexes work when created via meta[indexes]
|
"""Ensure that geo2d indexes work when created via meta[indexes]
|
||||||
@@ -269,7 +269,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('current.location.point', '2d')], info)
|
self.assertTrue([('current.location.point', '2d')] in info)
|
||||||
|
|
||||||
def test_explicit_geosphere_index(self):
|
def test_explicit_geosphere_index(self):
|
||||||
"""Ensure that geosphere indexes work when created via meta[indexes]
|
"""Ensure that geosphere indexes work when created via meta[indexes]
|
||||||
@@ -289,7 +289,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('location.point', '2dsphere')], info)
|
self.assertTrue([('location.point', '2dsphere')] in info)
|
||||||
|
|
||||||
def test_explicit_geohaystack_index(self):
|
def test_explicit_geohaystack_index(self):
|
||||||
"""Ensure that geohaystack indexes work when created via meta[indexes]
|
"""Ensure that geohaystack indexes work when created via meta[indexes]
|
||||||
@@ -311,7 +311,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
Place.ensure_indexes()
|
Place.ensure_indexes()
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('location.point', 'geoHaystack')], info)
|
self.assertTrue([('location.point', 'geoHaystack')] in info)
|
||||||
|
|
||||||
def test_create_geohaystack_index(self):
|
def test_create_geohaystack_index(self):
|
||||||
"""Ensure that geohaystack indexes can be created
|
"""Ensure that geohaystack indexes can be created
|
||||||
@@ -323,7 +323,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
|
Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
|
||||||
info = Place._get_collection().index_information()
|
info = Place._get_collection().index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info)
|
self.assertTrue([('location.point', 'geoHaystack'), ('name', 1)] in info)
|
||||||
|
|
||||||
def test_dictionary_indexes(self):
|
def test_dictionary_indexes(self):
|
||||||
"""Ensure that indexes are used when meta[indexes] contains
|
"""Ensure that indexes are used when meta[indexes] contains
|
||||||
@@ -356,7 +356,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
value.get('unique', False),
|
value.get('unique', False),
|
||||||
value.get('sparse', False))
|
value.get('sparse', False))
|
||||||
for key, value in info.iteritems()]
|
for key, value in info.iteritems()]
|
||||||
self.assertIn(([('addDate', -1)], True, True), info)
|
self.assertTrue(([('addDate', -1)], True, True) in info)
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
@@ -491,7 +491,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
obj = Test(a=1)
|
obj = Test(a=1)
|
||||||
obj.save()
|
obj.save()
|
||||||
|
|
||||||
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
|
IS_MONGODB_3 = get_mongodb_version()[0] >= 3
|
||||||
|
|
||||||
# Need to be explicit about covered indexes as mongoDB doesn't know if
|
# Need to be explicit about covered indexes as mongoDB doesn't know if
|
||||||
# the documents returned might have more keys in that here.
|
# the documents returned might have more keys in that here.
|
||||||
@@ -541,24 +541,19 @@ class IndexesTest(unittest.TestCase):
|
|||||||
[('categories', 1), ('_id', 1)])
|
[('categories', 1), ('_id', 1)])
|
||||||
|
|
||||||
def test_hint(self):
|
def test_hint(self):
|
||||||
MONGO_VER = self.mongodb_version
|
|
||||||
|
|
||||||
TAGS_INDEX_NAME = 'tags_1'
|
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
tags = ListField(StringField())
|
tags = ListField(StringField())
|
||||||
meta = {
|
meta = {
|
||||||
'indexes': [
|
'indexes': [
|
||||||
{
|
'tags',
|
||||||
'fields': ['tags'],
|
|
||||||
'name': TAGS_INDEX_NAME
|
|
||||||
}
|
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
for i in range(10):
|
for i in range(0, 10):
|
||||||
tags = [("tag %i" % n) for n in range(i % 2)]
|
tags = [("tag %i" % n) for n in range(0, i % 2)]
|
||||||
BlogPost(tags=tags).save()
|
BlogPost(tags=tags).save()
|
||||||
|
|
||||||
self.assertEqual(BlogPost.objects.count(), 10)
|
self.assertEqual(BlogPost.objects.count(), 10)
|
||||||
@@ -568,18 +563,18 @@ class IndexesTest(unittest.TestCase):
|
|||||||
if pymongo.version != '3.0':
|
if pymongo.version != '3.0':
|
||||||
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
|
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
|
||||||
|
|
||||||
if MONGO_VER == MONGODB_32:
|
|
||||||
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
|
|
||||||
# and you use hint on an index name that does not exist
|
|
||||||
with self.assertRaises(OperationFailure):
|
|
||||||
BlogPost.objects.hint([('ZZ', 1)]).count()
|
|
||||||
else:
|
|
||||||
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
|
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
|
||||||
|
|
||||||
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME ).count(), 10)
|
if pymongo.version >= '2.8':
|
||||||
|
self.assertEqual(BlogPost.objects.hint('tags').count(), 10)
|
||||||
|
else:
|
||||||
|
def invalid_index():
|
||||||
|
BlogPost.objects.hint('tags').next()
|
||||||
|
self.assertRaises(TypeError, invalid_index)
|
||||||
|
|
||||||
with self.assertRaises(Exception):
|
def invalid_index_2():
|
||||||
BlogPost.objects.hint(('tags', 1)).next()
|
return BlogPost.objects.hint(('tags', 1)).next()
|
||||||
|
self.assertRaises(Exception, invalid_index_2)
|
||||||
|
|
||||||
def test_unique(self):
|
def test_unique(self):
|
||||||
"""Ensure that uniqueness constraints are applied to fields.
|
"""Ensure that uniqueness constraints are applied to fields.
|
||||||
@@ -754,7 +749,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
except NotUniqueError:
|
except NotUniqueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def test_primary_save_duplicate_update_existing_object(self):
|
def test_unique_and_primary(self):
|
||||||
"""If you set a field as primary, then unexpected behaviour can occur.
|
"""If you set a field as primary, then unexpected behaviour can occur.
|
||||||
You won't create a duplicate but you will update an existing document.
|
You won't create a duplicate but you will update an existing document.
|
||||||
"""
|
"""
|
||||||
@@ -808,7 +803,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
info = BlogPost.objects._collection.index_information()
|
info = BlogPost.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
index_item = [('_id', 1), ('comments.comment_id', 1)]
|
index_item = [('_id', 1), ('comments.comment_id', 1)]
|
||||||
self.assertIn(index_item, info)
|
self.assertTrue(index_item in info)
|
||||||
|
|
||||||
def test_compound_key_embedded(self):
|
def test_compound_key_embedded(self):
|
||||||
|
|
||||||
@@ -855,8 +850,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
|
|
||||||
info = MyDoc.objects._collection.index_information()
|
info = MyDoc.objects._collection.index_information()
|
||||||
info = [value['key'] for key, value in info.iteritems()]
|
info = [value['key'] for key, value in info.iteritems()]
|
||||||
self.assertIn([('provider_ids.foo', 1)], info)
|
self.assertTrue([('provider_ids.foo', 1)] in info)
|
||||||
self.assertIn([('provider_ids.bar', 1)], info)
|
self.assertTrue([('provider_ids.bar', 1)] in info)
|
||||||
|
|
||||||
def test_sparse_compound_indexes(self):
|
def test_sparse_compound_indexes(self):
|
||||||
|
|
||||||
@@ -872,7 +867,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
|
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
|
||||||
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
|
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
@needs_mongodb_v26
|
||||||
def test_text_indexes(self):
|
def test_text_indexes(self):
|
||||||
class Book(Document):
|
class Book(Document):
|
||||||
title = DictField()
|
title = DictField()
|
||||||
@@ -881,9 +876,9 @@ class IndexesTest(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
indexes = Book.objects._collection.index_information()
|
indexes = Book.objects._collection.index_information()
|
||||||
self.assertIn("title_text", indexes)
|
self.assertTrue("title_text" in indexes)
|
||||||
key = indexes["title_text"]["key"]
|
key = indexes["title_text"]["key"]
|
||||||
self.assertIn(('_fts', 'text'), key)
|
self.assertTrue(('_fts', 'text') in key)
|
||||||
|
|
||||||
def test_hashed_indexes(self):
|
def test_hashed_indexes(self):
|
||||||
|
|
||||||
@@ -894,8 +889,8 @@ class IndexesTest(unittest.TestCase):
|
|||||||
}
|
}
|
||||||
|
|
||||||
indexes = Book.objects._collection.index_information()
|
indexes = Book.objects._collection.index_information()
|
||||||
self.assertIn("ref_id_hashed", indexes)
|
self.assertTrue("ref_id_hashed" in indexes)
|
||||||
self.assertIn(('ref_id', 'hashed'), indexes["ref_id_hashed"]["key"])
|
self.assertTrue(('ref_id', 'hashed') in indexes["ref_id_hashed"]["key"])
|
||||||
|
|
||||||
def test_indexes_after_database_drop(self):
|
def test_indexes_after_database_drop(self):
|
||||||
"""
|
"""
|
||||||
@@ -1018,7 +1013,7 @@ class IndexesTest(unittest.TestCase):
|
|||||||
TestDoc.ensure_indexes()
|
TestDoc.ensure_indexes()
|
||||||
|
|
||||||
index_info = TestDoc._get_collection().index_information()
|
index_info = TestDoc._get_collection().index_information()
|
||||||
self.assertIn('shard_1_1__cls_1_txt_1_1', index_info)
|
self.assertTrue('shard_1_1__cls_1_txt_1_1' in index_info)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
@@ -2,11 +2,14 @@
|
|||||||
import unittest
|
import unittest
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
from tests.fixtures import Base
|
from tests.fixtures import Base
|
||||||
|
|
||||||
from mongoengine import Document, EmbeddedDocument, connect, ReferenceField,\
|
from mongoengine import Document, EmbeddedDocument, connect
|
||||||
BooleanField, GenericReferenceField, IntField, StringField
|
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
|
from mongoengine.fields import (BooleanField, GenericReferenceField,
|
||||||
|
IntField, StringField)
|
||||||
|
|
||||||
__all__ = ('InheritanceTest', )
|
__all__ = ('InheritanceTest', )
|
||||||
|
|
||||||
@@ -255,10 +258,9 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
# can't inherit because Animal didn't explicitly allow inheritance
|
# can't inherit because Animal didn't explicitly allow inheritance
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError):
|
||||||
class Dog(Animal):
|
class Dog(Animal):
|
||||||
pass
|
pass
|
||||||
self.assertIn("Document Animal may not be subclassed", str(cm.exception))
|
|
||||||
|
|
||||||
# Check that _cls etc aren't present on simple documents
|
# Check that _cls etc aren't present on simple documents
|
||||||
dog = Animal(name='dog').save()
|
dog = Animal(name='dog').save()
|
||||||
@@ -266,7 +268,7 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
|
|
||||||
collection = self.db[Animal._get_collection_name()]
|
collection = self.db[Animal._get_collection_name()]
|
||||||
obj = collection.find_one()
|
obj = collection.find_one()
|
||||||
self.assertNotIn('_cls', obj)
|
self.assertFalse('_cls' in obj)
|
||||||
|
|
||||||
def test_cant_turn_off_inheritance_on_subclass(self):
|
def test_cant_turn_off_inheritance_on_subclass(self):
|
||||||
"""Ensure if inheritance is on in a subclass you cant turn it off.
|
"""Ensure if inheritance is on in a subclass you cant turn it off.
|
||||||
@@ -275,10 +277,9 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
name = StringField()
|
name = StringField()
|
||||||
meta = {'allow_inheritance': True}
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError):
|
||||||
class Mammal(Animal):
|
class Mammal(Animal):
|
||||||
meta = {'allow_inheritance': False}
|
meta = {'allow_inheritance': False}
|
||||||
self.assertEqual(str(cm.exception), 'Only direct subclasses of Document may set "allow_inheritance" to False')
|
|
||||||
|
|
||||||
def test_allow_inheritance_abstract_document(self):
|
def test_allow_inheritance_abstract_document(self):
|
||||||
"""Ensure that abstract documents can set inheritance rules and that
|
"""Ensure that abstract documents can set inheritance rules and that
|
||||||
@@ -291,48 +292,13 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
class Animal(FinalDocument):
|
class Animal(FinalDocument):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
|
|
||||||
with self.assertRaises(ValueError) as cm:
|
with self.assertRaises(ValueError):
|
||||||
class Mammal(Animal):
|
class Mammal(Animal):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Check that _cls isn't present in simple documents
|
# Check that _cls isn't present in simple documents
|
||||||
doc = Animal(name='dog')
|
doc = Animal(name='dog')
|
||||||
self.assertNotIn('_cls', doc.to_mongo())
|
self.assertFalse('_cls' in doc.to_mongo())
|
||||||
|
|
||||||
def test_using_abstract_class_in_reference_field(self):
|
|
||||||
# Ensures no regression of #1920
|
|
||||||
class AbstractHuman(Document):
|
|
||||||
meta = {'abstract': True}
|
|
||||||
|
|
||||||
class Dad(AbstractHuman):
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
class Home(Document):
|
|
||||||
dad = ReferenceField(AbstractHuman) # Referencing the abstract class
|
|
||||||
address = StringField()
|
|
||||||
|
|
||||||
dad = Dad(name='5').save()
|
|
||||||
Home(dad=dad, address='street').save()
|
|
||||||
|
|
||||||
home = Home.objects.first()
|
|
||||||
home.address = 'garbage'
|
|
||||||
home.save() # Was failing with ValidationError
|
|
||||||
|
|
||||||
def test_abstract_class_referencing_self(self):
|
|
||||||
# Ensures no regression of #1920
|
|
||||||
class Human(Document):
|
|
||||||
meta = {'abstract': True}
|
|
||||||
creator = ReferenceField('self', dbref=True)
|
|
||||||
|
|
||||||
class User(Human):
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
user = User(name='John').save()
|
|
||||||
user2 = User(name='Foo', creator=user).save()
|
|
||||||
|
|
||||||
user2 = User.objects.with_id(user2.id)
|
|
||||||
user2.name = 'Bar'
|
|
||||||
user2.save() # Was failing with ValidationError
|
|
||||||
|
|
||||||
def test_abstract_handle_ids_in_metaclass_properly(self):
|
def test_abstract_handle_ids_in_metaclass_properly(self):
|
||||||
|
|
||||||
@@ -392,11 +358,11 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
meta = {'abstract': True,
|
meta = {'abstract': True,
|
||||||
'allow_inheritance': False}
|
'allow_inheritance': False}
|
||||||
|
|
||||||
city = City(continent='asia')
|
bkk = City(continent='asia')
|
||||||
self.assertEqual(None, city.pk)
|
self.assertEqual(None, bkk.pk)
|
||||||
# TODO: expected error? Shouldn't we create a new error type?
|
# TODO: expected error? Shouldn't we create a new error type?
|
||||||
with self.assertRaises(KeyError):
|
with self.assertRaises(KeyError):
|
||||||
setattr(city, 'pk', 1)
|
setattr(bkk, 'pk', 1)
|
||||||
|
|
||||||
def test_allow_inheritance_embedded_document(self):
|
def test_allow_inheritance_embedded_document(self):
|
||||||
"""Ensure embedded documents respect inheritance."""
|
"""Ensure embedded documents respect inheritance."""
|
||||||
@@ -408,14 +374,14 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
doc = Comment(content='test')
|
doc = Comment(content='test')
|
||||||
self.assertNotIn('_cls', doc.to_mongo())
|
self.assertFalse('_cls' in doc.to_mongo())
|
||||||
|
|
||||||
class Comment(EmbeddedDocument):
|
class Comment(EmbeddedDocument):
|
||||||
content = StringField()
|
content = StringField()
|
||||||
meta = {'allow_inheritance': True}
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
doc = Comment(content='test')
|
doc = Comment(content='test')
|
||||||
self.assertIn('_cls', doc.to_mongo())
|
self.assertTrue('_cls' in doc.to_mongo())
|
||||||
|
|
||||||
def test_document_inheritance(self):
|
def test_document_inheritance(self):
|
||||||
"""Ensure mutliple inheritance of abstract documents
|
"""Ensure mutliple inheritance of abstract documents
|
||||||
@@ -468,8 +434,8 @@ class InheritanceTest(unittest.TestCase):
|
|||||||
for cls in [Animal, Fish, Guppy]:
|
for cls in [Animal, Fish, Guppy]:
|
||||||
self.assertEqual(cls._meta[k], v)
|
self.assertEqual(cls._meta[k], v)
|
||||||
|
|
||||||
self.assertNotIn('collection', Animal._meta)
|
self.assertFalse('collection' in Animal._meta)
|
||||||
self.assertNotIn('collection', Mammal._meta)
|
self.assertFalse('collection' in Mammal._meta)
|
||||||
|
|
||||||
self.assertEqual(Animal._get_collection_name(), None)
|
self.assertEqual(Animal._get_collection_name(), None)
|
||||||
self.assertEqual(Mammal._get_collection_name(), None)
|
self.assertEqual(Mammal._get_collection_name(), None)
|
||||||
|
@@ -8,12 +8,9 @@ import weakref
|
|||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from bson import DBRef, ObjectId
|
from bson import DBRef, ObjectId
|
||||||
from pymongo.errors import DuplicateKeyError
|
|
||||||
|
|
||||||
from tests import fixtures
|
from tests import fixtures
|
||||||
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
|
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
|
||||||
PickleDynamicEmbedded, PickleDynamicTest)
|
PickleDynamicEmbedded, PickleDynamicTest)
|
||||||
from tests.utils import MongoDBTestCase
|
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.base import get_document, _document_registry
|
from mongoengine.base import get_document, _document_registry
|
||||||
@@ -25,17 +22,18 @@ from mongoengine.queryset import NULLIFY, Q
|
|||||||
from mongoengine.context_managers import switch_db, query_counter
|
from mongoengine.context_managers import switch_db, query_counter
|
||||||
from mongoengine import signals
|
from mongoengine import signals
|
||||||
|
|
||||||
from tests.utils import requires_mongodb_gte_26
|
|
||||||
|
|
||||||
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__),
|
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__),
|
||||||
'../fields/mongoengine.png')
|
'../fields/mongoengine.png')
|
||||||
|
|
||||||
__all__ = ("InstanceTest",)
|
__all__ = ("InstanceTest",)
|
||||||
|
|
||||||
|
|
||||||
class InstanceTest(MongoDBTestCase):
|
class InstanceTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
connect(db='mongoenginetest')
|
||||||
|
self.db = get_db()
|
||||||
|
|
||||||
class Job(EmbeddedDocument):
|
class Job(EmbeddedDocument):
|
||||||
name = StringField()
|
name = StringField()
|
||||||
years = IntField()
|
years = IntField()
|
||||||
@@ -357,7 +355,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
user_son = User.objects._collection.find_one()
|
user_son = User.objects._collection.find_one()
|
||||||
self.assertEqual(user_son['_id'], 'test')
|
self.assertEqual(user_son['_id'], 'test')
|
||||||
self.assertNotIn('username', user_son['_id'])
|
self.assertTrue('username' not in user_son['_id'])
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
|
|
||||||
@@ -370,7 +368,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
user_son = User.objects._collection.find_one()
|
user_son = User.objects._collection.find_one()
|
||||||
self.assertEqual(user_son['_id'], 'mongo')
|
self.assertEqual(user_son['_id'], 'mongo')
|
||||||
self.assertNotIn('username', user_son['_id'])
|
self.assertTrue('username' not in user_son['_id'])
|
||||||
|
|
||||||
def test_document_not_registered(self):
|
def test_document_not_registered(self):
|
||||||
class Place(Document):
|
class Place(Document):
|
||||||
@@ -476,24 +474,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
def test_reload_with_changed_fields(self):
|
|
||||||
"""Ensures reloading will not affect changed fields"""
|
|
||||||
class User(Document):
|
|
||||||
name = StringField()
|
|
||||||
number = IntField()
|
|
||||||
User.drop_collection()
|
|
||||||
|
|
||||||
user = User(name="Bob", number=1).save()
|
|
||||||
user.name = "John"
|
|
||||||
user.number = 2
|
|
||||||
|
|
||||||
self.assertEqual(user._get_changed_fields(), ['name', 'number'])
|
|
||||||
user.reload('number')
|
|
||||||
self.assertEqual(user._get_changed_fields(), ['name'])
|
|
||||||
user.save()
|
|
||||||
user.reload()
|
|
||||||
self.assertEqual(user.name, "John")
|
|
||||||
|
|
||||||
def test_reload_referencing(self):
|
def test_reload_referencing(self):
|
||||||
"""Ensures reloading updates weakrefs correctly."""
|
"""Ensures reloading updates weakrefs correctly."""
|
||||||
class Embedded(EmbeddedDocument):
|
class Embedded(EmbeddedDocument):
|
||||||
@@ -539,7 +519,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
doc.dict_field['extra'] = 1
|
doc.dict_field['extra'] = 1
|
||||||
doc = doc.reload(10, 'list_field')
|
doc = doc.reload(10, 'list_field')
|
||||||
self.assertEqual(doc._get_changed_fields(), ['dict_field.extra'])
|
self.assertEqual(doc._get_changed_fields(), [])
|
||||||
self.assertEqual(len(doc.list_field), 5)
|
self.assertEqual(len(doc.list_field), 5)
|
||||||
self.assertEqual(len(doc.dict_field), 3)
|
self.assertEqual(len(doc.dict_field), 3)
|
||||||
self.assertEqual(len(doc.embedded_field.list_field), 4)
|
self.assertEqual(len(doc.embedded_field.list_field), 4)
|
||||||
@@ -550,14 +530,21 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
f = Foo()
|
f = Foo()
|
||||||
with self.assertRaises(Foo.DoesNotExist):
|
try:
|
||||||
f.reload()
|
f.reload()
|
||||||
|
except Foo.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
self.assertFalse("Threw wrong exception")
|
||||||
|
|
||||||
f.save()
|
f.save()
|
||||||
f.delete()
|
f.delete()
|
||||||
|
try:
|
||||||
with self.assertRaises(Foo.DoesNotExist):
|
|
||||||
f.reload()
|
f.reload()
|
||||||
|
except Foo.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except Exception:
|
||||||
|
self.assertFalse("Threw wrong exception")
|
||||||
|
|
||||||
def test_reload_of_non_strict_with_special_field_name(self):
|
def test_reload_of_non_strict_with_special_field_name(self):
|
||||||
"""Ensures reloading works for documents with meta strict == False."""
|
"""Ensures reloading works for documents with meta strict == False."""
|
||||||
@@ -594,10 +581,10 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
# Length = length(assigned fields + id)
|
# Length = length(assigned fields + id)
|
||||||
self.assertEqual(len(person), 5)
|
self.assertEqual(len(person), 5)
|
||||||
|
|
||||||
self.assertIn('age', person)
|
self.assertTrue('age' in person)
|
||||||
person.age = None
|
person.age = None
|
||||||
self.assertNotIn('age', person)
|
self.assertFalse('age' in person)
|
||||||
self.assertNotIn('nationality', person)
|
self.assertFalse('nationality' in person)
|
||||||
|
|
||||||
def test_embedded_document_to_mongo(self):
|
def test_embedded_document_to_mongo(self):
|
||||||
class Person(EmbeddedDocument):
|
class Person(EmbeddedDocument):
|
||||||
@@ -627,8 +614,8 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
class Comment(EmbeddedDocument):
|
class Comment(EmbeddedDocument):
|
||||||
content = StringField()
|
content = StringField()
|
||||||
|
|
||||||
self.assertIn('content', Comment._fields)
|
self.assertTrue('content' in Comment._fields)
|
||||||
self.assertNotIn('id', Comment._fields)
|
self.assertFalse('id' in Comment._fields)
|
||||||
|
|
||||||
def test_embedded_document_instance(self):
|
def test_embedded_document_instance(self):
|
||||||
"""Ensure that embedded documents can reference parent instance."""
|
"""Ensure that embedded documents can reference parent instance."""
|
||||||
@@ -727,12 +714,12 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
t = TestDocument(status="draft", pub_date=datetime.now())
|
t = TestDocument(status="draft", pub_date=datetime.now())
|
||||||
|
|
||||||
with self.assertRaises(ValidationError) as cm:
|
try:
|
||||||
t.save()
|
t.save()
|
||||||
|
except ValidationError as e:
|
||||||
expected_msg = "Draft entries may not have a publication date."
|
expect_msg = "Draft entries may not have a publication date."
|
||||||
self.assertIn(expected_msg, cm.exception.message)
|
self.assertTrue(expect_msg in e.message)
|
||||||
self.assertEqual(cm.exception.to_dict(), {'__all__': expected_msg})
|
self.assertEqual(e.to_dict(), {'__all__': expect_msg})
|
||||||
|
|
||||||
t = TestDocument(status="published")
|
t = TestDocument(status="published")
|
||||||
t.save(clean=False)
|
t.save(clean=False)
|
||||||
@@ -766,13 +753,12 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
TestDocument.drop_collection()
|
TestDocument.drop_collection()
|
||||||
|
|
||||||
t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25, z=15))
|
t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25, z=15))
|
||||||
|
try:
|
||||||
with self.assertRaises(ValidationError) as cm:
|
|
||||||
t.save()
|
t.save()
|
||||||
|
except ValidationError as e:
|
||||||
expected_msg = "Value of z != x + y"
|
expect_msg = "Value of z != x + y"
|
||||||
self.assertIn(expected_msg, cm.exception.message)
|
self.assertTrue(expect_msg in e.message)
|
||||||
self.assertEqual(cm.exception.to_dict(), {'doc': {'__all__': expected_msg}})
|
self.assertEqual(e.to_dict(), {'doc': {'__all__': expect_msg}})
|
||||||
|
|
||||||
t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25)).save()
|
t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25)).save()
|
||||||
self.assertEqual(t.doc.z, 35)
|
self.assertEqual(t.doc.z, 35)
|
||||||
@@ -840,22 +826,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
|
||||||
def test_modify_with_positional_push(self):
|
|
||||||
class BlogPost(Document):
|
|
||||||
tags = ListField(StringField())
|
|
||||||
|
|
||||||
post = BlogPost.objects.create(tags=['python'])
|
|
||||||
self.assertEqual(post.tags, ['python'])
|
|
||||||
post.modify(push__tags__0=['code', 'mongo'])
|
|
||||||
self.assertEqual(post.tags, ['code', 'mongo', 'python'])
|
|
||||||
|
|
||||||
# Assert same order of the list items is maintained in the db
|
|
||||||
self.assertEqual(
|
|
||||||
BlogPost._get_collection().find_one({'_id': post.pk})['tags'],
|
|
||||||
['code', 'mongo', 'python']
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_save(self):
|
def test_save(self):
|
||||||
"""Ensure that a document may be saved in the database."""
|
"""Ensure that a document may be saved in the database."""
|
||||||
|
|
||||||
@@ -1353,23 +1323,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
site = Site.objects.first()
|
site = Site.objects.first()
|
||||||
self.assertEqual(site.page.log_message, "Error: Dummy message")
|
self.assertEqual(site.page.log_message, "Error: Dummy message")
|
||||||
|
|
||||||
def test_update_list_field(self):
|
|
||||||
"""Test update on `ListField` with $pull + $in.
|
|
||||||
"""
|
|
||||||
class Doc(Document):
|
|
||||||
foo = ListField(StringField())
|
|
||||||
|
|
||||||
Doc.drop_collection()
|
|
||||||
doc = Doc(foo=['a', 'b', 'c'])
|
|
||||||
doc.save()
|
|
||||||
|
|
||||||
# Update
|
|
||||||
doc = Doc.objects.first()
|
|
||||||
doc.update(pull__foo__in=['a', 'c'])
|
|
||||||
|
|
||||||
doc = Doc.objects.first()
|
|
||||||
self.assertEqual(doc.foo, ['b'])
|
|
||||||
|
|
||||||
def test_embedded_update_db_field(self):
|
def test_embedded_update_db_field(self):
|
||||||
"""Test update on `EmbeddedDocumentField` fields when db_field
|
"""Test update on `EmbeddedDocumentField` fields when db_field
|
||||||
is other than default.
|
is other than default.
|
||||||
@@ -1422,60 +1375,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
self.assertEqual(person.age, 21)
|
self.assertEqual(person.age, 21)
|
||||||
self.assertEqual(person.active, False)
|
self.assertEqual(person.active, False)
|
||||||
|
|
||||||
def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop(self):
|
|
||||||
# Refers to Issue #1685
|
|
||||||
class EmbeddedChildModel(EmbeddedDocument):
|
|
||||||
id = DictField(primary_key=True)
|
|
||||||
|
|
||||||
class ParentModel(Document):
|
|
||||||
child = EmbeddedDocumentField(
|
|
||||||
EmbeddedChildModel)
|
|
||||||
|
|
||||||
emb = EmbeddedChildModel(id={'1': [1]})
|
|
||||||
ParentModel(children=emb)._get_changed_fields()
|
|
||||||
|
|
||||||
def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop(self):
|
|
||||||
class User(Document):
|
|
||||||
id = IntField(primary_key=True)
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
class Message(Document):
|
|
||||||
id = IntField(primary_key=True)
|
|
||||||
author = ReferenceField(User)
|
|
||||||
|
|
||||||
Message.drop_collection()
|
|
||||||
|
|
||||||
# All objects share the same id, but each in a different collection
|
|
||||||
user = User(id=1, name='user-name').save()
|
|
||||||
message = Message(id=1, author=user).save()
|
|
||||||
|
|
||||||
message.author.name = 'tutu'
|
|
||||||
self.assertEqual(message._get_changed_fields(), [])
|
|
||||||
self.assertEqual(user._get_changed_fields(), ['name'])
|
|
||||||
|
|
||||||
def test__get_changed_fields_same_ids_embedded(self):
|
|
||||||
# Refers to Issue #1768
|
|
||||||
class User(EmbeddedDocument):
|
|
||||||
id = IntField()
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
class Message(Document):
|
|
||||||
id = IntField(primary_key=True)
|
|
||||||
author = EmbeddedDocumentField(User)
|
|
||||||
|
|
||||||
Message.drop_collection()
|
|
||||||
|
|
||||||
# All objects share the same id, but each in a different collection
|
|
||||||
user = User(id=1, name='user-name')#.save()
|
|
||||||
message = Message(id=1, author=user).save()
|
|
||||||
|
|
||||||
message.author.name = 'tutu'
|
|
||||||
self.assertEqual(message._get_changed_fields(), ['author.name'])
|
|
||||||
message.save()
|
|
||||||
|
|
||||||
message_fetched = Message.objects.with_id(message.id)
|
|
||||||
self.assertEqual(message_fetched.author.name, 'tutu')
|
|
||||||
|
|
||||||
def test_query_count_when_saving(self):
|
def test_query_count_when_saving(self):
|
||||||
"""Ensure references don't cause extra fetches when saving"""
|
"""Ensure references don't cause extra fetches when saving"""
|
||||||
class Organization(Document):
|
class Organization(Document):
|
||||||
@@ -1509,9 +1408,9 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
user = User.objects.first()
|
user = User.objects.first()
|
||||||
# Even if stored as ObjectId's internally mongoengine uses DBRefs
|
# Even if stored as ObjectId's internally mongoengine uses DBRefs
|
||||||
# As ObjectId's aren't automatically derefenced
|
# As ObjectId's aren't automatically derefenced
|
||||||
self.assertIsInstance(user._data['orgs'][0], DBRef)
|
self.assertTrue(isinstance(user._data['orgs'][0], DBRef))
|
||||||
self.assertIsInstance(user.orgs[0], Organization)
|
self.assertTrue(isinstance(user.orgs[0], Organization))
|
||||||
self.assertIsInstance(user._data['orgs'][0], Organization)
|
self.assertTrue(isinstance(user._data['orgs'][0], Organization))
|
||||||
|
|
||||||
# Changing a value
|
# Changing a value
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -1891,8 +1790,9 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
post_obj = BlogPost.objects.first()
|
post_obj = BlogPost.objects.first()
|
||||||
|
|
||||||
# Test laziness
|
# Test laziness
|
||||||
self.assertIsInstance(post_obj._data['author'], bson.DBRef)
|
self.assertTrue(isinstance(post_obj._data['author'],
|
||||||
self.assertIsInstance(post_obj.author, self.Person)
|
bson.DBRef))
|
||||||
|
self.assertTrue(isinstance(post_obj.author, self.Person))
|
||||||
self.assertEqual(post_obj.author.name, 'Test User')
|
self.assertEqual(post_obj.author.name, 'Test User')
|
||||||
|
|
||||||
# Ensure that the dereferenced object may be changed and saved
|
# Ensure that the dereferenced object may be changed and saved
|
||||||
@@ -1966,25 +1866,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
author.delete()
|
author.delete()
|
||||||
self.assertEqual(BlogPost.objects.count(), 0)
|
self.assertEqual(BlogPost.objects.count(), 0)
|
||||||
|
|
||||||
def test_reverse_delete_rule_pull(self):
|
|
||||||
"""Ensure that a referenced document is also deleted with
|
|
||||||
pull.
|
|
||||||
"""
|
|
||||||
class Record(Document):
|
|
||||||
name = StringField()
|
|
||||||
children = ListField(ReferenceField('self', reverse_delete_rule=PULL))
|
|
||||||
|
|
||||||
Record.drop_collection()
|
|
||||||
|
|
||||||
parent_record = Record(name='parent').save()
|
|
||||||
child_record = Record(name='child').save()
|
|
||||||
parent_record.children.append(child_record)
|
|
||||||
parent_record.save()
|
|
||||||
|
|
||||||
child_record.delete()
|
|
||||||
self.assertEqual(Record.objects(name='parent').get().children, [])
|
|
||||||
|
|
||||||
|
|
||||||
def test_reverse_delete_rule_with_custom_id_field(self):
|
def test_reverse_delete_rule_with_custom_id_field(self):
|
||||||
"""Ensure that a referenced document with custom primary key
|
"""Ensure that a referenced document with custom primary key
|
||||||
is also deleted upon deletion.
|
is also deleted upon deletion.
|
||||||
@@ -2298,12 +2179,12 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
# Make sure docs are properly identified in a list (__eq__ is used
|
# Make sure docs are properly identified in a list (__eq__ is used
|
||||||
# for the comparison).
|
# for the comparison).
|
||||||
all_user_list = list(User.objects.all())
|
all_user_list = list(User.objects.all())
|
||||||
self.assertIn(u1, all_user_list)
|
self.assertTrue(u1 in all_user_list)
|
||||||
self.assertIn(u2, all_user_list)
|
self.assertTrue(u2 in all_user_list)
|
||||||
self.assertIn(u3, all_user_list)
|
self.assertTrue(u3 in all_user_list)
|
||||||
self.assertNotIn(u4, all_user_list) # New object
|
self.assertTrue(u4 not in all_user_list) # New object
|
||||||
self.assertNotIn(b1, all_user_list) # Other object
|
self.assertTrue(b1 not in all_user_list) # Other object
|
||||||
self.assertNotIn(b2, all_user_list) # Other object
|
self.assertTrue(b2 not in all_user_list) # Other object
|
||||||
|
|
||||||
# Make sure docs can be used as keys in a dict (__hash__ is used
|
# Make sure docs can be used as keys in a dict (__hash__ is used
|
||||||
# for hashing the docs).
|
# for hashing the docs).
|
||||||
@@ -2321,10 +2202,10 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
# Make sure docs are properly identified in a set (__hash__ is used
|
# Make sure docs are properly identified in a set (__hash__ is used
|
||||||
# for hashing the docs).
|
# for hashing the docs).
|
||||||
all_user_set = set(User.objects.all())
|
all_user_set = set(User.objects.all())
|
||||||
self.assertIn(u1, all_user_set)
|
self.assertTrue(u1 in all_user_set)
|
||||||
self.assertNotIn(u4, all_user_set)
|
self.assertTrue(u4 not in all_user_set)
|
||||||
self.assertNotIn(b1, all_user_list)
|
self.assertTrue(b1 not in all_user_list)
|
||||||
self.assertNotIn(b2, all_user_list)
|
self.assertTrue(b2 not in all_user_list)
|
||||||
|
|
||||||
# Make sure duplicate docs aren't accepted in the set
|
# Make sure duplicate docs aren't accepted in the set
|
||||||
self.assertEqual(len(all_user_set), 3)
|
self.assertEqual(len(all_user_set), 3)
|
||||||
@@ -3025,7 +2906,7 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
Person(name="Harry Potter").save()
|
Person(name="Harry Potter").save()
|
||||||
|
|
||||||
person = Person.objects.first()
|
person = Person.objects.first()
|
||||||
self.assertIn('id', person._data.keys())
|
self.assertTrue('id' in person._data.keys())
|
||||||
self.assertEqual(person._data.get('id'), person.id)
|
self.assertEqual(person._data.get('id'), person.id)
|
||||||
|
|
||||||
def test_complex_nesting_document_and_embedded_document(self):
|
def test_complex_nesting_document_and_embedded_document(self):
|
||||||
@@ -3117,36 +2998,36 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
dbref2 = f._data['test2']
|
dbref2 = f._data['test2']
|
||||||
obj2 = f.test2
|
obj2 = f.test2
|
||||||
self.assertIsInstance(dbref2, DBRef)
|
self.assertTrue(isinstance(dbref2, DBRef))
|
||||||
self.assertIsInstance(obj2, Test2)
|
self.assertTrue(isinstance(obj2, Test2))
|
||||||
self.assertEqual(obj2.id, dbref2.id)
|
self.assertTrue(obj2.id == dbref2.id)
|
||||||
self.assertEqual(obj2, dbref2)
|
self.assertTrue(obj2 == dbref2)
|
||||||
self.assertEqual(dbref2, obj2)
|
self.assertTrue(dbref2 == obj2)
|
||||||
|
|
||||||
dbref3 = f._data['test3']
|
dbref3 = f._data['test3']
|
||||||
obj3 = f.test3
|
obj3 = f.test3
|
||||||
self.assertIsInstance(dbref3, DBRef)
|
self.assertTrue(isinstance(dbref3, DBRef))
|
||||||
self.assertIsInstance(obj3, Test3)
|
self.assertTrue(isinstance(obj3, Test3))
|
||||||
self.assertEqual(obj3.id, dbref3.id)
|
self.assertTrue(obj3.id == dbref3.id)
|
||||||
self.assertEqual(obj3, dbref3)
|
self.assertTrue(obj3 == dbref3)
|
||||||
self.assertEqual(dbref3, obj3)
|
self.assertTrue(dbref3 == obj3)
|
||||||
|
|
||||||
self.assertEqual(obj2.id, obj3.id)
|
self.assertTrue(obj2.id == obj3.id)
|
||||||
self.assertEqual(dbref2.id, dbref3.id)
|
self.assertTrue(dbref2.id == dbref3.id)
|
||||||
self.assertNotEqual(dbref2, dbref3)
|
self.assertFalse(dbref2 == dbref3)
|
||||||
self.assertNotEqual(dbref3, dbref2)
|
self.assertFalse(dbref3 == dbref2)
|
||||||
self.assertNotEqual(dbref2, dbref3)
|
self.assertTrue(dbref2 != dbref3)
|
||||||
self.assertNotEqual(dbref3, dbref2)
|
self.assertTrue(dbref3 != dbref2)
|
||||||
|
|
||||||
self.assertNotEqual(obj2, dbref3)
|
self.assertFalse(obj2 == dbref3)
|
||||||
self.assertNotEqual(dbref3, obj2)
|
self.assertFalse(dbref3 == obj2)
|
||||||
self.assertNotEqual(obj2, dbref3)
|
self.assertTrue(obj2 != dbref3)
|
||||||
self.assertNotEqual(dbref3, obj2)
|
self.assertTrue(dbref3 != obj2)
|
||||||
|
|
||||||
self.assertNotEqual(obj3, dbref2)
|
self.assertFalse(obj3 == dbref2)
|
||||||
self.assertNotEqual(dbref2, obj3)
|
self.assertFalse(dbref2 == obj3)
|
||||||
self.assertNotEqual(obj3, dbref2)
|
self.assertTrue(obj3 != dbref2)
|
||||||
self.assertNotEqual(dbref2, obj3)
|
self.assertTrue(dbref2 != obj3)
|
||||||
|
|
||||||
def test_default_values(self):
|
def test_default_values(self):
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
@@ -3195,64 +3076,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
self.assertEquals(p.id, None)
|
self.assertEquals(p.id, None)
|
||||||
p.id = "12345" # in case it is not working: "OperationError: Shard Keys are immutable..." will be raised here
|
p.id = "12345" # in case it is not working: "OperationError: Shard Keys are immutable..." will be raised here
|
||||||
|
|
||||||
def test_from_son_created_False_without_id(self):
|
|
||||||
class MyPerson(Document):
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
MyPerson.objects.delete()
|
|
||||||
|
|
||||||
p = MyPerson.from_json('{"name": "a_fancy_name"}', created=False)
|
|
||||||
self.assertFalse(p._created)
|
|
||||||
self.assertIsNone(p.id)
|
|
||||||
p.save()
|
|
||||||
self.assertIsNotNone(p.id)
|
|
||||||
saved_p = MyPerson.objects.get(id=p.id)
|
|
||||||
self.assertEqual(saved_p.name, 'a_fancy_name')
|
|
||||||
|
|
||||||
def test_from_son_created_False_with_id(self):
|
|
||||||
# 1854
|
|
||||||
class MyPerson(Document):
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
MyPerson.objects.delete()
|
|
||||||
|
|
||||||
p = MyPerson.from_json('{"_id": "5b85a8b04ec5dc2da388296e", "name": "a_fancy_name"}', created=False)
|
|
||||||
self.assertFalse(p._created)
|
|
||||||
self.assertEqual(p._changed_fields, [])
|
|
||||||
self.assertEqual(p.name, 'a_fancy_name')
|
|
||||||
self.assertEqual(p.id, ObjectId('5b85a8b04ec5dc2da388296e'))
|
|
||||||
p.save()
|
|
||||||
|
|
||||||
with self.assertRaises(DoesNotExist):
|
|
||||||
# Since created=False and we gave an id in the json and _changed_fields is empty
|
|
||||||
# mongoengine assumes that the document exits with that structure already
|
|
||||||
# and calling .save() didn't save anything
|
|
||||||
MyPerson.objects.get(id=p.id)
|
|
||||||
|
|
||||||
self.assertFalse(p._created)
|
|
||||||
p.name = 'a new fancy name'
|
|
||||||
self.assertEqual(p._changed_fields, ['name'])
|
|
||||||
p.save()
|
|
||||||
saved_p = MyPerson.objects.get(id=p.id)
|
|
||||||
self.assertEqual(saved_p.name, p.name)
|
|
||||||
|
|
||||||
def test_from_son_created_True_with_an_id(self):
|
|
||||||
class MyPerson(Document):
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
MyPerson.objects.delete()
|
|
||||||
|
|
||||||
p = MyPerson.from_json('{"_id": "5b85a8b04ec5dc2da388296e", "name": "a_fancy_name"}', created=True)
|
|
||||||
self.assertTrue(p._created)
|
|
||||||
self.assertEqual(p._changed_fields, [])
|
|
||||||
self.assertEqual(p.name, 'a_fancy_name')
|
|
||||||
self.assertEqual(p.id, ObjectId('5b85a8b04ec5dc2da388296e'))
|
|
||||||
p.save()
|
|
||||||
|
|
||||||
saved_p = MyPerson.objects.get(id=p.id)
|
|
||||||
self.assertEqual(saved_p, p)
|
|
||||||
self.assertEqual(p.name, 'a_fancy_name')
|
|
||||||
|
|
||||||
def test_null_field(self):
|
def test_null_field(self):
|
||||||
# 734
|
# 734
|
||||||
class User(Document):
|
class User(Document):
|
||||||
@@ -3326,50 +3149,6 @@ class InstanceTest(MongoDBTestCase):
|
|||||||
|
|
||||||
person.update(set__height=2.0)
|
person.update(set__height=2.0)
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
|
||||||
def test_push_with_position(self):
|
|
||||||
"""Ensure that push with position works properly for an instance."""
|
|
||||||
class BlogPost(Document):
|
|
||||||
slug = StringField()
|
|
||||||
tags = ListField(StringField())
|
|
||||||
|
|
||||||
blog = BlogPost()
|
|
||||||
blog.slug = "ABC"
|
|
||||||
blog.tags = ["python"]
|
|
||||||
blog.save()
|
|
||||||
|
|
||||||
blog.update(push__tags__0=["mongodb", "code"])
|
|
||||||
blog.reload()
|
|
||||||
self.assertEqual(blog.tags, ['mongodb', 'code', 'python'])
|
|
||||||
|
|
||||||
def test_push_nested_list(self):
|
|
||||||
"""Ensure that push update works in nested list"""
|
|
||||||
class BlogPost(Document):
|
|
||||||
slug = StringField()
|
|
||||||
tags = ListField()
|
|
||||||
|
|
||||||
blog = BlogPost(slug="test").save()
|
|
||||||
blog.update(push__tags=["value1", 123])
|
|
||||||
blog.reload()
|
|
||||||
self.assertEqual(blog.tags, [["value1", 123]])
|
|
||||||
|
|
||||||
def test_accessing_objects_with_indexes_error(self):
|
|
||||||
insert_result = self.db.company.insert_many([{'name': 'Foo'},
|
|
||||||
{'name': 'Foo'}]) # Force 2 doc with same name
|
|
||||||
REF_OID = insert_result.inserted_ids[0]
|
|
||||||
self.db.user.insert_one({'company': REF_OID}) # Force 2 doc with same name
|
|
||||||
|
|
||||||
class Company(Document):
|
|
||||||
name = StringField(unique=True)
|
|
||||||
|
|
||||||
class User(Document):
|
|
||||||
company = ReferenceField(Company)
|
|
||||||
|
|
||||||
|
|
||||||
# Ensure index creation exception aren't swallowed (#1688)
|
|
||||||
with self.assertRaises(DuplicateKeyError):
|
|
||||||
User.objects().select_related()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -20,16 +20,16 @@ class ValidatorErrorTest(unittest.TestCase):
|
|||||||
|
|
||||||
# 1st level error schema
|
# 1st level error schema
|
||||||
error.errors = {'1st': ValidationError('bad 1st'), }
|
error.errors = {'1st': ValidationError('bad 1st'), }
|
||||||
self.assertIn('1st', error.to_dict())
|
self.assertTrue('1st' in error.to_dict())
|
||||||
self.assertEqual(error.to_dict()['1st'], 'bad 1st')
|
self.assertEqual(error.to_dict()['1st'], 'bad 1st')
|
||||||
|
|
||||||
# 2nd level error schema
|
# 2nd level error schema
|
||||||
error.errors = {'1st': ValidationError('bad 1st', errors={
|
error.errors = {'1st': ValidationError('bad 1st', errors={
|
||||||
'2nd': ValidationError('bad 2nd'),
|
'2nd': ValidationError('bad 2nd'),
|
||||||
})}
|
})}
|
||||||
self.assertIn('1st', error.to_dict())
|
self.assertTrue('1st' in error.to_dict())
|
||||||
self.assertIsInstance(error.to_dict()['1st'], dict)
|
self.assertTrue(isinstance(error.to_dict()['1st'], dict))
|
||||||
self.assertIn('2nd', error.to_dict()['1st'])
|
self.assertTrue('2nd' in error.to_dict()['1st'])
|
||||||
self.assertEqual(error.to_dict()['1st']['2nd'], 'bad 2nd')
|
self.assertEqual(error.to_dict()['1st']['2nd'], 'bad 2nd')
|
||||||
|
|
||||||
# moar levels
|
# moar levels
|
||||||
@@ -40,10 +40,10 @@ class ValidatorErrorTest(unittest.TestCase):
|
|||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})}
|
})}
|
||||||
self.assertIn('1st', error.to_dict())
|
self.assertTrue('1st' in error.to_dict())
|
||||||
self.assertIn('2nd', error.to_dict()['1st'])
|
self.assertTrue('2nd' in error.to_dict()['1st'])
|
||||||
self.assertIn('3rd', error.to_dict()['1st']['2nd'])
|
self.assertTrue('3rd' in error.to_dict()['1st']['2nd'])
|
||||||
self.assertIn('4th', error.to_dict()['1st']['2nd']['3rd'])
|
self.assertTrue('4th' in error.to_dict()['1st']['2nd']['3rd'])
|
||||||
self.assertEqual(error.to_dict()['1st']['2nd']['3rd']['4th'],
|
self.assertEqual(error.to_dict()['1st']['2nd']['3rd']['4th'],
|
||||||
'Inception')
|
'Inception')
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ class ValidatorErrorTest(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
User().validate()
|
User().validate()
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
self.assertIn("User:None", e.message)
|
self.assertTrue("User:None" in e.message)
|
||||||
self.assertEqual(e.to_dict(), {
|
self.assertEqual(e.to_dict(), {
|
||||||
'username': 'Field is required',
|
'username': 'Field is required',
|
||||||
'name': 'Field is required'})
|
'name': 'Field is required'})
|
||||||
@@ -68,7 +68,7 @@ class ValidatorErrorTest(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
user.save()
|
user.save()
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
self.assertIn("User:RossC0", e.message)
|
self.assertTrue("User:RossC0" in e.message)
|
||||||
self.assertEqual(e.to_dict(), {
|
self.assertEqual(e.to_dict(), {
|
||||||
'name': 'Field is required'})
|
'name': 'Field is required'})
|
||||||
|
|
||||||
@@ -116,7 +116,7 @@ class ValidatorErrorTest(unittest.TestCase):
|
|||||||
try:
|
try:
|
||||||
Doc(id="bad").validate()
|
Doc(id="bad").validate()
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
self.assertIn("SubDoc:None", e.message)
|
self.assertTrue("SubDoc:None" in e.message)
|
||||||
self.assertEqual(e.to_dict(), {
|
self.assertEqual(e.to_dict(), {
|
||||||
"e": {'val': 'OK could not be converted to int'}})
|
"e": {'val': 'OK could not be converted to int'}})
|
||||||
|
|
||||||
@@ -127,14 +127,14 @@ class ValidatorErrorTest(unittest.TestCase):
|
|||||||
doc = Doc.objects.first()
|
doc = Doc.objects.first()
|
||||||
keys = doc._data.keys()
|
keys = doc._data.keys()
|
||||||
self.assertEqual(2, len(keys))
|
self.assertEqual(2, len(keys))
|
||||||
self.assertIn('e', keys)
|
self.assertTrue('e' in keys)
|
||||||
self.assertIn('id', keys)
|
self.assertTrue('id' in keys)
|
||||||
|
|
||||||
doc.e.val = "OK"
|
doc.e.val = "OK"
|
||||||
try:
|
try:
|
||||||
doc.save()
|
doc.save()
|
||||||
except ValidationError as e:
|
except ValidationError as e:
|
||||||
self.assertIn("Doc:test", e.message)
|
self.assertTrue("Doc:test" in e.message)
|
||||||
self.assertEqual(e.to_dict(), {
|
self.assertEqual(e.to_dict(), {
|
||||||
"e": {'val': 'OK could not be converted to int'}})
|
"e": {'val': 'OK could not be converted to int'}})
|
||||||
|
|
||||||
|
@@ -1,3 +1,3 @@
|
|||||||
from .fields import *
|
from fields import *
|
||||||
from .file_tests import *
|
from file_tests import *
|
||||||
from .geo import *
|
from geo import *
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -53,8 +53,8 @@ class FileTest(MongoDBTestCase):
|
|||||||
putfile.save()
|
putfile.save()
|
||||||
|
|
||||||
result = PutFile.objects.first()
|
result = PutFile.objects.first()
|
||||||
self.assertEqual(putfile, result)
|
self.assertTrue(putfile == result)
|
||||||
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello (%s)>" % result.the_file.grid_id)
|
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello>")
|
||||||
self.assertEqual(result.the_file.read(), text)
|
self.assertEqual(result.the_file.read(), text)
|
||||||
self.assertEqual(result.the_file.content_type, content_type)
|
self.assertEqual(result.the_file.content_type, content_type)
|
||||||
result.the_file.delete() # Remove file from GridFS
|
result.the_file.delete() # Remove file from GridFS
|
||||||
@@ -71,7 +71,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
putfile.save()
|
putfile.save()
|
||||||
|
|
||||||
result = PutFile.objects.first()
|
result = PutFile.objects.first()
|
||||||
self.assertEqual(putfile, result)
|
self.assertTrue(putfile == result)
|
||||||
self.assertEqual(result.the_file.read(), text)
|
self.assertEqual(result.the_file.read(), text)
|
||||||
self.assertEqual(result.the_file.content_type, content_type)
|
self.assertEqual(result.the_file.content_type, content_type)
|
||||||
result.the_file.delete()
|
result.the_file.delete()
|
||||||
@@ -96,7 +96,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
streamfile.save()
|
streamfile.save()
|
||||||
|
|
||||||
result = StreamFile.objects.first()
|
result = StreamFile.objects.first()
|
||||||
self.assertEqual(streamfile, result)
|
self.assertTrue(streamfile == result)
|
||||||
self.assertEqual(result.the_file.read(), text + more_text)
|
self.assertEqual(result.the_file.read(), text + more_text)
|
||||||
self.assertEqual(result.the_file.content_type, content_type)
|
self.assertEqual(result.the_file.content_type, content_type)
|
||||||
result.the_file.seek(0)
|
result.the_file.seek(0)
|
||||||
@@ -132,7 +132,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
streamfile.save()
|
streamfile.save()
|
||||||
|
|
||||||
result = StreamFile.objects.first()
|
result = StreamFile.objects.first()
|
||||||
self.assertEqual(streamfile, result)
|
self.assertTrue(streamfile == result)
|
||||||
self.assertEqual(result.the_file.read(), text + more_text)
|
self.assertEqual(result.the_file.read(), text + more_text)
|
||||||
# self.assertEqual(result.the_file.content_type, content_type)
|
# self.assertEqual(result.the_file.content_type, content_type)
|
||||||
result.the_file.seek(0)
|
result.the_file.seek(0)
|
||||||
@@ -161,7 +161,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
setfile.save()
|
setfile.save()
|
||||||
|
|
||||||
result = SetFile.objects.first()
|
result = SetFile.objects.first()
|
||||||
self.assertEqual(setfile, result)
|
self.assertTrue(setfile == result)
|
||||||
self.assertEqual(result.the_file.read(), text)
|
self.assertEqual(result.the_file.read(), text)
|
||||||
|
|
||||||
# Try replacing file with new one
|
# Try replacing file with new one
|
||||||
@@ -169,7 +169,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
result.save()
|
result.save()
|
||||||
|
|
||||||
result = SetFile.objects.first()
|
result = SetFile.objects.first()
|
||||||
self.assertEqual(setfile, result)
|
self.assertTrue(setfile == result)
|
||||||
self.assertEqual(result.the_file.read(), more_text)
|
self.assertEqual(result.the_file.read(), more_text)
|
||||||
result.the_file.delete()
|
result.the_file.delete()
|
||||||
|
|
||||||
@@ -231,8 +231,8 @@ class FileTest(MongoDBTestCase):
|
|||||||
test_file_dupe = TestFile()
|
test_file_dupe = TestFile()
|
||||||
data = test_file_dupe.the_file.read() # Should be None
|
data = test_file_dupe.the_file.read() # Should be None
|
||||||
|
|
||||||
self.assertNotEqual(test_file.name, test_file_dupe.name)
|
self.assertTrue(test_file.name != test_file_dupe.name)
|
||||||
self.assertNotEqual(test_file.the_file.read(), data)
|
self.assertTrue(test_file.the_file.read() != data)
|
||||||
|
|
||||||
TestFile.drop_collection()
|
TestFile.drop_collection()
|
||||||
|
|
||||||
@@ -291,7 +291,7 @@ class FileTest(MongoDBTestCase):
|
|||||||
the_file = FileField()
|
the_file = FileField()
|
||||||
|
|
||||||
test_file = TestFile()
|
test_file = TestFile()
|
||||||
self.assertNotIn(test_file.the_file, [{"test": 1}])
|
self.assertFalse(test_file.the_file in [{"test": 1}])
|
||||||
|
|
||||||
def test_file_disk_space(self):
|
def test_file_disk_space(self):
|
||||||
""" Test disk space usage when we delete/replace a file """
|
""" Test disk space usage when we delete/replace a file """
|
||||||
|
@@ -298,9 +298,9 @@ class GeoFieldTest(unittest.TestCase):
|
|||||||
polygon = PolygonField()
|
polygon = PolygonField()
|
||||||
|
|
||||||
geo_indicies = Event._geo_indices()
|
geo_indicies = Event._geo_indices()
|
||||||
self.assertIn({'fields': [('line', '2dsphere')]}, geo_indicies)
|
self.assertTrue({'fields': [('line', '2dsphere')]} in geo_indicies)
|
||||||
self.assertIn({'fields': [('polygon', '2dsphere')]}, geo_indicies)
|
self.assertTrue({'fields': [('polygon', '2dsphere')]} in geo_indicies)
|
||||||
self.assertIn({'fields': [('point', '2dsphere')]}, geo_indicies)
|
self.assertTrue({'fields': [('point', '2dsphere')]} in geo_indicies)
|
||||||
|
|
||||||
def test_indexes_2dsphere_embedded(self):
|
def test_indexes_2dsphere_embedded(self):
|
||||||
"""Ensure that indexes are created automatically for GeoPointFields.
|
"""Ensure that indexes are created automatically for GeoPointFields.
|
||||||
@@ -316,9 +316,9 @@ class GeoFieldTest(unittest.TestCase):
|
|||||||
venue = EmbeddedDocumentField(Venue)
|
venue = EmbeddedDocumentField(Venue)
|
||||||
|
|
||||||
geo_indicies = Event._geo_indices()
|
geo_indicies = Event._geo_indices()
|
||||||
self.assertIn({'fields': [('venue.line', '2dsphere')]}, geo_indicies)
|
self.assertTrue({'fields': [('venue.line', '2dsphere')]} in geo_indicies)
|
||||||
self.assertIn({'fields': [('venue.polygon', '2dsphere')]}, geo_indicies)
|
self.assertTrue({'fields': [('venue.polygon', '2dsphere')]} in geo_indicies)
|
||||||
self.assertIn({'fields': [('venue.point', '2dsphere')]}, geo_indicies)
|
self.assertTrue({'fields': [('venue.point', '2dsphere')]} in geo_indicies)
|
||||||
|
|
||||||
def test_geo_indexes_recursion(self):
|
def test_geo_indexes_recursion(self):
|
||||||
|
|
||||||
@@ -335,9 +335,9 @@ class GeoFieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
Parent(name='Berlin').save()
|
Parent(name='Berlin').save()
|
||||||
info = Parent._get_collection().index_information()
|
info = Parent._get_collection().index_information()
|
||||||
self.assertNotIn('location_2d', info)
|
self.assertFalse('location_2d' in info)
|
||||||
info = Location._get_collection().index_information()
|
info = Location._get_collection().index_information()
|
||||||
self.assertIn('location_2d', info)
|
self.assertTrue('location_2d' in info)
|
||||||
|
|
||||||
self.assertEqual(len(Parent._geo_indices()), 0)
|
self.assertEqual(len(Parent._geo_indices()), 0)
|
||||||
self.assertEqual(len(Location._geo_indices()), 1)
|
self.assertEqual(len(Location._geo_indices()), 1)
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
from .transform import *
|
from transform import *
|
||||||
from .field_list import *
|
from field_list import *
|
||||||
from .queryset import *
|
from queryset import *
|
||||||
from .visitor import *
|
from visitor import *
|
||||||
from .geo import *
|
from geo import *
|
||||||
from .modify import *
|
from modify import *
|
@@ -181,7 +181,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
employee.save()
|
employee.save()
|
||||||
|
|
||||||
obj = self.Person.objects(id=employee.id).only('age').get()
|
obj = self.Person.objects(id=employee.id).only('age').get()
|
||||||
self.assertIsInstance(obj, Employee)
|
self.assertTrue(isinstance(obj, Employee))
|
||||||
|
|
||||||
# Check field names are looked up properly
|
# Check field names are looked up properly
|
||||||
obj = Employee.objects(id=employee.id).only('salary').get()
|
obj = Employee.objects(id=employee.id).only('salary').get()
|
||||||
@@ -197,18 +197,14 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
title = StringField()
|
title = StringField()
|
||||||
text = StringField()
|
text = StringField()
|
||||||
|
|
||||||
class VariousData(EmbeddedDocument):
|
|
||||||
some = BooleanField()
|
|
||||||
|
|
||||||
class BlogPost(Document):
|
class BlogPost(Document):
|
||||||
content = StringField()
|
content = StringField()
|
||||||
author = EmbeddedDocumentField(User)
|
author = EmbeddedDocumentField(User)
|
||||||
comments = ListField(EmbeddedDocumentField(Comment))
|
comments = ListField(EmbeddedDocumentField(Comment))
|
||||||
various = MapField(field=EmbeddedDocumentField(VariousData))
|
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
|
|
||||||
post = BlogPost(content='Had a good coffee today...', various={'test_dynamic':{'some': True}})
|
post = BlogPost(content='Had a good coffee today...')
|
||||||
post.author = User(name='Test User')
|
post.author = User(name='Test User')
|
||||||
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
|
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
|
||||||
post.save()
|
post.save()
|
||||||
@@ -219,9 +215,6 @@ class OnlyExcludeAllTest(unittest.TestCase):
|
|||||||
self.assertEqual(obj.author.name, 'Test User')
|
self.assertEqual(obj.author.name, 'Test User')
|
||||||
self.assertEqual(obj.comments, [])
|
self.assertEqual(obj.comments, [])
|
||||||
|
|
||||||
obj = BlogPost.objects.only('various.test_dynamic.some').get()
|
|
||||||
self.assertEqual(obj.various["test_dynamic"].some, True)
|
|
||||||
|
|
||||||
obj = BlogPost.objects.only('content', 'comments.title',).get()
|
obj = BlogPost.objects.only('content', 'comments.title',).get()
|
||||||
self.assertEqual(obj.content, 'Had a good coffee today...')
|
self.assertEqual(obj.content, 'Had a good coffee today...')
|
||||||
self.assertEqual(obj.author, None)
|
self.assertEqual(obj.author, None)
|
||||||
|
@@ -3,7 +3,7 @@ import unittest
|
|||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
|
|
||||||
from tests.utils import MongoDBTestCase, requires_mongodb_gte_3
|
from tests.utils import MongoDBTestCase, needs_mongodb_v3
|
||||||
|
|
||||||
|
|
||||||
__all__ = ("GeoQueriesTest",)
|
__all__ = ("GeoQueriesTest",)
|
||||||
@@ -72,7 +72,7 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
|
|
||||||
# $minDistance was added in MongoDB v2.6, but continued being buggy
|
# $minDistance was added in MongoDB v2.6, but continued being buggy
|
||||||
# until v3.0; skip for older versions
|
# until v3.0; skip for older versions
|
||||||
@requires_mongodb_gte_3
|
@needs_mongodb_v3
|
||||||
def test_near_and_min_distance(self):
|
def test_near_and_min_distance(self):
|
||||||
"""Ensure the "min_distance" operator works alongside the "near"
|
"""Ensure the "min_distance" operator works alongside the "near"
|
||||||
operator.
|
operator.
|
||||||
@@ -95,9 +95,9 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
location__within_distance=point_and_distance)
|
location__within_distance=point_and_distance)
|
||||||
self.assertEqual(events.count(), 2)
|
self.assertEqual(events.count(), 2)
|
||||||
events = list(events)
|
events = list(events)
|
||||||
self.assertNotIn(event2, events)
|
self.assertTrue(event2 not in events)
|
||||||
self.assertIn(event1, events)
|
self.assertTrue(event1 in events)
|
||||||
self.assertIn(event3, events)
|
self.assertTrue(event3 in events)
|
||||||
|
|
||||||
# find events within 10 degrees of san francisco
|
# find events within 10 degrees of san francisco
|
||||||
point_and_distance = [[-122.415579, 37.7566023], 10]
|
point_and_distance = [[-122.415579, 37.7566023], 10]
|
||||||
@@ -245,7 +245,7 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
|
|
||||||
# $minDistance was added in MongoDB v2.6, but continued being buggy
|
# $minDistance was added in MongoDB v2.6, but continued being buggy
|
||||||
# until v3.0; skip for older versions
|
# until v3.0; skip for older versions
|
||||||
@requires_mongodb_gte_3
|
@needs_mongodb_v3
|
||||||
def test_2dsphere_near_and_min_max_distance(self):
|
def test_2dsphere_near_and_min_max_distance(self):
|
||||||
"""Ensure "min_distace" and "max_distance" operators work well
|
"""Ensure "min_distace" and "max_distance" operators work well
|
||||||
together with the "near" operator in a 2dsphere index.
|
together with the "near" operator in a 2dsphere index.
|
||||||
@@ -285,9 +285,9 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
location__geo_within_center=point_and_distance)
|
location__geo_within_center=point_and_distance)
|
||||||
self.assertEqual(events.count(), 2)
|
self.assertEqual(events.count(), 2)
|
||||||
events = list(events)
|
events = list(events)
|
||||||
self.assertNotIn(event2, events)
|
self.assertTrue(event2 not in events)
|
||||||
self.assertIn(event1, events)
|
self.assertTrue(event1 in events)
|
||||||
self.assertIn(event3, events)
|
self.assertTrue(event3 in events)
|
||||||
|
|
||||||
def _test_embedded(self, point_field_class):
|
def _test_embedded(self, point_field_class):
|
||||||
"""Helper test method ensuring given point field class works
|
"""Helper test method ensuring given point field class works
|
||||||
@@ -329,7 +329,7 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
self._test_embedded(point_field_class=PointField)
|
self._test_embedded(point_field_class=PointField)
|
||||||
|
|
||||||
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
|
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
|
||||||
@requires_mongodb_gte_3
|
@needs_mongodb_v3
|
||||||
def test_spherical_geospatial_operators(self):
|
def test_spherical_geospatial_operators(self):
|
||||||
"""Ensure that spherical geospatial queries are working."""
|
"""Ensure that spherical geospatial queries are working."""
|
||||||
class Point(Document):
|
class Point(Document):
|
||||||
@@ -510,24 +510,6 @@ class GeoQueriesTest(MongoDBTestCase):
|
|||||||
roads = Road.objects.filter(poly__geo_intersects={"$geometry": polygon}).count()
|
roads = Road.objects.filter(poly__geo_intersects={"$geometry": polygon}).count()
|
||||||
self.assertEqual(1, roads)
|
self.assertEqual(1, roads)
|
||||||
|
|
||||||
def test_aspymongo_with_only(self):
|
|
||||||
"""Ensure as_pymongo works with only"""
|
|
||||||
class Place(Document):
|
|
||||||
location = PointField()
|
|
||||||
|
|
||||||
Place.drop_collection()
|
|
||||||
p = Place(location=[24.946861267089844, 60.16311983618494])
|
|
||||||
p.save()
|
|
||||||
qs = Place.objects().only('location')
|
|
||||||
self.assertDictEqual(
|
|
||||||
qs.as_pymongo()[0]['location'],
|
|
||||||
{u'type': u'Point',
|
|
||||||
u'coordinates': [
|
|
||||||
24.946861267089844,
|
|
||||||
60.16311983618494]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_2dsphere_point_sets_correctly(self):
|
def test_2dsphere_point_sets_correctly(self):
|
||||||
class Location(Document):
|
class Location(Document):
|
||||||
loc = PointField()
|
loc = PointField()
|
||||||
|
@@ -1,8 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mongoengine import connect, Document, IntField, StringField, ListField
|
from mongoengine import connect, Document, IntField
|
||||||
|
|
||||||
from tests.utils import requires_mongodb_gte_26
|
|
||||||
|
|
||||||
__all__ = ("FindAndModifyTest",)
|
__all__ = ("FindAndModifyTest",)
|
||||||
|
|
||||||
@@ -96,37 +94,6 @@ class FindAndModifyTest(unittest.TestCase):
|
|||||||
self.assertEqual(old_doc.to_mongo(), {"_id": 1})
|
self.assertEqual(old_doc.to_mongo(), {"_id": 1})
|
||||||
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
|
||||||
|
|
||||||
@requires_mongodb_gte_26
|
|
||||||
def test_modify_with_push(self):
|
|
||||||
class BlogPost(Document):
|
|
||||||
tags = ListField(StringField())
|
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
|
||||||
|
|
||||||
blog = BlogPost.objects.create()
|
|
||||||
|
|
||||||
# Push a new tag via modify with new=False (default).
|
|
||||||
BlogPost(id=blog.id).modify(push__tags='code')
|
|
||||||
self.assertEqual(blog.tags, [])
|
|
||||||
blog.reload()
|
|
||||||
self.assertEqual(blog.tags, ['code'])
|
|
||||||
|
|
||||||
# Push a new tag via modify with new=True.
|
|
||||||
blog = BlogPost.objects(id=blog.id).modify(push__tags='java', new=True)
|
|
||||||
self.assertEqual(blog.tags, ['code', 'java'])
|
|
||||||
|
|
||||||
# Push a new tag with a positional argument.
|
|
||||||
blog = BlogPost.objects(id=blog.id).modify(
|
|
||||||
push__tags__0='python',
|
|
||||||
new=True)
|
|
||||||
self.assertEqual(blog.tags, ['python', 'code', 'java'])
|
|
||||||
|
|
||||||
# Push multiple new tags with a positional argument.
|
|
||||||
blog = BlogPost.objects(id=blog.id).modify(
|
|
||||||
push__tags__1=['go', 'rust'],
|
|
||||||
new=True)
|
|
||||||
self.assertEqual(blog.tags, ['python', 'go', 'rust', 'code', 'java'])
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,5 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bson.son import SON
|
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.queryset import Q, transform
|
from mongoengine.queryset import Q, transform
|
||||||
|
|
||||||
@@ -30,16 +28,12 @@ class TransformTest(unittest.TestCase):
|
|||||||
{'name': {'$exists': True}})
|
{'name': {'$exists': True}})
|
||||||
|
|
||||||
def test_transform_update(self):
|
def test_transform_update(self):
|
||||||
class LisDoc(Document):
|
|
||||||
foo = ListField(StringField())
|
|
||||||
|
|
||||||
class DicDoc(Document):
|
class DicDoc(Document):
|
||||||
dictField = DictField()
|
dictField = DictField()
|
||||||
|
|
||||||
class Doc(Document):
|
class Doc(Document):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
LisDoc.drop_collection()
|
|
||||||
DicDoc.drop_collection()
|
DicDoc.drop_collection()
|
||||||
Doc.drop_collection()
|
Doc.drop_collection()
|
||||||
|
|
||||||
@@ -48,28 +42,14 @@ class TransformTest(unittest.TestCase):
|
|||||||
|
|
||||||
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
|
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
|
||||||
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
|
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
|
||||||
self.assertIsInstance(update[v]["dictField.test"], dict)
|
self.assertTrue(isinstance(update[v]["dictField.test"], dict))
|
||||||
|
|
||||||
# Update special cases
|
# Update special cases
|
||||||
update = transform.update(DicDoc, unset__dictField__test=doc)
|
update = transform.update(DicDoc, unset__dictField__test=doc)
|
||||||
self.assertEqual(update["$unset"]["dictField.test"], 1)
|
self.assertEqual(update["$unset"]["dictField.test"], 1)
|
||||||
|
|
||||||
update = transform.update(DicDoc, pull__dictField__test=doc)
|
update = transform.update(DicDoc, pull__dictField__test=doc)
|
||||||
self.assertIsInstance(update["$pull"]["dictField"]["test"], dict)
|
self.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict))
|
||||||
|
|
||||||
update = transform.update(LisDoc, pull__foo__in=['a'])
|
|
||||||
self.assertEqual(update, {'$pull': {'foo': {'$in': ['a']}}})
|
|
||||||
|
|
||||||
def test_transform_update_push(self):
|
|
||||||
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
|
|
||||||
class BlogPost(Document):
|
|
||||||
tags = ListField(StringField())
|
|
||||||
|
|
||||||
update = transform.update(BlogPost, push__tags=['mongo', 'db'])
|
|
||||||
self.assertEqual(update, {'$push': {'tags': ['mongo', 'db']}})
|
|
||||||
|
|
||||||
update = transform.update(BlogPost, push_all__tags=['mongo', 'db'])
|
|
||||||
self.assertEqual(update, {'$push': {'tags': {'$each': ['mongo', 'db']}}})
|
|
||||||
|
|
||||||
def test_query_field_name(self):
|
def test_query_field_name(self):
|
||||||
"""Ensure that the correct field name is used when querying.
|
"""Ensure that the correct field name is used when querying.
|
||||||
@@ -88,15 +68,17 @@ class TransformTest(unittest.TestCase):
|
|||||||
post = BlogPost(**data)
|
post = BlogPost(**data)
|
||||||
post.save()
|
post.save()
|
||||||
|
|
||||||
self.assertIn('postTitle', BlogPost.objects(title=data['title'])._query)
|
self.assertTrue('postTitle' in
|
||||||
|
BlogPost.objects(title=data['title'])._query)
|
||||||
self.assertFalse('title' in
|
self.assertFalse('title' in
|
||||||
BlogPost.objects(title=data['title'])._query)
|
BlogPost.objects(title=data['title'])._query)
|
||||||
self.assertEqual(BlogPost.objects(title=data['title']).count(), 1)
|
self.assertEqual(BlogPost.objects(title=data['title']).count(), 1)
|
||||||
|
|
||||||
self.assertIn('_id', BlogPost.objects(pk=post.id)._query)
|
self.assertTrue('_id' in BlogPost.objects(pk=post.id)._query)
|
||||||
self.assertEqual(BlogPost.objects(pk=post.id).count(), 1)
|
self.assertEqual(BlogPost.objects(pk=post.id).count(), 1)
|
||||||
|
|
||||||
self.assertIn('postComments.commentContent', BlogPost.objects(comments__content='test')._query)
|
self.assertTrue('postComments.commentContent' in
|
||||||
|
BlogPost.objects(comments__content='test')._query)
|
||||||
self.assertEqual(BlogPost.objects(comments__content='test').count(), 1)
|
self.assertEqual(BlogPost.objects(comments__content='test').count(), 1)
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
@@ -114,8 +96,8 @@ class TransformTest(unittest.TestCase):
|
|||||||
post = BlogPost(**data)
|
post = BlogPost(**data)
|
||||||
post.save()
|
post.save()
|
||||||
|
|
||||||
self.assertIn('_id', BlogPost.objects(pk=data['title'])._query)
|
self.assertTrue('_id' in BlogPost.objects(pk=data['title'])._query)
|
||||||
self.assertIn('_id', BlogPost.objects(title=data['title'])._query)
|
self.assertTrue('_id' in BlogPost.objects(title=data['title'])._query)
|
||||||
self.assertEqual(BlogPost.objects(pk=data['title']).count(), 1)
|
self.assertEqual(BlogPost.objects(pk=data['title']).count(), 1)
|
||||||
|
|
||||||
BlogPost.drop_collection()
|
BlogPost.drop_collection()
|
||||||
@@ -259,30 +241,6 @@ class TransformTest(unittest.TestCase):
|
|||||||
with self.assertRaises(InvalidQueryError):
|
with self.assertRaises(InvalidQueryError):
|
||||||
events.count()
|
events.count()
|
||||||
|
|
||||||
def test_update_pull_for_list_fields(self):
|
|
||||||
"""
|
|
||||||
Test added to check pull operation in update for
|
|
||||||
EmbeddedDocumentListField which is inside a EmbeddedDocumentField
|
|
||||||
"""
|
|
||||||
class Word(EmbeddedDocument):
|
|
||||||
word = StringField()
|
|
||||||
index = IntField()
|
|
||||||
|
|
||||||
class SubDoc(EmbeddedDocument):
|
|
||||||
heading = ListField(StringField())
|
|
||||||
text = EmbeddedDocumentListField(Word)
|
|
||||||
|
|
||||||
class MainDoc(Document):
|
|
||||||
title = StringField()
|
|
||||||
content = EmbeddedDocumentField(SubDoc)
|
|
||||||
|
|
||||||
word = Word(word='abc', index=1)
|
|
||||||
update = transform.update(MainDoc, pull__content__text=word)
|
|
||||||
self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}})
|
|
||||||
|
|
||||||
update = transform.update(MainDoc, pull__content__heading='xyz')
|
|
||||||
self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}})
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -196,7 +196,7 @@ class QTest(unittest.TestCase):
|
|||||||
|
|
||||||
test2 = test.clone()
|
test2 = test.clone()
|
||||||
self.assertEqual(test2.count(), 3)
|
self.assertEqual(test2.count(), 3)
|
||||||
self.assertNotEqual(test2, test)
|
self.assertFalse(test2 == test)
|
||||||
|
|
||||||
test3 = test2.filter(x=6)
|
test3 = test2.filter(x=6)
|
||||||
self.assertEqual(test3.count(), 1)
|
self.assertEqual(test3.count(), 1)
|
||||||
@@ -296,18 +296,6 @@ class QTest(unittest.TestCase):
|
|||||||
obj = self.Person.objects(Q(name__not=re.compile('^Gui'))).first()
|
obj = self.Person.objects(Q(name__not=re.compile('^Gui'))).first()
|
||||||
self.assertEqual(obj, None)
|
self.assertEqual(obj, None)
|
||||||
|
|
||||||
def test_q_repr(self):
|
|
||||||
self.assertEqual(repr(Q()), 'Q(**{})')
|
|
||||||
self.assertEqual(repr(Q(name='test')), "Q(**{'name': 'test'})")
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
repr(Q(name='test') & Q(age__gte=18)),
|
|
||||||
"(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))")
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
repr(Q(name='test') | Q(age__gte=18)),
|
|
||||||
"(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))")
|
|
||||||
|
|
||||||
def test_q_lists(self):
|
def test_q_lists(self):
|
||||||
"""Ensure that Q objects query ListFields correctly.
|
"""Ensure that Q objects query ListFields correctly.
|
||||||
"""
|
"""
|
||||||
|
@@ -39,15 +39,15 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
connect('mongoenginetest')
|
connect('mongoenginetest')
|
||||||
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
connect('mongoenginetest2', alias='testdb')
|
connect('mongoenginetest2', alias='testdb')
|
||||||
conn = get_connection('testdb')
|
conn = get_connection('testdb')
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
def test_connect_in_mocking(self):
|
def test_connect_in_mocking(self):
|
||||||
"""Ensure that the connect() method works properly in mocking.
|
"""Ensure that the connect() method works properly in mocking.
|
||||||
@@ -59,31 +59,31 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
connect('mongoenginetest', host='mongomock://localhost')
|
connect('mongoenginetest', host='mongomock://localhost')
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2')
|
connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2')
|
||||||
conn = get_connection('testdb2')
|
conn = get_connection('testdb2')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3')
|
connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3')
|
||||||
conn = get_connection('testdb3')
|
conn = get_connection('testdb3')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect('mongoenginetest4', is_mock=True, alias='testdb4')
|
connect('mongoenginetest4', is_mock=True, alias='testdb4')
|
||||||
conn = get_connection('testdb4')
|
conn = get_connection('testdb4')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5')
|
connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5')
|
||||||
conn = get_connection('testdb5')
|
conn = get_connection('testdb5')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6')
|
connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6')
|
||||||
conn = get_connection('testdb6')
|
conn = get_connection('testdb6')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7')
|
connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7')
|
||||||
conn = get_connection('testdb7')
|
conn = get_connection('testdb7')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
def test_connect_with_host_list(self):
|
def test_connect_with_host_list(self):
|
||||||
"""Ensure that the connect() method works when host is a list
|
"""Ensure that the connect() method works when host is a list
|
||||||
@@ -97,27 +97,27 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
connect(host=['mongomock://localhost'])
|
connect(host=['mongomock://localhost'])
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
|
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
|
||||||
conn = get_connection('testdb2')
|
conn = get_connection('testdb2')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host=['localhost'], is_mock=True, alias='testdb3')
|
connect(host=['localhost'], is_mock=True, alias='testdb3')
|
||||||
conn = get_connection('testdb3')
|
conn = get_connection('testdb3')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host=['mongomock://localhost:27017', 'mongomock://localhost:27018'], alias='testdb4')
|
connect(host=['mongomock://localhost:27017', 'mongomock://localhost:27018'], alias='testdb4')
|
||||||
conn = get_connection('testdb4')
|
conn = get_connection('testdb4')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
|
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
|
||||||
conn = get_connection('testdb5')
|
conn = get_connection('testdb5')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
|
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
|
||||||
conn = get_connection('testdb6')
|
conn = get_connection('testdb6')
|
||||||
self.assertIsInstance(conn, mongomock.MongoClient)
|
self.assertTrue(isinstance(conn, mongomock.MongoClient))
|
||||||
|
|
||||||
def test_disconnect(self):
|
def test_disconnect(self):
|
||||||
"""Ensure that the disconnect() method works properly
|
"""Ensure that the disconnect() method works properly
|
||||||
@@ -163,10 +163,10 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
connect("testdb_uri", host='mongodb://username:password@localhost/mongoenginetest')
|
connect("testdb_uri", host='mongodb://username:password@localhost/mongoenginetest')
|
||||||
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
c.admin.system.users.remove({})
|
c.admin.system.users.remove({})
|
||||||
@@ -179,10 +179,10 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
connect("mongoenginetest", host='mongodb://localhost/')
|
connect("mongoenginetest", host='mongodb://localhost/')
|
||||||
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
def test_connect_uri_default_db(self):
|
def test_connect_uri_default_db(self):
|
||||||
@@ -192,10 +192,10 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
connect(host='mongodb://localhost/')
|
connect(host='mongodb://localhost/')
|
||||||
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'test')
|
self.assertEqual(db.name, 'test')
|
||||||
|
|
||||||
def test_uri_without_credentials_doesnt_override_conn_settings(self):
|
def test_uri_without_credentials_doesnt_override_conn_settings(self):
|
||||||
@@ -242,7 +242,7 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
'mongoenginetest?authSource=admin')
|
'mongoenginetest?authSource=admin')
|
||||||
)
|
)
|
||||||
db = get_db('test2')
|
db = get_db('test2')
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest')
|
self.assertEqual(db.name, 'mongoenginetest')
|
||||||
|
|
||||||
# Clear all users
|
# Clear all users
|
||||||
@@ -255,10 +255,10 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(MongoEngineConnectionError, get_connection)
|
self.assertRaises(MongoEngineConnectionError, get_connection)
|
||||||
conn = get_connection('testdb')
|
conn = get_connection('testdb')
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
db = get_db('testdb')
|
db = get_db('testdb')
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest2')
|
self.assertEqual(db.name, 'mongoenginetest2')
|
||||||
|
|
||||||
def test_register_connection_defaults(self):
|
def test_register_connection_defaults(self):
|
||||||
@@ -267,7 +267,7 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
register_connection('testdb', 'mongoenginetest', host=None, port=None)
|
register_connection('testdb', 'mongoenginetest', host=None, port=None)
|
||||||
|
|
||||||
conn = get_connection('testdb')
|
conn = get_connection('testdb')
|
||||||
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
|
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
|
||||||
|
|
||||||
def test_connection_kwargs(self):
|
def test_connection_kwargs(self):
|
||||||
"""Ensure that connection kwargs get passed to pymongo."""
|
"""Ensure that connection kwargs get passed to pymongo."""
|
||||||
@@ -326,7 +326,7 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
if IS_PYMONGO_3:
|
if IS_PYMONGO_3:
|
||||||
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
|
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'test')
|
self.assertEqual(db.name, 'test')
|
||||||
else:
|
else:
|
||||||
# PyMongo < v3.x raises an exception:
|
# PyMongo < v3.x raises an exception:
|
||||||
@@ -343,7 +343,7 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertEqual(c._MongoClient__options.replica_set_name,
|
self.assertEqual(c._MongoClient__options.replica_set_name,
|
||||||
'local-rs')
|
'local-rs')
|
||||||
db = get_db()
|
db = get_db()
|
||||||
self.assertIsInstance(db, pymongo.database.Database)
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'test')
|
self.assertEqual(db.name, 'test')
|
||||||
else:
|
else:
|
||||||
# PyMongo < v3.x raises an exception:
|
# PyMongo < v3.x raises an exception:
|
||||||
@@ -364,12 +364,6 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
date_doc = DateDoc.objects.first()
|
date_doc = DateDoc.objects.first()
|
||||||
self.assertEqual(d, date_doc.the_date)
|
self.assertEqual(d, date_doc.the_date)
|
||||||
|
|
||||||
def test_read_preference_from_parse(self):
|
|
||||||
if IS_PYMONGO_3:
|
|
||||||
from pymongo import ReadPreference
|
|
||||||
conn = connect(host="mongodb://a1.vpc,a2.vpc,a3.vpc/prod?readPreference=secondaryPreferred")
|
|
||||||
self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_PREFERRED)
|
|
||||||
|
|
||||||
def test_multiple_connection_settings(self):
|
def test_multiple_connection_settings(self):
|
||||||
connect('mongoenginetest', alias='t1', host="localhost")
|
connect('mongoenginetest', alias='t1', host="localhost")
|
||||||
|
|
||||||
@@ -377,8 +371,8 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
|
|
||||||
mongo_connections = mongoengine.connection._connections
|
mongo_connections = mongoengine.connection._connections
|
||||||
self.assertEqual(len(mongo_connections.items()), 2)
|
self.assertEqual(len(mongo_connections.items()), 2)
|
||||||
self.assertIn('t1', mongo_connections.keys())
|
self.assertTrue('t1' in mongo_connections.keys())
|
||||||
self.assertIn('t2', mongo_connections.keys())
|
self.assertTrue('t2' in mongo_connections.keys())
|
||||||
if not IS_PYMONGO_3:
|
if not IS_PYMONGO_3:
|
||||||
self.assertEqual(mongo_connections['t1'].host, 'localhost')
|
self.assertEqual(mongo_connections['t1'].host, 'localhost')
|
||||||
self.assertEqual(mongo_connections['t2'].host, '127.0.0.1')
|
self.assertEqual(mongo_connections['t2'].host, '127.0.0.1')
|
||||||
|
@@ -89,15 +89,15 @@ class ContextManagersTest(unittest.TestCase):
|
|||||||
|
|
||||||
with no_dereference(Group) as Group:
|
with no_dereference(Group) as Group:
|
||||||
group = Group.objects.first()
|
group = Group.objects.first()
|
||||||
for m in group.members:
|
self.assertTrue(all([not isinstance(m, User)
|
||||||
self.assertNotIsInstance(m, User)
|
for m in group.members]))
|
||||||
self.assertNotIsInstance(group.ref, User)
|
self.assertFalse(isinstance(group.ref, User))
|
||||||
self.assertNotIsInstance(group.generic, User)
|
self.assertFalse(isinstance(group.generic, User))
|
||||||
|
|
||||||
for m in group.members:
|
self.assertTrue(all([isinstance(m, User)
|
||||||
self.assertIsInstance(m, User)
|
for m in group.members]))
|
||||||
self.assertIsInstance(group.ref, User)
|
self.assertTrue(isinstance(group.ref, User))
|
||||||
self.assertIsInstance(group.generic, User)
|
self.assertTrue(isinstance(group.generic, User))
|
||||||
|
|
||||||
def test_no_dereference_context_manager_dbref(self):
|
def test_no_dereference_context_manager_dbref(self):
|
||||||
"""Ensure that DBRef items in ListFields aren't dereferenced.
|
"""Ensure that DBRef items in ListFields aren't dereferenced.
|
||||||
@@ -129,17 +129,19 @@ class ContextManagersTest(unittest.TestCase):
|
|||||||
group = Group.objects.first()
|
group = Group.objects.first()
|
||||||
self.assertTrue(all([not isinstance(m, User)
|
self.assertTrue(all([not isinstance(m, User)
|
||||||
for m in group.members]))
|
for m in group.members]))
|
||||||
self.assertNotIsInstance(group.ref, User)
|
self.assertFalse(isinstance(group.ref, User))
|
||||||
self.assertNotIsInstance(group.generic, User)
|
self.assertFalse(isinstance(group.generic, User))
|
||||||
|
|
||||||
self.assertTrue(all([isinstance(m, User)
|
self.assertTrue(all([isinstance(m, User)
|
||||||
for m in group.members]))
|
for m in group.members]))
|
||||||
self.assertIsInstance(group.ref, User)
|
self.assertTrue(isinstance(group.ref, User))
|
||||||
self.assertIsInstance(group.generic, User)
|
self.assertTrue(isinstance(group.generic, User))
|
||||||
|
|
||||||
def test_no_sub_classes(self):
|
def test_no_sub_classes(self):
|
||||||
class A(Document):
|
class A(Document):
|
||||||
x = IntField()
|
x = IntField()
|
||||||
|
y = IntField()
|
||||||
|
|
||||||
meta = {'allow_inheritance': True}
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
class B(A):
|
class B(A):
|
||||||
@@ -150,29 +152,29 @@ class ContextManagersTest(unittest.TestCase):
|
|||||||
|
|
||||||
A.drop_collection()
|
A.drop_collection()
|
||||||
|
|
||||||
A(x=10).save()
|
A(x=10, y=20).save()
|
||||||
A(x=15).save()
|
A(x=15, y=30).save()
|
||||||
B(x=20).save()
|
B(x=20, y=40).save()
|
||||||
B(x=30).save()
|
B(x=30, y=50).save()
|
||||||
C(x=40).save()
|
C(x=40, y=60).save()
|
||||||
|
|
||||||
self.assertEqual(A.objects.count(), 5)
|
self.assertEqual(A.objects.count(), 5)
|
||||||
self.assertEqual(B.objects.count(), 3)
|
self.assertEqual(B.objects.count(), 3)
|
||||||
self.assertEqual(C.objects.count(), 1)
|
self.assertEqual(C.objects.count(), 1)
|
||||||
|
|
||||||
with no_sub_classes(A):
|
with no_sub_classes(A) as A:
|
||||||
self.assertEqual(A.objects.count(), 2)
|
self.assertEqual(A.objects.count(), 2)
|
||||||
|
|
||||||
for obj in A.objects:
|
for obj in A.objects:
|
||||||
self.assertEqual(obj.__class__, A)
|
self.assertEqual(obj.__class__, A)
|
||||||
|
|
||||||
with no_sub_classes(B):
|
with no_sub_classes(B) as B:
|
||||||
self.assertEqual(B.objects.count(), 2)
|
self.assertEqual(B.objects.count(), 2)
|
||||||
|
|
||||||
for obj in B.objects:
|
for obj in B.objects:
|
||||||
self.assertEqual(obj.__class__, B)
|
self.assertEqual(obj.__class__, B)
|
||||||
|
|
||||||
with no_sub_classes(C):
|
with no_sub_classes(C) as C:
|
||||||
self.assertEqual(C.objects.count(), 1)
|
self.assertEqual(C.objects.count(), 1)
|
||||||
|
|
||||||
for obj in C.objects:
|
for obj in C.objects:
|
||||||
@@ -183,124 +185,18 @@ class ContextManagersTest(unittest.TestCase):
|
|||||||
self.assertEqual(B.objects.count(), 3)
|
self.assertEqual(B.objects.count(), 3)
|
||||||
self.assertEqual(C.objects.count(), 1)
|
self.assertEqual(C.objects.count(), 1)
|
||||||
|
|
||||||
def test_no_sub_classes_modification_to_document_class_are_temporary(self):
|
|
||||||
class A(Document):
|
|
||||||
x = IntField()
|
|
||||||
meta = {'allow_inheritance': True}
|
|
||||||
|
|
||||||
class B(A):
|
|
||||||
z = IntField()
|
|
||||||
|
|
||||||
self.assertEqual(A._subclasses, ('A', 'A.B'))
|
|
||||||
with no_sub_classes(A):
|
|
||||||
self.assertEqual(A._subclasses, ('A',))
|
|
||||||
self.assertEqual(A._subclasses, ('A', 'A.B'))
|
|
||||||
|
|
||||||
self.assertEqual(B._subclasses, ('A.B',))
|
|
||||||
with no_sub_classes(B):
|
|
||||||
self.assertEqual(B._subclasses, ('A.B',))
|
|
||||||
self.assertEqual(B._subclasses, ('A.B',))
|
|
||||||
|
|
||||||
def test_no_subclass_context_manager_does_not_swallow_exception(self):
|
|
||||||
class User(Document):
|
|
||||||
name = StringField()
|
|
||||||
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
with no_sub_classes(User):
|
|
||||||
raise TypeError()
|
|
||||||
|
|
||||||
def test_query_counter_does_not_swallow_exception(self):
|
|
||||||
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
with query_counter() as q:
|
|
||||||
raise TypeError()
|
|
||||||
|
|
||||||
def test_query_counter_temporarily_modifies_profiling_level(self):
|
|
||||||
connect('mongoenginetest')
|
|
||||||
db = get_db()
|
|
||||||
|
|
||||||
initial_profiling_level = db.profiling_level()
|
|
||||||
|
|
||||||
try:
|
|
||||||
NEW_LEVEL = 1
|
|
||||||
db.set_profiling_level(NEW_LEVEL)
|
|
||||||
self.assertEqual(db.profiling_level(), NEW_LEVEL)
|
|
||||||
with query_counter() as q:
|
|
||||||
self.assertEqual(db.profiling_level(), 2)
|
|
||||||
self.assertEqual(db.profiling_level(), NEW_LEVEL)
|
|
||||||
except Exception:
|
|
||||||
db.set_profiling_level(initial_profiling_level) # Ensures it gets reseted no matter the outcome of the test
|
|
||||||
raise
|
|
||||||
|
|
||||||
def test_query_counter(self):
|
def test_query_counter(self):
|
||||||
connect('mongoenginetest')
|
connect('mongoenginetest')
|
||||||
db = get_db()
|
db = get_db()
|
||||||
|
db.test.find({})
|
||||||
collection = db.query_counter
|
|
||||||
collection.drop()
|
|
||||||
|
|
||||||
def issue_1_count_query():
|
|
||||||
collection.find({}).count()
|
|
||||||
|
|
||||||
def issue_1_insert_query():
|
|
||||||
collection.insert_one({'test': 'garbage'})
|
|
||||||
|
|
||||||
def issue_1_find_query():
|
|
||||||
collection.find_one()
|
|
||||||
|
|
||||||
counter = 0
|
|
||||||
with query_counter() as q:
|
|
||||||
self.assertEqual(q, counter)
|
|
||||||
self.assertEqual(q, counter) # Ensures previous count query did not get counted
|
|
||||||
|
|
||||||
for _ in range(10):
|
|
||||||
issue_1_insert_query()
|
|
||||||
counter += 1
|
|
||||||
self.assertEqual(q, counter)
|
|
||||||
|
|
||||||
for _ in range(4):
|
|
||||||
issue_1_find_query()
|
|
||||||
counter += 1
|
|
||||||
self.assertEqual(q, counter)
|
|
||||||
|
|
||||||
for _ in range(3):
|
|
||||||
issue_1_count_query()
|
|
||||||
counter += 1
|
|
||||||
self.assertEqual(q, counter)
|
|
||||||
|
|
||||||
def test_query_counter_counts_getmore_queries(self):
|
|
||||||
connect('mongoenginetest')
|
|
||||||
db = get_db()
|
|
||||||
|
|
||||||
collection = db.query_counter
|
|
||||||
collection.drop()
|
|
||||||
|
|
||||||
many_docs = [{'test': 'garbage %s' % i} for i in range(150)]
|
|
||||||
collection.insert_many(many_docs) # first batch of documents contains 101 documents
|
|
||||||
|
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
self.assertEqual(q, 0)
|
self.assertEqual(0, q)
|
||||||
list(collection.find())
|
|
||||||
self.assertEqual(q, 2) # 1st select + 1 getmore
|
|
||||||
|
|
||||||
def test_query_counter_ignores_particular_queries(self):
|
for i in range(1, 51):
|
||||||
connect('mongoenginetest')
|
db.test.find({}).count()
|
||||||
db = get_db()
|
|
||||||
|
|
||||||
collection = db.query_counter
|
self.assertEqual(50, q)
|
||||||
collection.insert_many([{'test': 'garbage %s' % i} for i in range(10)])
|
|
||||||
|
|
||||||
with query_counter() as q:
|
|
||||||
self.assertEqual(q, 0)
|
|
||||||
cursor = collection.find()
|
|
||||||
self.assertEqual(q, 0) # cursor wasn't opened yet
|
|
||||||
_ = next(cursor) # opens the cursor and fires the find query
|
|
||||||
self.assertEqual(q, 1)
|
|
||||||
|
|
||||||
cursor.close() # issues a `killcursors` query that is ignored by the context
|
|
||||||
self.assertEqual(q, 1)
|
|
||||||
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
|
|
||||||
self.assertEqual(q, 1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -1,21 +1,6 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from mongoengine.base.datastructures import StrictDict, BaseList
|
from mongoengine.base.datastructures import StrictDict, SemiStrictDict
|
||||||
|
|
||||||
|
|
||||||
class TestBaseList(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_iter_simple(self):
|
|
||||||
values = [True, False, True, False]
|
|
||||||
base_list = BaseList(values, instance=None, name='my_name')
|
|
||||||
self.assertEqual(values, list(base_list))
|
|
||||||
|
|
||||||
def test_iter_allow_modification_while_iterating_withou_error(self):
|
|
||||||
# regular list allows for this, thus this subclass must comply to that
|
|
||||||
base_list = BaseList([True, False, True, False], instance=None, name='my_name')
|
|
||||||
for idx, val in enumerate(base_list):
|
|
||||||
if val:
|
|
||||||
base_list.pop(idx)
|
|
||||||
|
|
||||||
|
|
||||||
class TestStrictDict(unittest.TestCase):
|
class TestStrictDict(unittest.TestCase):
|
||||||
@@ -91,5 +76,44 @@ class TestStrictDict(unittest.TestCase):
|
|||||||
assert dict(**d) == {'a': 1, 'b': 2}
|
assert dict(**d) == {'a': 1, 'b': 2}
|
||||||
|
|
||||||
|
|
||||||
|
class TestSemiSrictDict(TestStrictDict):
|
||||||
|
def strict_dict_class(self, *args, **kwargs):
|
||||||
|
return SemiStrictDict.create(*args, **kwargs)
|
||||||
|
|
||||||
|
def test_init_fails_on_nonexisting_attrs(self):
|
||||||
|
# disable irrelevant test
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_setattr_raises_on_nonexisting_attr(self):
|
||||||
|
# disable irrelevant test
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_setattr_getattr_nonexisting_attr_succeeds(self):
|
||||||
|
d = self.dtype()
|
||||||
|
d.x = 1
|
||||||
|
self.assertEqual(d.x, 1)
|
||||||
|
|
||||||
|
def test_init_succeeds_with_nonexisting_attrs(self):
|
||||||
|
d = self.dtype(a=1, b=1, c=1, x=2)
|
||||||
|
self.assertEqual((d.a, d.b, d.c, d.x), (1, 1, 1, 2))
|
||||||
|
|
||||||
|
def test_iter_with_nonexisting_attrs(self):
|
||||||
|
d = self.dtype(a=1, b=1, c=1, x=2)
|
||||||
|
self.assertEqual(list(d), ['a', 'b', 'c', 'x'])
|
||||||
|
|
||||||
|
def test_iteritems_with_nonexisting_attrs(self):
|
||||||
|
d = self.dtype(a=1, b=1, c=1, x=2)
|
||||||
|
self.assertEqual(list(d.iteritems()), [('a', 1), ('b', 1), ('c', 1), ('x', 2)])
|
||||||
|
|
||||||
|
def tets_cmp_with_strict_dicts(self):
|
||||||
|
d = self.dtype(a=1, b=1, c=1)
|
||||||
|
dd = StrictDict.create(("a", "b", "c"))(a=1, b=1, c=1)
|
||||||
|
self.assertEqual(d, dd)
|
||||||
|
|
||||||
|
def test_cmp_with_strict_dict_with_nonexisting_attrs(self):
|
||||||
|
d = self.dtype(a=1, b=1, c=1, x=2)
|
||||||
|
dd = StrictDict.create(("a", "b", "c", "x"))(a=1, b=1, c=1, x=2)
|
||||||
|
self.assertEqual(d, dd)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -2,10 +2,15 @@
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from bson import DBRef, ObjectId
|
from bson import DBRef, ObjectId
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.context_managers import query_counter
|
from mongoengine.context_managers import query_counter
|
||||||
|
from mongoengine.python_support import IS_PYMONGO_3
|
||||||
|
from mongoengine.base import TopLevelDocumentMetaclass
|
||||||
|
if IS_PYMONGO_3:
|
||||||
|
from bson import CodecOptions
|
||||||
|
|
||||||
|
|
||||||
class FieldTest(unittest.TestCase):
|
class FieldTest(unittest.TestCase):
|
||||||
@@ -200,8 +205,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
group = Group(author=user, members=[user]).save()
|
group = Group(author=user, members=[user]).save()
|
||||||
|
|
||||||
raw_data = Group._get_collection().find_one()
|
raw_data = Group._get_collection().find_one()
|
||||||
self.assertIsInstance(raw_data['author'], DBRef)
|
self.assertTrue(isinstance(raw_data['author'], DBRef))
|
||||||
self.assertIsInstance(raw_data['members'][0], DBRef)
|
self.assertTrue(isinstance(raw_data['members'][0], DBRef))
|
||||||
group = Group.objects.first()
|
group = Group.objects.first()
|
||||||
|
|
||||||
self.assertEqual(group.author, user)
|
self.assertEqual(group.author, user)
|
||||||
@@ -224,8 +229,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(group.members, [user])
|
self.assertEqual(group.members, [user])
|
||||||
|
|
||||||
raw_data = Group._get_collection().find_one()
|
raw_data = Group._get_collection().find_one()
|
||||||
self.assertIsInstance(raw_data['author'], ObjectId)
|
self.assertTrue(isinstance(raw_data['author'], ObjectId))
|
||||||
self.assertIsInstance(raw_data['members'][0], ObjectId)
|
self.assertTrue(isinstance(raw_data['members'][0], ObjectId))
|
||||||
|
|
||||||
def test_recursive_reference(self):
|
def test_recursive_reference(self):
|
||||||
"""Ensure that ReferenceFields can reference their own documents.
|
"""Ensure that ReferenceFields can reference their own documents.
|
||||||
@@ -469,7 +474,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for m in group_obj.members:
|
for m in group_obj.members:
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -485,7 +490,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for m in group_obj.members:
|
for m in group_obj.members:
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -502,7 +507,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for m in group_obj.members:
|
for m in group_obj.members:
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
UserA.drop_collection()
|
UserA.drop_collection()
|
||||||
UserB.drop_collection()
|
UserB.drop_collection()
|
||||||
@@ -560,7 +565,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for m in group_obj.members:
|
for m in group_obj.members:
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -576,7 +581,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for m in group_obj.members:
|
for m in group_obj.members:
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -593,7 +598,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for m in group_obj.members:
|
for m in group_obj.members:
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
UserA.drop_collection()
|
UserA.drop_collection()
|
||||||
UserB.drop_collection()
|
UserB.drop_collection()
|
||||||
@@ -633,7 +638,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIsInstance(m, User)
|
self.assertTrue(isinstance(m, User))
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -646,7 +651,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIsInstance(m, User)
|
self.assertTrue(isinstance(m, User))
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -660,7 +665,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIsInstance(m, User)
|
self.assertTrue(isinstance(m, User))
|
||||||
|
|
||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
Group.drop_collection()
|
Group.drop_collection()
|
||||||
@@ -715,7 +720,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -731,7 +736,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -748,7 +753,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
Group.objects.delete()
|
Group.objects.delete()
|
||||||
Group().save()
|
Group().save()
|
||||||
@@ -806,7 +811,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIsInstance(m, UserA)
|
self.assertTrue(isinstance(m, UserA))
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -822,7 +827,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIsInstance(m, UserA)
|
self.assertTrue(isinstance(m, UserA))
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -839,7 +844,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIsInstance(m, UserA)
|
self.assertTrue(isinstance(m, UserA))
|
||||||
|
|
||||||
UserA.drop_collection()
|
UserA.drop_collection()
|
||||||
Group.drop_collection()
|
Group.drop_collection()
|
||||||
@@ -894,7 +899,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Document select_related
|
# Document select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -910,7 +915,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
with query_counter() as q:
|
with query_counter() as q:
|
||||||
@@ -927,7 +932,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 4)
|
self.assertEqual(q, 4)
|
||||||
|
|
||||||
for k, m in group_obj.members.iteritems():
|
for k, m in group_obj.members.iteritems():
|
||||||
self.assertIn('User', m.__class__.__name__)
|
self.assertTrue('User' in m.__class__.__name__)
|
||||||
|
|
||||||
Group.objects.delete()
|
Group.objects.delete()
|
||||||
Group().save()
|
Group().save()
|
||||||
@@ -1029,6 +1034,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(type(foo.bar), Bar)
|
self.assertEqual(type(foo.bar), Bar)
|
||||||
self.assertEqual(type(foo.baz), Baz)
|
self.assertEqual(type(foo.baz), Baz)
|
||||||
|
|
||||||
|
|
||||||
def test_document_reload_reference_integrity(self):
|
def test_document_reload_reference_integrity(self):
|
||||||
"""
|
"""
|
||||||
Ensure reloading a document with multiple similar id
|
Ensure reloading a document with multiple similar id
|
||||||
@@ -1208,10 +1214,10 @@ class FieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
# Can't use query_counter across databases - so test the _data object
|
# Can't use query_counter across databases - so test the _data object
|
||||||
book = Book.objects.first()
|
book = Book.objects.first()
|
||||||
self.assertNotIsInstance(book._data['author'], User)
|
self.assertFalse(isinstance(book._data['author'], User))
|
||||||
|
|
||||||
book.select_related()
|
book.select_related()
|
||||||
self.assertIsInstance(book._data['author'], User)
|
self.assertTrue(isinstance(book._data['author'], User))
|
||||||
|
|
||||||
def test_non_ascii_pk(self):
|
def test_non_ascii_pk(self):
|
||||||
"""
|
"""
|
||||||
@@ -1286,5 +1292,70 @@ class FieldTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
|
def test_dynamic_field_dereference(self):
|
||||||
|
class Merchandise(Document):
|
||||||
|
name = StringField()
|
||||||
|
price = IntField()
|
||||||
|
|
||||||
|
class Store(Document):
|
||||||
|
merchandises = DynamicField()
|
||||||
|
|
||||||
|
Merchandise.drop_collection()
|
||||||
|
Store.drop_collection()
|
||||||
|
|
||||||
|
merchandises = {
|
||||||
|
'#1': Merchandise(name='foo', price=100).save(),
|
||||||
|
'#2': Merchandise(name='bar', price=120).save(),
|
||||||
|
'#3': Merchandise(name='baz', price=110).save(),
|
||||||
|
}
|
||||||
|
Store(merchandises=merchandises).save()
|
||||||
|
|
||||||
|
store = Store.objects().first()
|
||||||
|
for obj in store.merchandises.values():
|
||||||
|
self.assertFalse(isinstance(obj, Merchandise))
|
||||||
|
|
||||||
|
store.select_related()
|
||||||
|
for obj in store.merchandises.values():
|
||||||
|
self.assertTrue(isinstance(obj, Merchandise))
|
||||||
|
|
||||||
|
def test_dynamic_field_dereference_with_ordering_guarantee_on_pymongo3(self):
|
||||||
|
# This is because 'codec_options' is supported on pymongo3 or later
|
||||||
|
if IS_PYMONGO_3:
|
||||||
|
class OrderedDocument(Document):
|
||||||
|
my_metaclass = TopLevelDocumentMetaclass
|
||||||
|
__metaclass__ = TopLevelDocumentMetaclass
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _get_collection(cls):
|
||||||
|
collection = super(OrderedDocument, cls)._get_collection()
|
||||||
|
opts = CodecOptions(document_class=OrderedDict)
|
||||||
|
|
||||||
|
return collection.with_options(codec_options=opts)
|
||||||
|
|
||||||
|
class Merchandise(Document):
|
||||||
|
name = StringField()
|
||||||
|
price = IntField()
|
||||||
|
|
||||||
|
class Store(OrderedDocument):
|
||||||
|
merchandises = DynamicField(container_class=OrderedDict)
|
||||||
|
|
||||||
|
Merchandise.drop_collection()
|
||||||
|
Store.drop_collection()
|
||||||
|
|
||||||
|
merchandises = OrderedDict()
|
||||||
|
merchandises['#1'] = Merchandise(name='foo', price=100).save()
|
||||||
|
merchandises['#2'] = Merchandise(name='bar', price=120).save()
|
||||||
|
merchandises['#3'] = Merchandise(name='baz', price=110).save()
|
||||||
|
|
||||||
|
Store(merchandises=merchandises).save()
|
||||||
|
|
||||||
|
store = Store.objects().first()
|
||||||
|
|
||||||
|
store.select_related()
|
||||||
|
|
||||||
|
# confirms that the load data order is same with the one at storing
|
||||||
|
self.assertTrue(type(store.merchandises), OrderedDict)
|
||||||
|
self.assertEqual(','.join(store.merchandises.keys()), '#1,#2,#3')
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
@@ -1,38 +0,0 @@
|
|||||||
import unittest
|
|
||||||
import re
|
|
||||||
|
|
||||||
from mongoengine.base.utils import LazyRegexCompiler
|
|
||||||
|
|
||||||
signal_output = []
|
|
||||||
|
|
||||||
|
|
||||||
class LazyRegexCompilerTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_lazy_regex_compiler_verify_laziness_of_descriptor(self):
|
|
||||||
class UserEmail(object):
|
|
||||||
EMAIL_REGEX = LazyRegexCompiler('@', flags=32)
|
|
||||||
|
|
||||||
descriptor = UserEmail.__dict__['EMAIL_REGEX']
|
|
||||||
self.assertIsNone(descriptor._compiled_regex)
|
|
||||||
|
|
||||||
regex = UserEmail.EMAIL_REGEX
|
|
||||||
self.assertEqual(regex, re.compile('@', flags=32))
|
|
||||||
self.assertEqual(regex.search('user@domain.com').group(), '@')
|
|
||||||
|
|
||||||
user_email = UserEmail()
|
|
||||||
self.assertIs(user_email.EMAIL_REGEX, UserEmail.EMAIL_REGEX)
|
|
||||||
|
|
||||||
def test_lazy_regex_compiler_verify_cannot_set_descriptor_on_instance(self):
|
|
||||||
class UserEmail(object):
|
|
||||||
EMAIL_REGEX = LazyRegexCompiler('@')
|
|
||||||
|
|
||||||
user_email = UserEmail()
|
|
||||||
with self.assertRaises(AttributeError):
|
|
||||||
user_email.EMAIL_REGEX = re.compile('@')
|
|
||||||
|
|
||||||
def test_lazy_regex_compiler_verify_can_override_class_attr(self):
|
|
||||||
class UserEmail(object):
|
|
||||||
EMAIL_REGEX = LazyRegexCompiler('@')
|
|
||||||
|
|
||||||
UserEmail.EMAIL_REGEX = re.compile('cookies')
|
|
||||||
self.assertEqual(UserEmail.EMAIL_REGEX.search('Cake & cookies').group(), 'cookies')
|
|
@@ -7,19 +7,12 @@ from mongoengine.connection import get_db, get_connection
|
|||||||
from mongoengine.python_support import IS_PYMONGO_3
|
from mongoengine.python_support import IS_PYMONGO_3
|
||||||
|
|
||||||
|
|
||||||
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
|
MONGO_TEST_DB = 'mongoenginetest'
|
||||||
|
|
||||||
|
|
||||||
# Constant that can be used to compare the version retrieved with
|
|
||||||
# get_mongodb_version()
|
|
||||||
MONGODB_26 = (2, 6)
|
|
||||||
MONGODB_3 = (3,0)
|
|
||||||
MONGODB_32 = (3, 2)
|
|
||||||
|
|
||||||
|
|
||||||
class MongoDBTestCase(unittest.TestCase):
|
class MongoDBTestCase(unittest.TestCase):
|
||||||
"""Base class for tests that need a mongodb connection
|
"""Base class for tests that need a mongodb connection
|
||||||
It ensures that the db is clean at the beginning and dropped at the end automatically
|
db is being dropped automatically
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -34,46 +27,40 @@ class MongoDBTestCase(unittest.TestCase):
|
|||||||
|
|
||||||
|
|
||||||
def get_mongodb_version():
|
def get_mongodb_version():
|
||||||
"""Return the version of the connected mongoDB (first 2 digits)
|
"""Return the version tuple of the MongoDB server that the default
|
||||||
|
connection is connected to.
|
||||||
:return: tuple(int, int)
|
|
||||||
"""
|
"""
|
||||||
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
|
return tuple(get_connection().server_info()['versionArray'])
|
||||||
return tuple(version_list)
|
|
||||||
|
|
||||||
|
def _decorated_with_ver_requirement(func, ver_tuple):
|
||||||
def _decorated_with_ver_requirement(func, version):
|
|
||||||
"""Return a given function decorated with the version requirement
|
"""Return a given function decorated with the version requirement
|
||||||
for a particular MongoDB version tuple.
|
for a particular MongoDB version tuple.
|
||||||
|
|
||||||
:param version: The version required (tuple(int, int))
|
|
||||||
"""
|
"""
|
||||||
def _inner(*args, **kwargs):
|
def _inner(*args, **kwargs):
|
||||||
MONGODB_V = get_mongodb_version()
|
mongodb_ver = get_mongodb_version()
|
||||||
if MONGODB_V >= version:
|
if mongodb_ver >= ver_tuple:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in version)))
|
raise SkipTest('Needs MongoDB v{}+'.format(
|
||||||
|
'.'.join([str(v) for v in ver_tuple])
|
||||||
|
))
|
||||||
|
|
||||||
_inner.__name__ = func.__name__
|
_inner.__name__ = func.__name__
|
||||||
_inner.__doc__ = func.__doc__
|
_inner.__doc__ = func.__doc__
|
||||||
|
|
||||||
return _inner
|
return _inner
|
||||||
|
|
||||||
|
def needs_mongodb_v26(func):
|
||||||
def requires_mongodb_gte_26(func):
|
|
||||||
"""Raise a SkipTest exception if we're working with MongoDB version
|
"""Raise a SkipTest exception if we're working with MongoDB version
|
||||||
lower than v2.6.
|
lower than v2.6.
|
||||||
"""
|
"""
|
||||||
return _decorated_with_ver_requirement(func, MONGODB_26)
|
return _decorated_with_ver_requirement(func, (2, 6))
|
||||||
|
|
||||||
|
def needs_mongodb_v3(func):
|
||||||
def requires_mongodb_gte_3(func):
|
|
||||||
"""Raise a SkipTest exception if we're working with MongoDB version
|
"""Raise a SkipTest exception if we're working with MongoDB version
|
||||||
lower than v3.0.
|
lower than v3.0.
|
||||||
"""
|
"""
|
||||||
return _decorated_with_ver_requirement(func, MONGODB_3)
|
return _decorated_with_ver_requirement(func, (3, 0))
|
||||||
|
|
||||||
|
|
||||||
def skip_pymongo3(f):
|
def skip_pymongo3(f):
|
||||||
"""Raise a SkipTest exception if we're running a test against
|
"""Raise a SkipTest exception if we're running a test against
|
||||||
|
7
tox.ini
7
tox.ini
@@ -1,12 +1,13 @@
|
|||||||
[tox]
|
[tox]
|
||||||
envlist = {py27,py35,pypy,pypy3}-{mg35,mg3x}
|
envlist = {py27,py35,pypy,pypy3}-{mg27,mg28,mg30}
|
||||||
|
|
||||||
[testenv]
|
[testenv]
|
||||||
commands =
|
commands =
|
||||||
python setup.py nosetests {posargs}
|
python setup.py nosetests {posargs}
|
||||||
deps =
|
deps =
|
||||||
nose
|
nose
|
||||||
mg35: PyMongo==3.5
|
mg27: PyMongo<2.8
|
||||||
mg3x: PyMongo>=3.0,<3.7
|
mg28: PyMongo>=2.8,<2.9
|
||||||
|
mg30: PyMongo>=3.0
|
||||||
setenv =
|
setenv =
|
||||||
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
PYTHON_EGG_CACHE = {envdir}/python-eggs
|
||||||
|
Reference in New Issue
Block a user