Compare commits
345 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
74343841e4 | ||
|
3b3738b36b | ||
|
b15c3f6a3f | ||
|
2459f9b0aa | ||
|
6ff1bd9b3c | ||
|
1bc2d2ec37 | ||
|
d7fd6a4628 | ||
|
9236f365fa | ||
|
90d22c2a28 | ||
|
c9f6e6b62a | ||
|
260d9377f5 | ||
|
22d1ce6319 | ||
|
6997e02476 | ||
|
155d79ff4d | ||
|
452cd125fa | ||
|
e62c35b040 | ||
|
d5ec3c6a31 | ||
|
ad983dc279 | ||
|
bb15bf8d13 | ||
|
94adc207ad | ||
|
376d1c97ab | ||
|
4fe87b40da | ||
|
b10d76cf4b | ||
|
3bdc9a2f09 | ||
|
9d52e18659 | ||
|
653c4259ee | ||
|
9f5ab8149f | ||
|
66c6d14f7a | ||
|
2c0fc142a3 | ||
|
0da2dfd191 | ||
|
787fc1cd8b | ||
|
c31488add9 | ||
|
31ec7907b5 | ||
|
12f3f8c694 | ||
|
79098e997e | ||
|
dc1849bad5 | ||
|
e2d826c412 | ||
|
e6d796832e | ||
|
6f0a6df4f6 | ||
|
7a877a00d5 | ||
|
e8604d100e | ||
|
1647441ce8 | ||
|
9f8d6b3a00 | ||
|
4b2ad25405 | ||
|
3ce163b1a0 | ||
|
7c1ee28f13 | ||
|
2645e43da1 | ||
|
59bfe551a3 | ||
|
e2c78047b1 | ||
|
6a4351e44f | ||
|
adb60ef1ac | ||
|
3090adac04 | ||
|
b9253d86cc | ||
|
ab4d4e6230 | ||
|
7cd38c56c6 | ||
|
864053615b | ||
|
db2366f112 | ||
|
4defc82192 | ||
|
5949970a95 | ||
|
0ea4abda81 | ||
|
5c6035d636 | ||
|
a2183e3dcc | ||
|
99637151b5 | ||
|
a8e787c120 | ||
|
53339c7c72 | ||
|
3534bf7d70 | ||
|
1cf3989664 | ||
|
fd296918da | ||
|
8ad1f03dc5 | ||
|
fe7e17dbd5 | ||
|
d582394a42 | ||
|
02ef0df019 | ||
|
0dfd6aa518 | ||
|
0b23bc9cf2 | ||
|
f108c4288e | ||
|
9b9696aefd | ||
|
576e198ece | ||
|
52f85aab18 | ||
|
ab60fd0490 | ||
|
d79ae30f31 | ||
|
f27debe7f9 | ||
|
735e043ff6 | ||
|
6e7f2b73cf | ||
|
d645ce9745 | ||
|
7c08c140da | ||
|
81d402dc17 | ||
|
966fa12358 | ||
|
87792e1921 | ||
|
4c8296acc6 | ||
|
9989da07ed | ||
|
1c5e6a3425 | ||
|
eedf908770 | ||
|
5c9ef41403 | ||
|
0bf2ad5b67 | ||
|
a0e3f382cd | ||
|
f09c39b5d7 | ||
|
89c67bf259 | ||
|
ea666d4607 | ||
|
b8af154439 | ||
|
f594ece32a | ||
|
03beb6852a | ||
|
ab9e9a3329 | ||
|
a4b09344af | ||
|
8cb8aa392c | ||
|
3255519792 | ||
|
7e64bb2503 | ||
|
86a78402c3 | ||
|
ba276452fb | ||
|
4ffa8d0124 | ||
|
4bc5082681 | ||
|
0e3c34e1da | ||
|
658b3784ae | ||
|
0526f577ff | ||
|
bb1b9bc1d3 | ||
|
b1eeb77ddc | ||
|
999d4a7676 | ||
|
1b80193aac | ||
|
be8d39a48c | ||
|
a2f3d70f28 | ||
|
676a7bf712 | ||
|
e990a6c70c | ||
|
90fa0f6c4a | ||
|
22010d7d95 | ||
|
66279bd90f | ||
|
19da228855 | ||
|
9e67941bad | ||
|
0454fc74e9 | ||
|
2f6b1c7611 | ||
|
f00bed6058 | ||
|
529c522594 | ||
|
2bb9493fcf | ||
|
839ed8a64a | ||
|
017a31ffd0 | ||
|
83b961c84d | ||
|
fa07423ca5 | ||
|
dd4af2df81 | ||
|
44bd8cb85b | ||
|
52d80ac23c | ||
|
43a5d73e14 | ||
|
abc764951d | ||
|
9cc6164026 | ||
|
475488b9f2 | ||
|
95b1783834 | ||
|
12c8b5c0b9 | ||
|
f99b7a811b | ||
|
0575abab23 | ||
|
9eebcf7beb | ||
|
ed74477150 | ||
|
2801b38c75 | ||
|
dc3fea875e | ||
|
aab8c2b687 | ||
|
3577773af3 | ||
|
dd023edc0f | ||
|
8ac9e6dc19 | ||
|
f45d4d781d | ||
|
c95652d6a8 | ||
|
97b37f75d3 | ||
|
95dae48778 | ||
|
73635033bd | ||
|
c1619d2a62 | ||
|
b87ef982f6 | ||
|
91aa90ad4a | ||
|
4b3cea9e78 | ||
|
2420b5e937 | ||
|
f23a976bea | ||
|
4226cd08f1 | ||
|
7a230f1693 | ||
|
a43d0d4612 | ||
|
78a40a0c70 | ||
|
2c69d8f0b0 | ||
|
0018c38b83 | ||
|
8df81571fc | ||
|
d1add62a06 | ||
|
c419f3379a | ||
|
69d57209f7 | ||
|
7ca81d6fb8 | ||
|
8a046bfa5d | ||
|
3628a7653c | ||
|
48f988acd7 | ||
|
6526923345 | ||
|
24fd1acce6 | ||
|
cbb9235dc5 | ||
|
19ec2c9bc9 | ||
|
6459d4c0b6 | ||
|
1304f2721f | ||
|
8bde0c0e53 | ||
|
598ffd3e5c | ||
|
1a4533a9cf | ||
|
601f0eb168 | ||
|
3070e0bf5d | ||
|
83c11a9834 | ||
|
5c912b930e | ||
|
1b17fb0ae7 | ||
|
d83e67c121 | ||
|
ae39ed94c9 | ||
|
1e51180d42 | ||
|
87ba69d02e | ||
|
8879d5560b | ||
|
c1621ee39c | ||
|
b0aa98edb4 | ||
|
a7a2fe0216 | ||
|
8e50f5fa3c | ||
|
31793520bf | ||
|
0b6b0368c5 | ||
|
d1d30a9280 | ||
|
420c6f2d1e | ||
|
34f06c4971 | ||
|
9cc4bbd49d | ||
|
f66b312869 | ||
|
2405ba8708 | ||
|
a91b6bff8b | ||
|
450dc11a68 | ||
|
1ce2f84ce5 | ||
|
f55b241cfa | ||
|
34d08ce8ef | ||
|
4f5aa8c43b | ||
|
27b375060d | ||
|
cbfdc401f7 | ||
|
b58bf3e0ce | ||
|
1fff7e9aca | ||
|
494b981b13 | ||
|
dd93995bd0 | ||
|
b3bb4add9c | ||
|
d305e71c27 | ||
|
0d92baa670 | ||
|
7a1b110f62 | ||
|
db8df057ce | ||
|
5d8ffded40 | ||
|
07f3e5356d | ||
|
1ece62f960 | ||
|
056c604dc3 | ||
|
2d08eec093 | ||
|
614b590551 | ||
|
6d90ce250a | ||
|
ea31846a19 | ||
|
e6317776c1 | ||
|
efeaba39a4 | ||
|
1a97dfd479 | ||
|
9fecf2b303 | ||
|
3d0d2f48ad | ||
|
581605e0e2 | ||
|
45d3a7f6ff | ||
|
7ca2ea0766 | ||
|
89220c142b | ||
|
c73ce3d220 | ||
|
b0f127af4e | ||
|
766d54795f | ||
|
bd41c6eea4 | ||
|
2435786713 | ||
|
9e7ea64bd2 | ||
|
89a6eee6af | ||
|
2ec1476e50 | ||
|
2d9b581f34 | ||
|
5bb63f645b | ||
|
a856c7cc37 | ||
|
26db9d8a9d | ||
|
8060179f6d | ||
|
77ebd87fed | ||
|
e4bc92235d | ||
|
27a4d83ce8 | ||
|
ece9b902f8 | ||
|
65a2f8a68b | ||
|
9c212306b8 | ||
|
1fdc7ce6bb | ||
|
0b22c140c5 | ||
|
944aa45459 | ||
|
c9842ba13a | ||
|
8840680303 | ||
|
376b9b1316 | ||
|
54bb1cb3d9 | ||
|
43468b474e | ||
|
28a957c684 | ||
|
ec5ddbf391 | ||
|
bab186e195 | ||
|
bc7e874476 | ||
|
97114b5948 | ||
|
45e015d71d | ||
|
0ff6531953 | ||
|
ba298c3cfc | ||
|
0479bea40b | ||
|
a536097804 | ||
|
bbefd0fdf9 | ||
|
2aa8b04c21 | ||
|
aeebdfec51 | ||
|
debfcdf498 | ||
|
5c4b33e8e6 | ||
|
eb54037b66 | ||
|
f48af8db3b | ||
|
97c5b957dd | ||
|
95e7397803 | ||
|
43a989978a | ||
|
27734a7c26 | ||
|
dd786d6fc4 | ||
|
be1c28fc45 | ||
|
20e41b3523 | ||
|
e07ecc5cf8 | ||
|
3360b72531 | ||
|
233b13d670 | ||
|
5bcbb4fdaa | ||
|
dbe2f5f2b8 | ||
|
ca8b58d66d | ||
|
f80f0b416f | ||
|
d7765511ee | ||
|
0240a09056 | ||
|
ab15c4eec9 | ||
|
4ce1ba81a6 | ||
|
530440b333 | ||
|
b80fda36af | ||
|
42d24263ef | ||
|
1e2797e7ce | ||
|
f7075766fc | ||
|
5647ca70bb | ||
|
2b8aa6bafc | ||
|
410443471c | ||
|
0bb9781b91 | ||
|
2769d6d7ca | ||
|
120b9433c2 | ||
|
605092bd88 | ||
|
a4a8c94374 | ||
|
0e93f6c0db | ||
|
aa2add39ad | ||
|
a928047147 | ||
|
c474ca0f13 | ||
|
88dc64653e | ||
|
5f4b70f3a9 | ||
|
51b429e5b0 | ||
|
360624eb6e | ||
|
d9d2291837 | ||
|
cbdf816232 | ||
|
2d71eb8a18 | ||
|
64d2532ce9 | ||
|
0376910f33 | ||
|
6d503119a1 | ||
|
bfae93e57e | ||
|
49a66ba81a | ||
|
a1d43fecd9 | ||
|
d0e42a4798 | ||
|
46e088d379 | ||
|
1bf9f28f4b | ||
|
8cfe13ad90 | ||
|
7e376b40bb | ||
|
3ec9dfc108 | ||
|
540a0cc59c | ||
|
83eb4f6b16 | ||
|
95c58bd793 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -13,4 +13,5 @@ env/
|
|||||||
.settings
|
.settings
|
||||||
.project
|
.project
|
||||||
.pydevproject
|
.pydevproject
|
||||||
tests/bugfix.py
|
tests/test_bugfix.py
|
||||||
|
htmlcov/
|
29
.travis.yml
Normal file
29
.travis.yml
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# http://travis-ci.org/#!/MongoEngine/mongoengine
|
||||||
|
language: python
|
||||||
|
services: mongodb
|
||||||
|
python:
|
||||||
|
- 2.5
|
||||||
|
- 2.6
|
||||||
|
- 2.7
|
||||||
|
- 3.1
|
||||||
|
- 3.2
|
||||||
|
env:
|
||||||
|
- PYMONGO=dev
|
||||||
|
- PYMONGO=2.3
|
||||||
|
- PYMONGO=2.2
|
||||||
|
install:
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then sudo apt-get install zlib1g zlib1g-dev; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then sudo ln -s /usr/lib/i386-linux-gnu/libz.so /usr/lib/; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then pip install PIL --use-mirrors ; true; fi
|
||||||
|
- if [[ $TRAVIS_PYTHON_VERSION == '2.'* ]]; then pip install PIL --use-mirrors ; true; fi
|
||||||
|
- if [[ $PYMONGO == 'dev' ]]; then pip install https://github.com/mongodb/mongo-python-driver/tarball/master; true; fi
|
||||||
|
- if [[ $PYMONGO != 'dev' ]]; then pip install pymongo==$PYMONGO --use-mirrors; true; fi
|
||||||
|
- python setup.py install
|
||||||
|
script:
|
||||||
|
- python setup.py test
|
||||||
|
notifications:
|
||||||
|
irc: "irc.freenode.org#mongoengine"
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- 0.8
|
30
AUTHORS
30
AUTHORS
@@ -8,6 +8,7 @@ Florian Schlachter <flori@n-schlachter.de>
|
|||||||
Steve Challis <steve@stevechallis.com>
|
Steve Challis <steve@stevechallis.com>
|
||||||
Wilson Júnior <wilsonpjunior@gmail.com>
|
Wilson Júnior <wilsonpjunior@gmail.com>
|
||||||
Dan Crosta https://github.com/dcrosta
|
Dan Crosta https://github.com/dcrosta
|
||||||
|
Laine Herron https://github.com/LaineHerron
|
||||||
|
|
||||||
CONTRIBUTORS
|
CONTRIBUTORS
|
||||||
|
|
||||||
@@ -99,4 +100,31 @@ that much better:
|
|||||||
* Robert Kajic
|
* Robert Kajic
|
||||||
* Jacob Peddicord
|
* Jacob Peddicord
|
||||||
* Nils Hasenbanck
|
* Nils Hasenbanck
|
||||||
* mostlystatic
|
* mostlystatic
|
||||||
|
* Greg Banks
|
||||||
|
* swashbuckler
|
||||||
|
* Adam Reeve
|
||||||
|
* Anthony Nemitz
|
||||||
|
* deignacio
|
||||||
|
* Shaun Duncan
|
||||||
|
* Meir Kriheli
|
||||||
|
* Andrey Fedoseev
|
||||||
|
* aparajita
|
||||||
|
* Tristan Escalada
|
||||||
|
* Alexander Koshelev
|
||||||
|
* Jaime Irurzun
|
||||||
|
* Alexandre González
|
||||||
|
* Thomas Steinacher
|
||||||
|
* Tommi Komulainen
|
||||||
|
* Peter Landry
|
||||||
|
* biszkoptwielki
|
||||||
|
* Anton Kolechkin
|
||||||
|
* Sergey Nikitin
|
||||||
|
* psychogenic
|
||||||
|
* Stefan Wójcik
|
||||||
|
* dimonb
|
||||||
|
* Garry Polley
|
||||||
|
* Adrian Scott
|
||||||
|
* Peter Teichman
|
||||||
|
* Jakub Kot
|
||||||
|
* Jorge Bastida
|
||||||
|
61
CONTRIBUTING.rst
Normal file
61
CONTRIBUTING.rst
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
Contributing to MongoEngine
|
||||||
|
===========================
|
||||||
|
|
||||||
|
MongoEngine has a large `community
|
||||||
|
<https://raw.github.com/MongoEngine/mongoengine/master/AUTHORS>`_ and
|
||||||
|
contributions are always encouraged. Contributions can be as simple as
|
||||||
|
minor tweaks to the documentation. Please read these guidelines before
|
||||||
|
sending a pull request.
|
||||||
|
|
||||||
|
Bugfixes and New Features
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Before starting to write code, look for existing `tickets
|
||||||
|
<https://github.com/MongoEngine/mongoengine/issues?state=open>`_ or `create one
|
||||||
|
<https://github.com/MongoEngine/mongoengine/issues>`_ for your specific
|
||||||
|
issue or feature request. That way you avoid working on something
|
||||||
|
that might not be of interest or that has already been addressed. If in doubt
|
||||||
|
post to the `user group <http://groups.google.com/group/mongoengine-users>`
|
||||||
|
|
||||||
|
Supported Interpreters
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
PyMongo supports CPython 2.5 and newer. Language
|
||||||
|
features not supported by all interpreters can not be used.
|
||||||
|
Please also ensure that your code is properly converted by
|
||||||
|
`2to3 <http://docs.python.org/library/2to3.html>`_ for Python 3 support.
|
||||||
|
|
||||||
|
Style Guide
|
||||||
|
-----------
|
||||||
|
|
||||||
|
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
|
||||||
|
including 4 space indents and 79 character line limits.
|
||||||
|
|
||||||
|
Testing
|
||||||
|
-------
|
||||||
|
|
||||||
|
All tests are run on `Travis <http://travis-ci.org/MongoEngine/mongoengine>`_
|
||||||
|
and any pull requests are automatically tested by Travis. Any pull requests
|
||||||
|
without tests will take longer to be integrated and might be refused.
|
||||||
|
|
||||||
|
General Guidelines
|
||||||
|
------------------
|
||||||
|
|
||||||
|
- Avoid backward breaking changes if at all possible.
|
||||||
|
- Write inline documentation for new classes and methods.
|
||||||
|
- Write tests and make sure they pass (make sure you have a mongod
|
||||||
|
running on the default port, then execute ``python setup.py test``
|
||||||
|
from the cmd line to run the test suite).
|
||||||
|
- Add yourself to AUTHORS.rst :)
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
To contribute to the `API documentation
|
||||||
|
<http://docs.mongoengine.org/en/latest/apireference.html>`_
|
||||||
|
just make your changes to the inline documentation of the appropriate
|
||||||
|
`source code <https://github.com/MongoEngine/mongoengine>`_ or `rst file
|
||||||
|
<https://github.com/MongoEngine/mongoengine/tree/master/docs>`_ in a
|
||||||
|
branch and submit a `pull request <https://help.github.com/articles/using-pull-requests>`_.
|
||||||
|
You might also use the github `Edit <https://github.com/blog/844-forking-with-the-edit-button>`_
|
||||||
|
button.
|
8
LICENSE
8
LICENSE
@@ -1,5 +1,5 @@
|
|||||||
Copyright (c) 2009-2010 Harry Marr
|
Copyright (c) 2009-2012 See AUTHORS
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person
|
Permission is hereby granted, free of charge, to any person
|
||||||
obtaining a copy of this software and associated documentation
|
obtaining a copy of this software and associated documentation
|
||||||
files (the "Software"), to deal in the Software without
|
files (the "Software"), to deal in the Software without
|
||||||
@@ -8,10 +8,10 @@ copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|||||||
copies of the Software, and to permit persons to whom the
|
copies of the Software, and to permit persons to whom the
|
||||||
Software is furnished to do so, subject to the following
|
Software is furnished to do so, subject to the following
|
||||||
conditions:
|
conditions:
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
The above copyright notice and this permission notice shall be
|
||||||
included in all copies or substantial portions of the Software.
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
21
README.rst
21
README.rst
@@ -2,15 +2,19 @@
|
|||||||
MongoEngine
|
MongoEngine
|
||||||
===========
|
===========
|
||||||
:Info: MongoEngine is an ORM-like layer on top of PyMongo.
|
:Info: MongoEngine is an ORM-like layer on top of PyMongo.
|
||||||
|
:Repository: https://github.com/MongoEngine/mongoengine
|
||||||
:Author: Harry Marr (http://github.com/hmarr)
|
:Author: Harry Marr (http://github.com/hmarr)
|
||||||
:Maintainer: Ross Lawley (http://github.com/rozza)
|
:Maintainer: Ross Lawley (http://github.com/rozza)
|
||||||
|
|
||||||
|
.. image:: https://secure.travis-ci.org/MongoEngine/mongoengine.png?branch=master
|
||||||
|
:target: http://travis-ci.org/MongoEngine/mongoengine
|
||||||
|
|
||||||
About
|
About
|
||||||
=====
|
=====
|
||||||
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
|
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
|
||||||
Documentation available at http://mongoengine-odm.rtfd.org - there is currently
|
Documentation available at http://mongoengine-odm.rtfd.org - there is currently
|
||||||
a `tutorial <http://readthedocs.org/docs/mongoengine-odm/en/latest/tutorial.html>`_, a `user guide
|
a `tutorial <http://readthedocs.org/docs/mongoengine-odm/en/latest/tutorial.html>`_, a `user guide
|
||||||
<http://readthedocs.org/docs/mongoengine-odm/en/latest/userguide.html>`_ and an `API reference
|
<https://mongoengine-odm.readthedocs.org/en/latest/guide/index.html>`_ and an `API reference
|
||||||
<http://readthedocs.org/docs/mongoengine-odm/en/latest/apireference.html>`_.
|
<http://readthedocs.org/docs/mongoengine-odm/en/latest/apireference.html>`_.
|
||||||
|
|
||||||
Installation
|
Installation
|
||||||
@@ -22,7 +26,7 @@ setup.py install``.
|
|||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
============
|
============
|
||||||
- pymongo 1.1+
|
- pymongo 2.1.1+
|
||||||
- sphinx (optional - for documentation generation)
|
- sphinx (optional - for documentation generation)
|
||||||
|
|
||||||
Examples
|
Examples
|
||||||
@@ -59,11 +63,6 @@ Some simple examples of what MongoEngine code looks like::
|
|||||||
... print 'Link:', post.url
|
... print 'Link:', post.url
|
||||||
... print
|
... print
|
||||||
...
|
...
|
||||||
=== Using MongoEngine ===
|
|
||||||
See the tutorial
|
|
||||||
|
|
||||||
=== MongoEngine Docs ===
|
|
||||||
Link: hmarr.com/mongoengine
|
|
||||||
|
|
||||||
>>> len(BlogPost.objects)
|
>>> len(BlogPost.objects)
|
||||||
2
|
2
|
||||||
@@ -81,7 +80,7 @@ Some simple examples of what MongoEngine code looks like::
|
|||||||
Tests
|
Tests
|
||||||
=====
|
=====
|
||||||
To run the test suite, ensure you are running a local instance of MongoDB on
|
To run the test suite, ensure you are running a local instance of MongoDB on
|
||||||
the standard port, and run ``python setup.py test``.
|
the standard port, and run: ``python setup.py test``.
|
||||||
|
|
||||||
Community
|
Community
|
||||||
=========
|
=========
|
||||||
@@ -89,10 +88,8 @@ Community
|
|||||||
<http://groups.google.com/group/mongoengine-users>`_
|
<http://groups.google.com/group/mongoengine-users>`_
|
||||||
- `MongoEngine Developers mailing list
|
- `MongoEngine Developers mailing list
|
||||||
<http://groups.google.com/group/mongoengine-dev>`_
|
<http://groups.google.com/group/mongoengine-dev>`_
|
||||||
- `#mongoengine IRC channel <irc://irc.freenode.net/mongoengine>`_
|
- `#mongoengine IRC channel <http://webchat.freenode.net/?channels=mongoengine>`_
|
||||||
|
|
||||||
Contributing
|
Contributing
|
||||||
============
|
============
|
||||||
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ - to
|
We welcome contributions! see the`Contribution guidelines <https://github.com/MongoEngine/mongoengine/blob/master/CONTRIBUTING.rst>`_
|
||||||
contribute to the project, fork it on GitHub and send a pull request, all
|
|
||||||
contributions and suggestions are welcome!
|
|
||||||
|
49
benchmark.py
49
benchmark.py
@@ -28,47 +28,64 @@ def main():
|
|||||||
|
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - Pymongo
|
Creating 10000 dictionaries - Pymongo
|
||||||
1.1141769886
|
3.86744189262
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine
|
Creating 10000 dictionaries - MongoEngine
|
||||||
2.37724113464
|
6.23374891281
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
||||||
1.92479610443
|
5.33027005196
|
||||||
|
----------------------------------------------------------------------------------------------------
|
||||||
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
|
||||||
|
pass - No Cascade
|
||||||
|
|
||||||
0.5.X
|
0.5.X
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - Pymongo
|
Creating 10000 dictionaries - Pymongo
|
||||||
1.10552310944
|
3.89597702026
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine
|
Creating 10000 dictionaries - MongoEngine
|
||||||
16.5169169903
|
21.7735359669
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
||||||
14.9446101189
|
19.8670389652
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
|
||||||
14.912801981
|
pass - No Cascade
|
||||||
----------------------------------------------------------------------------------------------------
|
|
||||||
Creating 10000 dictionaries - MongoEngine, force=True
|
|
||||||
14.9617750645
|
|
||||||
|
|
||||||
Performance
|
0.6.X
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - Pymongo
|
Creating 10000 dictionaries - Pymongo
|
||||||
1.10072994232
|
3.81559205055
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine
|
Creating 10000 dictionaries - MongoEngine
|
||||||
5.27341103554
|
10.0446798801
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
||||||
4.49365401268
|
9.51354718208
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
|
||||||
4.43459296227
|
9.02567505836
|
||||||
----------------------------------------------------------------------------------------------------
|
----------------------------------------------------------------------------------------------------
|
||||||
Creating 10000 dictionaries - MongoEngine, force=True
|
Creating 10000 dictionaries - MongoEngine, force=True
|
||||||
4.40114378929
|
8.44933390617
|
||||||
|
|
||||||
|
0.7.X
|
||||||
|
----------------------------------------------------------------------------------------------------
|
||||||
|
Creating 10000 dictionaries - Pymongo
|
||||||
|
3.78801012039
|
||||||
|
----------------------------------------------------------------------------------------------------
|
||||||
|
Creating 10000 dictionaries - MongoEngine
|
||||||
|
9.73050498962
|
||||||
|
----------------------------------------------------------------------------------------------------
|
||||||
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
|
||||||
|
8.33456707001
|
||||||
|
----------------------------------------------------------------------------------------------------
|
||||||
|
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
|
||||||
|
8.37778115273
|
||||||
|
----------------------------------------------------------------------------------------------------
|
||||||
|
Creating 10000 dictionaries - MongoEngine, force=True
|
||||||
|
8.36906409264
|
||||||
"""
|
"""
|
||||||
|
|
||||||
setup = """
|
setup = """
|
||||||
|
@@ -31,6 +31,9 @@ Documents
|
|||||||
.. autoclass:: mongoengine.document.MapReduceDocument
|
.. autoclass:: mongoengine.document.MapReduceDocument
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: mongoengine.ValidationError
|
||||||
|
:members:
|
||||||
|
|
||||||
Querying
|
Querying
|
||||||
========
|
========
|
||||||
|
|
||||||
@@ -44,25 +47,28 @@ Querying
|
|||||||
Fields
|
Fields
|
||||||
======
|
======
|
||||||
|
|
||||||
.. autoclass:: mongoengine.StringField
|
.. autoclass:: mongoengine.BinaryField
|
||||||
.. autoclass:: mongoengine.URLField
|
.. autoclass:: mongoengine.BooleanField
|
||||||
.. autoclass:: mongoengine.EmailField
|
|
||||||
.. autoclass:: mongoengine.IntField
|
|
||||||
.. autoclass:: mongoengine.FloatField
|
|
||||||
.. autoclass:: mongoengine.DecimalField
|
|
||||||
.. autoclass:: mongoengine.DateTimeField
|
|
||||||
.. autoclass:: mongoengine.ComplexDateTimeField
|
.. autoclass:: mongoengine.ComplexDateTimeField
|
||||||
.. autoclass:: mongoengine.ListField
|
.. autoclass:: mongoengine.DateTimeField
|
||||||
.. autoclass:: mongoengine.SortedListField
|
.. autoclass:: mongoengine.DecimalField
|
||||||
.. autoclass:: mongoengine.DictField
|
.. autoclass:: mongoengine.DictField
|
||||||
|
.. autoclass:: mongoengine.DynamicField
|
||||||
|
.. autoclass:: mongoengine.EmailField
|
||||||
|
.. autoclass:: mongoengine.EmbeddedDocumentField
|
||||||
|
.. autoclass:: mongoengine.FileField
|
||||||
|
.. autoclass:: mongoengine.FloatField
|
||||||
|
.. autoclass:: mongoengine.GenericEmbeddedDocumentField
|
||||||
|
.. autoclass:: mongoengine.GenericReferenceField
|
||||||
|
.. autoclass:: mongoengine.GeoPointField
|
||||||
|
.. autoclass:: mongoengine.ImageField
|
||||||
|
.. autoclass:: mongoengine.IntField
|
||||||
|
.. autoclass:: mongoengine.ListField
|
||||||
.. autoclass:: mongoengine.MapField
|
.. autoclass:: mongoengine.MapField
|
||||||
.. autoclass:: mongoengine.ObjectIdField
|
.. autoclass:: mongoengine.ObjectIdField
|
||||||
.. autoclass:: mongoengine.ReferenceField
|
.. autoclass:: mongoengine.ReferenceField
|
||||||
.. autoclass:: mongoengine.GenericReferenceField
|
|
||||||
.. autoclass:: mongoengine.EmbeddedDocumentField
|
|
||||||
.. autoclass:: mongoengine.GenericEmbeddedDocumentField
|
|
||||||
.. autoclass:: mongoengine.BooleanField
|
|
||||||
.. autoclass:: mongoengine.FileField
|
|
||||||
.. autoclass:: mongoengine.BinaryField
|
|
||||||
.. autoclass:: mongoengine.GeoPointField
|
|
||||||
.. autoclass:: mongoengine.SequenceField
|
.. autoclass:: mongoengine.SequenceField
|
||||||
|
.. autoclass:: mongoengine.SortedListField
|
||||||
|
.. autoclass:: mongoengine.StringField
|
||||||
|
.. autoclass:: mongoengine.URLField
|
||||||
|
.. autoclass:: mongoengine.UUIDField
|
||||||
|
@@ -2,11 +2,198 @@
|
|||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Changes in 0.6.X
|
Changes in 0.7.9
|
||||||
|
================
|
||||||
|
- Better fix handling for old style _types
|
||||||
|
- Embedded SequenceFields follow collection naming convention
|
||||||
|
|
||||||
|
Changes in 0.7.8
|
||||||
|
================
|
||||||
|
- Fix sequence fields in embedded documents (MongoEngine/mongoengine#166)
|
||||||
|
- Fix query chaining with .order_by() (MongoEngine/mongoengine#176)
|
||||||
|
- Added optional encoding and collection config for Django sessions (MongoEngine/mongoengine#180, MongoEngine/mongoengine#181, MongoEngine/mongoengine#183)
|
||||||
|
- Fixed EmailField so can add extra validation (MongoEngine/mongoengine#173, MongoEngine/mongoengine#174, MongoEngine/mongoengine#187)
|
||||||
|
- Fixed bulk inserts can now handle custom pk's (MongoEngine/mongoengine#192)
|
||||||
|
- Added as_pymongo method to return raw or cast results from pymongo (MongoEngine/mongoengine#193)
|
||||||
|
|
||||||
|
Changes in 0.7.7
|
||||||
|
================
|
||||||
|
- Fix handling for old style _types
|
||||||
|
|
||||||
|
Changes in 0.7.6
|
||||||
|
================
|
||||||
|
- Unicode fix for repr (MongoEngine/mongoengine#133)
|
||||||
|
- Allow updates with match operators (MongoEngine/mongoengine#144)
|
||||||
|
- Updated URLField - now can have a override the regex (MongoEngine/mongoengine#136)
|
||||||
|
- Allow Django AuthenticationBackends to work with Django user (hmarr/mongoengine#573)
|
||||||
|
- Fixed reload issue with ReferenceField where dbref=False (MongoEngine/mongoengine#138)
|
||||||
|
|
||||||
|
Changes in 0.7.5
|
||||||
|
================
|
||||||
|
- ReferenceFields with dbref=False use ObjectId instead of strings (MongoEngine/mongoengine#134)
|
||||||
|
See ticket for upgrade notes (https://github.com/MongoEngine/mongoengine/issues/134)
|
||||||
|
|
||||||
|
Changes in 0.7.4
|
||||||
|
================
|
||||||
|
- Fixed index inheritance issues - firmed up testcases (MongoEngine/mongoengine#123) (MongoEngine/mongoengine#125)
|
||||||
|
|
||||||
|
Changes in 0.7.3
|
||||||
|
================
|
||||||
|
- Reverted EmbeddedDocuments meta handling - now can turn off inheritance (MongoEngine/mongoengine#119)
|
||||||
|
|
||||||
|
Changes in 0.7.2
|
||||||
|
================
|
||||||
|
- Update index spec generation so its not destructive (MongoEngine/mongoengine#113)
|
||||||
|
|
||||||
|
Changes in 0.7.1
|
||||||
|
=================
|
||||||
|
- Fixed index spec inheritance (MongoEngine/mongoengine#111)
|
||||||
|
|
||||||
|
Changes in 0.7.0
|
||||||
|
=================
|
||||||
|
- Updated queryset.delete so you can use with skip / limit (MongoEngine/mongoengine#107)
|
||||||
|
- Updated index creation allows kwargs to be passed through refs (MongoEngine/mongoengine#104)
|
||||||
|
- Fixed Q object merge edge case (MongoEngine/mongoengine#109)
|
||||||
|
- Fixed reloading on sharded documents (hmarr/mongoengine#569)
|
||||||
|
- Added NotUniqueError for duplicate keys (MongoEngine/mongoengine#62)
|
||||||
|
- Added custom collection / sequence naming for SequenceFields (MongoEngine/mongoengine#92)
|
||||||
|
- Fixed UnboundLocalError in composite index with pk field (MongoEngine/mongoengine#88)
|
||||||
|
- Updated ReferenceField's to optionally store ObjectId strings
|
||||||
|
this will become the default in 0.8 (MongoEngine/mongoengine#89)
|
||||||
|
- Added FutureWarning - save will default to `cascade=False` in 0.8
|
||||||
|
- Added example of indexing embedded document fields (MongoEngine/mongoengine#75)
|
||||||
|
- Fixed ImageField resizing when forcing size (MongoEngine/mongoengine#80)
|
||||||
|
- Add flexibility for fields handling bad data (MongoEngine/mongoengine#78)
|
||||||
|
- Embedded Documents no longer handle meta definitions
|
||||||
|
- Use weakref proxies in base lists / dicts (MongoEngine/mongoengine#74)
|
||||||
|
- Improved queryset filtering (hmarr/mongoengine#554)
|
||||||
|
- Fixed Dynamic Documents and Embedded Documents (hmarr/mongoengine#561)
|
||||||
|
- Fixed abstract classes and shard keys (MongoEngine/mongoengine#64)
|
||||||
|
- Fixed Python 2.5 support
|
||||||
|
- Added Python 3 support (thanks to Laine Heron)
|
||||||
|
|
||||||
|
Changes in 0.6.20
|
||||||
|
=================
|
||||||
|
- Added support for distinct and db_alias (MongoEngine/mongoengine#59)
|
||||||
|
- Improved support for chained querysets when constraining the same fields (hmarr/mongoengine#554)
|
||||||
|
- Fixed BinaryField lookup re (MongoEngine/mongoengine#48)
|
||||||
|
|
||||||
|
Changes in 0.6.19
|
||||||
|
=================
|
||||||
|
|
||||||
|
- Added Binary support to UUID (MongoEngine/mongoengine#47)
|
||||||
|
- Fixed MapField lookup for fields without declared lookups (MongoEngine/mongoengine#46)
|
||||||
|
- Fixed BinaryField python value issue (MongoEngine/mongoengine#48)
|
||||||
|
- Fixed SequenceField non numeric value lookup (MongoEngine/mongoengine#41)
|
||||||
|
- Fixed queryset manager issue (MongoEngine/mongoengine#52)
|
||||||
|
- Fixed FileField comparision (hmarr/mongoengine#547)
|
||||||
|
|
||||||
|
Changes in 0.6.18
|
||||||
|
=================
|
||||||
|
- Fixed recursion loading bug in _get_changed_fields
|
||||||
|
|
||||||
|
Changes in 0.6.17
|
||||||
|
=================
|
||||||
|
- Fixed issue with custom queryset manager expecting explict variable names
|
||||||
|
|
||||||
|
Changes in 0.6.16
|
||||||
|
=================
|
||||||
|
- Fixed issue where db_alias wasn't inherited
|
||||||
|
|
||||||
|
Changes in 0.6.15
|
||||||
|
=================
|
||||||
|
- Updated validation error messages
|
||||||
|
- Added support for null / zero / false values in item_frequencies
|
||||||
|
- Fixed cascade save edge case
|
||||||
|
- Fixed geo index creation through reference fields
|
||||||
|
- Added support for args / kwargs when using @queryset_manager
|
||||||
|
- Deref list custom id fix
|
||||||
|
|
||||||
|
Changes in 0.6.14
|
||||||
|
=================
|
||||||
|
- Fixed error dict with nested validation
|
||||||
|
- Fixed Int/Float fields and not equals None
|
||||||
|
- Exclude tests from installation
|
||||||
|
- Allow tuples for index meta
|
||||||
|
- Fixed use of str in instance checks
|
||||||
|
- Fixed unicode support in transform update
|
||||||
|
- Added support for add_to_set and each
|
||||||
|
|
||||||
|
Changes in 0.6.13
|
||||||
|
=================
|
||||||
|
- Fixed EmbeddedDocument db_field validation issue
|
||||||
|
- Fixed StringField unicode issue
|
||||||
|
- Fixes __repr__ modifying the cursor
|
||||||
|
|
||||||
|
Changes in 0.6.12
|
||||||
|
=================
|
||||||
|
- Fixes scalar lookups for primary_key
|
||||||
|
- Fixes error with _delta handling DBRefs
|
||||||
|
|
||||||
|
Changes in 0.6.11
|
||||||
|
==================
|
||||||
|
- Fixed inconsistency handling None values field attrs
|
||||||
|
- Fixed map_field embedded db_field issue
|
||||||
|
- Fixed .save() _delta issue with DbRefs
|
||||||
|
- Fixed Django TestCase
|
||||||
|
- Added cmp to Embedded Document
|
||||||
|
- Added PULL reverse_delete_rule
|
||||||
|
- Fixed CASCADE delete bug
|
||||||
|
- Fixed db_field data load error
|
||||||
|
- Fixed recursive save with FileField
|
||||||
|
|
||||||
|
Changes in 0.6.10
|
||||||
|
=================
|
||||||
|
- Fixed basedict / baselist to return super(..)
|
||||||
|
- Promoted BaseDynamicField to DynamicField
|
||||||
|
|
||||||
|
Changes in 0.6.9
|
||||||
|
================
|
||||||
|
- Fixed sparse indexes on inherited docs
|
||||||
|
- Removed FileField auto deletion, needs more work maybe 0.7
|
||||||
|
|
||||||
|
Changes in 0.6.8
|
||||||
|
================
|
||||||
|
- Fixed FileField losing reference when no default set
|
||||||
|
- Removed possible race condition from FileField (grid_file)
|
||||||
|
- Added assignment to save, can now do: `b = MyDoc(**kwargs).save()`
|
||||||
|
- Added support for pull operations on nested EmbeddedDocuments
|
||||||
|
- Added support for choices with GenericReferenceFields
|
||||||
|
- Added support for choices with GenericEmbeddedDocumentFields
|
||||||
|
- Fixed Django 1.4 sessions first save data loss
|
||||||
|
- FileField now automatically delete files on .delete()
|
||||||
|
- Fix for GenericReference to_mongo method
|
||||||
|
- Fixed connection regression
|
||||||
|
- Updated Django User document, now allows inheritance
|
||||||
|
|
||||||
|
Changes in 0.6.7
|
||||||
|
================
|
||||||
|
- Fixed indexing on '_id' or 'pk' or 'id'
|
||||||
|
- Invalid data from the DB now raises a InvalidDocumentError
|
||||||
|
- Cleaned up the Validation Error - docs and code
|
||||||
|
- Added meta `auto_create_index` so you can disable index creation
|
||||||
|
- Added write concern options to inserts
|
||||||
|
- Fixed typo in meta for index options
|
||||||
|
- Bug fix Read preference now passed correctly
|
||||||
|
- Added support for File like objects for GridFS
|
||||||
|
- Fix for #473 - Dereferencing abstracts
|
||||||
|
|
||||||
|
Changes in 0.6.6
|
||||||
|
================
|
||||||
|
- Django 1.4 fixed (finally)
|
||||||
|
- Added tests for Django
|
||||||
|
|
||||||
|
Changes in 0.6.5
|
||||||
|
================
|
||||||
|
- More Django updates
|
||||||
|
|
||||||
|
Changes in 0.6.4
|
||||||
================
|
================
|
||||||
|
|
||||||
- updated replicasetconnection - pop port if exists
|
- Refactored connection / fixed replicasetconnection
|
||||||
- bug fix for unknown connection alias error message
|
- Bug fix for unknown connection alias error message
|
||||||
|
- Sessions support Django 1.3 and Django 1.4
|
||||||
|
- Minor fix for ReferenceField
|
||||||
|
|
||||||
Changes in 0.6.3
|
Changes in 0.6.3
|
||||||
================
|
================
|
||||||
|
@@ -62,28 +62,31 @@ not provided. Default values may optionally be a callable, which will be called
|
|||||||
to retrieve the value (such as in the above example). The field types available
|
to retrieve the value (such as in the above example). The field types available
|
||||||
are as follows:
|
are as follows:
|
||||||
|
|
||||||
* :class:`~mongoengine.StringField`
|
* :class:`~mongoengine.BinaryField`
|
||||||
* :class:`~mongoengine.URLField`
|
* :class:`~mongoengine.BooleanField`
|
||||||
* :class:`~mongoengine.EmailField`
|
|
||||||
* :class:`~mongoengine.IntField`
|
|
||||||
* :class:`~mongoengine.FloatField`
|
|
||||||
* :class:`~mongoengine.DecimalField`
|
|
||||||
* :class:`~mongoengine.DateTimeField`
|
|
||||||
* :class:`~mongoengine.ComplexDateTimeField`
|
* :class:`~mongoengine.ComplexDateTimeField`
|
||||||
* :class:`~mongoengine.ListField`
|
* :class:`~mongoengine.DateTimeField`
|
||||||
* :class:`~mongoengine.SortedListField`
|
* :class:`~mongoengine.DecimalField`
|
||||||
* :class:`~mongoengine.DictField`
|
* :class:`~mongoengine.DictField`
|
||||||
|
* :class:`~mongoengine.DynamicField`
|
||||||
|
* :class:`~mongoengine.EmailField`
|
||||||
|
* :class:`~mongoengine.EmbeddedDocumentField`
|
||||||
|
* :class:`~mongoengine.FileField`
|
||||||
|
* :class:`~mongoengine.FloatField`
|
||||||
|
* :class:`~mongoengine.GenericEmbeddedDocumentField`
|
||||||
|
* :class:`~mongoengine.GenericReferenceField`
|
||||||
|
* :class:`~mongoengine.GeoPointField`
|
||||||
|
* :class:`~mongoengine.ImageField`
|
||||||
|
* :class:`~mongoengine.IntField`
|
||||||
|
* :class:`~mongoengine.ListField`
|
||||||
* :class:`~mongoengine.MapField`
|
* :class:`~mongoengine.MapField`
|
||||||
* :class:`~mongoengine.ObjectIdField`
|
* :class:`~mongoengine.ObjectIdField`
|
||||||
* :class:`~mongoengine.ReferenceField`
|
* :class:`~mongoengine.ReferenceField`
|
||||||
* :class:`~mongoengine.GenericReferenceField`
|
|
||||||
* :class:`~mongoengine.EmbeddedDocumentField`
|
|
||||||
* :class:`~mongoengine.GenericEmbeddedDocumentField`
|
|
||||||
* :class:`~mongoengine.BooleanField`
|
|
||||||
* :class:`~mongoengine.FileField`
|
|
||||||
* :class:`~mongoengine.BinaryField`
|
|
||||||
* :class:`~mongoengine.GeoPointField`
|
|
||||||
* :class:`~mongoengine.SequenceField`
|
* :class:`~mongoengine.SequenceField`
|
||||||
|
* :class:`~mongoengine.SortedListField`
|
||||||
|
* :class:`~mongoengine.StringField`
|
||||||
|
* :class:`~mongoengine.URLField`
|
||||||
|
* :class:`~mongoengine.UUIDField`
|
||||||
|
|
||||||
Field arguments
|
Field arguments
|
||||||
---------------
|
---------------
|
||||||
@@ -98,7 +101,7 @@ arguments can be set on all fields:
|
|||||||
|
|
||||||
:attr:`required` (Default: False)
|
:attr:`required` (Default: False)
|
||||||
If set to True and the field is not set on the document instance, a
|
If set to True and the field is not set on the document instance, a
|
||||||
:class:`~mongoengine.base.ValidationError` will be raised when the document is
|
:class:`~mongoengine.ValidationError` will be raised when the document is
|
||||||
validated.
|
validated.
|
||||||
|
|
||||||
:attr:`default` (Default: None)
|
:attr:`default` (Default: None)
|
||||||
@@ -256,6 +259,35 @@ as the constructor's argument::
|
|||||||
content = StringField()
|
content = StringField()
|
||||||
|
|
||||||
|
|
||||||
|
.. _one-to-many-with-listfields:
|
||||||
|
|
||||||
|
One to Many with ListFields
|
||||||
|
'''''''''''''''''''''''''''
|
||||||
|
|
||||||
|
If you are implementing a one to many relationship via a list of references,
|
||||||
|
then the references are stored as DBRefs and to query you need to pass an
|
||||||
|
instance of the object to the query::
|
||||||
|
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Page(Document):
|
||||||
|
content = StringField()
|
||||||
|
authors = ListField(ReferenceField(User))
|
||||||
|
|
||||||
|
bob = User(name="Bob Jones").save()
|
||||||
|
john = User(name="John Smith").save()
|
||||||
|
|
||||||
|
Page(content="Test Page", authors=[bob, john]).save()
|
||||||
|
Page(content="Another Page", authors=[john]).save()
|
||||||
|
|
||||||
|
# Find all pages Bob authored
|
||||||
|
Page.objects(authors__in=[bob])
|
||||||
|
|
||||||
|
# Find all pages that both Bob and John have authored
|
||||||
|
Page.objects(authors__all=[bob, john])
|
||||||
|
|
||||||
|
|
||||||
Dealing with deletion of referred documents
|
Dealing with deletion of referred documents
|
||||||
'''''''''''''''''''''''''''''''''''''''''''
|
'''''''''''''''''''''''''''''''''''''''''''
|
||||||
By default, MongoDB doesn't check the integrity of your data, so deleting
|
By default, MongoDB doesn't check the integrity of your data, so deleting
|
||||||
@@ -289,6 +321,10 @@ Its value can take any of the following constants:
|
|||||||
:const:`mongoengine.CASCADE`
|
:const:`mongoengine.CASCADE`
|
||||||
Any object containing fields that are refererring to the object being deleted
|
Any object containing fields that are refererring to the object being deleted
|
||||||
are deleted first.
|
are deleted first.
|
||||||
|
:const:`mongoengine.PULL`
|
||||||
|
Removes the reference to the object (using MongoDB's "pull" operation)
|
||||||
|
from any object's fields of
|
||||||
|
:class:`~mongoengine.ListField` (:class:`~mongoengine.ReferenceField`).
|
||||||
|
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
@@ -308,6 +344,10 @@ Its value can take any of the following constants:
|
|||||||
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
|
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
|
||||||
|
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
Signals are not triggered when doing cascading updates / deletes - if this
|
||||||
|
is required you must manually handle the update / delete.
|
||||||
|
|
||||||
Generic reference fields
|
Generic reference fields
|
||||||
''''''''''''''''''''''''
|
''''''''''''''''''''''''
|
||||||
A second kind of reference field also exists,
|
A second kind of reference field also exists,
|
||||||
@@ -429,13 +469,18 @@ If a dictionary is passed then the following options are available:
|
|||||||
Whether the index should be sparse.
|
Whether the index should be sparse.
|
||||||
|
|
||||||
:attr:`unique` (Default: False)
|
:attr:`unique` (Default: False)
|
||||||
Whether the index should be sparse.
|
Whether the index should be unique.
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
|
||||||
|
To index embedded files / dictionary fields use 'dot' notation eg:
|
||||||
|
`rank.title`
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
|
Inheritance adds extra indices.
|
||||||
Inheritance adds extra indices.
|
If don't need inheritance for a document turn inheritance off -
|
||||||
If don't need inheritance for a document turn inheritance off - see :ref:`document-inheritance`.
|
see :ref:`document-inheritance`.
|
||||||
|
|
||||||
|
|
||||||
Geospatial indexes
|
Geospatial indexes
|
||||||
|
@@ -91,5 +91,5 @@ is an alias to :attr:`id`::
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you define your own primary key field, the field implicitly becomes
|
If you define your own primary key field, the field implicitly becomes
|
||||||
required, so a :class:`ValidationError` will be thrown if you don't provide
|
required, so a :class:`~mongoengine.ValidationError` will be thrown if
|
||||||
it.
|
you don't provide it.
|
||||||
|
@@ -65,7 +65,7 @@ Deleting stored files is achieved with the :func:`delete` method::
|
|||||||
|
|
||||||
marmot.photo.delete()
|
marmot.photo.delete()
|
||||||
|
|
||||||
.. note::
|
.. warning::
|
||||||
|
|
||||||
The FileField in a Document actually only stores the ID of a file in a
|
The FileField in a Document actually only stores the ID of a file in a
|
||||||
separate GridFS collection. This means that deleting a document
|
separate GridFS collection. This means that deleting a document
|
||||||
|
@@ -232,7 +232,7 @@ custom manager methods as you like::
|
|||||||
BlogPost(title='test1', published=False).save()
|
BlogPost(title='test1', published=False).save()
|
||||||
BlogPost(title='test2', published=True).save()
|
BlogPost(title='test2', published=True).save()
|
||||||
assert len(BlogPost.objects) == 2
|
assert len(BlogPost.objects) == 2
|
||||||
assert len(BlogPost.live_posts) == 1
|
assert len(BlogPost.live_posts()) == 1
|
||||||
|
|
||||||
Custom QuerySets
|
Custom QuerySets
|
||||||
================
|
================
|
||||||
@@ -243,11 +243,16 @@ a document, set ``queryset_class`` to the custom class in a
|
|||||||
:class:`~mongoengine.Document`\ s ``meta`` dictionary::
|
:class:`~mongoengine.Document`\ s ``meta`` dictionary::
|
||||||
|
|
||||||
class AwesomerQuerySet(QuerySet):
|
class AwesomerQuerySet(QuerySet):
|
||||||
pass
|
|
||||||
|
def get_awesome(self):
|
||||||
|
return self.filter(awesome=True)
|
||||||
|
|
||||||
class Page(Document):
|
class Page(Document):
|
||||||
meta = {'queryset_class': AwesomerQuerySet}
|
meta = {'queryset_class': AwesomerQuerySet}
|
||||||
|
|
||||||
|
# To call:
|
||||||
|
Page.objects.get_awesome()
|
||||||
|
|
||||||
.. versionadded:: 0.4
|
.. versionadded:: 0.4
|
||||||
|
|
||||||
Aggregation
|
Aggregation
|
||||||
|
@@ -50,4 +50,11 @@ Example usage::
|
|||||||
signals.post_save.connect(Author.post_save, sender=Author)
|
signals.post_save.connect(Author.post_save, sender=Author)
|
||||||
|
|
||||||
|
|
||||||
|
ReferenceFields and signals
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Currently `reverse_delete_rules` do not trigger signals on the other part of
|
||||||
|
the relationship. If this is required you must manually handled the
|
||||||
|
reverse deletion.
|
||||||
|
|
||||||
.. _blinker: http://pypi.python.org/pypi/blinker
|
.. _blinker: http://pypi.python.org/pypi/blinker
|
||||||
|
@@ -34,10 +34,10 @@ To get help with using MongoEngine, use the `MongoEngine Users mailing list
|
|||||||
Contributing
|
Contributing
|
||||||
------------
|
------------
|
||||||
|
|
||||||
The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_ and
|
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
|
||||||
contributions are always encouraged. Contributions can be as simple as
|
contributions are always encouraged. Contributions can be as simple as
|
||||||
minor tweaks to this documentation. To contribute, fork the project on
|
minor tweaks to this documentation. To contribute, fork the project on
|
||||||
`GitHub <http://github.com/hmarr/mongoengine>`_ and send a
|
`GitHub <http://github.com/MongoEngine/mongoengine>`_ and send a
|
||||||
pull request.
|
pull request.
|
||||||
|
|
||||||
Also, you can join the developers' `mailing list
|
Also, you can join the developers' `mailing list
|
||||||
|
@@ -2,18 +2,86 @@
|
|||||||
Upgrading
|
Upgrading
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.6 to 0.7
|
||||||
|
==========
|
||||||
|
|
||||||
|
Cascade saves
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Saves will raise a `FutureWarning` if they cascade and cascade hasn't been set
|
||||||
|
to True. This is because in 0.8 it will default to False. If you require
|
||||||
|
cascading saves then either set it in the `meta` or pass
|
||||||
|
via `save` eg ::
|
||||||
|
|
||||||
|
# At the class level:
|
||||||
|
class Person(Document):
|
||||||
|
meta = {'cascade': True}
|
||||||
|
|
||||||
|
# Or in code:
|
||||||
|
my_document.save(cascade=True)
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
Remember: cascading saves **do not** cascade through lists.
|
||||||
|
|
||||||
|
ReferenceFields
|
||||||
|
---------------
|
||||||
|
|
||||||
|
ReferenceFields now can store references as ObjectId strings instead of DBRefs.
|
||||||
|
This will become the default in 0.8 and if `dbref` is not set a `FutureWarning`
|
||||||
|
will be raised.
|
||||||
|
|
||||||
|
|
||||||
|
To explicitly continue to use DBRefs change the `dbref` flag
|
||||||
|
to True ::
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
groups = ListField(ReferenceField(Group, dbref=True))
|
||||||
|
|
||||||
|
To migrate to using strings instead of DBRefs you will have to manually
|
||||||
|
migrate ::
|
||||||
|
|
||||||
|
# Step 1 - Migrate the model definition
|
||||||
|
class Group(Document):
|
||||||
|
author = ReferenceField(User, dbref=False)
|
||||||
|
members = ListField(ReferenceField(User, dbref=False))
|
||||||
|
|
||||||
|
# Step 2 - Migrate the data
|
||||||
|
for g in Group.objects():
|
||||||
|
g.author = g.author
|
||||||
|
g.members = g.members
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
|
||||||
|
item_frequencies
|
||||||
|
----------------
|
||||||
|
|
||||||
|
In the 0.6 series we added support for null / zero / false values in
|
||||||
|
item_frequencies. A side effect was to return keys in the value they are
|
||||||
|
stored in rather than as string representations. Your code may need to be
|
||||||
|
updated to handle native types rather than strings keys for the results of
|
||||||
|
item frequency queries.
|
||||||
|
|
||||||
|
BinaryFields
|
||||||
|
------------
|
||||||
|
|
||||||
|
Binary fields have been updated so that they are native binary types. If you
|
||||||
|
previously were doing `str` comparisons with binary field values you will have
|
||||||
|
to update and wrap the value in a `str`.
|
||||||
|
|
||||||
0.5 to 0.6
|
0.5 to 0.6
|
||||||
==========
|
==========
|
||||||
|
|
||||||
Embedded Documents - if you had a `pk` field you will have to rename it from `_id`
|
Embedded Documents - if you had a `pk` field you will have to rename it from
|
||||||
to `pk` as pk is no longer a property of Embedded Documents.
|
`_id` to `pk` as pk is no longer a property of Embedded Documents.
|
||||||
|
|
||||||
Reverse Delete Rules in Embedded Documents, MapFields and DictFields now throw
|
Reverse Delete Rules in Embedded Documents, MapFields and DictFields now throw
|
||||||
an InvalidDocument error as they aren't currently supported.
|
an InvalidDocument error as they aren't currently supported.
|
||||||
|
|
||||||
Document._get_subclasses - Is no longer used and the class method has been removed.
|
Document._get_subclasses - Is no longer used and the class method has been
|
||||||
|
removed.
|
||||||
|
|
||||||
Document.objects.with_id - now raises an InvalidQueryError if used with a filter.
|
Document.objects.with_id - now raises an InvalidQueryError if used with a
|
||||||
|
filter.
|
||||||
|
|
||||||
FutureWarning - A future warning has been added to all inherited classes that
|
FutureWarning - A future warning has been added to all inherited classes that
|
||||||
don't define `allow_inheritance` in their meta.
|
don't define `allow_inheritance` in their meta.
|
||||||
@@ -37,11 +105,11 @@ human-readable name for the option.
|
|||||||
PyMongo / MongoDB
|
PyMongo / MongoDB
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
map reduce now requires pymongo 1.11+- The pymongo merge_output and reduce_output
|
map reduce now requires pymongo 1.11+- The pymongo `merge_output` and
|
||||||
parameters, have been depreciated.
|
`reduce_output` parameters, have been depreciated.
|
||||||
|
|
||||||
More methods now use map_reduce as db.eval is not supported for sharding as such
|
More methods now use map_reduce as db.eval is not supported for sharding as
|
||||||
the following have been changed:
|
such the following have been changed:
|
||||||
|
|
||||||
* :meth:`~mongoengine.queryset.QuerySet.sum`
|
* :meth:`~mongoengine.queryset.QuerySet.sum`
|
||||||
* :meth:`~mongoengine.queryset.QuerySet.average`
|
* :meth:`~mongoengine.queryset.QuerySet.average`
|
||||||
@@ -51,8 +119,8 @@ the following have been changed:
|
|||||||
Default collection naming
|
Default collection naming
|
||||||
-------------------------
|
-------------------------
|
||||||
|
|
||||||
Previously it was just lowercase, its now much more pythonic and readable as its
|
Previously it was just lowercase, its now much more pythonic and readable as
|
||||||
lowercase and underscores, previously ::
|
its lowercase and underscores, previously ::
|
||||||
|
|
||||||
class MyAceDocument(Document):
|
class MyAceDocument(Document):
|
||||||
pass
|
pass
|
||||||
@@ -88,7 +156,8 @@ Alternatively, you can rename your collections eg ::
|
|||||||
|
|
||||||
failure = False
|
failure = False
|
||||||
|
|
||||||
collection_names = [d._get_collection_name() for d in _document_registry.values()]
|
collection_names = [d._get_collection_name()
|
||||||
|
for d in _document_registry.values()]
|
||||||
|
|
||||||
for new_style_name in collection_names:
|
for new_style_name in collection_names:
|
||||||
if not new_style_name: # embedded documents don't have collections
|
if not new_style_name: # embedded documents don't have collections
|
||||||
@@ -106,7 +175,8 @@ Alternatively, you can rename your collections eg ::
|
|||||||
old_style_name, new_style_name)
|
old_style_name, new_style_name)
|
||||||
else:
|
else:
|
||||||
db[old_style_name].rename(new_style_name)
|
db[old_style_name].rename(new_style_name)
|
||||||
print "Renamed: %s to %s" % (old_style_name, new_style_name)
|
print "Renamed: %s to %s" % (old_style_name,
|
||||||
|
new_style_name)
|
||||||
|
|
||||||
if failure:
|
if failure:
|
||||||
print "Upgrading collection names failed"
|
print "Upgrading collection names failed"
|
||||||
|
@@ -12,13 +12,12 @@ from signals import *
|
|||||||
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
|
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
|
||||||
queryset.__all__ + signals.__all__)
|
queryset.__all__ + signals.__all__)
|
||||||
|
|
||||||
VERSION = (0, 6, 3)
|
VERSION = (0, 7, 9)
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
version = '%s.%s' % (VERSION[0], VERSION[1])
|
if isinstance(VERSION[-1], basestring):
|
||||||
if VERSION[2]:
|
return '.'.join(map(str, VERSION[:-1])) + VERSION[-1]
|
||||||
version = '%s.%s' % (version, VERSION[2])
|
return '.'.join(map(str, VERSION))
|
||||||
return version
|
|
||||||
|
|
||||||
__version__ = get_version()
|
__version__ = get_version()
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -39,22 +39,7 @@ def register_connection(alias, name, host='localhost', port=27017,
|
|||||||
"""
|
"""
|
||||||
global _connection_settings
|
global _connection_settings
|
||||||
|
|
||||||
# Handle uri style connections
|
conn_settings = {
|
||||||
if "://" in host:
|
|
||||||
uri_dict = uri_parser.parse_uri(host)
|
|
||||||
if uri_dict.get('database') is None:
|
|
||||||
raise ConnectionError("If using URI style connection include "\
|
|
||||||
"database name in string")
|
|
||||||
_connection_settings[alias] = {
|
|
||||||
'host': host,
|
|
||||||
'name': uri_dict.get('database'),
|
|
||||||
'username': uri_dict.get('username'),
|
|
||||||
'password': uri_dict.get('password')
|
|
||||||
}
|
|
||||||
_connection_settings[alias].update(kwargs)
|
|
||||||
return
|
|
||||||
|
|
||||||
_connection_settings[alias] = {
|
|
||||||
'name': name,
|
'name': name,
|
||||||
'host': host,
|
'host': host,
|
||||||
'port': port,
|
'port': port,
|
||||||
@@ -64,7 +49,25 @@ def register_connection(alias, name, host='localhost', port=27017,
|
|||||||
'password': password,
|
'password': password,
|
||||||
'read_preference': read_preference
|
'read_preference': read_preference
|
||||||
}
|
}
|
||||||
_connection_settings[alias].update(kwargs)
|
|
||||||
|
# Handle uri style connections
|
||||||
|
if "://" in host:
|
||||||
|
uri_dict = uri_parser.parse_uri(host)
|
||||||
|
if uri_dict.get('database') is None:
|
||||||
|
raise ConnectionError("If using URI style connection include "\
|
||||||
|
"database name in string")
|
||||||
|
conn_settings.update({
|
||||||
|
'host': host,
|
||||||
|
'name': uri_dict.get('database'),
|
||||||
|
'username': uri_dict.get('username'),
|
||||||
|
'password': uri_dict.get('password'),
|
||||||
|
'read_preference': read_preference,
|
||||||
|
})
|
||||||
|
if "replicaSet" in host:
|
||||||
|
conn_settings['replicaSet'] = True
|
||||||
|
|
||||||
|
conn_settings.update(kwargs)
|
||||||
|
_connection_settings[alias] = conn_settings
|
||||||
|
|
||||||
|
|
||||||
def disconnect(alias=DEFAULT_CONNECTION_NAME):
|
def disconnect(alias=DEFAULT_CONNECTION_NAME):
|
||||||
@@ -112,7 +115,11 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
|
|||||||
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
|
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
|
||||||
# Discard port since it can't be used on ReplicaSetConnection
|
# Discard port since it can't be used on ReplicaSetConnection
|
||||||
conn_settings.pop('port', None)
|
conn_settings.pop('port', None)
|
||||||
|
# Discard replicaSet if not base string
|
||||||
|
if not isinstance(conn_settings['replicaSet'], basestring):
|
||||||
|
conn_settings.pop('replicaSet', None)
|
||||||
connection_class = ReplicaSetConnection
|
connection_class = ReplicaSetConnection
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_connections[alias] = connection_class(**conn_settings)
|
_connections[alias] = connection_class(**conn_settings)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
|
@@ -31,15 +31,34 @@ class DeReference(object):
|
|||||||
items = [i for i in items]
|
items = [i for i in items]
|
||||||
|
|
||||||
self.max_depth = max_depth
|
self.max_depth = max_depth
|
||||||
|
|
||||||
doc_type = None
|
doc_type = None
|
||||||
|
|
||||||
if instance and instance._fields:
|
if instance and instance._fields:
|
||||||
doc_type = instance._fields[name].field
|
doc_type = instance._fields.get(name)
|
||||||
|
if hasattr(doc_type, 'field'):
|
||||||
|
doc_type = doc_type.field
|
||||||
|
|
||||||
if isinstance(doc_type, ReferenceField):
|
if isinstance(doc_type, ReferenceField):
|
||||||
|
field = doc_type
|
||||||
doc_type = doc_type.document_type
|
doc_type = doc_type.document_type
|
||||||
if all([i.__class__ == doc_type for i in items]):
|
is_list = not hasattr(items, 'items')
|
||||||
|
|
||||||
|
if is_list and all([i.__class__ == doc_type for i in items]):
|
||||||
return items
|
return items
|
||||||
|
elif not is_list and all([i.__class__ == doc_type
|
||||||
|
for i in items.values()]):
|
||||||
|
return items
|
||||||
|
elif not field.dbref:
|
||||||
|
if not hasattr(items, 'items'):
|
||||||
|
items = [field.to_python(v)
|
||||||
|
if not isinstance(v, (DBRef, Document)) else v
|
||||||
|
for v in items]
|
||||||
|
else:
|
||||||
|
items = dict([
|
||||||
|
(k, field.to_python(v))
|
||||||
|
if not isinstance(v, (DBRef, Document)) else (k, v)
|
||||||
|
for k, v in items.iteritems()]
|
||||||
|
)
|
||||||
|
|
||||||
self.reference_map = self._find_references(items)
|
self.reference_map = self._find_references(items)
|
||||||
self.object_map = self._fetch_objects(doc_type=doc_type)
|
self.object_map = self._fetch_objects(doc_type=doc_type)
|
||||||
@@ -112,6 +131,10 @@ class DeReference(object):
|
|||||||
for ref in references:
|
for ref in references:
|
||||||
if '_cls' in ref:
|
if '_cls' in ref:
|
||||||
doc = get_document(ref["_cls"])._from_son(ref)
|
doc = get_document(ref["_cls"])._from_son(ref)
|
||||||
|
elif doc_type is None:
|
||||||
|
doc = get_document(
|
||||||
|
''.join(x.capitalize()
|
||||||
|
for x in col.split('_')))._from_son(ref)
|
||||||
else:
|
else:
|
||||||
doc = doc_type._from_son(ref)
|
doc = doc_type._from_son(ref)
|
||||||
object_map[doc.id] = doc
|
object_map[doc.id] = doc
|
||||||
@@ -143,7 +166,7 @@ class DeReference(object):
|
|||||||
return self.object_map.get(items['_ref'].id, items)
|
return self.object_map.get(items['_ref'].id, items)
|
||||||
elif '_types' in items and '_cls' in items:
|
elif '_types' in items and '_cls' in items:
|
||||||
doc = get_document(items['_cls'])._from_son(items)
|
doc = get_document(items['_cls'])._from_son(items)
|
||||||
doc._data = self._attach_objects(doc._data, depth, doc, name)
|
doc._data = self._attach_objects(doc._data, depth, doc, None)
|
||||||
return doc
|
return doc
|
||||||
|
|
||||||
if not hasattr(items, 'items'):
|
if not hasattr(items, 'items'):
|
||||||
@@ -162,7 +185,7 @@ class DeReference(object):
|
|||||||
else:
|
else:
|
||||||
data[k] = v
|
data[k] = v
|
||||||
|
|
||||||
if k in self.object_map:
|
if k in self.object_map and not is_list:
|
||||||
data[k] = self.object_map[k]
|
data[k] = self.object_map[k]
|
||||||
elif hasattr(v, '_fields'):
|
elif hasattr(v, '_fields'):
|
||||||
for field_name, field in v._fields.iteritems():
|
for field_name, field in v._fields.iteritems():
|
||||||
|
@@ -1,11 +1,38 @@
|
|||||||
|
import datetime
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
|
|
||||||
from django.utils.encoding import smart_str
|
from django.utils.encoding import smart_str
|
||||||
|
from django.contrib.auth.models import _user_get_all_permissions
|
||||||
|
from django.contrib.auth.models import _user_has_perm
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.contrib.auth.hashers import check_password, make_password
|
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
import datetime
|
try:
|
||||||
|
from django.contrib.auth.hashers import check_password, make_password
|
||||||
|
except ImportError:
|
||||||
|
"""Handle older versions of Django"""
|
||||||
|
from django.utils.hashcompat import md5_constructor, sha_constructor
|
||||||
|
|
||||||
|
def get_hexdigest(algorithm, salt, raw_password):
|
||||||
|
raw_password, salt = smart_str(raw_password), smart_str(salt)
|
||||||
|
if algorithm == 'md5':
|
||||||
|
return md5_constructor(salt + raw_password).hexdigest()
|
||||||
|
elif algorithm == 'sha1':
|
||||||
|
return sha_constructor(salt + raw_password).hexdigest()
|
||||||
|
raise ValueError('Got unknown password algorithm type in password')
|
||||||
|
|
||||||
|
def check_password(raw_password, password):
|
||||||
|
algo, salt, hash = password.split('$')
|
||||||
|
return hash == get_hexdigest(algo, salt, raw_password)
|
||||||
|
|
||||||
|
def make_password(raw_password):
|
||||||
|
from random import random
|
||||||
|
algo = 'sha1'
|
||||||
|
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
|
||||||
|
hash = get_hexdigest(algo, salt, raw_password)
|
||||||
|
return '%s$%s$%s' % (algo, salt, hash)
|
||||||
|
|
||||||
|
|
||||||
REDIRECT_FIELD_NAME = 'next'
|
REDIRECT_FIELD_NAME = 'next'
|
||||||
|
|
||||||
@@ -41,6 +68,7 @@ class User(Document):
|
|||||||
verbose_name=_('date joined'))
|
verbose_name=_('date joined'))
|
||||||
|
|
||||||
meta = {
|
meta = {
|
||||||
|
'allow_inheritance': True,
|
||||||
'indexes': [
|
'indexes': [
|
||||||
{'fields': ['username'], 'unique': True}
|
{'fields': ['username'], 'unique': True}
|
||||||
]
|
]
|
||||||
@@ -78,6 +106,25 @@ class User(Document):
|
|||||||
"""
|
"""
|
||||||
return check_password(raw_password, self.password)
|
return check_password(raw_password, self.password)
|
||||||
|
|
||||||
|
def get_all_permissions(self, obj=None):
|
||||||
|
return _user_get_all_permissions(self, obj)
|
||||||
|
|
||||||
|
def has_perm(self, perm, obj=None):
|
||||||
|
"""
|
||||||
|
Returns True if the user has the specified permission. This method
|
||||||
|
queries all available auth backends, but returns immediately if any
|
||||||
|
backend returns True. Thus, a user who has permission from a single
|
||||||
|
auth backend is assumed to have permission in general. If an object is
|
||||||
|
provided, permissions for this specific object are checked.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Active superusers have all permissions.
|
||||||
|
if self.is_active and self.is_superuser:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Otherwise we need to check the backends.
|
||||||
|
return _user_has_perm(self, perm, obj)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_user(cls, username, password, email=None):
|
def create_user(cls, username, password, email=None):
|
||||||
"""Create (and save) a new user with the given username, password and
|
"""Create (and save) a new user with the given username, password and
|
||||||
|
@@ -1,3 +1,6 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.sessions.backends.base import SessionBase, CreateError
|
from django.contrib.sessions.backends.base import SessionBase, CreateError
|
||||||
from django.core.exceptions import SuspiciousOperation
|
from django.core.exceptions import SuspiciousOperation
|
||||||
from django.utils.encoding import force_unicode
|
from django.utils.encoding import force_unicode
|
||||||
@@ -6,19 +9,29 @@ from mongoengine.document import Document
|
|||||||
from mongoengine import fields
|
from mongoengine import fields
|
||||||
from mongoengine.queryset import OperationError
|
from mongoengine.queryset import OperationError
|
||||||
from mongoengine.connection import DEFAULT_CONNECTION_NAME
|
from mongoengine.connection import DEFAULT_CONNECTION_NAME
|
||||||
from django.conf import settings
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
MONGOENGINE_SESSION_DB_ALIAS = getattr(
|
MONGOENGINE_SESSION_DB_ALIAS = getattr(
|
||||||
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
|
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
|
||||||
DEFAULT_CONNECTION_NAME)
|
DEFAULT_CONNECTION_NAME)
|
||||||
|
|
||||||
|
# a setting for the name of the collection used to store sessions
|
||||||
|
MONGOENGINE_SESSION_COLLECTION = getattr(
|
||||||
|
settings, 'MONGOENGINE_SESSION_COLLECTION',
|
||||||
|
'django_session')
|
||||||
|
|
||||||
|
# a setting for whether session data is stored encoded or not
|
||||||
|
MONGOENGINE_SESSION_DATA_ENCODE = getattr(
|
||||||
|
settings, 'MONGOENGINE_SESSION_DATA_ENCODE',
|
||||||
|
True)
|
||||||
|
|
||||||
class MongoSession(Document):
|
class MongoSession(Document):
|
||||||
session_key = fields.StringField(primary_key=True, max_length=40)
|
session_key = fields.StringField(primary_key=True, max_length=40)
|
||||||
session_data = fields.StringField()
|
session_data = fields.StringField() if MONGOENGINE_SESSION_DATA_ENCODE \
|
||||||
|
else fields.DictField()
|
||||||
expire_date = fields.DateTimeField()
|
expire_date = fields.DateTimeField()
|
||||||
|
|
||||||
meta = {'collection': 'django_session',
|
meta = {'collection': MONGOENGINE_SESSION_COLLECTION,
|
||||||
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
|
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
|
||||||
'allow_inheritance': False}
|
'allow_inheritance': False}
|
||||||
|
|
||||||
@@ -31,7 +44,10 @@ class SessionStore(SessionBase):
|
|||||||
try:
|
try:
|
||||||
s = MongoSession.objects(session_key=self.session_key,
|
s = MongoSession.objects(session_key=self.session_key,
|
||||||
expire_date__gt=datetime.now())[0]
|
expire_date__gt=datetime.now())[0]
|
||||||
return self.decode(force_unicode(s.session_data))
|
if MONGOENGINE_SESSION_DATA_ENCODE:
|
||||||
|
return self.decode(force_unicode(s.session_data))
|
||||||
|
else:
|
||||||
|
return s.session_data
|
||||||
except (IndexError, SuspiciousOperation):
|
except (IndexError, SuspiciousOperation):
|
||||||
self.create()
|
self.create()
|
||||||
return {}
|
return {}
|
||||||
@@ -51,10 +67,13 @@ class SessionStore(SessionBase):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def save(self, must_create=False):
|
def save(self, must_create=False):
|
||||||
if self._session_key is None:
|
if self.session_key is None:
|
||||||
self.create()
|
self._session_key = self._get_new_session_key()
|
||||||
s = MongoSession(session_key=self._session_key)
|
s = MongoSession(session_key=self.session_key)
|
||||||
s.session_data = self.encode(self._get_session(no_load=must_create))
|
if MONGOENGINE_SESSION_DATA_ENCODE:
|
||||||
|
s.session_data = self.encode(self._get_session(no_load=must_create))
|
||||||
|
else:
|
||||||
|
s.session_data = self._get_session(no_load=must_create)
|
||||||
s.expire_date = self.get_expiry_date()
|
s.expire_date = self.get_expiry_date()
|
||||||
try:
|
try:
|
||||||
s.save(force_insert=must_create, safe=True)
|
s.save(force_insert=must_create, safe=True)
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
from django.http import Http404
|
|
||||||
from mongoengine.queryset import QuerySet
|
from mongoengine.queryset import QuerySet
|
||||||
from mongoengine.base import BaseDocument
|
from mongoengine.base import BaseDocument
|
||||||
from mongoengine.base import ValidationError
|
from mongoengine.base import ValidationError
|
||||||
@@ -27,6 +26,7 @@ def get_document_or_404(cls, *args, **kwargs):
|
|||||||
try:
|
try:
|
||||||
return queryset.get(*args, **kwargs)
|
return queryset.get(*args, **kwargs)
|
||||||
except (queryset._document.DoesNotExist, ValidationError):
|
except (queryset._document.DoesNotExist, ValidationError):
|
||||||
|
from django.http import Http404
|
||||||
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
||||||
|
|
||||||
def get_list_or_404(cls, *args, **kwargs):
|
def get_list_or_404(cls, *args, **kwargs):
|
||||||
@@ -42,5 +42,6 @@ def get_list_or_404(cls, *args, **kwargs):
|
|||||||
queryset = _get_queryset(cls)
|
queryset = _get_queryset(cls)
|
||||||
obj_list = list(queryset.filter(*args, **kwargs))
|
obj_list = list(queryset.filter(*args, **kwargs))
|
||||||
if not obj_list:
|
if not obj_list:
|
||||||
|
from django.http import Http404
|
||||||
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
raise Http404('No %s matches the given query.' % queryset._document._class_name)
|
||||||
return obj_list
|
return obj_list
|
||||||
|
@@ -1,16 +1,34 @@
|
|||||||
#coding: utf-8
|
#coding: utf-8
|
||||||
from django.test import TestCase
|
from nose.plugins.skip import SkipTest
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
from mongoengine.python_support import PY3
|
||||||
from mongoengine import connect
|
from mongoengine import connect
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.conf import settings
|
||||||
|
except Exception as err:
|
||||||
|
if PY3:
|
||||||
|
from unittest import TestCase
|
||||||
|
# Dummy value so no error
|
||||||
|
class settings:
|
||||||
|
MONGO_DATABASE_NAME = 'dummy'
|
||||||
|
else:
|
||||||
|
raise err
|
||||||
|
|
||||||
|
|
||||||
class MongoTestCase(TestCase):
|
class MongoTestCase(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if PY3:
|
||||||
|
raise SkipTest('django does not have Python 3 support')
|
||||||
|
|
||||||
"""
|
"""
|
||||||
TestCase class that clear the collection between the tests
|
TestCase class that clear the collection between the tests
|
||||||
"""
|
"""
|
||||||
db_name = 'test_%s' % settings.MONGO_DATABASE_NAME
|
db_name = 'test_%s' % settings.MONGO_DATABASE_NAME
|
||||||
def __init__(self, methodName='runtest'):
|
def __init__(self, methodName='runtest'):
|
||||||
self.db = connect(self.db_name)
|
self.db = connect(self.db_name).get_db()
|
||||||
super(MongoTestCase, self).__init__(methodName)
|
super(MongoTestCase, self).__init__(methodName)
|
||||||
|
|
||||||
def _post_teardown(self):
|
def _post_teardown(self):
|
||||||
|
@@ -1,14 +1,19 @@
|
|||||||
import pymongo
|
import warnings
|
||||||
from bson.dbref import DBRef
|
|
||||||
|
import pymongo
|
||||||
|
import re
|
||||||
|
|
||||||
|
from bson.dbref import DBRef
|
||||||
|
from mongoengine import signals, queryset
|
||||||
|
|
||||||
from mongoengine import signals
|
|
||||||
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
|
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
|
||||||
BaseDict, BaseList)
|
BaseDict, BaseList)
|
||||||
from queryset import OperationError
|
from queryset import OperationError, NotUniqueError
|
||||||
from connection import get_db, DEFAULT_CONNECTION_NAME
|
from connection import get_db, DEFAULT_CONNECTION_NAME
|
||||||
|
|
||||||
__all__ = ['Document', 'EmbeddedDocument', 'DynamicDocument',
|
__all__ = ['Document', 'EmbeddedDocument', 'DynamicDocument',
|
||||||
'DynamicEmbeddedDocument', 'OperationError', 'InvalidCollectionError']
|
'DynamicEmbeddedDocument', 'OperationError',
|
||||||
|
'InvalidCollectionError', 'NotUniqueError']
|
||||||
|
|
||||||
|
|
||||||
class InvalidCollectionError(Exception):
|
class InvalidCollectionError(Exception):
|
||||||
@@ -20,8 +25,19 @@ class EmbeddedDocument(BaseDocument):
|
|||||||
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
|
||||||
:class:`~mongoengine.EmbeddedDocumentField` field type.
|
:class:`~mongoengine.EmbeddedDocumentField` field type.
|
||||||
|
|
||||||
|
A :class:`~mongoengine.EmbeddedDocument` subclass may be itself subclassed,
|
||||||
|
to create a specialised version of the embedded document that will be
|
||||||
|
stored in the same collection. To facilitate this behaviour, `_cls` and
|
||||||
|
`_types` fields are added to documents (hidden though the MongoEngine
|
||||||
|
interface though). To disable this behaviour and remove the dependence on
|
||||||
|
the presence of `_cls` and `_types`, set :attr:`allow_inheritance` to
|
||||||
|
``False`` in the :attr:`meta` dictionary.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 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 = DocumentMetaclass
|
||||||
__metaclass__ = DocumentMetaclass
|
__metaclass__ = DocumentMetaclass
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
@@ -39,6 +55,11 @@ class EmbeddedDocument(BaseDocument):
|
|||||||
else:
|
else:
|
||||||
super(EmbeddedDocument, self).__delattr__(*args, **kwargs)
|
super(EmbeddedDocument, self).__delattr__(*args, **kwargs)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, self.__class__):
|
||||||
|
return self._data == other._data
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Document(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
|
||||||
@@ -74,14 +95,23 @@ class Document(BaseDocument):
|
|||||||
names. Index direction may be specified by prefixing the field names with
|
names. Index direction may be specified by prefixing the field names with
|
||||||
a **+** or **-** sign.
|
a **+** or **-** sign.
|
||||||
|
|
||||||
|
Automatic index creation can be disabled by specifying
|
||||||
|
attr:`auto_create_index` in the :attr:`meta` dictionary. If this is set to
|
||||||
|
False then indexes will not be created by MongoEngine. This is useful in
|
||||||
|
production systems where index creation is performed as part of a deployment
|
||||||
|
system.
|
||||||
|
|
||||||
By default, _types will be added to the start of every index (that
|
By default, _types will be added to the start of every index (that
|
||||||
doesn't contain a list) if allow_inheritence is True. This can be
|
doesn't contain a list) if allow_inheritance is True. This can be
|
||||||
disabled by either setting types to False on the specific index or
|
disabled by either setting types to False on the specific index or
|
||||||
by setting index_types to False on the meta dictionary for the document.
|
by setting index_types to False on the meta dictionary for the document.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 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 = TopLevelDocumentMetaclass
|
||||||
__metaclass__ = TopLevelDocumentMetaclass
|
__metaclass__ = TopLevelDocumentMetaclass
|
||||||
|
|
||||||
@apply
|
|
||||||
def pk():
|
def pk():
|
||||||
"""Primary key alias
|
"""Primary key alias
|
||||||
"""
|
"""
|
||||||
@@ -90,6 +120,7 @@ class Document(BaseDocument):
|
|||||||
def fset(self, value):
|
def fset(self, value):
|
||||||
return setattr(self, self._meta['id_field'], value)
|
return setattr(self, self._meta['id_field'], value)
|
||||||
return property(fget, fset)
|
return property(fget, fset)
|
||||||
|
pk = pk()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _get_db(cls):
|
def _get_db(cls):
|
||||||
@@ -115,8 +146,9 @@ class Document(BaseDocument):
|
|||||||
options = cls._collection.options()
|
options = cls._collection.options()
|
||||||
if options.get('max') != max_documents or \
|
if options.get('max') != max_documents or \
|
||||||
options.get('size') != max_size:
|
options.get('size') != max_size:
|
||||||
msg = ('Cannot create collection "%s" as a capped '
|
msg = (('Cannot create collection "%s" as a capped '
|
||||||
'collection as it already exists') % cls._collection
|
'collection as it already exists')
|
||||||
|
% cls._collection)
|
||||||
raise InvalidCollectionError(msg)
|
raise InvalidCollectionError(msg)
|
||||||
else:
|
else:
|
||||||
# Create the collection as a capped collection
|
# Create the collection as a capped collection
|
||||||
@@ -130,8 +162,9 @@ class Document(BaseDocument):
|
|||||||
cls._collection = db[collection_name]
|
cls._collection = db[collection_name]
|
||||||
return cls._collection
|
return cls._collection
|
||||||
|
|
||||||
def save(self, safe=True, force_insert=False, validate=True, write_options=None,
|
def save(self, safe=True, force_insert=False, validate=True,
|
||||||
cascade=None, cascade_kwargs=None, _refs=None):
|
write_options=None, cascade=None, cascade_kwargs=None,
|
||||||
|
_refs=None):
|
||||||
"""Save the :class:`~mongoengine.Document` to the database. If the
|
"""Save the :class:`~mongoengine.Document` to the database. If the
|
||||||
document already exists, it will be updated, otherwise it will be
|
document already exists, it will be updated, otherwise it will be
|
||||||
created.
|
created.
|
||||||
@@ -144,26 +177,30 @@ class Document(BaseDocument):
|
|||||||
updates of existing documents
|
updates of existing documents
|
||||||
:param validate: validates the document; set to ``False`` to skip.
|
:param validate: validates the document; set to ``False`` to skip.
|
||||||
:param write_options: Extra keyword arguments are passed down to
|
:param write_options: Extra keyword arguments are passed down to
|
||||||
:meth:`~pymongo.collection.Collection.save` OR
|
:meth:`~pymongo.collection.Collection.save` OR
|
||||||
:meth:`~pymongo.collection.Collection.insert`
|
:meth:`~pymongo.collection.Collection.insert`
|
||||||
which will be used as options for the resultant ``getLastError`` command.
|
which will be used as options for the resultant
|
||||||
For example, ``save(..., w=2, fsync=True)`` will wait until at least two servers
|
``getLastError`` command. For example,
|
||||||
have recorded the write and will force an fsync on each server being written to.
|
``save(..., write_options={w: 2, fsync: True}, ...)`` will
|
||||||
:param cascade: Sets the flag for cascading saves. You can set a default by setting
|
wait until at least two servers have recorded the write and
|
||||||
"cascade" in the document __meta__
|
will force an fsync on the primary server.
|
||||||
:param cascade_kwargs: optional kwargs dictionary to be passed throw to cascading saves
|
:param cascade: Sets the flag for cascading saves. You can set a
|
||||||
|
default by setting "cascade" in the document __meta__
|
||||||
|
:param cascade_kwargs: optional kwargs dictionary to be passed throw
|
||||||
|
to cascading saves
|
||||||
:param _refs: A list of processed references used in cascading saves
|
:param _refs: A list of processed references used in cascading saves
|
||||||
|
|
||||||
.. versionchanged:: 0.5
|
.. versionchanged:: 0.5
|
||||||
In existing documents it only saves changed fields using set / unset
|
In existing documents it only saves changed fields using
|
||||||
Saves are cascaded and any :class:`~bson.dbref.DBRef` objects
|
set / unset. Saves are cascaded and any
|
||||||
that have changes are saved as well.
|
:class:`~bson.dbref.DBRef` objects that have changes are
|
||||||
|
saved as well.
|
||||||
.. versionchanged:: 0.6
|
.. versionchanged:: 0.6
|
||||||
Cascade saves are optional = defaults to True, if you want fine grain
|
Cascade saves are optional = defaults to True, if you want
|
||||||
control then you can turn off using document meta['cascade'] = False
|
fine grain control then you can turn off using document
|
||||||
Also you can pass different kwargs to the cascade save using cascade_kwargs
|
meta['cascade'] = False Also you can pass different kwargs to
|
||||||
which overwrites the existing kwargs with custom values
|
the cascade save using cascade_kwargs which overwrites the
|
||||||
|
existing kwargs with custom values
|
||||||
"""
|
"""
|
||||||
signals.pre_save.send(self.__class__, document=self)
|
signals.pre_save.send(self.__class__, document=self)
|
||||||
|
|
||||||
@@ -181,13 +218,14 @@ class Document(BaseDocument):
|
|||||||
collection = self.__class__.objects._collection
|
collection = self.__class__.objects._collection
|
||||||
if created:
|
if created:
|
||||||
if force_insert:
|
if force_insert:
|
||||||
object_id = collection.insert(doc, safe=safe, **write_options)
|
object_id = collection.insert(doc, safe=safe,
|
||||||
|
**write_options)
|
||||||
else:
|
else:
|
||||||
object_id = collection.save(doc, safe=safe, **write_options)
|
object_id = collection.save(doc, safe=safe,
|
||||||
|
**write_options)
|
||||||
else:
|
else:
|
||||||
object_id = doc['_id']
|
object_id = doc['_id']
|
||||||
updates, removals = self._delta()
|
updates, removals = self._delta()
|
||||||
|
|
||||||
# Need to add shard key to query, or you get an error
|
# Need to add shard key to query, or you get an error
|
||||||
select_dict = {'_id': object_id}
|
select_dict = {'_id': object_id}
|
||||||
shard_key = self.__class__._meta.get('shard_key', tuple())
|
shard_key = self.__class__._meta.get('shard_key', tuple())
|
||||||
@@ -197,11 +235,15 @@ class Document(BaseDocument):
|
|||||||
|
|
||||||
upsert = self._created
|
upsert = self._created
|
||||||
if updates:
|
if updates:
|
||||||
collection.update(select_dict, {"$set": updates}, upsert=upsert, safe=safe, **write_options)
|
collection.update(select_dict, {"$set": updates},
|
||||||
|
upsert=upsert, safe=safe, **write_options)
|
||||||
if removals:
|
if removals:
|
||||||
collection.update(select_dict, {"$unset": removals}, upsert=upsert, safe=safe, **write_options)
|
collection.update(select_dict, {"$unset": removals},
|
||||||
|
upsert=upsert, safe=safe, **write_options)
|
||||||
|
|
||||||
cascade = self._meta.get('cascade', True) if cascade is None else cascade
|
warn_cascade = not cascade and 'cascade' not in self._meta
|
||||||
|
cascade = (self._meta.get('cascade', True)
|
||||||
|
if cascade is None else cascade)
|
||||||
if cascade:
|
if cascade:
|
||||||
kwargs = {
|
kwargs = {
|
||||||
"safe": safe,
|
"safe": safe,
|
||||||
@@ -213,37 +255,64 @@ class Document(BaseDocument):
|
|||||||
if cascade_kwargs: # Allow granular control over cascades
|
if cascade_kwargs: # Allow granular control over cascades
|
||||||
kwargs.update(cascade_kwargs)
|
kwargs.update(cascade_kwargs)
|
||||||
kwargs['_refs'] = _refs
|
kwargs['_refs'] = _refs
|
||||||
self.cascade_save(**kwargs)
|
self.cascade_save(warn_cascade=warn_cascade, **kwargs)
|
||||||
|
|
||||||
except pymongo.errors.OperationFailure, err:
|
except pymongo.errors.OperationFailure, err:
|
||||||
message = 'Could not save document (%s)'
|
message = 'Could not save document (%s)'
|
||||||
if u'duplicate key' in unicode(err):
|
if re.match('^E1100[01] duplicate key', unicode(err)):
|
||||||
|
# E11000 - duplicate key error index
|
||||||
|
# E11001 - duplicate key on update
|
||||||
message = u'Tried to save duplicate unique keys (%s)'
|
message = u'Tried to save duplicate unique keys (%s)'
|
||||||
|
raise NotUniqueError(message % unicode(err))
|
||||||
raise OperationError(message % unicode(err))
|
raise OperationError(message % unicode(err))
|
||||||
id_field = self._meta['id_field']
|
id_field = self._meta['id_field']
|
||||||
self[id_field] = self._fields[id_field].to_python(object_id)
|
if id_field not in self._meta.get('shard_key', []):
|
||||||
|
self[id_field] = self._fields[id_field].to_python(object_id)
|
||||||
|
|
||||||
self._changed_fields = []
|
self._changed_fields = []
|
||||||
self._created = False
|
self._created = False
|
||||||
signals.post_save.send(self.__class__, document=self, created=created)
|
signals.post_save.send(self.__class__, document=self, created=created)
|
||||||
|
return self
|
||||||
|
|
||||||
def cascade_save(self, *args, **kwargs):
|
def cascade_save(self, warn_cascade=None, *args, **kwargs):
|
||||||
"""Recursively saves any references / generic references on an object"""
|
"""Recursively saves any references /
|
||||||
from fields import ReferenceField, GenericReferenceField
|
generic references on an objects"""
|
||||||
|
import fields
|
||||||
_refs = kwargs.get('_refs', []) or []
|
_refs = kwargs.get('_refs', []) or []
|
||||||
|
|
||||||
for name, cls in self._fields.items():
|
for name, cls in self._fields.items():
|
||||||
if not isinstance(cls, (ReferenceField, GenericReferenceField)):
|
if not isinstance(cls, (fields.ReferenceField,
|
||||||
|
fields.GenericReferenceField)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
ref = getattr(self, name)
|
ref = getattr(self, name)
|
||||||
if not ref:
|
if not ref or isinstance(ref, DBRef):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
if not getattr(ref, '_changed_fields', True):
|
||||||
|
continue
|
||||||
|
|
||||||
ref_id = "%s,%s" % (ref.__class__.__name__, str(ref._data))
|
ref_id = "%s,%s" % (ref.__class__.__name__, str(ref._data))
|
||||||
if ref and ref_id not in _refs:
|
if ref and ref_id not in _refs:
|
||||||
|
if warn_cascade:
|
||||||
|
msg = ("Cascading saves will default to off in 0.8, "
|
||||||
|
"please explicitly set `.save(cascade=True)`")
|
||||||
|
warnings.warn(msg, FutureWarning)
|
||||||
_refs.append(ref_id)
|
_refs.append(ref_id)
|
||||||
kwargs["_refs"] = _refs
|
kwargs["_refs"] = _refs
|
||||||
ref.save(**kwargs)
|
ref.save(**kwargs)
|
||||||
ref._changed_fields = []
|
ref._changed_fields = []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _object_key(self):
|
||||||
|
"""Dict to identify object in collection
|
||||||
|
"""
|
||||||
|
select_dict = {'pk': self.pk}
|
||||||
|
shard_key = self.__class__._meta.get('shard_key', tuple())
|
||||||
|
for k in shard_key:
|
||||||
|
select_dict[k] = getattr(self, k)
|
||||||
|
return select_dict
|
||||||
|
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs):
|
||||||
"""Performs an update on the :class:`~mongoengine.Document`
|
"""Performs an update on the :class:`~mongoengine.Document`
|
||||||
A convenience wrapper to :meth:`~mongoengine.QuerySet.update`.
|
A convenience wrapper to :meth:`~mongoengine.QuerySet.update`.
|
||||||
@@ -255,11 +324,7 @@ class Document(BaseDocument):
|
|||||||
raise OperationError('attempt to update a document not yet saved')
|
raise OperationError('attempt to update a document not yet saved')
|
||||||
|
|
||||||
# Need to add shard key to query, or you get an error
|
# Need to add shard key to query, or you get an error
|
||||||
select_dict = {'pk': self.pk}
|
return self.__class__.objects(**self._object_key).update_one(**kwargs)
|
||||||
shard_key = self.__class__._meta.get('shard_key', tuple())
|
|
||||||
for k in shard_key:
|
|
||||||
select_dict[k] = getattr(self, k)
|
|
||||||
return self.__class__.objects(**select_dict).update_one(**kwargs)
|
|
||||||
|
|
||||||
def delete(self, safe=False):
|
def delete(self, safe=False):
|
||||||
"""Delete the :class:`~mongoengine.Document` from the database. This
|
"""Delete the :class:`~mongoengine.Document` from the database. This
|
||||||
@@ -270,7 +335,7 @@ class Document(BaseDocument):
|
|||||||
signals.pre_delete.send(self.__class__, document=self)
|
signals.pre_delete.send(self.__class__, document=self)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.__class__.objects(pk=self.pk).delete(safe=safe)
|
self.__class__.objects(**self._object_key).delete(safe=safe)
|
||||||
except pymongo.errors.OperationFailure, err:
|
except pymongo.errors.OperationFailure, err:
|
||||||
message = u'Could not delete document (%s)' % err.message
|
message = u'Could not delete document (%s)' % err.message
|
||||||
raise OperationError(message)
|
raise OperationError(message)
|
||||||
@@ -283,8 +348,8 @@ class Document(BaseDocument):
|
|||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
from dereference import DeReference
|
import dereference
|
||||||
self._data = DeReference()(self._data, max_depth)
|
self._data = dereference.DeReference()(self._data, max_depth)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def reload(self, max_depth=1):
|
def reload(self, max_depth=1):
|
||||||
@@ -296,7 +361,12 @@ class Document(BaseDocument):
|
|||||||
id_field = self._meta['id_field']
|
id_field = self._meta['id_field']
|
||||||
obj = self.__class__.objects(
|
obj = self.__class__.objects(
|
||||||
**{id_field: self[id_field]}
|
**{id_field: self[id_field]}
|
||||||
).first().select_related(max_depth=max_depth)
|
).limit(1).select_related(max_depth=max_depth)
|
||||||
|
if obj:
|
||||||
|
obj = obj[0]
|
||||||
|
else:
|
||||||
|
msg = "Reloaded document has been deleted"
|
||||||
|
raise OperationError(msg)
|
||||||
for field in self._fields:
|
for field in self._fields:
|
||||||
setattr(self, field, self._reload(field, obj[field]))
|
setattr(self, field, self._reload(field, obj[field]))
|
||||||
if self._dynamic:
|
if self._dynamic:
|
||||||
@@ -332,17 +402,18 @@ class Document(BaseDocument):
|
|||||||
"""This method registers the delete rules to apply when removing this
|
"""This method registers the delete rules to apply when removing this
|
||||||
object.
|
object.
|
||||||
"""
|
"""
|
||||||
cls._meta['delete_rules'][(document_cls, field_name)] = rule
|
delete_rules = cls._meta.get('delete_rules') or {}
|
||||||
|
delete_rules[(document_cls, field_name)] = rule
|
||||||
|
cls._meta['delete_rules'] = delete_rules
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def drop_collection(cls):
|
def drop_collection(cls):
|
||||||
"""Drops the entire collection associated with this
|
"""Drops the entire collection associated with this
|
||||||
:class:`~mongoengine.Document` type from the database.
|
:class:`~mongoengine.Document` type from the database.
|
||||||
"""
|
"""
|
||||||
from mongoengine.queryset import QuerySet
|
|
||||||
db = cls._get_db()
|
db = cls._get_db()
|
||||||
db.drop_collection(cls._get_collection_name())
|
db.drop_collection(cls._get_collection_name())
|
||||||
QuerySet._reset_already_indexed(cls)
|
queryset.QuerySet._reset_already_indexed(cls)
|
||||||
|
|
||||||
|
|
||||||
class DynamicDocument(Document):
|
class DynamicDocument(Document):
|
||||||
@@ -351,14 +422,19 @@ class DynamicDocument(Document):
|
|||||||
way as an ordinary document but has expando 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.BaseDynamicField` and data can be attributed to that
|
:class:`~mongoengine.DynamicField` and data can be attributed to that
|
||||||
field.
|
field.
|
||||||
|
|
||||||
..note::
|
.. note::
|
||||||
|
|
||||||
There is one caveat on Dynamic Documents: fields cannot start with `_`
|
There is one caveat on Dynamic Documents: fields cannot start with `_`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 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 = TopLevelDocumentMetaclass
|
||||||
__metaclass__ = TopLevelDocumentMetaclass
|
__metaclass__ = TopLevelDocumentMetaclass
|
||||||
|
|
||||||
_dynamic = True
|
_dynamic = True
|
||||||
|
|
||||||
def __delattr__(self, *args, **kwargs):
|
def __delattr__(self, *args, **kwargs):
|
||||||
@@ -377,7 +453,11 @@ class DynamicEmbeddedDocument(EmbeddedDocument):
|
|||||||
information about dynamic documents.
|
information about dynamic documents.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
# 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 = DocumentMetaclass
|
||||||
__metaclass__ = DocumentMetaclass
|
__metaclass__ = DocumentMetaclass
|
||||||
|
|
||||||
_dynamic = True
|
_dynamic = True
|
||||||
|
|
||||||
def __delattr__(self, *args, **kwargs):
|
def __delattr__(self, *args, **kwargs):
|
||||||
|
@@ -1,18 +1,24 @@
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
|
||||||
import decimal
|
import decimal
|
||||||
import gridfs
|
import itertools
|
||||||
import re
|
import re
|
||||||
|
import time
|
||||||
|
import urllib2
|
||||||
|
import urlparse
|
||||||
import uuid
|
import uuid
|
||||||
|
import warnings
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
import gridfs
|
||||||
from bson import Binary, DBRef, SON, ObjectId
|
from bson import Binary, DBRef, SON, ObjectId
|
||||||
|
|
||||||
|
from mongoengine.python_support import (PY3, bin_type, txt_type,
|
||||||
|
str_types, StringIO)
|
||||||
from base import (BaseField, ComplexBaseField, ObjectIdField,
|
from base import (BaseField, ComplexBaseField, ObjectIdField,
|
||||||
ValidationError, get_document, BaseDocument)
|
ValidationError, get_document, BaseDocument)
|
||||||
from queryset import DO_NOTHING, QuerySet
|
from queryset import DO_NOTHING, QuerySet
|
||||||
from document import Document, EmbeddedDocument
|
from document import Document, EmbeddedDocument
|
||||||
from connection import get_db, DEFAULT_CONNECTION_NAME
|
from connection import get_db, DEFAULT_CONNECTION_NAME
|
||||||
from operator import itemgetter
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -21,16 +27,10 @@ except ImportError:
|
|||||||
Image = None
|
Image = None
|
||||||
ImageOps = None
|
ImageOps = None
|
||||||
|
|
||||||
try:
|
|
||||||
from cStringIO import StringIO
|
|
||||||
except ImportError:
|
|
||||||
from StringIO import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
|
__all__ = ['StringField', 'IntField', 'FloatField', 'BooleanField',
|
||||||
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
'DateTimeField', 'EmbeddedDocumentField', 'ListField', 'DictField',
|
||||||
'ObjectIdField', 'ReferenceField', 'ValidationError', 'MapField',
|
'ObjectIdField', 'ReferenceField', 'ValidationError', 'MapField',
|
||||||
'DecimalField', 'ComplexDateTimeField', 'URLField',
|
'DecimalField', 'ComplexDateTimeField', 'URLField', 'DynamicField',
|
||||||
'GenericReferenceField', 'FileField', 'BinaryField',
|
'GenericReferenceField', 'FileField', 'BinaryField',
|
||||||
'SortedListField', 'EmailField', 'GeoPointField', 'ImageField',
|
'SortedListField', 'EmailField', 'GeoPointField', 'ImageField',
|
||||||
'SequenceField', 'UUIDField', 'GenericEmbeddedDocumentField']
|
'SequenceField', 'UUIDField', 'GenericEmbeddedDocumentField']
|
||||||
@@ -49,10 +49,16 @@ class StringField(BaseField):
|
|||||||
super(StringField, self).__init__(**kwargs)
|
super(StringField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return unicode(value)
|
if isinstance(value, unicode):
|
||||||
|
return value
|
||||||
|
try:
|
||||||
|
value = value.decode('utf-8')
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, (str, unicode)):
|
if not isinstance(value, basestring):
|
||||||
self.error('StringField only accepts string values')
|
self.error('StringField only accepts string values')
|
||||||
|
|
||||||
if self.max_length is not None and len(value) > self.max_length:
|
if self.max_length is not None and len(value) > self.max_length:
|
||||||
@@ -97,25 +103,30 @@ class URLField(StringField):
|
|||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
|
|
||||||
URL_REGEX = re.compile(
|
_URL_REGEX = re.compile(
|
||||||
r'^https?://'
|
r'^(?:http|ftp)s?://' # http:// or https://
|
||||||
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|'
|
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' #domain...
|
||||||
r'localhost|'
|
r'localhost|' #localhost...
|
||||||
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})'
|
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
|
||||||
r'(?::\d+)?'
|
r'(?::\d+)?' # optional port
|
||||||
r'(?:/?|[/?]\S+)$', re.IGNORECASE
|
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
||||||
)
|
|
||||||
|
|
||||||
def __init__(self, verify_exists=False, **kwargs):
|
def __init__(self, verify_exists=False, url_regex=None, **kwargs):
|
||||||
self.verify_exists = verify_exists
|
self.verify_exists = verify_exists
|
||||||
|
self.url_regex = url_regex or self._URL_REGEX
|
||||||
super(URLField, self).__init__(**kwargs)
|
super(URLField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not URLField.URL_REGEX.match(value):
|
if not self.url_regex.match(value):
|
||||||
self.error('Invalid URL: %s' % value)
|
self.error('Invalid URL: %s' % value)
|
||||||
|
return
|
||||||
|
|
||||||
if self.verify_exists:
|
if self.verify_exists:
|
||||||
import urllib2
|
warnings.warn(
|
||||||
|
"The URLField verify_exists argument has intractable security "
|
||||||
|
"and performance issues. Accordingly, it has been deprecated.",
|
||||||
|
DeprecationWarning
|
||||||
|
)
|
||||||
try:
|
try:
|
||||||
request = urllib2.Request(value)
|
request = urllib2.Request(value)
|
||||||
urllib2.urlopen(request)
|
urllib2.urlopen(request)
|
||||||
@@ -138,6 +149,7 @@ class EmailField(StringField):
|
|||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not EmailField.EMAIL_REGEX.match(value):
|
if not EmailField.EMAIL_REGEX.match(value):
|
||||||
self.error('Invalid Mail-address: %s' % value)
|
self.error('Invalid Mail-address: %s' % value)
|
||||||
|
super(EmailField, self).validate(value)
|
||||||
|
|
||||||
|
|
||||||
class IntField(BaseField):
|
class IntField(BaseField):
|
||||||
@@ -149,7 +161,11 @@ class IntField(BaseField):
|
|||||||
super(IntField, self).__init__(**kwargs)
|
super(IntField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return int(value)
|
try:
|
||||||
|
value = int(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
try:
|
try:
|
||||||
@@ -164,6 +180,9 @@ class IntField(BaseField):
|
|||||||
self.error('Integer value is too large')
|
self.error('Integer value is too large')
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
def prepare_query_value(self, op, value):
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
|
||||||
return int(value)
|
return int(value)
|
||||||
|
|
||||||
|
|
||||||
@@ -176,13 +195,17 @@ class FloatField(BaseField):
|
|||||||
super(FloatField, self).__init__(**kwargs)
|
super(FloatField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return float(value)
|
try:
|
||||||
|
value = float(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if isinstance(value, int):
|
if isinstance(value, int):
|
||||||
value = float(value)
|
value = float(value)
|
||||||
if not isinstance(value, float):
|
if not isinstance(value, float):
|
||||||
self.error('FoatField only accepts float values')
|
self.error('FloatField only accepts float values')
|
||||||
|
|
||||||
if self.min_value is not None and value < self.min_value:
|
if self.min_value is not None and value < self.min_value:
|
||||||
self.error('Float value is too small')
|
self.error('Float value is too small')
|
||||||
@@ -191,6 +214,9 @@ class FloatField(BaseField):
|
|||||||
self.error('Float value is too large')
|
self.error('Float value is too large')
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
def prepare_query_value(self, op, value):
|
||||||
|
if value is None:
|
||||||
|
return value
|
||||||
|
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
|
||||||
@@ -205,9 +231,14 @@ class DecimalField(BaseField):
|
|||||||
super(DecimalField, self).__init__(**kwargs)
|
super(DecimalField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
|
original_value = value
|
||||||
if not isinstance(value, basestring):
|
if not isinstance(value, basestring):
|
||||||
value = unicode(value)
|
value = unicode(value)
|
||||||
return decimal.Decimal(value)
|
try:
|
||||||
|
value = decimal.Decimal(value)
|
||||||
|
except ValueError:
|
||||||
|
return original_value
|
||||||
|
return value
|
||||||
|
|
||||||
def to_mongo(self, value):
|
def to_mongo(self, value):
|
||||||
return unicode(value)
|
return unicode(value)
|
||||||
@@ -235,7 +266,11 @@ class BooleanField(BaseField):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return bool(value)
|
try:
|
||||||
|
value = bool(value)
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return value
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, bool):
|
if not isinstance(value, bool):
|
||||||
@@ -366,10 +401,12 @@ class ComplexDateTimeField(StringField):
|
|||||||
data = super(ComplexDateTimeField, self).__get__(instance, owner)
|
data = super(ComplexDateTimeField, self).__get__(instance, owner)
|
||||||
if data == None:
|
if data == None:
|
||||||
return datetime.datetime.now()
|
return datetime.datetime.now()
|
||||||
|
if isinstance(data, datetime.datetime):
|
||||||
|
return data
|
||||||
return self._convert_from_string(data)
|
return self._convert_from_string(data)
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
value = self._convert_from_datetime(value)
|
value = self._convert_from_datetime(value) if value else value
|
||||||
return super(ComplexDateTimeField, self).__set__(instance, value)
|
return super(ComplexDateTimeField, self).__set__(instance, value)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
@@ -378,7 +415,11 @@ class ComplexDateTimeField(StringField):
|
|||||||
'ComplexDateTimeField')
|
'ComplexDateTimeField')
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
return self._convert_from_string(value)
|
original_value = value
|
||||||
|
try:
|
||||||
|
return self._convert_from_string(value)
|
||||||
|
except:
|
||||||
|
return original_value
|
||||||
|
|
||||||
def to_mongo(self, value):
|
def to_mongo(self, value):
|
||||||
return self._convert_from_datetime(value)
|
return self._convert_from_datetime(value)
|
||||||
@@ -441,6 +482,10 @@ class GenericEmbeddedDocumentField(BaseField):
|
|||||||
:class:`~mongoengine.EmbeddedDocument` to be stored.
|
:class:`~mongoengine.EmbeddedDocument` to be stored.
|
||||||
|
|
||||||
Only valid values are subclasses of :class:`~mongoengine.EmbeddedDocument`.
|
Only valid values are subclasses of :class:`~mongoengine.EmbeddedDocument`.
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
You can use the choices param to limit the acceptable
|
||||||
|
EmbeddedDocument types
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
def prepare_query_value(self, op, value):
|
||||||
@@ -470,10 +515,56 @@ class GenericEmbeddedDocumentField(BaseField):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicField(BaseField):
|
||||||
|
"""A truly dynamic field type capable of handling different and varying
|
||||||
|
types of data.
|
||||||
|
|
||||||
|
Used by :class:`~mongoengine.DynamicDocument` to handle dynamic data"""
|
||||||
|
|
||||||
|
def to_mongo(self, value):
|
||||||
|
"""Convert a Python type to a MongoDBcompatible type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if isinstance(value, basestring):
|
||||||
|
return value
|
||||||
|
|
||||||
|
if hasattr(value, 'to_mongo'):
|
||||||
|
return value.to_mongo()
|
||||||
|
|
||||||
|
if not isinstance(value, (dict, list, tuple)):
|
||||||
|
return value
|
||||||
|
|
||||||
|
is_list = False
|
||||||
|
if not hasattr(value, 'items'):
|
||||||
|
is_list = True
|
||||||
|
value = dict([(k, v) for k, v in enumerate(value)])
|
||||||
|
|
||||||
|
data = {}
|
||||||
|
for k, v in value.items():
|
||||||
|
data[k] = self.to_mongo(v)
|
||||||
|
|
||||||
|
if is_list: # Convert back to a list
|
||||||
|
value = [v for k, v in sorted(data.items(), key=itemgetter(0))]
|
||||||
|
else:
|
||||||
|
value = data
|
||||||
|
return value
|
||||||
|
|
||||||
|
def lookup_member(self, member_name):
|
||||||
|
return member_name
|
||||||
|
|
||||||
|
def prepare_query_value(self, op, value):
|
||||||
|
if isinstance(value, basestring):
|
||||||
|
from mongoengine.fields import StringField
|
||||||
|
return StringField().prepare_query_value(op, value)
|
||||||
|
return self.to_mongo(value)
|
||||||
|
|
||||||
|
|
||||||
class ListField(ComplexBaseField):
|
class ListField(ComplexBaseField):
|
||||||
"""A list field that wraps a standard field, allowing multiple instances
|
"""A list field that wraps a standard field, allowing multiple instances
|
||||||
of the field to be used as a list in the database.
|
of the field to be used as a list in the database.
|
||||||
|
|
||||||
|
If using with ReferenceFields see: :ref:`one-to-many-with-listfields`
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
Required means it cannot be empty - as the default for ListFields is []
|
Required means it cannot be empty - as the default for ListFields is []
|
||||||
"""
|
"""
|
||||||
@@ -612,13 +703,33 @@ class ReferenceField(BaseField):
|
|||||||
* NULLIFY - Updates the reference to null.
|
* NULLIFY - Updates the reference to null.
|
||||||
* CASCADE - Deletes the documents associated with the reference.
|
* CASCADE - Deletes the documents associated with the reference.
|
||||||
* DENY - Prevent the deletion of the reference object.
|
* DENY - Prevent the deletion of the reference object.
|
||||||
|
* PULL - Pull the reference from a :class:`~mongoengine.ListField`
|
||||||
|
of references
|
||||||
|
|
||||||
|
Alternative syntax for registering delete rules (useful when implementing
|
||||||
|
bi-directional delete rules)
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
class Bar(Document):
|
||||||
|
content = StringField()
|
||||||
|
foo = ReferenceField('Foo')
|
||||||
|
|
||||||
|
Bar.register_delete_rule(Foo, 'bar', NULLIFY)
|
||||||
|
|
||||||
|
.. note ::
|
||||||
|
`reverse_delete_rules` do not trigger pre / post delete signals to be
|
||||||
|
triggered.
|
||||||
|
|
||||||
.. versionchanged:: 0.5 added `reverse_delete_rule`
|
.. versionchanged:: 0.5 added `reverse_delete_rule`
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, document_type, reverse_delete_rule=DO_NOTHING, **kwargs):
|
def __init__(self, document_type, dbref=None,
|
||||||
|
reverse_delete_rule=DO_NOTHING, **kwargs):
|
||||||
"""Initialises the Reference Field.
|
"""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
|
:param reverse_delete_rule: Determines what to do when the referring
|
||||||
object is deleted
|
object is deleted
|
||||||
"""
|
"""
|
||||||
@@ -626,6 +737,13 @@ class ReferenceField(BaseField):
|
|||||||
if not issubclass(document_type, (Document, basestring)):
|
if not issubclass(document_type, (Document, basestring)):
|
||||||
self.error('Argument to ReferenceField constructor must be a '
|
self.error('Argument to ReferenceField constructor must be a '
|
||||||
'document class or a string')
|
'document class or a string')
|
||||||
|
|
||||||
|
if dbref is None:
|
||||||
|
msg = ("ReferenceFields will default to using ObjectId "
|
||||||
|
" strings in 0.8, set DBRef=True if this isn't desired")
|
||||||
|
warnings.warn(msg, FutureWarning)
|
||||||
|
|
||||||
|
self.dbref = dbref if dbref is not None else True # To change in 0.8
|
||||||
self.document_type_obj = document_type
|
self.document_type_obj = document_type
|
||||||
self.reverse_delete_rule = reverse_delete_rule
|
self.reverse_delete_rule = reverse_delete_rule
|
||||||
super(ReferenceField, self).__init__(**kwargs)
|
super(ReferenceField, self).__init__(**kwargs)
|
||||||
@@ -648,8 +766,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)
|
||||||
|
|
||||||
# Dereference DBRefs
|
# Dereference DBRefs
|
||||||
if isinstance(value, (DBRef)):
|
if isinstance(value, DBRef):
|
||||||
value = self.document_type._get_db().dereference(value)
|
value = self.document_type._get_db().dereference(value)
|
||||||
if value is not None:
|
if value is not None:
|
||||||
instance._data[self.name] = self.document_type._from_son(value)
|
instance._data[self.name] = self.document_type._from_son(value)
|
||||||
@@ -657,12 +776,19 @@ class ReferenceField(BaseField):
|
|||||||
return super(ReferenceField, self).__get__(instance, owner)
|
return super(ReferenceField, self).__get__(instance, owner)
|
||||||
|
|
||||||
def to_mongo(self, document):
|
def to_mongo(self, document):
|
||||||
|
if isinstance(document, DBRef):
|
||||||
|
if not self.dbref:
|
||||||
|
return document.id
|
||||||
|
return document
|
||||||
|
elif not self.dbref and isinstance(document, basestring):
|
||||||
|
return document
|
||||||
|
|
||||||
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]
|
||||||
|
|
||||||
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.id
|
id_ = document.pk
|
||||||
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')
|
||||||
@@ -670,18 +796,30 @@ class ReferenceField(BaseField):
|
|||||||
id_ = document
|
id_ = document
|
||||||
|
|
||||||
id_ = id_field.to_mongo(id_)
|
id_ = id_field.to_mongo(id_)
|
||||||
collection = self.document_type._get_collection_name()
|
if self.dbref:
|
||||||
return DBRef(collection, id_)
|
collection = self.document_type._get_collection_name()
|
||||||
|
return DBRef(collection, id_)
|
||||||
|
|
||||||
|
return id_
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
"""Convert a MongoDB-compatible type to a Python type.
|
||||||
|
"""
|
||||||
|
if (not self.dbref and
|
||||||
|
not isinstance(value, (DBRef, Document, EmbeddedDocument))):
|
||||||
|
collection = self.document_type._get_collection_name()
|
||||||
|
value = DBRef(collection, self.document_type.id.to_python(value))
|
||||||
|
return value
|
||||||
|
|
||||||
def prepare_query_value(self, op, value):
|
def prepare_query_value(self, op, value):
|
||||||
if value is None:
|
if value is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
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, DBRef)):
|
if not isinstance(value, (self.document_type, DBRef)):
|
||||||
self.error('A ReferenceField only accepts DBRef')
|
self.error("A ReferenceField only accepts DBRef 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 '
|
||||||
@@ -695,8 +833,12 @@ 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 :: Any documents used as a generic reference must be registered in the
|
.. note ::
|
||||||
document registry. Importing the model will automatically register it.
|
* 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.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
@@ -732,6 +874,9 @@ class GenericReferenceField(BaseField):
|
|||||||
if document is None:
|
if document is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if isinstance(document, (dict, SON)):
|
||||||
|
return document
|
||||||
|
|
||||||
id_field_name = document.__class__._meta['id_field']
|
id_field_name = document.__class__._meta['id_field']
|
||||||
id_field = document.__class__._fields[id_field_name]
|
id_field = document.__class__._fields[id_field_name]
|
||||||
|
|
||||||
@@ -764,16 +909,20 @@ class BinaryField(BaseField):
|
|||||||
self.max_bytes = max_bytes
|
self.max_bytes = max_bytes
|
||||||
super(BinaryField, self).__init__(**kwargs)
|
super(BinaryField, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
def __set__(self, instance, value):
|
||||||
|
"""Handle bytearrays in python 3.1"""
|
||||||
|
if PY3 and isinstance(value, bytearray):
|
||||||
|
value = bin_type(value)
|
||||||
|
return super(BinaryField, self).__set__(instance, value)
|
||||||
|
|
||||||
def to_mongo(self, value):
|
def to_mongo(self, value):
|
||||||
return Binary(value)
|
return Binary(value)
|
||||||
|
|
||||||
def to_python(self, value):
|
|
||||||
# Returns str not unicode as this is binary data
|
|
||||||
return str(value)
|
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, str):
|
if not isinstance(value, (bin_type, txt_type, Binary)):
|
||||||
self.error('BinaryField only accepts string values')
|
self.error("BinaryField only accepts instances of "
|
||||||
|
"(%s, %s, Binary)" % (
|
||||||
|
bin_type.__name__, txt_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')
|
||||||
@@ -826,6 +975,17 @@ class GridFSProxy(object):
|
|||||||
self_dict['_fs'] = None
|
self_dict['_fs'] = None
|
||||||
return self_dict
|
return self_dict
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, GridFSProxy):
|
||||||
|
return ((self.grid_id == other.grid_id) and
|
||||||
|
(self.collection_name == other.collection_name) and
|
||||||
|
(self.db_alias == other.db_alias))
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fs(self):
|
def fs(self):
|
||||||
if not self._fs:
|
if not self._fs:
|
||||||
@@ -872,10 +1032,14 @@ class GridFSProxy(object):
|
|||||||
self.newfile.writelines(lines)
|
self.newfile.writelines(lines)
|
||||||
|
|
||||||
def read(self, size=-1):
|
def read(self, size=-1):
|
||||||
try:
|
gridout = self.get()
|
||||||
return self.get().read(size)
|
if gridout is None:
|
||||||
except:
|
|
||||||
return None
|
return None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return gridout.read(size)
|
||||||
|
except:
|
||||||
|
return ""
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
# Delete file from GridFS, FileField still remains
|
# Delete file from GridFS, FileField still remains
|
||||||
@@ -920,19 +1084,21 @@ class FileField(BaseField):
|
|||||||
|
|
||||||
# Check if a file already exists for this model
|
# Check if a file already exists for this model
|
||||||
grid_file = instance._data.get(self.name)
|
grid_file = instance._data.get(self.name)
|
||||||
self.grid_file = grid_file
|
if not isinstance(grid_file, self.proxy_class):
|
||||||
if isinstance(self.grid_file, self.proxy_class):
|
grid_file = self.proxy_class(key=self.name, instance=instance,
|
||||||
if not self.grid_file.key:
|
db_alias=self.db_alias,
|
||||||
self.grid_file.key = self.name
|
collection_name=self.collection_name)
|
||||||
self.grid_file.instance = instance
|
instance._data[self.name] = grid_file
|
||||||
return self.grid_file
|
|
||||||
return self.proxy_class(key=self.name, instance=instance,
|
if not grid_file.key:
|
||||||
db_alias=self.db_alias,
|
grid_file.key = self.name
|
||||||
collection_name=self.collection_name)
|
grid_file.instance = instance
|
||||||
|
return grid_file
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
key = self.name
|
key = self.name
|
||||||
if isinstance(value, file) or isinstance(value, str):
|
if ((hasattr(value, 'read') and not
|
||||||
|
isinstance(value, GridFSProxy)) or isinstance(value, str_types)):
|
||||||
# using "FileField() = file/string" notation
|
# using "FileField() = file/string" notation
|
||||||
grid_file = instance._data.get(self.name)
|
grid_file = instance._data.get(self.name)
|
||||||
# If a file already exists, delete it
|
# If a file already exists, delete it
|
||||||
@@ -988,6 +1154,7 @@ class ImageGridFsProxy(GridFSProxy):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
img = Image.open(file_obj)
|
img = Image.open(file_obj)
|
||||||
|
img_format = img.format
|
||||||
except:
|
except:
|
||||||
raise ValidationError('Invalid image')
|
raise ValidationError('Invalid image')
|
||||||
|
|
||||||
@@ -1022,20 +1189,20 @@ class ImageGridFsProxy(GridFSProxy):
|
|||||||
|
|
||||||
if thumbnail:
|
if thumbnail:
|
||||||
thumb_id = self._put_thumbnail(thumbnail,
|
thumb_id = self._put_thumbnail(thumbnail,
|
||||||
img.format)
|
img_format)
|
||||||
else:
|
else:
|
||||||
thumb_id = None
|
thumb_id = None
|
||||||
|
|
||||||
w, h = img.size
|
w, h = img.size
|
||||||
|
|
||||||
io = StringIO()
|
io = StringIO()
|
||||||
img.save(io, img.format)
|
img.save(io, img_format)
|
||||||
io.seek(0)
|
io.seek(0)
|
||||||
|
|
||||||
return super(ImageGridFsProxy, self).put(io,
|
return super(ImageGridFsProxy, self).put(io,
|
||||||
width=w,
|
width=w,
|
||||||
height=h,
|
height=h,
|
||||||
format=img.format,
|
format=img_format,
|
||||||
thumbnail_id=thumb_id,
|
thumbnail_id=thumb_id,
|
||||||
**kwargs)
|
**kwargs)
|
||||||
|
|
||||||
@@ -1121,11 +1288,15 @@ class ImageField(FileField):
|
|||||||
params_size = ('width', 'height', 'force')
|
params_size = ('width', 'height', 'force')
|
||||||
extra_args = dict(size=size, thumbnail_size=thumbnail_size)
|
extra_args = dict(size=size, thumbnail_size=thumbnail_size)
|
||||||
for att_name, att in extra_args.items():
|
for att_name, att in extra_args.items():
|
||||||
if att and (isinstance(att, tuple) or isinstance(att, list)):
|
value = None
|
||||||
setattr(self, att_name, dict(
|
if isinstance(att, (tuple, list)):
|
||||||
map(None, params_size, att)))
|
if PY3:
|
||||||
else:
|
value = dict(itertools.zip_longest(params_size, att,
|
||||||
setattr(self, att_name, None)
|
fillvalue=None))
|
||||||
|
else:
|
||||||
|
value = dict(map(None, params_size, att))
|
||||||
|
|
||||||
|
setattr(self, att_name, value)
|
||||||
|
|
||||||
super(ImageField, self).__init__(
|
super(ImageField, self).__init__(
|
||||||
collection_name=collection_name,
|
collection_name=collection_name,
|
||||||
@@ -1167,24 +1338,35 @@ class SequenceField(IntField):
|
|||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
def __init__(self, collection_name=None, db_alias = None, *args, **kwargs):
|
def __init__(self, collection_name=None, db_alias=None, sequence_name=None, *args, **kwargs):
|
||||||
self.collection_name = collection_name or 'mongoengine.counters'
|
self.collection_name = collection_name or 'mongoengine.counters'
|
||||||
self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
|
self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
|
||||||
|
self.sequence_name = sequence_name
|
||||||
return super(SequenceField, self).__init__(*args, **kwargs)
|
return super(SequenceField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def generate_new_value(self):
|
def generate_new_value(self):
|
||||||
"""
|
"""
|
||||||
Generate and Increment the counter
|
Generate and Increment the counter
|
||||||
"""
|
"""
|
||||||
sequence_id = "{0}.{1}".format(self.owner_document._get_collection_name(),
|
sequence_name = self.get_sequence_name()
|
||||||
self.name)
|
sequence_id = "%s.%s" % (sequence_name, self.name)
|
||||||
collection = get_db(alias = self.db_alias )[self.collection_name]
|
collection = get_db(alias=self.db_alias)[self.collection_name]
|
||||||
counter = collection.find_and_modify(query={"_id": sequence_id},
|
counter = collection.find_and_modify(query={"_id": sequence_id},
|
||||||
update={"$inc": {"next": 1}},
|
update={"$inc": {"next": 1}},
|
||||||
new=True,
|
new=True,
|
||||||
upsert=True)
|
upsert=True)
|
||||||
return counter['next']
|
return counter['next']
|
||||||
|
|
||||||
|
def get_sequence_name(self):
|
||||||
|
if self.sequence_name:
|
||||||
|
return self.sequence_name
|
||||||
|
owner = self.owner_document
|
||||||
|
if issubclass(owner, Document):
|
||||||
|
return owner._get_collection_name()
|
||||||
|
else:
|
||||||
|
return ''.join('_%s' % c if c.isupper() else c
|
||||||
|
for c in owner._class_name).strip('_').lower()
|
||||||
|
|
||||||
def __get__(self, instance, owner):
|
def __get__(self, instance, owner):
|
||||||
|
|
||||||
if instance is None:
|
if instance is None:
|
||||||
@@ -1200,7 +1382,7 @@ class SequenceField(IntField):
|
|||||||
instance._data[self.name] = value
|
instance._data[self.name] = value
|
||||||
instance._mark_as_changed(self.name)
|
instance._mark_as_changed(self.name)
|
||||||
|
|
||||||
return value
|
return int(value) if value else None
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
|
||||||
@@ -1220,17 +1402,44 @@ class UUIDField(BaseField):
|
|||||||
|
|
||||||
.. versionadded:: 0.6
|
.. versionadded:: 0.6
|
||||||
"""
|
"""
|
||||||
|
_binary = None
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, binary=None, **kwargs):
|
||||||
|
"""
|
||||||
|
Store UUID data in the database
|
||||||
|
|
||||||
|
:param binary: (optional) boolean store as binary.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.6.19
|
||||||
|
"""
|
||||||
|
if binary is None:
|
||||||
|
binary = False
|
||||||
|
msg = ("UUIDFields will soon default to store as binary, please "
|
||||||
|
"configure binary=False if you wish to store as a string")
|
||||||
|
warnings.warn(msg, FutureWarning)
|
||||||
|
self._binary = binary
|
||||||
super(UUIDField, self).__init__(**kwargs)
|
super(UUIDField, self).__init__(**kwargs)
|
||||||
|
|
||||||
def to_python(self, value):
|
def to_python(self, value):
|
||||||
if not isinstance(value, basestring):
|
if not self._binary:
|
||||||
value = unicode(value)
|
original_value = value
|
||||||
return uuid.UUID(value)
|
try:
|
||||||
|
if not isinstance(value, basestring):
|
||||||
|
value = unicode(value)
|
||||||
|
return uuid.UUID(value)
|
||||||
|
except:
|
||||||
|
return original_value
|
||||||
|
return value
|
||||||
|
|
||||||
def to_mongo(self, value):
|
def to_mongo(self, value):
|
||||||
return unicode(value)
|
if not self._binary:
|
||||||
|
return unicode(value)
|
||||||
|
return value
|
||||||
|
|
||||||
|
def prepare_query_value(self, op, value):
|
||||||
|
if value is None:
|
||||||
|
return None
|
||||||
|
return self.to_mongo(value)
|
||||||
|
|
||||||
def validate(self, value):
|
def validate(self, value):
|
||||||
if not isinstance(value, uuid.UUID):
|
if not isinstance(value, uuid.UUID):
|
||||||
|
61
mongoengine/python_support.py
Normal file
61
mongoengine/python_support.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
"""Helper functions and types to aid with Python 2.5 - 3 support."""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PY3 = sys.version_info[0] == 3
|
||||||
|
PY25 = sys.version_info[:2] == (2, 5)
|
||||||
|
UNICODE_KWARGS = int(''.join([str(x) for x in sys.version_info[:3]])) > 264
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
import codecs
|
||||||
|
from io import BytesIO as StringIO
|
||||||
|
# return s converted to binary. b('test') should be equivalent to b'test'
|
||||||
|
def b(s):
|
||||||
|
return codecs.latin_1_encode(s)[0]
|
||||||
|
|
||||||
|
bin_type = bytes
|
||||||
|
txt_type = str
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
from cStringIO import StringIO
|
||||||
|
except ImportError:
|
||||||
|
from StringIO import StringIO
|
||||||
|
|
||||||
|
# Conversion to binary only necessary in Python 3
|
||||||
|
def b(s):
|
||||||
|
return s
|
||||||
|
|
||||||
|
bin_type = str
|
||||||
|
txt_type = unicode
|
||||||
|
|
||||||
|
str_types = (bin_type, txt_type)
|
||||||
|
|
||||||
|
if PY25:
|
||||||
|
def product(*args, **kwds):
|
||||||
|
pools = map(tuple, args) * kwds.get('repeat', 1)
|
||||||
|
result = [[]]
|
||||||
|
for pool in pools:
|
||||||
|
result = [x + [y] for x in result for y in pool]
|
||||||
|
for prod in result:
|
||||||
|
yield tuple(prod)
|
||||||
|
reduce = reduce
|
||||||
|
else:
|
||||||
|
from itertools import product
|
||||||
|
from functools import reduce
|
||||||
|
|
||||||
|
|
||||||
|
# For use with Python 2.5
|
||||||
|
# converts all keys from unicode to str for d and all nested dictionaries
|
||||||
|
def to_str_keys_recursive(d):
|
||||||
|
if isinstance(d, list):
|
||||||
|
for val in d:
|
||||||
|
if isinstance(val, (dict, list)):
|
||||||
|
to_str_keys_recursive(val)
|
||||||
|
elif isinstance(d, dict):
|
||||||
|
for key, val in d.items():
|
||||||
|
if isinstance(val, (dict, list)):
|
||||||
|
to_str_keys_recursive(val)
|
||||||
|
if isinstance(key, unicode):
|
||||||
|
d[str(key)] = d.pop(key)
|
||||||
|
else:
|
||||||
|
raise ValueError("non list/dict parameter not allowed")
|
@@ -4,13 +4,18 @@ import copy
|
|||||||
import itertools
|
import itertools
|
||||||
import operator
|
import operator
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
from mongoengine.python_support import product, reduce
|
||||||
|
|
||||||
import pymongo
|
import pymongo
|
||||||
from bson.code import Code
|
from bson.code import Code
|
||||||
|
|
||||||
from mongoengine import signals
|
from mongoengine import signals
|
||||||
|
|
||||||
__all__ = ['queryset_manager', 'Q', 'InvalidQueryError',
|
__all__ = ['queryset_manager', 'Q', 'InvalidQueryError',
|
||||||
'DO_NOTHING', 'NULLIFY', 'CASCADE', 'DENY']
|
'DO_NOTHING', 'NULLIFY', 'CASCADE', 'DENY', 'PULL']
|
||||||
|
|
||||||
|
|
||||||
# The maximum number of items to display in a QuerySet.__repr__
|
# The maximum number of items to display in a QuerySet.__repr__
|
||||||
@@ -21,6 +26,7 @@ DO_NOTHING = 0
|
|||||||
NULLIFY = 1
|
NULLIFY = 1
|
||||||
CASCADE = 2
|
CASCADE = 2
|
||||||
DENY = 3
|
DENY = 3
|
||||||
|
PULL = 4
|
||||||
|
|
||||||
|
|
||||||
class DoesNotExist(Exception):
|
class DoesNotExist(Exception):
|
||||||
@@ -39,6 +45,10 @@ class OperationError(Exception):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotUniqueError(OperationError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
RE_TYPE = type(re.compile(''))
|
RE_TYPE = type(re.compile(''))
|
||||||
|
|
||||||
|
|
||||||
@@ -117,7 +127,7 @@ class QueryTreeTransformerVisitor(QNodeVisitor):
|
|||||||
# the necessary parts. Then for each $or part, create a new query
|
# the necessary parts. Then for each $or part, create a new query
|
||||||
# that ANDs the necessary part with the $or part.
|
# that ANDs the necessary part with the $or part.
|
||||||
clauses = []
|
clauses = []
|
||||||
for or_group in itertools.product(*or_groups):
|
for or_group in product(*or_groups):
|
||||||
q_object = reduce(lambda a, b: a & b, and_parts, Q())
|
q_object = reduce(lambda a, b: a & b, and_parts, Q())
|
||||||
q_object = reduce(lambda a, b: a & b, or_group, q_object)
|
q_object = reduce(lambda a, b: a & b, or_group, q_object)
|
||||||
clauses.append(q_object)
|
clauses.append(q_object)
|
||||||
@@ -208,7 +218,7 @@ class QNode(object):
|
|||||||
def _combine(self, other, operation):
|
def _combine(self, other, operation):
|
||||||
"""Combine this node with another node into a QCombination object.
|
"""Combine this node with another node into a QCombination object.
|
||||||
"""
|
"""
|
||||||
if other.empty:
|
if getattr(other, 'empty', True):
|
||||||
return self
|
return self
|
||||||
|
|
||||||
if self.empty:
|
if self.empty:
|
||||||
@@ -326,6 +336,7 @@ class QuerySet(object):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__already_indexed = set()
|
__already_indexed = set()
|
||||||
|
__dereference = False
|
||||||
|
|
||||||
def __init__(self, document, collection):
|
def __init__(self, document, collection):
|
||||||
self._document = document
|
self._document = document
|
||||||
@@ -340,11 +351,14 @@ class QuerySet(object):
|
|||||||
self._timeout = True
|
self._timeout = True
|
||||||
self._class_check = True
|
self._class_check = True
|
||||||
self._slave_okay = False
|
self._slave_okay = False
|
||||||
|
self._iter = False
|
||||||
self._scalar = []
|
self._scalar = []
|
||||||
|
self._as_pymongo = False
|
||||||
|
self._as_pymongo_coerce = False
|
||||||
|
|
||||||
# If inheritance is allowed, only return instances and instances of
|
# If inheritance is allowed, only return instances and instances of
|
||||||
# subclasses of the class being used
|
# subclasses of the class being used
|
||||||
if document._meta.get('allow_inheritance'):
|
if document._meta.get('allow_inheritance') != False:
|
||||||
self._initial_query = {'_types': self._document._class_name}
|
self._initial_query = {'_types': self._document._class_name}
|
||||||
self._loaded_fields = QueryFieldList(always_include=['_cls'])
|
self._loaded_fields = QueryFieldList(always_include=['_cls'])
|
||||||
self._cursor_obj = None
|
self._cursor_obj = None
|
||||||
@@ -386,69 +400,15 @@ class QuerySet(object):
|
|||||||
or a **-** to determine the index ordering
|
or a **-** to determine the index ordering
|
||||||
"""
|
"""
|
||||||
index_spec = QuerySet._build_index_spec(self._document, key_or_list)
|
index_spec = QuerySet._build_index_spec(self._document, key_or_list)
|
||||||
self._collection.ensure_index(
|
index_spec = index_spec.copy()
|
||||||
index_spec['fields'],
|
fields = index_spec.pop('fields')
|
||||||
drop_dups=drop_dups,
|
index_spec['drop_dups'] = drop_dups
|
||||||
background=background,
|
index_spec['background'] = background
|
||||||
sparse=index_spec.get('sparse', False),
|
index_spec.update(kwargs)
|
||||||
unique=index_spec.get('unique', False))
|
|
||||||
|
self._collection.ensure_index(fields, **index_spec)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _build_index_spec(cls, doc_cls, spec):
|
|
||||||
"""Build a PyMongo index spec from a MongoEngine index spec.
|
|
||||||
"""
|
|
||||||
if isinstance(spec, basestring):
|
|
||||||
spec = {'fields': [spec]}
|
|
||||||
if isinstance(spec, (list, tuple)):
|
|
||||||
spec = {'fields': spec}
|
|
||||||
|
|
||||||
index_list = []
|
|
||||||
use_types = doc_cls._meta.get('allow_inheritance', True)
|
|
||||||
for key in spec['fields']:
|
|
||||||
# Get ASCENDING direction from +, DESCENDING from -, and GEO2D from *
|
|
||||||
direction = pymongo.ASCENDING
|
|
||||||
if key.startswith("-"):
|
|
||||||
direction = pymongo.DESCENDING
|
|
||||||
elif key.startswith("*"):
|
|
||||||
direction = pymongo.GEO2D
|
|
||||||
if key.startswith(("+", "-", "*")):
|
|
||||||
key = key[1:]
|
|
||||||
|
|
||||||
# Use real field name, do it manually because we need field
|
|
||||||
# objects for the next part (list field checking)
|
|
||||||
parts = key.split('.')
|
|
||||||
fields = QuerySet._lookup_field(doc_cls, parts)
|
|
||||||
parts = [field.db_field for field in fields]
|
|
||||||
key = '.'.join(parts)
|
|
||||||
index_list.append((key, direction))
|
|
||||||
|
|
||||||
# Check if a list field is being used, don't use _types if it is
|
|
||||||
if use_types and not all(f._index_with_types for f in fields):
|
|
||||||
use_types = False
|
|
||||||
|
|
||||||
# If _types is being used, prepend it to every specified index
|
|
||||||
index_types = doc_cls._meta.get('index_types', True)
|
|
||||||
allow_inheritance = doc_cls._meta.get('allow_inheritance')
|
|
||||||
if spec.get('types', index_types) and allow_inheritance and use_types and direction is not pymongo.GEO2D:
|
|
||||||
index_list.insert(0, ('_types', 1))
|
|
||||||
|
|
||||||
spec['fields'] = index_list
|
|
||||||
|
|
||||||
if spec.get('sparse', False) and len(spec['fields']) > 1:
|
|
||||||
raise ValueError(
|
|
||||||
'Sparse indexes can only have one field in them. '
|
|
||||||
'See https://jira.mongodb.org/browse/SERVER-2193')
|
|
||||||
|
|
||||||
return spec
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def _reset_already_indexed(cls, document=None):
|
|
||||||
"""Helper to reset already indexed, can be useful for testing purposes"""
|
|
||||||
if document:
|
|
||||||
cls.__already_indexed.discard(document)
|
|
||||||
cls.__already_indexed.clear()
|
|
||||||
|
|
||||||
def __call__(self, q_obj=None, class_check=True, slave_okay=False, **query):
|
def __call__(self, q_obj=None, class_check=True, slave_okay=False, **query):
|
||||||
"""Filter the selected documents by calling the
|
"""Filter the selected documents by calling the
|
||||||
:class:`~mongoengine.queryset.QuerySet` with a query.
|
:class:`~mongoengine.queryset.QuerySet` with a query.
|
||||||
@@ -481,13 +441,141 @@ class QuerySet(object):
|
|||||||
"""Returns all documents."""
|
"""Returns all documents."""
|
||||||
return self.__call__()
|
return self.__call__()
|
||||||
|
|
||||||
|
def _ensure_indexes(self):
|
||||||
|
"""Checks the document meta data and ensures all the indexes exist.
|
||||||
|
|
||||||
|
.. note:: You can disable automatic index creation by setting
|
||||||
|
`auto_create_index` to False in the documents meta data
|
||||||
|
"""
|
||||||
|
background = self._document._meta.get('index_background', False)
|
||||||
|
drop_dups = self._document._meta.get('index_drop_dups', False)
|
||||||
|
index_opts = self._document._meta.get('index_opts') or {}
|
||||||
|
index_types = self._document._meta.get('index_types', True)
|
||||||
|
|
||||||
|
# determine if an index which we are creating includes
|
||||||
|
# _type as its first field; if so, we can avoid creating
|
||||||
|
# an extra index on _type, as mongodb will use the existing
|
||||||
|
# index to service queries against _type
|
||||||
|
types_indexed = False
|
||||||
|
|
||||||
|
def includes_types(fields):
|
||||||
|
first_field = None
|
||||||
|
if len(fields):
|
||||||
|
if isinstance(fields[0], basestring):
|
||||||
|
first_field = fields[0]
|
||||||
|
elif isinstance(fields[0], (list, tuple)) and len(fields[0]):
|
||||||
|
first_field = fields[0][0]
|
||||||
|
return first_field == '_types'
|
||||||
|
|
||||||
|
# Ensure indexes created by uniqueness constraints
|
||||||
|
for index in self._document._meta['unique_indexes']:
|
||||||
|
types_indexed = types_indexed or includes_types(index)
|
||||||
|
self._collection.ensure_index(index, unique=True,
|
||||||
|
background=background, drop_dups=drop_dups, **index_opts)
|
||||||
|
|
||||||
|
# Ensure document-defined indexes are created
|
||||||
|
if self._document._meta['index_specs']:
|
||||||
|
index_spec = self._document._meta['index_specs']
|
||||||
|
for spec in index_spec:
|
||||||
|
spec = spec.copy()
|
||||||
|
fields = spec.pop('fields')
|
||||||
|
types_indexed = types_indexed or includes_types(fields)
|
||||||
|
opts = index_opts.copy()
|
||||||
|
opts.update(spec)
|
||||||
|
self._collection.ensure_index(fields,
|
||||||
|
background=background, **opts)
|
||||||
|
|
||||||
|
# If _types is being used (for polymorphism), it needs an index,
|
||||||
|
# only if another index doesn't begin with _types
|
||||||
|
if index_types and '_types' in self._query and not types_indexed:
|
||||||
|
self._collection.ensure_index('_types',
|
||||||
|
background=background, **index_opts)
|
||||||
|
|
||||||
|
# Add geo indicies
|
||||||
|
for field in self._document._geo_indices():
|
||||||
|
index_spec = [(field.db_field, pymongo.GEO2D)]
|
||||||
|
self._collection.ensure_index(index_spec,
|
||||||
|
background=background, **index_opts)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _build_index_spec(cls, doc_cls, spec):
|
||||||
|
"""Build a PyMongo index spec from a MongoEngine index spec.
|
||||||
|
"""
|
||||||
|
if isinstance(spec, basestring):
|
||||||
|
spec = {'fields': [spec]}
|
||||||
|
elif isinstance(spec, (list, tuple)):
|
||||||
|
spec = {'fields': list(spec)}
|
||||||
|
elif isinstance(spec, dict):
|
||||||
|
spec = dict(spec)
|
||||||
|
|
||||||
|
index_list = []
|
||||||
|
direction = None
|
||||||
|
|
||||||
|
allow_inheritance = doc_cls._meta.get('allow_inheritance') != False
|
||||||
|
|
||||||
|
# If sparse - dont include types
|
||||||
|
use_types = allow_inheritance and not spec.get('sparse', False)
|
||||||
|
|
||||||
|
for key in spec['fields']:
|
||||||
|
# If inherited spec continue
|
||||||
|
if isinstance(key, (list, tuple)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Get ASCENDING direction from +, DESCENDING from -, and GEO2D from *
|
||||||
|
direction = pymongo.ASCENDING
|
||||||
|
if key.startswith("-"):
|
||||||
|
direction = pymongo.DESCENDING
|
||||||
|
elif key.startswith("*"):
|
||||||
|
direction = pymongo.GEO2D
|
||||||
|
if key.startswith(("+", "-", "*")):
|
||||||
|
key = key[1:]
|
||||||
|
|
||||||
|
# Use real field name, do it manually because we need field
|
||||||
|
# objects for the next part (list field checking)
|
||||||
|
parts = key.split('.')
|
||||||
|
if parts in (['pk'], ['id'], ['_id']):
|
||||||
|
key = '_id'
|
||||||
|
fields = []
|
||||||
|
else:
|
||||||
|
fields = QuerySet._lookup_field(doc_cls, parts)
|
||||||
|
parts = [field if field == '_id' else field.db_field
|
||||||
|
for field in fields]
|
||||||
|
key = '.'.join(parts)
|
||||||
|
index_list.append((key, direction))
|
||||||
|
|
||||||
|
# Check if a list field is being used, don't use _types if it is
|
||||||
|
if use_types and not all(f._index_with_types for f in fields):
|
||||||
|
use_types = False
|
||||||
|
|
||||||
|
# If _types is being used, prepend it to every specified index
|
||||||
|
index_types = doc_cls._meta.get('index_types', True)
|
||||||
|
|
||||||
|
if (spec.get('types', index_types) and use_types
|
||||||
|
and direction is not pymongo.GEO2D):
|
||||||
|
index_list.insert(0, ('_types', 1))
|
||||||
|
|
||||||
|
spec['fields'] = index_list
|
||||||
|
if spec.get('sparse', False) and len(spec['fields']) > 1:
|
||||||
|
raise ValueError(
|
||||||
|
'Sparse indexes can only have one field in them. '
|
||||||
|
'See https://jira.mongodb.org/browse/SERVER-2193')
|
||||||
|
|
||||||
|
return spec
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _reset_already_indexed(cls, document=None):
|
||||||
|
"""Helper to reset already indexed, can be useful for testing purposes"""
|
||||||
|
if document:
|
||||||
|
cls.__already_indexed.discard(document)
|
||||||
|
cls.__already_indexed.clear()
|
||||||
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _collection(self):
|
def _collection(self):
|
||||||
"""Property that returns the collection object. This allows us to
|
"""Property that returns the collection object. This allows us to
|
||||||
perform operations only if the collection is accessed.
|
perform operations only if the collection is accessed.
|
||||||
"""
|
"""
|
||||||
if self._document not in QuerySet.__already_indexed:
|
if self._document not in QuerySet.__already_indexed:
|
||||||
|
|
||||||
# Ensure collection exists
|
# Ensure collection exists
|
||||||
db = self._document._get_db()
|
db = self._document._get_db()
|
||||||
if self._collection_obj.name not in db.collection_names():
|
if self._collection_obj.name not in db.collection_names():
|
||||||
@@ -496,52 +584,8 @@ class QuerySet(object):
|
|||||||
|
|
||||||
QuerySet.__already_indexed.add(self._document)
|
QuerySet.__already_indexed.add(self._document)
|
||||||
|
|
||||||
background = self._document._meta.get('index_background', False)
|
if self._document._meta.get('auto_create_index', True):
|
||||||
drop_dups = self._document._meta.get('index_drop_dups', False)
|
self._ensure_indexes()
|
||||||
index_opts = self._document._meta.get('index_options', {})
|
|
||||||
index_types = self._document._meta.get('index_types', True)
|
|
||||||
|
|
||||||
# determine if an index which we are creating includes
|
|
||||||
# _type as its first field; if so, we can avoid creating
|
|
||||||
# an extra index on _type, as mongodb will use the existing
|
|
||||||
# index to service queries against _type
|
|
||||||
types_indexed = False
|
|
||||||
def includes_types(fields):
|
|
||||||
first_field = None
|
|
||||||
if len(fields):
|
|
||||||
if isinstance(fields[0], basestring):
|
|
||||||
first_field = fields[0]
|
|
||||||
elif isinstance(fields[0], (list, tuple)) and len(fields[0]):
|
|
||||||
first_field = fields[0][0]
|
|
||||||
return first_field == '_types'
|
|
||||||
|
|
||||||
# Ensure indexes created by uniqueness constraints
|
|
||||||
for index in self._document._meta['unique_indexes']:
|
|
||||||
types_indexed = types_indexed or includes_types(index)
|
|
||||||
self._collection.ensure_index(index, unique=True,
|
|
||||||
background=background, drop_dups=drop_dups, **index_opts)
|
|
||||||
|
|
||||||
# Ensure document-defined indexes are created
|
|
||||||
if self._document._meta['indexes']:
|
|
||||||
for spec in self._document._meta['indexes']:
|
|
||||||
types_indexed = types_indexed or includes_types(spec['fields'])
|
|
||||||
opts = index_opts.copy()
|
|
||||||
opts['unique'] = spec.get('unique', False)
|
|
||||||
opts['sparse'] = spec.get('sparse', False)
|
|
||||||
self._collection.ensure_index(spec['fields'],
|
|
||||||
background=background, **opts)
|
|
||||||
|
|
||||||
# If _types is being used (for polymorphism), it needs an index,
|
|
||||||
# only if another index doesn't begin with _types
|
|
||||||
if index_types and '_types' in self._query and not types_indexed:
|
|
||||||
self._collection.ensure_index('_types',
|
|
||||||
background=background, **index_opts)
|
|
||||||
|
|
||||||
# Add geo indicies
|
|
||||||
for field in self._document._geo_indices():
|
|
||||||
index_spec = [(field.db_field, pymongo.GEO2D)]
|
|
||||||
self._collection.ensure_index(index_spec,
|
|
||||||
background=background, **index_opts)
|
|
||||||
|
|
||||||
return self._collection_obj
|
return self._collection_obj
|
||||||
|
|
||||||
@@ -566,11 +610,13 @@ class QuerySet(object):
|
|||||||
if self._where_clause:
|
if self._where_clause:
|
||||||
self._cursor_obj.where(self._where_clause)
|
self._cursor_obj.where(self._where_clause)
|
||||||
|
|
||||||
# apply default ordering
|
|
||||||
if self._ordering:
|
if self._ordering:
|
||||||
|
# Apply query ordering
|
||||||
self._cursor_obj.sort(self._ordering)
|
self._cursor_obj.sort(self._ordering)
|
||||||
elif self._document._meta['ordering']:
|
elif self._document._meta['ordering']:
|
||||||
|
# Otherwise, apply the ordering from the document model
|
||||||
self.order_by(*self._document._meta['ordering'])
|
self.order_by(*self._document._meta['ordering'])
|
||||||
|
self._cursor_obj.sort(self._ordering)
|
||||||
|
|
||||||
if self._limit is not None:
|
if self._limit is not None:
|
||||||
self._cursor_obj.limit(self._limit - (self._skip or 0))
|
self._cursor_obj.limit(self._limit - (self._skip or 0))
|
||||||
@@ -580,7 +626,6 @@ class QuerySet(object):
|
|||||||
|
|
||||||
if self._hint != -1:
|
if self._hint != -1:
|
||||||
self._cursor_obj.hint(self._hint)
|
self._cursor_obj.hint(self._hint)
|
||||||
|
|
||||||
return self._cursor_obj
|
return self._cursor_obj
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -603,6 +648,7 @@ class QuerySet(object):
|
|||||||
"Can't use index on unsubscriptable field (%s)" % err)
|
"Can't use index on unsubscriptable field (%s)" % err)
|
||||||
fields.append(field_name)
|
fields.append(field_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if field is None:
|
if field is None:
|
||||||
# Look up first field from the document
|
# Look up first field from the document
|
||||||
if field_name == 'pk':
|
if field_name == 'pk':
|
||||||
@@ -611,8 +657,8 @@ class QuerySet(object):
|
|||||||
if field_name in document._fields:
|
if field_name in document._fields:
|
||||||
field = document._fields[field_name]
|
field = document._fields[field_name]
|
||||||
elif document._dynamic:
|
elif document._dynamic:
|
||||||
from base import BaseDynamicField
|
from fields import DynamicField
|
||||||
field = BaseDynamicField(db_field=field_name)
|
field = DynamicField(db_field=field_name)
|
||||||
else:
|
else:
|
||||||
raise InvalidQueryError('Cannot resolve field "%s"'
|
raise InvalidQueryError('Cannot resolve field "%s"'
|
||||||
% field_name)
|
% field_name)
|
||||||
@@ -620,8 +666,11 @@ class QuerySet(object):
|
|||||||
from mongoengine.fields import ReferenceField, GenericReferenceField
|
from mongoengine.fields import ReferenceField, GenericReferenceField
|
||||||
if isinstance(field, (ReferenceField, GenericReferenceField)):
|
if isinstance(field, (ReferenceField, GenericReferenceField)):
|
||||||
raise InvalidQueryError('Cannot perform join in mongoDB: %s' % '__'.join(parts))
|
raise InvalidQueryError('Cannot perform join in mongoDB: %s' % '__'.join(parts))
|
||||||
# Look up subfield on the previous field
|
if hasattr(getattr(field, 'field', None), 'lookup_member'):
|
||||||
new_field = field.lookup_member(field_name)
|
new_field = field.field.lookup_member(field_name)
|
||||||
|
else:
|
||||||
|
# Look up subfield on the previous field
|
||||||
|
new_field = field.lookup_member(field_name)
|
||||||
from base import ComplexBaseField
|
from base import ComplexBaseField
|
||||||
if not new_field and isinstance(field, ComplexBaseField):
|
if not new_field and isinstance(field, ComplexBaseField):
|
||||||
fields.append(field_name)
|
fields.append(field_name)
|
||||||
@@ -654,6 +703,7 @@ class QuerySet(object):
|
|||||||
custom_operators = ['match']
|
custom_operators = ['match']
|
||||||
|
|
||||||
mongo_query = {}
|
mongo_query = {}
|
||||||
|
merge_query = defaultdict(list)
|
||||||
for key, value in query.items():
|
for key, value in query.items():
|
||||||
if key == "__raw__":
|
if key == "__raw__":
|
||||||
mongo_query.update(value)
|
mongo_query.update(value)
|
||||||
@@ -680,7 +730,7 @@ class QuerySet(object):
|
|||||||
cleaned_fields = []
|
cleaned_fields = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
append_field = True
|
append_field = True
|
||||||
if isinstance(field, str):
|
if isinstance(field, basestring):
|
||||||
parts.append(field)
|
parts.append(field)
|
||||||
append_field = False
|
append_field = False
|
||||||
else:
|
else:
|
||||||
@@ -741,8 +791,23 @@ class QuerySet(object):
|
|||||||
key = '.'.join(parts)
|
key = '.'.join(parts)
|
||||||
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 and isinstance(mongo_query[key], dict):
|
elif key in mongo_query:
|
||||||
mongo_query[key].update(value)
|
if key in mongo_query and isinstance(mongo_query[key], dict):
|
||||||
|
mongo_query[key].update(value)
|
||||||
|
else:
|
||||||
|
# Store for manually merging later
|
||||||
|
merge_query[key].append(value)
|
||||||
|
|
||||||
|
# The queryset has been filter in such a way we must manually merge
|
||||||
|
for k, v in merge_query.items():
|
||||||
|
merge_query[k].append(mongo_query[k])
|
||||||
|
del mongo_query[k]
|
||||||
|
if isinstance(v, list):
|
||||||
|
value = [{k:val} for val in v]
|
||||||
|
if '$and' in mongo_query.keys():
|
||||||
|
mongo_query['$and'].append(value)
|
||||||
|
else:
|
||||||
|
mongo_query['$and'] = value
|
||||||
|
|
||||||
return mongo_query
|
return mongo_query
|
||||||
|
|
||||||
@@ -781,15 +846,19 @@ class QuerySet(object):
|
|||||||
dictionary of default values for the new document may be provided as a
|
dictionary of default values for the new document may be provided as a
|
||||||
keyword argument called :attr:`defaults`.
|
keyword argument called :attr:`defaults`.
|
||||||
|
|
||||||
|
.. note:: This requires two separate operations and therefore a
|
||||||
|
race condition exists. Because there are no transactions in mongoDB
|
||||||
|
other approaches should be investigated, to ensure you don't
|
||||||
|
accidently duplicate data when using this method.
|
||||||
|
|
||||||
:param write_options: optional extra keyword arguments used if we
|
:param write_options: optional extra keyword arguments used if we
|
||||||
have to create a new document.
|
have to create a new document.
|
||||||
Passes any write_options onto :meth:`~mongoengine.Document.save`
|
Passes any write_options onto :meth:`~mongoengine.Document.save`
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
|
||||||
|
|
||||||
:param auto_save: if the object is to be saved automatically if not found.
|
:param auto_save: if the object is to be saved automatically if not found.
|
||||||
|
|
||||||
.. versionadded:: 0.6
|
.. versionchanged:: 0.6 - added `auto_save`
|
||||||
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
defaults = query.get('defaults', {})
|
defaults = query.get('defaults', {})
|
||||||
if 'defaults' in query:
|
if 'defaults' in query:
|
||||||
@@ -824,11 +893,21 @@ class QuerySet(object):
|
|||||||
result = None
|
result = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def insert(self, doc_or_docs, load_bulk=True):
|
def insert(self, doc_or_docs, load_bulk=True, safe=False, write_options=None):
|
||||||
"""bulk insert documents
|
"""bulk insert documents
|
||||||
|
|
||||||
|
If ``safe=True`` and the operation is unsuccessful, an
|
||||||
|
:class:`~mongoengine.OperationError` will be raised.
|
||||||
|
|
||||||
:param docs_or_doc: a document or list of documents to be inserted
|
:param docs_or_doc: a document or list of documents to be inserted
|
||||||
:param load_bulk (optional): If True returns the list of document instances
|
:param load_bulk (optional): If True returns the list of document instances
|
||||||
|
:param safe: check if the operation succeeded before returning
|
||||||
|
:param write_options: Extra keyword arguments are passed down to
|
||||||
|
:meth:`~pymongo.collection.Collection.insert`
|
||||||
|
which will be used as options for the resultant ``getLastError`` command.
|
||||||
|
For example, ``insert(..., {w: 2, fsync: True})`` will wait until at least two
|
||||||
|
servers have recorded the write and will force an fsync on each server being
|
||||||
|
written to.
|
||||||
|
|
||||||
By default returns document instances, set ``load_bulk`` to False to
|
By default returns document instances, set ``load_bulk`` to False to
|
||||||
return just ``ObjectIds``
|
return just ``ObjectIds``
|
||||||
@@ -837,6 +916,10 @@ class QuerySet(object):
|
|||||||
"""
|
"""
|
||||||
from document import Document
|
from document import Document
|
||||||
|
|
||||||
|
if not write_options:
|
||||||
|
write_options = {}
|
||||||
|
write_options.update({'safe': safe})
|
||||||
|
|
||||||
docs = doc_or_docs
|
docs = doc_or_docs
|
||||||
return_one = False
|
return_one = False
|
||||||
if isinstance(docs, Document) or issubclass(docs.__class__, Document):
|
if isinstance(docs, Document) or issubclass(docs.__class__, Document):
|
||||||
@@ -848,13 +931,22 @@ class QuerySet(object):
|
|||||||
if not isinstance(doc, self._document):
|
if not isinstance(doc, self._document):
|
||||||
msg = "Some documents inserted aren't instances of %s" % str(self._document)
|
msg = "Some documents inserted aren't instances of %s" % str(self._document)
|
||||||
raise OperationError(msg)
|
raise OperationError(msg)
|
||||||
if doc.pk:
|
if doc.pk and not doc._created:
|
||||||
msg = "Some documents have ObjectIds use doc.update() instead"
|
msg = "Some documents have ObjectIds use doc.update() instead"
|
||||||
raise OperationError(msg)
|
raise OperationError(msg)
|
||||||
raw.append(doc.to_mongo())
|
raw.append(doc.to_mongo())
|
||||||
|
|
||||||
signals.pre_bulk_insert.send(self._document, documents=docs)
|
signals.pre_bulk_insert.send(self._document, documents=docs)
|
||||||
ids = self._collection.insert(raw)
|
try:
|
||||||
|
ids = self._collection.insert(raw, **write_options)
|
||||||
|
except pymongo.errors.OperationFailure, err:
|
||||||
|
message = 'Could not save document (%s)'
|
||||||
|
if re.match('^E1100[01] duplicate key', unicode(err)):
|
||||||
|
# E11000 - duplicate key error index
|
||||||
|
# E11001 - duplicate key on update
|
||||||
|
message = u'Tried to save duplicate unique keys (%s)'
|
||||||
|
raise NotUniqueError(message % unicode(err))
|
||||||
|
raise OperationError(message % unicode(err))
|
||||||
|
|
||||||
if not load_bulk:
|
if not load_bulk:
|
||||||
signals.post_bulk_insert.send(
|
signals.post_bulk_insert.send(
|
||||||
@@ -898,6 +990,9 @@ class QuerySet(object):
|
|||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc_map[doc['_id']] = self._get_scalar(
|
doc_map[doc['_id']] = self._get_scalar(
|
||||||
self._document._from_son(doc))
|
self._document._from_son(doc))
|
||||||
|
elif self._as_pymongo:
|
||||||
|
for doc in docs:
|
||||||
|
doc_map[doc['_id']] = self._get_as_pymongo(doc)
|
||||||
else:
|
else:
|
||||||
for doc in docs:
|
for doc in docs:
|
||||||
doc_map[doc['_id']] = self._document._from_son(doc)
|
doc_map[doc['_id']] = self._document._from_son(doc)
|
||||||
@@ -907,12 +1002,16 @@ class QuerySet(object):
|
|||||||
def next(self):
|
def next(self):
|
||||||
"""Wrap the result in a :class:`~mongoengine.Document` object.
|
"""Wrap the result in a :class:`~mongoengine.Document` object.
|
||||||
"""
|
"""
|
||||||
|
self._iter = True
|
||||||
try:
|
try:
|
||||||
if self._limit == 0:
|
if self._limit == 0:
|
||||||
raise StopIteration
|
raise StopIteration
|
||||||
if self._scalar:
|
if self._scalar:
|
||||||
return self._get_scalar(self._document._from_son(
|
return self._get_scalar(self._document._from_son(
|
||||||
self._cursor.next()))
|
self._cursor.next()))
|
||||||
|
if self._as_pymongo:
|
||||||
|
return self._get_as_pymongo(self._cursor.next())
|
||||||
|
|
||||||
return self._document._from_son(self._cursor.next())
|
return self._document._from_son(self._cursor.next())
|
||||||
except StopIteration, e:
|
except StopIteration, e:
|
||||||
self.rewind()
|
self.rewind()
|
||||||
@@ -923,6 +1022,7 @@ class QuerySet(object):
|
|||||||
|
|
||||||
.. versionadded:: 0.3
|
.. versionadded:: 0.3
|
||||||
"""
|
"""
|
||||||
|
self._iter = False
|
||||||
self._cursor.rewind()
|
self._cursor.rewind()
|
||||||
|
|
||||||
def count(self):
|
def count(self):
|
||||||
@@ -951,6 +1051,8 @@ class QuerySet(object):
|
|||||||
:class:`~bson.code.Code` or string
|
:class:`~bson.code.Code` or string
|
||||||
:param output: output collection name, if set to 'inline' will try to
|
:param output: output collection name, if set to 'inline' will try to
|
||||||
use :class:`~pymongo.collection.Collection.inline_map_reduce`
|
use :class:`~pymongo.collection.Collection.inline_map_reduce`
|
||||||
|
This can also be a dictionary containing output options
|
||||||
|
see: http://docs.mongodb.org/manual/reference/commands/#mapReduce
|
||||||
:param finalize_f: finalize function, an optional function that
|
:param finalize_f: finalize function, an optional function that
|
||||||
performs any post-reduction processing.
|
performs any post-reduction processing.
|
||||||
:param scope: values to insert into map/reduce global scope. Optional.
|
:param scope: values to insert into map/reduce global scope. Optional.
|
||||||
@@ -1092,6 +1194,8 @@ class QuerySet(object):
|
|||||||
if self._scalar:
|
if self._scalar:
|
||||||
return self._get_scalar(self._document._from_son(
|
return self._get_scalar(self._document._from_son(
|
||||||
self._cursor[key]))
|
self._cursor[key]))
|
||||||
|
if self._as_pymongo:
|
||||||
|
return self._get_as_pymongo(self._cursor.next())
|
||||||
return self._document._from_son(self._cursor[key])
|
return self._document._from_son(self._cursor[key])
|
||||||
raise AttributeError
|
raise AttributeError
|
||||||
|
|
||||||
@@ -1102,9 +1206,10 @@ class QuerySet(object):
|
|||||||
|
|
||||||
.. versionadded:: 0.4
|
.. versionadded:: 0.4
|
||||||
.. versionchanged:: 0.5 - Fixed handling references
|
.. versionchanged:: 0.5 - Fixed handling references
|
||||||
|
.. versionchanged:: 0.6 - Improved db_field refrence handling
|
||||||
"""
|
"""
|
||||||
from dereference import DeReference
|
return self._dereference(self._cursor.distinct(field), 1,
|
||||||
return DeReference()(self._cursor.distinct(field), 1)
|
name=field, instance=self._document)
|
||||||
|
|
||||||
def only(self, *fields):
|
def only(self, *fields):
|
||||||
"""Load only a subset of this document's fields. ::
|
"""Load only a subset of this document's fields. ::
|
||||||
@@ -1209,7 +1314,7 @@ class QuerySet(object):
|
|||||||
key_list.append((key, direction))
|
key_list.append((key, direction))
|
||||||
|
|
||||||
self._ordering = key_list
|
self._ordering = key_list
|
||||||
self._cursor.sort(key_list)
|
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def explain(self, format=False):
|
def explain(self, format=False):
|
||||||
@@ -1259,9 +1364,16 @@ class QuerySet(object):
|
|||||||
"""
|
"""
|
||||||
doc = self._document
|
doc = self._document
|
||||||
|
|
||||||
|
# Handle deletes where skips or limits have been applied
|
||||||
|
if self._skip or self._limit:
|
||||||
|
for doc in self:
|
||||||
|
doc.delete()
|
||||||
|
return
|
||||||
|
|
||||||
|
delete_rules = doc._meta.get('delete_rules') or {}
|
||||||
# Check for DENY rules before actually deleting/nullifying any other
|
# Check for DENY rules before actually deleting/nullifying any other
|
||||||
# references
|
# references
|
||||||
for rule_entry in doc._meta['delete_rules']:
|
for rule_entry in delete_rules:
|
||||||
document_cls, field_name = rule_entry
|
document_cls, field_name = rule_entry
|
||||||
rule = doc._meta['delete_rules'][rule_entry]
|
rule = doc._meta['delete_rules'][rule_entry]
|
||||||
if rule == DENY and document_cls.objects(**{field_name + '__in': self}).count() > 0:
|
if rule == DENY and document_cls.objects(**{field_name + '__in': self}).count() > 0:
|
||||||
@@ -1269,15 +1381,23 @@ class QuerySet(object):
|
|||||||
(document_cls.__name__, field_name)
|
(document_cls.__name__, field_name)
|
||||||
raise OperationError(msg)
|
raise OperationError(msg)
|
||||||
|
|
||||||
for rule_entry in doc._meta['delete_rules']:
|
for rule_entry in delete_rules:
|
||||||
document_cls, field_name = rule_entry
|
document_cls, field_name = rule_entry
|
||||||
rule = doc._meta['delete_rules'][rule_entry]
|
rule = doc._meta['delete_rules'][rule_entry]
|
||||||
if rule == CASCADE:
|
if rule == CASCADE:
|
||||||
document_cls.objects(**{field_name + '__in': self}).delete(safe=safe)
|
ref_q = document_cls.objects(**{field_name + '__in': self})
|
||||||
|
ref_q_count = ref_q.count()
|
||||||
|
if (doc != document_cls and ref_q_count > 0
|
||||||
|
or (doc == document_cls and ref_q_count > 0)):
|
||||||
|
ref_q.delete(safe=safe)
|
||||||
elif rule == NULLIFY:
|
elif rule == NULLIFY:
|
||||||
document_cls.objects(**{field_name + '__in': self}).update(
|
document_cls.objects(**{field_name + '__in': self}).update(
|
||||||
safe_update=safe,
|
safe_update=safe,
|
||||||
**{'unset__%s' % field_name: 1})
|
**{'unset__%s' % field_name: 1})
|
||||||
|
elif rule == PULL:
|
||||||
|
document_cls.objects(**{field_name + '__in': self}).update(
|
||||||
|
safe_update=safe,
|
||||||
|
**{'pull_all__%s' % field_name: self})
|
||||||
|
|
||||||
self._collection.remove(self._query, safe=safe)
|
self._collection.remove(self._query, safe=safe)
|
||||||
|
|
||||||
@@ -1287,6 +1407,8 @@ class QuerySet(object):
|
|||||||
"""
|
"""
|
||||||
operators = ['set', 'unset', 'inc', 'dec', 'pop', 'push', 'push_all',
|
operators = ['set', 'unset', 'inc', 'dec', 'pop', 'push', 'push_all',
|
||||||
'pull', 'pull_all', 'add_to_set']
|
'pull', 'pull_all', 'add_to_set']
|
||||||
|
match_operators = ['ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod',
|
||||||
|
'all', 'size', 'exists', 'not']
|
||||||
|
|
||||||
mongo_update = {}
|
mongo_update = {}
|
||||||
for key, value in update.items():
|
for key, value in update.items():
|
||||||
@@ -1310,6 +1432,10 @@ class QuerySet(object):
|
|||||||
elif op == 'add_to_set':
|
elif op == 'add_to_set':
|
||||||
op = op.replace('_to_set', 'ToSet')
|
op = op.replace('_to_set', 'ToSet')
|
||||||
|
|
||||||
|
match = None
|
||||||
|
if parts[-1] in match_operators:
|
||||||
|
match = parts.pop()
|
||||||
|
|
||||||
if _doc_cls:
|
if _doc_cls:
|
||||||
# Switch field names to proper names [set in Field(name='foo')]
|
# Switch field names to proper names [set in Field(name='foo')]
|
||||||
fields = QuerySet._lookup_field(_doc_cls, parts)
|
fields = QuerySet._lookup_field(_doc_cls, parts)
|
||||||
@@ -1318,7 +1444,7 @@ class QuerySet(object):
|
|||||||
cleaned_fields = []
|
cleaned_fields = []
|
||||||
for field in fields:
|
for field in fields:
|
||||||
append_field = True
|
append_field = True
|
||||||
if isinstance(field, str):
|
if isinstance(field, basestring):
|
||||||
# Convert the S operator to $
|
# Convert the S operator to $
|
||||||
if field == 'S':
|
if field == 'S':
|
||||||
field = '$'
|
field = '$'
|
||||||
@@ -1332,20 +1458,42 @@ class QuerySet(object):
|
|||||||
# Convert value to proper value
|
# Convert value to proper value
|
||||||
field = cleaned_fields[-1]
|
field = cleaned_fields[-1]
|
||||||
|
|
||||||
if op in (None, 'set', 'push', 'pull', 'addToSet'):
|
if op in (None, 'set', 'push', 'pull'):
|
||||||
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'):
|
||||||
value = [field.prepare_query_value(op, v) for v in value]
|
value = [field.prepare_query_value(op, v) for v in value]
|
||||||
|
elif op == 'addToSet':
|
||||||
|
if isinstance(value, (list, tuple, set)):
|
||||||
|
value = [field.prepare_query_value(op, v) for v in value]
|
||||||
|
elif field.required or value is not None:
|
||||||
|
value = field.prepare_query_value(op, value)
|
||||||
|
|
||||||
|
if match:
|
||||||
|
match = '$' + match
|
||||||
|
value = {match: value}
|
||||||
|
|
||||||
key = '.'.join(parts)
|
key = '.'.join(parts)
|
||||||
|
|
||||||
if not op:
|
if not op:
|
||||||
raise InvalidQueryError("Updates must supply an operation eg: set__FIELD=value")
|
raise InvalidQueryError("Updates must supply an operation "
|
||||||
|
"eg: set__FIELD=value")
|
||||||
|
|
||||||
if op:
|
if 'pull' in op and '.' in key:
|
||||||
|
# Dot operators don't work on pull operations
|
||||||
|
# it uses nested dict syntax
|
||||||
|
if op == 'pullAll':
|
||||||
|
raise InvalidQueryError("pullAll operations only support "
|
||||||
|
"a single field depth")
|
||||||
|
|
||||||
|
parts.reverse()
|
||||||
|
for key in parts:
|
||||||
|
value = {key: value}
|
||||||
|
elif op == 'addToSet' and isinstance(value, list):
|
||||||
|
value = {key: {"$each": value}}
|
||||||
|
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
|
||||||
@@ -1435,8 +1583,6 @@ class QuerySet(object):
|
|||||||
def lookup(obj, name):
|
def lookup(obj, name):
|
||||||
chunks = name.split('__')
|
chunks = name.split('__')
|
||||||
for chunk in chunks:
|
for chunk in chunks:
|
||||||
if hasattr(obj, '_db_field_map'):
|
|
||||||
chunk = obj._db_field_map.get(chunk, chunk)
|
|
||||||
obj = getattr(obj, chunk)
|
obj = getattr(obj, chunk)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
@@ -1446,6 +1592,48 @@ class QuerySet(object):
|
|||||||
|
|
||||||
return tuple(data)
|
return tuple(data)
|
||||||
|
|
||||||
|
def _get_as_pymongo(self, row):
|
||||||
|
# Extract which fields paths we should follow if .fields(...) was
|
||||||
|
# used. If not, handle all fields.
|
||||||
|
if not getattr(self, '__as_pymongo_fields', None):
|
||||||
|
self.__as_pymongo_fields = []
|
||||||
|
for field in self._loaded_fields.fields - set(['_cls', '_id', '_types']):
|
||||||
|
self.__as_pymongo_fields.append(field)
|
||||||
|
while '.' in field:
|
||||||
|
field, _ = field.rsplit('.', 1)
|
||||||
|
self.__as_pymongo_fields.append(field)
|
||||||
|
|
||||||
|
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 or new_path in self.__as_pymongo_fields:
|
||||||
|
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(...)
|
||||||
|
from mongoengine.fields import 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 scalar(self, *fields):
|
def scalar(self, *fields):
|
||||||
"""Instead of returning Document instances, return either a specific
|
"""Instead of returning Document instances, return either a specific
|
||||||
value or a tuple of values in order.
|
value or a tuple of values in order.
|
||||||
@@ -1468,6 +1656,16 @@ class QuerySet(object):
|
|||||||
"""An alias for scalar"""
|
"""An alias for scalar"""
|
||||||
return self.scalar(*fields)
|
return self.scalar(*fields)
|
||||||
|
|
||||||
|
def as_pymongo(self, coerce_types=False):
|
||||||
|
"""Instead of returning Document instances, return raw values from
|
||||||
|
pymongo.
|
||||||
|
|
||||||
|
:param coerce_type: Field types (if applicable) would be use to coerce types.
|
||||||
|
"""
|
||||||
|
self._as_pymongo = True
|
||||||
|
self._as_pymongo_coerce = coerce_types
|
||||||
|
return self
|
||||||
|
|
||||||
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
|
||||||
*fieldname* is the Python name of a field, *fieldname* will be
|
*fieldname* is the Python name of a field, *fieldname* will be
|
||||||
@@ -1648,10 +1846,11 @@ class QuerySet(object):
|
|||||||
def _item_frequencies_map_reduce(self, field, normalize=False):
|
def _item_frequencies_map_reduce(self, field, normalize=False):
|
||||||
map_func = """
|
map_func = """
|
||||||
function() {
|
function() {
|
||||||
path = '{{~%(field)s}}'.split('.');
|
var path = '{{~%(field)s}}'.split('.');
|
||||||
field = this;
|
var field = this;
|
||||||
|
|
||||||
for (p in path) {
|
for (p in path) {
|
||||||
if (field)
|
if (typeof field != 'undefined')
|
||||||
field = field[path[p]];
|
field = field[path[p]];
|
||||||
else
|
else
|
||||||
break;
|
break;
|
||||||
@@ -1660,7 +1859,7 @@ class QuerySet(object):
|
|||||||
field.forEach(function(item) {
|
field.forEach(function(item) {
|
||||||
emit(item, 1);
|
emit(item, 1);
|
||||||
});
|
});
|
||||||
} else if (field) {
|
} else if (typeof field != 'undefined') {
|
||||||
emit(field, 1);
|
emit(field, 1);
|
||||||
} else {
|
} else {
|
||||||
emit(null, 1);
|
emit(null, 1);
|
||||||
@@ -1684,12 +1883,12 @@ class QuerySet(object):
|
|||||||
if isinstance(key, float):
|
if isinstance(key, float):
|
||||||
if int(key) == key:
|
if int(key) == key:
|
||||||
key = int(key)
|
key = int(key)
|
||||||
key = str(key)
|
frequencies[key] = int(f.value)
|
||||||
frequencies[key] = f.value
|
|
||||||
|
|
||||||
if normalize:
|
if normalize:
|
||||||
count = sum(frequencies.values())
|
count = sum(frequencies.values())
|
||||||
frequencies = dict([(k, v / count) for k, v in frequencies.items()])
|
frequencies = dict([(k, float(v) / count)
|
||||||
|
for k, v in frequencies.items()])
|
||||||
|
|
||||||
return frequencies
|
return frequencies
|
||||||
|
|
||||||
@@ -1697,31 +1896,28 @@ class QuerySet(object):
|
|||||||
"""Uses exec_js to execute"""
|
"""Uses exec_js to execute"""
|
||||||
freq_func = """
|
freq_func = """
|
||||||
function(path) {
|
function(path) {
|
||||||
path = path.split('.');
|
var path = path.split('.');
|
||||||
|
|
||||||
if (options.normalize) {
|
var total = 0.0;
|
||||||
var total = 0.0;
|
db[collection].find(query).forEach(function(doc) {
|
||||||
db[collection].find(query).forEach(function(doc) {
|
var field = doc;
|
||||||
field = doc;
|
for (p in path) {
|
||||||
for (p in path) {
|
if (field)
|
||||||
if (field)
|
field = field[path[p]];
|
||||||
field = field[path[p]];
|
else
|
||||||
else
|
break;
|
||||||
break;
|
}
|
||||||
}
|
if (field && field.constructor == Array) {
|
||||||
if (field && field.constructor == Array) {
|
total += field.length;
|
||||||
total += field.length;
|
} else {
|
||||||
} else {
|
total++;
|
||||||
total++;
|
}
|
||||||
}
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var frequencies = {};
|
var frequencies = {};
|
||||||
|
var types = {};
|
||||||
var inc = 1.0;
|
var inc = 1.0;
|
||||||
if (options.normalize) {
|
|
||||||
inc /= total;
|
|
||||||
}
|
|
||||||
db[collection].find(query).forEach(function(doc) {
|
db[collection].find(query).forEach(function(doc) {
|
||||||
field = doc;
|
field = doc;
|
||||||
for (p in path) {
|
for (p in path) {
|
||||||
@@ -1736,34 +1932,48 @@ class QuerySet(object):
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var item = field;
|
var item = field;
|
||||||
|
types[item] = item;
|
||||||
frequencies[item] = inc + (isNaN(frequencies[item]) ? 0: frequencies[item]);
|
frequencies[item] = inc + (isNaN(frequencies[item]) ? 0: frequencies[item]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return frequencies;
|
return [total, frequencies, types];
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
data = self.exec_js(freq_func, field, normalize=normalize)
|
total, data, types = self.exec_js(freq_func, field)
|
||||||
if 'undefined' in data:
|
values = dict([(types.get(k), int(v)) for k, v in data.iteritems()])
|
||||||
data[None] = data['undefined']
|
|
||||||
del(data['undefined'])
|
if normalize:
|
||||||
return data
|
values = dict([(k, float(v) / total) for k, v in values.items()])
|
||||||
|
|
||||||
|
frequencies = {}
|
||||||
|
for k, v in values.iteritems():
|
||||||
|
if isinstance(k, float):
|
||||||
|
if int(k) == k:
|
||||||
|
k = int(k)
|
||||||
|
|
||||||
|
frequencies[k] = v
|
||||||
|
|
||||||
|
return frequencies
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
limit = REPR_OUTPUT_SIZE + 1
|
"""Provides the string representation of the QuerySet
|
||||||
start = (0 if self._skip is None else self._skip)
|
|
||||||
if self._limit is None:
|
.. versionchanged:: 0.6.13 Now doesnt modify the cursor
|
||||||
stop = start + limit
|
"""
|
||||||
if self._limit is not None:
|
|
||||||
if self._limit - start > limit:
|
if self._iter:
|
||||||
stop = start + limit
|
return '.. queryset mid-iteration ..'
|
||||||
else:
|
|
||||||
stop = self._limit
|
data = []
|
||||||
try:
|
for i in xrange(REPR_OUTPUT_SIZE + 1):
|
||||||
data = list(self[start:stop])
|
try:
|
||||||
except pymongo.errors.InvalidOperation:
|
data.append(self.next())
|
||||||
return ".. queryset mid-iteration .."
|
except StopIteration:
|
||||||
|
break
|
||||||
if len(data) > REPR_OUTPUT_SIZE:
|
if len(data) > REPR_OUTPUT_SIZE:
|
||||||
data[-1] = "...(remaining elements truncated)..."
|
data[-1] = "...(remaining elements truncated)..."
|
||||||
|
|
||||||
|
self.rewind()
|
||||||
return repr(data)
|
return repr(data)
|
||||||
|
|
||||||
def select_related(self, max_depth=1):
|
def select_related(self, max_depth=1):
|
||||||
@@ -1772,13 +1982,30 @@ class QuerySet(object):
|
|||||||
|
|
||||||
.. versionadded:: 0.5
|
.. versionadded:: 0.5
|
||||||
"""
|
"""
|
||||||
from dereference import DeReference
|
|
||||||
# Make select related work the same for querysets
|
# Make select related work the same for querysets
|
||||||
max_depth += 1
|
max_depth += 1
|
||||||
return DeReference()(self, max_depth=max_depth)
|
return self._dereference(self, max_depth=max_depth)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _dereference(self):
|
||||||
|
if not self.__dereference:
|
||||||
|
from dereference import DeReference
|
||||||
|
self.__dereference = DeReference() # Cached
|
||||||
|
return self.__dereference
|
||||||
|
|
||||||
|
|
||||||
class QuerySetManager(object):
|
class QuerySetManager(object):
|
||||||
|
"""
|
||||||
|
The default QuerySet Manager.
|
||||||
|
|
||||||
|
Custom QuerySet Manager functions can extend this class and users can
|
||||||
|
add extra queryset functionality. Any custom manager methods must accept a
|
||||||
|
:class:`~mongoengine.Document` class as its first argument, and a
|
||||||
|
:class:`~mongoengine.queryset.QuerySet` as its second argument.
|
||||||
|
|
||||||
|
The method function should return a :class:`~mongoengine.queryset.QuerySet`
|
||||||
|
, probably the same one that was passed in, but modified in some way.
|
||||||
|
"""
|
||||||
|
|
||||||
get_queryset = None
|
get_queryset = None
|
||||||
|
|
||||||
@@ -1796,13 +2023,16 @@ class QuerySetManager(object):
|
|||||||
return self
|
return self
|
||||||
|
|
||||||
# owner is the document that contains the QuerySetManager
|
# owner is the document that contains the QuerySetManager
|
||||||
queryset_class = owner._meta['queryset_class'] or QuerySet
|
queryset_class = owner._meta.get('queryset_class') or QuerySet
|
||||||
queryset = queryset_class(owner, owner._get_collection())
|
queryset = queryset_class(owner, owner._get_collection())
|
||||||
if self.get_queryset:
|
if self.get_queryset:
|
||||||
if self.get_queryset.func_code.co_argcount == 1:
|
arg_count = self.get_queryset.func_code.co_argcount
|
||||||
|
if arg_count == 1:
|
||||||
queryset = self.get_queryset(queryset)
|
queryset = self.get_queryset(queryset)
|
||||||
else:
|
elif arg_count == 2:
|
||||||
queryset = self.get_queryset(owner, queryset)
|
queryset = self.get_queryset(owner, queryset)
|
||||||
|
else:
|
||||||
|
queryset = partial(self.get_queryset, owner, queryset)
|
||||||
return queryset
|
return queryset
|
||||||
|
|
||||||
|
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
%define srcname mongoengine
|
%define srcname mongoengine
|
||||||
|
|
||||||
Name: python-%{srcname}
|
Name: python-%{srcname}
|
||||||
Version: 0.6.3
|
Version: 0.7.9
|
||||||
Release: 1%{?dist}
|
Release: 1%{?dist}
|
||||||
Summary: A Python Document-Object Mapper for working with MongoDB
|
Summary: A Python Document-Object Mapper for working with MongoDB
|
||||||
|
|
||||||
@@ -51,12 +51,4 @@ rm -rf $RPM_BUILD_ROOT
|
|||||||
# %{python_sitearch}/*
|
# %{python_sitearch}/*
|
||||||
|
|
||||||
%changelog
|
%changelog
|
||||||
* Mon Mar 05 2012 Ross Lawley <ross.lawley@gmail.com> 0.6
|
* See: http://readthedocs.org/docs/mongoengine-odm/en/latest/changelog.html
|
||||||
- 0.6 released
|
|
||||||
* Thu Oct 27 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.3-1
|
|
||||||
- Update to latest dev version
|
|
||||||
- Add PIL dependency for ImageField
|
|
||||||
* Wed Oct 12 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.2-1
|
|
||||||
- Update version
|
|
||||||
* Fri Sep 23 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.0-1
|
|
||||||
- Initial version
|
|
11
setup.cfg
Normal file
11
setup.cfg
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
[nosetests]
|
||||||
|
verbosity = 3
|
||||||
|
detailed-errors = 1
|
||||||
|
#with-coverage = 1
|
||||||
|
#cover-erase = 1
|
||||||
|
#cover-html = 1
|
||||||
|
#cover-html-dir = ../htmlcov
|
||||||
|
#cover-package = mongoengine
|
||||||
|
py3where = build
|
||||||
|
where = tests
|
||||||
|
#tests = test_bugfix.py
|
50
setup.py
50
setup.py
@@ -1,27 +1,35 @@
|
|||||||
from setuptools import setup, find_packages
|
|
||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
from setuptools import setup, find_packages
|
||||||
|
|
||||||
DESCRIPTION = "A Python Document-Object Mapper for working with MongoDB"
|
# Hack to silence atexit traceback in newer python versions
|
||||||
|
try:
|
||||||
|
import multiprocessing
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
DESCRIPTION = """MongoEngine is a Python Object-Document
|
||||||
|
Mapper for working with MongoDB."""
|
||||||
LONG_DESCRIPTION = None
|
LONG_DESCRIPTION = None
|
||||||
try:
|
try:
|
||||||
LONG_DESCRIPTION = open('README.rst').read()
|
LONG_DESCRIPTION = open('README.rst').read()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def get_version(version_tuple):
|
def get_version(version_tuple):
|
||||||
version = '%s.%s' % (version_tuple[0], version_tuple[1])
|
if not isinstance(version_tuple[-1], int):
|
||||||
if version_tuple[2]:
|
return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1]
|
||||||
version = '%s.%s' % (version, version_tuple[2])
|
return '.'.join(map(str, version_tuple))
|
||||||
return version
|
|
||||||
|
|
||||||
# Dirty hack to get version number from monogengine/__init__.py - we can't
|
# Dirty hack to get version number from monogengine/__init__.py - we can't
|
||||||
# import it as it depends on PyMongo and PyMongo isn't installed until this
|
# import it as it depends on PyMongo and PyMongo isn't installed until this
|
||||||
# file is read
|
# file is read
|
||||||
init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
|
init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
|
||||||
version_line = filter(lambda l: l.startswith('VERSION'), open(init))[0]
|
version_line = list(filter(lambda l: l.startswith('VERSION'), open(init)))[0]
|
||||||
|
|
||||||
VERSION = get_version(eval(version_line.split('=')[-1]))
|
VERSION = get_version(eval(version_line.split('=')[-1]))
|
||||||
print VERSION
|
print(VERSION)
|
||||||
|
|
||||||
CLASSIFIERS = [
|
CLASSIFIERS = [
|
||||||
'Development Status :: 4 - Beta',
|
'Development Status :: 4 - Beta',
|
||||||
@@ -29,18 +37,38 @@ CLASSIFIERS = [
|
|||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Operating System :: OS Independent',
|
'Operating System :: OS Independent',
|
||||||
'Programming Language :: Python',
|
'Programming Language :: Python',
|
||||||
|
"Programming Language :: Python :: 2",
|
||||||
|
"Programming Language :: Python :: 2.5",
|
||||||
|
"Programming Language :: Python :: 2.6",
|
||||||
|
"Programming Language :: Python :: 2.7",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.1",
|
||||||
|
"Programming Language :: Python :: 3.2",
|
||||||
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
'Topic :: Database',
|
'Topic :: Database',
|
||||||
'Topic :: Software Development :: Libraries :: Python Modules',
|
'Topic :: Software Development :: Libraries :: Python Modules',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
extra_opts = {}
|
||||||
|
if sys.version_info[0] == 3:
|
||||||
|
extra_opts['use_2to3'] = True
|
||||||
|
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker']
|
||||||
|
extra_opts['packages'] = find_packages(exclude=('tests',))
|
||||||
|
if "test" in sys.argv or "nosetests" in sys.argv:
|
||||||
|
extra_opts['packages'].append("tests")
|
||||||
|
extra_opts['package_data'] = {"tests": ["mongoengine.png"]}
|
||||||
|
else:
|
||||||
|
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'django>=1.3', 'PIL']
|
||||||
|
extra_opts['packages'] = find_packages(exclude=('tests',))
|
||||||
|
|
||||||
setup(name='mongoengine',
|
setup(name='mongoengine',
|
||||||
version=VERSION,
|
version=VERSION,
|
||||||
packages=find_packages(),
|
|
||||||
author='Harry Marr',
|
author='Harry Marr',
|
||||||
author_email='harry.marr@{nospam}gmail.com',
|
author_email='harry.marr@{nospam}gmail.com',
|
||||||
maintainer="Ross Lawley",
|
maintainer="Ross Lawley",
|
||||||
maintainer_email="ross.lawley@{nospam}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',
|
||||||
license='MIT',
|
license='MIT',
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
description=DESCRIPTION,
|
description=DESCRIPTION,
|
||||||
@@ -48,6 +76,6 @@ setup(name='mongoengine',
|
|||||||
platforms=['any'],
|
platforms=['any'],
|
||||||
classifiers=CLASSIFIERS,
|
classifiers=CLASSIFIERS,
|
||||||
install_requires=['pymongo'],
|
install_requires=['pymongo'],
|
||||||
test_suite='tests',
|
test_suite='nose.collector',
|
||||||
tests_require=['blinker', 'django>=1.3', 'PIL']
|
**extra_opts
|
||||||
)
|
)
|
||||||
|
98
tests/test_all_warnings.py
Normal file
98
tests/test_all_warnings.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import unittest
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine.tests import query_counter
|
||||||
|
|
||||||
|
|
||||||
|
class TestWarnings(unittest.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
conn = connect(db='mongoenginetest')
|
||||||
|
self.warning_list = []
|
||||||
|
self.showwarning_default = warnings.showwarning
|
||||||
|
warnings.showwarning = self.append_to_warning_list
|
||||||
|
|
||||||
|
def append_to_warning_list(self, message, category, *args):
|
||||||
|
self.warning_list.append({"message": message,
|
||||||
|
"category": category})
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# restore default handling of warnings
|
||||||
|
warnings.showwarning = self.showwarning_default
|
||||||
|
|
||||||
|
def test_allow_inheritance_future_warning(self):
|
||||||
|
"""Add FutureWarning for future allow_inhertiance default change.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class SimpleBase(Document):
|
||||||
|
a = IntField()
|
||||||
|
|
||||||
|
class InheritedClass(SimpleBase):
|
||||||
|
b = IntField()
|
||||||
|
|
||||||
|
InheritedClass()
|
||||||
|
self.assertEqual(len(self.warning_list), 1)
|
||||||
|
warning = self.warning_list[0]
|
||||||
|
self.assertEqual(FutureWarning, warning["category"])
|
||||||
|
self.assertTrue("InheritedClass" in str(warning["message"]))
|
||||||
|
|
||||||
|
def test_dbref_reference_field_future_warning(self):
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
parent = ReferenceField('self')
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
p1 = Person()
|
||||||
|
p1.parent = None
|
||||||
|
p1.save()
|
||||||
|
|
||||||
|
p2 = Person(name="Wilson Jr")
|
||||||
|
p2.parent = p1
|
||||||
|
p2.save(cascade=False)
|
||||||
|
|
||||||
|
self.assertTrue(len(self.warning_list) > 0)
|
||||||
|
warning = self.warning_list[0]
|
||||||
|
self.assertEqual(FutureWarning, warning["category"])
|
||||||
|
self.assertTrue("ReferenceFields will default to using ObjectId"
|
||||||
|
in str(warning["message"]))
|
||||||
|
|
||||||
|
def test_document_save_cascade_future_warning(self):
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
parent = ReferenceField('self')
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
p1 = Person(name="Wilson Snr")
|
||||||
|
p1.parent = None
|
||||||
|
p1.save()
|
||||||
|
|
||||||
|
p2 = Person(name="Wilson Jr")
|
||||||
|
p2.parent = p1
|
||||||
|
p2.parent.name = "Poppa Wilson"
|
||||||
|
p2.save()
|
||||||
|
|
||||||
|
self.assertEqual(len(self.warning_list), 1)
|
||||||
|
if len(self.warning_list) > 1:
|
||||||
|
print self.warning_list
|
||||||
|
warning = self.warning_list[0]
|
||||||
|
self.assertEqual(FutureWarning, warning["category"])
|
||||||
|
self.assertTrue("Cascading saves will default to off in 0.8"
|
||||||
|
in str(warning["message"]))
|
||||||
|
|
||||||
|
def test_document_collection_syntax_warning(self):
|
||||||
|
|
||||||
|
class NonAbstractBase(Document):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class InheritedDocumentFailTest(NonAbstractBase):
|
||||||
|
meta = {'collection': 'fail'}
|
||||||
|
|
||||||
|
warning = self.warning_list[0]
|
||||||
|
self.assertEqual(SyntaxWarning, warning["category"])
|
||||||
|
self.assertEqual('non_abstract_base',
|
||||||
|
InheritedDocumentFailTest._get_collection_name())
|
@@ -1,8 +1,11 @@
|
|||||||
import unittest
|
import datetime
|
||||||
import pymongo
|
import pymongo
|
||||||
|
import unittest
|
||||||
|
|
||||||
import mongoengine.connection
|
import mongoengine.connection
|
||||||
|
|
||||||
|
from bson.tz_util import utc
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db, get_connection, ConnectionError
|
from mongoengine.connection import get_db, get_connection, ConnectionError
|
||||||
|
|
||||||
@@ -65,6 +68,31 @@ class ConnectionTest(unittest.TestCase):
|
|||||||
self.assertTrue(isinstance(db, pymongo.database.Database))
|
self.assertTrue(isinstance(db, pymongo.database.Database))
|
||||||
self.assertEqual(db.name, 'mongoenginetest2')
|
self.assertEqual(db.name, 'mongoenginetest2')
|
||||||
|
|
||||||
|
def test_connection_kwargs(self):
|
||||||
|
"""Ensure that connection kwargs get passed to pymongo.
|
||||||
|
"""
|
||||||
|
connect('mongoenginetest', alias='t1', tz_aware=True)
|
||||||
|
conn = get_connection('t1')
|
||||||
|
|
||||||
|
self.assertTrue(conn.tz_aware)
|
||||||
|
|
||||||
|
connect('mongoenginetest2', alias='t2')
|
||||||
|
conn = get_connection('t2')
|
||||||
|
self.assertFalse(conn.tz_aware)
|
||||||
|
|
||||||
|
def test_datetime(self):
|
||||||
|
connect('mongoenginetest', tz_aware=True)
|
||||||
|
d = datetime.datetime(2010, 5, 5, tzinfo=utc)
|
||||||
|
|
||||||
|
class DateDoc(Document):
|
||||||
|
the_date = DateTimeField(required=True)
|
||||||
|
|
||||||
|
DateDoc.drop_collection()
|
||||||
|
DateDoc(the_date=d).save()
|
||||||
|
|
||||||
|
date_doc = DateDoc.objects.first()
|
||||||
|
self.assertEqual(d, date_doc.the_date)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
@@ -1,5 +1,8 @@
|
|||||||
|
from __future__ import with_statement
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
|
from bson import DBRef, ObjectId
|
||||||
|
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.connection import get_db
|
from mongoengine.connection import get_db
|
||||||
from mongoengine.tests import query_counter
|
from mongoengine.tests import query_counter
|
||||||
@@ -39,6 +42,12 @@ class FieldTest(unittest.TestCase):
|
|||||||
group_obj = Group.objects.first()
|
group_obj = Group.objects.first()
|
||||||
self.assertEqual(q, 1)
|
self.assertEqual(q, 1)
|
||||||
|
|
||||||
|
len(group_obj._data['members'])
|
||||||
|
self.assertEqual(q, 1)
|
||||||
|
|
||||||
|
len(group_obj.members)
|
||||||
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
[m for m in group_obj.members]
|
[m for m in group_obj.members]
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
@@ -63,6 +72,131 @@ class FieldTest(unittest.TestCase):
|
|||||||
User.drop_collection()
|
User.drop_collection()
|
||||||
Group.drop_collection()
|
Group.drop_collection()
|
||||||
|
|
||||||
|
def test_list_item_dereference_dref_false(self):
|
||||||
|
"""Ensure that DBRef items in ListFields are dereferenced.
|
||||||
|
"""
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Group(Document):
|
||||||
|
members = ListField(ReferenceField(User, dbref=False))
|
||||||
|
|
||||||
|
User.drop_collection()
|
||||||
|
Group.drop_collection()
|
||||||
|
|
||||||
|
for i in xrange(1, 51):
|
||||||
|
user = User(name='user %s' % i)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
group = Group(members=User.objects)
|
||||||
|
group.save()
|
||||||
|
group.reload() # Confirm reload works
|
||||||
|
|
||||||
|
with query_counter() as q:
|
||||||
|
self.assertEqual(q, 0)
|
||||||
|
|
||||||
|
group_obj = Group.objects.first()
|
||||||
|
self.assertEqual(q, 1)
|
||||||
|
|
||||||
|
[m for m in group_obj.members]
|
||||||
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
|
# Document select_related
|
||||||
|
with query_counter() as q:
|
||||||
|
self.assertEqual(q, 0)
|
||||||
|
|
||||||
|
group_obj = Group.objects.first().select_related()
|
||||||
|
|
||||||
|
self.assertEqual(q, 2)
|
||||||
|
[m for m in group_obj.members]
|
||||||
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
|
# Queryset select_related
|
||||||
|
with query_counter() as q:
|
||||||
|
self.assertEqual(q, 0)
|
||||||
|
group_objs = Group.objects.select_related()
|
||||||
|
self.assertEqual(q, 2)
|
||||||
|
for group_obj in group_objs:
|
||||||
|
[m for m in group_obj.members]
|
||||||
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
|
User.drop_collection()
|
||||||
|
Group.drop_collection()
|
||||||
|
|
||||||
|
def test_handle_old_style_references(self):
|
||||||
|
"""Ensure that DBRef items in ListFields are dereferenced.
|
||||||
|
"""
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Group(Document):
|
||||||
|
members = ListField(ReferenceField(User, dbref=True))
|
||||||
|
|
||||||
|
User.drop_collection()
|
||||||
|
Group.drop_collection()
|
||||||
|
|
||||||
|
for i in xrange(1, 26):
|
||||||
|
user = User(name='user %s' % i)
|
||||||
|
user.save()
|
||||||
|
|
||||||
|
group = Group(members=User.objects)
|
||||||
|
group.save()
|
||||||
|
|
||||||
|
group = Group._get_collection().find_one()
|
||||||
|
|
||||||
|
# Update the model to change the reference
|
||||||
|
class Group(Document):
|
||||||
|
members = ListField(ReferenceField(User, dbref=False))
|
||||||
|
|
||||||
|
group = Group.objects.first()
|
||||||
|
group.members.append(User(name="String!").save())
|
||||||
|
group.save()
|
||||||
|
|
||||||
|
group = Group.objects.first()
|
||||||
|
self.assertEqual(group.members[0].name, 'user 1')
|
||||||
|
self.assertEqual(group.members[-1].name, 'String!')
|
||||||
|
|
||||||
|
def test_migrate_references(self):
|
||||||
|
"""Example of migrating ReferenceField storage
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Create some sample data
|
||||||
|
class User(Document):
|
||||||
|
name = StringField()
|
||||||
|
|
||||||
|
class Group(Document):
|
||||||
|
author = ReferenceField(User, dbref=True)
|
||||||
|
members = ListField(ReferenceField(User, dbref=True))
|
||||||
|
|
||||||
|
User.drop_collection()
|
||||||
|
Group.drop_collection()
|
||||||
|
|
||||||
|
user = User(name="Ross").save()
|
||||||
|
group = Group(author=user, members=[user]).save()
|
||||||
|
|
||||||
|
raw_data = Group._get_collection().find_one()
|
||||||
|
self.assertTrue(isinstance(raw_data['author'], DBRef))
|
||||||
|
self.assertTrue(isinstance(raw_data['members'][0], DBRef))
|
||||||
|
|
||||||
|
# Migrate the model definition
|
||||||
|
class Group(Document):
|
||||||
|
author = ReferenceField(User, dbref=False)
|
||||||
|
members = ListField(ReferenceField(User, dbref=False))
|
||||||
|
|
||||||
|
# Migrate the data
|
||||||
|
for g in Group.objects():
|
||||||
|
g.author = g.author
|
||||||
|
g.members = g.members
|
||||||
|
g.save()
|
||||||
|
|
||||||
|
group = Group.objects.first()
|
||||||
|
self.assertEqual(group.author, user)
|
||||||
|
self.assertEqual(group.members, [user])
|
||||||
|
|
||||||
|
raw_data = Group._get_collection().find_one()
|
||||||
|
self.assertTrue(isinstance(raw_data['author'], 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.
|
||||||
"""
|
"""
|
||||||
@@ -109,10 +243,10 @@ class FieldTest(unittest.TestCase):
|
|||||||
peter = Employee.objects.with_id(peter.id).select_related()
|
peter = Employee.objects.with_id(peter.id).select_related()
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
self.assertEquals(peter.boss, bill)
|
self.assertEqual(peter.boss, bill)
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
self.assertEquals(peter.friends, friends)
|
self.assertEqual(peter.friends, friends)
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
# Queryset select_related
|
# Queryset select_related
|
||||||
@@ -123,10 +257,10 @@ class FieldTest(unittest.TestCase):
|
|||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
self.assertEquals(employee.boss, bill)
|
self.assertEqual(employee.boss, bill)
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
self.assertEquals(employee.friends, friends)
|
self.assertEqual(employee.friends, friends)
|
||||||
self.assertEqual(q, 2)
|
self.assertEqual(q, 2)
|
||||||
|
|
||||||
def test_circular_reference(self):
|
def test_circular_reference(self):
|
||||||
@@ -160,7 +294,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
daughter.relations.append(self_rel)
|
daughter.relations.append(self_rel)
|
||||||
daughter.save()
|
daughter.save()
|
||||||
|
|
||||||
self.assertEquals("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
|
self.assertEqual("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
|
||||||
|
|
||||||
def test_circular_reference_on_self(self):
|
def test_circular_reference_on_self(self):
|
||||||
"""Ensure you can handle circular references
|
"""Ensure you can handle circular references
|
||||||
@@ -186,7 +320,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
daughter.relations.append(daughter)
|
daughter.relations.append(daughter)
|
||||||
daughter.save()
|
daughter.save()
|
||||||
|
|
||||||
self.assertEquals("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
|
self.assertEqual("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
|
||||||
|
|
||||||
def test_circular_tree_reference(self):
|
def test_circular_tree_reference(self):
|
||||||
"""Ensure you can handle circular references with more than one level
|
"""Ensure you can handle circular references with more than one level
|
||||||
@@ -228,7 +362,7 @@ class FieldTest(unittest.TestCase):
|
|||||||
anna.other.name = "Anna's friends"
|
anna.other.name = "Anna's friends"
|
||||||
anna.save()
|
anna.save()
|
||||||
|
|
||||||
self.assertEquals(
|
self.assertEqual(
|
||||||
"[<Person: Paul>, <Person: Maria>, <Person: Julia>, <Person: Anna>]",
|
"[<Person: Paul>, <Person: Maria>, <Person: Julia>, <Person: Anna>]",
|
||||||
"%s" % Person.objects()
|
"%s" % Person.objects()
|
||||||
)
|
)
|
||||||
@@ -781,8 +915,8 @@ class FieldTest(unittest.TestCase):
|
|||||||
root.save()
|
root.save()
|
||||||
|
|
||||||
root = root.reload()
|
root = root.reload()
|
||||||
self.assertEquals(root.children, [company])
|
self.assertEqual(root.children, [company])
|
||||||
self.assertEquals(company.parents, [root])
|
self.assertEqual(company.parents, [root])
|
||||||
|
|
||||||
def test_dict_in_dbref_instance(self):
|
def test_dict_in_dbref_instance(self):
|
||||||
|
|
||||||
@@ -808,5 +942,58 @@ class FieldTest(unittest.TestCase):
|
|||||||
room_101.save()
|
room_101.save()
|
||||||
|
|
||||||
room = Room.objects.first().select_related()
|
room = Room.objects.first().select_related()
|
||||||
self.assertEquals(room.staffs_with_position[0]['staff'], sarah)
|
self.assertEqual(room.staffs_with_position[0]['staff'], sarah)
|
||||||
self.assertEquals(room.staffs_with_position[1]['staff'], bob)
|
self.assertEqual(room.staffs_with_position[1]['staff'], bob)
|
||||||
|
|
||||||
|
def test_document_reload_no_inheritance(self):
|
||||||
|
class Foo(Document):
|
||||||
|
meta = {'allow_inheritance': False}
|
||||||
|
bar = ReferenceField('Bar')
|
||||||
|
baz = ReferenceField('Baz')
|
||||||
|
|
||||||
|
class Bar(Document):
|
||||||
|
meta = {'allow_inheritance': False}
|
||||||
|
msg = StringField(required=True, default='Blammo!')
|
||||||
|
|
||||||
|
class Baz(Document):
|
||||||
|
meta = {'allow_inheritance': False}
|
||||||
|
msg = StringField(required=True, default='Kaboom!')
|
||||||
|
|
||||||
|
Foo.drop_collection()
|
||||||
|
Bar.drop_collection()
|
||||||
|
Baz.drop_collection()
|
||||||
|
|
||||||
|
bar = Bar()
|
||||||
|
bar.save()
|
||||||
|
baz = Baz()
|
||||||
|
baz.save()
|
||||||
|
foo = Foo()
|
||||||
|
foo.bar = bar
|
||||||
|
foo.baz = baz
|
||||||
|
foo.save()
|
||||||
|
foo.reload()
|
||||||
|
|
||||||
|
self.assertEqual(type(foo.bar), Bar)
|
||||||
|
self.assertEqual(type(foo.baz), Baz)
|
||||||
|
|
||||||
|
def test_list_lookup_not_checked_in_map(self):
|
||||||
|
"""Ensure we dereference list data correctly
|
||||||
|
"""
|
||||||
|
class Comment(Document):
|
||||||
|
id = IntField(primary_key=True)
|
||||||
|
text = StringField()
|
||||||
|
|
||||||
|
class Message(Document):
|
||||||
|
id = IntField(primary_key=True)
|
||||||
|
comments = ListField(ReferenceField(Comment))
|
||||||
|
|
||||||
|
Comment.drop_collection()
|
||||||
|
Message.drop_collection()
|
||||||
|
|
||||||
|
c1 = Comment(id=0, text='zero').save()
|
||||||
|
c2 = Comment(id=1, text='one').save()
|
||||||
|
Message(id=1, comments=[c1, c2]).save()
|
||||||
|
|
||||||
|
msg = Message.objects.get(id=1)
|
||||||
|
self.assertEqual(0, msg.comments[0].id)
|
||||||
|
self.assertEqual(1, msg.comments[1].id)
|
@@ -1,20 +1,34 @@
|
|||||||
# -*- coding: utf-8 -*-
|
from __future__ import with_statement
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
from nose.plugins.skip import SkipTest
|
||||||
|
from mongoengine.python_support import PY3
|
||||||
from mongoengine import *
|
from mongoengine import *
|
||||||
from mongoengine.django.shortcuts import get_document_or_404
|
|
||||||
|
|
||||||
from django.http import Http404
|
try:
|
||||||
from django.template import Context, Template
|
from mongoengine.django.shortcuts import get_document_or_404
|
||||||
from django.conf import settings
|
|
||||||
from django.core.paginator import Paginator
|
from django.http import Http404
|
||||||
|
from django.template import Context, Template
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.paginator import Paginator
|
||||||
|
|
||||||
|
settings.configure()
|
||||||
|
|
||||||
|
from django.contrib.sessions.tests import SessionTestsMixin
|
||||||
|
from mongoengine.django.sessions import SessionStore, MongoSession
|
||||||
|
except Exception, err:
|
||||||
|
if PY3:
|
||||||
|
SessionTestsMixin = type # dummy value so no error
|
||||||
|
SessionStore = None # dummy value so no error
|
||||||
|
else:
|
||||||
|
raise err
|
||||||
|
|
||||||
settings.configure()
|
|
||||||
|
|
||||||
class QuerySetTest(unittest.TestCase):
|
class QuerySetTest(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
if PY3:
|
||||||
|
raise SkipTest('django does not have Python 3 support')
|
||||||
connect(db='mongoenginetest')
|
connect(db='mongoenginetest')
|
||||||
|
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
@@ -88,3 +102,21 @@ class QuerySetTest(unittest.TestCase):
|
|||||||
end = p * 2
|
end = p * 2
|
||||||
start = end - 1
|
start = end - 1
|
||||||
self.assertEqual(t.render(Context(d)), u'%d:%d:' % (start, end))
|
self.assertEqual(t.render(Context(d)), u'%d:%d:' % (start, end))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class MongoDBSessionTest(SessionTestsMixin, unittest.TestCase):
|
||||||
|
backend = SessionStore
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
if PY3:
|
||||||
|
raise SkipTest('django does not have Python 3 support')
|
||||||
|
connect(db='mongoenginetest')
|
||||||
|
MongoSession.drop_collection()
|
||||||
|
super(MongoDBSessionTest, self).setUp()
|
||||||
|
|
||||||
|
def test_first_save(self):
|
||||||
|
session = SessionStore()
|
||||||
|
session['test'] = True
|
||||||
|
session.save()
|
||||||
|
self.assertTrue('test' in session)
|
File diff suppressed because it is too large
Load Diff
@@ -25,14 +25,14 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p.name = "James"
|
p.name = "James"
|
||||||
p.age = 34
|
p.age = 34
|
||||||
|
|
||||||
self.assertEquals(p.to_mongo(),
|
self.assertEqual(p.to_mongo(),
|
||||||
{"_types": ["Person"], "_cls": "Person",
|
{"_types": ["Person"], "_cls": "Person",
|
||||||
"name": "James", "age": 34}
|
"name": "James", "age": 34}
|
||||||
)
|
)
|
||||||
|
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
self.assertEquals(self.Person.objects.first().age, 34)
|
self.assertEqual(self.Person.objects.first().age, 34)
|
||||||
|
|
||||||
# Confirm no changes to self.Person
|
# Confirm no changes to self.Person
|
||||||
self.assertFalse(hasattr(self.Person, 'age'))
|
self.assertFalse(hasattr(self.Person, 'age'))
|
||||||
@@ -40,11 +40,11 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
def test_dynamic_document_delta(self):
|
def test_dynamic_document_delta(self):
|
||||||
"""Ensures simple dynamic documents can delta correctly"""
|
"""Ensures simple dynamic documents can delta correctly"""
|
||||||
p = self.Person(name="James", age=34)
|
p = self.Person(name="James", age=34)
|
||||||
self.assertEquals(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {}))
|
self.assertEqual(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {}))
|
||||||
|
|
||||||
p.doc = 123
|
p.doc = 123
|
||||||
del(p.doc)
|
del(p.doc)
|
||||||
self.assertEquals(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {'doc': 1}))
|
self.assertEqual(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {'doc': 1}))
|
||||||
|
|
||||||
def test_change_scope_of_variable(self):
|
def test_change_scope_of_variable(self):
|
||||||
"""Test changing the scope of a dynamic field has no adverse effects"""
|
"""Test changing the scope of a dynamic field has no adverse effects"""
|
||||||
@@ -58,7 +58,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
p = self.Person.objects.get()
|
p = self.Person.objects.get()
|
||||||
self.assertEquals(p.misc, {'hello': 'world'})
|
self.assertEqual(p.misc, {'hello': 'world'})
|
||||||
|
|
||||||
def test_delete_dynamic_field(self):
|
def test_delete_dynamic_field(self):
|
||||||
"""Test deleting a dynamic field works"""
|
"""Test deleting a dynamic field works"""
|
||||||
@@ -73,10 +73,10 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
p = self.Person.objects.get()
|
p = self.Person.objects.get()
|
||||||
self.assertEquals(p.misc, {'hello': 'world'})
|
self.assertEqual(p.misc, {'hello': 'world'})
|
||||||
collection = self.db[self.Person._get_collection_name()]
|
collection = self.db[self.Person._get_collection_name()]
|
||||||
obj = collection.find_one()
|
obj = collection.find_one()
|
||||||
self.assertEquals(sorted(obj.keys()), ['_cls', '_id', '_types', 'misc', 'name'])
|
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', '_types', 'misc', 'name'])
|
||||||
|
|
||||||
del(p.misc)
|
del(p.misc)
|
||||||
p.save()
|
p.save()
|
||||||
@@ -85,7 +85,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
self.assertFalse(hasattr(p, 'misc'))
|
self.assertFalse(hasattr(p, 'misc'))
|
||||||
|
|
||||||
obj = collection.find_one()
|
obj = collection.find_one()
|
||||||
self.assertEquals(sorted(obj.keys()), ['_cls', '_id', '_types', 'name'])
|
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', '_types', 'name'])
|
||||||
|
|
||||||
def test_dynamic_document_queries(self):
|
def test_dynamic_document_queries(self):
|
||||||
"""Ensure we can query dynamic fields"""
|
"""Ensure we can query dynamic fields"""
|
||||||
@@ -94,10 +94,10 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p.age = 22
|
p.age = 22
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
self.assertEquals(1, self.Person.objects(age=22).count())
|
self.assertEqual(1, self.Person.objects(age=22).count())
|
||||||
p = self.Person.objects(age=22)
|
p = self.Person.objects(age=22)
|
||||||
p = p.get()
|
p = p.get()
|
||||||
self.assertEquals(22, p.age)
|
self.assertEqual(22, p.age)
|
||||||
|
|
||||||
def test_complex_dynamic_document_queries(self):
|
def test_complex_dynamic_document_queries(self):
|
||||||
class Person(DynamicDocument):
|
class Person(DynamicDocument):
|
||||||
@@ -117,8 +117,8 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p2.age = 10
|
p2.age = 10
|
||||||
p2.save()
|
p2.save()
|
||||||
|
|
||||||
self.assertEquals(Person.objects(age__icontains='ten').count(), 2)
|
self.assertEqual(Person.objects(age__icontains='ten').count(), 2)
|
||||||
self.assertEquals(Person.objects(age__gte=10).count(), 1)
|
self.assertEqual(Person.objects(age__gte=10).count(), 1)
|
||||||
|
|
||||||
def test_complex_data_lookups(self):
|
def test_complex_data_lookups(self):
|
||||||
"""Ensure you can query dynamic document dynamic fields"""
|
"""Ensure you can query dynamic document dynamic fields"""
|
||||||
@@ -126,7 +126,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p.misc = {'hello': 'world'}
|
p.misc = {'hello': 'world'}
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
self.assertEquals(1, self.Person.objects(misc__hello='world').count())
|
self.assertEqual(1, self.Person.objects(misc__hello='world').count())
|
||||||
|
|
||||||
def test_inheritance(self):
|
def test_inheritance(self):
|
||||||
"""Ensure that dynamic document plays nice with inheritance"""
|
"""Ensure that dynamic document plays nice with inheritance"""
|
||||||
@@ -146,8 +146,8 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
joe_bloggs.age = 20
|
joe_bloggs.age = 20
|
||||||
joe_bloggs.save()
|
joe_bloggs.save()
|
||||||
|
|
||||||
self.assertEquals(1, self.Person.objects(age=20).count())
|
self.assertEqual(1, self.Person.objects(age=20).count())
|
||||||
self.assertEquals(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.assertTrue(isinstance(joe_bloggs, Employee))
|
self.assertTrue(isinstance(joe_bloggs, Employee))
|
||||||
@@ -170,7 +170,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
|
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
|
||||||
doc.embedded_field = embedded_1
|
doc.embedded_field = embedded_1
|
||||||
|
|
||||||
self.assertEquals(doc.to_mongo(), {"_types": ['Doc'], "_cls": "Doc",
|
self.assertEqual(doc.to_mongo(), {"_types": ['Doc'], "_cls": "Doc",
|
||||||
"embedded_field": {
|
"embedded_field": {
|
||||||
"_types": ['Embedded'], "_cls": "Embedded",
|
"_types": ['Embedded'], "_cls": "Embedded",
|
||||||
"string_field": "hello",
|
"string_field": "hello",
|
||||||
@@ -182,11 +182,11 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
doc = Doc.objects.first()
|
doc = Doc.objects.first()
|
||||||
self.assertEquals(doc.embedded_field.__class__, Embedded)
|
self.assertEqual(doc.embedded_field.__class__, Embedded)
|
||||||
self.assertEquals(doc.embedded_field.string_field, "hello")
|
self.assertEqual(doc.embedded_field.string_field, "hello")
|
||||||
self.assertEquals(doc.embedded_field.int_field, 1)
|
self.assertEqual(doc.embedded_field.int_field, 1)
|
||||||
self.assertEquals(doc.embedded_field.dict_field, {'hello': 'world'})
|
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
|
||||||
self.assertEquals(doc.embedded_field.list_field, ['1', 2, {'hello': 'world'}])
|
self.assertEqual(doc.embedded_field.list_field, ['1', 2, {'hello': 'world'}])
|
||||||
|
|
||||||
def test_complex_embedded_documents(self):
|
def test_complex_embedded_documents(self):
|
||||||
"""Test complex dynamic embedded documents setups"""
|
"""Test complex dynamic embedded documents setups"""
|
||||||
@@ -213,7 +213,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
embedded_1.list_field = ['1', 2, embedded_2]
|
embedded_1.list_field = ['1', 2, embedded_2]
|
||||||
doc.embedded_field = embedded_1
|
doc.embedded_field = embedded_1
|
||||||
|
|
||||||
self.assertEquals(doc.to_mongo(), {"_types": ['Doc'], "_cls": "Doc",
|
self.assertEqual(doc.to_mongo(), {"_types": ['Doc'], "_cls": "Doc",
|
||||||
"embedded_field": {
|
"embedded_field": {
|
||||||
"_types": ['Embedded'], "_cls": "Embedded",
|
"_types": ['Embedded'], "_cls": "Embedded",
|
||||||
"string_field": "hello",
|
"string_field": "hello",
|
||||||
@@ -230,20 +230,20 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
})
|
})
|
||||||
doc.save()
|
doc.save()
|
||||||
doc = Doc.objects.first()
|
doc = Doc.objects.first()
|
||||||
self.assertEquals(doc.embedded_field.__class__, Embedded)
|
self.assertEqual(doc.embedded_field.__class__, Embedded)
|
||||||
self.assertEquals(doc.embedded_field.string_field, "hello")
|
self.assertEqual(doc.embedded_field.string_field, "hello")
|
||||||
self.assertEquals(doc.embedded_field.int_field, 1)
|
self.assertEqual(doc.embedded_field.int_field, 1)
|
||||||
self.assertEquals(doc.embedded_field.dict_field, {'hello': 'world'})
|
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
|
||||||
self.assertEquals(doc.embedded_field.list_field[0], '1')
|
self.assertEqual(doc.embedded_field.list_field[0], '1')
|
||||||
self.assertEquals(doc.embedded_field.list_field[1], 2)
|
self.assertEqual(doc.embedded_field.list_field[1], 2)
|
||||||
|
|
||||||
embedded_field = doc.embedded_field.list_field[2]
|
embedded_field = doc.embedded_field.list_field[2]
|
||||||
|
|
||||||
self.assertEquals(embedded_field.__class__, Embedded)
|
self.assertEqual(embedded_field.__class__, Embedded)
|
||||||
self.assertEquals(embedded_field.string_field, "hello")
|
self.assertEqual(embedded_field.string_field, "hello")
|
||||||
self.assertEquals(embedded_field.int_field, 1)
|
self.assertEqual(embedded_field.int_field, 1)
|
||||||
self.assertEquals(embedded_field.dict_field, {'hello': 'world'})
|
self.assertEqual(embedded_field.dict_field, {'hello': 'world'})
|
||||||
self.assertEquals(embedded_field.list_field, ['1', 2, {'hello': 'world'}])
|
self.assertEqual(embedded_field.list_field, ['1', 2, {'hello': 'world'}])
|
||||||
|
|
||||||
def test_delta_for_dynamic_documents(self):
|
def test_delta_for_dynamic_documents(self):
|
||||||
p = self.Person()
|
p = self.Person()
|
||||||
@@ -252,18 +252,18 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
p.age = 24
|
p.age = 24
|
||||||
self.assertEquals(p.age, 24)
|
self.assertEqual(p.age, 24)
|
||||||
self.assertEquals(p._get_changed_fields(), ['age'])
|
self.assertEqual(p._get_changed_fields(), ['age'])
|
||||||
self.assertEquals(p._delta(), ({'age': 24}, {}))
|
self.assertEqual(p._delta(), ({'age': 24}, {}))
|
||||||
|
|
||||||
p = self.Person.objects(age=22).get()
|
p = self.Person.objects(age=22).get()
|
||||||
p.age = 24
|
p.age = 24
|
||||||
self.assertEquals(p.age, 24)
|
self.assertEqual(p.age, 24)
|
||||||
self.assertEquals(p._get_changed_fields(), ['age'])
|
self.assertEqual(p._get_changed_fields(), ['age'])
|
||||||
self.assertEquals(p._delta(), ({'age': 24}, {}))
|
self.assertEqual(p._delta(), ({'age': 24}, {}))
|
||||||
|
|
||||||
p.save()
|
p.save()
|
||||||
self.assertEquals(1, self.Person.objects(age=24).count())
|
self.assertEqual(1, self.Person.objects(age=24).count())
|
||||||
|
|
||||||
def test_delta(self):
|
def test_delta(self):
|
||||||
|
|
||||||
@@ -275,40 +275,40 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
doc = Doc.objects.first()
|
doc = Doc.objects.first()
|
||||||
self.assertEquals(doc._get_changed_fields(), [])
|
self.assertEqual(doc._get_changed_fields(), [])
|
||||||
self.assertEquals(doc._delta(), ({}, {}))
|
self.assertEqual(doc._delta(), ({}, {}))
|
||||||
|
|
||||||
doc.string_field = 'hello'
|
doc.string_field = 'hello'
|
||||||
self.assertEquals(doc._get_changed_fields(), ['string_field'])
|
self.assertEqual(doc._get_changed_fields(), ['string_field'])
|
||||||
self.assertEquals(doc._delta(), ({'string_field': 'hello'}, {}))
|
self.assertEqual(doc._delta(), ({'string_field': 'hello'}, {}))
|
||||||
|
|
||||||
doc._changed_fields = []
|
doc._changed_fields = []
|
||||||
doc.int_field = 1
|
doc.int_field = 1
|
||||||
self.assertEquals(doc._get_changed_fields(), ['int_field'])
|
self.assertEqual(doc._get_changed_fields(), ['int_field'])
|
||||||
self.assertEquals(doc._delta(), ({'int_field': 1}, {}))
|
self.assertEqual(doc._delta(), ({'int_field': 1}, {}))
|
||||||
|
|
||||||
doc._changed_fields = []
|
doc._changed_fields = []
|
||||||
dict_value = {'hello': 'world', 'ping': 'pong'}
|
dict_value = {'hello': 'world', 'ping': 'pong'}
|
||||||
doc.dict_field = dict_value
|
doc.dict_field = dict_value
|
||||||
self.assertEquals(doc._get_changed_fields(), ['dict_field'])
|
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
|
||||||
self.assertEquals(doc._delta(), ({'dict_field': dict_value}, {}))
|
self.assertEqual(doc._delta(), ({'dict_field': dict_value}, {}))
|
||||||
|
|
||||||
doc._changed_fields = []
|
doc._changed_fields = []
|
||||||
list_value = ['1', 2, {'hello': 'world'}]
|
list_value = ['1', 2, {'hello': 'world'}]
|
||||||
doc.list_field = list_value
|
doc.list_field = list_value
|
||||||
self.assertEquals(doc._get_changed_fields(), ['list_field'])
|
self.assertEqual(doc._get_changed_fields(), ['list_field'])
|
||||||
self.assertEquals(doc._delta(), ({'list_field': list_value}, {}))
|
self.assertEqual(doc._delta(), ({'list_field': list_value}, {}))
|
||||||
|
|
||||||
# Test unsetting
|
# Test unsetting
|
||||||
doc._changed_fields = []
|
doc._changed_fields = []
|
||||||
doc.dict_field = {}
|
doc.dict_field = {}
|
||||||
self.assertEquals(doc._get_changed_fields(), ['dict_field'])
|
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
|
||||||
self.assertEquals(doc._delta(), ({}, {'dict_field': 1}))
|
self.assertEqual(doc._delta(), ({}, {'dict_field': 1}))
|
||||||
|
|
||||||
doc._changed_fields = []
|
doc._changed_fields = []
|
||||||
doc.list_field = []
|
doc.list_field = []
|
||||||
self.assertEquals(doc._get_changed_fields(), ['list_field'])
|
self.assertEqual(doc._get_changed_fields(), ['list_field'])
|
||||||
self.assertEquals(doc._delta(), ({}, {'list_field': 1}))
|
self.assertEqual(doc._delta(), ({}, {'list_field': 1}))
|
||||||
|
|
||||||
def test_delta_recursive(self):
|
def test_delta_recursive(self):
|
||||||
"""Testing deltaing works with dynamic documents"""
|
"""Testing deltaing works with dynamic documents"""
|
||||||
@@ -323,8 +323,8 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
|
|
||||||
doc = Doc.objects.first()
|
doc = Doc.objects.first()
|
||||||
self.assertEquals(doc._get_changed_fields(), [])
|
self.assertEqual(doc._get_changed_fields(), [])
|
||||||
self.assertEquals(doc._delta(), ({}, {}))
|
self.assertEqual(doc._delta(), ({}, {}))
|
||||||
|
|
||||||
embedded_1 = Embedded()
|
embedded_1 = Embedded()
|
||||||
embedded_1.string_field = 'hello'
|
embedded_1.string_field = 'hello'
|
||||||
@@ -333,7 +333,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
|
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
|
||||||
doc.embedded_field = embedded_1
|
doc.embedded_field = embedded_1
|
||||||
|
|
||||||
self.assertEquals(doc._get_changed_fields(), ['embedded_field'])
|
self.assertEqual(doc._get_changed_fields(), ['embedded_field'])
|
||||||
|
|
||||||
embedded_delta = {
|
embedded_delta = {
|
||||||
'string_field': 'hello',
|
'string_field': 'hello',
|
||||||
@@ -341,28 +341,28 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
'dict_field': {'hello': 'world'},
|
'dict_field': {'hello': 'world'},
|
||||||
'list_field': ['1', 2, {'hello': 'world'}]
|
'list_field': ['1', 2, {'hello': 'world'}]
|
||||||
}
|
}
|
||||||
self.assertEquals(doc.embedded_field._delta(), (embedded_delta, {}))
|
self.assertEqual(doc.embedded_field._delta(), (embedded_delta, {}))
|
||||||
embedded_delta.update({
|
embedded_delta.update({
|
||||||
'_types': ['Embedded'],
|
'_types': ['Embedded'],
|
||||||
'_cls': 'Embedded',
|
'_cls': 'Embedded',
|
||||||
})
|
})
|
||||||
self.assertEquals(doc._delta(), ({'embedded_field': embedded_delta}, {}))
|
self.assertEqual(doc._delta(), ({'embedded_field': embedded_delta}, {}))
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
doc.embedded_field.dict_field = {}
|
doc.embedded_field.dict_field = {}
|
||||||
self.assertEquals(doc._get_changed_fields(), ['embedded_field.dict_field'])
|
self.assertEqual(doc._get_changed_fields(), ['embedded_field.dict_field'])
|
||||||
self.assertEquals(doc.embedded_field._delta(), ({}, {'dict_field': 1}))
|
self.assertEqual(doc.embedded_field._delta(), ({}, {'dict_field': 1}))
|
||||||
|
|
||||||
self.assertEquals(doc._delta(), ({}, {'embedded_field.dict_field': 1}))
|
self.assertEqual(doc._delta(), ({}, {'embedded_field.dict_field': 1}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
doc.embedded_field.list_field = []
|
doc.embedded_field.list_field = []
|
||||||
self.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field'])
|
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field'])
|
||||||
self.assertEquals(doc.embedded_field._delta(), ({}, {'list_field': 1}))
|
self.assertEqual(doc.embedded_field._delta(), ({}, {'list_field': 1}))
|
||||||
self.assertEquals(doc._delta(), ({}, {'embedded_field.list_field': 1}))
|
self.assertEqual(doc._delta(), ({}, {'embedded_field.list_field': 1}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
@@ -373,8 +373,8 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
|
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
|
||||||
|
|
||||||
doc.embedded_field.list_field = ['1', 2, embedded_2]
|
doc.embedded_field.list_field = ['1', 2, embedded_2]
|
||||||
self.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field'])
|
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field'])
|
||||||
self.assertEquals(doc.embedded_field._delta(), ({
|
self.assertEqual(doc.embedded_field._delta(), ({
|
||||||
'list_field': ['1', 2, {
|
'list_field': ['1', 2, {
|
||||||
'_cls': 'Embedded',
|
'_cls': 'Embedded',
|
||||||
'_types': ['Embedded'],
|
'_types': ['Embedded'],
|
||||||
@@ -385,7 +385,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
}]
|
}]
|
||||||
}, {}))
|
}, {}))
|
||||||
|
|
||||||
self.assertEquals(doc._delta(), ({
|
self.assertEqual(doc._delta(), ({
|
||||||
'embedded_field.list_field': ['1', 2, {
|
'embedded_field.list_field': ['1', 2, {
|
||||||
'_cls': 'Embedded',
|
'_cls': 'Embedded',
|
||||||
'_types': ['Embedded'],
|
'_types': ['Embedded'],
|
||||||
@@ -398,25 +398,25 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
self.assertEquals(doc.embedded_field.list_field[2]._changed_fields, [])
|
self.assertEqual(doc.embedded_field.list_field[2]._changed_fields, [])
|
||||||
self.assertEquals(doc.embedded_field.list_field[0], '1')
|
self.assertEqual(doc.embedded_field.list_field[0], '1')
|
||||||
self.assertEquals(doc.embedded_field.list_field[1], 2)
|
self.assertEqual(doc.embedded_field.list_field[1], 2)
|
||||||
for k in doc.embedded_field.list_field[2]._fields:
|
for k in doc.embedded_field.list_field[2]._fields:
|
||||||
self.assertEquals(doc.embedded_field.list_field[2][k], embedded_2[k])
|
self.assertEqual(doc.embedded_field.list_field[2][k], embedded_2[k])
|
||||||
|
|
||||||
doc.embedded_field.list_field[2].string_field = 'world'
|
doc.embedded_field.list_field[2].string_field = 'world'
|
||||||
self.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field.2.string_field'])
|
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field.2.string_field'])
|
||||||
self.assertEquals(doc.embedded_field._delta(), ({'list_field.2.string_field': 'world'}, {}))
|
self.assertEqual(doc.embedded_field._delta(), ({'list_field.2.string_field': 'world'}, {}))
|
||||||
self.assertEquals(doc._delta(), ({'embedded_field.list_field.2.string_field': 'world'}, {}))
|
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.string_field': 'world'}, {}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
self.assertEquals(doc.embedded_field.list_field[2].string_field, 'world')
|
self.assertEqual(doc.embedded_field.list_field[2].string_field, 'world')
|
||||||
|
|
||||||
# Test multiple assignments
|
# Test multiple assignments
|
||||||
doc.embedded_field.list_field[2].string_field = 'hello world'
|
doc.embedded_field.list_field[2].string_field = 'hello world'
|
||||||
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
|
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
|
||||||
self.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field'])
|
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field'])
|
||||||
self.assertEquals(doc.embedded_field._delta(), ({
|
self.assertEqual(doc.embedded_field._delta(), ({
|
||||||
'list_field': ['1', 2, {
|
'list_field': ['1', 2, {
|
||||||
'_types': ['Embedded'],
|
'_types': ['Embedded'],
|
||||||
'_cls': 'Embedded',
|
'_cls': 'Embedded',
|
||||||
@@ -424,7 +424,7 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
'int_field': 1,
|
'int_field': 1,
|
||||||
'list_field': ['1', 2, {'hello': 'world'}],
|
'list_field': ['1', 2, {'hello': 'world'}],
|
||||||
'dict_field': {'hello': 'world'}}]}, {}))
|
'dict_field': {'hello': 'world'}}]}, {}))
|
||||||
self.assertEquals(doc._delta(), ({
|
self.assertEqual(doc._delta(), ({
|
||||||
'embedded_field.list_field': ['1', 2, {
|
'embedded_field.list_field': ['1', 2, {
|
||||||
'_types': ['Embedded'],
|
'_types': ['Embedded'],
|
||||||
'_cls': 'Embedded',
|
'_cls': 'Embedded',
|
||||||
@@ -435,32 +435,32 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
]}, {}))
|
]}, {}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
self.assertEquals(doc.embedded_field.list_field[2].string_field, 'hello world')
|
self.assertEqual(doc.embedded_field.list_field[2].string_field, 'hello world')
|
||||||
|
|
||||||
# Test list native methods
|
# Test list native methods
|
||||||
doc.embedded_field.list_field[2].list_field.pop(0)
|
doc.embedded_field.list_field[2].list_field.pop(0)
|
||||||
self.assertEquals(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}]}, {}))
|
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}]}, {}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
doc.embedded_field.list_field[2].list_field.append(1)
|
doc.embedded_field.list_field[2].list_field.append(1)
|
||||||
self.assertEquals(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}, 1]}, {}))
|
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}, 1]}, {}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
self.assertEquals(doc.embedded_field.list_field[2].list_field, [2, {'hello': 'world'}, 1])
|
self.assertEqual(doc.embedded_field.list_field[2].list_field, [2, {'hello': 'world'}, 1])
|
||||||
|
|
||||||
doc.embedded_field.list_field[2].list_field.sort()
|
doc.embedded_field.list_field[2].list_field.sort(key=str)# use str as a key to allow comparing uncomperable types
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
self.assertEquals(doc.embedded_field.list_field[2].list_field, [1, 2, {'hello': 'world'}])
|
self.assertEqual(doc.embedded_field.list_field[2].list_field, [1, 2, {'hello': 'world'}])
|
||||||
|
|
||||||
del(doc.embedded_field.list_field[2].list_field[2]['hello'])
|
del(doc.embedded_field.list_field[2].list_field[2]['hello'])
|
||||||
self.assertEquals(doc._delta(), ({'embedded_field.list_field.2.list_field': [1, 2, {}]}, {}))
|
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.list_field': [1, 2, {}]}, {}))
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
del(doc.embedded_field.list_field[2].list_field)
|
del(doc.embedded_field.list_field[2].list_field)
|
||||||
self.assertEquals(doc._delta(), ({}, {'embedded_field.list_field.2.list_field': 1}))
|
self.assertEqual(doc._delta(), ({}, {'embedded_field.list_field.2.list_field': 1}))
|
||||||
|
|
||||||
doc.save()
|
doc.save()
|
||||||
doc.reload()
|
doc.reload()
|
||||||
@@ -470,8 +470,8 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
doc.reload()
|
doc.reload()
|
||||||
|
|
||||||
doc.dict_field['embedded'].string_field = 'Hello World'
|
doc.dict_field['embedded'].string_field = 'Hello World'
|
||||||
self.assertEquals(doc._get_changed_fields(), ['dict_field.embedded.string_field'])
|
self.assertEqual(doc._get_changed_fields(), ['dict_field.embedded.string_field'])
|
||||||
self.assertEquals(doc._delta(), ({'dict_field.embedded.string_field': 'Hello World'}, {}))
|
self.assertEqual(doc._delta(), ({'dict_field.embedded.string_field': 'Hello World'}, {}))
|
||||||
|
|
||||||
def test_indexes(self):
|
def test_indexes(self):
|
||||||
"""Ensure that indexes are used when meta[indexes] is specified.
|
"""Ensure that indexes are used when meta[indexes] is specified.
|
||||||
@@ -500,3 +500,34 @@ class DynamicDocTest(unittest.TestCase):
|
|||||||
self.assertTrue([('_types', 1), ('category', 1), ('date', -1)]
|
self.assertTrue([('_types', 1), ('category', 1), ('date', -1)]
|
||||||
in info)
|
in info)
|
||||||
self.assertTrue([('_types', 1), ('date', -1)] in info)
|
self.assertTrue([('_types', 1), ('date', -1)] in info)
|
||||||
|
|
||||||
|
def test_dynamic_and_embedded(self):
|
||||||
|
"""Ensure embedded documents play nicely"""
|
||||||
|
|
||||||
|
class Address(EmbeddedDocument):
|
||||||
|
city = StringField()
|
||||||
|
|
||||||
|
class Person(DynamicDocument):
|
||||||
|
name = StringField()
|
||||||
|
meta = {'allow_inheritance': True}
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
Person(name="Ross", address=Address(city="London")).save()
|
||||||
|
|
||||||
|
person = Person.objects.first()
|
||||||
|
person.address.city = "Lundenne"
|
||||||
|
person.save()
|
||||||
|
|
||||||
|
self.assertEqual(Person.objects.first().address.city, "Lundenne")
|
||||||
|
|
||||||
|
person = Person.objects.first()
|
||||||
|
person.address = Address(city="Londinium")
|
||||||
|
person.save()
|
||||||
|
|
||||||
|
self.assertEqual(Person.objects.first().address.city, "Londinium")
|
||||||
|
|
||||||
|
person = Person.objects.first()
|
||||||
|
person.age = 35
|
||||||
|
person.save()
|
||||||
|
self.assertEqual(Person.objects.first().age, 35)
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
33
tests/test_replicaset_connection.py
Normal file
33
tests/test_replicaset_connection.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
import pymongo
|
||||||
|
from pymongo import ReadPreference, ReplicaSetConnection
|
||||||
|
|
||||||
|
import mongoengine
|
||||||
|
from mongoengine import *
|
||||||
|
from mongoengine.connection import get_db, get_connection, ConnectionError
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionTest(unittest.TestCase):
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
mongoengine.connection._connection_settings = {}
|
||||||
|
mongoengine.connection._connections = {}
|
||||||
|
mongoengine.connection._dbs = {}
|
||||||
|
|
||||||
|
def test_replicaset_uri_passes_read_preference(self):
|
||||||
|
"""Requires a replica set called "rs" on port 27017
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = connect(db='mongoenginetest', host="mongodb://localhost/mongoenginetest?replicaSet=rs", read_preference=ReadPreference.SECONDARY_ONLY)
|
||||||
|
except ConnectionError, e:
|
||||||
|
return
|
||||||
|
|
||||||
|
if not isinstance(conn, ReplicaSetConnection):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.assertEqual(conn.read_preference, ReadPreference.SECONDARY_ONLY)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
@@ -212,9 +212,9 @@ class SignalTests(unittest.TestCase):
|
|||||||
|
|
||||||
# The output of this signal is not entirely deterministic. The reloaded
|
# The output of this signal is not entirely deterministic. The reloaded
|
||||||
# object will have an object ID. Hence, we only check part of the output
|
# object will have an object ID. Hence, we only check part of the output
|
||||||
self.assertEquals(signal_output[3],
|
self.assertEqual(signal_output[3],
|
||||||
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]")
|
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]")
|
||||||
self.assertEquals(signal_output[-2:],
|
self.assertEqual(signal_output[-2:],
|
||||||
["post_bulk_insert signal, [<Author: Bill Shakespeare>]",
|
["post_bulk_insert signal, [<Author: Bill Shakespeare>]",
|
||||||
"Is loaded",])
|
"Is loaded",])
|
||||||
|
|
Reference in New Issue
Block a user