Compare commits

...

1554 Commits

Author SHA1 Message Date
Stefan Wojcik
70378789e7 let the aggregation test run in MongoDB v3 2017-05-08 10:10:58 -04:00
Stefan Wojcik
0bc7aa52d8 more docs tweaks [ci skip] 2017-05-08 00:14:42 -04:00
Stefan Wojcik
e52603b4a7 ver bump to v0.14.0 + changelog/upgrade docs update 2017-05-08 00:12:26 -04:00
Stefan Wójcik
3b88712402 Cleaner as_pymongo (#1549) 2017-05-08 00:02:42 -04:00
Stefan Wojcik
33e9ef2106 dont test pypy3 temporarily 2017-05-07 21:37:38 -04:00
Stefan Wojcik
689fe4ed9a Revert "use a newer pypy3 (https://github.com/travis-ci/travis-ci/issues/6277)"
This reverts commit 944d1c0a4a.
2017-05-07 21:37:14 -04:00
Stefan Wojcik
b82d026f39 Revert "fix tox.ini"
This reverts commit c00914bea2.
2017-05-07 21:37:05 -04:00
Stefan Wojcik
009059def4 revert #1497 2017-05-07 21:29:13 -04:00
Stefan Wójcik
03ff61d113 better db_field validation (#1547) 2017-05-07 21:11:14 -04:00
Stefan Wojcik
c00914bea2 fix tox.ini 2017-05-07 20:32:52 -04:00
Stefan Wojcik
944d1c0a4a use a newer pypy3 (https://github.com/travis-ci/travis-ci/issues/6277) 2017-05-07 19:54:58 -04:00
Stefan Wojcik
2cf23e33e3 Document._get_update_doc helper method 2017-05-07 19:26:10 -04:00
Stefan Wojcik
e2a0b42d03 clarify test_get_changed_fields_query_count 2017-04-30 18:29:22 -04:00
Stefan Wójcik
894e9818ac use an external sphinx rtd theme (#1541)
Externalize Sphinx RTD theme
2017-04-30 15:38:21 -04:00
Stefan Wójcik
de18e256ce clean up the Document._get_collection code (#1540) 2017-04-30 14:35:33 -04:00
Stefan Wójcik
1a3c70ce1b make EmbeddedDocument not hashable by default (#1528) 2017-04-30 13:30:21 -04:00
Artemiy
bd4a603e16 Use De Morgan's laws to simplify an expression. (#1531) 2017-04-20 10:27:37 -04:00
Jason
358b80d782 Make the tutorial slightly more intuitive (#1530) 2017-04-20 09:11:38 -04:00
Stefan Wojcik
824ec42005 bump version to v0.13.0 and fill in the changelog and the upgrade docs 2017-04-16 14:08:46 -04:00
Stefan Wójcik
466935e9a3 Unicode support in EmailField (#1527) 2017-04-16 13:58:58 -04:00
Stefan Wojcik
b52d3e3a7b added one more item to the v0.12.0 changelog 2017-04-07 10:34:04 -04:00
Stefan Wojcik
888a6da4a5 update the changelog and bump the version to v0.12.0 2017-04-07 10:18:39 -04:00
Omer Katz
972ac73dd9 Merge pull request #1497 from userlocalhost/feature/order_guarantee
added a feature to save object data in order
2017-04-07 10:49:39 +03:00
Hiroyasu OHYAMA
d8b238d5f1 Refactored the implementation of DynamicField extension for storing data in order 2017-04-06 00:42:11 +00:00
Omer Katz
63206c3da2 Merge pull request #1520 from ZoetropeLabs/fix/allow-reference-fields-take-object-ids
Allow ReferenceFields to take ObjectIds
2017-04-02 13:57:58 +03:00
Richard Fortescue-Webb
5713de8966 Use the objectid in the test 2017-03-29 11:34:57 +01:00
Richard Fortescue-Webb
58f293fef3 Allow ReferenceFields to take ObjectIds 2017-03-29 10:34:50 +01:00
Hiroyasu OHYAMA
ffbb2c9689 This is Additional tests for the container_class parameter of DynamicField
This tests DynamicField dereference with ordering guarantee.
2017-03-08 14:46:04 +00:00
Hiroyasu OHYAMA
9cd3dcdebf Added a test for the change of the condition in DeReference processing
This checks DBRef conversion using DynamicField with the ordering
guarantee.
2017-03-08 14:45:43 +00:00
Hiroyasu OHYAMA
f2fe58c3c5 Added a condition to store data to ObjectDict when the items type is it
Previous dereference implementation re-contains data as `dict` except
for the predicted type.
But the OrderedDict is not predicted, so the its data would be converted
`dict` implicitly.
As the result, the order of stored data get wrong. And this patch
prevents it.
2017-03-08 14:35:50 +00:00
Stefan Wojcik
b78010aa94 remove test_last_field_name_like_operator (it's a dupe of the same test in tests/queryset/transform.py) 2017-03-05 21:24:46 -05:00
Stefan Wójcik
49035543b9 cleanup BaseQuerySet.__getitem__ (#1502) 2017-03-05 21:17:53 -05:00
Stefan Wójcik
f9ccf635ca Respect db fields in multiple layers of embedded docs (#1501) 2017-03-05 18:20:09 -05:00
Stefan Wojcik
e8ea294964 test negative indexes (closes #1119) 2017-03-05 18:12:01 -05:00
Stefan Wojcik
19ef2be88b fix #937 2017-03-05 00:05:33 -05:00
Stefan Wojcik
30e8b8186f clean up document instance tests 2017-03-02 00:25:56 -05:00
Stefan Wójcik
741643af5f clean up field unit tests (#1498) 2017-03-02 00:05:10 -05:00
Hiroyasu OHYAMA
6aaf9ba470 removed a checking of dict order because this order is not cared (some implementation might be in ordered, but other one is not) 2017-03-01 09:32:28 +00:00
Hiroyasu OHYAMA
5957dc72eb To achive storing object data in order with minimum implementation, I
changed followings.

- added optional parameter `container_class` which enables to choose
  intermediate class at encoding Python data, instead of additional
  field class.
- removed OrderedDocument class because the equivalent feature could
  be implemented by the outside of Mongoengine.
2017-03-01 09:20:57 +00:00
Hiroyasu OHYAMA
e32a9777d7 added test for OrderedDynamicField and OrderedDocument 2017-02-28 03:35:53 +00:00
Hiroyasu OHYAMA
84a8f1eb2b added OrderedDocument class to decode BSON data to OrderedDict for retrieving data in order 2017-02-28 03:35:39 +00:00
Hiroyasu OHYAMA
6810953014 added OrderedDynamicField class to store data in the defined order because of #203 2017-02-28 03:34:42 +00:00
Ephraim Berkovitch
398964945a Document.objects.create should raise NotUniqueError upon saving duplicate primary key (#1485) 2017-02-27 09:42:44 -05:00
Stefan Wójcik
5f43c032f2 revamp the "connecting" user guide and test more ways of connecting to a replica set (#1490) 2017-02-26 21:29:06 -05:00
Stefan Wojcik
627cf90de0 tutorial tweaks: better copy + use py3-friendly syntax 2017-02-26 20:30:37 -05:00
Omer Katz
2bedb36d7f Test against multiple MongoDB versions in Travis (#1074) 2017-02-26 14:52:43 -05:00
Stefan Wójcik
e93a95d0cb Test and document controlling the size of the connection pool (#1489) 2017-02-25 14:09:10 -05:00
Stefan Wójcik
3f31666796 Fix the exception message when validating unicode URLs (#1486) 2017-02-24 16:18:34 -05:00
Stefan Wojcik
3fe8031cf3 fix EmbeddedDocumentListFieldTestCase 2017-02-22 12:44:05 -05:00
bagerard
b27c7ce11b allow to use sets in field choices (#1482) 2017-02-15 08:51:47 -05:00
Stefan Wojcik
ed34c2ca68 update the changelog and upgrade docs 2017-02-09 12:13:56 -08:00
Stefan Wójcik
3ca2e953fb Fix limit/skip/hint/batch_size chaining (#1476) 2017-02-09 12:02:46 -08:00
martin sereinig
d8a7328365 Fix docs regarding reverse_delete_rule and delete signals (#1473) 2017-02-06 14:11:42 -07:00
Stefan Wojcik
f33cd625bf nicer readme 2017-01-17 02:47:45 -05:00
Stefan Wojcik
80530bb13c nicer readme 2017-01-17 02:46:37 -05:00
Stefan Wójcik
affc12df4b Update README.rst 2017-01-17 02:43:29 -05:00
Stefan Wojcik
4eedf00025 nicer readme note about dependencies 2017-01-17 02:42:23 -05:00
Eli Boyarski
e5acbcc0dd Improved a docstring for FieldDoesNotExist (#1466) 2017-01-09 11:24:27 -05:00
Stefan Wojcik
1b6743ee53 add a changelog entry about broken references raising DoesNotExist 2017-01-08 14:50:16 -05:00
Eli Boyarski
b5fb82d95d Typo fix (#1463) 2017-01-08 12:57:36 -05:00
lanf0n
193aa4e1f2 [#1459] fix typo __neq__ to __ne__ (#1461) 2017-01-05 22:37:09 -05:00
Stefan Wójcik
ebd34427c7 Cleaner Document.save (#1458) 2016-12-30 05:43:56 -05:00
Stefan Wójcik
3d75573889 Validate db_field (#1448) 2016-12-29 12:39:05 -05:00
Stefan Wójcik
c6240ca415 Test connection's write concern (#1456) 2016-12-29 12:37:38 -05:00
Stefan Wójcik
2ee8984b44 add a $rename operator (#1454) 2016-12-28 23:25:38 -05:00
Stefan Wojcik
b7ec587e5b better docstring for BaseDocument.to_json 2016-12-28 22:15:46 -05:00
Stefan Wojcik
47c58bce2b fix "connect" example in the docs 2016-12-28 21:08:18 -05:00
Stefan Wojcik
96e95ac533 minor readme tweaks 2016-12-28 17:18:55 -05:00
Stefan Wojcik
b013a065f7 remove readme mention of the irc channel 2016-12-28 11:50:28 -05:00
Stefan Wojcik
74b37d11cf only validate db_field if it's a string type 2016-12-28 11:46:18 -05:00
Stefan Wójcik
c6cc013617 fix BaseQuerySet.fields when mixing exclusion/inclusion with complex values like $slice (#1452) 2016-12-28 11:40:57 -05:00
Stefan Wójcik
f4e1d80a87 support a negative dec operator (#1450) 2016-12-28 02:04:49 -05:00
Stefan Wójcik
91dad4060f raise an error when trying to save an abstract document (#1449) 2016-12-28 00:51:47 -05:00
Stefan Wojcik
e07cb82c15 validate db_field 2016-12-27 17:38:26 -05:00
Stefan Wojcik
2770cec187 better docstring for BaseQuerySet.fields 2016-12-27 10:20:13 -05:00
Stefan Wojcik
5c3928190a fix line width 2016-12-22 13:20:05 -05:00
Manuel Jeckelmann
9f4b04ea0f Fix querying an embedded document field by an invalid value (#1440) 2016-12-22 13:19:18 -05:00
Stefan Wojcik
96d20756ca remove redundant whitespace 2016-12-22 13:13:19 -05:00
John Dupuy
b8454c7f5b Fixed ListField deletion bug (#1435) 2016-12-22 13:11:44 -05:00
George Karakostas
c84f703f92 Update documentation to include a Q import (#1441) 2016-12-22 13:06:55 -05:00
Manuel Jeckelmann
57c2e867d8 Remove py26 from contributing docs (#1439)
Python 2.6 is not supported anymore with version 0.11.0
2016-12-19 17:54:43 -05:00
Stefan Wojcik
553f496d84 fix tests 2016-12-13 00:42:03 -05:00
Stefan Wojcik
b1d8aca46a update the changelog 2016-12-12 23:33:49 -05:00
Stefan Wojcik
8e884fd3ea make the __in=non_iterable_or_doc tests more concise 2016-12-12 23:30:38 -05:00
Malthe Jørgensen
76524b7498 Raise TypeError when __in-operator used with a Document (#1237) 2016-12-12 23:27:25 -05:00
Stefan Wojcik
65914fb2b2 fix the way MongoDB URI w/ ?replicaset is passed 2016-12-12 23:24:19 -05:00
Stefan Wojcik
a4d0da0085 update the changelog 2016-12-12 23:08:57 -05:00
Stefan Wójcik
c9d496e9a0 Fix connecting to MongoReplicaSetClient (#1436) 2016-12-12 23:08:11 -05:00
Stefan Wojcik
88a951ba4f version bump 2016-12-12 19:03:21 -05:00
Stefan Wójcik
403ceb19dc set @wojcikstefan as the maintainer (closes #1342) (#1434) 2016-12-12 10:44:03 -05:00
Stefan Wójcik
835d3c3d18 Improve the health of this package (#1428) 2016-12-11 18:49:21 -05:00
Gilbert Gilb's
3135b456be Upgrade notice for 0.10.7 (#1433) 2016-12-11 15:38:06 -05:00
Omer Katz
0be6d3661a Merge branch 'master' of git://github.com/mrTable/mongoengine 2016-12-11 10:52:08 +02:00
Stefan Wojcik
6f5f5b4711 version bump (forgot to do it with v0.10.8 release, so have to go for v0.10.9) 2016-12-10 23:36:06 -05:00
Stefan Wojcik
c6c5f85abb update the changelog with everything we've added in v0.10.8 2016-12-10 23:30:16 -05:00
Stefan Wójcik
7b860f7739 Deprecate Python v2.6 (#1430) 2016-12-10 13:29:31 -05:00
Stefan Wojcik
e28804c03a add venv & venv3 to .gitignore 2016-12-10 13:11:37 -05:00
Stefan Wójcik
1b9432824b Add ability to filter the generic reference field by ObjectId and DBRef (#1425) 2016-12-09 12:56:06 -05:00
Дмитрий Янцен
3b71a6b5c5 Improved tests to avoid regression Issue #1103 2016-12-06 11:33:44 +05:00
Дмитрий Янцен
7ce8768c19 Added rounding for unicode return value of to_mongo in DecimalField. Closes #1103 2016-12-06 10:42:10 +05:00
rmendocna
25e0f12976 fix delete cascade for models without a literal id field: replace with pk (#1247) 2016-12-05 22:54:21 -05:00
Stefan Wójcik
f168682a68 Dont let the MongoDB URI override connection settings it doesnt explicitly specify (#1421) 2016-12-05 22:31:00 -05:00
Stefan Wójcik
d25058a46d Implement BaseQuerySet.batch_size (#1426) 2016-12-05 22:13:22 -05:00
Stefan Wójcik
4d0c092d9f Fix iteration within iteration (#1427) 2016-12-05 09:38:24 -05:00
Stefan Wójcik
15714ef855 Fix __repr__ method of the StrictDict (#1424) 2016-12-04 16:10:59 -05:00
Stefan Wójcik
eb743beaa3 fix doc.get_<field>_display + unit test inspired by #1279 (#1419) 2016-12-04 00:34:24 -05:00
Stefan Wójcik
0007535a46 Add support for cursor.comment (#1420) 2016-12-04 00:33:42 -05:00
Stefan Wójcik
8391af026c Fix filtering by embedded_doc=None (#1422) 2016-12-04 00:32:53 -05:00
Stefan Wójcik
800f656dcf remove unnecessary randomness in indexes tests (#1423) 2016-12-04 00:31:54 -05:00
Stefan Wojcik
088c5f49d9 update the changelog 2016-12-03 16:32:14 -05:00
Ollie Ford
d8d98b6143 Support Falsey primary_keys (#1354) 2016-12-03 16:10:05 -05:00
zeez
02fb3b9315 Support for authentication mechanism #905 (#1333) 2016-12-03 16:08:24 -05:00
Francesc Elies
4f87db784e Make the README example easier to replicate (#1382) 2016-12-02 22:05:20 -05:00
Jérôme Lafréchoux
7e6287b925 Merge pull request #1417 from MongoEngine/fix-db-field-in-sum-and-average
Fix BaseQuerySet#sum and BaseQuerySet#average for fields that specify a db_field
2016-12-02 20:53:48 +01:00
Stefan Wojcik
999cdfd997 Fix BaseQuerySet#sum and BaseQuerySet#average for fields that specify a db_field 2016-12-02 11:32:38 -05:00
Jérôme Lafréchoux
8d6cb087c6 Fix changelog 2016-11-29 09:28:13 +01:00
Stefan Wojcik
2b7417c728 add a missing entry to the changelog 2016-11-28 19:33:11 -05:00
Stefan Wójcik
3c455cf1c1 Improve health of this package (#1409)
* added flake8 and flake8-import-order to travis for py27

* fixed a test that fails from time to time depending on an order of a dict

* flake8 tweaks for the entire codebase excluding tests
2016-11-28 19:00:34 -05:00
Stefan Wójcik
5135185e31 Use SVG in README badges 2016-11-28 12:31:50 -05:00
Stefan Wojcik
b461f26e5d version bump 2016-11-28 10:42:05 -05:00
Stefan Wojcik
faef5b8570 finalize the v0.10.7 changelog 2016-11-28 10:40:20 -05:00
Omer Katz
0a20e04c10 Merge pull request #1383 from BenCoDev/patch-1
Dictionnary Field recommended use
2016-11-27 18:39:04 +02:00
Stefan Wojcik
d19bb2308d add #1389 to the changelog 2016-11-24 09:40:17 -05:00
BenCotte
d8dd07d9ef Updating Dict Fields use description
Update dict fields use misleading description to clarify use case.
2016-11-20 11:22:34 +01:00
Omer Katz
36c56243cd Merge pull request #1399 from sallyruthstruik/master
Add info in CHANGELOG
2016-11-20 10:28:14 +02:00
Stanivlav Kaledin
23d06b79a6 Add info in CHANGELOG 2016-11-17 19:05:46 +03:00
Omer Katz
e4c4e923ee Merge pull request #1397 from sallyruthstruik/master
Fixed issue https://github.com/MongoEngine/mongoengine/issues/442
2016-11-17 11:32:09 +02:00
Omer Katz
936d2f1f47 Merge pull request #1334 from touilleMan/bug-892
Raise DoesNotExist when dereferencing unknown document
2016-11-17 11:31:15 +02:00
Emmanuel Leblond
07018b5060 Raise DoesNotExist when dereferencing unknown document 2016-11-17 09:21:34 +01:00
Stanivlav Kaledin
ac90d6ae5c Don't force _cursor 2016-11-14 20:06:34 +03:00
Stanivlav Kaledin
2141f2c4c5 Fixed issue https://github.com/MongoEngine/mongoengine/issues/442
Added support for pickling BaseQueryset instances
Added BaseQueryset.__getstate__, BaseQuerySet.__setstate__ methods
2016-11-14 19:57:48 +03:00
Jérôme Lafréchoux
81870777a9 Update changelog 2016-10-24 12:00:01 +02:00
Jérôme Lafréchoux
845092dcad Merge pull request #1390 from closeio/dont-test-on-py-32
Don't run tests for python 3.2
2016-10-20 09:42:13 +02:00
Stefan Wojcik
dd473d1e1e remove v3.2 from .travis.yml 2016-10-19 18:15:15 -04:00
Stefan Wojcik
d2869bf4ed Merge branch 'master' of github.com:MongoEngine/mongoengine into dont-test-on-py-32 2016-10-19 18:13:41 -04:00
Jérôme Lafréchoux
891a3f4b29 Merge pull request #1391 from closeio/fix-py26
Fix Python 2.6 tests
2016-10-19 23:24:55 +02:00
Jérôme Lafréchoux
6767b50d75 Merge pull request #1389 from jtharpla/topic/fix-hosts-as-list
Fix connecting to a list of hosts
2016-10-19 23:01:21 +02:00
Stefan Wojcik
d9e4b562a9 for good measure, remove py32 from the commented-out envlist, too 2016-10-19 16:30:39 -04:00
Stefan Wojcik
fb3243f1bc readme fix 2016-10-19 16:23:48 -04:00
Stefan Wojcik
5fe1497c92 remove rednose from tox deps 2016-10-19 16:14:02 -04:00
Stefan Wojcik
5446592d44 remove rednose to see if it masks another issue 2016-10-19 16:05:59 -04:00
Stefan Wojcik
40ed9a53c9 dont run tests for python 3.2 2016-10-19 15:43:07 -04:00
Jeff Tharp
f7ac8cea90 Fix connecting to a list of hosts 2016-10-19 11:57:02 -07:00
Jérôme Lafréchoux
4ef5d1f0cd Merge pull request #1384 from closeio/fix-email-address-english
Use proper English spelling for "email address"
2016-10-12 17:40:52 +02:00
Thomas Steinacher
6992615c98 Use English spelling for "email address" 2016-10-12 16:59:54 +02:00
BenCotte
43dabb2825 Dictionnary Field recommended use
At Dictionary Fields description, it seems that the intent of the sentence make more sense by adding : "not".
2016-10-11 18:14:17 +02:00
Jérôme Lafréchoux
05e40e5681 Merge pull request #1128 from iici-gli/master
Fixed: ListField minus index assignment does not work #1119
2016-09-07 09:29:31 +02:00
Gang Li
2c4536e137 redo fix for ListField loses use_db_field when serializing #1217
The new fix reverted the change on BaseField to_mongo so that it will not force
new field class to add kwargs to to_mongo function. The new derived field class
to_mongo can support use_db_field and fields parameters as needed.
Basically all field classes derived from ComplexBaseField support those parameters.
2016-09-06 17:27:47 -04:00
Jérôme Lafréchoux
3dc81058a0 Merge pull request #1346 from anih/master
Speed up checking if we passed missing field
2016-09-06 09:51:33 +02:00
anih
bd84667a2b fixes 2016-09-06 09:27:41 +02:00
iici-gli
e5b6a12977 Merge pull request #1 from MongoEngine/master
pull new changes from original
2016-09-04 23:43:04 -04:00
Gang Li
ca415d5d62 Fix for:Base document _mark_as_changed bug #1369 2016-09-04 14:20:59 -04:00
Jérôme Lafréchoux
99b4fe7278 Merge pull request #1351 from mindojo-victor/1176
Fix for #1176 -- similar to https://github.com/MongoEngine/mongoengin…
2016-09-04 09:18:14 +02:00
Victor
327e164869 Fix for #1176 -- similar to https://github.com/MongoEngine/mongoengine/pull/982 but for update. 2016-09-04 08:12:17 +03:00
Jérôme Lafréchoux
25bc571f30 Merge pull request #1331 from bagerard/fix_unit_test
fixes in the test suite
2016-09-03 22:54:28 +02:00
Jérôme Lafréchoux
38c7e8a1d2 Merge pull request #1363 from skoval00/fix-misleading-comment
Fix misleading comment about the descriptor
2016-09-03 22:03:06 +02:00
Jérôme Lafréchoux
ca282e28e0 Merge pull request #1360 from Gallaecio/patch-1
Fix array-slicing documentation
2016-08-22 10:13:08 +02:00
Sergey Kovalev
5ef59c06df Fix misleading comment about the descriptor 2016-08-13 09:41:26 +03:00
Gallaecio
8f55d385d6 Fix array-slicing documentation
Fixes #1359.
2016-08-11 08:52:53 +02:00
Jérôme Lafréchoux
cd2fc25c19 Merge pull request #1353 from DionysusG/master
fix typo at docs/guide/defineing-documents.rst
2016-08-04 11:28:11 +02:00
DionysusG
709983eea6 fix typo at docs/guide/defineing-documents.rst 2016-08-04 16:21:52 +08:00
anih
40e99b1b80 Speed up checking if we passed missing field 2016-07-27 12:10:46 +02:00
Jérôme Lafréchoux
488684d960 Merge pull request #1340 from latteier/master
fix example for register_delete_rule. see issue #1339
2016-07-18 22:54:54 +02:00
Amos Latteier
f35034b989 fix example for register_delete_rule. see issue #1339 2016-07-18 13:23:01 -07:00
Omer Katz
9d6f9b1f26 Merge pull request #1336 from closeio/aggregate-sum-and-avg
Replace map-reduce based QuerySet.sum/average with aggregation-based implementations
2016-07-12 11:20:13 +03:00
Stefan Wojcik
6148a608fb update the changelog 2016-07-11 10:45:40 -07:00
Stefan Wojcik
3fa9e70383 prefer tuples over lists for immutable structures 2016-07-11 10:42:27 -07:00
Stefan Wojcik
16fea6f009 replace QuerySet.sum/average implementations with aggregate_sum/average + tweaks 2016-07-10 13:21:12 -07:00
Bastien Gérard
df9ed835ca fixes in unit tests 2016-07-02 23:01:36 +02:00
Jérôme Lafréchoux
e394c8f0f2 Merge pull request #1328 from anentropic/upsert-docs-fix
better description for upsert arg on some methods
2016-06-29 15:56:45 +02:00
Anentropic
21974f7288 better description for upsert arg on some methods 2016-06-29 14:24:33 +01:00
Jérôme Lafréchoux
5ef0170d77 Merge pull request #1324 from vahana/patch-1
Update changelog.rst
2016-06-24 19:58:31 +02:00
vahan
c21dcf14de Update changelog.rst 2016-06-24 13:45:42 -04:00
Jérôme Lafréchoux
a8d20d4e1e Merge pull request #1313 from roivision/master
Fix for issue # 1278
2016-06-24 17:46:04 +02:00
Jérôme Lafréchoux
8b307485b0 Merge pull request #1314 from adamchainz/readthedocs.io
Convert readthedocs links for their .org -> .io migration for hosted projects
2016-06-17 14:55:04 +02:00
Adam Chainz
4544afe422 Convert readthedocs links for their .org -> .io migration for hosted projects
As per [their blog post of the 27th April](https://blog.readthedocs.com/securing-subdomains/) ‘Securing subdomains’:

> Starting today, Read the Docs will start hosting projects from subdomains on the domain readthedocs.io, instead of on readthedocs.org. This change addresses some security concerns around site cookies while hosting user generated data on the same domain as our dashboard.

Test Plan: Manually visited all the links I’ve modified.
2016-06-16 21:21:10 +01:00
Jérôme Lafréchoux
9d7eba5f70 Merge pull request #1307 from xiaost/update-for-1304
Update changelog for #1304
2016-06-02 20:38:25 +02:00
xiaost
be0aee95f2 Update changelog for #1304 2016-06-03 01:27:39 +08:00
Omer Katz
3469ed7ab9 Merge pull request #1304 from xiaost/fix-no-cursor-timeout
Fix no_cursor_timeout with pymongo3
2016-05-29 10:15:20 +03:00
xiaost
1f223aa7e6 Fix no_cursor_timeout with pymongo3 2016-05-26 00:29:41 +08:00
Omer Katz
0a431ead5e Merge pull request #1289 from closeio/fix-typo
Fix typo in the docstring for __len__
2016-05-05 15:53:00 +03:00
Stefan Wojcik
f750796444 fix typo 2016-05-04 17:11:38 -07:00
vahan
c82bcd882a Merge pull request #1 from roivision/dynamic_document_dict_fix
* fixed the bug where dynamic doc has indx inside dict field
2016-05-01 23:07:24 -04:00
vahan
7d0ec33b54 * fixed the bug where dynamic doc has indx inside dict field 2016-05-01 22:59:39 -04:00
Omer Katz
43d48b3feb Merge pull request #1271 from maitbayev/master
Fixes unicode bug in EmbeddedDocumentListField
2016-04-17 09:15:23 +03:00
Omer Katz
2e406d2687 Merge pull request #1277 from shushen/Bug-681
Fix AttributeError when initializing EmbeddedDocuments
2016-04-11 12:57:08 +03:00
Shu Shen
3f30808104 Fix AttributeError when creating EmbeddedDocument
When an EmbeddedDocument is initialized with positional arguments, the
document attempts to read _auto_id_field attribute which may not exist
and would throw an AttributeError exception and fail the initialization.

This change and the test is based on the discussion in issue #681 and
PR #777 with a number of community members.
2016-04-07 15:18:33 -07:00
Omer Katz
ab10217c86 Merge pull request #1270 from Neurostack/master
Bug fixed accessing BaseList with negative indices
2016-03-31 22:27:56 +03:00
Neurostack
00430491ca Fixed bug accessing ListField (BaseList) with negative indices
If you __setitem__ in BaseList with a negative index and then try to save this, you will get an error like: OperationError: Could not save document (cannot use the part (shape of signal.shape.-1) to traverse the element ({shape: [ 0 ]})). To fix this I rectify negative list indices in BaseList _mark_as_changed as the appropriate positive index. This fixes the above error.
2016-03-31 08:04:19 -06:00
Madiyar Aitbayev
109202329f Handles unicode correctly EmbeddedDocumentListField 2016-03-31 02:33:13 +01:00
Omer Katz
3b1509f307 Added changelog entry for #1267. 2016-03-26 09:13:25 +03:00
Omer Katz
7ad7b08bed Merge pull request #1267 from wishtack/hotfix-map-field-unicode-key
Fix MapField in order to handle unicode keys.
2016-03-26 09:06:24 +03:00
Younes JAAIDI
4650e5e8fb Fix MapField in order to handle unicode keys. 2016-03-25 12:42:00 +01:00
Omer Katz
af59d4929e Merge pull request #1254 from gilbsgilbs/fix_long_fields_python3
Fix long fields python3
2016-03-23 15:17:06 +02:00
Gilb's
e34100bab4 Another attempt to fix random fails of test test_compound_key_dictfield. 2016-03-18 23:43:23 +01:00
Gilb's
d9b3a9fb60 Use six integer types instead of explicit types, since six is now a dependency of the project. 2016-03-18 19:51:09 +01:00
Gilb's
39eec59c90 Fix test failing randomly because of concurrency. 2016-03-18 19:45:34 +01:00
Gilb's
d651d0d472 Fix tests and imports. issue #1253 2016-03-18 19:45:34 +01:00
Gilbert Gilb's
87a2358a65 Fix unused variable. issue #1253 2016-03-18 19:45:34 +01:00
Gilbert Gilb's
cef4e313e1 Update changelog for #1253 2016-03-18 19:45:34 +01:00
Gilbert Gilb's
7cc1a4eba0 Fix long fields stored as int32 in Python 3. issue #1253 2016-03-18 19:45:34 +01:00
Omer Katz
c6cc0133b3 Merge pull request #1240 from gukoff/long_in_floatfield
Added support for long values in FloatFields
2016-03-18 10:24:53 +02:00
Omer Katz
7748e68440 Adjust changelog for #1188. 2016-03-10 12:19:11 +02:00
Omer Katz
6c2230a076 Merge pull request #1188 from DavidBord/fix-1187
fix-#1187: count on ListField of EmbeddedDocumentField fails
2016-03-10 12:18:20 +02:00
Konstantin Gukov
66b233eaea Added the six module to test int/long support 2016-03-06 23:01:49 +05:00
Konstantin Gukov
fed58f3920 Added support for long values in FloatFields 2016-02-24 14:07:22 +05:00
Omer Katz
815b2be7f7 Merge pull request #1183 from bitdivision/patch-1
Add EmbeddedDocumentListField to user guide
2016-02-24 09:01:54 +02:00
Omer Katz
f420c9fb7c Merge pull request #1235 from hhstore/master
fix a small bug - ReferenceField() comment give a wrong demo .
2016-02-24 08:58:40 +02:00
Omer Katz
01bdf10b94 Merge pull request #1241 from gukoff/broad_exceptions
Fixed too broad exception clauses in the project
2016-02-24 08:57:21 +02:00
Konstantin Gukov
ddedc1ee92 Fixed too broad exception clauses in the project 2016-02-23 23:50:45 +05:00
Emmanuel Leblond
9e9703183f Add test for nested list in EmbeddedDocument 2016-02-19 02:16:37 +01:00
Emmanuel Leblond
adce9e6220 Raise OperationError in drop_collection if no collection is set 2016-02-19 01:58:15 +01:00
Emmanuel Leblond
c499133bbe Add missing drop_collection in tests fields 2016-02-19 00:11:30 +01:00
hhstore
8f505c2dcc fix a small bug - ReferenceField() comment give a wrong demo . 2016-02-17 10:55:31 +08:00
Emmanuel Leblond
b320064418 Add signal_kwargs arg for save/delete/bulk insert 2016-02-09 14:28:55 +01:00
Emmanuel Leblond
a643933d16 Fix cascade delete mixing among collections 2016-01-30 11:59:55 +01:00
Omer Katz
2659ec5887 Merge pull request #1196 from nickptrvc/master
Fix pre_bulk_insert signal
2016-01-30 12:25:29 +02:00
Emmanuel Leblond
9f8327926d Improve a bit queryset's test_elem_match 2016-01-28 18:18:51 +01:00
Emmanuel Leblond
7a568dc118 Add version 0.10.7 - DEV in changelog.rst 2016-01-26 15:54:57 +01:00
Emmanuel Leblond
c946b06be5 Merge pull request #1218 from bbenne10/master
Curry **kwargs through to_mongo on fields
2016-01-26 15:53:21 +01:00
Bryan Bennett
c65fd0e477 Note changes for #1217 in Changelog 2016-01-26 08:34:52 -05:00
Bryan Bennett
8f8217e928 Add Bryan Bennett to AUTHORS 2016-01-26 08:34:52 -05:00
Bryan Bennett
6c9e1799c7 MongoEngine/mongoengine #1217: Curry **kwargs through to_mongo on fields 2016-01-26 08:34:52 -05:00
Emmanuel Leblond
decd70eb23 Merge pull request #1220 from bagerard/patch-1
fixed minor typo in docstring
(PR has been issued by mistake to dev branch insteed of master)
2016-01-26 00:28:04 +01:00
Emmanuel Leblond
a20d40618f Bump to v0.10.6 2016-01-25 01:42:19 +01:00
Emmanuel Leblond
b4af8ec751 Fix travis for python 3.2 2016-01-22 08:38:42 +01:00
Bastien
feb5eed8a5 fixed minor typo in docstring 2016-01-21 16:59:37 +01:00
Emmanuel Leblond
f4fa39c70e Revert "Force pip version to 7.1.2 in tox for py32 (support dropped for latter versions)"
This reverts commit 7b7165f5d8.
2016-01-20 13:07:06 +01:00
Emmanuel Leblond
7b7165f5d8 Force pip version to 7.1.2 in tox for py32 (support dropped for latter versions) 2016-01-20 11:48:31 +01:00
Emmanuel Leblond
13897db6d3 Fix mongomock url prefix error during connection 2016-01-20 11:06:45 +01:00
Emmanuel Leblond
c4afdb7198 Merge pull request #1123 from Cykooz/master
Fixed detection of shared connections
2016-01-19 18:38:03 +01:00
Emmanuel Leblond
0284975f3f Correct test_reload_of_non_strict_with_special_field_name for pymongo<2.9 2016-01-19 15:34:38 +01:00
Omer Katz
269e3d1303 Merge pull request #1205 from Zephor5/patch-1
add highlight for python code
2016-01-13 15:59:08 +02:00
Zephor
8c81f7ece9 add highlight for python code 2016-01-06 12:00:48 +08:00
Omer Katz
f6e0593774 Merge pull request #1198 from rusnassonov/patch-1
fix missing quote in /docs/guide/mongomock.rst
2015-12-27 13:54:58 +02:00
Ruslan Nassonov
3d80e549cb fix missing quote in /docs/guide/mongomock.rst 2015-12-25 15:52:35 +05:00
Nick Pjetrovic
acc7448dc5 Fix pre_bulk_insert signal 2015-12-24 18:30:46 -05:00
David Bordeynik
35d3d3de72 fix-#1187: count on ListField of EmbeddedDocumentField fails 2015-12-15 22:27:53 +02:00
Omer Katz
0372e07eb0 Merge pull request #1114 from gmacon/sparse-compound
Allow sparse compound indexes
2015-12-10 07:27:22 +02:00
George Macon
00221e3410 Allow sparse compound indexes 2015-12-09 18:38:28 -05:00
bitdivision
9c264611cf Add EmbeddedDocumentListField to user guide
The 'defining a document' section currently doesn't include EmbeddedDocumentListField. Only EmbeddedDocumentField
2015-12-09 11:40:22 +00:00
Omer Katz
31d7f70e27 Merge pull request #1153 from AWhetter/fixWindowsTest
Fixed not being able to run tests on Windows
2015-12-08 20:29:28 +02:00
Ashley Whetter
04e8b83d45 Fixed being unable to run tests on Windows 2015-12-08 18:08:10 +00:00
Omer Katz
e87bf71f20 Merge pull request #1170 from hhstore/master
fix for docs.code.tumblelog.py
2015-12-08 07:16:59 +02:00
Omer Katz
2dd70c8d62 Merge pull request #1180 from moonso/add-mongomock-docs
Added page for documenting mongomock. Updated docs/guide/index.rst
2015-12-06 13:23:04 +02:00
moonso
a3886702a3 Added page for documenting mongomock. Updated docs/guide/index.rst 2015-12-06 11:02:26 +01:00
Omer Katz
713af133a0 Moved #1151 changelog entry to the correct version. 2015-12-06 07:54:25 +02:00
Omer Katz
057ffffbf2 Merge pull request #1151 from RussellLuo/feature-support-mocking
Add support for mocking MongoEngine based on mongomock
2015-12-06 07:53:00 +02:00
RussellLuo
a81d6d124b Update AUTHORS and add changelog entry for #1151 2015-12-06 11:11:46 +08:00
RussellLuo
23f07fde5e Add support for mocking MongoEngine based on mongomock
Using `mongomock://` scheme in URI enables the mocking. Fix #1045.
2015-12-06 11:08:00 +08:00
Omer Katz
b42b760393 Merge branch 'fix-reloading-strict' of https://github.com/paularmand/mongoengine into fix-reloading-strict and bumped version.
# Conflicts:
#	AUTHORS
2015-11-30 12:13:47 +02:00
Omer Katz
bf6f4c48c0 Merge pull request #1167 from BeardedSteve/upsert_one
Upsert one
2015-11-30 12:08:04 +02:00
Paul-Armand Verhaegen
6133f04841 Manual merge conflicts in AUTHORS 2015-11-27 23:55:55 +01:00
Paul-Armand Verhaegen
3c18f79ea4 Added test for reloading of strict with special fields #1156 2015-11-27 23:45:25 +01:00
hhstore
2af8342fea bugfix - two small bugs. 2015-11-26 12:01:42 +08:00
srossiter
fc3db7942d updated changelog and version tuple 2015-11-24 12:56:59 +00:00
srossiter
164e2b2678 Docstring change and rename variable to avoid clash with kwargs 2015-11-24 12:53:09 +00:00
srossiter
b7b28390df Added upsert_one method on BaseQuerySet and modified test_upsert_one 2015-11-24 12:46:38 +00:00
Omer Katz
a6e996d921 Added #1165 to the changelog. 2015-11-24 07:06:54 +02:00
Omer Katz
07e666345d Merge pull request #1165 from touilleMan/bug-1164
Add SaveConditionError to __all__
2015-11-24 07:04:54 +02:00
Omer Katz
007f10d29d Merge pull request #1161 from AWhetter/docFix
Fixed a couple of documentation typos
2015-11-24 07:01:49 +02:00
Omer Katz
f9284d20ca Moved #1042 to the next version in the changelog. 2015-11-24 07:00:09 +02:00
Omer Katz
9050869781 Merge pull request #1042 from closeio/fix-read-preference
Fix read_preference
2015-11-24 06:58:51 +02:00
Stefan Wojcik
54975de0f3 fix read_preference for PyMongo 3+ 2015-11-23 10:46:52 -08:00
Stefan Wojcik
a7aead5138 re-create the cursor object whenever we apply read_preference 2015-11-23 10:46:52 -08:00
Omer Katz
6868f66f24 Merge pull request #1155 from AWhetter/fix837
ReferenceFields can now reference abstract Document types
2015-11-23 15:52:54 +02:00
Omer Katz
3c0b00e42d Added python 3.5 to the build. 2015-11-23 15:40:16 +02:00
Omer Katz
3327388f1f Merge pull request #1122 from larsbutler/improve-reverse_delete_rule-docs
fields.ReferenceField: add integer values to `reverse_delete_rule` docs
2015-11-23 15:29:20 +02:00
Ashley Whetter
04497aec36 Fixed setting dbref to True on abstract reference fields causing the reference to be stored incorrectly 2015-11-23 13:21:30 +00:00
Ashley Whetter
aa9d596930 Updated documentation for abstract reference changes 2015-11-23 13:21:30 +00:00
Ashley Whetter
f96e68cd11 Made type inheritance a validation check for abstract references 2015-11-23 13:20:35 +00:00
Ashley Whetter
013227323d ReferenceFields can now reference abstract Document types
A class that inherits from an abstract Document type is stored in the database
as a reference with a 'cls' field that is the class name of the document being
stored.

Fixes #837
2015-11-23 13:20:35 +00:00
Omer Katz
19cbb442ee Added #1129 to the changelog. 2015-11-23 13:57:15 +02:00
Omer Katz
c0e7f341cb Merge pull request #1129 from illico/feature/arbitrary-metadata
Indirection-free optimized field metadata.
2015-11-23 12:49:48 +02:00
Emmanuel Leblond
0a1ba7c434 Add SaveConditionError to __all__ 2015-11-21 10:25:11 +01:00
Omer Katz
b708dabf98 Merge pull request #1158 from gmacon/551-shard-key-embedded
Allow shard key to be in an embedded document (#551)
2015-11-20 07:50:52 +02:00
George Macon
899e56e5b8 Add gmacon to AUTHORS 2015-11-19 17:15:43 -05:00
George Macon
f6d3bd8ccb Update changelog for #551 2015-11-19 17:15:27 -05:00
George Macon
deb5677a57 Allow shard key to be in an embedded document (#551) 2015-11-19 17:14:45 -05:00
Omer Katz
5c464c3f5a Bumped version to 0.10.1 2015-11-18 14:07:39 +02:00
Ashley Whetter
cceef33fef Fixed a couple of documentation typos 2015-11-17 14:22:10 +00:00
Paul-Armand Verhaegen
ed8174fe36 Added Paul-Armand Verhaegen to contributor list 2015-11-15 15:32:26 +01:00
Paul-Armand Verhaegen
3c8906494f Added #1156 to changelog 2015-11-15 15:31:22 +01:00
Paul-Armand Verhaegen
6e745e9882 fixed wrong indentation style 2015-11-10 21:13:24 +01:00
Paul-Armand Verhaegen
fb4e9c3772 fix for reloading of strict with special fields 2015-11-10 20:43:49 +01:00
Omer Katz
2c282f9550 Added changelog entry for #1131. 2015-11-08 12:18:12 +02:00
Omer Katz
d92d41cb05 Merge pull request #1131 from noirbizarre/fix-instance-back-references
Fix instance back references
2015-11-08 12:14:37 +02:00
Omer Katz
82e7050561 Merge pull request #1134 from abonhomme/patch-1
docstring correction
2015-11-05 15:49:53 +02:00
abonhomme
44f92d4169 docstring correction
Corrected the docstring for `mongoengine.queryset.base.update_one()`
2015-10-23 11:07:26 -04:00
David Bordeynik
2f1fae38dd Merge pull request #1117 from reallistic/master
Enable ops in queries using elemMatch for EmbeddedDocuments
fixes #1130
2015-10-21 08:40:39 +03:00
Axel Haustant
9fe99979fe Fix tests on Python 2.6 (assertIsNotNone does not exists) 2015-10-19 18:04:15 +02:00
Axel Haustant
6399de0b51 Fix _instance on list of EmbeddedDocuments 2015-10-19 16:39:00 +02:00
Axel Haustant
959740a585 Fix false positive test on _instance 2015-10-19 16:33:40 +02:00
reallistic
159b082828 Recursively create mongo query for embeddeddocument elemMatch 2015-10-18 16:34:24 -07:00
Gang Li
8e7c5af16c Merge remote-tracking branch 'remotes/upstream/master'
Conflicts:
	AUTHORS
	docs/changelog.rst
2015-10-18 01:28:50 -04:00
Gang Li
c1645ab7a7 restored 2015-10-18 01:14:27 -04:00
Gang Li
2ae2bfdde9 updated changelog.rst for #1119 2015-10-18 00:31:40 -04:00
Gang Li
3fe93968a6 update test case for: Please recall fix on: Saving document doesn't create new fields in existing collection #620 #1126 2015-10-18 00:19:36 -04:00
Omer Katz
79a2d715b0 Merge pull request #1121 from larsbutler/simplify-install-deps
Move nose/rednose from install dependencies to test dependencies
2015-10-14 12:45:14 +03:00
Alice Bevan–McGregor
50b271c868 Arbitrary metadata documentation. 2015-10-13 22:51:03 -04:00
Alice Bevan–McGregor
a57f28ac83 Correction for local monkeypatch. 2015-10-13 22:41:58 -04:00
Alice Bevan–McGregor
3f3747a2fe Minor formatting tweaks and additional comments. 2015-10-13 21:59:46 -04:00
Alice Bevan–McGregor
d133913c3d Remove now superfluous special cases.
Removes `verbose_name`, `help_text`, and `custom_data`.  All three are
covered by the one metadata assignment and will continue working as
expected.
2015-10-13 21:59:29 -04:00
Alice Bevan–McGregor
e049cef00a Add arbitrary metadata capture to BaseField.
Includes ability to detect and report conflicts.
2015-10-13 21:54:58 -04:00
Gang Li
eb8176971c Removed "elif field.default" block to avoid silently, inconsistently changing database
This resolved issue Please recall fix on: Saving document doesn't create new fields in existing collection #620 #1126
2015-10-12 23:33:54 -04:00
Gang Li
5bbfca45fa Fixed: ListField minus index assignment does not work #1119
Add code to detect '-1' as a integer.
Normalize negative index to regular list index
Added list assignment test case
2015-10-12 10:34:26 -04:00
Lars Butler
9b500cd867 docs/changelog.rst: fix #1079 to version 0.10.1 - DEV 2015-10-12 10:13:41 +02:00
Lars Butler
b52cae6575 AUTHORS: Add Lars Butler (that's me!) 2015-10-12 10:13:11 +02:00
Lars Butler
35a0142f9b setup.py, tox.ini: move nose/rednose from install deps to test deps
Remove nose/rednose from `setup_requires` and instead declare them in
`tests_require`. Also explicitly add `nose` and `rednose` to
dependencies list in tox.ini (to avoid breaking test runs).

`python setup.py nosetests` is the preferred way for running tests, and
this works, except that placing test deps (nose/rednose) in the
`setup_requires` also means that these dependencies are pulled in for
installs of mongoengine. These deps are not actually be required just
to run mongoengine, so setup.py should not force users to install these
dependencies.

This refactoring should not change any test run semantics.
2015-10-12 10:13:11 +02:00
David Bordeynik
d4f6ef4f1b Merge pull request #1113 from DavidBord/fix-1105
fix-#1105: StrictDict & SemiStrictDict are shadowed at init time
2015-10-11 21:14:05 +03:00
Kirill Kuzminykh
11024deaae Fixed detection of shared connections 2015-10-05 22:40:44 +03:00
Lars Butler
5a038de1d5 fields.ReferenceField: add integer values to reverse_delete_rule docs
When I first tried to use the `reverse_delete_rule` feature of
`ReferenceField`, I had to dig through the source code to find what the
actual integer values were expected to be for DO_NOTHING, NULLIFY,
CASCADE, DENY, and PULL (or at least, where these constants were defined
so that I could import and use them). This patch adds the integer values
for those constants (which are defined in mongoengine.queryset.base) to
the docs so that users can easily choose the correct integer value.

Note: A possible improvement on this change would be to include
`mongoengine.queryset.base` module documentation in the generated docs,
and then update the `ReferenceField` docs to link to the documentation
of these constants (DO_NOTHING, NULLIFY, etc.).
2015-10-05 14:37:03 +02:00
Emmanuel Leblond
903982e896 Merge pull request #1088 from touilleMan/bug-1058
Fix DictField with '_cls' field is converted to Document on access
2015-09-21 12:10:23 +02:00
David Bordeynik
6355c404cc fix-#1105: StrictDict & SemiStrictDict are shadowed at init time 2015-09-16 20:27:52 +03:00
Emmanuel Leblond
92b9cb5d43 Add drop_collection for test_subclass_field_query 2015-09-08 17:35:35 +02:00
Emmanuel Leblond
7580383d26 Add #1050 fix to changelog 2015-09-02 19:00:18 +02:00
Catstyle
ba0934e41e added DynamicTest.test_reload_dynamic_field 2015-09-02 18:42:30 +02:00
Catstyle
a6a1021521 use obj._data instead of self._fields_ordered since DynamicDocument missing some attributes 2015-09-02 18:42:30 +02:00
Emmanuel Leblond
33b4d83c73 Merge pull request #1084 from optik/patch-1
Bad property name for text search index meta
2015-09-02 18:27:10 +02:00
Emmanuel Leblond
6cf630c74a Merge pull request #1096 from vasion/save-condition-for-2.4
save_condition uses "n" instead of "nModified"
2015-09-02 18:23:37 +02:00
Emmanuel Leblond
736fe5b84e Fix unwanted dereference in DictField (issue #1058) 2015-08-30 10:01:24 +02:00
Omer Katz
4241bde6ea Merge pull request #1055 from zxhuang/master
comply to pymongo MongoClient constructor host
2015-08-16 11:52:27 +03:00
Momchil Rogelov
b4ce14d744 use n instead of nModified in save_condition 2015-08-13 10:11:42 +01:00
Momchil Rogelov
10832a2ccc save_condition falls back to "n" if "nModified" is not found to support mongo 2.4 2015-08-12 10:57:20 +01:00
Emmanuel Leblond
91aca44f67 Merge pull request #1093 from touilleMan/bug-1069
Replace disconnect with close method in pymongo
2015-08-10 18:40:59 +02:00
Emmanuel Leblond
96cfbb201a Replace use close method in pymongo 2015-08-04 18:02:57 +02:00
David Bordeynik
b2bc155701 Merge pull request #1087 from marcoskv/patch-1
QuerySet count vs len #937
2015-08-01 11:22:35 +03:00
marcoskv
a70ef5594d QuerySet count vs len #937
Current documentation does not consider performance issues in using len instead of count.
https://github.com/MongoEngine/mongoengine/issues/937
2015-07-26 22:41:24 +02:00
optik
6d991586fd Bad property name for indices description in docs
The correct name for MongoDB index definition property is weights not weight. Using "weight" will cause "Index with name ... already exists with different options"
2015-07-24 15:26:10 +02:00
Emmanuel Leblond
f8890ca841 Merge pull request #1070 from touilleMan/save-condition-error
Use SaveConditionError instead of OperationError in save_condition
2015-07-19 11:18:33 +02:00
Emmanuel Leblond
0752c6b24f Update changelog for #1070 2015-07-19 10:33:54 +02:00
Emmanuel Leblond
3ffaf2c0e1 Correct SaveConditionError involved tests 2015-07-15 11:59:29 +02:00
Emmanuel Leblond
a3e0fbd606 Add SaveConditionError exception 2015-07-15 11:15:40 +02:00
Emmanuel Leblond
9c8ceb6b4e Merge pull request #1060 from touilleMan/GenericReferenceField-choices
Fix GenericReferenceField choices parameter
2015-07-09 11:38:22 +02:00
Emmanuel Leblond
bebce2c053 Clean ununsed variables in iterations 2015-07-09 10:51:04 +02:00
Emmanuel Leblond
34c6790762 Simplify implementation of choices in GenericReferenceField 2015-07-06 10:10:05 +02:00
Emmanuel Leblond
a5fb009b62 Fix GenericReferenceField choices with DBRef and let it possible to set Document choice as string 2015-07-06 02:33:43 +02:00
David Bordeynik
9671ca5ebf Merge pull request #1049 from DavidBord/fix-842
fix-#842: Fix ignored chained options
2015-07-03 08:23:15 +03:00
David Bordeynik
5334ea393e fix-#842: Fix ignored chained options 2015-07-02 23:08:09 +03:00
Zeke Huang
2aaacc02e3 comply to pymongo MongoClient constructor host
Only MongoReplicaSetClient use hosts_or_uri param and it will be deprecated soon.
2015-07-01 12:39:30 -07:00
Matthieu Rigal
222e929b2d Merge pull request #1048 from amitlicht/amitlicht/1047_cached_reference_field_bugfix
Suggested fix for #1047: CachedReferenceField DBRefs bug
2015-07-01 08:53:03 +02:00
amitlicht
6f16d35a92 Adding a changelog line & adding myself to AUTHORS. 2015-06-30 15:08:20 +03:00
amitlicht
d7a2ccf5ac Adding a test case for #1047. 2015-06-30 15:03:06 +03:00
amitlicht
9ce605221a Suggested fix for #1047: CachedReferenceField creates DBRef on to_python, but can't save them on to_mongo.
Dereferencing DBRef to document type before returning it from to_python.
2015-06-28 17:53:20 +03:00
Matthieu Rigal
1e930fe950 Merge branch 'emilecaron-master' 2015-06-26 17:59:25 +02:00
Matthieu Rigal
4dc158589c Moved change to right place and added fancier test 2015-06-26 17:58:53 +02:00
emilecaron
4525eb457b update changelog 2015-06-26 14:23:42 +02:00
emilecaron
56a2e07dc2 always store docs in cascade_refs 2015-06-26 10:45:07 +00:00
emilecaron
9b7fe9ac31 restore broken behavior 2015-06-26 09:31:07 +00:00
emilecaron
c3da07ccf7 Merge branch 'master' of https://github.com/emilecaron/mongoengine 2015-06-26 08:54:12 +00:00
emilecaron
b691a56d51 late set instanciation 2015-06-26 08:52:30 +00:00
Emile Caron
13e0a1b5bb Merge pull request #1 from emilecaron/fix_delete_rule_cascade_cycle
Fix delete rule cascade cycle
2015-06-25 21:53:04 +02:00
emilecaron
646baddce4 fix cascade delete cycle issuue 2015-06-25 18:27:22 +00:00
emilecaron
02f61c323d update test 2015-06-25 18:26:52 +00:00
emilecaron
1e3d2df9e7 fix illogicality 2015-06-25 15:40:12 +00:00
emilecaron
e43fae86f1 reproduce RuntimeError 2015-06-25 15:37:15 +00:00
Matthieu Rigal
c6151e34e0 Bumped version to 0.10.0 2015-06-24 12:39:21 +02:00
Matthieu Rigal
45cb991254 Merge pull request #980 from MRigal/fix/various-fixes
Pep8, code clean-up and 0.10.0 changelog finalisation
2015-06-24 10:20:42 +02:00
Matthieu Rigal
839bc99f94 Updated changelog to prepare 0.10.0 release 2015-06-24 01:16:32 +02:00
Matthieu Rigal
0aeb1ca408 Various fixes again 2015-06-24 00:50:36 +02:00
Matthieu Rigal
cd76a906f4 Set coverage to specific version as 4+ is not Python 3.2 compatible 2015-06-24 00:49:39 +02:00
mrigal
e438491938 typos 2015-06-23 23:16:08 +02:00
mrigal
307b35a5bf some more update, mainly docs 2015-06-23 23:16:08 +02:00
mrigal
217c9720ea added iterkeys method and optimized repr, still very ugly 2015-06-23 23:15:44 +02:00
mrigal
778c7dc5f2 general pep8 and more clean-up 2015-06-23 23:15:44 +02:00
Matthieu Rigal
4c80154437 Merge pull request #1014 from kivistein/fix-705
Allow to add custom metadata to fields
2015-06-23 23:06:05 +02:00
Vicky Donchenko
6bd9529a66 Allow to add custom metadata to fields 2015-06-23 16:25:56 +03:00
Matthieu Rigal
33ea2b4844 Merge pull request #1036 from MRigal/snario-min-distance
Added test, doc to implementation of min_distance query
2015-06-22 18:35:45 +02:00
Matthieu Rigal
5c807f3dc8 Various test adjustments to improve stability independantly of execution order 2015-06-22 16:41:36 +02:00
Matthieu Rigal
9063b559c4 Fix for PyMongo3+ 2015-06-22 16:40:50 +02:00
Matthieu Rigal
40f6df7160 Adapted one more test for MongoDB < 3 2015-06-22 14:57:59 +02:00
Matthieu Rigal
95165aa92f Logic and test adaptations for MongoDB < 3 2015-06-22 14:57:59 +02:00
Matthieu Rigal
d96fcdb35c Fixed problem of ordering when using near_sphere operator 2015-06-22 14:57:58 +02:00
Matthieu Rigal
5efabdcea3 Added tests, documentation and simplified code 2015-06-22 14:57:58 +02:00
Liam Horne
2d57dc0565 Fixed an indentation mistake 2015-06-22 14:57:30 +02:00
lihorne
576629f825 Added support for $minDistance query 2015-06-22 14:57:30 +02:00
Matthieu Rigal
5badb9d151 Merge pull request #1035 from MRigal/fix/882-dynamic-lookup-more-than-two-parts
Simplified lookup_field mechanic and allow dynamic lookup for more than two parts
2015-06-22 14:56:15 +02:00
Matthieu Rigal
45dc379d9a Added to changelog 2015-06-22 14:55:38 +02:00
Matthieu Rigal
49c0c9f44c Simplified lookup-field method, allowing dynamic lookup for more than two parts 2015-06-22 14:55:06 +02:00
Matthieu Rigal
ef5fa4d062 Merge pull request #1037 from MRigal/fix/1008-delete-returns-none-allowed
Added test and fix for delete with write_concern w:0
2015-06-22 14:50:04 +02:00
Matthieu Rigal
35b66d5d94 Merge pull request #1020 from nextoa/master
Improve _created status when switching collection and/or db
2015-06-21 13:32:02 +02:00
Matthieu Rigal
d0b749a43c Made test explicit with an assert 2015-06-21 13:02:59 +02:00
Matthieu Rigal
bcc4d4e8c6 Added test and fix for delete with write_concern w:0 2015-06-21 03:40:45 +02:00
Breeze.kay
41bff0b293 remove testcase:test_signals_with_switch_sharding_db() and fix code style error for pull#1020 2015-06-21 09:32:31 +08:00
Breeze.kay
dfc7f35ef1 add testcase and changelog for pull:#1020 'improve _created status when switch collection and db' 2015-06-19 15:40:05 +08:00
Breeze.kay
0bbbbdde80 Merge remote-tracking branch 'MongoEngine/master' 2015-06-19 11:14:51 +08:00
Matthieu Rigal
5fa5284b58 Merge pull request #1021 from elasticsales/aggregate-sum-and-avg
aggregate_sum/average + unit tests
2015-06-18 23:14:27 +02:00
Stefan Wojcik
b7ef82cb67 style tweaks + changelog entry 2015-06-18 11:02:11 -07:00
Stefan Wojcik
1233780265 make aggregate_sum/average compatible with pymongo 3.x 2015-06-18 11:01:37 -07:00
Stefan Wojcik
dd095279c8 aggregate_sum/average + unit tests 2015-06-18 11:01:37 -07:00
Matthieu Rigal
4d5200c50f Merge pull request #1031 from MRigal/fix/1011-capped-collection-size-multiple-of-256
CappedCollection max_size normalized to multiple of 256
2015-06-15 15:25:33 +02:00
Matthieu Rigal
1bcd675ead Python 3 fix, uses floor division 2015-06-15 13:44:11 +02:00
Matthieu Rigal
2a3d3de0b2 CappedCollection max_size normalized to multiple of 256 2015-06-15 00:22:07 +02:00
Matthieu Rigal
b124836f3a Merge pull request #936 from MRigal/fix/712-avoid-crash-looping-on-corrupted-obj-id
changed ObjectIdField to_python() method to avoid crash, issue 712
2015-06-14 23:31:22 +02:00
Matthieu Rigal
93ba95971b Merge pull request #1029 from MRigal/feature/300-remove-get-or-create
Removed get_or_create() method, deprecated since 0.8
2015-06-13 01:09:29 +02:00
Matthieu Rigal
7b193b3745 Merge pull request #1030 from MongoEngine/improved-doc-sequence-field
Improved doc for SequenceField
2015-06-12 22:17:01 +02:00
Matthieu Rigal
2b647d2405 Improved doc for SequenceField
Related to issue #497
2015-06-12 21:20:59 +02:00
Matthieu Rigal
7714cca599 Removed get_or_create() method, deprecated since 0.8 2015-06-12 20:51:59 +02:00
Matthieu Rigal
42511aa9cf Merge pull request #1028 from MRigal/fix/652-url-field-validation-too-restrictive-use-django-validation
Updated URL and Email regex validators, added schemes to url validator
2015-06-12 20:47:58 +02:00
Matthieu Rigal
ace2a2f3d1 Merge pull request #1027 from MRigal/fix/530-combining-only-and-save-deletes-embedded-fields-value-with-default
Added passing test to prove save and only problem was fixed
2015-06-12 20:40:51 +02:00
Matthieu Rigal
2062fe7a08 Merge pull request #1026 from MRigal/fix/497-sequence-field-with-abstract-classes
SequenceField for abstract classes now have a proper name
2015-06-12 15:17:08 +02:00
Matthieu Rigal
d4c02c3988 Added to changelog 2015-06-12 13:12:35 +02:00
Matthieu Rigal
4c1496b4a4 Updated URL and Email field regex validators, added schemes arg to urlfield 2015-06-12 13:10:36 +02:00
Matthieu Rigal
eec876295d Added passing test to prove save and only problem was fixed 2015-06-12 12:13:28 +02:00
Matthieu Rigal
3093175f54 SequenceField for abstract classes now have a proper name 2015-06-12 11:03:52 +02:00
Matthieu Rigal
dd05c4d34a Merge pull request #1024 from touilleMan/issue-1017
Fix #1017 (document clash between same ids but different collections)
2015-06-12 09:24:32 +02:00
Matthieu Rigal
57e3a40321 Merge pull request #1025 from MRigal/feature/259-improve-error-detection-for-invalid-query
Improve error message for invalid query
2015-06-12 09:13:18 +02:00
Matthieu Rigal
9e70152076 Merge pull request #961 from MRigal/id-meta-foo
Fixes and tests for default 'id' field creation in Document metaclass
2015-06-12 09:13:00 +02:00
Matthieu Rigal
e1da83a8f6 Cosmetic 2015-06-12 09:12:19 +02:00
Matthieu Rigal
8108198613 corrected formatting for Python 2.6 compatibility 2015-06-11 22:48:34 +02:00
Matthieu Rigal
915849b2ce Implemented method to auto-generate non-collisioning auto_id names 2015-06-11 22:48:34 +02:00
mrigal
2e96302336 not in fix 2015-06-11 22:47:10 +02:00
mrigal
051cd744ad added another test to proove we still do not handle all cases well 2015-06-11 22:47:10 +02:00
mrigal
53fbc165ba added content of PR #688 with a test to proove it is a bit right 2015-06-11 22:47:10 +02:00
mrigal
1862bcf867 added test for abstract document without pk creation and adapted behaviour 2015-06-11 22:47:10 +02:00
Omer Katz
8909d1d144 Merge pull request #1005 from touilleMan/master
Raise error if save_condition fails #991
2015-06-11 22:25:17 +03:00
Matthieu Rigal
a2f0f20284 Improve error message for invalid query 2015-06-11 17:48:34 +02:00
Emmanuel Leblond
1951b52aa5 Fix #1017 (document clash between same ids but different collections) 2015-06-11 14:55:04 +02:00
Emmanuel Leblond
cd7a9345ec Add issue related in changelog.rst 2015-06-11 14:45:19 +02:00
Matthieu Rigal
dba4c33c81 Merge pull request #1016 from bigblind/patch-2
Solution for documentation issue #1003
2015-06-11 14:40:41 +02:00
Emmanuel Leblond
153c239c9b Replace assertRaisesRegexp by assertRaises (python2.6 compatibility) 2015-06-11 14:36:51 +02:00
Emmanuel Leblond
4034ab4182 Clean save_condition exception implementation and related tests 2015-06-11 14:30:10 +02:00
Emmanuel Leblond
9c917c3bd3 Update changelog 2015-06-11 14:30:10 +02:00
Emmanuel Leblond
cca0222e1d Update AUTHORS 2015-06-11 14:29:42 +02:00
Emmanuel Leblond
682db9b81f Add versionchanged to document save_condition 2015-06-11 14:29:42 +02:00
Emmanuel Leblond
3e000f9be1 Raise error if save_condition fails #991 2015-06-11 14:29:42 +02:00
Matthieu Rigal
548a552638 Merge pull request #994 from MRigal/fix/cls-index-at-desired-position
Added hashed index, a bit more of geo-indexes, possibility to give _cls
2015-06-11 14:20:01 +02:00
Matthieu Rigal
1d5b5b7d15 Merge pull request #1018 from MRigal/fix/517-no_dereference-not-respected-on-embedded-docs
Respect no_dereference() on embedded docs containing Ref
2015-06-11 14:16:11 +02:00
Frederik Creemers
91aa4586e2 Fixes after code review 2015-06-04 22:38:11 +02:00
Matthieu Rigal
6d3bc43ef6 Merge pull request #1000 from ProgressivePlanning/test_update_related
added passing test for updates on related models
2015-06-04 19:18:11 +02:00
Marcel van den Elst
0f63e26641 use AssertEqual instead of AssertListEqual for py2.6 compatibility 2015-06-04 15:02:32 +02:00
Breeze.kay
ab2ef69c6a improve _created status when switch collection and db 2015-06-03 18:13:54 +08:00
Matthieu Rigal
621350515e Added test was still failing and implemented solution as described in #517 2015-06-03 01:02:19 +02:00
Matthieu Rigal
03ed5c398a Merge pull request #1007 from charanpald/master
GridFS files never deleted with Document deletion
2015-06-02 23:43:57 +02:00
Frederik Creemers
65d6f8c018 Solution for documentation issue #1003
Solution for documentation issue #1003. The explanation about reverse_delete_rule was a bit mixed up.
2015-06-02 12:35:25 +02:00
Charanpal
79d0673ae6 Merge remote-tracking branch 'origin/patch-2' 2015-06-02 10:49:25 +01:00
Charanpal
cbd488e19f Merge remote-tracking branch 'origin/patch-1' 2015-06-02 10:49:15 +01:00
Charanpal Dhanjal
380d869195 Add fix to FileField deletion 2015-06-02 10:23:37 +01:00
Charanpal Dhanjal
73893f2a33 Added charanpald 2015-06-02 10:20:37 +01:00
Charanpal Dhanjal
ad81470d35 Put space after hash 2015-06-02 10:17:17 +01:00
Charanpal Dhanjal
fc140d04ef Fix comment in delete 2015-06-02 10:15:27 +01:00
Matthieu Rigal
a0257ed7e7 Updated test to use new create_index method 2015-06-02 00:14:18 +02:00
Matthieu Rigal
4769487c3b Merge pull request #1012 from elin3t/patch-1
little object name fix in the readme
2015-06-01 23:46:36 +02:00
Matthieu Rigal
29def587ff Merge pull request #1004 from brunopgalvao/patch-1
spelling change definion to definition
2015-06-01 23:27:50 +02:00
Matthieu Rigal
f35d0b2b37 Added create_index method, warnings for drop_dups and a geohaystack test 2015-06-01 23:12:43 +02:00
Matthieu Rigal
283e92d55d Added hashed index, a bit more of geo-indexes, possibility to give _cls and docs 2015-06-01 22:11:21 +02:00
Eliecer Daza
c82b26d334 little object name fix
replace little object name HtmlPost with TextPost that is the one used on the example
2015-05-31 18:28:12 -05:00
Charanpal
2753e02cda Fix for case where Document is deleted and it's files (FieldFields) in GridFS remain. 2015-05-23 14:46:56 +01:00
Bruno Pierri Galvao
fde733c205 spelling change definion to definition 2015-05-21 10:22:02 -04:00
Marcel van den Elst
f730591f2c added passing test for updates on related models
ref #570: test would fail from v0.8.5 up, but fixed in master
2015-05-20 13:01:44 +02:00
David Bordeynik
94eac1e79d Merge pull request #946 from MRigal/fix/pymongo3-connection
fixes #946
2015-05-11 15:51:51 +03:00
Matthew Ellison
9f2b6d0ec6 Merge pull request #894 from MongoEngine/topic/not-in-style-issue
Code Cleanup
- Use not in instead of not (x in y)
2015-05-08 09:06:32 -04:00
Omer Katz
7d7d0ea001 Use not in instead of not (x in y). 2015-05-08 12:50:34 +03:00
Matthieu Rigal
794101691c removed wire_concern usage and cosmetics 2015-05-07 19:34:31 +02:00
Matthew Ellison
a443144a5c Merge pull request #995 from seglberg/PR/952-squash
Unit Test - Unique Multikey Index
2015-05-07 13:12:11 -04:00
Eli Boyarski
73f0867061 Unit Test - Unique Multikey Index
Adds a unit test to exhibit the behavior of MongoDB when using a unique
multikey index. MongoDB treats any missing unique multikey index value
as NULL, thus throwing a Duplicate Key Error when saving multiple
missing values.

See #930 for more information.

- Closes #930
- Closes #952
2015-05-07 11:16:47 -04:00
Matthieu Rigal
f97db93212 corrected test for MongoDB 2.X 2015-05-07 12:48:25 +02:00
Matthieu Rigal
d36708933c author and changelog 2015-05-07 12:48:25 +02:00
Matthieu Rigal
14f82ea0a9 enabled PYMONGO 3 and DEV for travis 2015-05-07 12:47:31 +02:00
Matthieu Rigal
c41dd6495d corrected connection test for PyMongo3+ 2015-05-07 12:47:31 +02:00
Matthieu Rigal
1005c99e9c corrected index test for MongoDB 3+ 2015-05-07 12:47:31 +02:00
Matthieu Rigal
f4478fc762 removed sleep thanks to @seglberg suggestion 2015-05-07 12:47:31 +02:00
mrigal
c5ed308ea5 comments update after having tested PyMongo 3.0.1 2015-05-07 12:47:31 +02:00
mrigal
3ab5ba6149 added explicit warnings when calling methods having no effect anymore with PyMongo3+ 2015-05-07 12:47:30 +02:00
mrigal
9b2fde962c added try except to geo test to catch random mongo internal errors 2015-05-07 12:47:30 +02:00
mrigal
571a7dc42d Fix last issue with binary field as primary key and skipped new test 2015-05-07 12:47:30 +02:00
mrigal
3421fffa9b reactivated unnecessarily skipped test 2015-05-07 12:47:30 +02:00
mrigal
c25619fd63 improved deprecation documentation and added warning when using snapshot with PyMongo3 2015-05-07 12:47:30 +02:00
mrigal
76adb13a64 Minor text and comments enhancements 2015-05-07 12:47:30 +02:00
mrigal
33b1eed361 corrected logical test for not Pymongo3 versions 2015-05-07 12:47:30 +02:00
mrigal
c44891a1a8 changed unittest to call for compatibility with Python 2.6 2015-05-07 12:47:30 +02:00
mrigal
f31f52ff1c corrected test condition, depending on mongodb and not pymongo version 2015-05-07 12:47:30 +02:00
mrigal
6ad9a56bd9 corrected bad import preventing to run on PyMongo 2.X versions 2015-05-07 12:47:30 +02:00
mrigal
a5c2fc4f9d reinforced test for BinaryField being a Primary Key 2015-05-07 12:47:30 +02:00
mrigal
0a65006bb4 replaced find_and_modify by PyMongo3 equivalents 2015-05-07 12:47:30 +02:00
mrigal
3db896c4e2 work-around for pymongo 3 bug 2015-05-07 12:47:30 +02:00
mrigal
e80322021a corrected and enhanced geo_index test 2015-05-07 12:47:29 +02:00
mrigal
48316ba60d implemented global IS_PYMONGO_3 2015-05-07 12:47:29 +02:00
mrigal
c0f1493473 fix revert situated at the wrong location 2015-05-07 12:47:29 +02:00
mrigal
ccbd128fa2 first adaptations after comments and find-outs 2015-05-07 12:47:29 +02:00
mrigal
46817caa68 various unused imports removed (I am allergic) 2015-05-07 12:47:29 +02:00
mrigal
775c8624d4 change to try to address issues due to new save() behaviour, not satisfying, some tests are still failing 2015-05-07 12:47:29 +02:00
mrigal
36eedc987c adapted index test to new explain output in pymongo3 and added comment to a possible pymongo3 bug 2015-05-07 12:47:29 +02:00
mrigal
3b8f31c888 fix problems with cursor arguments 2015-05-07 12:47:29 +02:00
mrigal
a34fa74eaa fix connection problems with pymongo3 and added tests 2015-05-07 12:47:29 +02:00
Matthieu Rigal
d6b2d8dcb5 Merge pull request #982 from elephanter/operator_name_in_field_name
Added __ support to escape field name in fields lookup keywords that match operators names
2015-05-07 11:47:12 +02:00
Eremeev Danil
aab0599280 test moved to another file, cosmetical fixes 2015-05-07 10:55:35 +05:00
Eremeev Danil
dfa8eaf24e Added changeset, updated documentation and tests, changed test condition 2015-05-07 10:55:35 +05:00
Eremeev Danil
63d55cb797 solution for #949 2015-05-07 10:54:16 +05:00
Matthieu Rigal
c642eee0d2 Merge pull request #992 from touilleMan/master
Add primary_key notice in defining-documents doc according to issue #985
2015-05-06 23:13:20 +02:00
Emmanuel Leblond
5f33d298d7 Fix typo in guide/defining-documents.rst 2015-05-06 21:32:36 +02:00
Emmanuel Leblond
fc39fd7519 Update defining-documents.rst
Add primary_key notice according to issue #985
2015-05-06 18:30:49 +02:00
Matthew Ellison
7f442f7485 Merge pull request #978 2015-05-06 09:41:00 -04:00
rma4ok
0ee3203a5a [docs] Adding SortedListField fix to changelog 2015-05-06 09:40:36 -04:00
rma4ok
43a5df8780 [dist] Adding rma4ok to contributors 2015-05-06 09:40:09 -04:00
rma4ok
0949df014b [fix] SortedListField: update whole list if order is changed 2015-05-06 09:40:08 -04:00
Matthew Ellison
01f4dd8f97 Merge pull request #989 2015-05-06 09:38:26 -04:00
Matthew Ellison
8b7599f5d9 Merge pull request #900 2015-05-06 09:37:56 -04:00
Stefan Wojcik
9bdc320cf8 dont send a "cls" option to ensureIndex (related to https://jira.mongodb.org/browse/SERVER-769) 2015-05-06 11:25:45 +02:00
Matthew Ellison
d9c8285806 Merge pull request #988 from olivierlefloch/master
Fix code formatting in upgrade doc
2015-05-05 09:08:43 -04:00
Gregor Kališnik
4b8344082f Testing if we can query embedded document's field inside MapField. Part of #912, which is fixed in 0.9. 2015-05-05 12:49:45 +02:00
Olivier Le Floch
e5cf76b460 Match other code blocks
This fixes rendering on the documentation website:
http://docs.mongoengine.org/upgrade.html
2015-05-04 15:02:01 -07:00
David Bordeynik
422ca87a12 Merge pull request #979 from DavidBord/fix-453
fix-#453: Queryset update doesn't go through field validation
2015-05-02 20:26:56 +03:00
David Bordeynik
a512ccca28 fix-#453: Queryset update doesn't go through field validation 2015-05-02 15:15:02 +03:00
Matthew Ellison
ba215be97c Merge pull request #984 from asmacdo/asmacdo-patch-1
Update docs with NotUniqueError
2015-05-01 07:22:50 -04:00
Austin
ca16050681 Update docs with NotUniqueError
This was changed in https://github.com/MongoEngine/mongoengine/pull/62. It is present in versions > 0.7 http://docs.mongoengine.org/changelog.html#changes-in-0-7-0. I can reopen against an older branch if preferred.
2015-04-30 16:02:50 -04:00
Matthew Ellison
06e4ed1bb4 Merge pull request #976 from seglberg/bugfix/#954-ref-field-subclass
Reflect Inheritance in Field's 'owner_document'
2015-04-30 09:22:15 -04:00
Matthieu Rigal
d4a8ae5743 Merge pull request #932 from elephanter/nested_map_fields_delta_fix
fix wrong _delta results on nested MapFields #931
2015-04-30 10:55:12 +02:00
Eremeev Danil
a4f2f811d3 removed forgotten print 2015-04-30 09:33:19 +05:00
Eremeev Danil
ebaba95eb3 fixed same bug for nested List inside MapField, little code refactoring, added test for nested list and nested reference fields 2015-04-30 09:33:19 +05:00
Eremeev Danil
31f7769199 percent string formatting changed to format method 2015-04-30 09:33:19 +05:00
elephant
7726be94be fixed wrong _delta results on nested MapFields #931 2015-04-30 09:33:18 +05:00
Matthew Ellison
f2cbcea6d7 Unit Tests for #954 Fail on Exception, not Error 2015-04-29 14:26:05 -04:00
Matthew Ellison
5d6a28954b Reflect Inheritance in Field's 'owner_document'
The 'owner_document' property of a Field now reflects the parent field
which first contained the Field when a Document in inherited.

Fixes #954
Closes #955
2015-04-29 14:23:57 -04:00
Sridhar Sundarraman
319f1deceb Unit Test to Demonstrate #954 2015-04-29 14:23:57 -04:00
Omer Katz
3f14958741 Merge pull request #957 from noirbizarre/metastrict
Allow to loads undeclared field with meta attribute (fix #934)
2015-04-29 19:32:26 +03:00
Matthew Ellison
42ba4a5c56 Merge pull request #960 from noirbizarre/tox
Tox support for cross-versions testing
2015-04-28 20:29:23 -04:00
Axel Haustant
c804c395ed Post rebase and Django removal tuning (and prepare for PyMongo 3) 2015-04-28 21:36:07 +02:00
Axel Haustant
58c8cf1a3a Split dependencies installation and test running avoing travis_retry on tests 2015-04-28 20:20:21 +02:00
Axel Haustant
76ea8c86b7 Use travis_retry on tox execution 2015-04-28 20:20:21 +02:00
Axel Haustant
050378fa72 Little README tuning 2015-04-28 20:20:21 +02:00
Axel Haustant
29d858d58c Removed the deprecated py3where parameter 2015-04-28 20:20:21 +02:00
Axel Haustant
dc45920afb Added missing coveralls install 2015-04-28 20:19:56 +02:00
Axel Haustant
15fcb57e2f Fix typo in travis config 2015-04-28 20:19:56 +02:00
Axel Haustant
91ee85152c Tests/Tox/TravisCI improvements 2015-04-28 20:19:56 +02:00
mrigal
aa7bf7af1e adapted setup.cfg to use nosetests standard and allow usage of --tests argument, documenting it in the readme 2015-04-28 20:08:54 +02:00
Axel Haustant
02c1ba39ad Added Django 1.8 to tox 2015-04-28 18:54:10 +02:00
Axel Haustant
8e8d9426df Document about tox testing 2015-04-28 18:54:10 +02:00
Axel Haustant
57f301815d Added a tox.ini file allowing to test with different versions 2015-04-28 18:54:10 +02:00
Matthew Ellison
dfc9dc713c Merge pull request #973 from seglberg/feature/#958-django-split
Removed Django Support from MongoEngine

+1 @thedrow @MRigal @DavidBord @rozza
2015-04-28 10:37:22 -04:00
Matthew Ellison
1a0cad7f5f Updated Django Support Documentation
Added "Call to Arms" for new Django Extension.
2015-04-28 10:02:39 -04:00
Omer Katz
3df436f0d8 Merge pull request #974 from eli-b/spelling
Spelling
2015-04-26 20:15:46 +03:00
Eli Boyarski
d737fca295 Spelling 2015-04-26 17:23:13 +03:00
Omer Katz
da5a3532d7 Merge pull request #967 from RussellLuo/master
Override `authentication_source` by "authSource" in URI
2015-04-25 17:08:14 +03:00
RussellLuo
27111e7b29 Update changelog for added authSource support 2015-04-25 20:57:26 +08:00
RussellLuo
b847bc0aba Make test_connect_uri_with_authsource to focus on the key point 2015-04-25 10:22:24 +08:00
RussellLuo
6eb0bc50e2 Add a test for "authSource" feature 2015-04-25 08:01:24 +08:00
Matthew Ellison
7530f03bf6 Removed Django Support from MongoEngine
Django support has now been split out of MongoEngine and will be
revisted as a new but separate module.

Closes #958
2015-04-24 13:50:26 -04:00
RussellLuo
24a9633edc Override authentication_source by "authSource" in URI 2015-04-20 16:05:34 +08:00
Omer Katz
7e1a5ce445 Merge pull request #911 from jimmyshen/complexdatetime-microsecond-bug
Fixed microsecond-level ordering/filtering bug with ComplexDateTimeField
2015-04-19 12:40:01 +03:00
Jimmy Shen
2ffdbc7fc0 fixed microsecond-level ordering/filtering bug with ComplexDateTimeField as well as unused separator option 2015-04-19 03:26:14 -04:00
Axel Haustant
52c7b68cc3 Restore Py26 compatibility on assertRaises 2015-04-13 22:28:58 +02:00
Axel Haustant
ddbcc8e84b Ensure meta.strict does not bypass constructor check 2015-04-13 18:48:42 +02:00
Axel Haustant
2bfb195ad6 Document FieldDoesNotExist and meta.strict 2015-04-13 18:07:48 +02:00
Axel Haustant
cd2d9517a0 Added 'strict' meta parameter 2015-04-13 17:49:08 +02:00
Omer Katz
19dc312128 Merge pull request #927 from Catstyle/feature/mark_as_changed_issue
mark_as_changed issue
2015-04-10 12:06:00 +03:00
Catstyle
175659628d fix mark_as_changed: handle higher/lower level changed fields correctly to avoid conflict update error 2015-04-10 11:31:31 +08:00
Omer Katz
8fea2b09be Merge pull request #925 from elephanter/fix__get_changed_fields
_get_changed_fields fix for embedded documents with id field.
2015-04-09 21:13:47 +03:00
Eremeev Danil
f77f45b70c _get_changed_fields fix for embedded documents with id field.
removed commented out piece of code

added author and record to changelog
2015-04-09 12:36:48 +05:00
Omer Katz
103a287f11 Merge pull request #941 from MongoEngine/yograterol-patch-1
Remove support to PIL.
2015-04-09 10:19:48 +03:00
Omer Katz
d600ade40c Merge pull request #947 from YoApp/optimization_issue888
Major dereferencing optimizations and fix for de-pickling outdated documents [migrated 921]
2015-04-09 10:06:12 +03:00
Michael Chase
a6a7cba121 Current class fields when unpickling. Fixes #888
Optimize dereferencing map by using sets.
2015-04-08 19:40:43 -07:00
Yohan Graterol
7fff635a3f Remove support to PIL. 2015-04-08 11:29:55 -05:00
mrigal
7a749b88c7 added new test like defined in issue #712 and changed ObjectIdField to_python() method to use a try except similar to other Field classes 2015-04-08 15:38:49 +02:00
Ross Lawley
1ce6a7f4be Update upgrade.rst
Add note to the upgrade guide about 0.8.7 bad package issue.  #919 #929
2015-04-08 10:49:23 +01:00
David Bordeynik
a092910fdd Merge pull request #920 from DavidBord/fix-914
ListField of embedded docs doesn't set the _instance attribute when iterating over it
2015-04-02 15:31:08 +03:00
David Bordeynik
bb77838b3e fix-#914: ListField of embedded docs doesn't set the _instance attribute when iterating over it 2015-04-02 08:59:24 +03:00
David Bordeynik
1001f1bd36 Merge pull request #917 from DavidBord/fix-595
Fix #595: Support += and *= for ListField
2015-04-02 01:08:30 +03:00
David Bordeynik
de0e5583a5 Fix #595: Support += and *= for ListField 2015-03-29 09:28:26 +03:00
Omer Katz
cbd2a44350 Changed an invalid classifier to a valid one. 2015-03-28 11:34:22 +03:00
Ross Lawley
c888e461ba Updated travis.yml to build release tags 2015-03-24 17:09:26 +00:00
David Bordeynik
d135522087 Adding #714 to changelog and AUTHORS 2015-03-23 15:46:12 +02:00
J. Fernando Sánchez
ce2b148dd2 Fixes #714 2015-03-23 12:21:21 +02:00
J. Fernando Sánchez
2d075c4dd6 Added test for new_file after saved as none. #714 2015-03-23 12:21:21 +02:00
Omer Katz
bcd1841f71 Enabled to release mongoengine to PyPi when tagging. 2015-03-23 11:52:24 +02:00
Omer Katz
029cf4ad1f Merge pull request #908 from rozza/travis
Updated travis build
2015-03-18 18:52:38 +02:00
Omer Katz
ed7fc86d69 Merge pull request #901 from elasticsales/fix-test-with-profiling
Fix the unit tests for mongodb w/ profiling enabled
2015-03-18 16:38:31 +02:00
Ross Lawley
82a9e43b6f Updated travis build
Fixed pymongo versions to 2.7.2 and 2.8
The dev build for pymongo is the master branch which points to the new 3.0 driver.
Support for 3.0 will need to be added separately in the future as its a major version change and
will contain some backwards breaking changes
2015-03-18 13:55:16 +00:00
Stefan Wojcik
9ae2c731ed dont drop any system collections 2015-03-03 14:30:09 -08:00
Omer Katz
7d1ba466b4 Merge pull request #893 from MongoEngine/topic/pop-default-argument
Use dict.pop() default argument instead of checking if the key exists ourselves
2015-03-01 17:34:10 +02:00
Omer Katz
4f1d8678ea Merge pull request #896 from MongoEngine/topic/remove-mutable-arguments
Use None instead of mutable arguments
2015-03-01 15:04:27 +02:00
Omer Katz
4bd72ebc63 Use None instead of mutable arguments. 2015-02-27 11:32:06 +02:00
Omer Katz
e5986e0ae2 Use dict.pop() default argument instead of checking if the key exists ourselves. 2015-02-27 11:18:09 +02:00
David Bordeynik
fae39e4bc9 Merge pull request #886 from rutsky/patch-1
fix typo: "a the structure" -> "the structure"
2015-02-26 11:38:41 +02:00
David Bordeynik
dbe8357dd5 Merge pull request #883 from jerrysxu/patch-1
Update fields.py
2015-02-26 11:38:33 +02:00
David Bordeynik
3234f0bdd7 Merge pull request #887 from rutsky/patch-2
fix reference format: "attr:`auto_create_index`"
2015-02-20 22:18:39 +02:00
David Bordeynik
47a4d58009 Merge pull request #826 from seglberg/mmelliso/fix#503
EmbeddedDocumentListField (Resolves #503)
2015-02-20 21:53:18 +02:00
Vladimir Rutsky
4ae60da58d fix reference format: "attr:auto_create_index" 2015-02-20 19:59:36 +03:00
Vladimir Rutsky
47f995bda3 fix typo: "a the structure" -> "the structure" 2015-02-20 19:41:20 +03:00
Matthew Ellison
42721628eb Added EmbeddedDocumentListField Implementation
- Added new field type: EmbeddedDocumentListField.
- Provides additional query ability for lists of embedded documents.
- Closes MongoEngine/mongoengine#503.
2015-02-20 11:18:40 -05:00
David Bordeynik
f42ab957d4 Merge pull request #885 from DavidBord/fix-864
Fix #864: ComplexDateTimeField should fall back to None when null=True
2015-02-19 11:46:03 +02:00
Jimmy Shen
ce9d0d7e82 Fix #864: ComplexDateTimeField should fall back to None when null=True 2015-02-19 09:47:38 +02:00
David Bordeynik
baf79dda21 Merge pull request #881 from DavidBord/fix-863
Fix #863: Request Support for $min, $max Field update operators
2015-02-18 11:13:25 +02:00
Jerry Xu
b71a9bc097 Update fields.py
Type name should be "MultiPolygon".
2015-02-17 13:22:02 -08:00
David Bordeynik
129632cd6b Fix #863: Request Support for $min, $max Field update operators 2015-02-17 21:48:25 +02:00
David Bordeynik
aca8899c4d Merge pull request #879 from DavidBord/fix-866
Fix #866:  does not follow
2015-02-16 15:49:06 +02:00
David Bordeynik
5c3d91e65e Fix #866: does not follow 2015-02-16 12:25:37 +02:00
David Bordeynik
0205d827f1 Merge pull request #876 from DavidBord/fix-766
Fix #766: Add support for  operator
2015-02-15 22:05:24 +02:00
David Bordeynik
225c31d583 Fix #766: Add support for operator 2015-02-15 15:05:07 +02:00
David Bordeynik
b18d87ddba Merge pull request #878 from DavidBord/fix-877
Fix #877: Fix tests for pymongo 2.8+
2015-02-15 15:00:35 +02:00
David Bordeynik
25298c72bb Fix #877: Fix tests for pymongo 2.8+ 2015-02-15 10:02:22 +02:00
David Bordeynik
3df3d27533 Merge pull request #873 from DavidBord/fix-872
Fix #872: No module named 'django.utils.importlib' (Django dev)
2015-02-15 09:31:38 +02:00
David Bordeynik
cbb0b57018 Fix #872: No module named 'django.utils.importlib' (Django dev) 2015-02-15 00:10:00 +02:00
David Bordeynik
65f205bca8 Merge pull request #848 from seglberg/choice-subclasses
Field Choices Now Accept Subclasses of Documents
2015-02-06 09:32:47 +02:00
Yohan Graterol
1cc7f80109 Merge pull request #845 from aeroeng/andQ
mongo $and list should not contain list elements in order to avoid this ...
2015-01-22 20:13:53 -05:00
Matthew Ellison
213a0a18a5 Updated Unit Tests for Field Choices of Documents
- Added Unit Test with Invalid EmbeddedDocument Choice.
- Updated Broken Link in Author's File
2015-01-12 10:11:42 -05:00
Matthew Ellison
1a24d599b3 Field Choices Now Accept Subclasses of Documents
- Fields containing 'choices' of which a choice is an
  EmbeddedDocument or Document will now accept subclasses of that
  choice.
2015-01-11 20:54:59 -05:00
aeroeng
d80be60e2b mongo $and list should not contain list elements in order to avoid this error:
$and/$or elements must be objects
2015-01-06 14:49:29 -05:00
Yohan Graterol
0ffe79d76c Merge pull request #823 from mmelliso/mmelliso/fix#812
Ensure Indexes before Each Save (Resolves #812)
2014-12-05 11:01:02 -05:00
Matthew Ellison
db36d0a375 Ensure Indexes before Each Save
- Rely on caching within the PyMongo driver to provide lightweight calls
  while indices are cached.
- Closes MongoEngine/mongoengine#812.
2014-12-04 08:45:15 -05:00
Omer Katz
ff659a0be3 Merge pull request #815 from MRigal/master
fixed bug for queryset.distinct to work also on embedded documents, not ...
2014-12-04 14:21:04 +02:00
Wilson Júnior
8485b12102 Merge pull request #821 from 3lnc/Document.switch_docstring_fix
Fixes #811. Fixes reflinks
2014-12-04 09:28:11 -02:00
Wilson Júnior
d889cc3c5a Merge pull request #825 from idlead/fix/reverse_delete_rules_on_abstract_documents
Fix crash when applying deletion rules
2014-12-04 09:23:59 -02:00
Antoine Français
7bb65fca4e Fix crash when applying deletion rules
When deleting a document references by other, if that refence is
defined on an abstract document, the operation fails, because it tries
to apply deletion on the abstract class which doesn't have a QuerySet.

Fix is simply to ignore document classes which are defined abstract when
applying rules on all classes referencing the document.
2014-12-04 11:34:23 +01:00
mrigal
8aaa5951ca fixed order of list for the test to pass 2014-12-03 13:13:07 +01:00
Matthieu Rigal
d58f3b7520 Merge pull request #1 from thedrow/patch-1
Added a test that verifies distinct operations on nested embedded docume...
2014-12-03 13:10:27 +01:00
Omer Katz
e5a636a159 Added a test that verifies distinct operations on nested embedded documents. 2014-12-03 11:09:05 +02:00
Slam
51f314e907 Doc fixes, thanks @3Inc
Author:    Slam <3lnc.slam@gmail.com>
Date:      Fri Nov 28 13:10:38 2014 +0200
2014-12-02 00:37:06 -02:00
mrigal
531fa30b69 added test for capacity to get distinct on subdocument of a list 2014-12-01 18:20:29 +01:00
Wilson Júnior
2b3bb81fae Refactoring: Simple is better than complex
Signed-off-by: Wilson Júnior <wilsonpjunior@gmail.com>
2014-11-29 23:48:58 -02:00
Rik
80f80cd31f fixed more tests that were using undefined model fields 2014-11-29 23:20:31 -02:00
Rik
79705fbf11 moved initialization of _created before FieldDoesNotExist check
Because otherwise we'll get a FieldDoesNotExist error on the field
_created.
2014-11-29 23:20:30 -02:00
Rik
191a4e569e added ints in string.format() for 2.6 compability 2014-11-29 23:20:30 -02:00
Rik
1cac35be03 using python 2.6 compatible way of assertRaises 2014-11-29 23:20:30 -02:00
Rik
6d48100f44 add test if FieldDoesNotExist is raised
When trying to set an undefined field.
2014-11-29 23:20:30 -02:00
Rik
4627af3e90 add FieldDoesNotExist exception to __all__
So it will be available when you do:
    from mongoengine import *
2014-11-29 23:20:30 -02:00
Rik
913952ffe1 remove unittest test_no_overwritting_no_data_loss
Now that fields need to be defined explicitly, it's not possible to have
another property with the same name on a model.
https://github.com/MongoEngine/mongoengine/pull/457#issuecomment-47513105
2014-11-29 23:20:30 -02:00
Rik
67bf6afc89 fixed tests that were using undefined model fields 2014-11-29 23:20:30 -02:00
Rik
06064decd2 check for dynamic document, exclude id pk and _cls 2014-11-29 23:20:30 -02:00
Rik
4cca9f17df Check if undefined fields are supplied on document
If an undefined field is supplied to a document instance, a
`FieldDoesNotExist` Exception will be raised.
2014-11-29 23:20:30 -02:00
Wilson Júnior
74a89223c0 replaced text_score attribute to get_text_score method
Signed-off-by: Wilson Júnior <wilsonpjunior@gmail.com>
2014-11-29 23:09:26 -02:00
Slam
2954017836 Fixes #811. Fixes reflinks 2014-11-30 00:23:40 +02:00
mrigal
a03262fc01 implemented ability to return instances and not simple dicts for distinct on subdocuments 2014-11-28 16:23:23 +01:00
mrigal
d65ce6fc2c fixed bug for queryset.distinct to work also on embedded documents, not just on lists of embedded documents 2014-11-28 13:54:33 +01:00
Yohan Graterol
d27e1eee25 Merge pull request #806 from mmelliso/mmelliso/indexing
Generate Unique Indices for Lists of EmbeddedDocs
2014-11-25 02:37:53 -05:00
Yohan Graterol
b1f00bb708 Merge pull request #810 from 3lnc/Doc_fixes
Minor typos fixes in docs
2014-11-25 02:36:59 -05:00
Slam
e0f1e79e6a Minor typos fixes in docs 2014-11-24 16:57:43 +02:00
Matthew Ellison
d70b7d41e8 Update to Changelog to include Fix for #358 2014-11-21 07:29:50 -05:00
Matthew Ellison
43af9f3fad Update Tests for EmbeddedDocument Unique Indicies 2014-11-20 11:20:04 -05:00
Matthew Ellison
bc53dd6830 Generate Unique Indices for Lists of EmbeddedDocs
- Unique indices are now created in the database for EmbeddedDocument
  fields when the EmbeddedDocument is in a ListField
- Closes Issue #358
2014-11-19 22:37:27 -05:00
Yohan Graterol
263616ef01 Merge pull request #804 from CestDiego/patch-1
Big typo fix for allow_inheritance page
2014-11-19 21:20:02 -05:00
Diego Berrocal
285da0542e Update AUTHORS with @cestdiego 2014-11-19 09:39:51 -05:00
Diego Berrocal
17f7e2f892 Big typo fix for allow_inheritance page 2014-11-19 02:49:08 -05:00
Yohan Graterol
a29d8f1d68 Merge pull request #803 from DavidBord/fix-515
fix-#515: sparse fields
2014-11-16 22:04:24 -05:00
David Bordeynik
8965172603 fix-#515: sparse fields 2014-11-14 21:45:46 +02:00
David Bordeynik
03c2967337 Update changelog & authors - #801 2014-11-13 20:47:31 +02:00
David Bordeynik
5b154a0da4 Merge pull request #801 from mikhailmoshnogorsky/patch-1
write_concern not in params of Collection#remove
2014-11-13 16:19:56 +02:00
mikhailmoshnogorsky
b2c8c326d7 write_concern not in params of Collection#remove 2014-11-12 17:00:07 -05:00
Yohan Graterol
96aedaa91f Install Django dev from repo with pip 2014-11-12 12:06:20 -05:00
Omer Katz
a22ad1ec32 Exclude Django 1,7 and Python 2.6 since Django 1.7 doesn't support 2.6. 2014-11-11 09:09:21 +02:00
Omer Katz
a4244defb5 Fixed build matrix. 2014-11-10 09:41:29 +02:00
Omer Katz
57328e55f3 Bumped django versions and added 1.7.1. 2014-11-10 09:37:35 +02:00
Yohan Graterol
87c32aeb40 Merge branch 'aericson-better_basedocument_eq' 2014-11-09 21:32:58 -05:00
Yohan Graterol
2e01e0c30e Added merge to changelog.rst 2014-11-09 21:32:50 -05:00
Yohan Graterol
a12b2de74a Fix merge MongoEngine/mongoengine#799 2014-11-09 21:31:56 -05:00
Yohan Graterol
6b01d8f99b Merge branch 'DavidBord-fix-734' 2014-11-09 21:28:12 -05:00
Yohan Graterol
eac4f6062e Fix merge in docs/changelog.rst 2014-11-09 21:28:03 -05:00
Yohan Graterol
5583cf0a5f PEP8 compliance tests/document/instance.py 2014-11-09 21:27:23 -05:00
Yohan Graterol
57d772fa23 Fix merge in tests/document/instance.py 2014-11-09 21:19:05 -05:00
Yohan Graterol
1bdc3988a9 Merge pull request #798 from DavidBord/fix-771
fix-#771: OperationError: Shard Keys are immutable. Tried to update id e...
2014-11-09 20:51:06 -05:00
André Ericson
2af55baa9a Better BaseDocument equality check when not saved
When 2 instances of a Document had id = None they would be considered
equal unless an __eq__ were implemented.

We now return False for such case. It now behaves more similar to
Django's ORM.
2014-11-09 16:19:15 -03:00
David Bordeynik
0452eec11d fix-#771: OperationError: Shard Keys are immutable. Tried to update id even though the document is not yet saved 2014-11-09 19:23:49 +02:00
Wilson Júnior
c4f7db6c04 Merge pull request #796 from aericson/fix_dynamic_document_reload
Fix KeyError on reload() from a DynamicDocument
2014-11-08 23:52:37 -02:00
André Ericson
3569529a84 Fix KeyError on reload() from a DynamicDocument
If the document is in memory and a field is deleted from the db,
calling reload() would raise a KeyError.
2014-11-08 19:11:51 -03:00
Yohan Graterol
70942ac0f6 Update changelog.rst 2014-11-07 11:03:49 -05:00
Yohan Graterol
dc02e39918 Merge branch 'a4tunado-753' 2014-11-07 11:03:02 -05:00
Yohan Graterol
73d6bc35ec Fix merge with AUTHORS 2014-11-07 11:02:48 -05:00
Yohan Graterol
b1d558d700 Merge branch 'DavidBord-fix-787' 2014-11-07 10:55:56 -05:00
Yohan Graterol
897480265f fix PR #787 2014-11-07 10:55:46 -05:00
Yohan Graterol
73724f5a33 Merge pull request #793 from DavidBord/fix-759
fix-#759: with_limit_and_skip for count should default like in pymongo
2014-11-07 10:53:04 -05:00
DavidBord
bdbd495a9e fix-#734: set attribute to None does not work (at least for fields with default values). Solves #735 as well 2014-11-07 15:11:21 +02:00
DavidBord
1fcf009804 fix-#787: Fix storing value of precision attribute in DecimalField 2014-11-07 15:03:11 +02:00
DavidBord
914c5752a5 fix-#759: with_limit_and_skip for count should default like in pymongo 2014-11-07 09:21:17 +02:00
DavidBord
201b12a886 Merge pull request #774 from DavidBord/fix-744
fix-#744: Querying by a field defined in a subclass raises InvalidQueryE...
2014-11-06 08:10:31 +02:00
DavidBord
c5f23ad93d fix-#744: Querying by a field defined in a subclass raises InvalidQueryError 2014-11-06 00:15:23 +02:00
Yohan Graterol
28d62009a7 Update changelog.rst 2014-11-05 14:56:13 -05:00
Yohan Graterol
1a5a436f82 Merge pull request #775 from claymation/in_bulk_honors_no_dereference
Make `in_bulk()` respect `no_dereference()`
2014-11-05 14:55:43 -05:00
Yohan Graterol
1275ac0569 Merge pull request #773 from KonishchevDmitry/pr-document-modify
Add Document.modify() method
2014-11-02 17:13:33 -05:00
Dmitry Konishchev
5112fb777e Mention Document.modify() in the documentation 2014-11-02 17:56:25 +03:00
Dmitry Konishchev
f571a944c9 Add #773 to changelog 2014-11-02 17:52:44 +03:00
Dmitry Konishchev
bc9aff8c60 Merge remote-tracking branch 'upstream/master' into pr-document-modify 2014-11-02 17:24:51 +03:00
Yohan Graterol
c4c7ab7888 Merge pull request #770 from yjaaidi/patch-1
Version bump 0.8.7 => 0.9.0
2014-11-01 14:37:51 -05:00
Yohan Graterol
d9819a990c Merge pull request #772 from shuuji3/patch-1
Marked up the last line as preformatted text.
2014-11-01 14:37:44 -05:00
Yohan Graterol
aea400e26a Merge pull request #791 from czarneckid/fix-reverse-delete-rule-documentation
Fix the documentation for reverse_delete_rule.
2014-11-01 14:32:48 -05:00
David Czarnecki
eb4e7735c1 Adding myself to AUTHORS and CHANGELOG 2014-11-01 13:15:02 -04:00
David Czarnecki
4b498ae8cd Fix the documentation for reverse_delete_rule. 2014-10-31 11:40:20 -04:00
DavidBord
158e2a4ca9 Merge pull request #779 from DavidBord/fix-778
fix-#778: Add Support For MongoDB 2.6.X's maxTimeMS
2014-10-29 17:54:40 +02:00
DavidBord
b011d48d82 fix-#778: Add Support For MongoDB 2.6.X's maxTimeMS 2014-10-29 15:40:29 +02:00
DavidBord
8ac3e725f8 Merge pull request #790 from DavidBord/fix-789
fix-#789: abstract shouldn't be inherited in EmbeddedDocument
2014-10-29 13:39:56 +02:00
DavidBord
9a4aef0358 fix-#789: abstract shouldn't be inherited in EmbeddedDocument 2014-10-29 13:36:42 +02:00
Clay McClure
7d3146234a Make in_bulk() respect no_dereference() 2014-10-01 15:59:13 -04:00
Dmitry Konishchev
5d2ca6493d Drop unnecessary id=ObjectId() in document creation 2014-10-01 12:38:41 +04:00
Dmitry Konishchev
4752f9aa37 Add Document.modify() method 2014-09-30 15:30:01 +04:00
Shuuji TAKAHASHI
025d3a03d6 Marked up the last line as preformatted text. 2014-09-30 02:31:48 +09:00
yjaaidi
aec06183e7 Version bump 0.8.7 => 0.9.0 2014-09-28 10:09:36 +02:00
Yohan Graterol
aa28abd517 Merge pull request #750 from foxx/patch-1
MongoTestCase relies on "nose"
2014-09-04 10:07:50 -05:00
Vjacheslav Murashkin
7430b31697 handle None from model __str__; Fixes #753 2014-09-04 16:54:23 +04:00
Yohan Graterol
759f72169a Merge pull request #751 from noirbizarre/patch-1
noirbizarre to AUTHORS
2014-09-02 21:48:16 -05:00
Axel Haustant
1f7135be61 Added myself to AUTHORS 2014-09-03 03:15:26 +02:00
Yohan Graterol
6942f9c1cf Merge pull request #748 from bocribbz/fix-multiple-connections
Fix multiple connections aliases being rewritten
2014-09-01 20:30:18 -05:00
Bob Cribbs
d9da75d1c0 Fix multiple connections aliases being rewritten 2014-09-01 23:26:01 +03:00
Cal Leeming
7ab7372be4 MongoTestCase relies on "nose" 2014-09-01 20:40:01 +01:00
Yohan Graterol
3503c98857 Merge pull request #749 from noirbizarre/multigeo
Multi geometry fields support
2014-09-01 11:21:08 -05:00
Axel Haustant
708c3f1e2a Added new geospatial fields to th documentation 2014-08-28 19:42:09 +02:00
Axel Haustant
6f645e8619 Added MultiPoint, MultiLine and MultiPolygon fields 2014-08-28 19:36:29 +02:00
Yohan Graterol
bce7ca7ac4 Fix merge of PR #747 2014-08-27 11:14:43 -05:00
Yohan Graterol
350465c25d Change with PR #719 and close mongoengine/Issue #397 2014-08-26 10:50:07 -05:00
Yohan Graterol
5b9c70ae22 Merge pull request #719 from DavidBord/fix-397
fix-#397: Allow specifying the '_cls' as a field for indexes
2014-08-26 10:48:10 -05:00
DavidBord
9b30afeca9 fix-#397: Allow specifying the '_cls' as a field for indexes 2014-08-24 10:51:49 +03:00
DavidBord
c1b202c119 fix-#746: Stop ensure_indexes running on a secondaries unless connection is through mongos 2014-08-24 10:48:54 +03:00
Yohan Graterol
41cfe5d2ca Update changelog.rst 2014-08-22 08:24:41 -05:00
Yohan Graterol
05339e184f Merge pull request #737 from wcdolphin/master
Make 'db' argument to connection optional
2014-08-22 08:24:03 -05:00
wcdolphin
447127d956 Makes 'db' argument to connection optional. 2014-08-21 16:08:31 -07:00
Yohan Graterol
394334fbea Merge branch 'jshirley-master' 2014-08-20 11:06:09 -05:00
Yohan Graterol
9f8cd33d43 Fix conflict for merge PR #726 2014-08-20 11:05:53 -05:00
Yohan Graterol
f066e28c35 Update changelog.rst 2014-08-19 23:14:00 -05:00
Yohan Graterol
b349a449bb Merge pull request #742 from bocribbz/dictfield-atomic-update
Allow atomic update for the entire `DictField`
2014-08-19 23:13:37 -05:00
Jay Shirley
1c5898d396 Adding changelog entry. 2014-08-19 15:54:29 -07:00
Jay Shirley
6802967863 Merge remote-tracking branch 'upstream/master' 2014-08-19 15:52:58 -07:00
Bob Cribbs
0462f18680 Allow atomic update for the entire DictField 2014-08-19 23:38:36 +03:00
Yohan Graterol
af6699098f Update .travis.yml for allow failures in pypy3 2014-08-19 10:43:22 -05:00
Yohan Graterol
6b7e7dc124 Update for add the change that closes issue #733 2014-08-17 22:43:36 -05:00
Yohan Graterol
6bae4c6a66 Merge pull request #738 from DavidBord/fix-733
fix-#733: index_cls is ignored when deciding to set _cls as index prefix
2014-08-17 22:41:41 -05:00
DavidBord
46da918dbe fix-#733: index_cls is ignored when deciding to set _cls as index prefix 2014-08-17 11:19:18 +03:00
Ross Lawley
bb7e5f17b5 Update .travis.yml
Added coveralls coverage...
2014-08-12 14:52:39 +01:00
Yohan Graterol
b9d03114c2 Merge pull request #728 from MongoEngine/topic/landscape-io
Added landscape.io badge
2014-08-11 08:31:11 -05:00
Omer Katz
436b1ce176 Added PyMongo 2.7.2 to the build matrix. 2014-08-10 18:44:30 +03:00
Omer Katz
50fb5d83f1 Added landscape.io badge. 2014-08-10 18:26:18 +03:00
Wilson Júnior
fda672f806 Merge pull request #727 from DavidBord/fix-725
fix-#725: queryset delete() should return the number of deleted objects
2014-08-10 11:43:59 -03:00
Omer Katz
2bf783b04d Added PyPy3 to the build matrix. 2014-08-10 17:01:18 +03:00
DavidBord
2f72b23a0d fix-#725: queryset delete() should return the number of deleted objects 2014-08-10 14:58:39 +03:00
Jay Shirley
85336f9777 Relax the RegEx restrictions to allow the new ICAAN TLDs. 2014-08-08 09:11:05 -07:00
Yohan Graterol
174d964553 Update changelog.rst 2014-08-06 01:54:42 -05:00
Yohan Graterol
cf8677248e Merge pull request #723 from DavidBord/fix-620
Fix 620
2014-08-06 01:53:28 -05:00
DavidBord
1e6a3163af fix-#620: saving document doesn't create new fields in existing collection 2014-08-05 17:29:14 +03:00
DavidBord
e008919978 fix-#399: Not overriding default values when loading a subset of fields 2014-08-05 14:34:54 +03:00
Wilson Júnior
4814066c67 Merge pull request #709 from wpjunior/cached-reference-field
CachedReferenceField implementation
2014-08-03 21:38:06 -03:00
Wilson Junior
f17f8b48c2 small fixes for python2.6 2014-08-03 18:59:50 -04:00
Yohan Graterol
ab0aec0ac5 Merge pull request #720 from pashadia/master
Fixed typo.
2014-08-03 14:04:15 -05:00
pashadia
b49a641ba5 Fixed typo. 2014-08-03 20:44:46 +03:00
Yohan Graterol
2f50051426 Merge pull request #718 from mbalasso/patch-1
Update README.rst
2014-08-01 10:46:50 -05:00
Matteo Balasso
43cc32db40 Update README.rst 2014-08-01 16:35:53 +02:00
Wilson Júnior
b4d6f6b947 added documentation about CachedReferenceField 2014-07-30 09:32:33 -03:00
Omer Katz
71ff533623 Updated Django development version in the build matrix. 2014-07-30 02:02:21 +03:00
Wilson Júnior
e33a5bbef5 fixes for python2.6 2014-07-26 07:24:04 -03:00
Wilson Júnior
6c0112c2be refs #709, added support to disable auto_sync 2014-07-25 18:12:26 -03:00
Wilson Júnior
15bbf26b93 refs #709, fix typos 2014-07-25 08:48:24 -03:00
Wilson Júnior
87c97efce0 refs #709, added CachedReferenceField.sync_all to sync all documents on demand 2014-07-25 08:44:59 -03:00
Wilson Júnior
6c4aee1479 added CachedReferenceField restriction to use in EmbeddedDocument 2014-07-17 13:42:34 -03:00
Wilson Júnior
73549a9044 fixes for rebase branch 2014-07-17 09:41:06 -03:00
Wilson Júnior
30fdd3e184 Added initial CachedReferenceField 2014-07-16 10:32:43 -03:00
Wilson Júnior
c97eb5d63f Added retry in apt-get operations for travis 2014-07-14 16:05:49 -03:00
Wilson Júnior
5729c7d5e7 Merge branch 'master' of https://github.com/MongoEngine/mongoengine 2014-07-14 16:00:26 -03:00
Wilson Júnior
d77b13efcb Merge pull request #703 from wpjunior/aggregate-framework
Simple aggregation framework wrapper
2014-07-14 14:29:59 -03:00
Wilson Júnior
c43faca7b9 refs #703, added changelog 2014-07-13 09:55:46 -03:00
Wilson Júnior
892ddd5724 added a wrapper to aggregate in Queryset.aggregate 2014-07-12 23:18:08 -03:00
Wilson Júnior
a9de779f33 Merge pull request #701 from yograterol/master
Remove allow_failures from .travis.yml file
2014-07-08 14:21:39 -03:00
Yohan Graterol
1c2f016ba0 Remove allow_failures from .travis.yml file 2014-07-08 10:25:12 -05:00
Wilson Júnior
7b4d9140af merge #700 2014-07-08 09:19:15 -03:00
Wilson Júnior
c1fc87ff4e added entry in changelog 2014-07-08 09:16:01 -03:00
Wilson Júnior
cd5ea5d4e0 testing a travis with mongodb 2.6 2014-07-08 08:49:03 -03:00
Wilson Júnior
30c01089f5 added ordering support for text queries 2014-07-08 08:38:41 -03:00
Wilson Júnior
89825a2b21 added skip to mongodb older 2014-07-07 23:45:44 -03:00
Wilson Júnior
a743b75bb4 fixed a order in command 2014-07-07 21:02:13 -03:00
Wilson Júnior
f7ebf8dedd Added support for text search and text_score. 2014-07-07 20:24:37 -03:00
Yohan Graterol
f6220cab3b Merge pull request #697 from nleite/master
to_json not resolving db_fields #654
2014-07-07 17:39:34 -05:00
Norberto
0c5e1c4138 adding myself to authors 2014-07-07 22:07:01 +02:00
Norberto
03fe431f1a merge with origin done 2014-07-07 21:59:42 +02:00
Norberto
a8e4554fec Update change log on #697 merge 2014-07-07 19:27:39 +02:00
Norberto
e81b09b9aa adding capability to extract json (to_json) using the field names and instead of the defined db_names, if those are set 2014-07-06 20:49:19 +02:00
Yohan Graterol
c6e846e0ae Merge pull request #696 from lexqt/fix_django_17_compat
Fix tests for django 1.7
2014-07-06 12:43:12 -05:00
Aleksey Porfirov
03dcfb5c4b Update changelog 2014-07-06 12:27:34 +04:00
Aleksey Porfirov
3e54da03e2 Fix MongoTestCase and add test for it 2014-07-05 21:35:31 +04:00
Aleksey Porfirov
c4b3196917 Fix MongoTestCase and add test for it 2014-07-05 21:13:25 +04:00
Aleksey Porfirov
0d81e7933e Prevent accessing not yet configured settings in django.MongoTestCase 2014-07-05 00:06:10 +04:00
Aleksey Porfirov
b2a2735034 Update AUTHORS 2014-07-04 22:32:07 +04:00
Aleksey Porfirov
f865c5de90 Fix tests for Django 1.7 2014-07-04 22:30:29 +04:00
Yohan Graterol
4159369e8b Merge pull request #690 from claymation/select-related
Follow ReferenceFields in EmbeddedDocuments with select_related
2014-07-03 15:28:09 -05:00
Clay McClure
170693cf0b Follow ReferenceFields in EmbeddedDocuments with select_related
For the following structure:

    class Playlist(Document):
        items = ListField(EmbeddedDocumentField("PlaylistItem"))

    class PlaylistItem(EmbeddedDocument):
        song = ReferenceField("Song")

    class Song(Document):
        title = StringField()

this patch prevents the N+1 queries otherwise required to fetch all
the `Song` instances referenced by all the `PlaylistItem`s.
2014-07-03 13:14:45 -04:00
Omer Katz
4e7b5d4af8 Added @rochacbrun to the AUTHORS file. #692 2014-07-03 17:35:55 +03:00
Omer Katz
67bf789fcf Merge pull request #692 from rochacbruno/master
Updates with no operator should default to $set Fix #667
2014-07-03 09:02:12 +03:00
Omer Katz
f5cf616c2f Merge pull request #635 from SergeChmelev/master
Fix for post_init signal to receive correct state of _created flag.
2014-07-03 02:38:52 +03:00
Bruno Rocha
7975f19817 Update querying.rst 2014-07-02 18:56:42 -03:00
Serge Chmelev
017602056d Add testcase 2014-07-03 01:48:19 +04:00
Serge Chmelev
c63f43854b Fix setting of _created flag in base.Document constructor 2014-07-03 01:48:19 +04:00
Serge Chmelev
5cc71ec2ad Fix for post_init signal to receive correct state of _created flag. 2014-07-03 01:48:19 +04:00
Omer Katz
80e81f8475 Python 2.6 requires positioning by number of fragments in format.
This commit will restore Python 2.6 compatability.
2014-07-02 23:22:06 +03:00
Omer Katz
3685c8e015 Allowed failures for Django development version. 2014-07-02 22:50:58 +03:00
Bruno Rocha
99e943c365 Updates with no operator should default to $set Fix #667 2014-07-02 14:39:29 -03:00
Yohan Graterol
21818e71f5 Revert change in .travis.yml: Delete apt cache 2014-06-30 08:48:04 -05:00
Ross Lawley
bcc6d25e21 Merge branch 'master' of github.com:MongoEngine/mongoengine 2014-06-30 10:30:12 +01:00
Ross Lawley
7b885ee0d3 Fix StrictDict repr 2014-06-30 10:29:28 +01:00
Omer Katz
c10e808a4f Fixed requirements file to fit the new PyMongo>=2.7.1 requirement. 2014-06-30 11:30:51 +03:00
Omer Katz
54e9be0ed8 Merge pull request #689 from brianhelba/pymongo_2.5
Make requirement for PyMongo>=2.5 more consistent
2014-06-30 11:30:15 +03:00
Yohan Graterol
938cdf316a Added cache for apt in Travis 2014-06-30 00:14:47 -05:00
Yohan Graterol
27c33911e6 Update .travis.yml 2014-06-30 00:09:28 -05:00
Yohan Graterol
e88f8759e7 Replace before_script for before_install 2014-06-29 23:33:30 -05:00
Yohan Graterol
f2992e3165 Travis problem with before_script 2014-06-29 23:31:08 -05:00
Yohan Graterol
c71fd1ee3b Before_script fixed. 2014-06-29 23:29:10 -05:00
Yohan Graterol
fb45b19fdc Enabling textSearch for build in Travis 2014-06-29 23:26:02 -05:00
Brian Helba
c4ea8d4942 Make requirement for PyMongo>=2.5 more consistent
Commit 7aa1f47378 requires PyMongo >= v2.5.
This updates the requirements file to make this requirement explicit to
package managers.

Commit 29309dac9a removed some legacy
compatibility code that would run only with versions of PyMongo < 2.1. The
options 'is_slave' and 'slaves' for register_connection were only used in
this compatibility code, so they are removed too.
2014-06-30 00:05:34 -04:00
Yohan Graterol
646aa131ef Corrected Travis config syntax 2014-06-29 22:58:10 -05:00
Yohan Graterol
0adb40bf92 Merge pull request #684 from brianhelba/doc-spelling
Fix some minor spelling and grammar in documentation
2014-06-29 22:41:23 -05:00
Brian Helba
17d6014bf1 Fix some minor spelling and grammar in documentation 2014-06-29 23:07:28 -04:00
Yohan Graterol
ff57cd4eaf Merge pull request #680 from claymation/text-index-specs
Include preliminary support for text indexes
2014-06-27 22:18:17 -05:00
Clay McClure
74bd7c3744 Include preliminary support for text indexes
To index a text field, prefix the field name with `$`, as in `$title`.
2014-06-27 14:48:32 -04:00
Omer Katz
cfbb283f85 Added Django 1.7RC1 to the build process and excluded it from running on Python 2.6. 2014-06-27 16:48:38 +03:00
Omer Katz
74a3c4451b using() was added in 0.9. Not 0.8. 2014-06-27 16:35:26 +03:00
Ross Lawley
be3643c962 Added elemMatch operator as well - match is too obscure #653 2014-06-27 13:39:47 +01:00
Ross Lawley
f4aa546af8 Added support for progressive JPEG #486 #548 2014-06-27 12:54:32 +01:00
Ross Lawley
67b876a7f4 Merge pull request #548 from yograterol/feature-progressive-jpeg
Feature for progressive JPEG. Issue #486
2014-06-27 12:53:51 +01:00
Ross Lawley
94e177c0ef Allow strings to be used in index creation #677 2014-06-27 12:49:31 +01:00
Ross Lawley
1bd83cc9bc Merge branch 'master' into pr/675 2014-06-27 12:48:28 +01:00
Ross Lawley
ecda3f4a7d Fixed EmbeddedDoc weakref proxy issue #592 2014-06-27 12:42:45 +01:00
Ross Lawley
8f972a965d Merge branch 'master' into pr/592 2014-06-27 12:36:39 +01:00
Ross Lawley
0f051fc57c Changelog & Authors #583 2014-06-27 12:33:19 +01:00
Ross Lawley
c3f8925f46 Merge pull request #583 from Gerrrr/distinct_bug
Fixed bug in distinct method
2014-06-27 12:22:09 +01:00
Ross Lawley
5d0cab2052 Merge branch 'master' into pr/539
Conflicts:
	mongoengine/base/datastructures.py
2014-06-27 12:20:44 +01:00
Ross Lawley
4d7492f682 Changelog & Author updates #425 #507 2014-06-27 12:10:17 +01:00
Ross Lawley
fc9d99080f Merge branch 'master' into pr/507
Conflicts:
	tests/document/dynamic.py
2014-06-27 12:06:18 +01:00
Ross Lawley
47ebac0276 Add authentication_source option to register_connection #178 #464 #573 #580 #590 2014-06-27 11:59:35 +01:00
Ross Lawley
cb3fca03e9 Merge branch 'master' into pr/590
Conflicts:
	mongoengine/connection.py
2014-06-27 11:53:46 +01:00
Ross Lawley
abbbd83729 Merge pull request #433 from reachveera/master
Overridden the prepare_query_value method in SequenceField inorder to re...
2014-06-27 11:49:28 +01:00
Ross Lawley
1743ab7812 Changelog update #567 2014-06-27 11:38:06 +01:00
Ross Lawley
324e3972a6 Merge pull request #567 from tomprimozic/master
Implemented equality between Documents and DBRefs
2014-06-27 11:37:24 +01:00
Ross Lawley
1502dda2ab Fixed ReferenceField inside nested ListFields dereferencing problem #368 2014-06-27 11:33:56 +01:00
Ross Lawley
f31b2c4a79 Merge branch 'master' into pr/368 2014-06-27 11:32:19 +01:00
Ross Lawley
89b9b60e0c Geo SON tweaks 2014-06-27 11:27:10 +01:00
Ross Lawley
de9ba12779 Turn on tests 2014-06-27 11:16:23 +01:00
Ross Lawley
9cc4359c04 Added the ability to reload specific document fields #100 2014-06-27 11:10:14 +01:00
Ross Lawley
67eaf120b9 db_alias support and fixes for custom map/reduce output #586 2014-06-27 10:07:05 +01:00
Ross Lawley
b8353c4a33 Merge branch 'master' into pr/586
Conflicts:
	mongoengine/queryset/base.py
	tests/queryset/queryset.py
2014-06-27 10:06:30 +01:00
Ross Lawley
7013033ae4 Update changelog & AUTHORS #594 #589 2014-06-27 10:03:35 +01:00
Ross Lawley
cb8cd03852 Merge pull request #594 from idlead/feature/post_save_delta
post_save signal should have delta information about field changes #594 #589
2014-06-27 10:02:08 +01:00
Ross Lawley
f63fb62014 Merge branch 'master' of github.com:MongoEngine/mongoengine 2014-06-27 10:00:42 +01:00
Ross Lawley
2e4fb86b86 Don't query with $orderby for qs.get() #600 2014-06-27 10:00:16 +01:00
Stefan Wojcik
5e776a07dd allow ordering to be cleared 2014-06-27 09:58:49 +01:00
Ross Lawley
81e637e50e Merge pull request #598 from philfreo/patch-1
clarifying the 'push' atomic update docs
2014-06-27 09:38:01 +01:00
Ross Lawley
0971ad0a80 Update changelog & authors - #636 2014-06-27 09:31:01 +01:00
Ross Lawley
8267ded7ec Merge branch 'master' into pr/636 2014-06-27 09:29:19 +01:00
Ross Lawley
7f36ea55f5 Fix bulk test where behaviour changes based on mongo version 2014-06-27 09:14:56 +01:00
Ross Lawley
72a051f2d3 Update AUTHORS & Changelog #557 2014-06-27 09:12:05 +01:00
Ross Lawley
51b197888c Merge remote-tracking branch 'origin/master' 2014-06-27 09:10:40 +01:00
Ross Lawley
cd63865d31 Fix clear_changed_fields() clearing unsaved documents bug #602 2014-06-27 09:08:07 +01:00
Martyn Smith
5be5685a09 Test to illustrate failure in changed attribute tracking 2014-06-27 09:06:17 +01:00
Yohan Graterol
76b2f25d46 Merge pull request #557 from SpotOnInc/recursive_embedded_errors_fix
Fixes issue with recursive embedded document errors
2014-06-26 21:55:49 -05:00
Ross Lawley
58607d4a7f Merge pull request #609 from nicolasdespres/fix-gridfs-guide
Save is called on the document not the file field.
2014-06-26 19:56:32 +01:00
Ross Lawley
c0a5b16a7f Travis bump 2014-06-26 19:52:05 +01:00
Ross Lawley
3a0c69005b Update AUTHORS and Changelog
Refs: #664, #677, #676, #673, #674, #655, #657, #626, #625, #619, #613, #608, #511, #559
2014-06-26 19:41:40 +01:00
Ross Lawley
5c295fb9e3 Merge branch 'master' of github.com:MongoEngine/mongoengine 2014-06-26 19:25:35 +01:00
Ross Lawley
4ee212e7d5 Skip Test due to server bug in 2.6 2014-06-26 19:25:05 +01:00
Ross Lawley
70651ce994 Fix as_pymongo bug 2014-06-26 19:24:52 +01:00
Yohan Graterol
a778a91106 Merge pull request #584 from FrankSomething/consistent-inits
inherit parent Document type _auto_id_field value
2014-06-26 11:24:13 -05:00
Ross Lawley
cfc31eead3 Fixed $maxDistance location for geoJSON $near queries with MongoDB 2.6+
Closes #664
2014-06-26 17:13:35 +01:00
Ross Lawley
da0a1bbe9f Fix test_using 2014-06-26 17:13:21 +01:00
Ross Lawley
bc66fb33e9 Merge branch 'master' into pr/625 2014-06-26 16:48:12 +01:00
Ross Lawley
b1b6493755 Merge branch 'pr/676' 2014-06-26 16:46:01 +01:00
Ross Lawley
1d189f239b Merge branch 'pr/562' 2014-06-26 16:42:23 +01:00
Ross Lawley
5b90691bcc Merge branch 'master' into pr/585 2014-06-26 16:41:27 +01:00
Ross Lawley
d1d5972277 Removed support for old versions
- Removing support for Django 1.4.x, pymongo 2.5.x, pymongo 2.6.x.
- Removing support for Python < 2.6.6
2014-06-26 16:34:02 +01:00
Ross Lawley
2c07d77368 Updated changelog
Enabled connection pooling
2014-06-26 16:24:37 +01:00
Ross Lawley
642cfbf59a Merge branch 'master' into pr/672
Conflicts:
	.travis.yml
2014-06-26 16:23:32 +01:00
Ross Lawley
bb1367cfb9 Merge branch 'master' into pr/674 2014-06-26 16:22:21 +01:00
Dmitry Konishchev
11724aa555 QuerySet.modify() method to provide find_and_modify() like behaviour 2014-06-26 16:18:42 +01:00
Ross Lawley
4d374712de Merge branch '0.9'
Conflicts:
	.travis.yml
2014-06-26 16:14:34 +01:00
Ross Lawley
eb9003187d Updated changelog & authors #673 2014-06-26 16:13:01 +01:00
Ross Lawley
caba444962 Merge branch '0.9' into pr/673
Conflicts:
	.travis.yml
2014-06-26 16:11:12 +01:00
Ross Lawley
5b6c8c191f Updated .travis.yml 2014-06-26 16:06:30 +01:00
Ross Lawley
dd51589f67 Updates 2014-06-26 16:02:40 +01:00
Ross Lawley
b02a31d4b9 Updated .travis.yml 2014-06-26 14:44:44 +01:00
Omer Katz
0e7878b406 Only run 2to3 on Python 3.x. Makes sense no? 2014-06-26 12:41:26 +03:00
Omer Katz
cae91ce0c5 Convert codebase to Python 3 using 2to3 before running benchmarks. 2014-06-26 12:31:07 +03:00
Omer Katz
67a65a2aa9 Installing unittest2 on Python 2.6. 2014-06-26 11:17:57 +03:00
Yohan Graterol
364b0a7163 Merge pull request #591 from pavlov99/master
fix docstring for DictField
2014-06-25 22:52:07 -05:00
Yohan Graterol
d6419f2059 Merge pull request #613 from falcondai/master
minor change to geo-related docs
2014-06-25 10:44:36 -05:00
Yohan Graterol
6f7ad7ef91 Merge pull request #619 from polyrabbit/master
Fixed incorrectly split a query key, when it ends with "_"
2014-06-25 10:42:27 -05:00
Omer Katz
5ae588833b Allowed to switch databases for a specific query. 2014-06-25 18:22:39 +03:00
Yohan Graterol
a70dbac0e6 Merge pull request #626 from KonishchevDmitry/pr-not-unique-error-on-update
Raise NotUniqueError in Document.update() on pymongo.errors.DuplicateKeyError
2014-06-25 10:18:09 -05:00
Yohan Graterol
4d34a02afe Merge pull request #634 from jatin/patch-1
Updated Jatin's name and github name
2014-06-25 10:16:49 -05:00
Yohan Graterol
4db4f45897 Merge pull request #651 from elasticsales/server-13975-precaution
Don't use a system collection in the tests
2014-06-25 09:48:30 -05:00
Yohan Graterol
2d5280fc95 Merge pull request #655 from jonathansp/master
Avoid to open all documents from cursors in an if stmt
2014-06-25 09:45:51 -05:00
Omer Katz
b8d568761e Getting rid of xrange since it's not in Python 3 and does not affect the benchmark. 2014-06-25 17:24:52 +03:00
Omer Katz
29309dac9a Mongo clients with the same settings should be shared since they manage a connection pool.
Also, I removed old code that was supposed to support Pymongo<2.1 which we don't support anymore.
2014-06-25 16:53:24 +03:00
Omer Katz
7f7745071a Found more print statements that were not turned into function calls. 2014-06-25 15:47:54 +03:00
Omer Katz
1914032e35 Missed some of the print statements in the benchmarks script. 2014-06-25 14:20:54 +03:00
Omer Katz
f44c8f1205 Skipping a test that does not work on PyPy due to a PyPy bug/feature. 2014-06-25 13:11:32 +03:00
Omer Katz
fe2ef4e61c Made the benchmark script compatitable with Python 3 and ensured it runs on every build. 2014-06-25 11:39:08 +03:00
Omer Katz
fc3eda55c7 Added a note about optional dependencies to the README file. 2014-06-25 11:32:41 +03:00
Omer Katz
8adf1cdd02 Fast finish the build if there are failures since we have a very large build matrix and each build takes a very long time. 2014-06-25 11:18:35 +03:00
Omer Katz
adbbc656d4 Removing zlib hack since only PIL needs it. The build should pass without it. 2014-06-25 11:12:40 +03:00
Omer Katz
8e852bce02 Pillow provides a more descriptive error message, therefor the build failure. 2014-06-25 10:58:00 +03:00
Omer Katz
bb461b009f Travis build improvements.
The latest patch version of each Django minor version is used.
The build now installs existing pymongo versions.
The build now actually tests against the specified Django version.
Replaced PIL with Pillow.
Added PyPy and Python 3.4 to the build.

Rebase Log:

Installing Pillow instead of PIL for testing since it's recommended and it supports PyPy.

Excluding Django versions that do not work with Python 3.

Improved formatting of .travis.yml.

Specifying Pillow 2.0.0 and above since it's the first version that is supported in Python 3.

PIL should not be installed alongside Pillow.

Also, I installed some libraries that both PIL and Pillow depend on.

It seems I have to be explicit on all envvars in order to exclude Django 1.4 from the build matrix.

The build is now installing pymongo versions that actually exist.

openjpeg has a different name on Ubuntu 12.04.

Restoring libz hack.

Also installing all Pillow requirements just in case.

Fixed the build matrix.

Acting according to @BanzaiMan's advice in travis-ci/travis-ci/#1492.
2014-06-25 10:40:28 +03:00
Omer Katz
03559a3cc4 Added Python 3.4 to the build process. 2014-06-24 19:20:15 +03:00
Jonathan Prates
7bb2fe128a Added PR #657 2014-06-12 11:08:41 -03:00
Jonathan Prates
2312e17a8e Merge remote-tracking branch 'elasticsales/clear-default-ordering' 2014-06-12 10:28:36 -03:00
Sagiv Malihi
9835b382da added __slots__ to BaseDocument and Document
changed the _data field to static key-value mapping instead of hash table
This implements #624
2014-06-10 16:11:27 +03:00
Stefan Wojcik
1eacc6fbff clear ordering via empty order_by 2014-05-30 15:08:03 -07:00
Jonathan Prates
85187239b6 Fix tests msg 2014-05-29 15:21:24 -03:00
Jonathan Prates
819ff2a902 Renamed to has_data() 2014-05-29 14:36:30 -03:00
Jonathan Prates
c744104a18 Added test with meta 2014-05-29 10:53:20 -03:00
Jonathan Prates
c87801f0a9 Using first() from cloned queryset 2014-05-28 17:26:28 -03:00
Jonathan Prates
39735594bd Removed blank line 2014-05-28 17:15:48 -03:00
Jonathan Prates
30964f65e4 Remove orderby in if stmt 2014-05-28 17:06:15 -03:00
Jonathan Prates
ee0c7fd8bf Change for loop to self.first() 2014-05-28 13:21:00 -03:00
Jonathan Prates
dfdecef8e7 Fix py2 and py3 2014-05-28 09:40:22 -03:00
Jonathan Prates
edcdfeb057 Fix syntax error 2014-05-28 09:03:12 -03:00
Jonathan Prates
47f0de9836 Py3 fix 2014-05-28 08:36:57 -03:00
Jonathan Prates
9ba657797e Authors updated according guideline 2014-05-28 08:33:22 -03:00
Clay McClure
07442a6f84 Allow index specs to be composed from raw strings
This allows an index spec to reference arbitrary keys of a DictField:

    class MyDoc(Document):
        frobs = DictField()
        meta = {
            "indexes": ["frobs.fmep", "frobs.gorp"],
        }
2014-05-28 01:31:35 -04:00
Jonathan Prates
3faf3c84be Avoid to open all documents from cursors in an if stmt
Using a cursos in an if statement:

cursor = Collection.objects

	if cursor:
		(...)

Will open all documents, because there are not an __nonzero__ method.
This change check only one document (if present) and returns True or False.
2014-05-27 16:33:38 -03:00
Stefan Wojcik
abcacc82f3 dont use a system collection 2014-05-21 22:21:46 -07:00
Ronald van Rij
9544b7d968 Fixed unit test which used assertIsNotNone 2014-05-09 14:33:18 +02:00
Ronald van Rij
babbc8bcd6 When using autogenerated document ids in a sharded collection, do set that id back into the Document 2014-05-06 09:34:16 +02:00
Jatin Chopra
12809ebc74 Updated Jatin's name and github name 2014-05-06 00:25:55 -07:00
Dmitry Konishchev
b45a601ad2 Test raising NotUniqueError by Document.update() 2014-04-15 19:32:42 +04:00
Serge Matveenko
f099dc6a37 Merge pull request #608 from cloudbuy/dateutil-bug-workaround
workaround a dateutil bug
2014-04-10 12:18:25 +04:00
Dmitry Konishchev
803caddbd4 Raise NotUniqueError in Document.update() on pymongo.errors.DuplicateKeyError 2014-04-09 14:25:53 +04:00
poly
4d7b988018 Fixed uncorrectly split a query key, when it ends with "_" 2014-04-01 19:52:21 +08:00
Falcon Dai
c1f88a4e14 minor change to geo-related docs 2014-03-17 22:29:53 -05:00
Nicolas Despres
5d9ec0b208 Save is called on the document not the file field. 2014-03-17 17:19:17 +01:00
Damien Churchill
1877cacf9c fix modifying slices under python3 2014-03-12 19:49:43 +00:00
Damien Churchill
2f4978cfea Merge branch 'dateutil-bug-workaround' 2014-03-12 17:27:04 +00:00
Damien Churchill
d27a1103fa workaround a dateutil bug
In the latest released version of dateutil, there's a bug whereby a TypeError
can be raised whilst parsing a date. This is because it calls a method which
it expects to return 2 arguments, however it can return 1 depending upon the
input, which results in a TypeError: ArgType not iterable exception. Since
this is equivalent to a failed parse anyway, we can treat it the same as a
ValueError.
2014-03-12 17:19:49 +00:00
Damien Churchill
b85bb95082 Merge branch 'master' of github.com:cloudbuy/mongoengine 2014-03-12 15:11:53 +00:00
Damien Churchill
db7f93cff3 improved update queries for BaseDict & BaseList
Migrate changes to include updating single elements of ListFields as
well as MapFields by adding the same changes to BaseList. This is
done by ensuring all BaseDicts and BaseLists have the correct name
from the base of the nearest (Embedded)Document, then marking changes
with their key or index when they are changed.

Tests also all fixed up.
2014-03-12 15:07:40 +00:00
Damien Churchill
85e271098f Merge branch 'master' of https://github.com/MongoEngine/mongoengine 2014-03-12 12:44:04 +00:00
Damien Churchill
17001e2f74 Merge remote-tracking branch 'origin/master' 2014-03-11 13:00:08 +00:00
Phil Freo
c82f4f0d45 clarifying the 'push' atomic update docs
the first time I read this I was all like... "no duh it will remove either the first or the last, but which does it do???"
2014-03-07 13:37:15 -08:00
tprimozi
88247a3af9 Bugfix for weakref _instance bug. 2014-03-03 15:11:05 +00:00
tprimozi
158578a406 Added test that fails due to weakref _instance bug. 2014-03-03 15:10:35 +00:00
Kirill Pavlov
19314e7e06 fix docstring for DictField 2014-03-03 13:09:26 +08:00
Brian Helba
8bcbc6d545 Add authentication_source option to register_connection (#573) (#580)
Since v2.5, PyMongo has supported a "source" option, to specify a
particular database to authenticate against. This adds support for that
option, in the form of a "authentication_source" option to
register_connection.
2014-03-02 18:35:49 -05:00
Vlad Zloteanu
ef55e6d476 fixes MongoEngine/mongoengine#589 2014-03-01 17:51:59 +01:00
Wilson Júnior
295ef3dc1d db_alias support and fixes for custom map/reduce output 2014-02-25 15:36:30 -03:00
Frank Battaglia
9d125c9e79 inherit parent Document type _auto_id_field value 2014-02-24 11:10:02 -05:00
Frank Battaglia
86363986fc whitespace 2014-02-24 11:04:29 -05:00
Frank Battaglia
0a2dbbc58b add tests for mongo query operators 2014-02-24 11:03:50 -05:00
Frank Battaglia
673a966541 add tests for save_condition kwarg in document.save() 2014-02-24 11:02:37 -05:00
Frank Battaglia
db1e69813b add atomic conditions to save
Conflicts:
	mongoengine/document.py
2014-02-24 10:57:32 -05:00
Aleksandr Sorokoumov
e60d56f060 test implemented 2014-02-24 19:22:36 +04:00
Aleksandr Sorokoumov
328e062ae9 Distinct method bugfix
Creation of instances is executed now only for EmbeddedDocumentField
and GenericEmbeddedDocumentField in distinct method
2014-02-24 19:21:11 +04:00
tprimozi
0523c2ea4b Fixed document equality: documents in different collections can have equal ids. 2014-02-13 18:12:33 +00:00
tprimozi
c5c7378c63 Implemented equality between Documents and DBRefs 2014-02-04 13:41:17 +00:00
Brian J. Dowling
9b2080d036 Added a test for allowing dynamic dictionary-style field access
Closes #559
2014-01-28 22:10:26 -05:00
Ross Lawley
d4b3649640 Added coveralls.io badge
https://coveralls.io/r/MongoEngine/mongoengine
2014-01-28 09:25:59 +00:00
Brian J. Dowling
b085993901 Allow dynamic dictionary-style field access
Allows the doc[key] syntax to work for dynamicembeddeddocument fields

Fixes #559
2014-01-27 23:05:29 +00:00
Andrei Zbikowski
0d4afad342 Fixes issue with recursive embedded document errors 2014-01-24 16:54:29 -06:00
Ross Lawley
0da694b845 0.8.7 2014-01-24 16:23:52 +00:00
Ross Lawley
6d5e7d9e81 Calling reload on deleted / nonexistant documents raises DoesNotExist (#538) 2014-01-24 14:10:55 +00:00
Ross Lawley
bc08bea284 Fix circular import issue with django auth (#531) (#545) 2014-01-24 13:54:16 +00:00
Ross Lawley
0e5a0661e1 Fixed possible issue not catching duplicate key errors 2014-01-24 13:50:09 +00:00
Ross Lawley
a839bd428f Merge branch 'master' of github.com:MongoEngine/mongoengine 2014-01-24 13:18:11 +00:00
Ross Lawley
0277062693 Stop ensure_indexes running on a secondaries (#555) 2014-01-24 13:17:52 +00:00
Ross Lawley
7affa5ab69 Merge pull request #554 from yprez/patch-1
geo_within docs - fix broken code samples
2014-01-24 05:05:34 -08:00
Ross Lawley
ed22af4e73 Merge pull request #553 from davidwilemski/patch-2
Fix typo in upgrade docs
2014-01-24 05:05:00 -08:00
Yuri Prezument
63ebb6998e geo_within docs - fix broken code samples
No such thing as "geo_with"
2014-01-22 22:47:48 +02:00
David Wilemski
7914cd47ca Fix typo in upgrade docs
Removed extra closing parenthesis
2014-01-17 20:36:02 -05:00
Ross Lawley
708dbac70e Depending on pymongo it might raise a TypeError or ValueError 2014-01-14 10:09:11 +00:00
Ross Lawley
1b62dd5c40 Fix duplicate error check 2014-01-14 10:01:39 +00:00
Ross Lawley
4911545843 Merge branch 'master' of github.com:MongoEngine/mongoengine 2014-01-13 16:59:20 +00:00
Ross Lawley
c5cc4b7867 Updated docs 2014-01-13 16:57:49 +00:00
Damien Churchill
eacb614750 Merge branch 'master' of https://github.com/MongoEngine/mongoengine 2014-01-10 11:03:48 +00:00
Yohan Graterol
341e1e7a6d Feature for progressive JPEG. Issue #486 2014-01-08 14:48:34 -04:30
Ross Lawley
a02c820c2d Merge pull request #534 from matthewowen/master
Reject email addresses where hostname ends with '.'
2013-12-18 01:18:37 -08:00
Damien Churchill
2f6890c78a fix for nested MapFields
When using nested MapFields from a document loaded from the database, the
nested dictionaries aren't converted to BaseDict, so changes aren't
marked.

This also includes a change when marking a field as changed to ensure that
nested fields aren't included in a $set query if a parent is already marked
as changed. Not sure if this could occur but it prevents breakage if it does.
2013-12-16 13:44:07 +00:00
Ross Lawley
516591fe88 Docs update and canonical url fix 2013-12-13 09:46:45 +00:00
Ross Lawley
d2941a9110 Updated doc examples to open using read binary mode 2013-12-13 09:22:41 +00:00
Matthew Owen
f7302f710b Reject email addresses where hostname ends with '.' 2013-12-05 09:50:12 -08:00
Ross Lawley
6a02ac7e80 Fix django auth import (#531) 2013-12-04 13:32:56 +00:00
Ross Lawley
d1b86fdef5 Fix for import 2013-12-04 13:31:54 +00:00
Ross Lawley
57ac38ddca Version bump 0.8.5 2013-12-04 10:02:05 +00:00
Ross Lawley
7a73a92074 Update changelog 2013-12-04 10:01:16 +00:00
Ross Lawley
d1b30f4792 Fix auth to use get_user_document #527 2013-12-04 10:00:12 +00:00
Ross Lawley
16dcf78cab Ensure id is Hashable 2013-12-02 14:14:46 +00:00
Ross Lawley
d868cfdeb0 Fix multi level nested fields getting marked as changed (#523) 2013-11-29 16:24:32 +00:00
Ross Lawley
c074f4d925 Django 1.6 login fix (#522) 2013-11-29 13:19:52 +00:00
Ross Lawley
453024c58d Django 1.6 session fix (#509) 2013-11-29 13:11:56 +00:00
Ross Lawley
fe8340617a Merge pull request #509 from j0hnsmith/bson_serializer
django 1.6 session problem
2013-11-29 05:10:02 -08:00
Ross Lawley
b024dd913d EmbeddedDocument._instance is now set when settng the attribute (#506) 2013-11-29 13:09:11 +00:00
Ross Lawley
a2a698ab0e Fixed EmbeddedDocument with ReferenceField equality issue (#502) 2013-11-29 12:46:18 +00:00
Ross Lawley
bb56f92213 Updated changelog and AUTHORS (#499) 2013-11-29 12:07:33 +00:00
Ross Lawley
8dcd998945 Merge pull request #499 from drudim/master
Error in GenericReferenceField serialization was fixed
2013-11-29 04:06:04 -08:00
Ross Lawley
bcbbbe4046 Added test, updated AUTHORS and changelog (#498) 2013-11-29 12:04:30 +00:00
Ross Lawley
7200a8cb84 Merge pull request #498 from woakas/patch-1
Fixed bug for count method when _none is True
2013-11-29 04:01:45 -08:00
Ross Lawley
6925344807 Test updates 2013-11-29 12:01:14 +00:00
Ross Lawley
60ceeb0ddd Update change log and Authors (#496) 2013-11-29 11:55:03 +00:00
Ross Lawley
06caabf333 Import fix 2013-11-29 11:53:24 +00:00
Ross Lawley
954131bd51 Merge remote-tracking branch 'origin/pr/496' 2013-11-29 11:49:54 +00:00
Ross Lawley
855efe7fe8 Added user_permissions to Django User object (#491, #492) 2013-11-29 11:48:14 +00:00
Ross Lawley
d902a74ab0 Merge remote-tracking branch 'origin/pr/492' 2013-11-29 11:45:06 +00:00
Ross Lawley
499e11f730 Updated AUTHORS (#488) 2013-11-29 11:42:30 +00:00
Ross Lawley
6db59a9c31 Fix setting Geo Location fields (#488) 2013-11-29 11:41:54 +00:00
Ross Lawley
6465726008 Fix handling invalid dict field value (#485) 2013-11-29 10:33:36 +00:00
Ross Lawley
3a3b96e0be Merge remote-tracking branch 'origin/pr/485' 2013-11-29 10:32:49 +00:00
Ross Lawley
992c91dc0c Merge remote-tracking branch 'origin/pr/484'
Conflicts:
	AUTHORS
2013-11-29 10:17:15 +00:00
Ross Lawley
809473c15c Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-11-29 10:14:54 +00:00
Ross Lawley
d79a5ec3d6 Updated Authors and changelog (#483) 2013-11-29 10:12:02 +00:00
Ross Lawley
237469ceaf Merge pull request #483 from grampajoe/connection-defaults
Use defaults when host and port are passed as None
2013-11-29 02:10:05 -08:00
Ross Lawley
c28d9135d9 Fixed distinct casting issue with ListField of EmbeddedDocuments (#470) 2013-11-29 09:48:53 +00:00
Ross Lawley
48a5679087 Added Django 1.6 to travis Refs: #480, #454 2013-11-29 09:13:37 +00:00
Ross Lawley
7c938712f2 Updates to docs ref: #454, #480 2013-11-29 09:11:08 +00:00
Ross Lawley
4df12bebc2 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-11-29 09:09:44 +00:00
Ross Lawley
dfe8987aaa Updated geo tests 2013-11-29 09:09:16 +00:00
Ross Lawley
02dbe401d8 Merge pull request #480 from mishudark/master
fix(session Issue #454): session serializer to json
2013-11-29 01:08:30 -08:00
j0hnsmith
c18f8c92e7 added BSONSerializer 2013-11-11 15:13:41 +00:00
Eric Plumb
857cd718df Fix for issue #425 - allow undeclared fields in an embedded dynamic document to be seen by queryset methods 2013-11-08 14:57:35 -08:00
Dmytro Popovych
11d4f6499a Python 2.5-2.6 compatibility fix 2013-10-20 20:21:57 +03:00
Dmytro Popovych
f2c25b4744 Error in GenericReferenceField serialization was fixed 2013-10-20 20:08:00 +03:00
Gustavo Andrés Angulo
27b846717f Fixed bug for count method when _none is True
If my queryset have elements example:

qs.all().count() => 10
q = qs.all().none()

the count in queryset "q" must be 0
q.count() => 0
2013-10-17 16:37:31 -05:00
Loic Raucy
9ed138f896 Fixed bug with numeric valuedict keys and BaseDocument._lookup_field(). 2013-10-17 16:32:47 +02:00
Loic Raucy
1978dc80eb Added regression test for bug with DictField and numeric keys.
When a DictField has numeric fields, BaseDocument._lookup_field doesn't
work correclty.
2013-10-17 16:26:19 +02:00
rfkrocktk
fc4b247f4f Patch for #491
Fix for issue #491, a bug in the permissions system.
2013-10-14 13:13:34 -07:00
Mitar
ebf7056f4a Test for testing invalid dict field value. 2013-10-06 02:55:02 -07:00
Dan Ward
eb975d7e13 Added entry to AUTHORS 2013-10-05 20:46:51 +01:00
Dan Ward
a2dd8cb6b9 Added app_label Meta attribute to MongoUser class 2013-10-05 20:39:41 +01:00
Joe Friedl
7c254c6136 Use defaults when host and port are passed as None 2013-10-04 14:18:36 -04:00
mishudark
c8a33b83f1 fix(session): session serializer to json 2013-09-29 20:25:26 +02:00
Ross Lawley
1145c72b01 Merge pull request #462 from bool-dev/master
Fixes #458, DecimalField now ignores incorrect values until validate is called just like FloatField
2013-09-27 08:41:32 -07:00
Ross Lawley
7fc45fb711 Merge pull request #468 from elasticsales/fix-dict-validation
Fix validation for a nested DictField
2013-09-27 08:33:02 -07:00
Ross Lawley
e146262c38 Merge pull request #467 from adamsc64/doc_foreignkey_push_pull_example
Expanded example of using ListFields for one-to-many relationships.
2013-09-27 08:31:59 -07:00
Ross Lawley
6f808bd06e Merge pull request #469 from jyapayne/master
Fixes AttributeError when using storage.exists() on a non-existing file.
2013-09-27 08:21:49 -07:00
Ross Lawley
0b6ab49325 Merge pull request #472 from swistakm/fix/docs-map-reduce-mongodb.org-link
fix broken external docs link on map_reduce
2013-09-27 07:58:49 -07:00
swistakm
66d9182e50 fix broken external docs link 2013-09-20 12:43:26 +02:00
Joey Payne
654cca82a9 Fixes AttributeError when using storage.exists() on a non-existing file. 2013-09-18 11:38:38 -06:00
Stefan Wojcik
89785da1c5 fix validation for a nested DictField 2013-09-16 23:50:13 -07:00
Christopher Adams
2f9964e46e Expanded example of using ListFields for one-to-many relationships.
- Added an example of how to use the atomic update operations to
  pull and push to a ListField containing ReferenceFields.
2013-09-17 00:02:38 -04:00
bool.dev
168ecd67b0 Fixes #458, DecimalField now ignores incorrect values until validate is called,
just like FloatField.
2013-09-06 22:05:31 +05:30
Ross Lawley
bcbe740598 Updated setup.py 2013-08-23 13:41:15 +00:00
Ross Lawley
86c8929d77 0.8.4 is a go 2013-08-23 10:03:10 +00:00
Ross Lawley
6738a9433b Updated travis 2013-08-23 09:36:33 +00:00
Ross Lawley
23843ec86e Updated travis config 2013-08-23 09:06:57 +00:00
Ross Lawley
f4db0da585 Update changelog add LK4D4 to authors (#452) 2013-08-23 09:03:51 +00:00
Ross Lawley
9ee3b796cd Merge pull request #452 from LK4D4/master
Remove database name necessity in uri connection schema
2013-08-23 02:02:12 -07:00
Alexandr Morozov
f57569f553 Remove database name necessity in uri connection schema 2013-08-21 13:52:24 +04:00
Ross Lawley
fffd0e8990 Fixed error raise 2013-08-20 18:54:14 +00:00
Ross Lawley
200e52bab5 Added documentation about abstract meta
Refs #438
2013-08-20 18:44:12 +00:00
Ross Lawley
a0ef649dd8 Update travis.yml 2013-08-20 18:31:33 +00:00
Ross Lawley
0dd01bda01 Fixed "$pull" semantics for nested ListFields (#447) 2013-08-20 15:54:42 +00:00
Ross Lawley
a707598042 Allow fields to be named the same as query operators (#445) 2013-08-20 13:13:17 +00:00
Ross Lawley
8a3171308a Merge remote-tracking branch 'origin/pr/445' 2013-08-20 13:04:20 +00:00
Ross Lawley
29c887f30b Updated field filter logic - can now exclude subclass fields (#443) 2013-08-20 12:21:20 +00:00
Ross Lawley
661398d891 Fixed dereference issue with embedded listfield referencefields (#439) 2013-08-20 10:22:06 +00:00
Ross Lawley
2cd722d751 Updated setup.py 2013-08-20 10:20:05 +00:00
Ross Lawley
49f5b4fa5c Fix Queryset docs (#448) 2013-08-20 09:45:00 +00:00
Ross Lawley
67baf465f4 Fixed slice when using inheritance causing fields to be excluded (#437) 2013-08-20 09:14:58 +00:00
Ross Lawley
ee7666ddea Update AUTHORS and Changelog (#441) 2013-08-20 08:31:56 +00:00
Ross Lawley
02fc41ff1c Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-08-20 08:30:33 +00:00
Ross Lawley
d07a9d2ef8 Dynamic Fields store and recompose Embedded Documents / Documents correctly (#449) 2013-08-20 08:30:20 +00:00
Ross Lawley
3622ebfabd Merge pull request #441 from Karmak23/patch-1
Fix the ._get_db() attribute after a Document.switch_db()
2013-08-20 01:19:26 -07:00
crazyzubr
70b320633f permit the establishment of a field with the name of size or other
Example:

# model
class Example(Document):
    size = ReferenceField(Size, verbose_name='Size')

# query

examples = Example.objects(size=instance_size)

# caused an error

"""
File ".../mongoengine/queryset/transform.py", line 50, in query
if parts[-1] == 'not':
IndexError: list index out of range
"""
2013-08-15 19:32:13 +08:00
Olivier Cortès
f30208f345 Fix the ._get_db() attribute after a Document.switch_db()
Without this patch, I've got:

```
myobj._get_db()

<bound method TopLevelDocumentMetaclass._get_db of <class 'oneflow.core.models.nonrel.Article'>>

```

I need to `myobj._get_db()()` to get the database.

I felt this like a bug.

regards,
2013-08-12 19:12:53 +02:00
Ross Lawley
5bcc454678 Handle dynamic fieldnames that look like digits (#434) 2013-08-07 09:07:57 +00:00
Ross Lawley
473110568f Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-08-06 11:07:25 +00:00
Ross Lawley
88ca0f8196 Merge remote-tracking branch 'origin/pr/432'
Conflicts:
	tests/test_django.py
2013-08-06 11:05:52 +00:00
Ross Lawley
a171005010 Merge pull request #428 from devoto13/patch-1
Removed duplicated line for 'pop' method in documentation
2013-08-06 03:20:50 -07:00
Ross Lawley
f56ad2fa58 Merge pull request #426 from laurentpayot/Django_shortcuts_docs
updated docs for django shortcuts get_object_or_404 and get_list_or_404
2013-08-06 03:17:56 -07:00
veera
c9dc441915 Overridden the prepare_query_value method in SequenceField inorder to return the value as the required type. 2013-08-05 15:33:54 +05:30
Nicolas Cortot
a0d255369a Add a test case for get_user_document 2013-08-04 11:29:16 +02:00
Nicolas Cortot
40b0a15b35 Fixing typos 2013-08-04 11:03:34 +02:00
Nicolas Cortot
b98b06ff79 Fix an error in get_user_document 2013-08-04 11:01:09 +02:00
devoto13
a448c9aebf removed duplicated method 2013-08-01 17:54:41 +03:00
Laurent Payot
b3f462a39d updated docs for django shortcuts get_object_or_404 and get_list_or_404 2013-08-01 03:51:10 +02:00
Ross Lawley
7ce34ca019 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-07-31 09:44:50 +00:00
Ross Lawley
719bb53c3a Updated changelog (#423) 2013-07-31 09:44:15 +00:00
Ross Lawley
214415969f Merge pull request #423 from ncortot/get_user_document
Add get_user_document and improve mongo_auth module
2013-07-31 02:43:19 -07:00
Ross Lawley
7431b1f123 Updated AUTHORS (#424) 2013-07-31 09:31:04 +00:00
Ross Lawley
d8ffa843a9 Added str representation of GridFSProxy (#424) 2013-07-31 09:29:41 +00:00
Paul
a69db231cc Pretty-print GridFSProxy objects 2013-07-31 11:26:23 +10:00
Nicolas Cortot
c17f94422f Add get_user_document and improve mongo_auth module
* Added a get_user_document() methot to access the actual Document class
    used for authentication.
  * Clarified the docstring on MongoUser to prevent its use when the user
    Document class should be used.
  * Removed the masking of exceptions when loading the user document class.
2013-07-30 20:48:25 +02:00
Ross Lawley
b4777f7f4f Fix test 2013-07-30 15:04:52 +00:00
Ross Lawley
a57d9a9303 Added regression test (#418) 2013-07-30 13:28:05 +00:00
Ross Lawley
5e70e1bcb2 Update transform to handle docs erroneously passed to unset (#416) 2013-07-30 13:17:38 +00:00
Ross Lawley
0c43787996 Fixed indexing - turn off _cls (#414) 2013-07-30 11:43:52 +00:00
Ross Lawley
dc310b99f9 Updated docs about TTL indexes and signals (#413) 2013-07-30 10:54:04 +00:00
Ross Lawley
e98c5e10bc Fixed dereference threading issue in ComplexField.__get__ (#412) 2013-07-30 10:49:08 +00:00
Ross Lawley
f1b1090263 Merge remote-tracking branch 'origin/pr/412' into 412
Conflicts:
	AUTHORS
2013-07-30 10:32:07 +00:00
Ross Lawley
6efd6faa3f Fixed QuerySetNoCache.count() caching (#410) 2013-07-30 10:30:16 +00:00
Ross Lawley
1e4d48d371 Don't follow references in _get_changed_fields (#422, #417)
A better fix so we dont follow down a references rabbit hole.
2013-07-29 17:22:24 +00:00
Ross Lawley
93a2adb3e6 Updating changelog and authors #417 2013-07-29 15:43:54 +00:00
Ross Lawley
a66d516777 Merge pull request #417 from ProgressiveCompany/delta-dbref-false-bug
BaseDocument._delta doesn't properly end it's path at Documents when using `dbref=False`
2013-07-29 08:41:09 -07:00
Ross Lawley
7a97d42338 to_json test updates #420 2013-07-29 15:38:08 +00:00
Ross Lawley
b66cdc8fa0 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-07-29 15:30:21 +00:00
Ross Lawley
67f43b2aad Allow args and kwargs to be passed through to_json (#420) 2013-07-29 15:29:48 +00:00
Paul Uithol
d143e50238 Replace assertIn with an assertTrue; apparently missing in Python 2.6 2013-07-25 15:34:58 +02:00
Paul Uithol
e27439be6a Fix BaseDocument._delta when working with plain ObjectIds instead of DBRefs 2013-07-25 14:52:03 +02:00
Paul Uithol
2ad5ffbda2 Add asserts to test_delta_with_dbref_*, instead of relying on exceptions 2013-07-25 14:51:09 +02:00
Paul Uithol
dae9e662a5 Create test case for failing saves (wrong delta) with dbref=False 2013-07-25 14:30:20 +02:00
Ross Lawley
f22737d6a4 Merge pull request #409 from bool-dev/master
Corrected mistakes in upgrade.rst documentation
2013-07-23 01:19:36 -07:00
Ross Lawley
a458d5a176 Docs update #406 2013-07-23 08:16:06 +00:00
Ross Lawley
d92ed04538 Docs update #406 2013-07-23 08:13:52 +00:00
Thom Knowles
80b3df8953 dereference instance not thread-safe 2013-07-22 20:07:57 -04:00
bool-dev
bcf83ec761 Corrected spelling mistakes, some grammar, and UUID/DecimalField error in upgrade.rst 2013-07-18 09:17:28 +05:30
bool.dev
e44e72bce3 Merge remote-tracking branch 'upstream/master' 2013-07-18 08:49:02 +05:30
Ross Lawley
35f2781518 Update changelog 2013-07-12 09:11:27 +00:00
Ross Lawley
dc5512e403 Upgrade warning for 0.8.3 2013-07-12 09:01:11 +00:00
Ross Lawley
48ef176e28 0.8.3 is a go 2013-07-12 08:41:56 +00:00
Ross Lawley
1aa2b86df3 travis install python-dateutil direct 2013-07-11 09:38:59 +00:00
Ross Lawley
73026047e9 Trying to fix dateutil 2013-07-11 09:29:06 +00:00
Ross Lawley
6c2c33cac8 Add Jatin- to Authors, changelog update 2013-07-11 08:12:27 +00:00
Ross Lawley
d593f7e04b Fixed EmbeddedDocuments with id also storing _id (#402) 2013-07-11 08:11:00 +00:00
Ross Lawley
6c599ef506 Fix edge case where _dynamic_keys stored as None (#387, #401) 2013-07-11 07:15:34 +00:00
Ross Lawley
f48a0b7b7d Trying to fix travis 2013-07-10 21:30:29 +00:00
Ross Lawley
d9f538170b Added get_proxy_object helper to filefields (#391) 2013-07-10 21:19:11 +00:00
Ross Lawley
1785ced655 Merge branch 'master' into 391 2013-07-10 20:35:21 +00:00
Ross Lawley
e155e1fa86 Add a default for previously pickled versions 2013-07-10 20:10:01 +00:00
Ross Lawley
e28fab0550 Merge remote-tracking branch 'origin/pr/400' 2013-07-10 19:56:15 +00:00
Ross Lawley
fb0dd2c1ca Updated changelog 2013-07-10 19:54:30 +00:00
Ross Lawley
6e89e736b7 Merge remote-tracking branch 'origin/pr/393' into 393
Conflicts:
	mongoengine/queryset/queryset.py
	tests/queryset/queryset.py
2013-07-10 19:53:13 +00:00
Ross Lawley
634b874c46 Added QuerySetNoCache and QuerySet.no_cache() for lower memory consumption (#365) 2013-07-10 19:40:57 +00:00
Ross Lawley
9d16364394 Merge remote-tracking branch 'origin/pr/391' into 391 2013-07-10 14:10:09 +00:00
Wilson Júnior
daeecef59e Update fields.py
Typo in documentation for DecimalField
2013-07-10 10:59:41 -03:00
Ross Lawley
8131f0a752 Fixed sum and average mapreduce dot notation support (#375, #376) 2013-07-10 13:53:18 +00:00
Ross Lawley
f4ea1ad517 Merge remote-tracking branch 'origin/pr/376'
Conflicts:
	AUTHORS
2013-07-10 13:50:52 +00:00
Ross Lawley
f34e8a0ff6 Fixed as_pymongo to return the id (#386) 2013-07-10 13:38:53 +00:00
Ross Lawley
4209d61b13 Document.select_related() now respects db_alias (#377) 2013-07-10 12:49:19 +00:00
Ross Lawley
fa83fba637 Reload uses shard_key if applicable (#384) 2013-07-10 11:18:49 +00:00
Ross Lawley
af86aee970 _dynamic field updates - fixed pickling and creation order
Dynamic fields are ordered based on creation and stored in _fields_ordered (#396)
Fixed pickling dynamic documents `_dynamic_fields` (#387)
2013-07-10 10:57:24 +00:00
Ross Lawley
f26f1a526c Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-07-10 09:12:36 +00:00
Ross Lawley
7cb46d0761 Fixed ListField setslice and delslice dirty tracking (#390) 2013-07-10 09:11:50 +00:00
Ross Lawley
0cb4070364 Added Django 1.5 PY3 support (#392) 2013-07-10 08:53:56 +00:00
Ross Lawley
bc008c2597 Merge remote-tracking branch 'origin/pr/392' into 392 2013-07-10 08:44:10 +00:00
Ross Lawley
a1d142d3a4 Prep for django and py3 support 2013-07-10 08:38:13 +00:00
Ross Lawley
aa00dc1031 Merge pull request #392 from lig/patch-1
Fix crash on Python 3.x and Django >= 1.5
2013-07-10 01:37:40 -07:00
Wilson Júnior
592c654916 extending support for queryset.sum and queryset.average methods 2013-07-05 10:36:11 -03:00
Serge Matveenko
5021b10535 Fix crash on Python 3.x and Django >= 1.5 2013-07-03 01:17:40 +04:00
Jan Schrewe
43d6e64cfa Added a get_proxy_obj method to FileField and handle FileFields in container fields properly in ImageGridFsProxy. 2013-07-02 17:04:15 +02:00
Ross Lawley
8d21e5f3c1 Fix tests for py2.6 2013-07-02 09:47:54 +00:00
Ross Lawley
fbe5df84c0 Remove users post uri test 2013-06-25 09:30:28 +00:00
kelvinhammond
caff44c663 Fixed sum and average queryset function
* Fixed sum and average map reduce functions for sum and average so that
        it works with mongo dot notation.

* Added unittest cases / updated them for the new changes
2013-06-21 09:39:11 -04:00
Ross Lawley
d6edef98c6 Added match ($elemMatch) support for EmbeddedDocuments (#379) 2013-06-21 11:29:23 +00:00
kelvinhammond
e0d2fab3c3 Merge branch 'master' of https://github.com/MongoEngine/mongoengine
Conflicts:
	AUTHORS
2013-06-21 07:26:40 -04:00
Ross Lawley
9867e918fa Fixed weakref being valid after reload (#374) 2013-06-21 11:04:29 +00:00
Ross Lawley
e6374ab425 Added Michael Bartnett to Authors (#373) 2013-06-21 10:40:15 +00:00
Ross Lawley
e116bb9227 Fixed queryset.get() respecting no_dereference (#373) 2013-06-21 10:39:10 +00:00
Ross Lawley
f1a1aa54d8 Added full_result kwarg to update (#380) 2013-06-21 10:19:40 +00:00
Ross Lawley
574f3c23d3 get should clone before calling 2013-06-21 09:35:22 +00:00
kelvinhammond
c31d6a6898 Fixed sum and average mapreduce function for issue #375 2013-06-19 10:34:33 -04:00
Ross Lawley
44a2a164c0 Doc updates 2013-06-13 10:54:39 +00:00
Stefan Wojcik
a7ca9950fc potential fix for dereferencing nested lists 2013-06-11 15:36:35 -07:00
Stefan Wojcik
e0dd33e6be move the test into a more appropriate location 2013-06-11 12:18:03 -07:00
Stefan Wojcik
2e718e1130 unit test showing the problem 2013-06-11 12:00:59 -07:00
Ross Lawley
ede9fcfb00 Version bump 0.8.2 2013-06-07 08:45:40 +00:00
Ross Lawley
a3d43b77ca Updated changelog 2013-06-07 08:44:33 +00:00
Ross Lawley
e2b32b4bb3 Added more docs about compare_indexes (#364) 2013-06-07 08:43:05 +00:00
Ross Lawley
025c16c95d Add BobDickinson to authors (#361) 2013-06-07 08:34:57 +00:00
Ross Lawley
000eff73cc Make test_indexes_and_multiple_inheritance place nice with py3.3 (#364) 2013-06-07 08:33:34 +00:00
Ross Lawley
254efdde79 Merge remote-tracking branch 'origin/pr/364' into 364 2013-06-07 08:25:42 +00:00
Ross Lawley
f0d4e76418 Documentation updates 2013-06-07 08:21:15 +00:00
Stefan Wojcik
ba7101ff92 list_indexes support for multiple inheritance 2013-06-06 22:22:43 -07:00
Stefan Wojcik
a2457df45e make sure to only search for indexes in base classes inheriting from TopLevelDocumentMetaclass 2013-06-06 19:14:21 -07:00
Stefan Wojcik
305540f0fd better comment 2013-06-06 17:21:27 -07:00
Stefan Wojcik
c2928d8a57 list_indexes and compare_indexes class methods + unit tests 2013-06-06 17:16:03 -07:00
Ross Lawley
7451244cd2 Fixed cascading saves which weren't turned off as planned (#291) 2013-06-06 21:04:54 +00:00
Ross Lawley
d935b5764a apt only had an ancient version of python-dateutil *sigh* 2013-06-06 18:02:06 +00:00
Ross Lawley
f3af76e38c Added ygbourhis to AUTHORS (#363) 2013-06-06 17:59:07 +00:00
Ross Lawley
a7631223a3 Fixed Datastructures so instances are a Document or EmbeddedDocument (#363) 2013-06-06 17:58:10 +00:00
Ross Lawley
8aae4f0ed0 Trying to stabalise the build 2013-06-06 17:34:34 +00:00
Ross Lawley
542049f252 Trying to fix annoying python-dateutil bug 2013-06-06 17:31:50 +00:00
Ross Lawley
9f3394dc6d Added testcase for ListFields with just pks (#361) 2013-06-06 17:19:19 +00:00
Ross Lawley
06f5dc6ad7 Docs update 2013-06-06 16:44:43 +00:00
Ross Lawley
dc3b09c218 Improved cascading saves write performance (#361) 2013-06-06 16:36:17 +00:00
Ross Lawley
ad15781d8f Fixed amibiguity and differing behaviour regarding field defaults (#349)
Now field defaults are king, unsetting or setting to None on a field
with a default means the default is reapplied.
2013-06-06 13:31:52 +00:00
Ross Lawley
ea53612822 Merge remote-tracking branch 'origin/pr/349' into 349 2013-06-06 12:06:23 +00:00
Ross Lawley
c3a065dd33 Removing old test re: #348 2013-06-05 13:44:21 +00:00
Ross Lawley
5cb2812231 Reverting Fixed hashing of EmbeddedDocuments (#348) 2013-06-05 13:03:15 +00:00
Ross Lawley
f8904a5504 Explicitly set w:1 if None in save 2013-06-05 12:14:22 +00:00
Ross Lawley
eb1df23e68 Updated AUTHORS (#340, #348, #353) 2013-06-05 11:50:26 +00:00
Ross Lawley
e5648a4af9 ImageFields now include PIL error messages if invalid error (#353) 2013-06-05 11:45:08 +00:00
Ross Lawley
a246154961 Fixed hashing of EmbeddedDocuments (#348) 2013-06-05 11:31:13 +00:00
Ross Lawley
ce44843e27 Doc fix for #340 2013-06-05 11:11:02 +00:00
Ross Lawley
1a54dad643 Filter out index scan for pymongo cache 2013-06-05 10:42:41 +00:00
Ross Lawley
940dfff625 Code cleanup 2013-06-05 09:49:26 +00:00
Ross Lawley
c2b15183cb Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-06-05 09:30:56 +00:00
Ross Lawley
27e8aa9c68 Added comment about why temp debugging exists 2013-06-05 09:30:01 +00:00
Ross Lawley
e1d8c6516a Merge pull request #356 from elasticsales/simpler-cls-query
Simplify _cls queries when only a single class
2013-06-05 02:20:22 -07:00
Stefan Wojcik
eba81e368b dont use $in for _cls queries with a single subclass 2013-06-04 15:32:23 -07:00
Ross Lawley
74a3fd7596 Added queryset delete tests for signals 2013-06-04 16:59:25 +00:00
Ross Lawley
eeb5a83e98 Added lock when calling doc.Delete() for when signals have no sender (#350) 2013-06-04 16:35:25 +00:00
Ross Lawley
d47134bbf1 Reload forces read preference to be PRIMARY (#355) 2013-06-04 11:03:50 +00:00
Ross Lawley
ee725354db Querysets are now lest restrictive when querying duplicate fields (#332, #333) 2013-06-04 10:46:38 +00:00
Ross Lawley
985bfd22de Merge remote-tracking branch 'origin/pr/333' into 333 2013-06-04 10:22:54 +00:00
Ross Lawley
0d35e3a3e9 Added debugging for query counter 2013-06-04 10:20:49 +00:00
Ross Lawley
d94a191656 Updated Changelog added test for #341 2013-06-04 10:20:24 +00:00
Ross Lawley
0eafa4acd8 Merge pull request #341 from ichuang/master
FileField now honouring db_alias
2013-06-04 02:58:20 -07:00
Ross Lawley
f27a53653b Updated changelog 2013-06-04 09:56:38 +00:00
Ross Lawley
3b60adc8da Merge pull request #344 from matchbox/complex-change-tracking
Remove custom change tracking for ComplexBaseFields just use BaseField's one
2013-06-04 02:54:59 -07:00
Ross Lawley
626a3369b5 Removed unused var in _get_changed_fields (#347) 2013-06-04 09:51:58 +00:00
Ross Lawley
4244e7569b Added pre_save_post_validation signal (#345) 2013-06-04 09:35:44 +00:00
Ross Lawley
ef4b32aca7 Merge remote-tracking branch 'origin/pr/346' into 345
Conflicts:
	AUTHORS
	docs/guide/signals.rst
2013-06-04 09:35:26 +00:00
Ross Lawley
dcd23a0b4d Merge pull request #345 from amcgregor/master
Addition of pre_save_validation and move of pre_save to after validation.
2013-06-04 02:13:25 -07:00
Ross Lawley
5447c6e947 DateTimeField now auto converts valid datetime isostrings into dates (#343) 2013-06-04 09:08:13 +00:00
Ross Lawley
f1b97fbc8b Merge pull request #343 from matchbox/dateutil
if `dateutil` is available, use it to parse datetimes
2013-06-04 01:22:31 -07:00
Ross Lawley
4c8dfc3fc2 Fixed Doc.objects(read_preference=X) not setting read preference (#352) 2013-06-03 15:40:54 +00:00
Ross Lawley
ceece5a7e2 Improved PIL detection for tests 2013-06-03 13:38:58 +00:00
Ross Lawley
7e6b035ca2 Added hensom to AUTHORS #329 2013-06-03 13:32:30 +00:00
Ross Lawley
fbc46a52af Updated changelog 2013-06-03 13:31:42 +00:00
Ross Lawley
8d2e7b4372 Django session ttl index expiry fixed (#329) 2013-06-03 13:31:35 +00:00
Ross Lawley
e7da9144f5 Merge pull request #342 from elasticsales/fix-pickle-loads
Fix for pickle.loads
2013-06-03 06:20:21 -07:00
Ross Lawley
2128e169f3 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-06-03 13:14:21 +00:00
Ross Lawley
8410d64daa Merge pull request #337 from jinzhang273/patch-1
Corrected some typos in django.rst
2013-06-03 06:13:59 -07:00
Ross Lawley
b2f78fadd9 Added test for upsert & update_one #336 2013-06-03 13:05:52 +00:00
Ross Lawley
3656323f25 Merge pull request #335 from ryanwitt/patch-2
minor typos in documentation
2013-06-03 06:03:59 -07:00
Ross Lawley
2fe1c20475 Added Jiequan to AUTHORS #354 2013-06-03 13:03:07 +00:00
Ross Lawley
0fb976a80a Added Ryan to AUTHORS #334 2013-06-03 13:01:48 +00:00
Ross Lawley
3cf62de753 Merge pull request #334 from ryanwitt/patch-1
fix link to guide from tutorial
2013-06-03 06:00:48 -07:00
Ross Lawley
06119b306d Merge pull request #354 from Jiequan/patch-1
Update upgrade.rst: Added docs for the new function clean()
2013-06-03 05:56:56 -07:00
Jiequan
0493bbbc76 Update upgrade.rst
Added docs for the new function: clean()
2013-06-02 20:46:51 +08:00
Nigel McNie
4c9e90732e Apply defaults to fields with None value at 'set' time.
If a field has a default, and you explicitly set it to None, the
behaviour before this patch was very confusing:

    class Person(Document):
        created = DateTimeField(default=datetime.datetime.utcnow)

    >>> p = Person(created=None)
    >>> p.created
    datetime.datetime(2013, 5, 30, 0, 18, 20, 242628)
    >>> p.created
    datetime.datetime(2013, 5, 30, 0, 18, 20, 995248)
    >>> p.created
    datetime.datetime(2013, 5, 30, 0, 18, 21, 370578)

It would be stored as None, and then at 'get' time, the default would be
applied. As you can see, if the default is a generator, this leads to some
crazy behaviour.

There's an argument that if I asked it to be set to None, why not respect that?
But I don't think that's how the rest of mongoengine seems to work (for
example, setting a field to None seems to mean it doesn't even get set in mongo
- as opposed to being set but with a 'null' value). Besides, as the code shows
above, you'd expect p.created to return None. So clearly, mongoengine is
already expecting None to mean 'default' where a default is available.

This bug also interacts nastily with required=True - if you're forcibly setting
the field to None, then at validation time, the None will fail validation
despite a perfectly valid default being available.

With this patch, when the field is set, the default is immediately applied.
This means any generation happens once, the getter always returns the same
value, and 'required' validation always respects the default.

Note: this breakage seems to be new since mongoengine 0.8.
2013-05-30 16:37:40 +12:00
Alice Bevan-McGregor
35f084ba76 Fixed :module: reference in docs and added myself to authors. 2013-05-29 13:23:18 -04:00
Alice Bevan-McGregor
f28f336026 Improved signals documentation and some typo fixes. 2013-05-29 13:17:08 -04:00
Alice Bevan-McGregor
122d75f677 Added pre_save_validation to signal list in documentation. 2013-05-29 12:23:32 -04:00
Alice Bevan-McGregor
12f6a3f5a3 Added tests for pre_save_validation and updated tests for pre_save to encompass created flag. 2013-05-29 12:22:15 -04:00
Alice Bevan-McGregor
5d44e1d6ca Added missing reference in __all__. 2013-05-29 12:12:51 -04:00
Alice Bevan-McGregor
04592c876b Moved pre_save after validation and determination of creation state; added pre_save_validation where pre_save had been. 2013-05-29 12:04:53 -04:00
Paul Swartz
c0571beec8 fix change tracking for ComplexBaseFields 2013-05-28 17:19:46 -04:00
Paul Swartz
1302316eb0 add some tests 2013-05-28 16:08:33 -04:00
Paul Swartz
18d8008b89 if dateutil is available, use it to parse datetimes
In particular, this picks up the default `datetime.isoformat()` output, with
a "T" as the separator.
2013-05-28 15:59:32 -04:00
Stefan Wojcik
4670f09a67 fix __set_state__ 2013-05-27 13:48:02 -07:00
ichuang
159ef12ed7 FileField should pass db_alias to GridFSProxy in __set__ call 2013-05-27 11:19:34 -04:00
Jin Zhang
7a760f5640 Update django.rst 2013-05-25 06:46:23 -06:00
Ryan Witt
2b6c42a56c minor typos 2013-05-24 11:34:15 -03:00
Ryan Witt
ab4ff99105 fix guide link 2013-05-24 11:24:40 -03:00
Stefan Wojcik
774895ec8c dont simplify queries with duplicate conditions 2013-05-23 17:49:28 -07:00
Ross Lawley
c5ce96c391 Fix py3 test 2013-05-23 19:44:05 +00:00
Ross Lawley
b4a98a4000 More upgrade clarifications #331 2013-05-23 19:30:57 +00:00
Ross Lawley
5f0d86f509 Upgrade doc fix (#330) 2013-05-23 19:12:13 +00:00
Ross Lawley
c96a1b00cf Documentation cleanup (#328) 2013-05-23 19:09:05 +00:00
Ross Lawley
1eb6436682 Added get image by grid_id example 2013-05-22 10:29:45 +00:00
Ross Lawley
a84e1f17bb Fixing django tests for py 2.6 2013-05-21 09:42:22 +00:00
Ross Lawley
3ffc9dffc2 Updated requirements for test suite 2013-05-21 09:37:22 +00:00
Ross Lawley
048c84ab95 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-05-21 09:12:38 +00:00
Ross Lawley
a7470360d2 Version bump 2013-05-21 09:12:09 +00:00
Ross Lawley
50f1ca91d4 Updated Changelog 2013-05-21 09:05:55 +00:00
Ross Lawley
0d37e1cd98 Merge pull request #326 from mitar/importlib-python26
importlib does not exist on Python 2.6. Use Django version.
2013-05-21 00:19:29 -07:00
Ross Lawley
9aa77bb3c9 Fixed pickle unsaved document regression (#327) 2013-05-21 07:07:17 +00:00
Ross Lawley
fd11244966 Merge pull request #327 from elasticsales/pickle-error
Document is not picklable before it is saved
2013-05-21 00:05:00 -07:00
Stefan Wojcik
d060da094f update pickling test case to show the error 2013-05-20 17:40:56 -07:00
Mitar
306f9c5ffd importlib does not exist on Python 2.6. Use Django version. 2013-05-20 17:30:41 -07:00
Ross Lawley
5ef5611682 0.8.0 is a go 2013-05-20 12:34:47 +00:00
Ross Lawley
ebdd2d730c Fixed querying ReferenceField custom_id (#317) 2013-05-20 10:20:43 +00:00
Ross Lawley
1ddf8b3159 Merge remote-tracking branch 'origin/pr/317' 2013-05-20 10:20:04 +00:00
Ross Lawley
a6bc870815 Fixed pickle issues with collections (#316) 2013-05-20 10:10:53 +00:00
Ross Lawley
56cd73823e Add backwards compat for pickle 2013-05-20 10:09:16 +00:00
Ross Lawley
6299015039 Updated pickling (#316) 2013-05-20 10:04:17 +00:00
Ross Lawley
11b7cfb5ff Merge remote-tracking branch 'origin/pr/316' into 316 2013-05-20 09:08:12 +00:00
Ross Lawley
367f49ce1c Updated changelog 2013-05-20 08:12:50 +00:00
Ross Lawley
8165131419 Doc updated 2013-05-20 08:12:09 +00:00
Ross Lawley
e402157b4d Merge remote-tracking branch 'origin/pr/319' 2013-05-20 08:10:37 +00:00
Ross Lawley
967da7944f Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-05-20 08:09:30 +00:00
Ross Lawley
89f1c21f20 Updated AUTHORS (#325) 2013-05-20 08:08:52 +00:00
Ross Lawley
7e706190a5 Merge pull request #325 from daxtens/doc-fixes
Minor documentation fix: switch_collection -> switch_db for changing database
2013-05-20 01:07:39 -07:00
Daniel Axtens
36a3770673 If you need to read from another database, use switch_db not switch_collection. 2013-05-20 15:49:01 +10:00
Wilson Júnior
bc92f78afb fixes for value_decorator 2013-05-16 13:12:49 -03:00
Wilson Júnior
f7e22d2b8b fixes for get_next_value 2013-05-16 13:05:07 -03:00
Wilson Júnior
0b1e11ba1f added my github profile 2013-05-16 12:55:16 -03:00
Wilson Júnior
10e0b1daec Merge branch 'master' of https://github.com/MongoEngine/mongoengine 2013-05-16 12:50:47 -03:00
Wilson Júnior
731d8fc6be added get_next_value to SequenceField 2013-05-16 12:50:34 -03:00
Stefan Wojcik
f6d0b53ae5 test reference to a custom pk doc 2013-05-13 21:42:20 -07:00
Léo S
0efb90deb6 Added a failing test when using pickle with signal hooks 2013-05-13 13:14:15 +02:00
Ross Lawley
b16eabd2b6 Updated version 2013-05-10 15:09:08 +00:00
Ross Lawley
f8350409ad assertEquals is bad 2013-05-10 15:08:01 +00:00
Ross Lawley
5b498bd8d6 Added no_sub_classes context manager and queryset helper (#312) 2013-05-10 15:05:16 +00:00
Ross Lawley
941042d0ba Merge remote-tracking branch 'origin/pr/312' into 312 2013-05-10 14:44:44 +00:00
Ross Lawley
9251ce312b Querysets now utilises a local cache
Changed __len__ behavour in the queryset (#247, #311)
2013-05-10 13:57:32 +00:00
Wilson Júnior
96a964a183 added .disable_inheritance method for the simple fetch exclusives classes 2013-05-09 13:18:58 -03:00
Ross Lawley
9e513e08ae Updated RC version 2013-05-07 11:55:56 +00:00
Ross Lawley
9dfee83e68 Fixed querying string versions of ObjectIds issue with ReferenceField (#307) 2013-05-07 11:54:47 +00:00
Ross Lawley
7cde979736 Updated fields 2013-05-07 11:39:16 +00:00
Ross Lawley
870ff1d4d9 Added $setOnInsert support for upserts (#308)
Upserts now possible with just query parameters (#309)
2013-05-07 11:11:55 +00:00
Ross Lawley
52c162a478 Pep8 2013-05-07 11:01:23 +00:00
Ross Lawley
ddd11c7ed2 Added offline docs links 2013-05-07 10:57:52 +00:00
Ross Lawley
2c119dea47 Upserting is the only way to ensure docs are saved correctly (#306) 2013-05-07 10:34:13 +00:00
Ross Lawley
ebd1561682 Updated changelog 2013-05-03 14:21:36 +00:00
Ross Lawley
3ccc495c75 Fixed register_delete_rule inheritance issue 2013-05-03 12:56:53 +00:00
Ross Lawley
0eda7a5a3c 0.8.0RC2 is a go 2013-05-02 10:51:04 +00:00
Ross Lawley
f2c16452c6 Help with backwards compatibility 2013-05-02 10:48:30 +00:00
Ross Lawley
a2c429a4a5 Queryset cursor regeneration testcase 2013-05-02 10:48:09 +00:00
Ross Lawley
4a71c5b424 Updates to CONTRIBUTING.rst 2013-05-02 10:47:37 +00:00
Ross Lawley
268dd80cd0 Added Jin Zhang to authors (#304) 2013-05-02 07:35:44 +00:00
Ross Lawley
3002e79c98 Updated changelog 2013-05-02 07:35:33 +00:00
Ross Lawley
5eab348e82 Merge pull request #304 from jinzhang273/master
Fixed typo in defining-documents.rst
2013-05-02 00:34:18 -07:00
Jin
1cdbade761 fixed typo in defining-documents.rst 2013-05-01 16:54:48 -07:00
Ross Lawley
8c9afbd278 Fix cloning of sliced querysets (#303) 2013-05-01 19:40:49 +00:00
Ross Lawley
cd73654683 Update readme 2013-05-01 09:48:58 +00:00
Ross Lawley
9654fe0d8d 0.8.0RC1 is a go! 2013-05-01 09:30:20 +00:00
Ross Lawley
3d49c33c6a Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-05-01 08:48:42 +00:00
Ross Lawley
e58b3390aa Removed import with from future 2013-05-01 08:48:14 +00:00
Ross Lawley
92a1f5736b Merge pull request #302 from Demoncode/upstream
Pass through write_concern from update_one to update correctly
2013-05-01 01:44:52 -07:00
Alex Kelly
00a57f6cea Pass write_concern parameter from update_one 2013-04-30 21:44:07 +01:00
Alex Kelly
1c345edc49 Updated tests for passing write_concern to update and update_one to check return. 2013-04-30 21:43:44 +01:00
Ross Lawley
7aa1f47378 Updated minimum requirements 2013-04-30 16:46:08 +00:00
Ross Lawley
473d5ead7b Geo errors fix and test update 2013-04-30 16:42:38 +00:00
Ross Lawley
68f760b563 get_db() only assigns the db after authentication (#257) 2013-04-30 15:05:41 +00:00
Ross Lawley
9c1cd81adb Add support for new geojson fields, indexes and queries (#299) 2013-04-30 14:46:23 +00:00
Ross Lawley
85b81fb12a If values cant be compared mark as changed (#287) 2013-04-29 10:36:11 +00:00
Ross Lawley
5d7444c115 Ensure as_pymongo() and to_json honour only() and exclude() (#293) 2013-04-29 09:38:21 +00:00
Ross Lawley
b0c1ec04b5 Improvements to indexing documentation (#130) 2013-04-29 07:38:31 +00:00
bool.dev
5cfd8909a8 Merge remote-tracking branch 'upstream/master' 2013-04-28 13:40:58 +05:30
Ross Lawley
6e2d2f33de Updated benchmarks for #27 2013-04-26 14:33:40 +00:00
Ross Lawley
5e65d27832 PEP8 x == True should be x is True 2013-04-26 11:46:12 +00:00
Ross Lawley
36993097b4 Document serialization uses field order to ensure a strict order is set (#296) 2013-04-26 11:38:45 +00:00
Ross Lawley
2447349383 Added a note about distinct being a command 2013-04-26 09:59:43 +00:00
Ross Lawley
7765f272ac Documentation api and reference cleanups 2013-04-26 08:46:46 +00:00
Ross Lawley
13d8dfdb5f Save py2.6 from Decimal Float fun 2013-04-26 08:43:38 +00:00
Ross Lawley
5e94637adc DecimalField now stores as float not string (#289) 2013-04-25 15:39:57 +00:00
Ross Lawley
ac6e793bbe UUIDField now stores as a binary by default (#292) 2013-04-25 13:43:56 +00:00
Ross Lawley
d0d9c3ea26 Test to ensure that pickled complex fields work with save() (#228) 2013-04-25 12:21:25 +00:00
Ross Lawley
f7bc58a767 Added assertIn / assertNotIn for python 2.6 2013-04-25 12:03:44 +00:00
Ross Lawley
bafdf0381a Updates 2013-04-25 11:59:56 +00:00
Ross Lawley
3fc5dc8523 Testing if travis 2.6 is >= 2.6.6 2013-04-25 11:46:18 +00:00
Ross Lawley
df4dc3492c Upgrade changelog, docs and django/auth.py 2013-04-25 11:41:01 +00:00
Ross Lawley
10731b0fd8 Merge remote-tracking branch 'origin/pr/285' into p285 2013-04-25 11:18:21 +00:00
Ross Lawley
cb9166aba4 Auth cleanups - removed duplicates 2013-04-25 11:04:33 +00:00
Ross Lawley
fe62c3aacb Cascading saves now default to off (#291) 2013-04-25 10:24:33 +00:00
Ross Lawley
c60ea40828 ReferenceField now store ObjectId's by default rather than DBRef (#290) 2013-04-24 12:14:34 +00:00
Ross Lawley
c59ea26845 Updated docs to clarify or != | (##288) 2013-04-24 11:17:21 +00:00
Ross Lawley
9bd8b3e9a5 Connection doc updates 2013-04-23 20:55:37 +00:00
Ross Lawley
5271f3b4a0 More doc updates 2013-04-23 20:49:22 +00:00
Ross Lawley
8a7b619b77 Tutorial updates 2013-04-23 20:37:05 +00:00
Ross Lawley
88f96b0838 Ensure all field params are documented (#97) 2013-04-23 15:59:23 +00:00
Ross Lawley
1e1e48732a Update setup.py python versions / langauge in changelog 2013-04-23 15:33:38 +00:00
Ross Lawley
3537897fc5 Update build assets for PY3 2013-04-23 15:32:10 +00:00
Ross Lawley
3653981416 Added ImageField support for inline replacements (#86) 2013-04-23 15:12:57 +00:00
Ross Lawley
94d1e566c0 Added SequenceField.set_next_value(value) helper (#159) 2013-04-23 14:44:17 +00:00
Ross Lawley
a692316293 Update to upgrade docs 2013-04-23 14:09:41 +00:00
Ross Lawley
e2f3406e89 Updated .only() behaviour - now like exclude it is chainable (#202) 2013-04-23 14:06:29 +00:00
Ross Lawley
81c7007f80 Added with_limit_and_skip support to count() (#235) 2013-04-23 10:38:32 +00:00
Ross Lawley
e4f38b5665 Fragile test fix 2013-04-22 16:46:59 +00:00
Ross Lawley
14b6c471cf Fix PY3 hasattr connecting to the db at define time 2013-04-22 16:37:09 +00:00
Ross Lawley
0d0befe23e Removed __len__ from queryset (#247) 2013-04-22 16:19:55 +00:00
Ross Lawley
efad628a87 Objects queryset manager now inherited (#256) 2013-04-22 15:32:11 +00:00
Ross Lawley
c16e6d74e6 Updated connection to use MongoClient (#262, #274) 2013-04-22 15:07:15 +00:00
Ross Lawley
80db9e7716 Updated travis 2013-04-22 13:06:29 +00:00
Ross Lawley
7cf2a3e978 Merge branch 'master' into 0.8
Conflicts:
	mongoengine/__init__.py
2013-04-22 13:03:10 +00:00
Nicolas Cortot
681b74a41c Travis: adding Django-1.5.1 to env 2013-04-19 19:08:53 +02:00
Nicolas Cortot
d39d10b9fb Tests should not require Django 1.5 to run 2013-04-19 18:28:45 +02:00
Nicolas Cortot
dff44ef74e Fixing warning which prevented tests from succeeding
Now that we're importing the auth classes in the tests, no warning can be
raised or test_dbref_reference_field_future_warning will fail.
2013-04-19 17:50:15 +02:00
Nicolas Cortot
485047f20b Custom User Model for Django 1.5 2013-04-19 17:34:00 +02:00
Ross Lawley
6affbbe865 Update changelog location 2013-04-19 13:08:46 +00:00
Ross Lawley
e3600ef4de Updated version 2013-04-19 12:53:46 +00:00
Ross Lawley
f0eaec98c7 Merge branch 'master' into 0.8 2013-04-19 12:47:41 +00:00
Ross Lawley
6dcd7006d0 Fix test 2013-04-19 12:47:19 +00:00
Ross Lawley
5de4812477 Updating AUTHORS (#283) 2013-04-18 13:38:36 +00:00
Ross Lawley
d5b28356bc Merge pull request #283 from ncortot/delete_signals_fix
Do not fail on delete() when blinker is not available
2013-04-18 06:37:47 -07:00
Ross Lawley
76fddd0db0 Merge branch 'master' into 0.8
Conflicts:
	AUTHORS
	mongoengine/base.py
	tests/test_dereference.py
2013-04-18 13:30:00 +00:00
Ross Lawley
1108586303 Updated queryset 2013-04-18 13:26:35 +00:00
Ross Lawley
3f49923298 Update AUTHORS and changelog (#278) 2013-04-18 13:21:36 +00:00
Ross Lawley
c277be8b6b Merge remote-tracking branch 'origin/pr/278' 2013-04-18 13:17:08 +00:00
Ross Lawley
6e083fa6a1 Merge pull request #280 from Multiposting/ncortot/auth_datetime_fix
Fix datetime call in UserManager
2013-04-18 06:11:10 -07:00
Nicolas Cortot
073091a06e Do not fail on delete() when blinker is not available 2013-04-17 21:45:54 +02:00
Ross Lawley
03bfd01862 Updated field iteration for py2.5 2013-04-17 15:54:32 +00:00
Ross Lawley
539f01d08e Merge branch 'master' into 0.8
Conflicts:
	mongoengine/base.py
	tests/test_document.py
2013-04-17 15:09:03 +00:00
Ross Lawley
dcf3c86dce Using "id" in data not "_id" as its a mapping of fieldnames (#255) 2013-04-17 15:07:57 +00:00
Nicolas Cortot
ec639cd6e9 Fix datetime call in UserManager 2013-04-17 16:34:09 +02:00
Ross Lawley
420376d036 Merge fixes 2013-04-17 14:27:33 +00:00
Ross Lawley
51e50bf0a9 Merge branch 'master' into 0.8M
Conflicts:
	AUTHORS
	docs/django.rst
	mongoengine/base.py
	mongoengine/queryset.py
	tests/fields/fields.py
	tests/queryset/queryset.py
	tests/test_dereference.py
	tests/test_document.py
2013-04-17 11:57:53 +00:00
daniil
c2d77f51bb test for #278 issue 2013-04-17 12:14:07 +04:00
Ross Lawley
b4d87d9128 Updated changelog 2013-04-16 20:50:34 +00:00
Ross Lawley
4401a309ee Merge remote-tracking branch 'origin/pr/268' 2013-04-16 20:49:33 +00:00
Ross Lawley
b562e209d1 Updated EmailField length to support long domains (#243) 2013-04-16 20:46:02 +00:00
Ross Lawley
3a85422e8f Added 64-bit integer support (#251) 2013-04-16 20:35:29 +00:00
Ross Lawley
e45397c975 Merge remote-tracking branch 'origin/pr/251' 2013-04-16 20:34:32 +00:00
Ross Lawley
1f9ec0c888 Added Django sessions TTL support (#224) 2013-04-16 20:30:40 +00:00
Ross Lawley
f8ee470e70 Merge remote-tracking branch 'origin/pr/224' 2013-04-16 20:28:24 +00:00
Ross Lawley
d02de0798f Documentation fix explaining adding a dummy backend for django (#172) 2013-04-16 20:26:23 +00:00
Ross Lawley
6fe074fb13 Fixed issue with numerical keys in MapField(EmbeddedDocumentField()) (#240) 2013-04-16 20:21:11 +00:00
Ross Lawley
4db339c5f4 Merge remote-tracking branch 'origin/pr/240' 2013-04-16 20:19:23 +00:00
Ross Lawley
a525764359 Fixed clearing _changed_fields for complex nested embedded documents (#237, #239, #242) 2013-04-16 20:12:01 +00:00
Ross Lawley
f970d5878a Merge remote-tracking branch 'origin/pr/242'
Conflicts:
	tests/test_document.py
2013-04-16 20:07:03 +00:00
Daniil Sharou
cc0a2cbc6f fix UnicodeEncodeError for dbref
Fix "UnicodeEncodeError: 'ascii' codec can't encode character ..." error in case dbref contains non-ascii characters
2013-04-16 22:34:33 +04:00
Daniil Sharou
add0b463f5 fix UnicodeEncodeError for dbref
Fix "UnicodeEncodeError: 'ascii' codec can't encode character ..." error in case dbref contains non-ascii characters
2013-04-16 21:12:57 +04:00
Ross Lawley
d80b1a7749 Test clean up (#255) 2013-04-15 08:03:51 +00:00
Ross Lawley
6186691259 Updated changelog and AUTHORS (#255) 2013-04-15 08:01:24 +00:00
Ross Lawley
b451cc567d Return '_id' as the key for document.id in _data dictionary
* Re #146
Conflicts:

	mongoengine/base.py
2013-04-15 08:00:14 +00:00
Ross Lawley
757ff31661 Updated changelog 2013-04-15 07:53:57 +00:00
Ross Lawley
97a98f0045 Only mark a field as changed if the value has changed (#258) 2013-04-15 07:52:04 +00:00
Ross Lawley
8f05896bc9 Merge remote-tracking branch 'origin/pr/258'
Conflicts:
	AUTHORS
2013-04-15 07:51:26 +00:00
Ross Lawley
da7a8939df Also check if a TopLevelMetaclass instance (#261) 2013-04-15 07:41:04 +00:00
Ross Lawley
b6977a88ea Explicitly check for Document instances when dereferencing (#261) 2013-04-15 07:32:04 +00:00
Ross Lawley
eafbc7f20d Merge remote-tracking branch 'origin/pr/261' 2013-04-15 07:31:02 +00:00
bool.dev
d92f992c01 Removed merge trackers in code, merged correctly now. 2013-04-14 13:48:11 +05:30
bool.dev
20a5d9051d Merge conflicts resolved. 2013-04-14 13:39:54 +05:30
Ross Lawley
c9a5710554 Fixed order_by chaining issue (#265) 2013-04-12 15:56:40 +00:00
Ross Lawley
f10e946896 Merge remote-tracking branch 'origin/pr/265' into 265 2013-04-12 14:27:37 +00:00
Ross Lawley
2f19b22bb2 Added dereference support for tuples (#250) 2013-04-12 14:25:43 +00:00
Ross Lawley
d134e11c6d Merge remote-tracking branch 'origin/pr/250' 2013-04-12 14:24:18 +00:00
Ross Lawley
63edd16a92 Resolve field name to db field name when using distinct(#260, #264, #269) 2013-04-12 14:20:44 +00:00
Ross Lawley
37740dc010 Added kwargs to doc.save to help interop with django (#223, #270) 2013-04-12 14:05:08 +00:00
Ross Lawley
04b85ddbf2 Merge branch 'master' into 0.8
Conflicts:
	.travis.yml
	AUTHORS
	docs/changelog.rst
	mongoengine/base.py
	mongoengine/queryset.py
	tests/queryset/queryset.py
2013-04-12 13:36:29 +00:00
Ross Lawley
836dc96f67 Updated changelog 2013-04-12 12:56:15 +00:00
Ross Lawley
49a7542b14 Fixing cloning in python 3 2013-04-12 12:55:03 +00:00
Ross Lawley
a84ffce5a0 Merge remote-tracking branch 'origin/pr/272' 2013-04-12 10:55:09 +00:00
Ross Lawley
210b3e5192 Merge remote-tracking branch 'origin/pr/254' 2013-04-12 10:41:07 +00:00
Ross Lawley
5f1d5ea056 Try and fix wobbly test 2013-04-12 10:35:09 +00:00
Ross Lawley
19a7372ff9 Fix test_require for Django 2013-04-12 10:32:50 +00:00
Ross Lawley
cc5b60b004 Updated pymongo versions and pillow wont work 2013-04-12 10:30:52 +00:00
Ross Lawley
b06f9dbf8d Travis travis travis 2013-04-12 10:02:55 +00:00
Ross Lawley
d9b8ee7895 next test 2013-04-11 15:47:53 +00:00
Ross Lawley
e9ff655b0e Trying to fix travis 2013-04-11 15:00:49 +00:00
Ross Lawley
d58341d7ae Fix doc generation path (#230)
Add Lukaszb to Authors
2013-04-11 13:15:17 +00:00
Ross Lawley
669d21a114 Merge pull request #241 from benoitlouy/fixing_travis_build
Fixing travis build
2013-04-11 04:51:51 -07:00
Kristinn Örn Sigurðsson
7e980a16d0 Don't run unset on IntField if the value is 0 (zero).
The IntField in unset if the IntField value doesn't validate to "truthify" (therefore, is
set as 0) and the default value of the IntField in question is 0.
This is not a logical functionality in my opinion. Take this example.
You have an IntField that is a counter which can be incremented and
decremented. This counter has the default value of 0 and is a required
field. Every time the counter reaches 0, the field is unset.
2013-04-05 11:01:46 +02:00
Kristinn Örn Sigurðsson
47df8deb58 Fix the implementation. 2013-04-04 17:30:21 +02:00
Kristinn Örn Sigurðsson
dd006a502e Don't run unset on IntField if the value is 0 (zero). 2013-04-04 17:09:05 +02:00
bool.dev
782d48594a Fixes resolving to db_field from class field name, in distinct() query. 2013-04-04 09:02:30 +05:30
Alice Bevan-McGregor
07d3e52e6a Tests for construction using positional parameters. 2013-04-03 15:03:33 -04:00
Alice Bevan-McGregor
fc1ce6d39b Allow construction of document instances using positional arguments. 2013-04-03 15:00:51 -04:00
Alice Bevan-McGregor
32d5c0c946 Store ordered list of field names, and return the ordered list when iterating a document instance. 2013-04-03 15:00:34 -04:00
Stefan Wojcik
dfabfce01b show that order_by followed by limit works, but not the other way around 2013-04-01 17:17:01 -07:00
Stefan Wojcik
74f3f4eb15 more ordering unit tests 2013-04-01 16:17:17 -07:00
Paul Swartz
20cb0285f0 explicitly check for Document instances when dereferencing
In particular, `collections.namedtuple` instances also have a `_fields`
attribute which confuses the dereferencing.
2013-03-27 14:53:47 -04:00
Paul Swartz
faf840f924 only mark a field as changed if the value has changed
Prevents spurious changes from being recorded.
2013-03-25 11:00:20 -04:00
Aleksandr Sorokoumov
165bea5bb9 QuerySet chaining test was supplemented with ReferenceField
chaining test
2013-03-18 12:32:49 +01:00
Aleksandr Sorokoumov
f7515cfca8 add myself to AUTHORS 2013-03-18 12:22:55 +01:00
Jaepil Jeong
a762a10dec Revert "Fixed potential overflow error."
This reverts commit 67182713d9.
2013-03-18 19:30:04 +09:00
Aleksandr Sorokoumov
a192029901 ReferenceField query chaining bug fixed. 2013-03-16 16:54:26 +01:00
Jaepil Jeong
67182713d9 Fixed potential overflow error. 2013-03-15 00:12:48 +09:00
Jaepil Jeong
e9464e32db Added test cases for LongField. 2013-03-14 23:59:50 +09:00
Jaepil Jeong
2d6ae16912 Added LongField to support 64-bit integer type. 2013-03-14 23:25:22 +09:00
Russ Weeks
f9cd8b1841 added unit test for dereference patch 2013-03-12 12:45:38 -07:00
Russ Weeks
41a698b442 Changed dereference.py to keep tuples as tuples 2013-03-12 10:28:29 -07:00
Ross Lawley
9f58bc9207 Merge pull request #245 from zmolodchenko/patch-1
fix error reporting, where choices is list of flat values
2013-03-12 01:13:16 -07:00
zmolodchenko
d36f6e7f24 fix error reporting, where choices is list of flat values 2013-03-09 21:08:10 +02:00
Ross Lawley
eeb672feb9 Removing custom layout 2013-03-07 15:37:01 +00:00
Ross Lawley
063a162ce0 Merge branch 'master' of github.com:MongoEngine/mongoengine 2013-03-07 15:31:48 +00:00
Ross Lawley
3e4a900279 Adding google analytics 2013-03-07 15:25:18 +00:00
benoitlouy
43327ea4e1 Add testcase for issue #237 2013-03-01 07:38:28 -05:00
benoitlouy
0d2e84b16b Fix for issue #237: clearing changed fields recursively in EmbeddedDocuments after saving a Document 2013-02-28 00:37:34 -05:00
Benoit Louy
3c78757778 fix travis build: builds were failing because libz.so location changed. 2013-02-26 09:55:29 -05:00
Loic Raucy
d0245bb5ba Fixed #238: dictfields handle numerical strings indexes. 2013-02-26 11:26:51 +01:00
Loic Raucy
3477b0107a Added regression test for numerical string keys. 2013-02-26 11:26:38 +01:00
Ross Lawley
8df9ff90cb Update LICENSE 2013-02-14 08:26:36 +00:00
hellysmile
d6b4ca7a98 Fix docs quotes 2013-02-01 04:19:55 +02:00
hellysmile
2e18199eb2 Django sessions TTL support 2013-02-01 04:17:16 +02:00
Ross Lawley
025e17701b Fixed inner queryset looping (#204) 2013-01-29 10:33:13 +00:00
Ross Lawley
156ca44a13 Doc fix thanks to @jabapyth (#206) 2013-01-28 16:49:34 +00:00
Ross Lawley
39dac7d4db Fix file open rules 2013-01-28 16:26:01 +00:00
Ross Lawley
9ca632d518 Updated Save so it calls $set and $unset in a single operation (#211) 2013-01-28 16:00:38 +00:00
Ross Lawley
4177fc6df2 Can call del Doc.attr to delete field value 2013-01-28 15:57:33 +00:00
Ross Lawley
d90890c08e Merge branch 'single-work-op' of https://github.com/njoyce/mongoengine into 211
Conflicts:
	mongoengine/document.py
	tests/test_document.py
2013-01-28 15:05:12 +00:00
Ross Lawley
1ca098c402 Fixed invalid choices error bubbling (#214) 2013-01-28 14:40:26 +00:00
Ross Lawley
3208a7f15d Merge fix tests 2013-01-28 14:28:40 +00:00
Ross Lawley
8eda52e8e0 Merge branch 'master' of https://github.com/malderete/mongoengine into 214
Conflicts:
	AUTHORS
	mongoengine/base.py
	tests/fields/fields.py
2013-01-28 14:27:17 +00:00
Ross Lawley
5b161b7445 ReadPreference that overrides slave_okay (#218) 2013-01-28 14:17:54 +00:00
Ross Lawley
8c1f8e54cd Added the "get_decoded" method to the MongoSession class (#216) 2013-01-28 14:12:47 +00:00
Ross Lawley
03d3c26a99 Merge branch 'patch-1' of https://github.com/lcya86/mongoengine into 0.8 2013-01-28 14:09:51 +00:00
Ross Lawley
0cbd3663e4 Updated tests 2013-01-28 13:40:28 +00:00
Ross Lawley
f182daa85e Fixed Documents deleted via a queryset don't call any signals (#105) 2013-01-28 13:32:21 +00:00
Ross Lawley
de2f774e85 Fix validation test 2013-01-28 13:29:44 +00:00
Ross Lawley
9d9a4afee9 Added Doc class and pk to Validation messages (#69) 2013-01-28 12:05:09 +00:00
Ross Lawley
0ea363c7fc Updated authors and changelof (#142) 2013-01-25 12:13:46 +00:00
Ross Lawley
d7ee47ee25 Merge branch 'add_group_permission' of https://github.com/multmeio/mongoengine into 0.8
Conflicts:
	mongoengine/django/auth.py
2013-01-25 12:11:22 +00:00
Ross Lawley
eb1b6e34c7 Updated upgrade docs (#49) 2013-01-25 11:51:58 +00:00
Ross Lawley
621b2b3f72 Undefined data should not override instance methods (#49) 2013-01-25 11:28:20 +00:00
Ross Lawley
83da08ef7d Documentation fixes 2013-01-24 17:43:57 +00:00
Ross Lawley
9f551121fb Added docs for no_dereference and scalar (#68) 2013-01-24 17:41:21 +00:00
Ross Lawley
ba48dfb4bf Added no_dereference method for querysets (#82) (#61) 2013-01-24 17:33:10 +00:00
Ross Lawley
ed2ea24b75 More test edge case fixing 2013-01-24 13:10:51 +00:00
Ross Lawley
eefbd3f597 Updated wobbly python 3.3 test 2013-01-24 12:52:16 +00:00
Ross Lawley
e38bf63be0 Fixed overriding objects with custom manager (#58) 2013-01-24 11:29:51 +00:00
Ross Lawley
e7ba5eb160 Added #121 to changelog 2013-01-24 10:41:01 +00:00
Ross Lawley
fff27f9b87 Added support for compound primary keys (#149) 2013-01-24 10:37:54 +00:00
Ross Lawley
d58f594c17 Updated changelog 2013-01-23 21:21:46 +00:00
Ross Lawley
9797d7a7fb Added switch_collection context manager and method (#220) 2013-01-23 21:19:21 +00:00
Ross Lawley
c8b65317ef Updated documentation instance tests 2013-01-23 20:15:05 +00:00
Ross Lawley
3a6dc77d36 Added no_dereference context manager (#82)
Reorganised the context_managers as well
2013-01-23 19:05:44 +00:00
Ross Lawley
4f70c27b56 Updated doc string 2013-01-23 16:19:07 +00:00
Ross Lawley
ea46edf50a Added switch_db method to document instances (#106) 2013-01-23 16:07:07 +00:00
Ross Lawley
e5e88d792e Added SwitchDB context manager (#106) 2013-01-23 12:54:14 +00:00
Ross Lawley
6d68ad735c Fixed validation for GenericReferences
Where the references haven't been dereferenced
2013-01-22 17:56:15 +00:00
Ross Lawley
c44b98a7e1 Updated changelog 2013-01-22 17:54:35 +00:00
Ross Lawley
445f9453c4 Fixed reverse delete rule with inheritance (#197) 2013-01-22 16:38:07 +00:00
Ross Lawley
3364e040c8 Ensure $maxDistance is always the last part of the query (#179) 2013-01-22 16:05:44 +00:00
Ross Lawley
692f00864d Fixed inheritance and unique index creation (#140) 2013-01-22 15:16:58 +00:00
Ross Lawley
344dc64df8 Updated authors and changelog #163 2013-01-22 14:05:06 +00:00
Ross Lawley
473425a36a Merge branch 'getlasterror' of https://github.com/helduel/mongoengine into 163 2013-01-22 13:55:06 +00:00
Ross Lawley
3ba58ebaae Added Nicolas Trippar to AUTHORS #179 2013-01-22 13:39:53 +00:00
Ross Lawley
2c7b12c022 Added support for $maxDistance (#179) 2013-01-22 13:31:53 +00:00
lcya86
17eeeb7536 Update mongoengine/django/sessions.py
added the "get_decoded" method to the MongoSession class
2013-01-17 15:31:27 +08:00
Martin Alderete
de5fbfde2c Merge branch 'validation-message' 2013-01-15 02:45:47 -03:00
Martin Alderete
f5d02e1b10 Fixed issue with choices validation when they are simple list/tuple,
after model.validate() did not get any error message.
Added test to ensure that model.validate() set the correct error
messages.
2013-01-15 02:40:15 -03:00
Ross Lawley
e508625935 Update docs/upgrade.rst 2013-01-14 16:37:40 +00:00
Ross Lawley
0b177ec4c1 Merge branch 'master' into 0.8
Conflicts:
	mongoengine/queryset.py
	tests/queryset/queryset.py
	tests/test_document.py
2013-01-10 15:12:56 +00:00
Ross Lawley
87c965edd3 Fixing PY3.3 test cases 2013-01-10 11:08:07 +00:00
Ross Lawley
72dd9daa23 Fixing py3.3 tests 2013-01-09 16:16:48 +00:00
Ross Lawley
a68529fba8 Merge branch 'master' into 0.8
Conflicts:
	tests/test_document.py
2013-01-09 13:36:17 +00:00
Ross Lawley
06681a453f python 3.3 test fixes 2013-01-09 13:15:21 +00:00
Ross Lawley
5907dde4a8 Merge branch 'master' into 0.8
Conflicts:
	.travis.yml
2013-01-09 08:52:24 +00:00
Ross Lawley
8e038dd563 Updated travis python-3.1 no longer supported 2013-01-09 08:51:45 +00:00
Ross Lawley
50905ab459 Test update 2013-01-09 08:41:03 +00:00
Nick Joyce
7bb9c7d47f Ensure that the update actions are grouped rather than serial.
This is a performance update. When multiple properties of the same
entity have been deleted and modified, 2 calls to update the entity are
made, one {"$set": … } and another {"$unset": … }. This is 2 network
interface calls which is a performance killer (even at lan speeds).

Fixes: #210
2013-01-07 15:03:29 +00:00
Ross Lawley
5c45eee817 Whitespace 2013-01-04 16:28:26 +00:00
Ross Lawley
0f9e4ef352 Add mongoengine.png asset in the build 2013-01-04 16:27:58 +00:00
Ross Lawley
85173d188b Add simplejson to python 2.5 build 2013-01-04 15:57:08 +00:00
Ross Lawley
d9ed33d1b1 Added python 3.3 support to travis 2013-01-04 14:33:08 +00:00
Ross Lawley
e6ac8cab53 Fixing python 2.5 support 2013-01-04 14:28:42 +00:00
Ross Lawley
f890ebd0f4 Merge branch 'master' into 0.8 2013-01-04 14:03:00 +00:00
Ross Lawley
e537369d98 Trying to get travis to build the 0.8 branch 2013-01-04 14:02:34 +00:00
Ross Lawley
9bbd8dbe62 Querysets now return clones and are no longer edit in place
Fixes #56
2013-01-04 09:41:08 +00:00
Ross Lawley
09a5f5c8f3 Added note for django and sites config issues 2013-01-03 12:56:42 +00:00
Ross Lawley
b9e0f52526 FileFields now copyable (#198) 2012-12-21 17:09:10 +00:00
Ross Lawley
1cdf71b647 Simplified Q objects
Removed QueryTreeTransformerVisitor (#98) (#171)
2012-12-21 16:35:09 +00:00
Ross Lawley
3aff461039 Fix test discovery 2012-12-21 16:29:27 +00:00
Ross Lawley
bf74d7537c Fix Django timezone support - update field for callable #151 2012-12-21 16:20:01 +00:00
Ross Lawley
0c2fb6807e Added Aleksey Porfirov to AUTHORS #151 2012-12-21 12:14:32 +00:00
Ross Lawley
b9c9d127a2 Merge branch 'fix_use_django_tz' of https://github.com/lexqt/mongoengine into 0.8
Conflicts:
	mongoengine/django/sessions.py
Closes #151
2012-12-21 12:10:54 +00:00
Ross Lawley
286beca6c5 Added Marcelo Anton to authors #152 2012-12-21 12:07:15 +00:00
Ross Lawley
3a1521a34e Merge branch 'master' of https://github.com/mbanton/mongoengine into 0.8 2012-12-21 12:06:37 +00:00
Ross Lawley
c5b047d0cd Fixed GridFSProxy __getattr__ behaviour (#196) 2012-12-21 11:55:05 +00:00
Ross Lawley
485b811bd0 Test case for embedded docs and 2d indexes #183 2012-12-19 17:05:27 +00:00
Ross Lawley
f335591045 Fix index build_spec #177 2012-12-19 16:55:14 +00:00
Ross Lawley
1c10f3020b Added support for multiple slices
Also made slicing chainable. (#170) (#190) (#191)
2012-12-19 14:56:15 +00:00
Ross Lawley
3074dad293 Test mixing only, include and exclude #191 2012-12-19 13:58:22 +00:00
Ross Lawley
42f506adc6 Updates to test suite 2012-12-19 13:58:04 +00:00
Ross Lawley
50b755db0c Split out queryset tests 2012-12-19 13:35:37 +00:00
Ross Lawley
420c3e0073 Fixing for python2.5
closes #188
2012-12-19 12:51:42 +00:00
Ross Lawley
4a57fc33e4 Merge branch 'master' into 0.8 2012-12-19 12:38:04 +00:00
Ross Lawley
25cdf16cc0 Updated travis 2012-12-19 12:37:37 +00:00
Ross Lawley
7f732459a1 Updated tickets links as now default to MongoEngine/mongoengine 2012-12-19 12:34:02 +00:00
Ross Lawley
9cc02d4dbe Dynamic fields are now validated on save
(MongoEngine/mongoengine#153) (MongoEngine/mongoengine#154)
2012-12-19 12:32:06 +00:00
Ross Lawley
c528ac09d6 Fix merge for QNode checks 2012-12-19 12:29:46 +00:00
Ross Lawley
1a131ff120 Only allow QNode instances to be passed as query objects (MongoEngine/mongoengine#199) 2012-12-19 12:16:12 +00:00
Ross Lawley
accdd82970 Merge branch 'feature-saferfilter' of https://github.com/elasticsales/mongoengine into 0.8 2012-12-19 11:47:46 +00:00
Ross Lawley
3e8f02c64b Merge sequence field changes 2012-12-19 11:39:19 +00:00
Ross Lawley
3425264077 Merge branch 'master' into 0.8
Conflicts:
	AUTHORS
	docs/changelog.rst
	mongoengine/__init__.py
	mongoengine/base.py
	mongoengine/fields.py
	python-mongoengine.spec
	tests/test_document.py
	tests/test_fields.py
	tests/test_queryset.py
2012-12-19 11:35:49 +00:00
Stefan Wojcik
148f8b8a3a Only allow QNode instances to be passed as query objects 2012-12-17 21:13:45 -08:00
Ross Lawley
f6f7c12f0e Added test case checking type with dbref=False
Ensures when dbref=False the data is stored as the same type
as the primary key of the item stored. MongoEngine/mongoengine#160
2012-11-27 14:37:13 +00:00
Ross Lawley
219b28c97b Updated docs regarding 3598fe0fb4
Fixed db_alias and inherited Documents (MongoEngine/mongoengine#143)
2012-11-27 14:04:57 +00:00
Ross Lawley
3598fe0fb4 Adding _collection to _cls 2012-11-27 14:02:50 +00:00
Ross Lawley
f9dd051ec9 Merged get_document fix 2012-11-27 14:02:50 +00:00
Ross Lawley
68e4a27aaf Fixed handling for old style types 2012-11-27 14:02:50 +00:00
Ross Lawley
b849c719a8 Adding some debugging 2012-11-27 14:02:50 +00:00
Ross Lawley
59e7617e82 Trying to fix seesaw test on travis 2012-11-27 14:02:49 +00:00
Ross Lawley
b5e868655e Updated travis.yml 2012-11-27 14:02:49 +00:00
Ross Lawley
027b3d36de Fixed deprecation warning 2012-11-27 14:01:58 +00:00
Ross Lawley
003454573c Making django user sparse (MongoEngine/mongoengine#128) 2012-11-21 17:14:53 +00:00
Ross Lawley
aa5a9ff1f4 Documentation update for document errors (MongoEngine/mongoengine#124) 2012-11-21 17:03:32 +00:00
Ross Lawley
28ef54986d Deprecated get_or_create (MongoEngine/mongoengine#35) 2012-11-21 16:54:27 +00:00
Ross Lawley
dfdc0d92c3 Updated docs 2012-11-08 16:40:58 +00:00
Ross Lawley
f265915aa2 Updated inheritable objects created by upsert now contain _cls (MongoEngine/mongoengine#118) 2012-11-08 16:35:20 +00:00
helduel
4228d06934 Merge branch 'getlasterror' of github.com:helduel/mongoengine into getlasterror
Conflicts:
	mongoengine/document.py
2012-11-08 16:41:18 +01:00
helduel
1a93b9b226 More precise "created" keyword argument signals
If a document has a user given id value, the post_save signal always got the
"created" keyword argument with False value (unless force_insert is True).

This patch uses the result of getlasterror to check whether the save was an
update or not.
2012-11-08 16:30:29 +01:00
Ross Lawley
363e50abbe Updated documents with embedded documents can be created in a single operation (MongoEngine/mongoengine#6) 2012-11-08 14:46:56 +00:00
Ross Lawley
b8d53a6f0d Added json serialisation support
- Added to_json and from_json to Document (MongoEngine/mongoengine#1)
- Added to_json and from_json to QuerySet (MongoEngine/mongoengine#131)
2012-11-08 12:04:14 +00:00
Ross Lawley
4b45c0cd14 Removed deprecation warning #55 2012-11-07 15:15:04 +00:00
Ross Lawley
e7c0da38c2 Better implementation for none - MongoEngine/mongoengine#127 2012-11-07 15:09:11 +00:00
Ross Lawley
8706fbe461 Updated index creation now tied to Document class ((MongoEngine/mongoengine#102) 2012-11-07 15:04:45 +00:00
Ross Lawley
9ca96e4e17 Added none() to queryset (MongoEngine/mongoengine#127) 2012-11-07 13:51:02 +00:00
Ross Lawley
99fe1da345 Add value_decorator into SequenceField
Allows post processing of the calculated counter value.
2012-11-07 13:20:34 +00:00
Ross Lawley
1986e82783 Added clean method to documents for pre validation data cleaning (MongoEngine/mongoengine#60) 2012-11-07 12:12:28 +00:00
Ross Lawley
7073b9d395 Added validation and tests 2012-11-06 18:55:14 +00:00
Samuel Clay
f2049e9c18 Adding QuerySet(read_preference=pymongo.ReadPreference.X) and QuerySet().read_preference() method to override connection-level read_preference on a per-query basis. 2012-11-06 18:55:13 +00:00
Ross Lawley
f0f1308465 Updated changelog 2012-11-06 16:06:54 +00:00
Ross Lawley
7d90aa76ff Add _instance to Embedded Documents
Fixes MongoEngine/mongoengine#139
2012-11-06 16:04:23 +00:00
Ross Lawley
3cc2c617fd Merge branch 'master' into 0.8
Conflicts:
	docs/changelog.rst
	mongoengine/__init__.py
2012-11-06 14:28:55 +00:00
Ross Lawley
3d5b6ae332 Inheritance is off by default (MongoEngine/mongoengine#122) 2012-10-22 19:29:26 +00:00
Marcelo Anton
59826c8cfd This change in how the variable is declared DESCRIPTION corrects problems
when running the command ``python setup.py bdist_rpm``
2012-10-18 11:44:18 -03:00
Ross Lawley
6f29d12386 Changed the inheritance model to remove types
The inheritance model has changed, we no longer need to store an array of
`types` with the model we can just use the classname in `_cls`.

See the upgrade docs for information on how to upgrade
MongoEngine/mongoengine#148
2012-10-15 13:48:02 +00:00
Aleksey Porfirov
0a89899ad0 Fix django timezone support 2012-10-15 02:13:52 +04:00
Aleksey Porfirov
e4af0e361a Add session expiration test (with django timezone support activated) 2012-10-15 02:11:01 +04:00
Luis Araujo
0bfc96e459 exposing mongoengine.django module 2012-09-27 14:32:50 -03:00
Luis Araujo
3425574ddc Adding, adjust and transplant more methods to auth.User model 2012-09-27 14:30:59 -03:00
Luis Araujo
6a31736644 Initial support to Group and Permission. The /admin can't be exec login in MongoDB yet. Only SQLsDB (SQLite,...)
This code work with django-mongoadmin pluggin.
2012-09-26 14:43:59 -03:00
Manuel Hermann
500eb920e4 Use info from getlasterror whether a document has been updated or created. 2012-08-07 17:04:03 +02:00
106 changed files with 31007 additions and 16338 deletions

5
.gitignore vendored
View File

@@ -14,4 +14,7 @@ env/
.project
.pydevproject
tests/test_bugfix.py
htmlcov/
htmlcov/
venv
venv3
scratchpad

View File

@@ -0,0 +1,23 @@
#!/bin/bash
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
if [ "$MONGODB" = "2.4" ]; then
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-10gen=2.4.14
sudo service mongodb start
elif [ "$MONGODB" = "2.6" ]; then
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-org-server=2.6.12
# service should be started automatically
elif [ "$MONGODB" = "3.0" ]; then
echo "deb http://repo.mongodb.org/apt/ubuntu precise/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-org-server=3.0.14
# service should be started automatically
else
echo "Invalid MongoDB version, expected 2.4, 2.6, or 3.0."
exit 1
fi;

22
.landscape.yml Normal file
View File

@@ -0,0 +1,22 @@
pylint:
disable:
# We use this a lot (e.g. via document._meta)
- protected-access
options:
additional-builtins:
# add xrange and long as valid built-ins. In Python 3, xrange is
# translated into range and long is translated into int via 2to3 (see
# "use_2to3" in setup.py). This should be removed when we drop Python
# 2 support (which probably won't happen any time soon).
- xrange
- long
pyflakes:
disable:
# undefined variables are already covered by pylint (and exclude
# xrange & long)
- F821
ignore-paths:
- benchmark.py

View File

@@ -1,29 +1,100 @@
# http://travis-ci.org/#!/MongoEngine/mongoengine
# For full coverage, we'd have to test all supported Python, MongoDB, and
# PyMongo combinations. However, that would result in an overly long build
# with a very large number of jobs, hence we only test a subset of all the
# combinations:
# * MongoDB v2.4 & v3.0 are only tested against Python v2.7 & v3.5.
# * MongoDB v2.4 is tested against PyMongo v2.7 & v3.x.
# * MongoDB v3.0 is tested against PyMongo v3.x.
# * MongoDB v2.6 is currently the "main" version tested against Python v2.7,
# v3.5, PyPy & PyPy3, and PyMongo v2.7, v2.8 & v3.x.
#
# Reminder: Update README.rst if you change MongoDB versions we test.
language: python
services: mongodb
python:
- 2.5
- 2.6
- 2.7
- 3.1
- 3.2
- 2.7
- 3.5
- pypy
env:
- PYMONGO=dev
- PYMONGO=2.3
- PYMONGO=2.2
- MONGODB=2.6 PYMONGO=2.7
- MONGODB=2.6 PYMONGO=2.8
- MONGODB=2.6 PYMONGO=3.0
matrix:
# Finish the build as soon as one job fails
fast_finish: true
include:
- python: 2.7
env: MONGODB=2.4 PYMONGO=2.7
- python: 2.7
env: MONGODB=2.4 PYMONGO=3.0
- python: 2.7
env: MONGODB=3.0 PYMONGO=3.0
- python: 3.5
env: MONGODB=2.4 PYMONGO=2.7
- python: 3.5
env: MONGODB=2.4 PYMONGO=3.0
- python: 3.5
env: MONGODB=3.0 PYMONGO=3.0
before_install:
- bash .install_mongodb_on_travis.sh
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
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
libtiff4-dev libjpeg8-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.5-dev tk8.5-dev
python-tk
- travis_retry pip install --upgrade pip
- travis_retry pip install coveralls
- travis_retry pip install flake8 flake8-import-order
- travis_retry pip install tox>=1.9
- travis_retry pip install "virtualenv<14.0.0" # virtualenv>=14.0.0 has dropped Python 3.2 support (and pypy3 is based on py32)
- travis_retry tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- -e test
# Cache dependencies installed via pip
cache: pip
# Run flake8 for py27
before_script:
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then flake8 .; else echo "flake8 only runs on py27"; fi
script:
- python setup.py test
- tox -e $(echo py$TRAVIS_PYTHON_VERSION-mg$PYMONGO | tr -d . | sed -e 's/pypypy/pypy/') -- --with-coverage
# For now only submit coveralls for Python v2.7. Python v3.x currently shows
# 0% coverage. That's caused by 'use_2to3', which builds the py3-compatible
# code in a separate dir and runs tests on that.
after_success:
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then coveralls --verbose; fi
notifications:
irc: "irc.freenode.org#mongoengine"
irc: irc.freenode.org#mongoengine
# Only run builds on the master branch and GitHub releases (tagged as vX.Y.Z)
branches:
only:
- master
- 0.8
- master
- /^v.*$/
# Whenever a new release is created via GitHub, publish it on PyPI.
deploy:
provider: pypi
user: the_drow
password:
secure: QMyatmWBnC6ZN3XLW2+fTBDU4LQcp1m/LjR2/0uamyeUzWKdlOoh/Wx5elOgLwt/8N9ppdPeG83ose1jOz69l5G0MUMjv8n/RIcMFSpCT59tGYqn3kh55b0cIZXFT9ar+5cxlif6a5rS72IHm5li7QQyxexJIII6Uxp0kpvUmek=
# create a source distribution and a pure python wheel for faster installs
distributions: "sdist bdist_wheel"
# only deploy on tagged commits (aka GitHub releases) and only for the
# parent repo's builds running Python 2.7 along with dev PyMongo (we run
# Travis against many different Python and PyMongo versions and we don't
# want the deploy to occur multiple times).
on:
tags: true
repo: MongoEngine/mongoengine
condition: "$PYMONGO = 3.0"
python: 2.7

127
AUTHORS
View File

@@ -12,12 +12,10 @@ Laine Herron https://github.com/LaineHerron
CONTRIBUTORS
Dervived from the git logs, inevitably incomplete but all of whom and others
Derived from the git logs, inevitably incomplete but all of whom and others
have submitted patches, reported bugs and generally helped make MongoEngine
that much better:
* Harry Marr
* Ross Lawley
* blackbrrr
* Florian Schlachter
* Vincent Driessen
@@ -25,7 +23,7 @@ that much better:
* flosch
* Deepak Thukral
* Colin Howe
* Wilson Júnior
* Wilson Júnior (https://github.com/wpjunior)
* Alistair Roche
* Dan Crosta
* Viktor Kerkez
@@ -77,7 +75,7 @@ that much better:
* Adam Parrish
* jpfarias
* jonrscott
* Alice Zoë Bevan-McGregor
* Alice Zoë Bevan-McGregor (https://github.com/amcgregor/)
* Stephen Young
* tkloc
* aid
@@ -121,10 +119,127 @@ that much better:
* Anton Kolechkin
* Sergey Nikitin
* psychogenic
* Stefan Wójcik
* Stefan Wójcik (https://github.com/wojcikstefan)
* dimonb
* Garry Polley
* James Slagle
* Adrian Scott
* Peter Teichman
* Jakub Kot
* Jorge Bastida
* Aleksandr Sorokoumov
* Yohan Graterol
* bool-dev
* Russ Weeks
* Paul Swartz
* Sundar Raman
* Benoit Louy
* Loic Raucy (https://github.com/lraucy)
* hellysmile
* Jaepil Jeong
* Daniil Sharou
* Pete Campton
* Martyn Smith
* Marcelo Anton
* Aleksey Porfirov (https://github.com/lexqt)
* Nicolas Trippar
* Manuel Hermann
* Gustavo Gawryszewski
* Max Countryman
* caitifbrito
* lcya86 刘春洋
* Martin Alderete (https://github.com/malderete)
* Nick Joyce
* Jared Forsyth
* Kenneth Falck
* Lukasz Balcerzak
* Nicolas Cortot
* Alex (https://github.com/kelsta)
* Jin Zhang
* Daniel Axtens
* Leo-Naeka
* Ryan Witt (https://github.com/ryanwitt)
* Jiequan (https://github.com/Jiequan)
* hensom (https://github.com/hensom)
* zhy0216 (https://github.com/zhy0216)
* istinspring (https://github.com/istinspring)
* Massimo Santini (https://github.com/mapio)
* Nigel McNie (https://github.com/nigelmcnie)
* ygbourhis (https://github.com/ygbourhis)
* Bob Dickinson (https://github.com/BobDickinson)
* Michael Bartnett (https://github.com/michaelbartnett)
* Alon Horev (https://github.com/alonho)
* Kelvin Hammond (https://github.com/kelvinhammond)
* Jatin Chopra (https://github.com/jatin)
* Paul Uithol (https://github.com/PaulUithol)
* Thom Knowles (https://github.com/fleat)
* Paul (https://github.com/squamous)
* Olivier Cortès (https://github.com/Karmak23)
* crazyzubr (https://github.com/crazyzubr)
* FrankSomething (https://github.com/FrankSomething)
* Alexandr Morozov (https://github.com/LK4D4)
* mishudark (https://github.com/mishudark)
* Joe Friedl (https://github.com/grampajoe)
* Daniel Ward (https://github.com/danielward)
* Aniket Deshpande (https://github.com/anicake)
* rfkrocktk (https://github.com/rfkrocktk)
* Gustavo Andrés Angulo (https://github.com/woakas)
* Dmytro Popovych (https://github.com/drudim)
* Tom (https://github.com/tomprimozic)
* j0hnsmith (https://github.com/j0hnsmith)
* Damien Churchill (https://github.com/damoxc)
* Jonathan Simon Prates (https://github.com/jonathansp)
* Thiago Papageorgiou (https://github.com/tmpapageorgiou)
* Omer Katz (https://github.com/thedrow)
* Falcon Dai (https://github.com/falcondai)
* Polyrabbit (https://github.com/polyrabbit)
* Sagiv Malihi (https://github.com/sagivmalihi)
* Dmitry Konishchev (https://github.com/KonishchevDmitry)
* Martyn Smith (https://github.com/martynsmith)
* Andrei Zbikowski (https://github.com/b1naryth1ef)
* Ronald van Rij (https://github.com/ronaldvanrij)
* François Schmidts (https://github.com/jaesivsm)
* Eric Plumb (https://github.com/professorplumb)
* Damien Churchill (https://github.com/damoxc)
* Aleksandr Sorokoumov (https://github.com/Gerrrr)
* Clay McClure (https://github.com/claymation)
* Bruno Rocha (https://github.com/rochacbruno)
* Norberto Leite (https://github.com/nleite)
* Bob Cribbs (https://github.com/bocribbz)
* Jay Shirley (https://github.com/jshirley)
* David Bordeynik (https://github.com/DavidBord)
* Axel Haustant (https://github.com/noirbizarre)
* David Czarnecki (https://github.com/czarneckid)
* Vyacheslav Murashkin (https://github.com/a4tunado)
* André Ericson https://github.com/aericson)
* Mikhail Moshnogorsky (https://github.com/mikhailmoshnogorsky)
* Diego Berrocal (https://github.com/cestdiego)
* Matthew Ellison (https://github.com/seglberg)
* Jimmy Shen (https://github.com/jimmyshen)
* J. Fernando Sánchez (https://github.com/balkian)
* Michael Chase (https://github.com/rxsegrxup)
* Eremeev Danil (https://github.com/elephanter)
* Catstyle Lee (https://github.com/Catstyle)
* Kiryl Yermakou (https://github.com/rma4ok)
* Matthieu Rigal (https://github.com/MRigal)
* Charanpal Dhanjal (https://github.com/charanpald)
* Emmanuel Leblond (https://github.com/touilleMan)
* Breeze.Kay (https://github.com/9nix00)
* Vicki Donchenko (https://github.com/kivistein)
* Emile Caron (https://github.com/emilecaron)
* Amit Lichtenberg (https://github.com/amitlicht)
* Gang Li (https://github.com/iici-gli)
* Lars Butler (https://github.com/larsbutler)
* George Macon (https://github.com/gmacon)
* Ashley Whetter (https://github.com/AWhetter)
* Paul-Armand Verhaegen (https://github.com/paularmand)
* Steven Rossiter (https://github.com/BeardedSteve)
* Luo Peng (https://github.com/RussellLuo)
* Bryan Bennett (https://github.com/bbenne10)
* Gilb's Gilb's (https://github.com/gilbsgilbs)
* Joshua Nedrud (https://github.com/Neurostack)
* Shu Shen (https://github.com/shushen)
* xiaost7 (https://github.com/xiaost7)
* Victor Varvaryuk
* Stanislav Kaledin (https://github.com/sallyruthstruik)
* Dmitry Yantsen (https://github.com/mrTable)

View File

@@ -14,13 +14,13 @@ 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
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
MongoEngine supports CPython 2.7 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.
@@ -29,24 +29,40 @@ Style Guide
-----------
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
including 4 space indents and 79 character line limits.
including 4 space indents. When possible we try to stick to 79 character line
limits. However, screens got bigger and an ORM has a strong focus on
readability and if it can help, we accept 119 as maximum line length, in a
similar way as `django does
<https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#python-style>`_
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.
and any pull requests are automatically tested. Any pull requests without
tests will take longer to be integrated and might be refused.
You may also submit a simple failing test as a pull request if you don't know
how to fix it, it will be easier for other people to work on it and it may get
fixed faster.
General Guidelines
------------------
- Avoid backward breaking changes if at all possible.
- If you *have* to introduce a breaking change, make it very clear in your
pull request's description. Also, describe how users of this package
should adapt to the breaking change in docs/upgrade.rst.
- 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``
running on the default port, then execute ``python setup.py nosetests``
from the cmd line to run the test suite).
- Add yourself to AUTHORS.rst :)
- Ensure tests pass on all supported Python, PyMongo, and MongoDB versions.
You can test various Python and PyMongo versions locally by executing
``tox``. For different MongoDB versions, you can rely on our automated
Travis tests.
- Add enhancements or problematic bug fixes to docs/changelog.rst.
- Add yourself to AUTHORS :)
Documentation
-------------
@@ -59,3 +75,8 @@ just make your changes to the inline documentation of the appropriate
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.
If you want to test your documentation changes locally, you need to install
the ``sphinx`` and ``sphinx_rtd_theme`` packages. Once these are installed,
go to the ``docs`` directory, run ``make html`` and inspect the updated docs
by running ``open _build/html/index.html``.

View File

@@ -1,4 +1,4 @@
Copyright (c) 2009-2012 See AUTHORS
Copyright (c) 2009 See AUTHORS
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation

View File

@@ -4,39 +4,72 @@ MongoEngine
:Info: MongoEngine is an ORM-like layer on top of PyMongo.
:Repository: https://github.com/MongoEngine/mongoengine
:Author: Harry Marr (http://github.com/hmarr)
:Maintainer: Ross Lawley (http://github.com/rozza)
:Maintainer: Stefan Wójcik (http://github.com/wojcikstefan)
.. image:: https://secure.travis-ci.org/MongoEngine/mongoengine.png?branch=master
:target: http://travis-ci.org/MongoEngine/mongoengine
.. image:: https://travis-ci.org/MongoEngine/mongoengine.svg?branch=master
:target: https://travis-ci.org/MongoEngine/mongoengine
.. image:: https://coveralls.io/repos/github/MongoEngine/mongoengine/badge.svg?branch=master
:target: https://coveralls.io/github/MongoEngine/mongoengine?branch=master
.. image:: https://landscape.io/github/MongoEngine/mongoengine/master/landscape.svg?style=flat
:target: https://landscape.io/github/MongoEngine/mongoengine/master
:alt: Code Health
About
=====
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
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
<https://mongoengine-odm.readthedocs.org/en/latest/guide/index.html>`_ and an `API reference
<http://readthedocs.org/docs/mongoengine-odm/en/latest/apireference.html>`_.
Documentation is available at https://mongoengine-odm.readthedocs.io - there
is currently a `tutorial <https://mongoengine-odm.readthedocs.io/tutorial.html>`_,
a `user guide <https://mongoengine-odm.readthedocs.io/guide/index.html>`_, and
an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
Supported MongoDB Versions
==========================
MongoEngine is currently tested against MongoDB v2.4, v2.6, and v3.0. Future
versions should be supported as well, but aren't actively tested at the moment.
Make sure to open an issue or submit a pull request if you experience any
problems with MongoDB v3.2+.
Installation
============
If you have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
you can use ``easy_install -U mongoengine``. Otherwise, you can download the
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
and thus you can use ``easy_install -U mongoengine``. Otherwise, you can download the
source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and run ``python
setup.py install``.
Dependencies
============
- pymongo 2.1.1+
- sphinx (optional - for documentation generation)
All of the dependencies can easily be installed via `pip <https://pip.pypa.io/>`_.
At the very least, you'll need these two packages to use MongoEngine:
- pymongo>=2.7.1
- six>=1.10.0
If you utilize a ``DateTimeField``, you might also use a more flexible date parser:
- dateutil>=2.1.0
If you need to use an ``ImageField`` or ``ImageGridFsProxy``:
- Pillow>=2.0.0
Examples
========
Some simple examples of what MongoEngine code looks like::
Some simple examples of what MongoEngine code looks like:
.. code :: python
from mongoengine import *
connect('mydb')
class BlogPost(Document):
title = StringField(required=True, max_length=200)
posted = DateTimeField(default=datetime.datetime.now)
posted = DateTimeField(default=datetime.datetime.utcnow)
tags = ListField(StringField(max_length=50))
meta = {'allow_inheritance': True}
class TextPost(BlogPost):
content = StringField(required=True)
@@ -64,23 +97,46 @@ Some simple examples of what MongoEngine code looks like::
... print
...
>>> len(BlogPost.objects)
# Count all blog posts and its subtypes
>>> BlogPost.objects.count()
2
>>> len(HtmlPost.objects)
>>> TextPost.objects.count()
1
>>> len(LinkPost.objects)
>>> LinkPost.objects.count()
1
# Find tagged posts
>>> len(BlogPost.objects(tags='mongoengine'))
# Count tagged posts
>>> BlogPost.objects(tags='mongoengine').count()
2
>>> len(BlogPost.objects(tags='mongodb'))
>>> BlogPost.objects(tags='mongodb').count()
1
Tests
=====
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 have ``nose`` installed. Then, run ``python setup.py nosetests``.
To run the test suite on every supported Python and PyMongo version, you can
use ``tox``. You'll need to make sure you have each supported Python version
installed in your environment and then:
.. code-block:: shell
# Install tox
$ pip install tox
# Run the test suites
$ tox
If you wish to run a subset of tests, use the nosetests convention:
.. code-block:: shell
# Run all the tests in a particular test file
$ python setup.py nosetests --tests tests/fields/fields.py
# Run only particular test class in that file
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest
# Use the -s option if you want to print some debug statements or use pdb
$ python setup.py nosetests --tests tests/fields/fields.py:FieldTest -s
Community
=========
@@ -88,8 +144,7 @@ Community
<http://groups.google.com/group/mongoengine-users>`_
- `MongoEngine Developers mailing list
<http://groups.google.com/group/mongoengine-dev>`_
- `#mongoengine IRC channel <http://webchat.freenode.net/?channels=mongoengine>`_
Contributing
============
We welcome contributions! see the`Contribution guidelines <https://github.com/MongoEngine/mongoengine/blob/master/CONTRIBUTING.rst>`_
We welcome contributions! See the `Contribution guidelines <https://github.com/MongoEngine/mongoengine/blob/master/CONTRIBUTING.rst>`_

View File

@@ -1,137 +1,111 @@
#!/usr/bin/env python
"""
Simple benchmark comparing PyMongo and MongoEngine.
Sample run on a mid 2015 MacBook Pro (commit b282511):
Benchmarking...
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
2.58979988098
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo write_concern={"w": 0}
1.26657605171
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
8.4351580143
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries without continual assign - MongoEngine
7.20191693306
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine - write_concern={"w": 0}, cascade = True
6.31104588509
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False, cascade=True
6.07083487511
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False
5.97704291344
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force_insert=True, write_concern={"w": 0}, validate=False
5.9111430645
"""
import timeit
def cprofile_main():
from pymongo import Connection
connection = Connection()
connection.drop_database('timeit_test')
connection.disconnect()
from mongoengine import Document, DictField, connect
connect("timeit_test")
class Noddy(Document):
fields = DictField()
for i in xrange(1):
noddy = Noddy()
for j in range(20):
noddy.fields["key" + str(j)] = "value " + str(j)
noddy.save()
def main():
"""
0.4 Performance Figures ...
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.86744189262
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
6.23374891281
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
5.33027005196
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
pass - No Cascade
0.5.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.89597702026
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
21.7735359669
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
19.8670389652
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
pass - No Cascade
0.6.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.81559205055
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
10.0446798801
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
9.51354718208
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
9.02567505836
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force=True
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
"""
print("Benchmarking...")
setup = """
from pymongo import Connection
connection = Connection()
from pymongo import MongoClient
connection = MongoClient()
connection.drop_database('timeit_test')
"""
stmt = """
from pymongo import Connection
connection = Connection()
from pymongo import MongoClient
connection = MongoClient()
db = connection.timeit_test
noddy = db.noddy
for i in xrange(10000):
for i in range(10000):
example = {'fields': {}}
for j in range(20):
example['fields']["key"+str(j)] = "value "+str(j)
example['fields']['key' + str(j)] = 'value ' + str(j)
noddy.insert(example)
noddy.save(example)
myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - Pymongo"""
print("-" * 100)
print("""Creating 10000 dictionaries - Pymongo""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
from pymongo import MongoClient
from pymongo.write_concern import WriteConcern
connection = MongoClient()
db = connection.get_database('timeit_test', write_concern=WriteConcern(w=0))
noddy = db.noddy
for i in range(10000):
example = {'fields': {}}
for j in range(20):
example['fields']["key"+str(j)] = "value "+str(j)
noddy.save(example)
myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print("-" * 100)
print("""Creating 10000 dictionaries - Pymongo write_concern={"w": 0}""")
t = timeit.Timer(stmt=stmt, setup=setup)
print(t.timeit(1))
setup = """
from pymongo import Connection
connection = Connection()
from pymongo import MongoClient
connection = MongoClient()
connection.drop_database('timeit_test')
connection.disconnect()
connection.close()
from mongoengine import Document, DictField, connect
connect("timeit_test")
connect('timeit_test')
class Noddy(Document):
fields = DictField()
"""
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
@@ -141,59 +115,93 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
fields = {}
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(safe=False, validate=False)
fields["key"+str(j)] = "value "+str(j)
noddy.fields = fields
noddy.save()
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, safe=False, validate=False"""
print("-" * 100)
print("""Creating 10000 dictionaries without continual assign - MongoEngine""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(safe=False, validate=False, cascade=False)
noddy.save(write_concern={"w": 0}, cascade=True)
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine - write_concern={"w": 0}, cascade = True""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(force_insert=True, safe=False, validate=False, cascade=False)
noddy.save(write_concern={"w": 0}, validate=False, cascade=True)
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, force=True"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False, cascade=True""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(validate=False, write_concern={"w": 0})
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False""")
t = timeit.Timer(stmt=stmt, setup=setup)
print(t.timeit(1))
stmt = """
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(force_insert=True, write_concern={"w": 0}, validate=False)
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine, force_insert=True, write_concern={"w": 0}, validate=False""")
t = timeit.Timer(stmt=stmt, setup=setup)
print(t.timeit(1))
if __name__ == "__main__":
main()

View File

@@ -1,229 +0,0 @@
/**
* Sphinx stylesheet -- default theme
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/
@import url("basic.css");
/* -- page layout ----------------------------------------------------------- */
body {
font-family: Arial, sans-serif;
font-size: 100%;
background-color: #111;
color: #555;
margin: 0;
padding: 0;
}
div.documentwrapper {
float: left;
width: 100%;
}
div.bodywrapper {
margin: 0 0 0 230px;
}
hr{
border: 1px solid #B1B4B6;
}
div.document {
background-color: #eee;
}
div.body {
background-color: #ffffff;
color: #3E4349;
padding: 0 30px 30px 30px;
font-size: 0.8em;
}
div.footer {
color: #555;
width: 100%;
padding: 13px 0;
text-align: center;
font-size: 75%;
}
div.footer a {
color: #444;
text-decoration: underline;
}
div.related {
background-color: #6BA81E;
line-height: 32px;
color: #fff;
text-shadow: 0px 1px 0 #444;
font-size: 0.80em;
}
div.related a {
color: #E2F3CC;
}
div.sphinxsidebar {
font-size: 0.75em;
line-height: 1.5em;
}
div.sphinxsidebarwrapper{
padding: 20px 0;
}
div.sphinxsidebar h3,
div.sphinxsidebar h4 {
font-family: Arial, sans-serif;
color: #222;
font-size: 1.2em;
font-weight: normal;
margin: 0;
padding: 5px 10px;
background-color: #ddd;
text-shadow: 1px 1px 0 white
}
div.sphinxsidebar h4{
font-size: 1.1em;
}
div.sphinxsidebar h3 a {
color: #444;
}
div.sphinxsidebar p {
color: #888;
padding: 5px 20px;
}
div.sphinxsidebar p.topless {
}
div.sphinxsidebar ul {
margin: 10px 20px;
padding: 0;
color: #000;
}
div.sphinxsidebar a {
color: #444;
}
div.sphinxsidebar input {
border: 1px solid #ccc;
font-family: sans-serif;
font-size: 1em;
}
div.sphinxsidebar input[type=text]{
margin-left: 20px;
}
/* -- body styles ----------------------------------------------------------- */
a {
color: #005B81;
text-decoration: none;
}
a:hover {
color: #E32E00;
text-decoration: underline;
}
div.body h1,
div.body h2,
div.body h3,
div.body h4,
div.body h5,
div.body h6 {
font-family: Arial, sans-serif;
background-color: #BED4EB;
font-weight: normal;
color: #212224;
margin: 30px 0px 10px 0px;
padding: 5px 0 5px 10px;
text-shadow: 0px 1px 0 white
}
div.body h1 { border-top: 20px solid white; margin-top: 0; font-size: 200%; }
div.body h2 { font-size: 150%; background-color: #C8D5E3; }
div.body h3 { font-size: 120%; background-color: #D8DEE3; }
div.body h4 { font-size: 110%; background-color: #D8DEE3; }
div.body h5 { font-size: 100%; background-color: #D8DEE3; }
div.body h6 { font-size: 100%; background-color: #D8DEE3; }
a.headerlink {
color: #c60f0f;
font-size: 0.8em;
padding: 0 4px 0 4px;
text-decoration: none;
}
a.headerlink:hover {
background-color: #c60f0f;
color: white;
}
div.body p, div.body dd, div.body li {
line-height: 1.5em;
}
div.admonition p.admonition-title + p {
display: inline;
}
div.highlight{
background-color: white;
}
div.note {
background-color: #eee;
border: 1px solid #ccc;
}
div.seealso {
background-color: #ffc;
border: 1px solid #ff6;
}
div.topic {
background-color: #eee;
}
div.warning {
background-color: #ffe4e4;
border: 1px solid #f66;
}
p.admonition-title {
display: inline;
}
p.admonition-title:after {
content: ":";
}
pre {
padding: 10px;
background-color: White;
color: #222;
line-height: 1.2em;
border: 1px solid #C6C9CB;
font-size: 1.2em;
margin: 1.5em 0 1.5em 0;
-webkit-box-shadow: 1px 1px 1px #d8d8d8;
-moz-box-shadow: 1px 1px 1px #d8d8d8;
}
tt {
background-color: #ecf0f3;
color: #222;
padding: 1px 2px;
font-size: 1.2em;
font-family: monospace;
}

View File

@@ -1,54 +0,0 @@
.c { color: #999988; font-style: italic } /* Comment */
.k { font-weight: bold } /* Keyword */
.o { font-weight: bold } /* Operator */
.cm { color: #999988; font-style: italic } /* Comment.Multiline */
.cp { color: #999999; font-weight: bold } /* Comment.preproc */
.c1 { color: #999988; font-style: italic } /* Comment.Single */
.gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.ge { font-style: italic } /* Generic.Emph */
.gr { color: #aa0000 } /* Generic.Error */
.gh { color: #999999 } /* Generic.Heading */
.gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.go { color: #111 } /* Generic.Output */
.gp { color: #555555 } /* Generic.Prompt */
.gs { font-weight: bold } /* Generic.Strong */
.gu { color: #aaaaaa } /* Generic.Subheading */
.gt { color: #aa0000 } /* Generic.Traceback */
.kc { font-weight: bold } /* Keyword.Constant */
.kd { font-weight: bold } /* Keyword.Declaration */
.kp { font-weight: bold } /* Keyword.Pseudo */
.kr { font-weight: bold } /* Keyword.Reserved */
.kt { color: #445588; font-weight: bold } /* Keyword.Type */
.m { color: #009999 } /* Literal.Number */
.s { color: #bb8844 } /* Literal.String */
.na { color: #008080 } /* Name.Attribute */
.nb { color: #999999 } /* Name.Builtin */
.nc { color: #445588; font-weight: bold } /* Name.Class */
.no { color: #ff99ff } /* Name.Constant */
.ni { color: #800080 } /* Name.Entity */
.ne { color: #990000; font-weight: bold } /* Name.Exception */
.nf { color: #990000; font-weight: bold } /* Name.Function */
.nn { color: #555555 } /* Name.Namespace */
.nt { color: #000080 } /* Name.Tag */
.nv { color: purple } /* Name.Variable */
.ow { font-weight: bold } /* Operator.Word */
.mf { color: #009999 } /* Literal.Number.Float */
.mh { color: #009999 } /* Literal.Number.Hex */
.mi { color: #009999 } /* Literal.Number.Integer */
.mo { color: #009999 } /* Literal.Number.Oct */
.sb { color: #bb8844 } /* Literal.String.Backtick */
.sc { color: #bb8844 } /* Literal.String.Char */
.sd { color: #bb8844 } /* Literal.String.Doc */
.s2 { color: #bb8844 } /* Literal.String.Double */
.se { color: #bb8844 } /* Literal.String.Escape */
.sh { color: #bb8844 } /* Literal.String.Heredoc */
.si { color: #bb8844 } /* Literal.String.Interpol */
.sx { color: #bb8844 } /* Literal.String.Other */
.sr { color: #808000 } /* Literal.String.Regex */
.s1 { color: #bb8844 } /* Literal.String.Single */
.ss { color: #bb8844 } /* Literal.String.Symbol */
.bp { color: #999999 } /* Name.Builtin.Pseudo */
.vc { color: #ff99ff } /* Name.Variable.Class */
.vg { color: #ff99ff } /* Name.Variable.Global */
.vi { color: #ff99ff } /* Name.Variable.Instance */
.il { color: #009999 } /* Literal.Number.Integer.Long */

View File

@@ -1,4 +0,0 @@
[theme]
inherit = basic
stylesheet = nature.css
pygments_style = tango

View File

@@ -34,41 +34,95 @@ Documents
.. autoclass:: mongoengine.ValidationError
:members:
.. autoclass:: mongoengine.FieldDoesNotExist
Context Managers
================
.. autoclass:: mongoengine.context_managers.switch_db
.. autoclass:: mongoengine.context_managers.switch_collection
.. autoclass:: mongoengine.context_managers.no_dereference
.. autoclass:: mongoengine.context_managers.query_counter
Querying
========
.. autoclass:: mongoengine.queryset.QuerySet
:members:
.. automodule:: mongoengine.queryset
:synopsis: Queryset level operations
.. automethod:: mongoengine.queryset.QuerySet.__call__
.. autoclass:: mongoengine.queryset.QuerySet
:members:
:inherited-members:
.. autofunction:: mongoengine.queryset.queryset_manager
.. automethod:: QuerySet.__call__
.. autoclass:: mongoengine.queryset.QuerySetNoCache
:members:
.. automethod:: mongoengine.queryset.QuerySetNoCache.__call__
.. autofunction:: mongoengine.queryset.queryset_manager
Fields
======
.. autoclass:: mongoengine.BinaryField
.. autoclass:: mongoengine.BooleanField
.. autoclass:: mongoengine.ComplexDateTimeField
.. autoclass:: mongoengine.DateTimeField
.. autoclass:: mongoengine.DecimalField
.. 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.ObjectIdField
.. autoclass:: mongoengine.ReferenceField
.. autoclass:: mongoengine.SequenceField
.. autoclass:: mongoengine.SortedListField
.. autoclass:: mongoengine.StringField
.. autoclass:: mongoengine.URLField
.. autoclass:: mongoengine.UUIDField
.. autoclass:: mongoengine.base.fields.BaseField
.. autoclass:: mongoengine.fields.StringField
.. autoclass:: mongoengine.fields.URLField
.. autoclass:: mongoengine.fields.EmailField
.. autoclass:: mongoengine.fields.IntField
.. autoclass:: mongoengine.fields.LongField
.. autoclass:: mongoengine.fields.FloatField
.. autoclass:: mongoengine.fields.DecimalField
.. autoclass:: mongoengine.fields.BooleanField
.. autoclass:: mongoengine.fields.DateTimeField
.. autoclass:: mongoengine.fields.ComplexDateTimeField
.. autoclass:: mongoengine.fields.EmbeddedDocumentField
.. autoclass:: mongoengine.fields.GenericEmbeddedDocumentField
.. autoclass:: mongoengine.fields.DynamicField
.. autoclass:: mongoengine.fields.ListField
.. autoclass:: mongoengine.fields.EmbeddedDocumentListField
.. autoclass:: mongoengine.fields.SortedListField
.. autoclass:: mongoengine.fields.DictField
.. autoclass:: mongoengine.fields.MapField
.. autoclass:: mongoengine.fields.ReferenceField
.. autoclass:: mongoengine.fields.GenericReferenceField
.. autoclass:: mongoengine.fields.CachedReferenceField
.. autoclass:: mongoengine.fields.BinaryField
.. autoclass:: mongoengine.fields.FileField
.. autoclass:: mongoengine.fields.ImageField
.. autoclass:: mongoengine.fields.SequenceField
.. autoclass:: mongoengine.fields.ObjectIdField
.. autoclass:: mongoengine.fields.UUIDField
.. autoclass:: mongoengine.fields.GeoPointField
.. autoclass:: mongoengine.fields.PointField
.. autoclass:: mongoengine.fields.LineStringField
.. autoclass:: mongoengine.fields.PolygonField
.. autoclass:: mongoengine.fields.MultiPointField
.. autoclass:: mongoengine.fields.MultiLineStringField
.. autoclass:: mongoengine.fields.MultiPolygonField
.. autoclass:: mongoengine.fields.GridFSError
.. autoclass:: mongoengine.fields.GridFSProxy
.. autoclass:: mongoengine.fields.ImageGridFsProxy
.. autoclass:: mongoengine.fields.ImproperlyConfigured
Embedded Document Querying
==========================
.. versionadded:: 0.9
Additional queries for Embedded Documents are available when using the
:class:`~mongoengine.EmbeddedDocumentListField` to store a list of embedded
documents.
A list of embedded documents is returned as a special list with the
following methods:
.. autoclass:: mongoengine.base.datastructures.EmbeddedDocumentList
:members:
Misc
====
.. autofunction:: mongoengine.common._import_class

View File

@@ -2,6 +2,425 @@
Changelog
=========
Development
===========
- (Fill this out as you fix issues and develop your features).
Changes in 0.14.0
=================
- BREAKING CHANGE: Removed the `coerce_types` param from `QuerySet.as_pymongo` #1549
- POTENTIAL BREAKING CHANGE: Made EmbeddedDocument not hashable by default #1528
- Improved code quality #1531, #1540, #1541, #1547
Changes in 0.13.0
=================
- POTENTIAL BREAKING CHANGE: Added Unicode support to the `EmailField`, see
docs/upgrade.rst for details.
Changes in 0.12.0
=================
- POTENTIAL BREAKING CHANGE: Fixed limit/skip/hint/batch_size chaining #1476
- POTENTIAL BREAKING CHANGE: Changed a public `QuerySet.clone_into` method to a private `QuerySet._clone_into` #1476
- Fixed the way `Document.objects.create` works with duplicate IDs #1485
- Fixed connecting to a replica set with PyMongo 2.x #1436
- Fixed using sets in field choices #1481
- Fixed deleting items from a `ListField` #1318
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
- Fixed behavior of a `dec` update operator #1450
- Added a `rename` update operator #1454
- Added validation for the `db_field` parameter #1448
- Fixed the error message displayed when querying an `EmbeddedDocumentField` by an invalid value #1440
- Fixed the error message displayed when validating unicode URLs #1486
- Raise an error when trying to save an abstract document #1449
Changes in 0.11.0
=================
- BREAKING CHANGE: Renamed `ConnectionError` to `MongoEngineConnectionError` since the former is a built-in exception name in Python v3.x. #1428
- BREAKING CHANGE: Dropped Python 2.6 support. #1428
- BREAKING CHANGE: `from mongoengine.base import ErrorClass` won't work anymore for any error from `mongoengine.errors` (e.g. `ValidationError`). Use `from mongoengine.errors import ErrorClass instead`. #1428
- BREAKING CHANGE: Accessing a broken reference will raise a `DoesNotExist` error. In the past it used to return `None`. #1334
- Fixed absent rounding for DecimalField when `force_string` is set. #1103
Changes in 0.10.8
=================
- Added support for QuerySet.batch_size (#1426)
- Fixed query set iteration within iteration #1427
- Fixed an issue where specifying a MongoDB URI host would override more information than it should #1421
- Added ability to filter the generic reference field by ObjectId and DBRef #1425
- Fixed delete cascade for models with a custom primary key field #1247
- Added ability to specify an authentication mechanism (e.g. X.509) #1333
- Added support for falsey primary keys (e.g. doc.pk = 0) #1354
- Fixed QuerySet#sum/average for fields w/ explicit db_field #1417
- Fixed filtering by embedded_doc=None #1422
- Added support for cursor.comment #1420
- Fixed doc.get_<field>_display #1419
- Fixed __repr__ method of the StrictDict #1424
- Added a deprecation warning for Python 2.6
Changes in 0.10.7
=================
- Dropped Python 3.2 support #1390
- Fixed the bug where dynamic doc has index inside a dict field #1278
- Fixed: ListField minus index assignment does not work #1128
- Fixed cascade delete mixing among collections #1224
- Add `signal_kwargs` argument to `Document.save`, `Document.delete` and `BaseQuerySet.insert` to be passed to signals calls #1206
- Raise `OperationError` when trying to do a `drop_collection` on document with no collection set.
- count on ListField of EmbeddedDocumentField fails. #1187
- Fixed long fields stored as int32 in Python 3. #1253
- MapField now handles unicodes keys correctly. #1267
- ListField now handles negative indicies correctly. #1270
- Fixed AttributeError when initializing EmbeddedDocument with positional args. #681
- Fixed no_cursor_timeout error with pymongo 3.0+ #1304
- Replaced map-reduce based QuerySet.sum/average with aggregation-based implementations #1336
- Fixed support for `__` to escape field names that match operators names in `update` #1351
- Fixed BaseDocument#_mark_as_changed #1369
- Added support for pickling QuerySet instances. #1397
- Fixed connecting to a list of hosts #1389
- Fixed a bug where accessing broken references wouldn't raise a DoesNotExist error #1334
- Fixed not being able to specify use_db_field=False on ListField(EmbeddedDocumentField) instances #1218
- Improvements to the dictionary fields docs #1383
Changes in 0.10.6
=================
- Add support for mocking MongoEngine based on mongomock. #1151
- Fixed not being able to run tests on Windows. #1153
- Allow creation of sparse compound indexes. #1114
- count on ListField of EmbeddedDocumentField fails. #1187
Changes in 0.10.5
=================
- Fix for reloading of strict with special fields. #1156
Changes in 0.10.4
=================
- SaveConditionError is now importable from the top level package. #1165
- upsert_one method added. #1157
Changes in 0.10.3
=================
- Fix `read_preference` (it had chaining issues with PyMongo 2.x and it didn't work at all with PyMongo 3.x) #1042
Changes in 0.10.2
=================
- Allow shard key to point to a field in an embedded document. #551
- Allow arbirary metadata in fields. #1129
- ReferenceFields now support abstract document types. #837
Changes in 0.10.1
=================
- Fix infinite recursion with CASCADE delete rules under specific conditions. #1046
- Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047
- Fix ignored chained options #842
- Document save's save_condition error raises `SaveConditionError` exception #1070
- Fix Document.reload for DynamicDocument. #1050
- StrictDict & SemiStrictDict are shadowed at init time. #1105
- Fix ListField minus index assignment does not work. #1119
- Remove code that marks field as changed when the field has default but not existed in database #1126
- Remove test dependencies (nose and rednose) from install dependencies list. #1079
- Recursively build query when using elemMatch operator. #1130
- Fix instance back references for lists of embedded documents. #1131
Changes in 0.10.0
=================
- Django support was removed and will be available as a separate extension. #958
- Allow to load undeclared field with meta attribute 'strict': False #957
- Support for PyMongo 3+ #946
- Removed get_or_create() deprecated since 0.8.0. #300
- Improve Document._created status when switch collection and db #1020
- Queryset update doesn't go through field validation #453
- Added support for specifying authentication source as option `authSource` in URI. #967
- Fixed mark_as_changed to handle higher/lower level fields changed. #927
- ListField of embedded docs doesn't set the _instance attribute when iterating over it #914
- Support += and *= for ListField #595
- Use sets for populating dbrefs to dereference
- Fixed unpickled documents replacing the global field's list. #888
- Fixed storage of microseconds in ComplexDateTimeField and unused separator option. #910
- Don't send a "cls" option to ensureIndex (related to https://jira.mongodb.org/browse/SERVER-769)
- Fix for updating sorting in SortedListField. #978
- Added __ support to escape field name in fields lookup keywords that match operators names #949
- Fix for issue where FileField deletion did not free space in GridFS.
- No_dereference() not respected on embedded docs containing reference. #517
- Document save raise an exception if save_condition fails #1005
- Fixes some internal _id handling issue. #961
- Updated URL and Email Field regex validators, added schemes argument to URLField validation. #652
- Capped collection multiple of 256. #1011
- Added `BaseQuerySet.aggregate_sum` and `BaseQuerySet.aggregate_average` methods.
- Fix for delete with write_concern {'w': 0}. #1008
- Allow dynamic lookup for more than two parts. #882
- Added support for min_distance on geo queries. #831
- Allow to add custom metadata to fields #705
Changes in 0.9.0
================
- Update FileField when creating a new file #714
- Added `EmbeddedDocumentListField` for Lists of Embedded Documents. #826
- ComplexDateTimeField should fall back to None when null=True #864
- Request Support for $min, $max Field update operators #863
- `BaseDict` does not follow `setdefault` #866
- Add support for $type operator # 766
- Fix tests for pymongo 2.8+ #877
- No module named 'django.utils.importlib' (Django dev) #872
- Field Choices Now Accept Subclasses of Documents
- Ensure Indexes before Each Save #812
- Generate Unique Indices for Lists of EmbeddedDocuments #358
- Sparse fields #515
- write_concern not in params of Collection#remove #801
- Better BaseDocument equality check when not saved #798
- OperationError: Shard Keys are immutable. Tried to update id even though the document is not yet saved #771
- with_limit_and_skip for count should default like in pymongo #759
- Fix storing value of precision attribute in DecimalField #787
- Set attribute to None does not work (at least for fields with default values) #734
- Querying by a field defined in a subclass raises InvalidQueryError #744
- Add Support For MongoDB 2.6.X's maxTimeMS #778
- abstract shouldn't be inherited in EmbeddedDocument # 789
- Allow specifying the '_cls' as a field for indexes #397
- Stop ensure_indexes running on a secondaries unless connection is through mongos #746
- Not overriding default values when loading a subset of fields #399
- Saving document doesn't create new fields in existing collection #620
- Added `Queryset.aggregate` wrapper to aggregation framework #703
- Added support to show original model fields on to_json calls instead of db_field #697
- Added Queryset.search_text to Text indexes searchs #700
- Fixed tests for Django 1.7 #696
- Follow ReferenceFields in EmbeddedDocuments with select_related #690
- Added preliminary support for text indexes #680
- Added `elemMatch` operator as well - `match` is too obscure #653
- Added support for progressive JPEG #486 #548
- Allow strings to be used in index creation #675
- Fixed EmbeddedDoc weakref proxy issue #592
- Fixed nested reference field distinct error #583
- Fixed change tracking on nested MapFields #539
- Dynamic fields in embedded documents now visible to queryset.only() / qs.exclude() #425 #507
- Add authentication_source option to register_connection #178 #464 #573 #580 #590
- Implemented equality between Documents and DBRefs #597
- Fixed ReferenceField inside nested ListFields dereferencing problem #368
- Added the ability to reload specific document fields #100
- Added db_alias support and fixes for custom map/reduce output #586
- post_save signal now has access to delta information about field changes #594 #589
- Don't query with $orderby for qs.get() #600
- Fix id shard key save issue #636
- Fixes issue with recursive embedded document errors #557
- Fix clear_changed_fields() clearing unsaved documents bug #602
- Removing support for Django 1.4.x, pymongo 2.5.x, pymongo 2.6.x.
- Removing support for Python < 2.6.6
- Fixed $maxDistance location for geoJSON $near queries with MongoDB 2.6+ #664
- QuerySet.modify() and Document.modify() methods to provide find_and_modify() like behaviour #677 #773
- Added support for the using() method on a queryset #676
- PYPY support #673
- Connection pooling #674
- Avoid to open all documents from cursors in an if stmt #655
- Ability to clear the ordering #657
- Raise NotUniqueError in Document.update() on pymongo.errors.DuplicateKeyError #626
- Slots - memory improvements #625
- Fixed incorrectly split a query key when it ends with "_" #619
- Geo docs updates #613
- Workaround a dateutil bug #608
- Conditional save for atomic-style operations #511
- Allow dynamic dictionary-style field access #559
- Increase email field length to accommodate new TLDs #726
- index_cls is ignored when deciding to set _cls as index prefix #733
- Make 'db' argument to connection optional #737
- Allow atomic update for the entire `DictField` #742
- Added MultiPointField, MultiLineField, MultiPolygonField
- Fix multiple connections aliases being rewritten #748
- Fixed a few instances where reverse_delete_rule was written as reverse_delete_rules. #791
- Make `in_bulk()` respect `no_dereference()` #775
- Handle None from model __str__; Fixes #753 #754
- _get_changed_fields fix for embedded documents with id field. #925
Changes in 0.8.7
================
- Calling reload on deleted / nonexistent documents raises DoesNotExist (#538)
- Stop ensure_indexes running on a secondaries (#555)
- Fix circular import issue with django auth (#531) (#545)
Changes in 0.8.6
================
- Fix django auth import (#531)
Changes in 0.8.5
================
- Fix multi level nested fields getting marked as changed (#523)
- Django 1.6 login fix (#522) (#527)
- Django 1.6 session fix (#509)
- EmbeddedDocument._instance is now set when setting the attribute (#506)
- Fixed EmbeddedDocument with ReferenceField equality issue (#502)
- Fixed GenericReferenceField serialization order (#499)
- Fixed count and none bug (#498)
- Fixed bug with .only() and DictField with digit keys (#496)
- Added user_permissions to Django User object (#491, #492)
- Fix updating Geo Location fields (#488)
- Fix handling invalid dict field value (#485)
- Added app_label to MongoUser (#484)
- Use defaults when host and port are passed as None (#483)
- Fixed distinct casting issue with ListField of EmbeddedDocuments (#470)
- Fixed Django 1.6 sessions (#454, #480)
Changes in 0.8.4
================
- Remove database name necessity in uri connection schema (#452)
- Fixed "$pull" semantics for nested ListFields (#447)
- Allow fields to be named the same as query operators (#445)
- Updated field filter logic - can now exclude subclass fields (#443)
- Fixed dereference issue with embedded listfield referencefields (#439)
- Fixed slice when using inheritance causing fields to be excluded (#437)
- Fixed ._get_db() attribute after a Document.switch_db() (#441)
- Dynamic Fields store and recompose Embedded Documents / Documents correctly (#449)
- Handle dynamic fieldnames that look like digits (#434)
- Added get_user_document and improve mongo_auth module (#423)
- Added str representation of GridFSProxy (#424)
- Update transform to handle docs erroneously passed to unset (#416)
- Fixed indexing - turn off _cls (#414)
- Fixed dereference threading issue in ComplexField.__get__ (#412)
- Fixed QuerySetNoCache.count() caching (#410)
- Don't follow references in _get_changed_fields (#422, #417)
- Allow args and kwargs to be passed through to_json (#420)
Changes in 0.8.3
================
- Fixed EmbeddedDocuments with `id` also storing `_id` (#402)
- Added get_proxy_object helper to filefields (#391)
- Added QuerySetNoCache and QuerySet.no_cache() for lower memory consumption (#365)
- Fixed sum and average mapreduce dot notation support (#375, #376, #393)
- Fixed as_pymongo to return the id (#386)
- Document.select_related() now respects `db_alias` (#377)
- Reload uses shard_key if applicable (#384)
- Dynamic fields are ordered based on creation and stored in _fields_ordered (#396)
**Potential breaking change:** http://docs.mongoengine.org/en/latest/upgrade.html#to-0-8-3
- Fixed pickling dynamic documents `_dynamic_fields` (#387)
- Fixed ListField setslice and delslice dirty tracking (#390)
- Added Django 1.5 PY3 support (#392)
- Added match ($elemMatch) support for EmbeddedDocuments (#379)
- Fixed weakref being valid after reload (#374)
- Fixed queryset.get() respecting no_dereference (#373)
- Added full_result kwarg to update (#380)
Changes in 0.8.2
================
- Added compare_indexes helper (#361)
- Fixed cascading saves which weren't turned off as planned (#291)
- Fixed Datastructures so instances are a Document or EmbeddedDocument (#363)
- Improved cascading saves write performance (#361)
- Fixed ambiguity and differing behaviour regarding field defaults (#349)
- ImageFields now include PIL error messages if invalid error (#353)
- Added lock when calling doc.Delete() for when signals have no sender (#350)
- Reload forces read preference to be PRIMARY (#355)
- Querysets are now lest restrictive when querying duplicate fields (#332, #333)
- FileField now honouring db_alias (#341)
- Removed customised __set__ change tracking in ComplexBaseField (#344)
- Removed unused var in _get_changed_fields (#347)
- Added pre_save_post_validation signal (#345)
- DateTimeField now auto converts valid datetime isostrings into dates (#343)
- DateTimeField now uses dateutil for parsing if available (#343)
- Fixed Doc.objects(read_preference=X) not setting read preference (#352)
- Django session ttl index expiry fixed (#329)
- Fixed pickle.loads (#342)
- Documentation fixes
Changes in 0.8.1
================
- Fixed Python 2.6 django auth importlib issue (#326)
- Fixed pickle unsaved document regression (#327)
Changes in 0.8.0
================
- Fixed querying ReferenceField custom_id (#317)
- Fixed pickle issues with collections (#316)
- Added `get_next_value` preview for SequenceFields (#319)
- Added no_sub_classes context manager and queryset helper (#312)
- Querysets now utilises a local cache
- Changed __len__ behaviour in the queryset (#247, #311)
- Fixed querying string versions of ObjectIds issue with ReferenceField (#307)
- Added $setOnInsert support for upserts (#308)
- Upserts now possible with just query parameters (#309)
- Upserting is the only way to ensure docs are saved correctly (#306)
- Fixed register_delete_rule inheritance issue
- Fix cloning of sliced querysets (#303)
- Fixed update_one write concern (#302)
- Updated minimum requirement for pymongo to 2.5
- Add support for new geojson fields, indexes and queries (#299)
- If values cant be compared mark as changed (#287)
- Ensure as_pymongo() and to_json honour only() and exclude() (#293)
- Document serialization uses field order to ensure a strict order is set (#296)
- DecimalField now stores as float not string (#289)
- UUIDField now stores as a binary by default (#292)
- Added Custom User Model for Django 1.5 (#285)
- Cascading saves now default to off (#291)
- ReferenceField now store ObjectId's by default rather than DBRef (#290)
- Added ImageField support for inline replacements (#86)
- Added SequenceField.set_next_value(value) helper (#159)
- Updated .only() behaviour - now like exclude it is chainable (#202)
- Added with_limit_and_skip support to count() (#235)
- Objects queryset manager now inherited (#256)
- Updated connection to use MongoClient (#262, #274)
- Fixed db_alias and inherited Documents (#143)
- Documentation update for document errors (#124)
- Deprecated `get_or_create` (#35)
- Updated inheritable objects created by upsert now contain _cls (#118)
- Added support for creating documents with embedded documents in a single operation (#6)
- Added to_json and from_json to Document (#1)
- Added to_json and from_json to QuerySet (#131)
- Updated index creation now tied to Document class (#102)
- Added none() to queryset (#127)
- Updated SequenceFields to allow post processing of the calculated counter value (#141)
- Added clean method to documents for pre validation data cleaning (#60)
- Added support setting for read prefrence at a query level (#157)
- Added _instance to EmbeddedDocuments pointing to the parent (#139)
- Inheritance is off by default (#122)
- Remove _types and just use _cls for inheritance (#148)
- Only allow QNode instances to be passed as query objects (#199)
- Dynamic fields are now validated on save (#153) (#154)
- Added support for multiple slices and made slicing chainable. (#170) (#190) (#191)
- Fixed GridFSProxy __getattr__ behaviour (#196)
- Fix Django timezone support (#151)
- Simplified Q objects, removed QueryTreeTransformerVisitor (#98) (#171)
- FileFields now copyable (#198)
- Querysets now return clones and are no longer edit in place (#56)
- Added support for $maxDistance (#179)
- Uses getlasterror to test created on updated saves (#163)
- Fixed inheritance and unique index creation (#140)
- Fixed reverse delete rule with inheritance (#197)
- Fixed validation for GenericReferences which haven't been dereferenced
- Added switch_db context manager (#106)
- Added switch_db method to document instances (#106)
- Added no_dereference context manager (#82) (#61)
- Added switch_collection context manager (#220)
- Added switch_collection method to document instances (#220)
- Added support for compound primary keys (#149) (#121)
- Fixed overriding objects with custom manager (#58)
- Added no_dereference method for querysets (#82) (#61)
- Undefined data should not override instance methods (#49)
- Added Django Group and Permission (#142)
- Added Doc class and pk to Validation messages (#69)
- Fixed Documents deleted via a queryset don't call any signals (#105)
- Added the "get_decoded" method to the MongoSession class (#216)
- Fixed invalid choices error bubbling (#214)
- Updated Save so it calls $set and $unset in a single operation (#211)
- Fixed inner queryset looping (#204)
Changes in 0.7.10
=================
- Fix UnicodeEncodeError for dbref (#278)
- Allow construction using positional parameters (#268)
- Updated EmailField length to support long domains (#243)
- Added 64-bit integer support (#251)
- Added Django sessions TTL support (#224)
- Fixed issue with numerical keys in MapField(EmbeddedDocumentField()) (#240)
- Fixed clearing _changed_fields for complex nested embedded documents (#237, #239, #242)
- Added "id" back to _data dictionary (#255)
- Only mark a field as changed if the value has changed (#258)
- Explicitly check for Document instances when dereferencing (#261)
- Fixed order_by chaining issue (#265)
- Added dereference support for tuples (#250)
- Resolve field name to db field name when using distinct(#260, #264, #269)
- Added kwargs to doc.save to help interop with django (#223, #270)
- Fixed cloning querysets in PY3
- Int fields no longer unset in save when changed to 0 (#272)
- Fixed ReferenceField query chaining bug fixed (#254)
Changes in 0.7.9
================
- Better fix handling for old style _types
@@ -9,12 +428,12 @@ Changes in 0.7.9
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)
- Fix sequence fields in embedded documents (#166)
- Fix query chaining with .order_by() (#176)
- Added optional encoding and collection config for Django sessions (#180, #181, #183)
- Fixed EmailField so can add extra validation (#173, #174, #187)
- Fixed bulk inserts can now handle custom pk's (#192)
- Added as_pymongo method to return raw or cast results from pymongo (#193)
Changes in 0.7.7
================
@@ -22,70 +441,70 @@ Changes in 0.7.7
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)
- Unicode fix for repr (#133)
- Allow updates with match operators (#144)
- Updated URLField - now can have a override the regex (#136)
- Allow Django AuthenticationBackends to work with Django user (hmarr/mongoengine#573)
- Fixed reload issue with ReferenceField where dbref=False (MongoEngine/mongoengine#138)
- Fixed reload issue with ReferenceField where dbref=False (#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)
- ReferenceFields with dbref=False use ObjectId instead of strings (#134)
See ticket for upgrade notes (#134)
Changes in 0.7.4
================
- Fixed index inheritance issues - firmed up testcases (MongoEngine/mongoengine#123) (MongoEngine/mongoengine#125)
- Fixed index inheritance issues - firmed up testcases (#123) (#125)
Changes in 0.7.3
================
- Reverted EmbeddedDocuments meta handling - now can turn off inheritance (MongoEngine/mongoengine#119)
- Reverted EmbeddedDocuments meta handling - now can turn off inheritance (#119)
Changes in 0.7.2
================
- Update index spec generation so its not destructive (MongoEngine/mongoengine#113)
- Update index spec generation so its not destructive (#113)
Changes in 0.7.1
=================
- Fixed index spec inheritance (MongoEngine/mongoengine#111)
================
- Fixed index spec inheritance (#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)
================
- Updated queryset.delete so you can use with skip / limit (#107)
- Updated index creation allows kwargs to be passed through refs (#104)
- Fixed Q object merge edge case (#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)
- Added NotUniqueError for duplicate keys (#62)
- Added custom collection / sequence naming for SequenceFields (#92)
- Fixed UnboundLocalError in composite index with pk field (#88)
- Updated ReferenceField's to optionally store ObjectId strings
this will become the default in 0.8 (MongoEngine/mongoengine#89)
this will become the default in 0.8 (#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)
- Added example of indexing embedded document fields (#75)
- Fixed ImageField resizing when forcing size (#80)
- Add flexibility for fields handling bad data (#78)
- Embedded Documents no longer handle meta definitions
- Use weakref proxies in base lists / dicts (MongoEngine/mongoengine#74)
- Use weakref proxies in base lists / dicts (#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 abstract classes and shard keys (#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)
- Added support for distinct and db_alias (#59)
- Improved support for chained querysets when constraining the same fields (hmarr/mongoengine#554)
- Fixed BinaryField lookup re (MongoEngine/mongoengine#48)
- Fixed BinaryField lookup re (#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)
- Added Binary support to UUID (#47)
- Fixed MapField lookup for fields without declared lookups (#46)
- Fixed BinaryField python value issue (#48)
- Fixed SequenceField non numeric value lookup (#41)
- Fixed queryset manager issue (#52)
- Fixed FileField comparision (hmarr/mongoengine#547)
Changes in 0.6.18
@@ -131,7 +550,7 @@ Changes in 0.6.12
- 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
@@ -211,7 +630,7 @@ Changes in 0.6.1
- Fix for replicaSet connections
Changes in 0.6
================
==============
- Added FutureWarning to inherited classes not declaring 'allow_inheritance' as the default will change in 0.7
- Added support for covered indexes when inheritance is off
@@ -299,8 +718,8 @@ Changes in v0.5
- Updated default collection naming convention
- Added Document Mixin support
- Fixed queryet __repr__ mid iteration
- Added hint() support, so cantell Mongo the proper index to use for the query
- Fixed issue with inconsitent setting of _cls breaking inherited referencing
- Added hint() support, so can tell Mongo the proper index to use for the query
- Fixed issue with inconsistent setting of _cls breaking inherited referencing
- Added help_text and verbose_name to fields to help with some form libs
- Updated item_frequencies to handle embedded document lookups
- Added delta tracking now only sets / unsets explicitly changed fields

View File

@@ -17,6 +17,10 @@ class Post(Document):
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
# bugfix
meta = {'allow_inheritance': True}
class TextPost(Post):
content = StringField()
@@ -45,7 +49,8 @@ print 'ALL POSTS'
print
for post in Post.objects:
print post.title
print '=' * len(post.title)
#print '=' * post.title.count()
print "=" * 20
if isinstance(post, TextPost):
print post.content

View File

@@ -13,10 +13,14 @@
import sys, os
import sphinx_rtd_theme
import mongoengine
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.append(os.path.abspath('..'))
sys.path.insert(0, os.path.abspath('..'))
# -- General configuration -----------------------------------------------------
@@ -38,13 +42,12 @@ master_doc = 'index'
# General information about the project.
project = u'MongoEngine'
copyright = u'2009-2012, MongoEngine Authors'
copyright = u'2009, MongoEngine Authors'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
import mongoengine
# The short X.Y version.
version = mongoengine.get_version()
# The full version, including alpha/beta/rc tags.
@@ -92,15 +95,17 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'nature'
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
html_theme_options = {
'canonical_url': 'http://docs.mongoengine.org/en/latest/'
}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@@ -116,7 +121,7 @@ html_theme_path = ['_themes']
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
html_favicon = "favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -132,7 +137,11 @@ html_theme_path = ['_themes']
html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
html_sidebars = {
'index': ['globaltoc.html', 'searchbox.html'],
'**': ['localtoc.html', 'relations.html', 'searchbox.html']
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
@@ -173,8 +182,8 @@ latex_paper_size = 'a4'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'MongoEngine.tex', u'MongoEngine Documentation',
u'Harry Marr', 'manual'),
('index', 'MongoEngine.tex', 'MongoEngine Documentation',
'Ross Lawley', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
@@ -193,3 +202,5 @@ latex_documents = [
# If false, no module index is generated.
#latex_use_modindex = True
autoclass_content = 'both'

View File

@@ -1,90 +1,19 @@
=============================
Using MongoEngine with Django
=============================
.. note :: Updated to support Django 1.4
Connecting
==========
In your **settings.py** file, ignore the standard database settings (unless you
also plan to use the ORM in your project), and instead call
:func:`~mongoengine.connect` somewhere in the settings module.
Authentication
==============
MongoEngine includes a Django authentication backend, which uses MongoDB. The
:class:`~mongoengine.django.auth.User` model is a MongoEngine
:class:`~mongoengine.Document`, but implements most of the methods and
attributes that the standard Django :class:`User` model does - so the two are
moderately compatible. Using this backend will allow you to store users in
MongoDB but still use many of the Django authentication infrastucture (such as
the :func:`login_required` decorator and the :func:`authenticate` function). To
enable the MongoEngine auth backend, add the following to you **settings.py**
file::
Django Support
==============
AUTHENTICATION_BACKENDS = (
'mongoengine.django.auth.MongoEngineBackend',
)
.. note:: Django support has been split from the main MongoEngine
repository. The *legacy* Django extension may be found bundled with the
0.9 release of MongoEngine.
The :mod:`~mongoengine.django.auth` module also contains a
:func:`~mongoengine.django.auth.get_user` helper function, that takes a user's
:attr:`id` and returns a :class:`~mongoengine.django.auth.User` object.
.. versionadded:: 0.1.3
Sessions
========
Django allows the use of different backend stores for its sessions. MongoEngine
provides a MongoDB-based session backend for Django, which allows you to use
sessions in you Django application with just MongoDB. To enable the MongoEngine
session backend, ensure that your settings module has
``'django.contrib.sessions.middleware.SessionMiddleware'`` in the
``MIDDLEWARE_CLASSES`` field and ``'django.contrib.sessions'`` in your
``INSTALLED_APPS``. From there, all you need to do is add the following line
into you settings module::
Help Wanted!
------------
SESSION_ENGINE = 'mongoengine.django.sessions'
.. versionadded:: 0.2.1
Storage
=======
With MongoEngine's support for GridFS via the :class:`~mongoengine.FileField`,
it is useful to have a Django file storage backend that wraps this. The new
storage module is called :class:`~mongoengine.django.storage.GridFSStorage`.
Using it is very similar to using the default FileSystemStorage.::
from mongoengine.django.storage import GridFSStorage
fs = GridFSStorage()
filename = fs.save('hello.txt', 'Hello, World!')
All of the `Django Storage API methods
<http://docs.djangoproject.com/en/dev/ref/files/storage/>`_ have been
implemented except :func:`path`. If the filename provided already exists, an
underscore and a number (before # the file extension, if one exists) will be
appended to the filename until the generated filename doesn't exist. The
:func:`save` method will return the new filename.::
>>> fs.exists('hello.txt')
True
>>> fs.open('hello.txt').read()
'Hello, World!'
>>> fs.size('hello.txt')
13
>>> fs.url('hello.txt')
'http://your_media_url/hello.txt'
>>> fs.open('hello.txt').name
'hello.txt'
>>> fs.listdir()
([], [u'hello.txt'])
All files will be saved and retrieved in GridFS via the :class::`FileDocument`
document, allowing easy access to the files without the GridFSStorage
backend.::
>>> from mongoengine.django.storage import FileDocument
>>> FileDocument.objects()
[<FileDocument: FileDocument object>]
.. versionadded:: 0.4
The MongoEngine team is looking for help contributing and maintaining a new
Django extension for MongoEngine! If you have Django experience and would like
to help contribute to the project, please get in touch on the
`mailing list <http://groups.google.com/group/mongoengine-users>`_ or by
simply contributing on
`GitHub <https://github.com/MongoEngine/django-mongoengine>`_.

View File

@@ -6,60 +6,134 @@ Connecting to MongoDB
To connect to a running instance of :program:`mongod`, use the
:func:`~mongoengine.connect` function. The first argument is the name of the
database to connect to. If the database does not exist, it will be created. If
the database requires authentication, :attr:`username` and :attr:`password`
arguments may be provided::
database to connect to::
from mongoengine import connect
connect('project1', username='webapp', password='pwd123')
connect('project1')
By default, MongoEngine assumes that the :program:`mongod` instance is running
on **localhost** on port **27017**. If MongoDB is running elsewhere, you may
provide :attr:`host` and :attr:`port` arguments to
on **localhost** on port **27017**. If MongoDB is running elsewhere, you should
provide the :attr:`host` and :attr:`port` arguments to
:func:`~mongoengine.connect`::
connect('project1', host='192.168.1.35', port=12345)
Uri style connections are also supported as long as you include the database
name - just supply the uri as the :attr:`host` to
If the database requires authentication, :attr:`username` and :attr:`password`
arguments should be provided::
connect('project1', username='webapp', password='pwd123')
URI style connections are also supported -- just supply the URI as
the :attr:`host` to
:func:`~mongoengine.connect`::
connect('project1', host='mongodb://localhost/database_name')
ReplicaSets
===========
.. note:: Database, username and password from URI string overrides
corresponding parameters in :func:`~mongoengine.connect`: ::
MongoEngine now supports :func:`~pymongo.replica_set_connection.ReplicaSetConnection`
to use them please use a URI style connection and provide the `replicaSet` name in the
connection kwargs.
connect(
db='test',
username='user',
password='12345',
host='mongodb://admin:qwerty@localhost/production'
)
will establish connection to ``production`` database using
``admin`` username and ``qwerty`` password.
Replica Sets
============
MongoEngine supports connecting to replica sets::
from mongoengine import connect
# Regular connect
connect('dbname', replicaset='rs-name')
# MongoDB URI-style connect
connect(host='mongodb://localhost/dbname?replicaSet=rs-name')
Read preferences are supported through the connection or via individual
queries by passing the read_preference ::
Bar.objects().read_preference(ReadPreference.PRIMARY)
Bar.objects(read_preference=ReadPreference.PRIMARY)
Multiple Databases
==================
Multiple database support was added in MongoEngine 0.6. To use multiple
databases you can use :func:`~mongoengine.connect` and provide an `alias` name
for the connection - if no `alias` is provided then "default" is used.
To use multiple databases you can use :func:`~mongoengine.connect` and provide
an `alias` name for the connection - if no `alias` is provided then "default"
is used.
In the background this uses :func:`~mongoengine.register_connection` to
store the data and you can register all aliases up front if required.
Individual documents can also support multiple databases by providing a
`db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef` objects
to point across databases and collections. Below is an example schema, using
3 different databases to store data::
`db_alias` in their meta data. This allows :class:`~pymongo.dbref.DBRef`
objects to point across databases and collections. Below is an example schema,
using 3 different databases to store data::
class User(Document):
name = StringField()
meta = {"db_alias": "user-db"}
meta = {'db_alias': 'user-db'}
class Book(Document):
name = StringField()
meta = {"db_alias": "book-db"}
meta = {'db_alias': 'book-db'}
class AuthorBooks(Document):
author = ReferenceField(User)
book = ReferenceField(Book)
meta = {"db_alias": "users-books-db"}
meta = {'db_alias': 'users-books-db'}
Context Managers
================
Sometimes you may want to switch the database or collection to query against.
For example, archiving older data into a separate database for performance
reasons or writing functions that dynamically choose collections to write
a document to.
Switch Database
---------------
The :class:`~mongoengine.context_managers.switch_db` context manager allows
you to change the database alias for a given class allowing quick and easy
access to the same User document across databases::
from mongoengine.context_managers import switch_db
class User(Document):
name = StringField()
meta = {'db_alias': 'user-db'}
with switch_db(User, 'archive-user-db') as User:
User(name='Ross').save() # Saves the 'archive-user-db'
Switch Collection
-----------------
The :class:`~mongoengine.context_managers.switch_collection` context manager
allows you to change the collection for a given class allowing quick and easy
access to the same Group document across collection::
from mongoengine.context_managers import switch_collection
class Group(Document):
name = StringField()
Group(name='test').save() # Saves in the default db
with switch_collection(Group, 'group2000') as Group:
Group(name='hello Group 2000 collection!').save() # Saves in group2000 collection
.. note:: Make sure any aliases have been registered with
:func:`~mongoengine.register_connection` or :func:`~mongoengine.connect`
before using the context manager.

View File

@@ -4,7 +4,7 @@ Defining documents
In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When
working with relational databases, rows are stored in **tables**, which have a
strict **schema** that the rows follow. MongoDB stores documents in
**collections** rather than tables - the principle difference is that no schema
**collections** rather than tables --- the principal difference is that no schema
is enforced at a database level.
Defining a document's schema
@@ -24,9 +24,12 @@ objects** as class attributes to the document class::
title = StringField(max_length=200, required=True)
date_modified = DateTimeField(default=datetime.datetime.now)
As BSON (the binary format for storing data in mongodb) is order dependent,
documents are serialized based on their field order.
Dynamic document schemas
========================
One of the benefits of MongoDb is dynamic schemas for a collection, whilst data
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
should be planned and organised (after all explicit is better than implicit!)
there are scenarios where having dynamic / expando style documents is desirable.
@@ -47,10 +50,11 @@ be saved ::
>>> Page.objects(tags='mongoengine').count()
>>> 1
..note::
.. note::
There is one caveat on Dynamic Documents: fields cannot start with `_`
Dynamic fields are stored in creation order *after* any declared fields.
Fields
======
@@ -62,31 +66,38 @@ 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
are as follows:
* :class:`~mongoengine.BinaryField`
* :class:`~mongoengine.BooleanField`
* :class:`~mongoengine.ComplexDateTimeField`
* :class:`~mongoengine.DateTimeField`
* :class:`~mongoengine.DecimalField`
* :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.ObjectIdField`
* :class:`~mongoengine.ReferenceField`
* :class:`~mongoengine.SequenceField`
* :class:`~mongoengine.SortedListField`
* :class:`~mongoengine.StringField`
* :class:`~mongoengine.URLField`
* :class:`~mongoengine.UUIDField`
* :class:`~mongoengine.fields.BinaryField`
* :class:`~mongoengine.fields.BooleanField`
* :class:`~mongoengine.fields.ComplexDateTimeField`
* :class:`~mongoengine.fields.DateTimeField`
* :class:`~mongoengine.fields.DecimalField`
* :class:`~mongoengine.fields.DictField`
* :class:`~mongoengine.fields.DynamicField`
* :class:`~mongoengine.fields.EmailField`
* :class:`~mongoengine.fields.EmbeddedDocumentField`
* :class:`~mongoengine.fields.EmbeddedDocumentListField`
* :class:`~mongoengine.fields.FileField`
* :class:`~mongoengine.fields.FloatField`
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
* :class:`~mongoengine.fields.GenericReferenceField`
* :class:`~mongoengine.fields.GeoPointField`
* :class:`~mongoengine.fields.ImageField`
* :class:`~mongoengine.fields.IntField`
* :class:`~mongoengine.fields.ListField`
* :class:`~mongoengine.fields.MapField`
* :class:`~mongoengine.fields.ObjectIdField`
* :class:`~mongoengine.fields.ReferenceField`
* :class:`~mongoengine.fields.SequenceField`
* :class:`~mongoengine.fields.SortedListField`
* :class:`~mongoengine.fields.StringField`
* :class:`~mongoengine.fields.URLField`
* :class:`~mongoengine.fields.UUIDField`
* :class:`~mongoengine.fields.PointField`
* :class:`~mongoengine.fields.LineStringField`
* :class:`~mongoengine.fields.PolygonField`
* :class:`~mongoengine.fields.MultiPointField`
* :class:`~mongoengine.fields.MultiLineStringField`
* :class:`~mongoengine.fields.MultiPolygonField`
Field arguments
---------------
@@ -96,9 +107,6 @@ arguments can be set on all fields:
:attr:`db_field` (Default: None)
The MongoDB field name.
:attr:`name` (Default: None)
The mongoengine field name.
:attr:`required` (Default: False)
If set to True and the field is not set on the document instance, a
:class:`~mongoengine.ValidationError` will be raised when the document is
@@ -107,10 +115,10 @@ arguments can be set on all fields:
:attr:`default` (Default: None)
A value to use when no value is set for this field.
The definion of default parameters follow `the general rules on Python
The definition of default parameters follow `the general rules on Python
<http://docs.python.org/reference/compound_stmts.html#function-definitions>`__,
which means that some care should be taken when dealing with default mutable objects
(like in :class:`~mongoengine.ListField` or :class:`~mongoengine.DictField`)::
(like in :class:`~mongoengine.fields.ListField` or :class:`~mongoengine.fields.DictField`)::
class ExampleFirst(Document):
# Default an empty list
@@ -125,6 +133,7 @@ arguments can be set on all fields:
# instead to just an object
values = ListField(IntField(), default=[1,2,3])
.. note:: Unsetting a field with a default value will revert back to the default.
:attr:`unique` (Default: False)
When True, no documents in the collection will have the same value for this
@@ -135,10 +144,13 @@ arguments can be set on all fields:
field, will not have two documents in the collection with the same value.
:attr:`primary_key` (Default: False)
When True, use this field as a primary key for the collection.
When True, use this field as a primary key for the collection. `DictField`
and `EmbeddedDocuments` both support being the primary key for a document.
.. note:: If set, this field is also accessible through the `pk` field.
:attr:`choices` (Default: None)
An iterable (e.g. a list or tuple) of choices to which the value of this
An iterable (e.g. list, tuple or set) of choices to which the value of this
field should be limited.
Can be either be a nested tuples of value (stored in mongo) and a
@@ -161,18 +173,18 @@ arguments can be set on all fields:
class Shirt(Document):
size = StringField(max_length=3, choices=SIZE)
:attr:`help_text` (Default: None)
Optional help text to output with the field - used by form libraries
:attr:`verbose_name` (Default: None)
Optional human-readable name for the field - used by form libraries
:attr:`**kwargs` (Optional)
You can supply additional metadata as arbitrary additional keyword
arguments. You can not override existing attributes, however. Common
choices include `help_text` and `verbose_name`, commonly used by form and
widget libraries.
List fields
-----------
MongoDB allows the storage of lists of items. To add a list of items to a
:class:`~mongoengine.Document`, use the :class:`~mongoengine.ListField` field
type. :class:`~mongoengine.ListField` takes another field object as its first
MongoDB allows storing lists of items. To add a list of items to a
:class:`~mongoengine.Document`, use the :class:`~mongoengine.fields.ListField` field
type. :class:`~mongoengine.fields.ListField` takes another field object as its first
argument, which specifies which type elements may be stored within the list::
class Page(Document):
@@ -190,7 +202,7 @@ inherit from :class:`~mongoengine.EmbeddedDocument` rather than
content = StringField()
To embed the document within another document, use the
:class:`~mongoengine.EmbeddedDocumentField` field type, providing the embedded
:class:`~mongoengine.fields.EmbeddedDocumentField` field type, providing the embedded
document class as the first argument::
class Page(Document):
@@ -202,10 +214,10 @@ document class as the first argument::
Dictionary Fields
-----------------
Often, an embedded document may be used instead of a dictionary -- generally
this is recommended as dictionaries don't support validation or custom field
types. However, sometimes you will not know the structure of what you want to
store; in this situation a :class:`~mongoengine.DictField` is appropriate::
Often, an embedded document may be used instead of a dictionary generally
embedded documents are recommended as dictionaries dont support validation
or custom field types. However, sometimes you will not know the structure of what you want to
store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate::
class SurveyResponse(Document):
date = DateTimeField()
@@ -223,7 +235,7 @@ other objects, so are the most flexible field type available.
Reference fields
----------------
References may be stored to other documents in the database using the
:class:`~mongoengine.ReferenceField`. Pass in another document class as the
:class:`~mongoengine.fields.ReferenceField`. Pass in another document class as the
first argument to the constructor, then simply assign document objects to the
field::
@@ -244,9 +256,9 @@ field::
The :class:`User` object is automatically turned into a reference behind the
scenes, and dereferenced when the :class:`Page` object is retrieved.
To add a :class:`~mongoengine.ReferenceField` that references the document
To add a :class:`~mongoengine.fields.ReferenceField` that references the document
being defined, use the string ``'self'`` in place of the document class as the
argument to :class:`~mongoengine.ReferenceField`'s constructor. To reference a
argument to :class:`~mongoengine.fields.ReferenceField`'s constructor. To reference a
document that has not yet been defined, use the name of the undefined document
as the constructor's argument::
@@ -287,6 +299,12 @@ instance of the object to the query::
# Find all pages that both Bob and John have authored
Page.objects(authors__all=[bob, john])
# Remove Bob from the authors for a page.
Page.objects(id='...').update_one(pull__authors=bob)
# Add John to the authors for a page.
Page.objects(id='...').update_one(push__authors=john)
Dealing with deletion of referred documents
'''''''''''''''''''''''''''''''''''''''''''
@@ -298,12 +316,12 @@ reference with a delete rule specification. A delete rule is specified by
supplying the :attr:`reverse_delete_rule` attributes on the
:class:`ReferenceField` definition, like this::
class Employee(Document):
class ProfilePage(Document):
...
profile_page = ReferenceField('ProfilePage', reverse_delete_rule=mongoengine.NULLIFY)
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
The declaration in this example means that when an :class:`Employee` object is
removed, the :class:`ProfilePage` that belongs to that employee is removed as
removed, the :class:`ProfilePage` that references that employee is removed as
well. If a whole batch of employees is removed, all profile pages that are
linked are removed as well.
@@ -319,12 +337,12 @@ Its value can take any of the following constants:
Any object's fields still referring to the object being deleted are removed
(using MongoDB's "unset" operation), effectively nullifying the relationship.
:const:`mongoengine.CASCADE`
Any object containing fields that are refererring to the object being deleted
Any object containing fields that are referring to the object being deleted
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`).
:class:`~mongoengine.fields.ListField` (:class:`~mongoengine.fields.ReferenceField`).
.. warning::
@@ -343,15 +361,10 @@ Its value can take any of the following constants:
In Django, be sure to put all apps that have such delete rule declarations in
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
.. warning::
Signals are not triggered when doing cascading updates / deletes - if this
is required you must manually handle the update / delete.
Generic reference fields
''''''''''''''''''''''''
A second kind of reference field also exists,
:class:`~mongoengine.GenericReferenceField`. This allows you to reference any
:class:`~mongoengine.fields.GenericReferenceField`. This allows you to reference any
kind of :class:`~mongoengine.Document`, and hence doesn't take a
:class:`~mongoengine.Document` subclass as a constructor argument::
@@ -375,18 +388,18 @@ kind of :class:`~mongoengine.Document`, and hence doesn't take a
.. note::
Using :class:`~mongoengine.GenericReferenceField`\ s is slightly less
efficient than the standard :class:`~mongoengine.ReferenceField`\ s, so if
Using :class:`~mongoengine.fields.GenericReferenceField`\ s is slightly less
efficient than the standard :class:`~mongoengine.fields.ReferenceField`\ s, so if
you will only be referencing one document type, prefer the standard
:class:`~mongoengine.ReferenceField`.
:class:`~mongoengine.fields.ReferenceField`.
Uniqueness constraints
----------------------
MongoEngine allows you to specify that a field should be unique across a
collection by providing ``unique=True`` to a :class:`~mongoengine.Field`\ 's
collection by providing ``unique=True`` to a :class:`~mongoengine.fields.Field`\ 's
constructor. If you try to save a document that has the same value for a unique
field as a document that is already in the database, a
:class:`~mongoengine.OperationError` will be raised. You may also specify
:class:`~mongoengine.NotUniqueError` will be raised. You may also specify
multi-field uniqueness constraints by using :attr:`unique_with`, which may be
either a single field name, or a list or tuple of field names::
@@ -398,7 +411,7 @@ either a single field name, or a list or tuple of field names::
Skipping Document validation on save
------------------------------------
You can also skip the whole document validation process by setting
``validate=False`` when caling the :meth:`~mongoengine.document.Document.save`
``validate=False`` when calling the :meth:`~mongoengine.document.Document.save`
method::
class Recipient(Document):
@@ -413,7 +426,7 @@ Document collections
====================
Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection
is by default the name of the class, coverted to lowercase (so in the example
is by default the name of the class, converted to lowercase (so in the example
above, the collection would be called `page`). If you need to change the name
of the collection (e.g. to use MongoEngine with an existing database), then
create a class dictionary attribute called :attr:`meta` on your document, and
@@ -430,8 +443,10 @@ A :class:`~mongoengine.Document` may use a **Capped Collection** by specifying
:attr:`max_documents` and :attr:`max_size` in the :attr:`meta` dictionary.
:attr:`max_documents` is the maximum number of documents that is allowed to be
stored in the collection, and :attr:`max_size` is the maximum size of the
collection in bytes. If :attr:`max_size` is not specified and
:attr:`max_documents` is, :attr:`max_size` defaults to 10000000 bytes (10MB).
collection in bytes. :attr:`max_size` is rounded up to the next multiple of 256
by MongoDB internally and mongoengine before. Use also a multiple of 256 to
avoid confusions. If :attr:`max_size` is not specified and
:attr:`max_documents` is, :attr:`max_size` defaults to 10485760 bytes (10MB).
The following example shows a :class:`Log` document that will be limited to
1000 entries and 2MB of disk space::
@@ -439,21 +454,40 @@ The following example shows a :class:`Log` document that will be limited to
ip_address = StringField()
meta = {'max_documents': 1000, 'max_size': 2000000}
.. defining-indexes_
Indexes
=======
You can specify indexes on collections to make querying faster. This is done
by creating a list of index specifications called :attr:`indexes` in the
:attr:`~mongoengine.Document.meta` dictionary, where an index specification may
either be a single field name, a tuple containing multiple field names, or a
dictionary containing a full index definition. A direction may be specified on
fields by prefixing the field name with a **+** or a **-** sign. Note that
direction only matters on multi-field indexes. ::
dictionary containing a full index definition.
A direction may be specified on fields by prefixing the field name with a
**+** (for ascending) or a **-** sign (for descending). Note that direction
only matters on multi-field indexes. Text indexes may be specified by prefixing
the field name with a **$**. Hashed indexes may be specified by prefixing
the field name with a **#**::
class Page(Document):
category = IntField()
title = StringField()
rating = StringField()
created = DateTimeField()
meta = {
'indexes': ['title', ('title', '-rating')]
'indexes': [
'title',
'$title', # text index
'#title', # hashed index
('title', '-rating'),
('category', '_cls'),
{
'fields': ['created'],
'expireAfterSeconds': 3600
}
]
}
If a dictionary is passed then the following options are available:
@@ -461,9 +495,11 @@ If a dictionary is passed then the following options are available:
:attr:`fields` (Default: None)
The fields to index. Specified in the same format as described above.
:attr:`types` (Default: True)
Whether the index should have the :attr:`_types` field added automatically
to the start of the index.
:attr:`cls` (Default: True)
If you have polymorphic models that inherit and have
:attr:`allow_inheritance` turned on, you can configure whether the index
should have the :attr:`_cls` field added automatically to the start of the
index.
:attr:`sparse` (Default: False)
Whether the index should be sparse.
@@ -471,26 +507,97 @@ If a dictionary is passed then the following options are available:
:attr:`unique` (Default: False)
Whether the index should be unique.
.. note ::
:attr:`expireAfterSeconds` (Optional)
Allows you to automatically expire data from a collection by setting the
time in seconds to expire the a field.
To index embedded files / dictionary fields use 'dot' notation eg:
`rank.title`
.. note::
.. warning::
Inheritance adds extra fields indices see: :ref:`document-inheritance`.
Inheritance adds extra indices.
If don't need inheritance for a document turn inheritance off -
see :ref:`document-inheritance`.
Global index default options
----------------------------
There are a few top level defaults for all indexes that can be set::
class Page(Document):
title = StringField()
rating = StringField()
meta = {
'index_options': {},
'index_background': True,
'index_drop_dups': True,
'index_cls': False
}
:attr:`index_options` (Optional)
Set any default index options - see the `full options list <http://docs.mongodb.org/manual/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex>`_
:attr:`index_background` (Optional)
Set the default value for if an index should be indexed in the background
:attr:`index_cls` (Optional)
A way to turn off a specific index for _cls.
:attr:`index_drop_dups` (Optional)
Set the default value for if an index should drop duplicates
.. note:: Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
and has no effect
Compound Indexes and Indexing sub documents
-------------------------------------------
Compound indexes can be created by adding the Embedded field or dictionary
field name to the index definition.
Sometimes its more efficient to index parts of Embedded / dictionary fields,
in this case use 'dot' notation to identify the value to index eg: `rank.title`
.. _geospatial-indexes:
Geospatial indexes
---------------------------
------------------
The best geo index for mongodb is the new "2dsphere", which has an improved
spherical model and provides better performance and more options when querying.
The following fields will explicitly add a "2dsphere" index:
- :class:`~mongoengine.fields.PointField`
- :class:`~mongoengine.fields.LineStringField`
- :class:`~mongoengine.fields.PolygonField`
- :class:`~mongoengine.fields.MultiPointField`
- :class:`~mongoengine.fields.MultiLineStringField`
- :class:`~mongoengine.fields.MultiPolygonField`
As "2dsphere" indexes can be part of a compound index, you may not want the
automatic index but would prefer a compound index. In this example we turn off
auto indexing and explicitly declare a compound index on ``location`` and ``datetime``::
class Log(Document):
location = PointField(auto_index=False)
datetime = DateTimeField()
meta = {
'indexes': [[("location", "2dsphere"), ("datetime", 1)]]
}
Pre MongoDB 2.4 Geo
'''''''''''''''''''
.. note:: For MongoDB < 2.4 this is still current, however the new 2dsphere
index is a big improvement over the previous 2D model - so upgrading is
advised.
Geospatial indexes will be automatically created for all
:class:`~mongoengine.GeoPointField`\ s
:class:`~mongoengine.fields.GeoPointField`\ s
It is also possible to explicitly define geospatial indexes. This is
useful if you need to define a geospatial index on a subfield of a
:class:`~mongoengine.DictField` or a custom field that contains a
:class:`~mongoengine.fields.DictField` or a custom field that contains a
point. To create a geospatial index you must prefix the field with the
***** sign. ::
@@ -502,6 +609,35 @@ point. To create a geospatial index you must prefix the field with the
],
}
Time To Live indexes
--------------------
A special index type that allows you to automatically expire data from a
collection after a given period. See the official
`ttl <http://docs.mongodb.org/manual/tutorial/expire-data/#expire-data-from-collections-by-setting-ttl>`_
documentation for more information. A common usecase might be session data::
class Session(Document):
created = DateTimeField(default=datetime.now)
meta = {
'indexes': [
{'fields': ['created'], 'expireAfterSeconds': 3600}
]
}
.. warning:: TTL indexes happen on the MongoDB server and not in the application
code, therefore no signals will be fired on document deletion.
If you need signals to be fired on deletion, then you must handle the
deletion of Documents in your application code.
Comparing Indexes
-----------------
Use :func:`mongoengine.Document.compare_indexes` to compare actual indexes in
the database to those that your document definitions define. This is useful
for maintenance purposes and ensuring you have the correct indexes for your
schema.
Ordering
========
A default ordering can be specified for your
@@ -546,11 +682,11 @@ Shard keys
==========
If your collection is sharded, then you need to specify the shard key as a tuple,
using the :attr:`shard_key` attribute of :attr:`-mongoengine.Document.meta`.
using the :attr:`shard_key` attribute of :attr:`~mongoengine.Document.meta`.
This ensures that the shard key is sent with the query when calling the
:meth:`~mongoengine.document.Document.save` or
:meth:`~mongoengine.document.Document.update` method on an existing
:class:`-mongoengine.Document` instance::
:class:`~mongoengine.Document` instance::
class LogEntry(Document):
machine = StringField()
@@ -572,7 +708,9 @@ defined, you may subclass it and add any extra fields or methods you may need.
As this is new class is not a direct subclass of
:class:`~mongoengine.Document`, it will not be stored in its own collection; it
will use the same collection as its superclass uses. This allows for more
convenient and efficient retrieval of related documents::
convenient and efficient retrieval of related documents -- all you need do is
set :attr:`allow_inheritance` to True in the :attr:`meta` data for a
document.::
# Stored in a collection named 'page'
class Page(Document):
@@ -584,25 +722,47 @@ convenient and efficient retrieval of related documents::
class DatedPage(Page):
date = DateTimeField()
.. note:: From 0.7 onwards you must declare `allow_inheritance` in the document meta.
.. note:: From 0.8 onwards :attr:`allow_inheritance` defaults
to False, meaning you must set it to True to use inheritance.
Working with existing data
--------------------------
To enable correct retrieval of documents involved in this kind of heirarchy,
two extra attributes are stored on each document in the database: :attr:`_cls`
and :attr:`_types`. These are hidden from the user through the MongoEngine
interface, but may not be present if you are trying to use MongoEngine with
an existing database. For this reason, you may disable this inheritance
mechansim, removing the dependency of :attr:`_cls` and :attr:`_types`, enabling
you to work with existing databases. To disable inheritance on a document
class, set :attr:`allow_inheritance` to ``False`` in the :attr:`meta`
dictionary::
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and
easily get working with existing data. Just define the document to match
the expected schema in your database ::
# Will work with data in an existing collection named 'cmsPage'
class Page(Document):
title = StringField(max_length=200, required=True)
meta = {
'collection': 'cmsPage',
'allow_inheritance': False,
'collection': 'cmsPage'
}
If you have wildly varying schemas then using a
:class:`~mongoengine.DynamicDocument` might be more appropriate, instead of
defining all possible field types.
If you use :class:`~mongoengine.Document` and the database contains data that
isn't defined then that data will be stored in the `document._data` dictionary.
Abstract classes
================
If you want to add some extra functionality to a group of Document classes but
you don't need or want the overhead of inheritance you can use the
:attr:`abstract` attribute of :attr:`~mongoengine.Document.meta`.
This won't turn on :ref:`document-inheritance` but will allow you to keep your
code DRY::
class BaseDocument(Document):
meta = {
'abstract': True,
}
def check_permissions(self):
...
class User(BaseDocument):
...
Now the User class will have access to the inherited `check_permissions` method
and won't store any of the extra `_cls` information.

View File

@@ -2,7 +2,7 @@
Documents instances
===================
To create a new document object, create an instance of the relevant document
class, providing values for its fields as its constructor keyword arguments.
class, providing values for its fields as constructor keyword arguments.
You may provide values for any of the fields on the document::
>>> page = Page(title="Test Page")
@@ -30,21 +30,53 @@ already exist, then any changes will be updated atomically. For example::
.. note::
Changes to documents are tracked and on the whole perform `set` operations.
Changes to documents are tracked and on the whole perform ``set`` operations.
* ``list_field.pop(0)`` - *sets* the resulting list
* ``del(list_field)`` - *unsets* whole list
* ``list_field.push(0)`` --- *sets* the resulting list
* ``del(list_field)`` --- *unsets* whole list
With lists its preferable to use ``Doc.update(push__list_field=0)`` as
this stops the whole list being updated --- stopping any race conditions.
.. seealso::
:ref:`guide-atomic-updates`
Pre save data validation and cleaning
-------------------------------------
MongoEngine allows you to create custom cleaning rules for your documents when
calling :meth:`~mongoengine.Document.save`. By providing a custom
:meth:`~mongoengine.Document.clean` method you can do any pre validation / data
cleaning.
This might be useful if you want to ensure a default value based on other
document values for example::
class Essay(Document):
status = StringField(choices=('Published', 'Draft'), required=True)
pub_date = DateTimeField()
def clean(self):
"""Ensures that only published essays have a `pub_date` and
automatically sets the pub_date if published and not set"""
if self.status == 'Draft' and self.pub_date is not None:
msg = 'Draft entries should not have a publication date.'
raise ValidationError(msg)
# Set the pub_date for published items if not set.
if self.status == 'Published' and self.pub_date is None:
self.pub_date = datetime.now()
.. note::
Cleaning is only called if validation is turned on and when calling
:meth:`~mongoengine.Document.save`.
Cascading Saves
---------------
If your document contains :class:`~mongoengine.ReferenceField` or
:class:`~mongoengine.GenericReferenceField` objects, then by default the
:meth:`~mongoengine.Document.save` method will automatically save any changes to
those objects as well. If this is not desired passing :attr:`cascade` as False
to the save method turns this feature off.
If your document contains :class:`~mongoengine.fields.ReferenceField` or
:class:`~mongoengine.fields.GenericReferenceField` objects, then by default the
:meth:`~mongoengine.Document.save` method will not save any changes to
those objects. If you want all references to be saved also, noting each
save is a separate query, then passing :attr:`cascade` as True
to the save method will cascade any saves.
Deleting documents
------------------
@@ -81,12 +113,13 @@ you may still use :attr:`id` to access the primary key if you want::
>>> bob.id == bob.email == 'bob@example.com'
True
You can also access the document's "primary key" using the :attr:`pk` field; in
is an alias to :attr:`id`::
You can also access the document's "primary key" using the :attr:`pk` field,
it's an alias to :attr:`id`::
>>> page = Page(title="Another Test Page")
>>> page.save()
>>> page.id == page.pk
True
.. note::

View File

@@ -7,7 +7,7 @@ GridFS
Writing
-------
GridFS support comes in the form of the :class:`~mongoengine.FileField` field
GridFS support comes in the form of the :class:`~mongoengine.fields.FileField` field
object. This field acts as a file-like object and provides a couple of
different ways of inserting and retrieving data. Arbitrary metadata such as
content type can also be stored alongside the files. In the following example,
@@ -18,26 +18,16 @@ a document is created to store details about animals, including a photo::
family = StringField()
photo = FileField()
marmot = Animal('Marmota', 'Sciuridae')
marmot_photo = open('marmot.jpg', 'r') # Retrieve a photo from disk
marmot.photo = marmot_photo # Store photo in the document
marmot.photo.content_type = 'image/jpeg' # Store metadata
marmot.save()
Another way of writing to a :class:`~mongoengine.FileField` is to use the
:func:`put` method. This allows for metadata to be stored in the same call as
the file::
marmot.photo.put(marmot_photo, content_type='image/jpeg')
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open('marmot.jpg', 'rb')
marmot.photo.put(marmot_photo, content_type = 'image/jpeg')
marmot.save()
Retrieval
---------
So using the :class:`~mongoengine.FileField` is just like using any other
So using the :class:`~mongoengine.fields.FileField` is just like using any other
field. The file can also be retrieved just as easily::
marmot = Animal.objects(genus='Marmota').first()
@@ -47,7 +37,7 @@ field. The file can also be retrieved just as easily::
Streaming
---------
Streaming data into a :class:`~mongoengine.FileField` is achieved in a
Streaming data into a :class:`~mongoengine.fields.FileField` is achieved in a
slightly different manner. First, a new file must be created by calling the
:func:`new_file` method. Data can then be written using :func:`write`::
@@ -56,7 +46,7 @@ slightly different manner. First, a new file must be created by calling the
marmot.photo.write('some_more_image_data')
marmot.photo.close()
marmot.photo.save()
marmot.save()
Deletion
--------
@@ -80,5 +70,5 @@ Replacing files
Files can be replaced with the :func:`replace` method. This works just like
the :func:`put` method so even metadata can (and should) be replaced::
another_marmot = open('another_marmot.png', 'r')
another_marmot = open('another_marmot.png', 'rb')
marmot.photo.replace(another_marmot, content_type='image/png')

View File

@@ -12,3 +12,5 @@ User Guide
querying
gridfs
signals
text-indexes
mongomock

View File

@@ -2,13 +2,13 @@
Installing MongoEngine
======================
To use MongoEngine, you will need to download `MongoDB <http://mongodb.org/>`_
To use MongoEngine, you will need to download `MongoDB <http://mongodb.com/>`_
and ensure it is running in an accessible location. You will also need
`PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you
install MongoEngine using setuptools, then the dependencies will be handled for
you.
MongoEngine is available on PyPI, so to use it you can use :program:`pip`:
MongoEngine is available on PyPI, so you can use :program:`pip`:
.. code-block:: console
@@ -22,10 +22,10 @@ Alternatively, if you don't have setuptools installed, `download it from PyPi
$ python setup.py install
To use the bleeding-edge version of MongoEngine, you can get the source from
`GitHub <http://github.com/hmarr/mongoengine/>`_ and install it as above:
`GitHub <http://github.com/mongoengine/mongoengine/>`_ and install it as above:
.. code-block:: console
$ git clone git://github.com/hmarr/mongoengine
$ git clone git://github.com/mongoengine/mongoengine
$ cd mongoengine
$ python setup.py install

21
docs/guide/mongomock.rst Normal file
View File

@@ -0,0 +1,21 @@
==============================
Use mongomock for testing
==============================
`mongomock <https://github.com/vmalloc/mongomock/>`_ is a package to do just
what the name implies, mocking a mongo database.
To use with mongoengine, simply specify mongomock when connecting with
mongoengine:
.. code-block:: python
connect('mongoenginetest', host='mongomock://localhost')
conn = get_connection()
or with an alias:
.. code-block:: python
connect('mongoenginetest', host='mongomock://localhost', alias='testdb')
conn = get_connection('testdb')

View File

@@ -15,11 +15,10 @@ fetch documents from the database::
.. note::
Once the iteration finishes (when :class:`StopIteration` is raised),
:meth:`~mongoengine.queryset.QuerySet.rewind` will be called so that the
:class:`~mongoengine.queryset.QuerySet` may be iterated over again. The
results of the first iteration are *not* cached, so the database will be hit
each time the :class:`~mongoengine.queryset.QuerySet` is iterated over.
As of MongoEngine 0.8 the querysets utilise a local cache. So iterating
it multiple times will only cause a single query. If this is not the
desired behaviour you can call :class:`~mongoengine.QuerySet.no_cache`
(version **0.8.3+**) to return a non-caching queryset.
Filtering queries
=================
@@ -40,10 +39,18 @@ syntax::
# been written by a user whose 'country' field is set to 'uk'
uk_pages = Page.objects(author__country='uk')
.. note::
(version **0.9.1+**) if your field name is like mongodb operator name (for example
type, lte, lt...) and you want to place it at the end of lookup keyword
mongoengine automatically prepend $ to it. To avoid this use __ at the end of
your lookup keyword. For example if your field name is ``type`` and you want to
query by this field you must use ``.objects(user__type__="admin")`` instead of
``.objects(user__type="admin")``
Query operators
===============
Operators other than equality may also be used in queries; just attach the
Operators other than equality may also be used in queries --- just attach the
operator name to a key with a double-underscore::
# Only find users whose age is 18 or less
@@ -65,6 +72,9 @@ Available operators are as follows:
* ``size`` -- the size of the array is
* ``exists`` -- value for field exists
String queries
--------------
The following operators are available as shortcuts to querying with regular
expressions:
@@ -78,12 +88,75 @@ expressions:
* ``iendswith`` -- string field ends with value (case insensitive)
* ``match`` -- performs an $elemMatch so you can match an entire document within an array
There are a few special operators for performing geographical queries, that
may used with :class:`~mongoengine.GeoPointField`\ s:
Geo queries
-----------
There are a few special operators for performing geographical queries.
The following were added in MongoEngine 0.8 for
:class:`~mongoengine.fields.PointField`,
:class:`~mongoengine.fields.LineStringField` and
:class:`~mongoengine.fields.PolygonField`:
* ``geo_within`` -- check if a geometry is within a polygon. For ease of use
it accepts either a geojson geometry or just the polygon coordinates eg::
loc.objects(point__geo_within=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
loc.objects(point__geo_within={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
* ``geo_within_box`` -- simplified geo_within searching with a box eg::
loc.objects(point__geo_within_box=[(-125.0, 35.0), (-100.0, 40.0)])
loc.objects(point__geo_within_box=[<bottom left coordinates>, <upper right coordinates>])
* ``geo_within_polygon`` -- simplified geo_within searching within a simple polygon eg::
loc.objects(point__geo_within_polygon=[[40, 5], [40, 6], [41, 6], [40, 5]])
loc.objects(point__geo_within_polygon=[ [ <x1> , <y1> ] ,
[ <x2> , <y2> ] ,
[ <x3> , <y3> ] ])
* ``geo_within_center`` -- simplified geo_within the flat circle radius of a point eg::
loc.objects(point__geo_within_center=[(-125.0, 35.0), 1])
loc.objects(point__geo_within_center=[ [ <x>, <y> ] , <radius> ])
* ``geo_within_sphere`` -- simplified geo_within the spherical circle radius of a point eg::
loc.objects(point__geo_within_sphere=[(-125.0, 35.0), 1])
loc.objects(point__geo_within_sphere=[ [ <x>, <y> ] , <radius> ])
* ``geo_intersects`` -- selects all locations that intersect with a geometry eg::
# Inferred from provided points lists:
loc.objects(poly__geo_intersects=[40, 6])
loc.objects(poly__geo_intersects=[[40, 5], [40, 6]])
loc.objects(poly__geo_intersects=[[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]])
# With geoJson style objects
loc.objects(poly__geo_intersects={"type": "Point", "coordinates": [40, 6]})
loc.objects(poly__geo_intersects={"type": "LineString",
"coordinates": [[40, 5], [40, 6]]})
loc.objects(poly__geo_intersects={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]})
* ``near`` -- find all the locations near a given point::
loc.objects(point__near=[40, 5])
loc.objects(point__near={"type": "Point", "coordinates": [40, 5]})
You can also set the maximum and/or the minimum distance in meters as well::
loc.objects(point__near=[40, 5], point__max_distance=1000)
loc.objects(point__near=[40, 5], point__min_distance=100)
The older 2D indexes are still supported with the
:class:`~mongoengine.fields.GeoPointField`:
* ``within_distance`` -- provide a list containing a point and a maximum
distance (e.g. [(41.342, -87.653), 5])
* ``within_spherical_distance`` -- Same as above but using the spherical geo model
* ``within_spherical_distance`` -- same as above but using the spherical geo model
(e.g. [(41.342, -87.653), 5/earth_radius])
* ``near`` -- order the documents by how close they are to a given point
* ``near_sphere`` -- Same as above but using the spherical geo model
@@ -91,14 +164,19 @@ may used with :class:`~mongoengine.GeoPointField`\ s:
[(35.0, -125.0), (40.0, -100.0)])
* ``within_polygon`` -- filter documents to those within a given polygon (e.g.
[(41.91,-87.69), (41.92,-87.68), (41.91,-87.65), (41.89,-87.65)]).
.. note:: Requires Mongo Server 2.0
* ``max_distance`` -- can be added to your location queries to set a maximum
distance.
* ``min_distance`` -- can be added to your location queries to set a minimum
distance.
Querying lists
--------------
On most fields, this syntax will look up documents where the field specified
matches the given value exactly, but when the field refers to a
:class:`~mongoengine.ListField`, a single item may be provided, in which case
:class:`~mongoengine.fields.ListField`, a single item may be provided, in which case
lists that contain that item will be matched::
class Page(Document):
@@ -129,12 +207,14 @@ However, this doesn't map well to the syntax so you can also use a capital S ins
Post.objects(comments__by="joe").update(inc__comments__S__votes=1)
.. note:: Due to Mongo currently the $ operator only applies to the first matched item in the query.
.. note::
Due to :program:`Mongo`, currently the $ operator only applies to the
first matched item in the query.
Raw queries
-----------
It is possible to provide a raw PyMongo query as a query parameter, which will
It is possible to provide a raw :mod:`PyMongo` query as a query parameter, which will
be integrated directly into the query. This is done using the ``__raw__``
keyword argument::
@@ -144,12 +224,12 @@ keyword argument::
Limiting and skipping results
=============================
Just as with traditional ORMs, you may limit the number of results returned, or
Just as with traditional ORMs, you may limit the number of results returned or
skip a number or results in you query.
:meth:`~mongoengine.queryset.QuerySet.limit` and
:meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
:class:`~mongoengine.queryset.QuerySet` objects, but the prefered syntax for
achieving this is using array-slicing syntax::
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
is preferred for achieving this::
# Only the first 5 people
users = User.objects[:5]
@@ -157,7 +237,7 @@ achieving this is using array-slicing syntax::
# All except for the first 5 people
users = User.objects[5:]
# 5 users, starting from the 10th user found
# 5 users, starting from the 11th user found
users = User.objects[10:15]
You may also index the query to retrieve a single result. If an item at that
@@ -179,25 +259,21 @@ Retrieving unique results
-------------------------
To retrieve a result that should be unique in the collection, use
:meth:`~mongoengine.queryset.QuerySet.get`. This will raise
:class:`~mongoengine.queryset.DoesNotExist` if no document matches the query,
and :class:`~mongoengine.queryset.MultipleObjectsReturned` if more than one
document matched the query.
:class:`~mongoengine.queryset.DoesNotExist` if
no document matches the query, and
:class:`~mongoengine.queryset.MultipleObjectsReturned`
if more than one document matched the query. These exceptions are merged into
your document definitions eg: `MyDoc.DoesNotExist`
A variation of this method exists,
:meth:`~mongoengine.queryset.Queryset.get_or_create`, that will create a new
document with the query arguments if no documents match the query. An
additional keyword argument, :attr:`defaults` may be provided, which will be
used as default values for the new document, in the case that it should need
to be created::
>>> a, created = User.objects.get_or_create(name='User A', defaults={'age': 30})
>>> b, created = User.objects.get_or_create(name='User A', defaults={'age': 40})
>>> a.name == b.name and a.age == b.age
True
A variation of this method, get_or_create() existed, but it was unsafe. It
could not be made safe, because there are no transactions in mongoDB. Other
approaches should be investigated, to ensure you don't accidentally duplicate
data when using something similar to this method. Therefore it was deprecated
in 0.8 and removed in 0.10.
Default Document queries
========================
By default, the objects :attr:`~mongoengine.Document.objects` attribute on a
By default, the objects :attr:`~Document.objects` attribute on a
document returns a :class:`~mongoengine.queryset.QuerySet` that doesn't filter
the collection -- it returns all objects. This may be changed by defining a
method on a document that modifies a queryset. The method should accept two
@@ -240,7 +316,7 @@ Should you want to add custom methods for interacting with or filtering
documents, extending the :class:`~mongoengine.queryset.QuerySet` class may be
the way to go. To use a custom :class:`~mongoengine.queryset.QuerySet` class on
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):
@@ -264,12 +340,19 @@ Javascript code that is executed on the database server.
Counting results
----------------
Just as with limiting and skipping results, there is a method on
:class:`~mongoengine.queryset.QuerySet` objects --
:meth:`~mongoengine.queryset.QuerySet.count`, but there is also a more Pythonic
way of achieving this::
Just as with limiting and skipping results, there is a method on a
:class:`~mongoengine.queryset.QuerySet` object --
:meth:`~mongoengine.queryset.QuerySet.count`::
num_users = len(User.objects)
num_users = User.objects.count()
You could technically use ``len(User.objects)`` to get the same result, but it
would be significantly slower than :meth:`~mongoengine.queryset.QuerySet.count`.
When you execute a server-side count query, you let MongoDB do the heavy
lifting and you receive a single integer over the wire. Meanwhile, len()
retrieves all the results, places them in a local cache, and finally counts
them. If we compare the performance of the two operations, len() is much slower
than :meth:`~mongoengine.queryset.QuerySet.count`.
Further aggregation
-------------------
@@ -315,7 +398,7 @@ Retrieving a subset of fields
Sometimes a subset of fields on a :class:`~mongoengine.Document` is required,
and for efficiency only these should be retrieved from the database. This issue
is especially important for MongoDB, as fields may often be extremely large
(e.g. a :class:`~mongoengine.ListField` of
(e.g. a :class:`~mongoengine.fields.ListField` of
:class:`~mongoengine.EmbeddedDocument`\ s, which represent the comments on a
blog post. To select only a subset of fields, use
:meth:`~mongoengine.queryset.QuerySet.only`, specifying the fields you want to
@@ -347,14 +430,14 @@ If you later need the missing fields, just call
Getting related data
--------------------
When iterating the results of :class:`~mongoengine.ListField` or
:class:`~mongoengine.DictField` we automatically dereference any
When iterating the results of :class:`~mongoengine.fields.ListField` or
:class:`~mongoengine.fields.DictField` we automatically dereference any
:class:`~pymongo.dbref.DBRef` objects as efficiently as possible, reducing the
number the queries to mongo.
There are times when that efficiency is not enough, documents that have
:class:`~mongoengine.ReferenceField` objects or
:class:`~mongoengine.GenericReferenceField` objects at the top level are
:class:`~mongoengine.fields.ReferenceField` objects or
:class:`~mongoengine.fields.GenericReferenceField` objects at the top level are
expensive as the number of queries to MongoDB can quickly rise.
To limit the number of queries use
@@ -365,8 +448,30 @@ references to the depth of 1 level. If you have more complicated documents and
want to dereference more of the object at once then increasing the :attr:`max_depth`
will dereference more levels of the document.
Turning off dereferencing
-------------------------
Sometimes for performance reasons you don't want to automatically dereference
data. To turn off dereferencing of the results of a query use
:func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so::
post = Post.objects.no_dereference().first()
assert(isinstance(post.author, ObjectId))
You can also turn off all dereferencing for a fixed period by using the
:class:`~mongoengine.context_managers.no_dereference` context manager::
with no_dereference(Post) as Post:
post = Post.objects.first()
assert(isinstance(post.author, ObjectId))
# Outside the context manager dereferencing occurs.
assert(isinstance(post.author, User))
Advanced queries
================
Sometimes calling a :class:`~mongoengine.queryset.QuerySet` object with keyword
arguments can't fully express the query you want to use -- for example if you
need to combine a number of constraints using *and* and *or*. This is made
@@ -379,34 +484,46 @@ operators. To use a :class:`~mongoengine.queryset.Q` object, pass it in as the
first positional argument to :attr:`Document.objects` when you filter it by
calling it with keyword arguments::
from mongoengine.queryset.visitor import Q
# Get published posts
Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now()))
# Get top posts
Post.objects((Q(featured=True) & Q(hits__gte=1000)) | Q(hits__gte=5000))
.. warning:: You have to use bitwise operators. You cannot use ``or``, ``and``
to combine queries as ``Q(a=a) or Q(b=b)`` is not the same as
``Q(a=a) | Q(b=b)``. As ``Q(a=a)`` equates to true ``Q(a=a) or Q(b=b)`` is
the same as ``Q(a=a)``.
.. _guide-atomic-updates:
Atomic updates
==============
Documents may be updated atomically by using the
:meth:`~mongoengine.queryset.QuerySet.update_one` and
:meth:`~mongoengine.queryset.QuerySet.update` methods on a
:meth:`~mongoengine.queryset.QuerySet`. There are several different "modifiers"
that you may use with these methods:
:meth:`~mongoengine.queryset.QuerySet.update_one`,
:meth:`~mongoengine.queryset.QuerySet.update` and
:meth:`~mongoengine.queryset.QuerySet.modify` methods on a
:class:`~mongoengine.queryset.QuerySet` or
:meth:`~mongoengine.Document.modify` and
:meth:`~mongoengine.Document.save` (with :attr:`save_condition` argument) on a
:class:`~mongoengine.Document`.
There are several different "modifiers" that you may use with these methods:
* ``set`` -- set a particular value
* ``unset`` -- delete a particular value (since MongoDB v1.3+)
* ``unset`` -- delete a particular value (since MongoDB v1.3)
* ``inc`` -- increment a value by a given amount
* ``dec`` -- decrement a value by a given amount
* ``pop`` -- remove the last item from a list
* ``push`` -- append a value to a list
* ``push_all`` -- append several values to a list
* ``pop`` -- remove the first or last element of a list
* ``pop`` -- remove the first or last element of a list `depending on the value`_
* ``pull`` -- remove a value from a list
* ``pull_all`` -- remove several values from a list
* ``add_to_set`` -- add value to a list only if its not in the list already
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/
The syntax for atomic updates is similar to the querying syntax, but the
modifier comes before the field, not after it::
@@ -425,7 +542,14 @@ modifier comes before the field, not after it::
>>> post.tags
['database', 'nosql']
.. note ::
.. note::
If no modifier operator is specified the default will be ``$set``. So the following sentences are identical::
>>> BlogPost.objects(id=post.id).update(title='Example Post')
>>> BlogPost.objects(id=post.id).update(set__title='Example Post')
.. note::
In version 0.5 the :meth:`~mongoengine.Document.save` runs atomic updates
on changed documents by tracking changes to that document.
@@ -441,7 +565,7 @@ cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
>>> post.tags
['database', 'mongodb']
.. note ::
.. note::
Currently only top level lists are handled, future versions of mongodb /
pymongo plan to support nested positional operators. See `The $ positional
operator <http://www.mongodb.org/display/DOCS/Updating#Updating-The%24positionaloperator>`_.
@@ -483,7 +607,7 @@ Some variables are made available in the scope of the Javascript function:
The following example demonstrates the intended usage of
:meth:`~mongoengine.queryset.QuerySet.exec_js` by defining a function that sums
over a field on a document (this functionality is already available throught
over a field on a document (this functionality is already available through
:meth:`~mongoengine.queryset.QuerySet.sum` but is shown here for sake of
example)::
@@ -510,7 +634,7 @@ Javascript code. When accessing a field on a collection object, use
square-bracket notation, and prefix the MongoEngine field name with a tilde.
The field name that follows the tilde will be translated to the name used in
the database. Note that when referring to fields on embedded documents,
the name of the :class:`~mongoengine.EmbeddedDocumentField`, followed by a dot,
the name of the :class:`~mongoengine.fields.EmbeddedDocumentField`, followed by a dot,
should be used before the name of the field on the embedded document. The
following example shows how the substitutions are made::

View File

@@ -1,5 +1,6 @@
.. _signals:
=======
Signals
=======
@@ -7,32 +8,95 @@ Signals
.. note::
Signal support is provided by the excellent `blinker`_ library and
will gracefully fall back if it is not available.
Signal support is provided by the excellent `blinker`_ library. If you wish
to enable signal support this library must be installed, though it is not
required for MongoEngine to function.
Overview
--------
The following document signals exist in MongoEngine and are pretty self-explanatory:
Signals are found within the `mongoengine.signals` module. Unless
specified signals receive no additional arguments beyond the `sender` class and
`document` instance. Post-signals are only called if there were no exceptions
raised during the processing of their related function.
* `mongoengine.signals.pre_init`
* `mongoengine.signals.post_init`
* `mongoengine.signals.pre_save`
* `mongoengine.signals.post_save`
* `mongoengine.signals.pre_delete`
* `mongoengine.signals.post_delete`
* `mongoengine.signals.pre_bulk_insert`
* `mongoengine.signals.post_bulk_insert`
Available signals include:
Example usage::
`pre_init`
Called during the creation of a new :class:`~mongoengine.Document` or
:class:`~mongoengine.EmbeddedDocument` instance, after the constructor
arguments have been collected but before any additional processing has been
done to them. (I.e. assignment of default values.) Handlers for this signal
are passed the dictionary of arguments using the `values` keyword argument
and may modify this dictionary prior to returning.
`post_init`
Called after all processing of a new :class:`~mongoengine.Document` or
:class:`~mongoengine.EmbeddedDocument` instance has been completed.
`pre_save`
Called within :meth:`~mongoengine.Document.save` prior to performing
any actions.
`pre_save_post_validation`
Called within :meth:`~mongoengine.Document.save` after validation
has taken place but before saving.
`post_save`
Called within :meth:`~mongoengine.Document.save` after all actions
(validation, insert/update, cascades, clearing dirty flags) have completed
successfully. Passed the additional boolean keyword argument `created` to
indicate if the save was an insert or an update.
`pre_delete`
Called within :meth:`~mongoengine.Document.delete` prior to
attempting the delete operation.
`post_delete`
Called within :meth:`~mongoengine.Document.delete` upon successful
deletion of the record.
`pre_bulk_insert`
Called after validation of the documents to insert, but prior to any data
being written. In this case, the `document` argument is replaced by a
`documents` argument representing the list of documents being inserted.
`post_bulk_insert`
Called after a successful bulk insert operation. As per `pre_bulk_insert`,
the `document` argument is omitted and replaced with a `documents` argument.
An additional boolean argument, `loaded`, identifies the contents of
`documents` as either :class:`~mongoengine.Document` instances when `True` or
simply a list of primary key values for the inserted records if `False`.
Attaching Events
----------------
After writing a handler function like the following::
import logging
from datetime import datetime
from mongoengine import *
from mongoengine import signals
def update_modified(sender, document):
document.modified = datetime.utcnow()
You attach the event handler to your :class:`~mongoengine.Document` or
:class:`~mongoengine.EmbeddedDocument` subclass::
class Record(Document):
modified = DateTimeField()
signals.pre_save.connect(update_modified)
While this is not the most elaborate document model, it does demonstrate the
concepts involved. As a more complete demonstration you can also define your
handlers within your subclass::
class Author(Document):
name = StringField()
def __unicode__(self):
return self.name
@classmethod
def pre_save(cls, sender, document, **kwargs):
logging.debug("Pre Save: %s" % document.name)
@@ -49,12 +113,33 @@ Example usage::
signals.pre_save.connect(Author.pre_save, sender=Author)
signals.post_save.connect(Author.post_save, sender=Author)
Finally, you can also use this small decorator to quickly create a number of
signals and attach them to your :class:`~mongoengine.Document` or
:class:`~mongoengine.EmbeddedDocument` subclasses as class decorators::
ReferenceFields and signals
---------------------------
def handler(event):
"""Signal decorator to allow use of callback functions as class decorators."""
def decorator(fn):
def apply(cls):
event.connect(fn, sender=cls)
return cls
fn.apply = apply
return fn
return decorator
Using the first example of updating a modification time the code is now much
cleaner looking while still allowing manual execution of the callback::
@handler(signals.pre_save)
def update_modified(sender, document):
document.modified = datetime.utcnow()
@update_modified.apply
class Record(Document):
modified = DateTimeField()
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

View File

@@ -0,0 +1,51 @@
===========
Text Search
===========
After MongoDB 2.4 version, supports search documents by text indexes.
Defining a Document with text index
===================================
Use the *$* prefix to set a text index, Look the declaration::
class News(Document):
title = StringField()
content = StringField()
is_active = BooleanField()
meta = {'indexes': [
{'fields': ['$title', "$content"],
'default_language': 'english',
'weights': {'title': 10, 'content': 2}
}
]}
Querying
========
Saving a document::
News(title="Using mongodb text search",
content="Testing text search").save()
News(title="MongoEngine 0.9 released",
content="Various improvements").save()
Next, start a text search using :attr:`QuerySet.search_text` method::
document = News.objects.search_text('testing').first()
document.title # may be: "Using mongodb text search"
document = News.objects.search_text('released').first()
document.title # may be: "MongoEngine 0.9 released"
Ordering by text score
======================
::
objects = News.objects.search('mongo').order_by('$text_score')

View File

@@ -7,16 +7,18 @@ MongoDB. To install it, simply run
.. code-block:: console
# pip install -U mongoengine
$ pip install -U mongoengine
:doc:`tutorial`
Start here for a quick overview.
A quick tutorial building a tumblelog to get you up and running with
MongoEngine.
:doc:`guide/index`
The Full guide to MongoEngine
The Full guide to MongoEngine --- from modeling documents to storing files,
from querying for data to firing signals and *everything* between.
:doc:`apireference`
The complete API documentation.
The complete API documentation --- the innards of documents, querysets and fields.
:doc:`upgrade`
How to upgrade MongoEngine.
@@ -28,35 +30,50 @@ Community
---------
To get help with using MongoEngine, use the `MongoEngine Users mailing list
<http://groups.google.com/group/mongoengine-users>`_ or come chat on the
`#mongoengine IRC channel <irc://irc.freenode.net/mongoengine>`_.
<http://groups.google.com/group/mongoengine-users>`_ or the ever popular
`stackoverflow <http://www.stackoverflow.com>`_.
Contributing
------------
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ and
contributions are always encouraged. Contributions can be as simple as
minor tweaks to this documentation. To contribute, fork the project on
**Yes please!** We are always looking for contributions, additions and improvements.
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_
and contributions are always encouraged. Contributions can be as simple as
minor tweaks to this documentation, the website or the core.
To contribute, fork the project on
`GitHub <http://github.com/MongoEngine/mongoengine>`_ and send a
pull request.
Also, you can join the developers' `mailing list
<http://groups.google.com/group/mongoengine-dev>`_.
Changes
-------
See the :doc:`changelog` for a full list of changes to MongoEngine and
:doc:`upgrade` for upgrade information.
.. toctree::
:hidden:
.. note:: Always read and test the `upgrade <upgrade>`_ documentation before
putting updates live in production **;)**
tutorial
guide/index
apireference
django
changelog
upgrade
Offline Reading
---------------
Download the docs in `pdf <https://media.readthedocs.org/pdf/mongoengine-odm/latest/mongoengine-odm.pdf>`_
or `epub <https://media.readthedocs.org/epub/mongoengine-odm/latest/mongoengine-odm.epub>`_
formats for offline reading.
.. toctree::
:maxdepth: 1
:numbered:
:hidden:
tutorial
guide/index
apireference
changelog
upgrade
django
Indices and tables
------------------

View File

@@ -1,73 +1,83 @@
========
Tutorial
========
This tutorial introduces **MongoEngine** by means of example --- we will walk
through how to create a simple **Tumblelog** application. A Tumblelog is a type
of blog where posts are not constrained to being conventional text-based posts.
As well as text-based entries, users may post images, links, videos, etc. For
simplicity's sake, we'll stick to text, image and link entries in our
application. As the purpose of this tutorial is to introduce MongoEngine, we'll
through how to create a simple **Tumblelog** application. A tumblelog is a
blog that supports mixed media content, including text, images, links, video,
audio, etc. For simplicity's sake, we'll stick to text, image, and link
entries. As the purpose of this tutorial is to introduce MongoEngine, we'll
focus on the data-modelling side of the application, leaving out a user
interface.
Getting started
===============
Before we start, make sure that a copy of MongoDB is running in an accessible
location --- running it locally will be easier, but if that is not an option
then it may be run on a remote server.
then it may be run on a remote server. If you haven't installed MongoEngine,
simply use pip to install it like so::
$ pip install mongoengine
Before we can start using MongoEngine, we need to tell it how to connect to our
instance of :program:`mongod`. For this we use the :func:`~mongoengine.connect`
function. The only argument we need to provide is the name of the MongoDB
database to use::
function. If running locally, the only argument we need to provide is the name
of the MongoDB database to use::
from mongoengine import *
connect('tumblelog')
For more information about connecting to MongoDB see :ref:`guide-connecting`.
There are lots of options for connecting to MongoDB, for more information about
them see the :ref:`guide-connecting` guide.
Defining our documents
======================
MongoDB is *schemaless*, which means that no schema is enforced by the database
--- we may add and remove fields however we want and MongoDB won't complain.
This makes life a lot easier in many regards, especially when there is a change
to the data model. However, defining schemata for our documents can help to
iron out bugs involving incorrect types or missing fields, and also allow us to
to the data model. However, defining schemas for our documents can help to iron
out bugs involving incorrect types or missing fields, and also allow us to
define utility methods on our documents in the same way that traditional
:abbr:`ORMs (Object-Relational Mappers)` do.
In our Tumblelog application we need to store several different types of
information. We will need to have a collection of **users**, so that we may
link posts to an individual. We also need to store our different types
**posts** (text, image and link) in the database. To aid navigation of our
information. We will need to have a collection of **users**, so that we may
link posts to an individual. We also need to store our different types of
**posts** (eg: text, image and link) in the database. To aid navigation of our
Tumblelog, posts may have **tags** associated with them, so that the list of
posts shown to the user may be limited to posts that have been assigned a
specified tag. Finally, it would be nice if **comments** could be added to
posts. We'll start with **users**, as the others are slightly more involved.
specific tag. Finally, it would be nice if **comments** could be added to
posts. We'll start with **users**, as the other document models are slightly
more involved.
Users
-----
Just as if we were using a relational database with an ORM, we need to define
which fields a :class:`User` may have, and what their types will be::
which fields a :class:`User` may have, and what types of data they might store::
class User(Document):
email = StringField(required=True)
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
This looks similar to how a the structure of a table would be defined in a
This looks similar to how the structure of a table would be defined in a
regular ORM. The key difference is that this schema will never be passed on to
MongoDB --- this will only be enforced at the application level. Also, the User
documents will be stored in a MongoDB *collection* rather than a table.
MongoDB --- this will only be enforced at the application level, making future
changes easy to manage. Also, the User documents will be stored in a
MongoDB *collection* rather than a table.
Posts, Comments and Tags
------------------------
Now we'll think about how to store the rest of the information. If we were
using a relational database, we would most likely have a table of **posts**, a
table of **comments** and a table of **tags**. To associate the comments with
individual posts, we would put a column in the comments table that contained a
foreign key to the posts table. We'd also need a link table to provide the
foreign key to the posts table. We'd also need a link table to provide the
many-to-many relationship between posts and tags. Then we'd need to address the
problem of storing the specialised post-types (text, image and link). There are
several ways we can achieve this, but each of them have their problems --- none
@@ -75,21 +85,25 @@ of them stand out as particularly intuitive solutions.
Posts
^^^^^
But MongoDB *isn't* a relational database, so we're not going to do it that
Happily mongoDB *isn't* a relational database, so we're not going to do it that
way. As it turns out, we can use MongoDB's schemaless nature to provide us with
a much nicer solution. We will store all of the posts in *one collection* ---
each post type will just have the fields it needs. If we later want to add
a much nicer solution. We will store all of the posts in *one collection* and
each post type will only store the fields it needs. If we later want to add
video posts, we don't have to modify the collection at all, we just *start
using* the new fields we need to support video posts. This fits with the
Object-Oriented principle of *inheritance* nicely. We can think of
:class:`Post` as a base class, and :class:`TextPost`, :class:`ImagePost` and
:class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports
this kind of modelling out of the box::
this kind of modeling out of the box --- all you need do is turn on inheritance
by setting :attr:`allow_inheritance` to True in the :attr:`meta`::
class Post(Document):
title = StringField(max_length=120, required=True)
author = ReferenceField(User)
meta = {'allow_inheritance': True}
class TextPost(Post):
content = StringField()
@@ -100,20 +114,21 @@ this kind of modelling out of the box::
link_url = StringField()
We are storing a reference to the author of the posts using a
:class:`~mongoengine.ReferenceField` object. These are similar to foreign key
:class:`~mongoengine.fields.ReferenceField` object. These are similar to foreign key
fields in traditional ORMs, and are automatically translated into references
when they are saved, and dereferenced when they are loaded.
Tags
^^^^
Now that we have our Post models figured out, how will we attach tags to them?
MongoDB allows us to store lists of items natively, so rather than having a
link table, we can just store a list of tags in each post. So, for both
efficiency and simplicity's sake, we'll store the tags as strings directly
within the post, rather than storing references to tags in a separate
collection. Especially as tags are generally very short (often even shorter
than a document's id), this denormalisation won't impact very strongly on the
size of our database. So let's take a look that the code our modified
than a document's id), this denormalization won't impact the size of the
database very strongly. Let's take a look at the code of our modified
:class:`Post` class::
class Post(Document):
@@ -121,16 +136,19 @@ size of our database. So let's take a look that the code our modified
author = ReferenceField(User)
tags = ListField(StringField(max_length=30))
The :class:`~mongoengine.ListField` object that is used to define a Post's tags
The :class:`~mongoengine.fields.ListField` object that is used to define a Post's tags
takes a field object as its first argument --- this means that you can have
lists of any type of field (including lists). Note that we don't need to
modify the specialised post types as they all inherit from :class:`Post`.
lists of any type of field (including lists).
.. note:: We don't need to modify the specialized post types as they all
inherit from :class:`Post`.
Comments
^^^^^^^^
A comment is typically associated with *one* post. In a relational database, to
display a post with its comments, we would have to retrieve the post from the
database, then query the database again for the comments associated with the
database and then query the database again for the comments associated with the
post. This works, but there is no real reason to be storing the comments
separately from their associated posts, other than to work around the
relational model. Using MongoDB we can store the comments as a list of
@@ -155,7 +173,7 @@ We can then store a list of comment documents in our post document::
Handling deletions of references
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :class:`~mongoengine.ReferenceField` object takes a keyword
The :class:`~mongoengine.fields.ReferenceField` object takes a keyword
`reverse_delete_rule` for handling deletion rules if the reference is deleted.
To delete all the posts if a user is deleted set the rule::
@@ -165,9 +183,9 @@ To delete all the posts if a user is deleted set the rule::
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
See :class:`~mongoengine.ReferenceField` for more information.
See :class:`~mongoengine.fields.ReferenceField` for more information.
..note::
.. note::
MapFields and DictFields currently don't support automatic handling of
deleted references
@@ -178,33 +196,37 @@ Now that we've defined how our documents will be structured, let's start adding
some documents to the database. Firstly, we'll need to create a :class:`User`
object::
john = User(email='jdoe@example.com', first_name='John', last_name='Doe')
john.save()
ross = User(email='ross@example.com', first_name='Ross', last_name='Lawley').save()
Note that we could have also defined our user using attribute syntax::
.. note::
We could have also defined our user using attribute syntax::
john = User(email='jdoe@example.com')
john.first_name = 'John'
john.last_name = 'Doe'
john.save()
ross = User(email='ross@example.com')
ross.first_name = 'Ross'
ross.last_name = 'Lawley'
ross.save()
Now that we've got our user in the database, let's add a couple of posts::
Assign another user to a variable called ``john``, just like we did above with
``ross``.
Now that we've got our users in the database, let's add a couple of posts::
post1 = TextPost(title='Fun with MongoEngine', author=john)
post1.content = 'Took a look at MongoEngine today, looks pretty cool.'
post1.tags = ['mongodb', 'mongoengine']
post1.save()
post2 = LinkPost(title='MongoEngine Documentation', author=john)
post2.link_url = 'http://tractiondigital.com/labs/mongoengine/docs'
post2 = LinkPost(title='MongoEngine Documentation', author=ross)
post2.link_url = 'http://docs.mongoengine.com/'
post2.tags = ['mongoengine']
post2.save()
Note that if you change a field on a object that has already been saved, then
call :meth:`save` again, the document will be updated.
.. note:: If you change a field on an object that has already been saved and
then call :meth:`save` again, the document will be updated.
Accessing our data
==================
So now we've got a couple of posts in our database, how do we display them?
Each document class (i.e. any class that inherits either directly or indirectly
from :class:`~mongoengine.Document`) has an :attr:`objects` attribute, which is
@@ -212,16 +234,17 @@ used to access the documents in the database collection associated with that
class. So let's see how we can get our posts' titles::
for post in Post.objects:
print post.title
print(post.title)
Retrieving type-specific information
------------------------------------
This will print the titles of our posts, one on each line. But What if we want
This will print the titles of our posts, one on each line. But what if we want
to access the type-specific data (link_url, content, etc.)? One way is simply
to use the :attr:`objects` attribute of a subclass of :class:`Post`::
for post in TextPost.objects:
print post.content
print(post.content)
Using TextPost's :attr:`objects` attribute only returns documents that were
created using :class:`TextPost`. Actually, there is a more general rule here:
@@ -238,22 +261,21 @@ instances of :class:`Post` --- they were instances of the subclass of
practice::
for post in Post.objects:
print post.title
print '=' * len(post.title)
print(post.title)
print('=' * len(post.title))
if isinstance(post, TextPost):
print post.content
print(post.content)
if isinstance(post, LinkPost):
print 'Link:', post.link_url
print
print('Link: {}'.format(post.link_url))
This would print the title of each post, followed by the content if it was a
text post, and "Link: <url>" if it was a link post.
Searching our posts by tag
--------------------------
The :attr:`objects` attribute of a :class:`~mongoengine.Document` is actually a
:class:`~mongoengine.queryset.QuerySet` object. This lazily queries the
database only when you need the data. It may also be filtered to narrow down
@@ -261,7 +283,7 @@ your query. Let's adjust our query so that only posts with the tag "mongodb"
are returned::
for post in Post.objects(tags='mongodb'):
print post.title
print(post.title)
There are also methods available on :class:`~mongoengine.queryset.QuerySet`
objects that allow different results to be returned, for example, calling
@@ -270,5 +292,11 @@ the first matched by the query you provide. Aggregation functions may also be
used on :class:`~mongoengine.queryset.QuerySet` objects::
num_posts = Post.objects(tags='mongodb').count()
print 'Found %d posts with tag "mongodb"' % num_posts
print('Found {} posts with tag "mongodb"'.format(num_posts))
Learning more about MongoEngine
-------------------------------
If you got this far you've made a great start, so well done! The next step on
your MongoEngine journey is the `full user guide <guide/index.html>`_, where
you can learn in-depth about how to use MongoEngine and MongoDB.

View File

@@ -1,12 +1,444 @@
=========
#########
Upgrading
=========
#########
0.6 to 0.7
Development
***********
(Fill this out whenever you introduce breaking changes to MongoEngine)
0.14.0
******
This release includes a few bug fixes and a significant code cleanup. The most
important change is that `QuerySet.as_pymongo` no longer supports a
`coerce_types` mode. If you used it in the past, a) please let us know of your
use case, b) you'll need to override `as_pymongo` to get the desired outcome.
This release also makes the EmbeddedDocument not hashable by default. If you
use embedded documents in sets or dictionaries, you might have to override
`__hash__` and implement a hashing logic specific to your use case. See #1528
for the reason behind this change.
0.13.0
******
This release adds Unicode support to the `EmailField` and changes its
structure significantly. Previously, email addresses containing Unicode
characters didn't work at all. Starting with v0.13.0, domains with Unicode
characters are supported out of the box, meaning some emails that previously
didn't pass validation now do. Make sure the rest of your application can
accept such email addresses. Additionally, if you subclassed the `EmailField`
in your application and overrode `EmailField.EMAIL_REGEX`, you will have to
adjust your code to override `EmailField.USER_REGEX`, `EmailField.DOMAIN_REGEX`,
and potentially `EmailField.UTF8_USER_REGEX`.
0.12.0
******
This release includes various fixes for the `BaseQuerySet` methods and how they
are chained together. Since version 0.10.1 applying limit/skip/hint/batch_size
to an already-existing queryset wouldn't modify the underlying PyMongo cursor.
This has been fixed now, so you'll need to make sure that your code didn't rely
on the broken implementation.
Additionally, a public `BaseQuerySet.clone_into` has been renamed to a private
`_clone_into`. If you directly used that method in your code, you'll need to
rename its occurrences.
0.11.0
******
This release includes a major rehaul of MongoEngine's code quality and
introduces a few breaking changes. It also touches many different parts of
the package and although all the changes have been tested and scrutinized,
you're encouraged to thorougly test the upgrade.
First breaking change involves renaming `ConnectionError` to `MongoEngineConnectionError`.
If you import or catch this exception, you'll need to rename it in your code.
Second breaking change drops Python v2.6 support. If you run MongoEngine on
that Python version, you'll need to upgrade it first.
Third breaking change drops an old backward compatibility measure where
`from mongoengine.base import ErrorClass` would work on top of
`from mongoengine.errors import ErrorClass` (where `ErrorClass` is e.g.
`ValidationError`). If you import any exceptions from `mongoengine.base`,
change it to `mongoengine.errors`.
0.10.8
******
This version fixed an issue where specifying a MongoDB URI host would override
more information than it should. These changes are minor, but they still
subtly modify the connection logic and thus you're encouraged to test your
MongoDB connection before shipping v0.10.8 in production.
0.10.7
******
`QuerySet.aggregate_sum` and `QuerySet.aggregate_average` are dropped. Use
`QuerySet.sum` and `QuerySet.average` instead which use the aggreation framework
by default from now on.
0.9.0
*****
The 0.8.7 package on pypi was corrupted. If upgrading from 0.8.7 to 0.9.0 please follow: ::
pip uninstall pymongo
pip uninstall mongoengine
pip install pymongo==2.8
pip install mongoengine
0.8.7
*****
Calling reload on deleted / nonexistent documents now raises a DoesNotExist
exception.
0.8.2 to 0.8.3
**************
Minor change that may impact users:
DynamicDocument fields are now stored in creation order after any declared
fields. Previously they were stored alphabetically.
0.7 to 0.8
**********
There have been numerous backwards breaking changes in 0.8. The reasons for
these are to ensure that MongoEngine has sane defaults going forward and that it
performs the best it can out of the box. Where possible there have been
FutureWarnings to help get you ready for the change, but that hasn't been
possible for the whole of the release.
.. warning:: Breaking changes - test upgrading on a test system before putting
live. There maybe multiple manual steps in migrating and these are best honed
on a staging / test system.
Python and PyMongo
==================
MongoEngine requires python 2.6 (or above) and pymongo 2.5 (or above)
Data Model
==========
Inheritance
-----------
The inheritance model has changed, we no longer need to store an array of
:attr:`types` with the model we can just use the classname in :attr:`_cls`.
This means that you will have to update your indexes for each of your
inherited classes like so: ::
# 1. Declaration of the class
class Animal(Document):
name = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['name']
}
# 2. Remove _types
collection = Animal._get_collection()
collection.update({}, {"$unset": {"_types": 1}}, multi=True)
# 3. Confirm extra data is removed
count = collection.find({'_types': {"$exists": True}}).count()
assert count == 0
# 4. Remove indexes
info = collection.index_information()
indexes_to_drop = [key for key, value in info.iteritems()
if '_types' in dict(value['key'])]
for index in indexes_to_drop:
collection.drop_index(index)
# 5. Recreate indexes
Animal.ensure_indexes()
Document Definition
-------------------
The default for inheritance has changed - it is now off by default and
:attr:`_cls` will not be stored automatically with the class. So if you extend
your :class:`~mongoengine.Document` or :class:`~mongoengine.EmbeddedDocuments`
you will need to declare :attr:`allow_inheritance` in the meta data like so: ::
class Animal(Document):
name = StringField()
meta = {'allow_inheritance': True}
Previously, if you had data in the database that wasn't defined in the Document
definition, it would set it as an attribute on the document. This is no longer
the case and the data is set only in the ``document._data`` dictionary: ::
>>> from mongoengine import *
>>> class Animal(Document):
... name = StringField()
...
>>> cat = Animal(name="kit", size="small")
# 0.7
>>> cat.size
u'small'
# 0.8
>>> cat.size
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute 'size'
The Document class has introduced a reserved function `clean()`, which will be
called before saving the document. If your document class happens to have a method
with the same name, please try to rename it.
def clean(self):
pass
ReferenceField
--------------
ReferenceFields now store ObjectIds by default - this is more efficient than
DBRefs as we already know what Document types they reference::
# Old code
class Animal(Document):
name = ReferenceField('self')
# New code to keep dbrefs
class Animal(Document):
name = ReferenceField('self', dbref=True)
To migrate all the references you need to touch each object and mark it as dirty
eg::
# Doc definition
class Person(Document):
name = StringField()
parent = ReferenceField('self')
friends = ListField(ReferenceField('self'))
# Mark all ReferenceFields as dirty and save
for p in Person.objects:
p._mark_as_changed('parent')
p._mark_as_changed('friends')
p.save()
`An example test migration for ReferenceFields is available on github
<https://github.com/MongoEngine/mongoengine/blob/master/tests/migration/refrencefield_dbref_to_object_id.py>`_.
.. Note:: Internally mongoengine handles ReferenceFields the same, so they are
converted to DBRef on loading and ObjectIds or DBRefs depending on settings
on storage.
UUIDField
---------
UUIDFields now default to storing binary values::
# Old code
class Animal(Document):
uuid = UUIDField()
# New code
class Animal(Document):
uuid = UUIDField(binary=False)
To migrate all the uuids you need to touch each object and mark it as dirty
eg::
# Doc definition
class Animal(Document):
uuid = UUIDField()
# Mark all UUIDFields as dirty and save
for a in Animal.objects:
a._mark_as_changed('uuid')
a.save()
`An example test migration for UUIDFields is available on github
<https://github.com/MongoEngine/mongoengine/blob/master/tests/migration/uuidfield_to_binary.py>`_.
DecimalField
------------
DecimalFields now store floats - previously it was storing strings and that
made it impossible to do comparisons when querying correctly.::
# Old code
class Person(Document):
balance = DecimalField()
# New code
class Person(Document):
balance = DecimalField(force_string=True)
To migrate all the DecimalFields you need to touch each object and mark it as dirty
eg::
# Doc definition
class Person(Document):
balance = DecimalField()
# Mark all DecimalField's as dirty and save
for p in Person.objects:
p._mark_as_changed('balance')
p.save()
.. note:: DecimalFields have also been improved with the addition of precision
and rounding. See :class:`~mongoengine.fields.DecimalField` for more information.
`An example test migration for DecimalFields is available on github
<https://github.com/MongoEngine/mongoengine/blob/master/tests/migration/decimalfield_as_float.py>`_.
Cascading Saves
---------------
To improve performance document saves will no longer automatically cascade.
Any changes to a Document's references will either have to be saved manually or
you will have to explicitly tell it to cascade on save::
# At the class level:
class Person(Document):
meta = {'cascade': True}
# Or on save:
my_document.save(cascade=True)
Storage
-------
Document and Embedded Documents are now serialized based on declared field order.
Previously, the data was passed to mongodb as a dictionary and which meant that
order wasn't guaranteed - so things like ``$addToSet`` operations on
:class:`~mongoengine.EmbeddedDocument` could potentially fail in unexpected
ways.
If this impacts you, you may want to rewrite the objects using the
``doc.mark_as_dirty('field')`` pattern described above. If you are using a
compound primary key then you will need to ensure the order is fixed and match
your EmbeddedDocument to that order.
Querysets
=========
Attack of the clones
--------------------
Querysets now return clones and should no longer be considered editable in
place. This brings us in line with how Django's querysets work and removes a
long running gotcha. If you edit your querysets inplace you will have to
update your code like so: ::
# Old code:
mammals = Animal.objects(type="mammal")
mammals.filter(order="Carnivora") # Returns a cloned queryset that isn't assigned to anything - so this will break in 0.8
[m for m in mammals] # This will return all mammals in 0.8 as the 2nd filter returned a new queryset
# Update example a) assign queryset after a change:
mammals = Animal.objects(type="mammal")
carnivores = mammals.filter(order="Carnivora") # Reassign the new queryset so filter can be applied
[m for m in carnivores] # This will return all carnivores
# Update example b) chain the queryset:
mammals = Animal.objects(type="mammal").filter(order="Carnivora") # The final queryset is assgined to mammals
[m for m in mammals] # This will return all carnivores
Len iterates the queryset
-------------------------
If you ever did `len(queryset)` it previously did a `count()` under the covers,
this caused some unusual issues. As `len(queryset)` is most often used by
`list(queryset)` we now cache the queryset results and use that for the length.
This isn't as performant as a `count()` and if you aren't iterating the
queryset you should upgrade to use count::
# Old code
len(Animal.objects(type="mammal"))
# New code
Animal.objects(type="mammal").count()
.only() now inline with .exclude()
----------------------------------
The behaviour of `.only()` was highly ambiguous, now it works in mirror fashion
to `.exclude()`. Chaining `.only()` calls will increase the fields required::
# Old code
Animal.objects().only(['type', 'name']).only('name', 'order') # Would have returned just `name`
# New code
Animal.objects().only('name')
# Note:
Animal.objects().only(['name']).only('order') # Now returns `name` *and* `order`
Client
======
PyMongo 2.4 came with a new connection client; MongoClient_ and started the
depreciation of the old :class:`~pymongo.connection.Connection`. MongoEngine
now uses the latest `MongoClient` for connections. By default operations were
`safe` but if you turned them off or used the connection directly this will
impact your queries.
Querysets
---------
Safe
^^^^
`safe` has been depreciated in the new MongoClient connection. Please use
`write_concern` instead. As `safe` always defaulted as `True` normally no code
change is required. To disable confirmation of the write just pass `{"w": 0}`
eg: ::
# Old
Animal(name="Dinasour").save(safe=False)
# new code:
Animal(name="Dinasour").save(write_concern={"w": 0})
Write Concern
^^^^^^^^^^^^^
`write_options` has been replaced with `write_concern` to bring it inline with
pymongo. To upgrade simply rename any instances where you used the `write_option`
keyword to `write_concern` like so::
# Old code:
Animal(name="Dinasour").save(write_options={"w": 2})
# new code:
Animal(name="Dinasour").save(write_concern={"w": 2})
Indexes
=======
Index methods are no longer tied to querysets but rather to the document class.
Although `QuerySet._ensure_indexes` and `QuerySet.ensure_index` still exist.
They should be replaced with :func:`~mongoengine.Document.ensure_indexes` /
:func:`~mongoengine.Document.ensure_index`.
SequenceFields
==============
:class:`~mongoengine.fields.SequenceField` now inherits from `BaseField` to
allow flexible storage of the calculated value. As such MIN and MAX settings
are no longer handled.
.. _MongoClient: http://blog.mongodb.org/post/36666163412/introducing-mongoclient
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
@@ -20,11 +452,11 @@ via `save` eg ::
# Or in code:
my_document.save(cascade=True)
.. note ::
.. 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`
@@ -53,7 +485,7 @@ migrate ::
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
@@ -62,14 +494,14 @@ 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
==========
**********
Embedded Documents - if you had a `pk` field you will have to rename it from
`_id` to `pk` as pk is no longer a property of Embedded Documents.
@@ -84,26 +516,26 @@ 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
don't define `allow_inheritance` in their meta.
don't define :attr:`allow_inheritance` in their meta.
You may need to update pyMongo to 2.0 for use with Sharding.
0.4 to 0.5
===========
**********
There have been the following backwards incompatibilities from 0.4 to 0.5. The
main areas of changed are: choices in fields, map_reduce and collection names.
Choice options:
---------------
===============
Are now expected to be an iterable of tuples, with the first element in each
Are now expected to be an iterable of tuples, with the first element in each
tuple being the actual value to be stored. The second element is the
human-readable name for the option.
PyMongo / MongoDB
-----------------
=================
map reduce now requires pymongo 1.11+- The pymongo `merge_output` and
`reduce_output` parameters, have been depreciated.
@@ -117,10 +549,10 @@ such the following have been changed:
Default collection naming
-------------------------
=========================
Previously it was just lowercase, its now much more pythonic and readable as
its lowercase and underscores, previously ::
Previously it was just lowercase, it's now much more pythonic and readable as
it's lowercase and underscores, previously ::
class MyAceDocument(Document):
pass
@@ -183,3 +615,9 @@ Alternatively, you can rename your collections eg ::
else:
print "Upgraded collection names"
mongodb 1.8 > 2.0 +
===================
It's been reported that indexes may need to be recreated to the newer version of indexes.
To do this drop indexes and call ``ensure_indexes`` on each model.

View File

@@ -1,23 +1,36 @@
import document
from document import *
import fields
from fields import *
import connection
from connection import *
import queryset
from queryset import *
import signals
from signals import *
# Import submodules so that we can expose their __all__
from mongoengine import connection
from mongoengine import document
from mongoengine import errors
from mongoengine import fields
from mongoengine import queryset
from mongoengine import signals
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
queryset.__all__ + signals.__all__)
# Import everything from each submodule so that it can be accessed via
# mongoengine, e.g. instead of `from mongoengine.connection import connect`,
# users can simply use `from mongoengine import connect`, or even
# `from mongoengine import *` and then `connect('testdb')`.
from mongoengine.connection import *
from mongoengine.document import *
from mongoengine.errors import *
from mongoengine.fields import *
from mongoengine.queryset import *
from mongoengine.signals import *
VERSION = (0, 7, 9)
__all__ = (list(document.__all__) + list(fields.__all__) +
list(connection.__all__) + list(queryset.__all__) +
list(signals.__all__) + list(errors.__all__))
VERSION = (0, 14, 0)
def get_version():
if isinstance(VERSION[-1], basestring):
return '.'.join(map(str, VERSION[:-1])) + VERSION[-1]
"""Return the VERSION as a string, e.g. for VERSION == (0, 10, 7),
return '0.10.7'.
"""
return '.'.join(map(str, VERSION))
__version__ = get_version()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
# Base module is split into several files for convenience. Files inside of
# this module should import from a specific submodule (e.g.
# `from mongoengine.base.document import BaseDocument`), but all of the
# other modules should import directly from the top-level module (e.g.
# `from mongoengine.base import BaseDocument`). This approach is cleaner and
# also helps with cyclical import errors.
from mongoengine.base.common import *
from mongoengine.base.datastructures import *
from mongoengine.base.document import *
from mongoengine.base.fields import *
from mongoengine.base.metaclasses import *
__all__ = (
# common
'UPDATE_OPERATORS', '_document_registry', 'get_document',
# datastructures
'BaseDict', 'BaseList', 'EmbeddedDocumentList',
# document
'BaseDocument',
# fields
'BaseField', 'ComplexBaseField', 'ObjectIdField', 'GeoJsonBaseField',
# metaclasses
'DocumentMetaclass', 'TopLevelDocumentMetaclass'
)

View File

@@ -0,0 +1,31 @@
from mongoengine.errors import NotRegistered
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
UPDATE_OPERATORS = set(['set', 'unset', 'inc', 'dec', 'pop', 'push',
'push_all', 'pull', 'pull_all', 'add_to_set',
'set_on_insert', 'min', 'max', 'rename'])
_document_registry = {}
def get_document(name):
"""Get a document class by name."""
doc = _document_registry.get(name, None)
if not doc:
# Possible old style name
single_end = name.split('.')[-1]
compound_end = '.%s' % single_end
possible_match = [k for k in _document_registry.keys()
if k.endswith(compound_end) or k == single_end]
if len(possible_match) == 1:
doc = _document_registry.get(possible_match.pop(), None)
if not doc:
raise NotRegistered("""
`%s` has not been registered in the document registry.
Importing the document class automatically registers it, has it
been imported?
""".strip() % name)
return doc

View File

@@ -0,0 +1,486 @@
import itertools
import weakref
import six
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList')
class BaseDict(dict):
"""A special dict so we can watch any changes."""
_dereferenced = False
_instance = None
_name = None
def __init__(self, dict_items, instance, name):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(instance, (Document, EmbeddedDocument)):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseDict, self).__init__(dict_items)
def __getitem__(self, key, *args, **kwargs):
value = super(BaseDict, self).__getitem__(key)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif not isinstance(value, BaseDict) and isinstance(value, dict):
value = BaseDict(value, None, '%s.%s' % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value._instance = self._instance
elif not isinstance(value, BaseList) and isinstance(value, list):
value = BaseList(value, None, '%s.%s' % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value._instance = self._instance
return value
def __setitem__(self, key, value, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__setitem__(key, value)
def __delete__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).__delete__(*args, **kwargs)
def __delitem__(self, key, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__delitem__(key)
def __delattr__(self, key, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__delattr__(key)
def __getstate__(self):
self.instance = None
self._dereferenced = False
return self
def __setstate__(self, state):
self = state
return self
def clear(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).clear()
def pop(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).pop(*args, **kwargs)
def popitem(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).popitem()
def setdefault(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).setdefault(*args, **kwargs)
def update(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).update(*args, **kwargs)
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
if key:
self._instance._mark_as_changed('%s.%s' % (self._name, key))
else:
self._instance._mark_as_changed(self._name)
class BaseList(list):
"""A special list so we can watch any changes."""
_dereferenced = False
_instance = None
_name = None
def __init__(self, list_items, instance, name):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(instance, (Document, EmbeddedDocument)):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseList, self).__init__(list_items)
def __getitem__(self, key, *args, **kwargs):
value = super(BaseList, self).__getitem__(key)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif not isinstance(value, BaseDict) and isinstance(value, dict):
value = BaseDict(value, None, '%s.%s' % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
elif not isinstance(value, BaseList) and isinstance(value, list):
value = BaseList(value, None, '%s.%s' % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
return value
def __iter__(self):
for i in xrange(self.__len__()):
yield self[i]
def __setitem__(self, key, value, *args, **kwargs):
if isinstance(key, slice):
self._mark_as_changed()
else:
self._mark_as_changed(key)
return super(BaseList, self).__setitem__(key, value)
def __delitem__(self, key, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__delitem__(key)
def __setslice__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__setslice__(*args, **kwargs)
def __delslice__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__delslice__(*args, **kwargs)
def __getstate__(self):
self.instance = None
self._dereferenced = False
return self
def __setstate__(self, state):
self = state
return self
def __iadd__(self, other):
self._mark_as_changed()
return super(BaseList, self).__iadd__(other)
def __imul__(self, other):
self._mark_as_changed()
return super(BaseList, self).__imul__(other)
def append(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).append(*args, **kwargs)
def extend(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).extend(*args, **kwargs)
def insert(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).insert(*args, **kwargs)
def pop(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).pop(*args, **kwargs)
def remove(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).remove(*args, **kwargs)
def reverse(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).reverse()
def sort(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).sort(*args, **kwargs)
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
if key:
self._instance._mark_as_changed(
'%s.%s' % (self._name, key % len(self))
)
else:
self._instance._mark_as_changed(self._name)
class EmbeddedDocumentList(BaseList):
@classmethod
def __match_all(cls, embedded_doc, kwargs):
"""Return True if a given embedded doc matches all the filter
kwargs. If it doesn't return False.
"""
for key, expected_value in kwargs.items():
doc_val = getattr(embedded_doc, key)
if doc_val != expected_value and six.text_type(doc_val) != expected_value:
return False
return True
@classmethod
def __only_matches(cls, embedded_docs, kwargs):
"""Return embedded docs that match the filter kwargs."""
if not kwargs:
return embedded_docs
return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
self._instance = instance
def filter(self, **kwargs):
"""
Filters the list by only including embedded documents with the
given keyword arguments.
:param kwargs: The keyword arguments corresponding to the fields to
filter on. *Multiple arguments are treated as if they are ANDed
together.*
:return: A new ``EmbeddedDocumentList`` containing the matching
embedded documents.
Raises ``AttributeError`` if a given keyword is not a valid field for
the embedded document class.
"""
values = self.__only_matches(self, kwargs)
return EmbeddedDocumentList(values, self._instance, self._name)
def exclude(self, **kwargs):
"""
Filters the list by excluding embedded documents with the given
keyword arguments.
:param kwargs: The keyword arguments corresponding to the fields to
exclude on. *Multiple arguments are treated as if they are ANDed
together.*
:return: A new ``EmbeddedDocumentList`` containing the non-matching
embedded documents.
Raises ``AttributeError`` if a given keyword is not a valid field for
the embedded document class.
"""
exclude = self.__only_matches(self, kwargs)
values = [item for item in self if item not in exclude]
return EmbeddedDocumentList(values, self._instance, self._name)
def count(self):
"""
The number of embedded documents in the list.
:return: The length of the list, equivalent to the result of ``len()``.
"""
return len(self)
def get(self, **kwargs):
"""
Retrieves an embedded document determined by the given keyword
arguments.
:param kwargs: The keyword arguments corresponding to the fields to
search on. *Multiple arguments are treated as if they are ANDed
together.*
:return: The embedded document matched by the given keyword arguments.
Raises ``DoesNotExist`` if the arguments used to query an embedded
document returns no results. ``MultipleObjectsReturned`` if more
than one result is returned.
"""
values = self.__only_matches(self, kwargs)
if len(values) == 0:
raise DoesNotExist(
'%s matching query does not exist.' % self._name
)
elif len(values) > 1:
raise MultipleObjectsReturned(
'%d items returned, instead of 1' % len(values)
)
return values[0]
def first(self):
"""Return the first embedded document in the list, or ``None``
if empty.
"""
if len(self) > 0:
return self[0]
def create(self, **values):
"""
Creates a new embedded document and saves it to the database.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:param values: A dictionary of values for the embedded document.
:return: The new embedded document instance.
"""
name = self._name
EmbeddedClass = self._instance._fields[name].field.document_type_obj
self._instance[self._name].append(EmbeddedClass(**values))
return self._instance[self._name][-1]
def save(self, *args, **kwargs):
"""
Saves the ancestor document.
:param args: Arguments passed up to the ancestor Document's save
method.
:param kwargs: Keyword arguments passed up to the ancestor Document's
save method.
"""
self._instance.save(*args, **kwargs)
def delete(self):
"""
Deletes the embedded documents from the database.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:return: The number of entries deleted.
"""
values = list(self)
for item in values:
self._instance[self._name].remove(item)
return len(values)
def update(self, **update):
"""
Updates the embedded documents with the given update values.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:param update: A dictionary of update values to apply to each
embedded document.
:return: The number of entries updated.
"""
if len(update) == 0:
return 0
values = list(self)
for item in values:
for k, v in update.items():
setattr(item, k, v)
return len(values)
class StrictDict(object):
__slots__ = ()
_special_fields = set(['get', 'pop', 'iteritems', 'items', 'keys', 'create'])
_classes = {}
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
setattr(self, k, v)
def __getitem__(self, key):
key = '_reserved_' + key if key in self._special_fields else key
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key)
def __setitem__(self, key, value):
key = '_reserved_' + key if key in self._special_fields else key
return setattr(self, key, value)
def __contains__(self, key):
return hasattr(self, key)
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def pop(self, key, default=None):
v = self.get(key, default)
try:
delattr(self, key)
except AttributeError:
pass
return v
def iteritems(self):
for key in self:
yield key, self[key]
def items(self):
return [(k, self[k]) for k in iter(self)]
def iterkeys(self):
return iter(self)
def keys(self):
return list(iter(self))
def __iter__(self):
return (key for key in self.__slots__ if hasattr(self, key))
def __len__(self):
return len(list(self.iteritems()))
def __eq__(self, other):
return self.items() == other.items()
def __ne__(self, other):
return self.items() != other.items()
@classmethod
def create(cls, allowed_keys):
allowed_keys_tuple = tuple(('_reserved_' + k if k in cls._special_fields else k) for k in allowed_keys)
allowed_keys = frozenset(allowed_keys_tuple)
if allowed_keys not in cls._classes:
class SpecificStrictDict(cls):
__slots__ = allowed_keys_tuple
def __repr__(self):
return '{%s}' % ', '.join('"{0!s}": {1!r}'.format(k, v) for k, v in self.items())
cls._classes[allowed_keys] = SpecificStrictDict
return cls._classes[allowed_keys]
class SemiStrictDict(StrictDict):
__slots__ = ('_extras', )
_classes = {}
def __getattr__(self, attr):
try:
super(SemiStrictDict, self).__getattr__(attr)
except AttributeError:
try:
return self.__getattribute__('_extras')[attr]
except KeyError as e:
raise AttributeError(e)
def __setattr__(self, attr, value):
try:
super(SemiStrictDict, self).__setattr__(attr, value)
except AttributeError:
try:
self._extras[attr] = value
except AttributeError:
self._extras = {attr: value}
def __delattr__(self, attr):
try:
super(SemiStrictDict, self).__delattr__(attr)
except AttributeError:
try:
del self._extras[attr]
except KeyError as e:
raise AttributeError(e)
def __iter__(self):
try:
extras_iter = iter(self.__getattribute__('_extras'))
except AttributeError:
extras_iter = ()
return itertools.chain(super(SemiStrictDict, self).__iter__(), extras_iter)

1084
mongoengine/base/document.py Normal file

File diff suppressed because it is too large Load Diff

639
mongoengine/base/fields.py Normal file
View File

@@ -0,0 +1,639 @@
import operator
import warnings
import weakref
from bson import DBRef, ObjectId, SON
import pymongo
import six
from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import (BaseDict, BaseList,
EmbeddedDocumentList)
from mongoengine.common import _import_class
from mongoengine.errors import ValidationError
__all__ = ('BaseField', 'ComplexBaseField', 'ObjectIdField',
'GeoJsonBaseField')
class BaseField(object):
"""A base class for fields in a MongoDB document. Instances of this class
may be added to subclasses of `Document` to define a document's schema.
.. versionchanged:: 0.5 - added verbose and help text
"""
name = None
_geo_index = False
_auto_gen = False # Call `generate` to generate a value
_auto_dereference = True
# These track each time a Field instance is created. Used to retain order.
# The auto_creation_counter is used for fields that MongoEngine implicitly
# creates, creation_counter is used for all user-specified fields.
creation_counter = 0
auto_creation_counter = -1
def __init__(self, db_field=None, name=None, required=False, default=None,
unique=False, unique_with=None, primary_key=False,
validation=None, choices=None, null=False, sparse=False,
**kwargs):
"""
:param db_field: The database field to store this field in
(defaults to the name of the field)
:param name: Deprecated - use db_field
:param required: If the field is required. Whether it has to have a
value or not. Defaults to False.
:param default: (optional) The default value for this field if no value
has been set (or if the value has been unset). It can be a
callable.
:param unique: Is the field value unique or not. Defaults to False.
:param unique_with: (optional) The other field this field should be
unique with.
:param primary_key: Mark this field as the primary key. Defaults to False.
:param validation: (optional) A callable to validate the value of the
field. Generally this is deprecated in favour of the
`FIELD.validate` method
:param choices: (optional) The valid choices
:param null: (optional) Is the field value can be null. If no and there is a default value
then the default value is set
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
means that uniqueness won't be enforced for `None` values
:param **kwargs: (optional) Arbitrary indirection-free metadata for
this field can be supplied as additional keyword arguments and
accessed as attributes of the field. Must not conflict with any
existing attributes. Common metadata includes `verbose_name` and
`help_text`.
"""
self.db_field = (db_field or name) if not primary_key else '_id'
if name:
msg = 'Field\'s "name" attribute deprecated in favour of "db_field"'
warnings.warn(msg, DeprecationWarning)
self.required = required or primary_key
self.default = default
self.unique = bool(unique or unique_with)
self.unique_with = unique_with
self.primary_key = primary_key
self.validation = validation
self.choices = choices
self.null = null
self.sparse = sparse
self._owner_document = None
# Make sure db_field is a string (if it's explicitly defined).
if (
self.db_field is not None and
not isinstance(self.db_field, six.string_types)
):
raise TypeError('db_field should be a string.')
# Make sure db_field doesn't contain any forbidden characters.
if isinstance(self.db_field, six.string_types) and (
'.' in self.db_field or
'\0' in self.db_field or
self.db_field.startswith('$')
):
raise ValueError(
'field names cannot contain dots (".") or null characters '
'("\\0"), and they must not start with a dollar sign ("$").'
)
# Detect and report conflicts between metadata and base properties.
conflicts = set(dir(self)) & set(kwargs)
if conflicts:
raise TypeError('%s already has attribute(s): %s' % (
self.__class__.__name__, ', '.join(conflicts)))
# Assign metadata to the instance
# This efficient method is available because no __slots__ are defined.
self.__dict__.update(kwargs)
# Adjust the appropriate creation counter, and save our local copy.
if self.db_field == '_id':
self.creation_counter = BaseField.auto_creation_counter
BaseField.auto_creation_counter -= 1
else:
self.creation_counter = BaseField.creation_counter
BaseField.creation_counter += 1
def __get__(self, instance, owner):
"""Descriptor for retrieving a value from a field in a document.
"""
if instance is None:
# Document class being used rather than a document object
return self
# Get value from document instance if available
return instance._data.get(self.name)
def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document.
"""
# If setting to None and there is a default
# Then set the value to the default value
if value is None:
if self.null:
value = None
elif self.default is not None:
value = self.default
if callable(value):
value = value()
if instance._initialised:
try:
if (self.name not in instance._data or
instance._data[self.name] != value):
instance._mark_as_changed(self.name)
except Exception:
# Values cant be compared eg: naive and tz datetimes
# So mark it as changed
instance._mark_as_changed(self.name)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument):
value._instance = weakref.proxy(instance)
elif isinstance(value, (list, tuple)):
for v in value:
if isinstance(v, EmbeddedDocument):
v._instance = weakref.proxy(instance)
instance._data[self.name] = value
def error(self, message='', errors=None, field_name=None):
"""Raise a ValidationError."""
field_name = field_name if field_name else self.name
raise ValidationError(message, errors=errors, field_name=field_name)
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type."""
return value
def to_mongo(self, value):
"""Convert a Python type to a MongoDB-compatible type."""
return self.to_python(value)
def _to_mongo_safe_call(self, value, use_db_field=True, fields=None):
"""Helper method to call to_mongo with proper inputs."""
f_inputs = self.to_mongo.__code__.co_varnames
ex_vars = {}
if 'fields' in f_inputs:
ex_vars['fields'] = fields
if 'use_db_field' in f_inputs:
ex_vars['use_db_field'] = use_db_field
return self.to_mongo(value, **ex_vars)
def prepare_query_value(self, op, value):
"""Prepare a value that is being used in a query for PyMongo."""
if op in UPDATE_OPERATORS:
self.validate(value)
return value
def validate(self, value, clean=True):
"""Perform validation on a value."""
pass
def _validate_choices(self, value):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
choice_list = self.choices
if isinstance(next(iter(choice_list)), (list, tuple)):
# next(iter) is useful for sets
choice_list = [k for k, _ in choice_list]
# Choices which are other types of Documents
if isinstance(value, (Document, EmbeddedDocument)):
if not any(isinstance(value, c) for c in choice_list):
self.error(
'Value must be an instance of %s' % (
six.text_type(choice_list)
)
)
# Choices which are types other than Documents
elif value not in choice_list:
self.error('Value must be one of %s' % six.text_type(choice_list))
def _validate(self, value, **kwargs):
# Check the Choices Constraint
if self.choices:
self._validate_choices(value)
# check validation argument
if self.validation is not None:
if callable(self.validation):
if not self.validation(value):
self.error('Value does not match custom validation method')
else:
raise ValueError('validation argument for "%s" must be a '
'callable.' % self.name)
self.validate(value, **kwargs)
@property
def owner_document(self):
return self._owner_document
def _set_owner_document(self, owner_document):
self._owner_document = owner_document
@owner_document.setter
def owner_document(self, owner_document):
self._set_owner_document(owner_document)
class ComplexBaseField(BaseField):
"""Handles complex fields, such as lists / dictionaries.
Allows for nesting of embedded documents inside complex types.
Handles the lazy dereferencing of a queryset by lazily dereferencing all
items in a list / dict rather than one at a time.
.. versionadded:: 0.5
"""
field = None
def __get__(self, instance, owner):
"""Descriptor to automatically dereference references."""
if instance is None:
# Document class being used rather than a document object
return self
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
dereference = (self._auto_dereference and
(self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))))
_dereference = _import_class('DeReference')()
self._auto_dereference = instance._fields[self.name]._auto_dereference
if instance._initialised and dereference and instance._data.get(self.name):
instance._data[self.name] = _dereference(
instance._data.get(self.name), max_depth=1, instance=instance,
name=self.name
)
value = super(ComplexBaseField, self).__get__(instance, owner)
# Convert lists / values so we can watch for any changes on them
if isinstance(value, (list, tuple)):
if (issubclass(type(self), EmbeddedDocumentListField) and
not isinstance(value, EmbeddedDocumentList)):
value = EmbeddedDocumentList(value, instance, self.name)
elif not isinstance(value, BaseList):
value = BaseList(value, instance, self.name)
instance._data[self.name] = value
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, instance, self.name)
instance._data[self.name] = value
if (self._auto_dereference and instance._initialised and
isinstance(value, (BaseList, BaseDict)) and
not value._dereferenced):
value = _dereference(
value, max_depth=1, instance=instance, name=self.name
)
value._dereferenced = True
instance._data[self.name] = value
return value
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type."""
if isinstance(value, six.string_types):
return value
if hasattr(value, 'to_python'):
return value.to_python()
is_list = False
if not hasattr(value, 'items'):
try:
is_list = True
value = {k: v for k, v in enumerate(value)}
except TypeError: # Not iterable return the value
return value
if self.field:
self.field._auto_dereference = self._auto_dereference
value_dict = {key: self.field.to_python(item)
for key, item in value.items()}
else:
Document = _import_class('Document')
value_dict = {}
for k, v in value.items():
if isinstance(v, Document):
# We need the id from the saved object to create the DBRef
if v.pk is None:
self.error('You can only reference documents once they'
' have been saved to the database')
collection = v._get_collection_name()
value_dict[k] = DBRef(collection, v.pk)
elif hasattr(v, 'to_python'):
value_dict[k] = v.to_python()
else:
value_dict[k] = self.to_python(v)
if is_list: # Convert back to a list
return [v for _, v in sorted(value_dict.items(),
key=operator.itemgetter(0))]
return value_dict
def to_mongo(self, value, use_db_field=True, fields=None):
"""Convert a Python type to a MongoDB-compatible type."""
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
GenericReferenceField = _import_class('GenericReferenceField')
if isinstance(value, six.string_types):
return value
if hasattr(value, 'to_mongo'):
if isinstance(value, Document):
return GenericReferenceField().to_mongo(value)
cls = value.__class__
val = value.to_mongo(use_db_field, fields)
# If it's a document that is not inherited add _cls
if isinstance(value, EmbeddedDocument):
val['_cls'] = cls.__name__
return val
is_list = False
if not hasattr(value, 'items'):
try:
is_list = True
value = {k: v for k, v in enumerate(value)}
except TypeError: # Not iterable return the value
return value
if self.field:
value_dict = {
key: self.field._to_mongo_safe_call(item, use_db_field, fields)
for key, item in value.iteritems()
}
else:
value_dict = {}
for k, v in value.iteritems():
if isinstance(v, Document):
# We need the id from the saved object to create the DBRef
if v.pk is None:
self.error('You can only reference documents once they'
' have been saved to the database')
# If its a document that is not inheritable it won't have
# any _cls data so make it a generic reference allows
# us to dereference
meta = getattr(v, '_meta', {})
allow_inheritance = meta.get('allow_inheritance')
if not allow_inheritance and not self.field:
value_dict[k] = GenericReferenceField().to_mongo(v)
else:
collection = v._get_collection_name()
value_dict[k] = DBRef(collection, v.pk)
elif hasattr(v, 'to_mongo'):
cls = v.__class__
val = v.to_mongo(use_db_field, fields)
# If it's a document that is not inherited add _cls
if isinstance(v, (Document, EmbeddedDocument)):
val['_cls'] = cls.__name__
value_dict[k] = val
else:
value_dict[k] = self.to_mongo(v, use_db_field, fields)
if is_list: # Convert back to a list
return [v for _, v in sorted(value_dict.items(),
key=operator.itemgetter(0))]
return value_dict
def validate(self, value):
"""If field is provided ensure the value is valid."""
errors = {}
if self.field:
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
sequence = value.iteritems()
else:
sequence = enumerate(value)
for k, v in sequence:
try:
self.field._validate(v)
except ValidationError as error:
errors[k] = error.errors or error
except (ValueError, AssertionError) as error:
errors[k] = error
if errors:
field_class = self.field.__class__.__name__
self.error('Invalid %s item (%s)' % (field_class, value),
errors=errors)
# Don't allow empty values if required
if self.required and not value:
self.error('Field is required and cannot be empty')
def prepare_query_value(self, op, value):
return self.to_mongo(value)
def lookup_member(self, member_name):
if self.field:
return self.field.lookup_member(member_name)
return None
def _set_owner_document(self, owner_document):
if self.field:
self.field.owner_document = owner_document
self._owner_document = owner_document
class ObjectIdField(BaseField):
"""A field wrapper around MongoDB's ObjectIds."""
def to_python(self, value):
try:
if not isinstance(value, ObjectId):
value = ObjectId(value)
except Exception:
pass
return value
def to_mongo(self, value):
if not isinstance(value, ObjectId):
try:
return ObjectId(six.text_type(value))
except Exception as e:
# e.message attribute has been deprecated since Python 2.6
self.error(six.text_type(e))
return value
def prepare_query_value(self, op, value):
return self.to_mongo(value)
def validate(self, value):
try:
ObjectId(six.text_type(value))
except Exception:
self.error('Invalid Object ID')
class GeoJsonBaseField(BaseField):
"""A geo json field storing a geojson style object.
.. versionadded:: 0.8
"""
_geo_index = pymongo.GEOSPHERE
_type = 'GeoBase'
def __init__(self, auto_index=True, *args, **kwargs):
"""
:param bool auto_index: Automatically create a '2dsphere' index.\
Defaults to `True`.
"""
self._name = '%sField' % self._type
if not auto_index:
self._geo_index = False
super(GeoJsonBaseField, self).__init__(*args, **kwargs)
def validate(self, value):
"""Validate the GeoJson object based on its type."""
if isinstance(value, dict):
if set(value.keys()) == set(['type', 'coordinates']):
if value['type'] != self._type:
self.error('%s type must be "%s"' %
(self._name, self._type))
return self.validate(value['coordinates'])
else:
self.error('%s can only accept a valid GeoJson dictionary'
' or lists of (x, y)' % self._name)
return
elif not isinstance(value, (list, tuple)):
self.error('%s can only accept lists of [x, y]' % self._name)
return
validate = getattr(self, '_validate_%s' % self._type.lower())
error = validate(value)
if error:
self.error(error)
def _validate_polygon(self, value, top_level=True):
if not isinstance(value, (list, tuple)):
return 'Polygons must contain list of linestrings'
# Quick and dirty validator
try:
value[0][0][0]
except (TypeError, IndexError):
return 'Invalid Polygon must contain at least one valid linestring'
errors = []
for val in value:
error = self._validate_linestring(val, False)
if not error and val[0] != val[-1]:
error = 'LineStrings must start and end at the same point'
if error and error not in errors:
errors.append(error)
if errors:
if top_level:
return 'Invalid Polygon:\n%s' % ', '.join(errors)
else:
return '%s' % ', '.join(errors)
def _validate_linestring(self, value, top_level=True):
"""Validate a linestring."""
if not isinstance(value, (list, tuple)):
return 'LineStrings must contain list of coordinate pairs'
# Quick and dirty validator
try:
value[0][0]
except (TypeError, IndexError):
return 'Invalid LineString must contain at least one valid point'
errors = []
for val in value:
error = self._validate_point(val)
if error and error not in errors:
errors.append(error)
if errors:
if top_level:
return 'Invalid LineString:\n%s' % ', '.join(errors)
else:
return '%s' % ', '.join(errors)
def _validate_point(self, value):
"""Validate each set of coords"""
if not isinstance(value, (list, tuple)):
return 'Points must be a list of coordinate pairs'
elif not len(value) == 2:
return 'Value (%s) must be a two-dimensional point' % repr(value)
elif (not isinstance(value[0], (float, int)) or
not isinstance(value[1], (float, int))):
return 'Both values (%s) in point must be float or int' % repr(value)
def _validate_multipoint(self, value):
if not isinstance(value, (list, tuple)):
return 'MultiPoint must be a list of Point'
# Quick and dirty validator
try:
value[0][0]
except (TypeError, IndexError):
return 'Invalid MultiPoint must contain at least one valid point'
errors = []
for point in value:
error = self._validate_point(point)
if error and error not in errors:
errors.append(error)
if errors:
return '%s' % ', '.join(errors)
def _validate_multilinestring(self, value, top_level=True):
if not isinstance(value, (list, tuple)):
return 'MultiLineString must be a list of LineString'
# Quick and dirty validator
try:
value[0][0][0]
except (TypeError, IndexError):
return 'Invalid MultiLineString must contain at least one valid linestring'
errors = []
for linestring in value:
error = self._validate_linestring(linestring, False)
if error and error not in errors:
errors.append(error)
if errors:
if top_level:
return 'Invalid MultiLineString:\n%s' % ', '.join(errors)
else:
return '%s' % ', '.join(errors)
def _validate_multipolygon(self, value):
if not isinstance(value, (list, tuple)):
return 'MultiPolygon must be a list of Polygon'
# Quick and dirty validator
try:
value[0][0][0][0]
except (TypeError, IndexError):
return 'Invalid MultiPolygon must contain at least one valid Polygon'
errors = []
for polygon in value:
error = self._validate_polygon(polygon, False)
if error and error not in errors:
errors.append(error)
if errors:
return 'Invalid MultiPolygon:\n%s' % ', '.join(errors)
def to_mongo(self, value):
if isinstance(value, dict):
return value
return SON([('type', self._type), ('coordinates', value)])

View File

@@ -0,0 +1,452 @@
import warnings
import six
from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
from mongoengine.common import _import_class
from mongoengine.errors import InvalidDocumentError
from mongoengine.queryset import (DO_NOTHING, DoesNotExist,
MultipleObjectsReturned,
QuerySetManager)
__all__ = ('DocumentMetaclass', 'TopLevelDocumentMetaclass')
class DocumentMetaclass(type):
"""Metaclass for all documents."""
# TODO lower complexity of this method
def __new__(cls, name, bases, attrs):
flattened_bases = cls._get_bases(bases)
super_new = super(DocumentMetaclass, cls).__new__
# If a base class just call super
metaclass = attrs.get('my_metaclass')
if metaclass and issubclass(metaclass, DocumentMetaclass):
return super_new(cls, name, bases, attrs)
attrs['_is_document'] = attrs.get('_is_document', False)
attrs['_cached_reference_fields'] = []
# EmbeddedDocuments could have meta data for inheritance
if 'meta' in attrs:
attrs['_meta'] = attrs.pop('meta')
# EmbeddedDocuments should inherit meta data
if '_meta' not in attrs:
meta = MetaDict()
for base in flattened_bases[::-1]:
# Add any mixin metadata from plain objects
if hasattr(base, 'meta'):
meta.merge(base.meta)
elif hasattr(base, '_meta'):
meta.merge(base._meta)
attrs['_meta'] = meta
attrs['_meta']['abstract'] = False # 789: EmbeddedDocument shouldn't inherit abstract
# If allow_inheritance is True, add a "_cls" string field to the attrs
if attrs['_meta'].get('allow_inheritance'):
StringField = _import_class('StringField')
attrs['_cls'] = StringField()
# Handle document Fields
# Merge all fields from subclasses
doc_fields = {}
for base in flattened_bases[::-1]:
if hasattr(base, '_fields'):
doc_fields.update(base._fields)
# Standard object mixin - merge in any Fields
if not hasattr(base, '_meta'):
base_fields = {}
for attr_name, attr_value in base.__dict__.iteritems():
if not isinstance(attr_value, BaseField):
continue
attr_value.name = attr_name
if not attr_value.db_field:
attr_value.db_field = attr_name
base_fields[attr_name] = attr_value
doc_fields.update(base_fields)
# Discover any document fields
field_names = {}
for attr_name, attr_value in attrs.iteritems():
if not isinstance(attr_value, BaseField):
continue
attr_value.name = attr_name
if not attr_value.db_field:
attr_value.db_field = attr_name
doc_fields[attr_name] = attr_value
# Count names to ensure no db_field redefinitions
field_names[attr_value.db_field] = field_names.get(
attr_value.db_field, 0) + 1
# Ensure no duplicate db_fields
duplicate_db_fields = [k for k, v in field_names.items() if v > 1]
if duplicate_db_fields:
msg = ('Multiple db_fields defined for: %s ' %
', '.join(duplicate_db_fields))
raise InvalidDocumentError(msg)
# Set _fields and db_field maps
attrs['_fields'] = doc_fields
attrs['_db_field_map'] = {k: getattr(v, 'db_field', k)
for k, v in doc_fields.items()}
attrs['_reverse_db_field_map'] = {
v: k for k, v in attrs['_db_field_map'].items()
}
attrs['_fields_ordered'] = tuple(i[1] for i in sorted(
(v.creation_counter, v.name)
for v in doc_fields.itervalues()))
#
# Set document hierarchy
#
superclasses = ()
class_name = [name]
for base in flattened_bases:
if (not getattr(base, '_is_base_cls', True) and
not getattr(base, '_meta', {}).get('abstract', True)):
# Collate hierarchy for _cls and _subclasses
class_name.append(base.__name__)
if hasattr(base, '_meta'):
# Warn if allow_inheritance isn't set and prevent
# inheritance of classes where inheritance is set to False
allow_inheritance = base._meta.get('allow_inheritance')
if not allow_inheritance and not base._meta.get('abstract'):
raise ValueError('Document %s may not be subclassed' %
base.__name__)
# Get superclasses from last base superclass
document_bases = [b for b in flattened_bases
if hasattr(b, '_class_name')]
if document_bases:
superclasses = document_bases[0]._superclasses
superclasses += (document_bases[0]._class_name, )
_cls = '.'.join(reversed(class_name))
attrs['_class_name'] = _cls
attrs['_superclasses'] = superclasses
attrs['_subclasses'] = (_cls, )
attrs['_types'] = attrs['_subclasses'] # TODO depreciate _types
# Create the new_class
new_class = super_new(cls, name, bases, attrs)
# Set _subclasses
for base in document_bases:
if _cls not in base._subclasses:
base._subclasses += (_cls,)
base._types = base._subclasses # TODO depreciate _types
(Document, EmbeddedDocument, DictField,
CachedReferenceField) = cls._import_classes()
if issubclass(new_class, Document):
new_class._collection = None
# Add class to the _document_registry
_document_registry[new_class._class_name] = new_class
# In Python 2, User-defined methods objects have special read-only
# attributes 'im_func' and 'im_self' which contain the function obj
# and class instance object respectively. With Python 3 these special
# attributes have been replaced by __func__ and __self__. The Blinker
# module continues to use im_func and im_self, so the code below
# copies __func__ into im_func and __self__ into im_self for
# classmethod objects in Document derived classes.
if six.PY3:
for val in new_class.__dict__.values():
if isinstance(val, classmethod):
f = val.__get__(new_class)
if hasattr(f, '__func__') and not hasattr(f, 'im_func'):
f.__dict__.update({'im_func': getattr(f, '__func__')})
if hasattr(f, '__self__') and not hasattr(f, 'im_self'):
f.__dict__.update({'im_self': getattr(f, '__self__')})
# Handle delete rules
for field in new_class._fields.itervalues():
f = field
if f.owner_document is None:
f.owner_document = new_class
delete_rule = getattr(f, 'reverse_delete_rule', DO_NOTHING)
if isinstance(f, CachedReferenceField):
if issubclass(new_class, EmbeddedDocument):
raise InvalidDocumentError('CachedReferenceFields is not '
'allowed in EmbeddedDocuments')
if not f.document_type:
raise InvalidDocumentError(
'Document is not available to sync')
if f.auto_sync:
f.start_listener()
f.document_type._cached_reference_fields.append(f)
if isinstance(f, ComplexBaseField) and hasattr(f, 'field'):
delete_rule = getattr(f.field,
'reverse_delete_rule',
DO_NOTHING)
if isinstance(f, DictField) and delete_rule != DO_NOTHING:
msg = ('Reverse delete rules are not supported '
'for %s (field: %s)' %
(field.__class__.__name__, field.name))
raise InvalidDocumentError(msg)
f = field.field
if delete_rule != DO_NOTHING:
if issubclass(new_class, EmbeddedDocument):
msg = ('Reverse delete rules are not supported for '
'EmbeddedDocuments (field: %s)' % field.name)
raise InvalidDocumentError(msg)
f.document_type.register_delete_rule(new_class,
field.name, delete_rule)
if (field.name and hasattr(Document, field.name) and
EmbeddedDocument not in new_class.mro()):
msg = ('%s is a document method and not a valid '
'field name' % field.name)
raise InvalidDocumentError(msg)
return new_class
def add_to_class(self, name, value):
setattr(self, name, value)
@classmethod
def _get_bases(cls, bases):
if isinstance(bases, BasesTuple):
return bases
seen = []
bases = cls.__get_bases(bases)
unique_bases = (b for b in bases if not (b in seen or seen.append(b)))
return BasesTuple(unique_bases)
@classmethod
def __get_bases(cls, bases):
for base in bases:
if base is object:
continue
yield base
for child_base in cls.__get_bases(base.__bases__):
yield child_base
@classmethod
def _import_classes(cls):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
DictField = _import_class('DictField')
CachedReferenceField = _import_class('CachedReferenceField')
return Document, EmbeddedDocument, DictField, CachedReferenceField
class TopLevelDocumentMetaclass(DocumentMetaclass):
"""Metaclass for top-level documents (i.e. documents that have their own
collection in the database.
"""
def __new__(cls, name, bases, attrs):
flattened_bases = cls._get_bases(bases)
super_new = super(TopLevelDocumentMetaclass, cls).__new__
# Set default _meta data if base class, otherwise get user defined meta
if attrs.get('my_metaclass') == TopLevelDocumentMetaclass:
# defaults
attrs['_meta'] = {
'abstract': True,
'max_documents': None,
'max_size': None,
'ordering': [], # default ordering applied at runtime
'indexes': [], # indexes to be ensured at runtime
'id_field': None,
'index_background': False,
'index_drop_dups': False,
'index_opts': None,
'delete_rules': None,
# allow_inheritance can be True, False, and None. True means
# "allow inheritance", False means "don't allow inheritance",
# None means "do whatever your parent does, or don't allow
# inheritance if you're a top-level class".
'allow_inheritance': None,
}
attrs['_is_base_cls'] = True
attrs['_meta'].update(attrs.get('meta', {}))
else:
attrs['_meta'] = attrs.get('meta', {})
# Explicitly set abstract to false unless set
attrs['_meta']['abstract'] = attrs['_meta'].get('abstract', False)
attrs['_is_base_cls'] = False
# Set flag marking as document class - as opposed to an object mixin
attrs['_is_document'] = True
# Ensure queryset_class is inherited
if 'objects' in attrs:
manager = attrs['objects']
if hasattr(manager, 'queryset_class'):
attrs['_meta']['queryset_class'] = manager.queryset_class
# Clean up top level meta
if 'meta' in attrs:
del attrs['meta']
# Find the parent document class
parent_doc_cls = [b for b in flattened_bases
if b.__class__ == TopLevelDocumentMetaclass]
parent_doc_cls = None if not parent_doc_cls else parent_doc_cls[0]
# Prevent classes setting collection different to their parents
# If parent wasn't an abstract class
if (parent_doc_cls and 'collection' in attrs.get('_meta', {}) and
not parent_doc_cls._meta.get('abstract', True)):
msg = 'Trying to set a collection on a subclass (%s)' % name
warnings.warn(msg, SyntaxWarning)
del attrs['_meta']['collection']
# Ensure abstract documents have abstract bases
if attrs.get('_is_base_cls') or attrs['_meta'].get('abstract'):
if (parent_doc_cls and
not parent_doc_cls._meta.get('abstract', False)):
msg = 'Abstract document cannot have non-abstract base'
raise ValueError(msg)
return super_new(cls, name, bases, attrs)
# Merge base class metas.
# Uses a special MetaDict that handles various merging rules
meta = MetaDict()
for base in flattened_bases[::-1]:
# Add any mixin metadata from plain objects
if hasattr(base, 'meta'):
meta.merge(base.meta)
elif hasattr(base, '_meta'):
meta.merge(base._meta)
# Set collection in the meta if its callable
if (getattr(base, '_is_document', False) and
not base._meta.get('abstract')):
collection = meta.get('collection', None)
if callable(collection):
meta['collection'] = collection(base)
meta.merge(attrs.get('_meta', {})) # Top level meta
# Only simple classes (i.e. direct subclasses of Document) may set
# allow_inheritance to False. If the base Document allows inheritance,
# none of its subclasses can override allow_inheritance to False.
simple_class = all([b._meta.get('abstract')
for b in flattened_bases if hasattr(b, '_meta')])
if (
not simple_class and
meta['allow_inheritance'] is False and
not meta['abstract']
):
raise ValueError('Only direct subclasses of Document may set '
'"allow_inheritance" to False')
# Set default collection name
if 'collection' not in meta:
meta['collection'] = ''.join('_%s' % c if c.isupper() else c
for c in name).strip('_').lower()
attrs['_meta'] = meta
# Call super and get the new class
new_class = super_new(cls, name, bases, attrs)
meta = new_class._meta
# Set index specifications
meta['index_specs'] = new_class._build_index_specs(meta['indexes'])
# If collection is a callable - call it and set the value
collection = meta.get('collection')
if callable(collection):
new_class._meta['collection'] = collection(new_class)
# Provide a default queryset unless exists or one has been set
if 'objects' not in dir(new_class):
new_class.objects = QuerySetManager()
# Validate the fields and set primary key if needed
for field_name, field in new_class._fields.iteritems():
if field.primary_key:
# Ensure only one primary key is set
current_pk = new_class._meta.get('id_field')
if current_pk and current_pk != field_name:
raise ValueError('Cannot override primary key field')
# Set primary key
if not current_pk:
new_class._meta['id_field'] = field_name
new_class.id = field
# Set primary key if not defined by the document
new_class._auto_id_field = getattr(parent_doc_cls,
'_auto_id_field', False)
if not new_class._meta.get('id_field'):
# After 0.10, find not existing names, instead of overwriting
id_name, id_db_name = cls.get_auto_id_names(new_class)
new_class._auto_id_field = True
new_class._meta['id_field'] = id_name
new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
new_class._fields[id_name].name = id_name
new_class.id = new_class._fields[id_name]
new_class._db_field_map[id_name] = id_db_name
new_class._reverse_db_field_map[id_db_name] = id_name
# Prepend id field to _fields_ordered
new_class._fields_ordered = (id_name, ) + new_class._fields_ordered
# Merge in exceptions with parent hierarchy
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
module = attrs.get('__module__')
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
setattr(new_class, name, exception)
return new_class
@classmethod
def get_auto_id_names(cls, new_class):
id_name, id_db_name = ('id', '_id')
if id_name not in new_class._fields and \
id_db_name not in (v.db_field for v in new_class._fields.values()):
return id_name, id_db_name
id_basename, id_db_basename, i = 'auto_id', '_auto_id', 0
while id_name in new_class._fields or \
id_db_name in (v.db_field for v in new_class._fields.values()):
id_name = '{0}_{1}'.format(id_basename, i)
id_db_name = '{0}_{1}'.format(id_db_basename, i)
i += 1
return id_name, id_db_name
class MetaDict(dict):
"""Custom dictionary for meta classes.
Handles the merging of set indexes
"""
_merge_options = ('indexes',)
def merge(self, new_options):
for k, v in new_options.iteritems():
if k in self._merge_options:
self[k] = self.get(k, []) + v
else:
self[k] = v
class BasesTuple(tuple):
"""Special class to handle introspection of bases tuple in __new__"""
pass

58
mongoengine/common.py Normal file
View File

@@ -0,0 +1,58 @@
_class_registry_cache = {}
_field_list_cache = []
def _import_class(cls_name):
"""Cache mechanism for imports.
Due to complications of circular imports mongoengine needs to do lots of
inline imports in functions. This is inefficient as classes are
imported repeated throughout the mongoengine code. This is
compounded by some recursive functions requiring inline imports.
:mod:`mongoengine.common` provides a single point to import all these
classes. Circular imports aren't an issue as it dynamically imports the
class when first needed. Subsequent calls to the
:func:`~mongoengine.common._import_class` can then directly retrieve the
class from the :data:`mongoengine.common._class_registry_cache`.
"""
if cls_name in _class_registry_cache:
return _class_registry_cache.get(cls_name)
doc_classes = ('Document', 'DynamicEmbeddedDocument', 'EmbeddedDocument',
'MapReduceDocument')
# Field Classes
if not _field_list_cache:
from mongoengine.fields import __all__ as fields
_field_list_cache.extend(fields)
from mongoengine.base.fields import __all__ as fields
_field_list_cache.extend(fields)
field_classes = _field_list_cache
queryset_classes = ('OperationError',)
deref_classes = ('DeReference',)
if cls_name == 'BaseDocument':
from mongoengine.base import document as module
import_classes = ['BaseDocument']
elif cls_name in doc_classes:
from mongoengine import document as module
import_classes = doc_classes
elif cls_name in field_classes:
from mongoengine import fields as module
import_classes = field_classes
elif cls_name in queryset_classes:
from mongoengine import queryset as module
import_classes = queryset_classes
elif cls_name in deref_classes:
from mongoengine import dereference as module
import_classes = deref_classes
else:
raise ValueError('No import set for: ' % cls_name)
for cls in import_classes:
_class_registry_cache[cls] = getattr(module, cls)
return _class_registry_cache.get(cls_name)

View File

@@ -1,15 +1,25 @@
import pymongo
from pymongo import Connection, ReplicaSetConnection, uri_parser
from pymongo import MongoClient, ReadPreference, uri_parser
import six
from mongoengine.python_support import IS_PYMONGO_3
__all__ = ['ConnectionError', 'connect', 'register_connection',
__all__ = ['MongoEngineConnectionError', 'connect', 'register_connection',
'DEFAULT_CONNECTION_NAME']
DEFAULT_CONNECTION_NAME = 'default'
if IS_PYMONGO_3:
READ_PREFERENCE = ReadPreference.PRIMARY
else:
from pymongo import MongoReplicaSetClient
READ_PREFERENCE = False
class ConnectionError(Exception):
class MongoEngineConnectionError(Exception):
"""Error raised when the database connection can't be established or
when a connection with a requested alias can't be retrieved.
"""
pass
@@ -18,9 +28,12 @@ _connections = {}
_dbs = {}
def register_connection(alias, name, host='localhost', port=27017,
is_slave=False, read_preference=False, slaves=None,
username=None, password=None, **kwargs):
def register_connection(alias, name=None, host=None, port=None,
read_preference=READ_PREFERENCE,
username=None, password=None,
authentication_source=None,
authentication_mechanism=None,
**kwargs):
"""Add a connection.
:param alias: the name that will be used to refer to this connection
@@ -28,139 +41,222 @@ def register_connection(alias, name, host='localhost', port=27017,
:param name: the name of the specific database to use
:param host: the host name of the :program:`mongod` instance to connect to
:param port: the port that the :program:`mongod` instance is running on
:param is_slave: whether the connection can act as a slave ** Depreciated pymongo 2.0.1+
:param read_preference: The read preference for the collection ** Added pymongo 2.1
:param slaves: a list of aliases of slave connections; each of these must
be a registered connection that has :attr:`is_slave` set to ``True``
:param read_preference: The read preference for the collection
** Added pymongo 2.1
:param username: username to authenticate with
:param password: password to authenticate with
:param kwargs: allow ad-hoc parameters to be passed into the pymongo driver
:param authentication_source: database to authenticate against
:param authentication_mechanism: database authentication mechanisms.
By default, use SCRAM-SHA-1 with MongoDB 3.0 and later,
MONGODB-CR (MongoDB Challenge Response protocol) for older servers.
:param is_mock: explicitly use mongomock for this connection
(can also be done by using `mongomock://` as db host prefix)
:param kwargs: ad-hoc parameters to be passed into the pymongo driver,
for example maxpoolsize, tz_aware, etc. See the documentation
for pymongo's `MongoClient` for a full list.
.. versionchanged:: 0.10.6 - added mongomock support
"""
global _connection_settings
conn_settings = {
'name': name,
'host': host,
'port': port,
'is_slave': is_slave,
'slaves': slaves or [],
'name': name or 'test',
'host': host or 'localhost',
'port': port or 27017,
'read_preference': read_preference,
'username': username,
'password': password,
'read_preference': read_preference
'authentication_source': authentication_source,
'authentication_mechanism': authentication_mechanism
}
# 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_host = conn_settings['host']
# Host can be a list or a string, so if string, force to a list.
if isinstance(conn_host, six.string_types):
conn_host = [conn_host]
resolved_hosts = []
for entity in conn_host:
# Handle Mongomock
if entity.startswith('mongomock://'):
conn_settings['is_mock'] = True
# `mongomock://` is not a valid url prefix and must be replaced by `mongodb://`
resolved_hosts.append(entity.replace('mongomock://', 'mongodb://', 1))
# Handle URI style connections, only updating connection params which
# were explicitly specified in the URI.
elif '://' in entity:
uri_dict = uri_parser.parse_uri(entity)
resolved_hosts.append(entity)
if uri_dict.get('database'):
conn_settings['name'] = uri_dict.get('database')
for param in ('read_preference', 'username', 'password'):
if uri_dict.get(param):
conn_settings[param] = uri_dict[param]
uri_options = uri_dict['options']
if 'replicaset' in uri_options:
conn_settings['replicaSet'] = uri_options['replicaset']
if 'authsource' in uri_options:
conn_settings['authentication_source'] = uri_options['authsource']
if 'authmechanism' in uri_options:
conn_settings['authentication_mechanism'] = uri_options['authmechanism']
else:
resolved_hosts.append(entity)
conn_settings['host'] = resolved_hosts
# Deprecated parameters that should not be passed on
kwargs.pop('slaves', None)
kwargs.pop('is_slave', None)
conn_settings.update(kwargs)
_connection_settings[alias] = conn_settings
def disconnect(alias=DEFAULT_CONNECTION_NAME):
global _connections
global _dbs
"""Close the connection with a given alias."""
if alias in _connections:
get_connection(alias=alias).disconnect()
get_connection(alias=alias).close()
del _connections[alias]
if alias in _dbs:
del _dbs[alias]
def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
global _connections
"""Return a connection with a given alias."""
# Connect to the database if not already connected
if reconnect:
disconnect(alias)
if alias not in _connections:
if alias not in _connection_settings:
msg = 'Connection with alias "%s" has not been defined' % alias
if alias == DEFAULT_CONNECTION_NAME:
msg = 'You have not defined a default connection'
raise ConnectionError(msg)
conn_settings = _connection_settings[alias].copy()
# If the requested alias already exists in the _connections list, return
# it immediately.
if alias in _connections:
return _connections[alias]
if hasattr(pymongo, 'version_tuple'): # Support for 2.1+
conn_settings.pop('name', None)
conn_settings.pop('slaves', None)
conn_settings.pop('is_slave', None)
conn_settings.pop('username', None)
conn_settings.pop('password', None)
# Validate that the requested alias exists in the _connection_settings.
# Raise MongoEngineConnectionError if it doesn't.
if alias not in _connection_settings:
if alias == DEFAULT_CONNECTION_NAME:
msg = 'You have not defined a default connection'
else:
# Get all the slave connections
if 'slaves' in conn_settings:
slaves = []
for slave_alias in conn_settings['slaves']:
slaves.append(get_connection(slave_alias))
conn_settings['slaves'] = slaves
conn_settings.pop('read_preference', None)
msg = 'Connection with alias "%s" has not been defined' % alias
raise MongoEngineConnectionError(msg)
connection_class = Connection
if 'replicaSet' in conn_settings:
def _clean_settings(settings_dict):
irrelevant_fields = set([
'name', 'username', 'password', 'authentication_source',
'authentication_mechanism'
])
return {
k: v for k, v in settings_dict.items()
if k not in irrelevant_fields
}
# Retrieve a copy of the connection settings associated with the requested
# alias and remove the database name and authentication info (we don't
# care about them at this point).
conn_settings = _clean_settings(_connection_settings[alias].copy())
# Determine if we should use PyMongo's or mongomock's MongoClient.
is_mock = conn_settings.pop('is_mock', False)
if is_mock:
try:
import mongomock
except ImportError:
raise RuntimeError('You need mongomock installed to mock '
'MongoEngine.')
connection_class = mongomock.MongoClient
else:
connection_class = MongoClient
# For replica set connections with PyMongo 2.x, use
# MongoReplicaSetClient.
# TODO remove this once we stop supporting PyMongo 2.x.
if 'replicaSet' in conn_settings and not IS_PYMONGO_3:
connection_class = MongoReplicaSetClient
conn_settings['hosts_or_uri'] = conn_settings.pop('host', None)
# Discard port since it can't be used on ReplicaSetConnection
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
# hosts_or_uri has to be a string, so if 'host' was provided
# as a list, join its parts and separate them by ','
if isinstance(conn_settings['hosts_or_uri'], list):
conn_settings['hosts_or_uri'] = ','.join(
conn_settings['hosts_or_uri'])
# Discard port since it can't be used on MongoReplicaSetClient
conn_settings.pop('port', None)
# Iterate over all of the connection settings and if a connection with
# the same parameters is already established, use it instead of creating
# a new one.
existing_connection = None
connection_settings_iterator = (
(db_alias, settings.copy())
for db_alias, settings in _connection_settings.items()
)
for db_alias, connection_settings in connection_settings_iterator:
connection_settings = _clean_settings(connection_settings)
if conn_settings == connection_settings and _connections.get(db_alias):
existing_connection = _connections[db_alias]
break
# If an existing connection was found, assign it to the new alias
if existing_connection:
_connections[alias] = existing_connection
else:
# Otherwise, create the new connection for this alias. Raise
# MongoEngineConnectionError if it can't be established.
try:
_connections[alias] = connection_class(**conn_settings)
except Exception, e:
raise ConnectionError("Cannot connect to database %s :\n%s" % (alias, e))
except Exception as e:
raise MongoEngineConnectionError(
'Cannot connect to database %s :\n%s' % (alias, e))
return _connections[alias]
def get_db(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
global _dbs
if reconnect:
disconnect(alias)
if alias not in _dbs:
conn = get_connection(alias)
conn_settings = _connection_settings[alias]
_dbs[alias] = conn[conn_settings['name']]
db = conn[conn_settings['name']]
auth_kwargs = {'source': conn_settings['authentication_source']}
if conn_settings['authentication_mechanism'] is not None:
auth_kwargs['mechanism'] = conn_settings['authentication_mechanism']
# Authenticate if necessary
if conn_settings['username'] and conn_settings['password']:
_dbs[alias].authenticate(conn_settings['username'],
conn_settings['password'])
if conn_settings['username'] and (conn_settings['password'] or
conn_settings['authentication_mechanism'] == 'MONGODB-X509'):
db.authenticate(conn_settings['username'], conn_settings['password'], **auth_kwargs)
_dbs[alias] = db
return _dbs[alias]
def connect(db, alias=DEFAULT_CONNECTION_NAME, **kwargs):
def connect(db=None, alias=DEFAULT_CONNECTION_NAME, **kwargs):
"""Connect to the database specified by the 'db' argument.
Connection settings may be provided here as well if the database is not
running on the default port on localhost. If authentication is needed,
provide username and password arguments as well.
Multiple databases are supported by using aliases. Provide a separate
Multiple databases are supported by using aliases. Provide a separate
`alias` to connect to a different instance of :program:`mongod`.
See the docstring for `register_connection` for more details about all
supported kwargs.
.. versionchanged:: 0.6 - added multiple database support.
"""
global _connections
if alias not in _connections:
register_connection(alias, db, **kwargs)
return get_connection(alias)
# Support old naming convention
_get_connection = get_connection
_get_db = get_db

View File

@@ -0,0 +1,217 @@
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
'no_sub_classes', 'query_counter')
class switch_db(object):
"""switch_db alias context manager.
Example ::
# Register connections
register_connection('default', 'mongoenginetest')
register_connection('testdb-1', 'mongoenginetest2')
class Group(Document):
name = StringField()
Group(name='test').save() # Saves in the default db
with switch_db(Group, 'testdb-1') as Group:
Group(name='hello testdb!').save() # Saves in testdb-1
"""
def __init__(self, cls, db_alias):
"""Construct the switch_db context manager
:param cls: the class to change the registered db
:param db_alias: the name of the specific database to use
"""
self.cls = cls
self.collection = cls._get_collection()
self.db_alias = db_alias
self.ori_db_alias = cls._meta.get('db_alias', DEFAULT_CONNECTION_NAME)
def __enter__(self):
"""Change the db_alias and clear the cached collection."""
self.cls._meta['db_alias'] = self.db_alias
self.cls._collection = None
return self.cls
def __exit__(self, t, value, traceback):
"""Reset the db_alias and collection."""
self.cls._meta['db_alias'] = self.ori_db_alias
self.cls._collection = self.collection
class switch_collection(object):
"""switch_collection alias context manager.
Example ::
class Group(Document):
name = StringField()
Group(name='test').save() # Saves in the default db
with switch_collection(Group, 'group1') as Group:
Group(name='hello testdb!').save() # Saves in group1 collection
"""
def __init__(self, cls, collection_name):
"""Construct the switch_collection context manager.
:param cls: the class to change the registered db
:param collection_name: the name of the collection to use
"""
self.cls = cls
self.ori_collection = cls._get_collection()
self.ori_get_collection_name = cls._get_collection_name
self.collection_name = collection_name
def __enter__(self):
"""Change the _get_collection_name and clear the cached collection."""
@classmethod
def _get_collection_name(cls):
return self.collection_name
self.cls._get_collection_name = _get_collection_name
self.cls._collection = None
return self.cls
def __exit__(self, t, value, traceback):
"""Reset the collection."""
self.cls._collection = self.ori_collection
self.cls._get_collection_name = self.ori_get_collection_name
class no_dereference(object):
"""no_dereference context manager.
Turns off all dereferencing in Documents for the duration of the context
manager::
with no_dereference(Group) as Group:
Group.objects.find()
"""
def __init__(self, cls):
"""Construct the no_dereference context manager.
:param cls: the class to turn dereferencing off on
"""
self.cls = cls
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
ComplexBaseField = _import_class('ComplexBaseField')
self.deref_fields = [k for k, v in self.cls._fields.iteritems()
if isinstance(v, (ReferenceField,
GenericReferenceField,
ComplexBaseField))]
def __enter__(self):
"""Change the objects default and _auto_dereference values."""
for field in self.deref_fields:
self.cls._fields[field]._auto_dereference = False
return self.cls
def __exit__(self, t, value, traceback):
"""Reset the default and _auto_dereference values."""
for field in self.deref_fields:
self.cls._fields[field]._auto_dereference = True
return self.cls
class no_sub_classes(object):
"""no_sub_classes context manager.
Only returns instances of this class and no sub (inherited) classes::
with no_sub_classes(Group) as Group:
Group.objects.find()
"""
def __init__(self, cls):
"""Construct the no_sub_classes context manager.
:param cls: the class to turn querying sub classes on
"""
self.cls = cls
def __enter__(self):
"""Change the objects default and _auto_dereference values."""
self.cls._all_subclasses = self.cls._subclasses
self.cls._subclasses = (self.cls,)
return self.cls
def __exit__(self, t, value, traceback):
"""Reset the default and _auto_dereference values."""
self.cls._subclasses = self.cls._all_subclasses
delattr(self.cls, '_all_subclasses')
return self.cls
class query_counter(object):
"""Query_counter context manager to get the number of queries."""
def __init__(self):
"""Construct the query_counter."""
self.counter = 0
self.db = get_db()
def __enter__(self):
"""On every with block we need to drop the profile collection."""
self.db.set_profiling_level(0)
self.db.system.profile.drop()
self.db.set_profiling_level(2)
return self
def __exit__(self, t, value, traceback):
"""Reset the profiling level."""
self.db.set_profiling_level(0)
def __eq__(self, value):
"""== Compare querycounter."""
counter = self._get_count()
return value == counter
def __ne__(self, value):
"""!= Compare querycounter."""
return not self.__eq__(value)
def __lt__(self, value):
"""< Compare querycounter."""
return self._get_count() < value
def __le__(self, value):
"""<= Compare querycounter."""
return self._get_count() <= value
def __gt__(self, value):
"""> Compare querycounter."""
return self._get_count() > value
def __ge__(self, value):
""">= Compare querycounter."""
return self._get_count() >= value
def __int__(self):
"""int representation."""
return self._get_count()
def __repr__(self):
"""repr query_counter as the number of queries."""
return u"%s" % self._get_count()
def _get_count(self):
"""Get the number of queries."""
ignore_query = {'ns': {'$ne': '%s.system.indexes' % self.db.name}}
count = self.db.system.profile.find(ignore_query).count() - self.counter
self.counter += 1
return count

View File

@@ -1,18 +1,19 @@
from bson import DBRef, SON
import six
from base import (BaseDict, BaseList, TopLevelDocumentMetaclass, get_document)
from fields import (ReferenceField, ListField, DictField, MapField)
from connection import get_db
from queryset import QuerySet
from document import Document
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document)
from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
from mongoengine.queryset import QuerySet
class DeReference(object):
def __call__(self, items, max_depth=1, instance=None, name=None):
"""
Cheaply dereferences the items to a set depth.
Also handles the convertion of complex data types.
Also handles the conversion of complex data types.
:param items: The iterable (dict, list, queryset) to be dereferenced.
:param max_depth: The maximum depth to recurse to
@@ -22,7 +23,7 @@ class DeReference(object):
:class:`~mongoengine.base.ComplexBaseField`
:param get: A boolean determining if being called by __get__
"""
if items is None or isinstance(items, basestring):
if items is None or isinstance(items, six.string_types):
return items
# cheapest way to convert a queryset to a list
@@ -33,9 +34,10 @@ class DeReference(object):
self.max_depth = max_depth
doc_type = None
if instance and instance._fields:
if instance and isinstance(instance, (Document, EmbeddedDocument,
TopLevelDocumentMetaclass)):
doc_type = instance._fields.get(name)
if hasattr(doc_type, 'field'):
while hasattr(doc_type, 'field'):
doc_type = doc_type.field
if isinstance(doc_type, ReferenceField):
@@ -45,20 +47,30 @@ class DeReference(object):
if is_list and all([i.__class__ == doc_type for i in items]):
return items
elif not is_list and all([i.__class__ == doc_type
for i in items.values()]):
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]
def _get_items(items):
new_items = []
for v in items:
if isinstance(v, list):
new_items.append(_get_items(v))
elif not isinstance(v, (DBRef, Document)):
new_items.append(field.to_python(v))
else:
new_items.append(v)
return new_items
items = _get_items(items)
else:
items = dict([
(k, field.to_python(v))
if not isinstance(v, (DBRef, Document)) else (k, v)
for k, v in items.iteritems()]
)
items = {
k: (v if isinstance(v, (DBRef, Document))
else field.to_python(v))
for k, v in items.iteritems()
}
self.reference_map = self._find_references(items)
self.object_map = self._fetch_objects(doc_type=doc_type)
@@ -76,36 +88,36 @@ class DeReference(object):
return reference_map
# Determine the iterator to use
if not hasattr(items, 'items'):
iterator = enumerate(items)
if isinstance(items, dict):
iterator = items.values()
else:
iterator = items.iteritems()
iterator = items
# Recursively find dbreferences
depth += 1
for k, item in iterator:
if hasattr(item, '_fields'):
for item in iterator:
if isinstance(item, (Document, EmbeddedDocument)):
for field_name, field in item._fields.iteritems():
v = item._data.get(field_name, None)
if isinstance(v, (DBRef)):
reference_map.setdefault(field.document_type, []).append(v.id)
if isinstance(v, DBRef):
reference_map.setdefault(field.document_type, set()).add(v.id)
elif isinstance(v, (dict, SON)) and '_ref' in v:
reference_map.setdefault(get_document(v['_cls']), []).append(v['_ref'].id)
reference_map.setdefault(get_document(v['_cls']), set()).add(v['_ref'].id)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
field_cls = getattr(getattr(field, 'field', None), 'document_type', None)
references = self._find_references(v, depth)
for key, refs in references.iteritems():
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
key = field_cls
reference_map.setdefault(key, []).extend(refs)
elif isinstance(item, (DBRef)):
reference_map.setdefault(item.collection, []).append(item.id)
reference_map.setdefault(key, set()).update(refs)
elif isinstance(item, DBRef):
reference_map.setdefault(item.collection, set()).add(item.id)
elif isinstance(item, (dict, SON)) and '_ref' in item:
reference_map.setdefault(get_document(item['_cls']), []).append(item['_ref'].id)
reference_map.setdefault(get_document(item['_cls']), set()).add(item['_ref'].id)
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
references = self._find_references(item, depth - 1)
for key, refs in references.iteritems():
reference_map.setdefault(key, []).extend(refs)
reference_map.setdefault(key, set()).update(refs)
return reference_map
@@ -113,31 +125,38 @@ class DeReference(object):
"""Fetch all references and convert to their document objects
"""
object_map = {}
for col, dbrefs in self.reference_map.iteritems():
keys = object_map.keys()
refs = list(set([dbref for dbref in dbrefs if str(dbref) not in keys]))
if hasattr(col, 'objects'): # We have a document class for the refs
references = col.objects.in_bulk(refs)
for collection, dbrefs in self.reference_map.iteritems():
if hasattr(collection, 'objects'): # We have a document class for the refs
col_name = collection._get_collection_name()
refs = [dbref for dbref in dbrefs
if (col_name, dbref) not in object_map]
references = collection.objects.in_bulk(refs)
for key, doc in references.iteritems():
object_map[key] = doc
object_map[(col_name, key)] = doc
else: # Generic reference: use the refs data to convert to document
if doc_type and not isinstance(doc_type, (ListField, DictField, MapField,) ):
references = doc_type._get_db()[col].find({'_id': {'$in': refs}})
if isinstance(doc_type, (ListField, DictField, MapField,)):
continue
refs = [dbref for dbref in dbrefs
if (collection, dbref) not in object_map]
if doc_type:
references = doc_type._get_db()[collection].find({'_id': {'$in': refs}})
for ref in references:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
object_map[(collection, doc.id)] = doc
else:
references = get_db()[col].find({'_id': {'$in': refs}})
references = get_db()[collection].find({'_id': {'$in': refs}})
for ref in references:
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)
for x in collection.split('_')))._from_son(ref)
else:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
object_map[(collection, doc.id)] = doc
return object_map
def _attach_objects(self, items, depth=0, instance=None, name=None):
@@ -163,14 +182,23 @@ class DeReference(object):
if isinstance(items, (dict, SON)):
if '_ref' in items:
return self.object_map.get(items['_ref'].id, items)
elif '_types' in items and '_cls' in items:
return self.object_map.get(
(items['_ref'].collection, items['_ref'].id), items)
elif '_cls' in items:
doc = get_document(items['_cls'])._from_son(items)
_cls = doc._data.pop('_cls', None)
del items['_cls']
doc._data = self._attach_objects(doc._data, depth, doc, None)
if _cls is not None:
doc._data['_cls'] = _cls
return doc
if not hasattr(items, 'items'):
is_list = True
list_type = BaseList
if isinstance(items, EmbeddedDocumentList):
list_type = EmbeddedDocumentList
as_tuple = isinstance(items, tuple)
iterator = enumerate(items)
data = []
else:
@@ -187,25 +215,27 @@ class DeReference(object):
if k in self.object_map and not is_list:
data[k] = self.object_map[k]
elif hasattr(v, '_fields'):
for field_name, field in v._fields.iteritems():
elif isinstance(v, (Document, EmbeddedDocument)):
for field_name in v._fields:
v = data[k]._data.get(field_name, None)
if isinstance(v, (DBRef)):
data[k]._data[field_name] = self.object_map.get(v.id, v)
if isinstance(v, DBRef):
data[k]._data[field_name] = self.object_map.get(
(v.collection, v.id), v)
elif isinstance(v, (dict, SON)) and '_ref' in v:
data[k]._data[field_name] = self.object_map.get(v['_ref'].id, v)
elif isinstance(v, dict) and depth <= self.max_depth:
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=name)
elif isinstance(v, (list, tuple)) and depth <= self.max_depth:
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=name)
data[k]._data[field_name] = self.object_map.get(
(v['_ref'].collection, v['_ref'].id), v)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = six.text_type('{0}.{1}.{2}').format(name, k, field_name)
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=item_name)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=name)
item_name = '%s.%s' % (name, k) if name else name
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
elif hasattr(v, 'id'):
data[k] = self.object_map.get(v.id, v)
data[k] = self.object_map.get((v.collection, v.id), v)
if instance and name:
if is_list:
return BaseList(data, instance, name)
return tuple(data) if as_tuple else list_type(data, instance, name)
return BaseDict(data, instance, name)
depth += 1
return data

View File

@@ -1,180 +0,0 @@
import datetime
from mongoengine import *
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.utils.translation import ugettext_lazy as _
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'
class User(Document):
"""A User document that aims to mirror most of the API specified by Django
at http://docs.djangoproject.com/en/dev/topics/auth/#users
"""
username = StringField(max_length=30, required=True,
verbose_name=_('username'),
help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
first_name = StringField(max_length=30,
verbose_name=_('first name'))
last_name = StringField(max_length=30,
verbose_name=_('last name'))
email = EmailField(verbose_name=_('e-mail address'))
password = StringField(max_length=128,
verbose_name=_('password'),
help_text=_("Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
is_staff = BooleanField(default=False,
verbose_name=_('staff status'),
help_text=_("Designates whether the user can log into this admin site."))
is_active = BooleanField(default=True,
verbose_name=_('active'),
help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
is_superuser = BooleanField(default=False,
verbose_name=_('superuser status'),
help_text=_("Designates that this user has all permissions without explicitly assigning them."))
last_login = DateTimeField(default=datetime.datetime.now,
verbose_name=_('last login'))
date_joined = DateTimeField(default=datetime.datetime.now,
verbose_name=_('date joined'))
meta = {
'allow_inheritance': True,
'indexes': [
{'fields': ['username'], 'unique': True}
]
}
def __unicode__(self):
return self.username
def get_full_name(self):
"""Returns the users first and last names, separated by a space.
"""
full_name = u'%s %s' % (self.first_name or '', self.last_name or '')
return full_name.strip()
def is_anonymous(self):
return False
def is_authenticated(self):
return True
def set_password(self, raw_password):
"""Sets the user's password - always use this rather than directly
assigning to :attr:`~mongoengine.django.auth.User.password` as the
password is hashed before storage.
"""
self.password = make_password(raw_password)
self.save()
return self
def check_password(self, raw_password):
"""Checks the user's password against a provided password - always use
this rather than directly comparing to
:attr:`~mongoengine.django.auth.User.password` as the password is
hashed before storage.
"""
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
def create_user(cls, username, password, email=None):
"""Create (and save) a new user with the given username, password and
email address.
"""
now = datetime.datetime.now()
# Normalize the address by lowercasing the domain part of the email
# address.
if email is not None:
try:
email_name, domain_part = email.strip().split('@', 1)
except ValueError:
pass
else:
email = '@'.join([email_name, domain_part.lower()])
user = cls(username=username, email=email, date_joined=now)
user.set_password(password)
user.save()
return user
def get_and_delete_messages(self):
return []
class MongoEngineBackend(object):
"""Authenticate using MongoEngine and mongoengine.django.auth.User.
"""
supports_object_permissions = False
supports_anonymous_user = False
supports_inactive_user = False
def authenticate(self, username=None, password=None):
user = User.objects(username=username).first()
if user:
if password and user.check_password(password):
return user
return None
def get_user(self, user_id):
return User.objects.with_id(user_id)
def get_user(userid):
"""Returns a User object from an id (User.id). Django's equivalent takes
request, but taking an id instead leaves it up to the developer to store
the id in any way they want (session, signed cookie, etc.)
"""
if not userid:
return AnonymousUser()
return MongoEngineBackend().get_user(userid) or AnonymousUser()

View File

@@ -1,90 +0,0 @@
from datetime import datetime
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation
from django.utils.encoding import force_unicode
from mongoengine.document import Document
from mongoengine import fields
from mongoengine.queryset import OperationError
from mongoengine.connection import DEFAULT_CONNECTION_NAME
MONGOENGINE_SESSION_DB_ALIAS = getattr(
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
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):
session_key = fields.StringField(primary_key=True, max_length=40)
session_data = fields.StringField() if MONGOENGINE_SESSION_DATA_ENCODE \
else fields.DictField()
expire_date = fields.DateTimeField()
meta = {'collection': MONGOENGINE_SESSION_COLLECTION,
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
'allow_inheritance': False}
class SessionStore(SessionBase):
"""A MongoEngine-based session store for Django.
"""
def load(self):
try:
s = MongoSession.objects(session_key=self.session_key,
expire_date__gt=datetime.now())[0]
if MONGOENGINE_SESSION_DATA_ENCODE:
return self.decode(force_unicode(s.session_data))
else:
return s.session_data
except (IndexError, SuspiciousOperation):
self.create()
return {}
def exists(self, session_key):
return bool(MongoSession.objects(session_key=session_key).first())
def create(self):
while True:
self._session_key = self._get_new_session_key()
try:
self.save(must_create=True)
except CreateError:
continue
self.modified = True
self._session_cache = {}
return
def save(self, must_create=False):
if self.session_key is None:
self._session_key = self._get_new_session_key()
s = MongoSession(session_key=self.session_key)
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()
try:
s.save(force_insert=must_create, safe=True)
except OperationError:
if must_create:
raise CreateError
raise
def delete(self, session_key=None):
if session_key is None:
if self.session_key is None:
return
session_key = self.session_key
MongoSession.objects(session_key=session_key).delete()

View File

@@ -1,47 +0,0 @@
from mongoengine.queryset import QuerySet
from mongoengine.base import BaseDocument
from mongoengine.base import ValidationError
def _get_queryset(cls):
"""Inspired by django.shortcuts.*"""
if isinstance(cls, QuerySet):
return cls
else:
return cls.objects
def get_document_or_404(cls, *args, **kwargs):
"""
Uses get() to return an document, or raises a Http404 exception if the document
does not exist.
cls may be a Document or QuerySet object. All other passed
arguments and keyword arguments are used in the get() query.
Note: Like with get(), an MultipleObjectsReturned will be raised if more than one
object is found.
Inspired by django.shortcuts.*
"""
queryset = _get_queryset(cls)
try:
return queryset.get(*args, **kwargs)
except (queryset._document.DoesNotExist, ValidationError):
from django.http import Http404
raise Http404('No %s matches the given query.' % queryset._document._class_name)
def get_list_or_404(cls, *args, **kwargs):
"""
Uses filter() to return a list of documents, or raise a Http404 exception if
the list is empty.
cls may be a Document or QuerySet object. All other passed
arguments and keyword arguments are used in the filter() query.
Inspired by django.shortcuts.*
"""
queryset = _get_queryset(cls)
obj_list = list(queryset.filter(*args, **kwargs))
if not obj_list:
from django.http import Http404
raise Http404('No %s matches the given query.' % queryset._document._class_name)
return obj_list

View File

@@ -1,112 +0,0 @@
import os
import itertools
import urlparse
from mongoengine import *
from django.conf import settings
from django.core.files.storage import Storage
from django.core.exceptions import ImproperlyConfigured
class FileDocument(Document):
"""A document used to store a single file in GridFS.
"""
file = FileField()
class GridFSStorage(Storage):
"""A custom storage backend to store files in GridFS
"""
def __init__(self, base_url=None):
if base_url is None:
base_url = settings.MEDIA_URL
self.base_url = base_url
self.document = FileDocument
self.field = 'file'
def delete(self, name):
"""Deletes the specified file from the storage system.
"""
if self.exists(name):
doc = self.document.objects.first()
field = getattr(doc, self.field)
self._get_doc_with_name(name).delete() # Delete the FileField
field.delete() # Delete the FileDocument
def exists(self, name):
"""Returns True if a file referened by the given name already exists in the
storage system, or False if the name is available for a new file.
"""
doc = self._get_doc_with_name(name)
if doc:
field = getattr(doc, self.field)
return bool(field.name)
else:
return False
def listdir(self, path=None):
"""Lists the contents of the specified path, returning a 2-tuple of lists;
the first item being directories, the second item being files.
"""
def name(doc):
return getattr(doc, self.field).name
docs = self.document.objects
return [], [name(d) for d in docs if name(d)]
def size(self, name):
"""Returns the total size, in bytes, of the file specified by name.
"""
doc = self._get_doc_with_name(name)
if doc:
return getattr(doc, self.field).length
else:
raise ValueError("No such file or directory: '%s'" % name)
def url(self, name):
"""Returns an absolute URL where the file's contents can be accessed
directly by a web browser.
"""
if self.base_url is None:
raise ValueError("This file is not accessible via a URL.")
return urlparse.urljoin(self.base_url, name).replace('\\', '/')
def _get_doc_with_name(self, name):
"""Find the documents in the store with the given name
"""
docs = self.document.objects
doc = [d for d in docs if getattr(d, self.field).name == name]
if doc:
return doc[0]
else:
return None
def _open(self, name, mode='rb'):
doc = self._get_doc_with_name(name)
if doc:
return getattr(doc, self.field)
else:
raise ValueError("No file found with the name '%s'." % name)
def get_available_name(self, name):
"""Returns a filename that's free on the target storage system, and
available for new content to be written to.
"""
file_root, file_ext = os.path.splitext(name)
# If the filename already exists, add an underscore and a number (before
# the file extension, if one exists) to the filename until the generated
# filename doesn't exist.
count = itertools.count(1)
while self.exists(name):
# file_ext includes the dot.
name = os.path.join("%s_%s%s" % (file_root, count.next(), file_ext))
return name
def _save(self, name, content):
doc = self.document()
getattr(doc, self.field).put(content, filename=name)
doc.save()
return name

View File

@@ -1,39 +0,0 @@
#coding: utf-8
from nose.plugins.skip import SkipTest
from mongoengine.python_support import PY3
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):
def setUp(self):
if PY3:
raise SkipTest('django does not have Python 3 support')
"""
TestCase class that clear the collection between the tests
"""
db_name = 'test_%s' % settings.MONGO_DATABASE_NAME
def __init__(self, methodName='runtest'):
self.db = connect(self.db_name).get_db()
super(MongoTestCase, self).__init__(methodName)
def _post_teardown(self):
super(MongoTestCase, self)._post_teardown()
for collection in self.db.collection_names():
if collection == 'system.indexes':
continue
self.db.drop_collection(collection)

File diff suppressed because it is too large Load Diff

145
mongoengine/errors.py Normal file
View File

@@ -0,0 +1,145 @@
from collections import defaultdict
import six
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
'ValidationError', 'SaveConditionError')
class NotRegistered(Exception):
pass
class InvalidDocumentError(Exception):
pass
class LookUpError(AttributeError):
pass
class DoesNotExist(Exception):
pass
class MultipleObjectsReturned(Exception):
pass
class InvalidQueryError(Exception):
pass
class OperationError(Exception):
pass
class NotUniqueError(OperationError):
pass
class SaveConditionError(OperationError):
pass
class FieldDoesNotExist(Exception):
"""Raised when trying to set a field
not declared in a :class:`~mongoengine.Document`
or an :class:`~mongoengine.EmbeddedDocument`.
To avoid this behavior on data loading,
you should set the :attr:`strict` to ``False``
in the :attr:`meta` dictionary.
"""
class ValidationError(AssertionError):
"""Validation exception.
May represent an error validating a field or a
document containing fields with validation errors.
:ivar errors: A dictionary of errors for fields within this
document or list, or None if the error is for an
individual field.
"""
errors = {}
field_name = None
_message = None
def __init__(self, message='', **kwargs):
self.errors = kwargs.get('errors', {})
self.field_name = kwargs.get('field_name')
self.message = message
def __str__(self):
return six.text_type(self.message)
def __repr__(self):
return '%s(%s,)' % (self.__class__.__name__, self.message)
def __getattribute__(self, name):
message = super(ValidationError, self).__getattribute__(name)
if name == 'message':
if self.field_name:
message = '%s' % message
if self.errors:
message = '%s(%s)' % (message, self._format_errors())
return message
def _get_message(self):
return self._message
def _set_message(self, message):
self._message = message
message = property(_get_message, _set_message)
def to_dict(self):
"""Returns a dictionary of all errors within a document
Keys are field names or list indices and values are the
validation error messages, or a nested dictionary of
errors for an embedded document or list.
"""
def build_dict(source):
errors_dict = {}
if not source:
return errors_dict
if isinstance(source, dict):
for field_name, error in source.iteritems():
errors_dict[field_name] = build_dict(error)
elif isinstance(source, ValidationError) and source.errors:
return build_dict(source.errors)
else:
return six.text_type(source)
return errors_dict
if not self.errors:
return {}
return build_dict(self.errors)
def _format_errors(self):
"""Returns a string listing all errors within a document"""
def generate_key(value, prefix=''):
if isinstance(value, list):
value = ' '.join([generate_key(k) for k in value])
elif isinstance(value, dict):
value = ' '.join(
[generate_key(v, k) for k, v in value.iteritems()])
results = '%s.%s' % (prefix, value) if prefix else value
return results
error_dict = defaultdict(list)
for k, v in self.to_dict().iteritems():
error_dict[generate_key(v)].append(k)
return ' '.join(['%s: %s' % (k, v) for k, v in error_dict.iteritems()])

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,25 @@
"""Helper functions and types to aid with Python 2.5 - 3 support."""
"""
Helper functions, constants, and types to aid with Python v2.7 - v3.x and
PyMongo v2.7 - v3.x support.
"""
import pymongo
import six
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
if pymongo.version_tuple[0] < 3:
IS_PYMONGO_3 = False
else:
IS_PYMONGO_3 = True
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
StringIO = six.BytesIO
# Additionally for Py2, try to use the faster cStringIO, if available
if not six.PY3:
try:
from cStringIO import StringIO
import cStringIO
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)
pass
else:
raise ValueError("non list/dict parameter not allowed")
StringIO = cStringIO.StringIO

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
from mongoengine.errors import *
from mongoengine.queryset.field_list import *
from mongoengine.queryset.manager import *
from mongoengine.queryset.queryset import *
from mongoengine.queryset.transform import *
from mongoengine.queryset.visitor import *
# Expose just the public subset of all imported objects and constants.
__all__ = (
'QuerySet', 'QuerySetNoCache', 'Q', 'queryset_manager', 'QuerySetManager',
'QueryFieldList', 'DO_NOTHING', 'NULLIFY', 'CASCADE', 'DENY', 'PULL',
# Errors that might be related to a queryset, mostly here for backward
# compatibility
'DoesNotExist', 'InvalidQueryError', 'MultipleObjectsReturned',
'NotUniqueError', 'OperationError',
)

1880
mongoengine/queryset/base.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,85 @@
__all__ = ('QueryFieldList',)
class QueryFieldList(object):
"""Object that handles combinations of .only() and .exclude() calls"""
ONLY = 1
EXCLUDE = 0
def __init__(self, fields=None, value=ONLY, always_include=None, _only_called=False):
"""The QueryFieldList builder
:param fields: A list of fields used in `.only()` or `.exclude()`
:param value: How to handle the fields; either `ONLY` or `EXCLUDE`
:param always_include: Any fields to always_include eg `_cls`
:param _only_called: Has `.only()` been called? If so its a set of fields
otherwise it performs a union.
"""
self.value = value
self.fields = set(fields or [])
self.always_include = set(always_include or [])
self._id = None
self._only_called = _only_called
self.slice = {}
def __add__(self, f):
if isinstance(f.value, dict):
for field in f.fields:
self.slice[field] = f.value
if not self.fields:
self.fields = f.fields
elif not self.fields:
self.fields = f.fields
self.value = f.value
self.slice = {}
elif self.value is self.ONLY and f.value is self.ONLY:
self._clean_slice()
if self._only_called:
self.fields = self.fields.union(f.fields)
else:
self.fields = f.fields
elif self.value is self.EXCLUDE and f.value is self.EXCLUDE:
self.fields = self.fields.union(f.fields)
self._clean_slice()
elif self.value is self.ONLY and f.value is self.EXCLUDE:
self.fields -= f.fields
self._clean_slice()
elif self.value is self.EXCLUDE and f.value is self.ONLY:
self.value = self.ONLY
self.fields = f.fields - self.fields
self._clean_slice()
if '_id' in f.fields:
self._id = f.value
if self.always_include:
if self.value is self.ONLY and self.fields:
if sorted(self.slice.keys()) != sorted(self.fields):
self.fields = self.fields.union(self.always_include)
else:
self.fields -= self.always_include
if getattr(f, '_only_called', False):
self._only_called = True
return self
def __nonzero__(self):
return bool(self.fields)
def as_dict(self):
field_list = {field: self.value for field in self.fields}
if self.slice:
field_list.update(self.slice)
if self._id is not None:
field_list['_id'] = self._id
return field_list
def reset(self):
self.fields = set([])
self.slice = {}
self.value = self.ONLY
def _clean_slice(self):
if self.slice:
for field in set(self.slice.keys()) - self.fields:
del self.slice[field]

View File

@@ -0,0 +1,57 @@
from functools import partial
from mongoengine.queryset.queryset import QuerySet
__all__ = ('queryset_manager', 'QuerySetManager')
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
default = QuerySet
def __init__(self, queryset_func=None):
if queryset_func:
self.get_queryset = queryset_func
def __get__(self, instance, owner):
"""Descriptor for instantiating a new QuerySet object when
Document.objects is accessed.
"""
if instance is not None:
# Document object being used rather than a document class
return self
# owner is the document that contains the QuerySetManager
queryset_class = owner._meta.get('queryset_class', self.default)
queryset = queryset_class(owner, owner._get_collection())
if self.get_queryset:
arg_count = self.get_queryset.func_code.co_argcount
if arg_count == 1:
queryset = self.get_queryset(queryset)
elif arg_count == 2:
queryset = self.get_queryset(owner, queryset)
else:
queryset = partial(self.get_queryset, owner, queryset)
return queryset
def queryset_manager(func):
"""Decorator that allows you to define custom QuerySet managers on
:class:`~mongoengine.Document` classes. The manager must be a function that
accepts 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.
"""
return QuerySetManager(func)

View File

@@ -0,0 +1,193 @@
from mongoengine.errors import OperationError
from mongoengine.queryset.base import (BaseQuerySet, CASCADE, DENY, DO_NOTHING,
NULLIFY, PULL)
__all__ = ('QuerySet', 'QuerySetNoCache', 'DO_NOTHING', 'NULLIFY', 'CASCADE',
'DENY', 'PULL')
# The maximum number of items to display in a QuerySet.__repr__
REPR_OUTPUT_SIZE = 20
ITER_CHUNK_SIZE = 100
class QuerySet(BaseQuerySet):
"""The default queryset, that builds queries and handles a set of results
returned from a query.
Wraps a MongoDB cursor, providing :class:`~mongoengine.Document` objects as
the results.
"""
_has_more = True
_len = None
_result_cache = None
def __iter__(self):
"""Iteration utilises a results cache which iterates the cursor
in batches of ``ITER_CHUNK_SIZE``.
If ``self._has_more`` the cursor hasn't been exhausted so cache then
batch. Otherwise iterate the result_cache.
"""
self._iter = True
if self._has_more:
return self._iter_results()
# iterating over the cache.
return iter(self._result_cache)
def __len__(self):
"""Since __len__ is called quite frequently (for example, as part of
list(qs)), we populate the result cache and cache the length.
"""
if self._len is not None:
return self._len
# Populate the result cache with *all* of the docs in the cursor
if self._has_more:
list(self._iter_results())
# Cache the length of the complete result cache and return it
self._len = len(self._result_cache)
return self._len
def __repr__(self):
"""Provide a string representation of the QuerySet"""
if self._iter:
return '.. queryset mid-iteration ..'
self._populate_cache()
data = self._result_cache[:REPR_OUTPUT_SIZE + 1]
if len(data) > REPR_OUTPUT_SIZE:
data[-1] = '...(remaining elements truncated)...'
return repr(data)
def _iter_results(self):
"""A generator for iterating over the result cache.
Also populates the cache if there are more possible results to
yield. Raises StopIteration when there are no more results.
"""
if self._result_cache is None:
self._result_cache = []
pos = 0
while True:
# For all positions lower than the length of the current result
# cache, serve the docs straight from the cache w/o hitting the
# database.
# XXX it's VERY important to compute the len within the `while`
# condition because the result cache might expand mid-iteration
# (e.g. if we call len(qs) inside a loop that iterates over the
# queryset). Fortunately len(list) is O(1) in Python, so this
# doesn't cause performance issues.
while pos < len(self._result_cache):
yield self._result_cache[pos]
pos += 1
# Raise StopIteration if we already established there were no more
# docs in the db cursor.
if not self._has_more:
raise StopIteration
# Otherwise, populate more of the cache and repeat.
if len(self._result_cache) <= pos:
self._populate_cache()
def _populate_cache(self):
"""
Populates the result cache with ``ITER_CHUNK_SIZE`` more entries
(until the cursor is exhausted).
"""
if self._result_cache is None:
self._result_cache = []
# Skip populating the cache if we already established there are no
# more docs to pull from the database.
if not self._has_more:
return
# Pull in ITER_CHUNK_SIZE docs from the database and store them in
# the result cache.
try:
for _ in xrange(ITER_CHUNK_SIZE):
self._result_cache.append(self.next())
except StopIteration:
# Getting this exception means there are no more docs in the
# db cursor. Set _has_more to False so that we can use that
# information in other places.
self._has_more = False
def count(self, with_limit_and_skip=False):
"""Count the selected elements in the query.
:param with_limit_and_skip (optional): take any :meth:`limit` or
:meth:`skip` that has been applied to this cursor into account when
getting the count
"""
if with_limit_and_skip is False:
return super(QuerySet, self).count(with_limit_and_skip)
if self._len is None:
self._len = super(QuerySet, self).count(with_limit_and_skip)
return self._len
def no_cache(self):
"""Convert to a non-caching queryset
.. versionadded:: 0.8.3 Convert to non caching queryset
"""
if self._result_cache is not None:
raise OperationError('QuerySet already cached')
return self._clone_into(QuerySetNoCache(self._document,
self._collection))
class QuerySetNoCache(BaseQuerySet):
"""A non caching QuerySet"""
def cache(self):
"""Convert to a caching queryset
.. versionadded:: 0.8.3 Convert to caching queryset
"""
return self._clone_into(QuerySet(self._document, self._collection))
def __repr__(self):
"""Provides the string representation of the QuerySet
.. versionchanged:: 0.6.13 Now doesnt modify the cursor
"""
if self._iter:
return '.. queryset mid-iteration ..'
data = []
for _ in xrange(REPR_OUTPUT_SIZE + 1):
try:
data.append(self.next())
except StopIteration:
break
if len(data) > REPR_OUTPUT_SIZE:
data[-1] = '...(remaining elements truncated)...'
self.rewind()
return repr(data)
def __iter__(self):
queryset = self
if queryset._iter:
queryset = self.clone()
queryset.rewind()
return queryset
class QuerySetNoDeRef(QuerySet):
"""Special no_dereference QuerySet"""
def __dereference(items, max_depth=1, instance=None, name=None):
return items

View File

@@ -0,0 +1,427 @@
from collections import defaultdict
from bson import ObjectId, SON
from bson.dbref import DBRef
import pymongo
import six
from mongoengine.base import UPDATE_OPERATORS
from mongoengine.common import _import_class
from mongoengine.connection import get_connection
from mongoengine.errors import InvalidQueryError
from mongoengine.python_support import IS_PYMONGO_3
__all__ = ('query', 'update')
COMPARISON_OPERATORS = ('ne', 'gt', 'gte', 'lt', 'lte', 'in', 'nin', 'mod',
'all', 'size', 'exists', 'not', 'elemMatch', 'type')
GEO_OPERATORS = ('within_distance', 'within_spherical_distance',
'within_box', 'within_polygon', 'near', 'near_sphere',
'max_distance', 'min_distance', 'geo_within', 'geo_within_box',
'geo_within_polygon', 'geo_within_center',
'geo_within_sphere', 'geo_intersects')
STRING_OPERATORS = ('contains', 'icontains', 'startswith',
'istartswith', 'endswith', 'iendswith',
'exact', 'iexact')
CUSTOM_OPERATORS = ('match',)
MATCH_OPERATORS = (COMPARISON_OPERATORS + GEO_OPERATORS +
STRING_OPERATORS + CUSTOM_OPERATORS)
# TODO make this less complex
def query(_doc_cls=None, **kwargs):
"""Transform a query from Django-style format to Mongo format."""
mongo_query = {}
merge_query = defaultdict(list)
for key, value in sorted(kwargs.items()):
if key == '__raw__':
mongo_query.update(value)
continue
parts = key.rsplit('__')
indices = [(i, p) for i, p in enumerate(parts) if p.isdigit()]
parts = [part for part in parts if not part.isdigit()]
# Check for an operator and transform to mongo-style if there is
op = None
if len(parts) > 1 and parts[-1] in MATCH_OPERATORS:
op = parts.pop()
# Allow to escape operator-like field name by __
if len(parts) > 1 and parts[-1] == '':
parts.pop()
negate = False
if len(parts) > 1 and parts[-1] == 'not':
parts.pop()
negate = True
if _doc_cls:
# Switch field names to proper names [set in Field(name='foo')]
try:
fields = _doc_cls._lookup_field(parts)
except Exception as e:
raise InvalidQueryError(e)
parts = []
CachedReferenceField = _import_class('CachedReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
cleaned_fields = []
for field in fields:
append_field = True
if isinstance(field, six.string_types):
parts.append(field)
append_field = False
# is last and CachedReferenceField
elif isinstance(field, CachedReferenceField) and fields[-1] == field:
parts.append('%s._id' % field.db_field)
else:
parts.append(field.db_field)
if append_field:
cleaned_fields.append(field)
# Convert value to proper value
field = cleaned_fields[-1]
singular_ops = [None, 'ne', 'gt', 'gte', 'lt', 'lte', 'not']
singular_ops += STRING_OPERATORS
if op in singular_ops:
if isinstance(field, six.string_types):
if (op in STRING_OPERATORS and
isinstance(value, six.string_types)):
StringField = _import_class('StringField')
value = StringField.prepare_query_value(op, value)
else:
value = field
else:
value = field.prepare_query_value(op, value)
if isinstance(field, CachedReferenceField) and value:
value = value['_id']
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
# Raise an error if the in/nin/all/near param is not iterable. We need a
# special check for BaseDocument, because - although it's iterable - using
# it as such in the context of this method is most definitely a mistake.
BaseDocument = _import_class('BaseDocument')
if isinstance(value, BaseDocument):
raise TypeError("When using the `in`, `nin`, `all`, or "
"`near`-operators you can\'t use a "
"`Document`, you must wrap your object "
"in a list (object -> [object]).")
elif not hasattr(value, '__iter__'):
raise TypeError("The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list).")
else:
value = [field.prepare_query_value(op, v) for v in value]
# If we're querying a GenericReferenceField, we need to alter the
# key depending on the value:
# * If the value is a DBRef, the key should be "field_name._ref".
# * If the value is an ObjectId, the key should be "field_name._ref.$id".
if isinstance(field, GenericReferenceField):
if isinstance(value, DBRef):
parts[-1] += '._ref'
elif isinstance(value, ObjectId):
parts[-1] += '._ref.$id'
# if op and op not in COMPARISON_OPERATORS:
if op:
if op in GEO_OPERATORS:
value = _geo_operator(field, op, value)
elif op in ('match', 'elemMatch'):
ListField = _import_class('ListField')
EmbeddedDocumentField = _import_class('EmbeddedDocumentField')
if (
isinstance(value, dict) and
isinstance(field, ListField) and
isinstance(field.field, EmbeddedDocumentField)
):
value = query(field.field.document_type, **value)
else:
value = field.prepare_query_value(op, value)
value = {'$elemMatch': value}
elif op in CUSTOM_OPERATORS:
NotImplementedError('Custom method "%s" has not '
'been implemented' % op)
elif op not in STRING_OPERATORS:
value = {'$' + op: value}
if negate:
value = {'$not': value}
for i, part in indices:
parts.insert(i, part)
key = '.'.join(parts)
if op is None or key not in mongo_query:
mongo_query[key] = value
elif key in mongo_query:
if isinstance(mongo_query[key], dict):
mongo_query[key].update(value)
# $max/minDistance needs to come last - convert to SON
value_dict = mongo_query[key]
if ('$maxDistance' in value_dict or '$minDistance' in value_dict) and \
('$near' in value_dict or '$nearSphere' in value_dict):
value_son = SON()
for k, v in value_dict.iteritems():
if k == '$maxDistance' or k == '$minDistance':
continue
value_son[k] = v
# Required for MongoDB >= 2.6, may fail when combining
# PyMongo 3+ and MongoDB < 2.6
near_embedded = False
for near_op in ('$near', '$nearSphere'):
if isinstance(value_dict.get(near_op), dict) and (
IS_PYMONGO_3 or get_connection().max_wire_version > 1):
value_son[near_op] = SON(value_son[near_op])
if '$maxDistance' in value_dict:
value_son[near_op][
'$maxDistance'] = value_dict['$maxDistance']
if '$minDistance' in value_dict:
value_son[near_op][
'$minDistance'] = value_dict['$minDistance']
near_embedded = True
if not near_embedded:
if '$maxDistance' in value_dict:
value_son['$maxDistance'] = value_dict['$maxDistance']
if '$minDistance' in value_dict:
value_son['$minDistance'] = value_dict['$minDistance']
mongo_query[key] = value_son
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'].extend(value)
else:
mongo_query['$and'] = value
return mongo_query
def update(_doc_cls=None, **update):
"""Transform an update spec from Django-style format to Mongo
format.
"""
mongo_update = {}
for key, value in update.items():
if key == '__raw__':
mongo_update.update(value)
continue
parts = key.split('__')
# if there is no operator, default to 'set'
if len(parts) < 3 and parts[0] not in UPDATE_OPERATORS:
parts.insert(0, 'set')
# Check for an operator and transform to mongo-style if there is
op = None
if parts[0] in UPDATE_OPERATORS:
op = parts.pop(0)
# Convert Pythonic names to Mongo equivalents
if op in ('push_all', 'pull_all'):
op = op.replace('_all', 'All')
elif op == 'dec':
# Support decrement by flipping a positive value's sign
# and using 'inc'
op = 'inc'
value = -value
elif op == 'add_to_set':
op = 'addToSet'
elif op == 'set_on_insert':
op = 'setOnInsert'
match = None
if parts[-1] in COMPARISON_OPERATORS:
match = parts.pop()
# Allow to escape operator-like field name by __
if len(parts) > 1 and parts[-1] == '':
parts.pop()
if _doc_cls:
# Switch field names to proper names [set in Field(name='foo')]
try:
fields = _doc_cls._lookup_field(parts)
except Exception as e:
raise InvalidQueryError(e)
parts = []
cleaned_fields = []
appended_sub_field = False
for field in fields:
append_field = True
if isinstance(field, six.string_types):
# Convert the S operator to $
if field == 'S':
field = '$'
parts.append(field)
append_field = False
else:
parts.append(field.db_field)
if append_field:
appended_sub_field = False
cleaned_fields.append(field)
if hasattr(field, 'field'):
cleaned_fields.append(field.field)
appended_sub_field = True
# Convert value to proper value
if appended_sub_field:
field = cleaned_fields[-2]
else:
field = cleaned_fields[-1]
GeoJsonBaseField = _import_class('GeoJsonBaseField')
if isinstance(field, GeoJsonBaseField):
value = field.to_mongo(value)
if op in (None, 'set', 'push', 'pull'):
if field.required or value is not None:
value = field.prepare_query_value(op, value)
elif op in ('pushAll', 'pullAll'):
value = [field.prepare_query_value(op, v) for v in value]
elif op in ('addToSet', 'setOnInsert'):
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)
elif op == 'unset':
value = 1
if match:
match = '$' + match
value = {match: value}
key = '.'.join(parts)
if not op:
raise InvalidQueryError('Updates must supply an operation '
'eg: set__FIELD=value')
if 'pull' in op and '.' in key:
# Dot operators don't work on pull operations
# unless they point to a list field
# Otherwise it uses nested dict syntax
if op == 'pullAll':
raise InvalidQueryError('pullAll operations only support '
'a single field depth')
# Look for the last list field and use dot notation until there
field_classes = [c.__class__ for c in cleaned_fields]
field_classes.reverse()
ListField = _import_class('ListField')
if ListField in field_classes:
# Join all fields via dot notation to the last ListField
# Then process as normal
last_listField = len(
cleaned_fields) - field_classes.index(ListField)
key = '.'.join(parts[:last_listField])
parts = parts[last_listField:]
parts.insert(0, key)
parts.reverse()
for key in parts:
value = {key: value}
elif op == 'addToSet' and isinstance(value, list):
value = {key: {'$each': value}}
else:
value = {key: value}
key = '$' + op
if key not in mongo_update:
mongo_update[key] = value
elif key in mongo_update and isinstance(mongo_update[key], dict):
mongo_update[key].update(value)
return mongo_update
def _geo_operator(field, op, value):
"""Helper to return the query for a given geo query."""
if op == 'max_distance':
value = {'$maxDistance': value}
elif op == 'min_distance':
value = {'$minDistance': value}
elif field._geo_index == pymongo.GEO2D:
if op == 'within_distance':
value = {'$within': {'$center': value}}
elif op == 'within_spherical_distance':
value = {'$within': {'$centerSphere': value}}
elif op == 'within_polygon':
value = {'$within': {'$polygon': value}}
elif op == 'near':
value = {'$near': value}
elif op == 'near_sphere':
value = {'$nearSphere': value}
elif op == 'within_box':
value = {'$within': {'$box': value}}
else:
raise NotImplementedError('Geo method "%s" has not been '
'implemented for a GeoPointField' % op)
else:
if op == 'geo_within':
value = {'$geoWithin': _infer_geometry(value)}
elif op == 'geo_within_box':
value = {'$geoWithin': {'$box': value}}
elif op == 'geo_within_polygon':
value = {'$geoWithin': {'$polygon': value}}
elif op == 'geo_within_center':
value = {'$geoWithin': {'$center': value}}
elif op == 'geo_within_sphere':
value = {'$geoWithin': {'$centerSphere': value}}
elif op == 'geo_intersects':
value = {'$geoIntersects': _infer_geometry(value)}
elif op == 'near':
value = {'$near': _infer_geometry(value)}
else:
raise NotImplementedError(
'Geo method "%s" has not been implemented for a %s '
% (op, field._name)
)
return value
def _infer_geometry(value):
"""Helper method that tries to infer the $geometry shape for a
given value.
"""
if isinstance(value, dict):
if '$geometry' in value:
return value
elif 'coordinates' in value and 'type' in value:
return {'$geometry': value}
raise InvalidQueryError('Invalid $geometry dictionary should have '
'type and coordinates keys')
elif isinstance(value, (list, set)):
# TODO: shouldn't we test value[0][0][0][0] to see if it is MultiPolygon?
# TODO: should both TypeError and IndexError be alike interpreted?
try:
value[0][0][0]
return {'$geometry': {'type': 'Polygon', 'coordinates': value}}
except (TypeError, IndexError):
pass
try:
value[0][0]
return {'$geometry': {'type': 'LineString', 'coordinates': value}}
except (TypeError, IndexError):
pass
try:
value[0]
return {'$geometry': {'type': 'Point', 'coordinates': value}}
except (TypeError, IndexError):
pass
raise InvalidQueryError('Invalid $geometry data. Can be either a '
'dictionary or (nested) lists of coordinate(s)')

View File

@@ -0,0 +1,159 @@
import copy
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import transform
__all__ = ('Q',)
class QNodeVisitor(object):
"""Base visitor class for visiting Q-object nodes in a query tree.
"""
def visit_combination(self, combination):
"""Called by QCombination objects.
"""
return combination
def visit_query(self, query):
"""Called by (New)Q objects.
"""
return query
class DuplicateQueryConditionsError(InvalidQueryError):
pass
class SimplificationVisitor(QNodeVisitor):
"""Simplifies query trees by combining unnecessary 'and' connection nodes
into a single Q-object.
"""
def visit_combination(self, combination):
if combination.operation == combination.AND:
# The simplification only applies to 'simple' queries
if all(isinstance(node, Q) for node in combination.children):
queries = [n.query for n in combination.children]
try:
return Q(**self._query_conjunction(queries))
except DuplicateQueryConditionsError:
# Cannot be simplified
pass
return combination
def _query_conjunction(self, queries):
"""Merges query dicts - effectively &ing them together.
"""
query_ops = set()
combined_query = {}
for query in queries:
ops = set(query.keys())
# Make sure that the same operation isn't applied more than once
# to a single field
intersection = ops.intersection(query_ops)
if intersection:
raise DuplicateQueryConditionsError()
query_ops.update(ops)
combined_query.update(copy.deepcopy(query))
return combined_query
class QueryCompilerVisitor(QNodeVisitor):
"""Compiles the nodes in a query tree to a PyMongo-compatible query
dictionary.
"""
def __init__(self, document):
self.document = document
def visit_combination(self, combination):
operator = '$and'
if combination.operation == combination.OR:
operator = '$or'
return {operator: combination.children}
def visit_query(self, query):
return transform.query(self.document, **query.query)
class QNode(object):
"""Base class for nodes in query trees."""
AND = 0
OR = 1
def to_query(self, document):
query = self.accept(SimplificationVisitor())
query = query.accept(QueryCompilerVisitor(document))
return query
def accept(self, visitor):
raise NotImplementedError
def _combine(self, other, operation):
"""Combine this node with another node into a QCombination
object.
"""
if getattr(other, 'empty', True):
return self
if self.empty:
return other
return QCombination(operation, [self, other])
@property
def empty(self):
return False
def __or__(self, other):
return self._combine(other, self.OR)
def __and__(self, other):
return self._combine(other, self.AND)
class QCombination(QNode):
"""Represents the combination of several conditions by a given
logical operator.
"""
def __init__(self, operation, children):
self.operation = operation
self.children = []
for node in children:
# If the child is a combination of the same type, we can merge its
# children directly into this combinations children
if isinstance(node, QCombination) and node.operation == operation:
self.children += node.children
else:
self.children.append(node)
def accept(self, visitor):
for i in range(len(self.children)):
if isinstance(self.children[i], QNode):
self.children[i] = self.children[i].accept(visitor)
return visitor.visit_combination(self)
@property
def empty(self):
return not bool(self.children)
class Q(QNode):
"""A simple query object, used in a query tree to build up more complex
query structures.
"""
def __init__(self, **query):
self.query = query
def accept(self, visitor):
return visitor.visit_query(self)
@property
def empty(self):
return not bool(self.query)

View File

@@ -1,11 +1,10 @@
# -*- coding: utf-8 -*-
__all__ = ['pre_init', 'post_init', 'pre_save', 'post_save',
'pre_delete', 'post_delete']
__all__ = ('pre_init', 'post_init', 'pre_save', 'pre_save_post_validation',
'post_save', 'pre_delete', 'post_delete')
signals_available = False
try:
from blinker import Namespace
signals_available = True
except ImportError:
class Namespace(object):
@@ -27,11 +26,13 @@ except ImportError:
raise RuntimeError('signalling support is unavailable '
'because the blinker library is '
'not installed.')
send = lambda *a, **kw: None
send = lambda *a, **kw: None # noqa
connect = disconnect = has_receivers_for = receivers_for = \
temporarily_connected_to = _fail
del _fail
# the namespace for code signals. If you are not mongoengine code, do
# not put signals in here. Create your own namespace instead.
_signals = Namespace()
@@ -39,6 +40,7 @@ _signals = Namespace()
pre_init = _signals.signal('pre_init')
post_init = _signals.signal('post_init')
pre_save = _signals.signal('pre_save')
pre_save_post_validation = _signals.signal('pre_save_post_validation')
post_save = _signals.signal('post_save')
pre_delete = _signals.signal('pre_delete')
post_delete = _signals.signal('post_delete')

View File

@@ -1,59 +0,0 @@
from mongoengine.connection import get_db
class query_counter(object):
""" Query_counter contextmanager to get the number of queries. """
def __init__(self):
""" Construct the query_counter. """
self.counter = 0
self.db = get_db()
def __enter__(self):
""" On every with block we need to drop the profile collection. """
self.db.set_profiling_level(0)
self.db.system.profile.drop()
self.db.set_profiling_level(2)
return self
def __exit__(self, t, value, traceback):
""" Reset the profiling level. """
self.db.set_profiling_level(0)
def __eq__(self, value):
""" == Compare querycounter. """
return value == self._get_count()
def __ne__(self, value):
""" != Compare querycounter. """
return not self.__eq__(value)
def __lt__(self, value):
""" < Compare querycounter. """
return self._get_count() < value
def __le__(self, value):
""" <= Compare querycounter. """
return self._get_count() <= value
def __gt__(self, value):
""" > Compare querycounter. """
return self._get_count() > value
def __ge__(self, value):
""" >= Compare querycounter. """
return self._get_count() >= value
def __int__(self):
""" int representation. """
return self._get_count()
def __repr__(self):
""" repr query_counter as the number of queries. """
return u"%s" % self._get_count()
def _get_count(self):
""" Get the number of queries. """
count = self.db.system.profile.find().count() - self.counter
self.counter += 1
return count

View File

@@ -5,7 +5,7 @@
%define srcname mongoengine
Name: python-%{srcname}
Version: 0.7.9
Version: 0.8.7
Release: 1%{?dist}
Summary: A Python Document-Object Mapper for working with MongoDB
@@ -51,4 +51,4 @@ rm -rf $RPM_BUILD_ROOT
# %{python_sitearch}/*
%changelog
* See: http://readthedocs.org/docs/mongoengine-odm/en/latest/changelog.html
* See: http://docs.mongoengine.org/en/latest/changelog.html

View File

@@ -1 +1,7 @@
pymongo
nose
pymongo>=2.7.1
six==1.10.0
flake8
flake8-import-order
Sphinx==1.5.5
sphinx-rtd-theme==0.2.4

View File

@@ -1,11 +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
verbosity=2
detailed-errors=1
tests=tests
cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
application-import-names=mongoengine,tests

View File

@@ -1,6 +1,6 @@
import os
import sys
from setuptools import setup, find_packages
from setuptools import find_packages, setup
# Hack to silence atexit traceback in newer python versions
try:
@@ -8,20 +8,25 @@ try:
except ImportError:
pass
DESCRIPTION = """MongoEngine is a Python Object-Document
Mapper for working with MongoDB."""
LONG_DESCRIPTION = None
DESCRIPTION = (
'MongoEngine is a Python Object-Document '
'Mapper for working with MongoDB.'
)
try:
LONG_DESCRIPTION = open('README.rst').read()
except:
pass
with open('README.rst') as fin:
LONG_DESCRIPTION = fin.read()
except Exception:
LONG_DESCRIPTION = None
def get_version(version_tuple):
if not isinstance(version_tuple[-1], int):
return '.'.join(map(str, version_tuple[:-1])) + version_tuple[-1]
"""Return the version tuple as a string, e.g. for (0, 10, 7),
return '0.10.7'.
"""
return '.'.join(map(str, version_tuple))
# 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
# file is read
@@ -29,7 +34,6 @@ init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
version_line = list(filter(lambda l: l.startswith('VERSION'), open(init)))[0]
VERSION = get_version(eval(version_line.split('=')[-1]))
print(VERSION)
CLASSIFIERS = [
'Development Status :: 4 - Beta',
@@ -38,44 +42,46 @@ CLASSIFIERS = [
'Operating System :: OS Independent',
'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 :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
'Topic :: Database',
'Topic :: Software Development :: Libraries :: Python Modules',
]
extra_opts = {}
extra_opts = {
'packages': find_packages(exclude=['tests', 'tests.*']),
'tests_require': ['nose', 'coverage==4.2', 'blinker', 'Pillow>=2.0.0']
}
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"]}
if 'test' in sys.argv or 'nosetests' in sys.argv:
extra_opts['packages'] = find_packages()
extra_opts['package_data'] = {
'tests': ['fields/mongoengine.png', 'fields/mongodb_leaf.png']}
else:
extra_opts['tests_require'] = ['nose', 'coverage', 'blinker', 'django>=1.3', 'PIL']
extra_opts['packages'] = find_packages(exclude=('tests',))
extra_opts['tests_require'] += ['python-dateutil']
setup(name='mongoengine',
version=VERSION,
author='Harry Marr',
author_email='harry.marr@{nospam}gmail.com',
maintainer="Ross Lawley",
maintainer_email="ross.lawley@{nospam}gmail.com",
url='http://mongoengine.org/',
download_url='https://github.com/MongoEngine/mongoengine/tarball/master',
license='MIT',
include_package_data=True,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
platforms=['any'],
classifiers=CLASSIFIERS,
install_requires=['pymongo'],
test_suite='nose.collector',
**extra_opts
setup(
name='mongoengine',
version=VERSION,
author='Harry Marr',
author_email='harry.marr@{nospam}gmail.com',
maintainer="Ross Lawley",
maintainer_email="ross.lawley@{nospam}gmail.com",
url='http://mongoengine.org/',
download_url='https://github.com/MongoEngine/mongoengine/tarball/master',
license='MIT',
include_package_data=True,
description=DESCRIPTION,
long_description=LONG_DESCRIPTION,
platforms=['any'],
classifiers=CLASSIFIERS,
install_requires=['pymongo>=2.7.1', 'six'],
test_suite='nose.collector',
**extra_opts
)

View File

@@ -0,0 +1,4 @@
from all_warnings import AllWarnings
from document import *
from queryset import *
from fields import *

View File

@@ -0,0 +1,42 @@
"""
This test has been put into a module. This is because it tests warnings that
only get triggered on first hit. This way we can ensure its imported into the
top level and called first by the test suite.
"""
import unittest
import warnings
from mongoengine import *
__all__ = ('AllWarnings', )
class AllWarnings(unittest.TestCase):
def setUp(self):
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_document_collection_syntax_warning(self):
class NonAbstractBase(Document):
meta = {'allow_inheritance': True}
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())

View File

@@ -0,0 +1,13 @@
import unittest
from class_methods import *
from delta import *
from dynamic import *
from indexes import *
from inheritance import *
from instance import *
from json_serialisation import *
from validation import *
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,350 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import *
from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db
__all__ = ("ClassMethodsTest", )
class ClassMethodsTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class Person(Document):
name = StringField()
age = IntField()
non_field = True
meta = {"allow_inheritance": True}
self.Person = Person
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
self.db.drop_collection(collection)
def test_definition(self):
"""Ensure that document may be defined using fields.
"""
self.assertEqual(['_cls', 'age', 'id', 'name'],
sorted(self.Person._fields.keys()))
self.assertEqual(["IntField", "ObjectIdField", "StringField", "StringField"],
sorted([x.__class__.__name__ for x in
self.Person._fields.values()]))
def test_get_db(self):
"""Ensure that get_db returns the expected db.
"""
db = self.Person._get_db()
self.assertEqual(self.db, db)
def test_get_collection_name(self):
"""Ensure that get_collection_name returns the expected collection
name.
"""
collection_name = 'person'
self.assertEqual(collection_name, self.Person._get_collection_name())
def test_get_collection(self):
"""Ensure that get_collection returns the expected collection.
"""
collection_name = 'person'
collection = self.Person._get_collection()
self.assertEqual(self.db[collection_name], collection)
def test_drop_collection(self):
"""Ensure that the collection may be dropped from the database.
"""
collection_name = 'person'
self.Person(name='Test').save()
self.assertTrue(collection_name in self.db.collection_names())
self.Person.drop_collection()
self.assertFalse(collection_name in self.db.collection_names())
def test_register_delete_rule(self):
"""Ensure that register delete rule adds a delete rule to the document
meta.
"""
class Job(Document):
employee = ReferenceField(self.Person)
self.assertEqual(self.Person._meta.get('delete_rules'), None)
self.Person.register_delete_rule(Job, 'employee', NULLIFY)
self.assertEqual(self.Person._meta['delete_rules'],
{(Job, 'employee'): NULLIFY})
def test_compare_indexes(self):
""" Ensure that the indexes are properly created and that
compare_indexes identifies the missing/extra indexes
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
tags = StringField()
meta = {
'indexes': [('author', 'title')]
}
BlogPost.drop_collection()
BlogPost.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPost.ensure_index(['author', 'description'])
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('author', 1), ('description', 1)]] })
BlogPost._get_collection().drop_index('author_1_description_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPost._get_collection().drop_index('author_1_title_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('author', 1), ('title', 1)]], 'extra': [] })
def test_compare_indexes_inheritance(self):
""" Ensure that the indexes are properly created and that
compare_indexes identifies the missing/extra indexes for subclassed
documents (_cls included)
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
class BlogPostWithTags(BlogPost):
tags = StringField()
tag_list = ListField(StringField())
meta = {
'indexes': [('author', 'tags')]
}
BlogPost.drop_collection()
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPostWithTags.ensure_index(['author', 'tag_list'])
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]] })
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tag_list_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tags_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': [] })
def test_compare_indexes_multiple_subclasses(self):
""" Ensure that compare_indexes behaves correctly if called from a
class, which base class has multiple subclasses
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
class BlogPostWithTags(BlogPost):
tags = StringField()
tag_list = ListField(StringField())
meta = {
'indexes': [('author', 'tags')]
}
class BlogPostWithCustomField(BlogPost):
custom = DictField()
meta = {
'indexes': [('author', 'custom')]
}
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
BlogPostWithCustomField.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
def test_list_indexes_inheritance(self):
""" ensure that all of the indexes are listed regardless of the super-
or sub-class that we call it from
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
class BlogPostWithTags(BlogPost):
tags = StringField()
meta = {
'indexes': [('author', 'tags')]
}
class BlogPostWithTagsAndExtraText(BlogPostWithTags):
extra_text = StringField()
meta = {
'indexes': [('author', 'tags', 'extra_text')]
}
BlogPost.drop_collection()
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
BlogPostWithTagsAndExtraText.ensure_indexes()
self.assertEqual(BlogPost.list_indexes(),
BlogPostWithTags.list_indexes())
self.assertEqual(BlogPost.list_indexes(),
BlogPostWithTagsAndExtraText.list_indexes())
self.assertEqual(BlogPost.list_indexes(),
[[('_cls', 1), ('author', 1), ('tags', 1)],
[('_cls', 1), ('author', 1), ('tags', 1), ('extra_text', 1)],
[(u'_id', 1)], [('_cls', 1)]])
def test_register_delete_rule_inherited(self):
class Vaccine(Document):
name = StringField(required=True)
meta = {"indexes": ["name"]}
class Animal(Document):
family = StringField(required=True)
vaccine_made = ListField(ReferenceField("Vaccine", reverse_delete_rule=PULL))
meta = {"allow_inheritance": True, "indexes": ["family"]}
class Cat(Animal):
name = StringField(required=True)
self.assertEqual(Vaccine._meta['delete_rules'][(Animal, 'vaccine_made')], PULL)
self.assertEqual(Vaccine._meta['delete_rules'][(Cat, 'vaccine_made')], PULL)
def test_collection_naming(self):
"""Ensure that a collection with a specified name may be used.
"""
class DefaultNamingTest(Document):
pass
self.assertEqual('default_naming_test',
DefaultNamingTest._get_collection_name())
class CustomNamingTest(Document):
meta = {'collection': 'pimp_my_collection'}
self.assertEqual('pimp_my_collection',
CustomNamingTest._get_collection_name())
class DynamicNamingTest(Document):
meta = {'collection': lambda c: "DYNAMO"}
self.assertEqual('DYNAMO', DynamicNamingTest._get_collection_name())
# Use Abstract class to handle backwards compatibility
class BaseDocument(Document):
meta = {
'abstract': True,
'collection': lambda c: c.__name__.lower()
}
class OldNamingConvention(BaseDocument):
pass
self.assertEqual('oldnamingconvention',
OldNamingConvention._get_collection_name())
class InheritedAbstractNamingTest(BaseDocument):
meta = {'collection': 'wibble'}
self.assertEqual('wibble',
InheritedAbstractNamingTest._get_collection_name())
# Mixin tests
class BaseMixin(object):
meta = {
'collection': lambda c: c.__name__.lower()
}
class OldMixinNamingConvention(Document, BaseMixin):
pass
self.assertEqual('oldmixinnamingconvention',
OldMixinNamingConvention._get_collection_name())
class BaseMixin(object):
meta = {
'collection': lambda c: c.__name__.lower()
}
class BaseDocument(Document, BaseMixin):
meta = {'allow_inheritance': True}
class MyDocument(BaseDocument):
pass
self.assertEqual('basedocument', MyDocument._get_collection_name())
def test_custom_collection_name_operations(self):
"""Ensure that a collection with a specified name is used as expected.
"""
collection_name = 'personCollTest'
class Person(Document):
name = StringField()
meta = {'collection': collection_name}
Person(name="Test User").save()
self.assertTrue(collection_name in self.db.collection_names())
user_obj = self.db[collection_name].find_one()
self.assertEqual(user_obj['name'], "Test User")
user_obj = Person.objects[0]
self.assertEqual(user_obj.name, "Test User")
Person.drop_collection()
self.assertFalse(collection_name in self.db.collection_names())
def test_collection_name_and_primary(self):
"""Ensure that a collection with a specified name may be used.
"""
class Person(Document):
name = StringField(primary_key=True)
meta = {'collection': 'app'}
Person(name="Test User").save()
user_obj = Person.objects.first()
self.assertEqual(user_obj.name, "Test User")
Person.drop_collection()
if __name__ == '__main__':
unittest.main()

867
tests/document/delta.py Normal file
View File

@@ -0,0 +1,867 @@
# -*- coding: utf-8 -*-
import unittest
from bson import SON
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("DeltaTest",)
class DeltaTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class Person(Document):
name = StringField()
age = IntField()
non_field = True
meta = {"allow_inheritance": True}
self.Person = Person
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
self.db.drop_collection(collection)
def test_delta(self):
self.delta(Document)
self.delta(DynamicDocument)
def delta(self, DocClass):
class Doc(DocClass):
string_field = StringField()
int_field = IntField()
dict_field = DictField()
list_field = ListField()
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
doc.string_field = 'hello'
self.assertEqual(doc._get_changed_fields(), ['string_field'])
self.assertEqual(doc._delta(), ({'string_field': 'hello'}, {}))
doc._changed_fields = []
doc.int_field = 1
self.assertEqual(doc._get_changed_fields(), ['int_field'])
self.assertEqual(doc._delta(), ({'int_field': 1}, {}))
doc._changed_fields = []
dict_value = {'hello': 'world', 'ping': 'pong'}
doc.dict_field = dict_value
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
self.assertEqual(doc._delta(), ({'dict_field': dict_value}, {}))
doc._changed_fields = []
list_value = ['1', 2, {'hello': 'world'}]
doc.list_field = list_value
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({'list_field': list_value}, {}))
# Test unsetting
doc._changed_fields = []
doc.dict_field = {}
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
self.assertEqual(doc._delta(), ({}, {'dict_field': 1}))
doc._changed_fields = []
doc.list_field = []
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({}, {'list_field': 1}))
def test_delta_recursive(self):
self.delta_recursive(Document, EmbeddedDocument)
self.delta_recursive(DynamicDocument, EmbeddedDocument)
self.delta_recursive(Document, DynamicEmbeddedDocument)
self.delta_recursive(DynamicDocument, DynamicEmbeddedDocument)
def delta_recursive(self, DocClass, EmbeddedClass):
class Embedded(EmbeddedClass):
id = StringField()
string_field = StringField()
int_field = IntField()
dict_field = DictField()
list_field = ListField()
class Doc(DocClass):
string_field = StringField()
int_field = IntField()
dict_field = DictField()
list_field = ListField()
embedded_field = EmbeddedDocumentField(Embedded)
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
embedded_1 = Embedded()
embedded_1.id = "010101"
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field = embedded_1
self.assertEqual(doc._get_changed_fields(), ['embedded_field'])
embedded_delta = {
'id': "010101",
'string_field': 'hello',
'int_field': 1,
'dict_field': {'hello': 'world'},
'list_field': ['1', 2, {'hello': 'world'}]
}
self.assertEqual(doc.embedded_field._delta(), (embedded_delta, {}))
self.assertEqual(doc._delta(),
({'embedded_field': embedded_delta}, {}))
doc.save()
doc = doc.reload(10)
doc.embedded_field.dict_field = {}
self.assertEqual(doc._get_changed_fields(),
['embedded_field.dict_field'])
self.assertEqual(doc.embedded_field._delta(), ({}, {'dict_field': 1}))
self.assertEqual(doc._delta(), ({}, {'embedded_field.dict_field': 1}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.dict_field, {})
doc.embedded_field.list_field = []
self.assertEqual(doc._get_changed_fields(),
['embedded_field.list_field'])
self.assertEqual(doc.embedded_field._delta(), ({}, {'list_field': 1}))
self.assertEqual(doc._delta(), ({}, {'embedded_field.list_field': 1}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field, [])
embedded_2 = Embedded()
embedded_2.string_field = 'hello'
embedded_2.int_field = 1
embedded_2.dict_field = {'hello': 'world'}
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field.list_field = ['1', 2, embedded_2]
self.assertEqual(doc._get_changed_fields(),
['embedded_field.list_field'])
self.assertEqual(doc.embedded_field._delta(), ({
'list_field': ['1', 2, {
'_cls': 'Embedded',
'string_field': 'hello',
'dict_field': {'hello': 'world'},
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
}]
}, {}))
self.assertEqual(doc._delta(), ({
'embedded_field.list_field': ['1', 2, {
'_cls': 'Embedded',
'string_field': 'hello',
'dict_field': {'hello': 'world'},
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
}]
}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[0], '1')
self.assertEqual(doc.embedded_field.list_field[1], 2)
for k in doc.embedded_field.list_field[2]._fields:
self.assertEqual(doc.embedded_field.list_field[2][k],
embedded_2[k])
doc.embedded_field.list_field[2].string_field = 'world'
self.assertEqual(doc._get_changed_fields(),
['embedded_field.list_field.2.string_field'])
self.assertEqual(doc.embedded_field._delta(),
({'list_field.2.string_field': 'world'}, {}))
self.assertEqual(doc._delta(),
({'embedded_field.list_field.2.string_field': 'world'}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].string_field,
'world')
# Test multiple assignments
doc.embedded_field.list_field[2].string_field = 'hello world'
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
self.assertEqual(doc._get_changed_fields(),
['embedded_field.list_field.2'])
self.assertEqual(doc.embedded_field._delta(), ({'list_field.2': {
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}
}, {}))
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2': {
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}
}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].string_field,
'hello world')
# Test list native methods
doc.embedded_field.list_field[2].list_field.pop(0)
self.assertEqual(doc._delta(),
({'embedded_field.list_field.2.list_field':
[2, {'hello': 'world'}]}, {}))
doc.save()
doc = doc.reload(10)
doc.embedded_field.list_field[2].list_field.append(1)
self.assertEqual(doc._delta(),
({'embedded_field.list_field.2.list_field':
[2, {'hello': 'world'}, 1]}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].list_field,
[2, {'hello': 'world'}, 1])
doc.embedded_field.list_field[2].list_field.sort(key=str)
doc.save()
doc = doc.reload(10)
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']
self.assertEqual(doc._delta(),
({}, {'embedded_field.list_field.2.list_field.2.hello': 1}))
doc.save()
doc = doc.reload(10)
del doc.embedded_field.list_field[2].list_field
self.assertEqual(doc._delta(),
({}, {'embedded_field.list_field.2.list_field': 1}))
doc.save()
doc = doc.reload(10)
doc.dict_field['Embedded'] = embedded_1
doc.save()
doc = doc.reload(10)
doc.dict_field['Embedded'].string_field = 'Hello World'
self.assertEqual(doc._get_changed_fields(),
['dict_field.Embedded.string_field'])
self.assertEqual(doc._delta(),
({'dict_field.Embedded.string_field': 'Hello World'}, {}))
def test_circular_reference_deltas(self):
self.circular_reference_deltas(Document, Document)
self.circular_reference_deltas(Document, DynamicDocument)
self.circular_reference_deltas(DynamicDocument, Document)
self.circular_reference_deltas(DynamicDocument, DynamicDocument)
def circular_reference_deltas(self, DocClass1, DocClass2):
class Person(DocClass1):
name = StringField()
owns = ListField(ReferenceField('Organization'))
class Organization(DocClass2):
name = StringField()
owner = ReferenceField('Person')
Person.drop_collection()
Organization.drop_collection()
person = Person(name="owner").save()
organization = Organization(name="company").save()
person.owns.append(organization)
organization.owner = person
person.save()
organization.save()
p = Person.objects[0].select_related()
o = Organization.objects.first()
self.assertEqual(p.owns[0], o)
self.assertEqual(o.owner, p)
def test_circular_reference_deltas_2(self):
self.circular_reference_deltas_2(Document, Document)
self.circular_reference_deltas_2(Document, DynamicDocument)
self.circular_reference_deltas_2(DynamicDocument, Document)
self.circular_reference_deltas_2(DynamicDocument, DynamicDocument)
def circular_reference_deltas_2(self, DocClass1, DocClass2, dbref=True):
class Person(DocClass1):
name = StringField()
owns = ListField(ReferenceField('Organization', dbref=dbref))
employer = ReferenceField('Organization', dbref=dbref)
class Organization(DocClass2):
name = StringField()
owner = ReferenceField('Person', dbref=dbref)
employees = ListField(ReferenceField('Person', dbref=dbref))
Person.drop_collection()
Organization.drop_collection()
person = Person(name="owner").save()
employee = Person(name="employee").save()
organization = Organization(name="company").save()
person.owns.append(organization)
organization.owner = person
organization.employees.append(employee)
employee.employer = organization
person.save()
organization.save()
employee.save()
p = Person.objects.get(name="owner")
e = Person.objects.get(name="employee")
o = Organization.objects.first()
self.assertEqual(p.owns[0], o)
self.assertEqual(o.owner, p)
self.assertEqual(e.employer, o)
return person, organization, employee
def test_delta_db_field(self):
self.delta_db_field(Document)
self.delta_db_field(DynamicDocument)
def delta_db_field(self, DocClass):
class Doc(DocClass):
string_field = StringField(db_field='db_string_field')
int_field = IntField(db_field='db_int_field')
dict_field = DictField(db_field='db_dict_field')
list_field = ListField(db_field='db_list_field')
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
doc.string_field = 'hello'
self.assertEqual(doc._get_changed_fields(), ['db_string_field'])
self.assertEqual(doc._delta(), ({'db_string_field': 'hello'}, {}))
doc._changed_fields = []
doc.int_field = 1
self.assertEqual(doc._get_changed_fields(), ['db_int_field'])
self.assertEqual(doc._delta(), ({'db_int_field': 1}, {}))
doc._changed_fields = []
dict_value = {'hello': 'world', 'ping': 'pong'}
doc.dict_field = dict_value
self.assertEqual(doc._get_changed_fields(), ['db_dict_field'])
self.assertEqual(doc._delta(), ({'db_dict_field': dict_value}, {}))
doc._changed_fields = []
list_value = ['1', 2, {'hello': 'world'}]
doc.list_field = list_value
self.assertEqual(doc._get_changed_fields(), ['db_list_field'])
self.assertEqual(doc._delta(), ({'db_list_field': list_value}, {}))
# Test unsetting
doc._changed_fields = []
doc.dict_field = {}
self.assertEqual(doc._get_changed_fields(), ['db_dict_field'])
self.assertEqual(doc._delta(), ({}, {'db_dict_field': 1}))
doc._changed_fields = []
doc.list_field = []
self.assertEqual(doc._get_changed_fields(), ['db_list_field'])
self.assertEqual(doc._delta(), ({}, {'db_list_field': 1}))
# Test it saves that data
doc = Doc()
doc.save()
doc.string_field = 'hello'
doc.int_field = 1
doc.dict_field = {'hello': 'world'}
doc.list_field = ['1', 2, {'hello': 'world'}]
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.string_field, 'hello')
self.assertEqual(doc.int_field, 1)
self.assertEqual(doc.dict_field, {'hello': 'world'})
self.assertEqual(doc.list_field, ['1', 2, {'hello': 'world'}])
def test_delta_recursive_db_field(self):
self.delta_recursive_db_field(Document, EmbeddedDocument)
self.delta_recursive_db_field(Document, DynamicEmbeddedDocument)
self.delta_recursive_db_field(DynamicDocument, EmbeddedDocument)
self.delta_recursive_db_field(DynamicDocument, DynamicEmbeddedDocument)
def delta_recursive_db_field(self, DocClass, EmbeddedClass):
class Embedded(EmbeddedClass):
string_field = StringField(db_field='db_string_field')
int_field = IntField(db_field='db_int_field')
dict_field = DictField(db_field='db_dict_field')
list_field = ListField(db_field='db_list_field')
class Doc(DocClass):
string_field = StringField(db_field='db_string_field')
int_field = IntField(db_field='db_int_field')
dict_field = DictField(db_field='db_dict_field')
list_field = ListField(db_field='db_list_field')
embedded_field = EmbeddedDocumentField(Embedded,
db_field='db_embedded_field')
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field = embedded_1
self.assertEqual(doc._get_changed_fields(), ['db_embedded_field'])
embedded_delta = {
'db_string_field': 'hello',
'db_int_field': 1,
'db_dict_field': {'hello': 'world'},
'db_list_field': ['1', 2, {'hello': 'world'}]
}
self.assertEqual(doc.embedded_field._delta(), (embedded_delta, {}))
self.assertEqual(doc._delta(),
({'db_embedded_field': embedded_delta}, {}))
doc.save()
doc = doc.reload(10)
doc.embedded_field.dict_field = {}
self.assertEqual(doc._get_changed_fields(),
['db_embedded_field.db_dict_field'])
self.assertEqual(doc.embedded_field._delta(),
({}, {'db_dict_field': 1}))
self.assertEqual(doc._delta(),
({}, {'db_embedded_field.db_dict_field': 1}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.dict_field, {})
doc.embedded_field.list_field = []
self.assertEqual(doc._get_changed_fields(),
['db_embedded_field.db_list_field'])
self.assertEqual(doc.embedded_field._delta(),
({}, {'db_list_field': 1}))
self.assertEqual(doc._delta(),
({}, {'db_embedded_field.db_list_field': 1}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field, [])
embedded_2 = Embedded()
embedded_2.string_field = 'hello'
embedded_2.int_field = 1
embedded_2.dict_field = {'hello': 'world'}
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field.list_field = ['1', 2, embedded_2]
self.assertEqual(doc._get_changed_fields(),
['db_embedded_field.db_list_field'])
self.assertEqual(doc.embedded_field._delta(), ({
'db_list_field': ['1', 2, {
'_cls': 'Embedded',
'db_string_field': 'hello',
'db_dict_field': {'hello': 'world'},
'db_int_field': 1,
'db_list_field': ['1', 2, {'hello': 'world'}],
}]
}, {}))
self.assertEqual(doc._delta(), ({
'db_embedded_field.db_list_field': ['1', 2, {
'_cls': 'Embedded',
'db_string_field': 'hello',
'db_dict_field': {'hello': 'world'},
'db_int_field': 1,
'db_list_field': ['1', 2, {'hello': 'world'}],
}]
}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[0], '1')
self.assertEqual(doc.embedded_field.list_field[1], 2)
for k in doc.embedded_field.list_field[2]._fields:
self.assertEqual(doc.embedded_field.list_field[2][k],
embedded_2[k])
doc.embedded_field.list_field[2].string_field = 'world'
self.assertEqual(doc._get_changed_fields(),
['db_embedded_field.db_list_field.2.db_string_field'])
self.assertEqual(doc.embedded_field._delta(),
({'db_list_field.2.db_string_field': 'world'}, {}))
self.assertEqual(doc._delta(),
({'db_embedded_field.db_list_field.2.db_string_field': 'world'},
{}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].string_field,
'world')
# Test multiple assignments
doc.embedded_field.list_field[2].string_field = 'hello world'
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
self.assertEqual(doc._get_changed_fields(),
['db_embedded_field.db_list_field.2'])
self.assertEqual(doc.embedded_field._delta(), ({'db_list_field.2': {
'_cls': 'Embedded',
'db_string_field': 'hello world',
'db_int_field': 1,
'db_list_field': ['1', 2, {'hello': 'world'}],
'db_dict_field': {'hello': 'world'}}}, {}))
self.assertEqual(doc._delta(), ({
'db_embedded_field.db_list_field.2': {
'_cls': 'Embedded',
'db_string_field': 'hello world',
'db_int_field': 1,
'db_list_field': ['1', 2, {'hello': 'world'}],
'db_dict_field': {'hello': 'world'}}
}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].string_field,
'hello world')
# Test list native methods
doc.embedded_field.list_field[2].list_field.pop(0)
self.assertEqual(doc._delta(),
({'db_embedded_field.db_list_field.2.db_list_field':
[2, {'hello': 'world'}]}, {}))
doc.save()
doc = doc.reload(10)
doc.embedded_field.list_field[2].list_field.append(1)
self.assertEqual(doc._delta(),
({'db_embedded_field.db_list_field.2.db_list_field':
[2, {'hello': 'world'}, 1]}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].list_field,
[2, {'hello': 'world'}, 1])
doc.embedded_field.list_field[2].list_field.sort(key=str)
doc.save()
doc = doc.reload(10)
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']
self.assertEqual(doc._delta(),
({}, {'db_embedded_field.db_list_field.2.db_list_field.2.hello': 1}))
doc.save()
doc = doc.reload(10)
del doc.embedded_field.list_field[2].list_field
self.assertEqual(doc._delta(), ({},
{'db_embedded_field.db_list_field.2.db_list_field': 1}))
def test_delta_for_dynamic_documents(self):
class Person(DynamicDocument):
name = StringField()
meta = {'allow_inheritance': True}
Person.drop_collection()
p = Person(name="James", age=34)
self.assertEqual(p._delta(), (
SON([('_cls', 'Person'), ('name', 'James'), ('age', 34)]), {}))
p.doc = 123
del p.doc
self.assertEqual(p._delta(), (
SON([('_cls', 'Person'), ('name', 'James'), ('age', 34)]), {}))
p = Person()
p.name = "Dean"
p.age = 22
p.save()
p.age = 24
self.assertEqual(p.age, 24)
self.assertEqual(p._get_changed_fields(), ['age'])
self.assertEqual(p._delta(), ({'age': 24}, {}))
p = Person.objects(age=22).get()
p.age = 24
self.assertEqual(p.age, 24)
self.assertEqual(p._get_changed_fields(), ['age'])
self.assertEqual(p._delta(), ({'age': 24}, {}))
p.save()
self.assertEqual(1, Person.objects(age=24).count())
def test_dynamic_delta(self):
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
doc.string_field = 'hello'
self.assertEqual(doc._get_changed_fields(), ['string_field'])
self.assertEqual(doc._delta(), ({'string_field': 'hello'}, {}))
doc._changed_fields = []
doc.int_field = 1
self.assertEqual(doc._get_changed_fields(), ['int_field'])
self.assertEqual(doc._delta(), ({'int_field': 1}, {}))
doc._changed_fields = []
dict_value = {'hello': 'world', 'ping': 'pong'}
doc.dict_field = dict_value
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
self.assertEqual(doc._delta(), ({'dict_field': dict_value}, {}))
doc._changed_fields = []
list_value = ['1', 2, {'hello': 'world'}]
doc.list_field = list_value
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({'list_field': list_value}, {}))
# Test unsetting
doc._changed_fields = []
doc.dict_field = {}
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
self.assertEqual(doc._delta(), ({}, {'dict_field': 1}))
doc._changed_fields = []
doc.list_field = []
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({}, {'list_field': 1}))
def test_delta_with_dbref_true(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, True)
employee.name = 'test'
self.assertEqual(organization._get_changed_fields(), [])
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertEqual({}, updates)
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
def test_delta_with_dbref_false(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, False)
employee.name = 'test'
self.assertEqual(organization._get_changed_fields(), [])
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertEqual({}, updates)
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
def test_nested_nested_fields_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(MapField(EmbeddedDocumentField(EmbeddedDoc)))
name = StringField()
MyDoc.drop_collection()
mydoc = MyDoc(name='testcase1', subs={'a': {'b': EmbeddedDoc(name='foo')}}).save()
mydoc = MyDoc.objects.first()
subdoc = mydoc.subs['a']['b']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a.b.name"], mydoc._get_changed_fields())
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
def test_lower_level_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(EmbeddedDocumentField(EmbeddedDoc))
MyDoc.drop_collection()
MyDoc().save()
mydoc = MyDoc.objects.first()
mydoc.subs['a'] = EmbeddedDoc()
self.assertEqual(["subs.a"], mydoc._get_changed_fields())
subdoc = mydoc.subs['a']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a"], mydoc._get_changed_fields())
mydoc.save()
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
def test_upper_level_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(EmbeddedDocumentField(EmbeddedDoc))
MyDoc.drop_collection()
MyDoc(subs={'a': EmbeddedDoc(name='foo')}).save()
mydoc = MyDoc.objects.first()
subdoc = mydoc.subs['a']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a.name"], mydoc._get_changed_fields())
mydoc.subs['a'] = EmbeddedDoc()
self.assertEqual(["subs.a"], mydoc._get_changed_fields())
mydoc.save()
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
def test_referenced_object_changed_attributes(self):
"""Ensures that when you save a new reference to a field, the referenced object isn't altered"""
class Organization(Document):
name = StringField()
class User(Document):
name = StringField()
org = ReferenceField('Organization', required=True)
Organization.drop_collection()
User.drop_collection()
org1 = Organization(name='Org 1')
org1.save()
org2 = Organization(name='Org 2')
org2.save()
user = User(name='Fred', org=org1)
user.save()
org1.reload()
org2.reload()
user.reload()
self.assertEqual(org1.name, 'Org 1')
self.assertEqual(org2.name, 'Org 2')
self.assertEqual(user.name, 'Fred')
user.name = 'Harold'
user.org = org2
org2.name = 'New Org 2'
self.assertEqual(org2.name, 'New Org 2')
user.save()
org2.save()
self.assertEqual(org2.name, 'New Org 2')
org2.reload()
self.assertEqual(org2.name, 'New Org 2')
def test_delta_for_nested_map_fields(self):
class UInfoDocument(Document):
phone = StringField()
class EmbeddedRole(EmbeddedDocument):
type = StringField()
class EmbeddedUser(EmbeddedDocument):
name = StringField()
roles = MapField(field=EmbeddedDocumentField(EmbeddedRole))
rolist = ListField(field=EmbeddedDocumentField(EmbeddedRole))
info = ReferenceField(UInfoDocument)
class Doc(Document):
users = MapField(field=EmbeddedDocumentField(EmbeddedUser))
num = IntField(default=-1)
Doc.drop_collection()
doc = Doc(num=1)
doc.users["007"] = EmbeddedUser(name="Agent007")
doc.save()
uinfo = UInfoDocument(phone="79089269066")
uinfo.save()
d = Doc.objects(num=1).first()
d.users["007"]["roles"]["666"] = EmbeddedRole(type="superadmin")
d.users["007"]["rolist"].append(EmbeddedRole(type="oops"))
d.users["007"]["info"] = uinfo
delta = d._delta()
self.assertEqual(True, "users.007.roles.666" in delta[0])
self.assertEqual(True, "users.007.rolist" in delta[0])
self.assertEqual(True, "users.007.info" in delta[0])
self.assertEqual('superadmin', delta[0]["users.007.roles.666"]["type"])
self.assertEqual('oops', delta[0]["users.007.rolist"][0]["type"])
self.assertEqual(uinfo.id, delta[0]["users.007.info"])
if __name__ == '__main__':
unittest.main()

373
tests/document/dynamic.py Normal file
View File

@@ -0,0 +1,373 @@
import unittest
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("DynamicTest", )
class DynamicTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class Person(DynamicDocument):
name = StringField()
meta = {'allow_inheritance': True}
Person.drop_collection()
self.Person = Person
def test_simple_dynamic_document(self):
"""Ensures simple dynamic documents are saved correctly"""
p = self.Person()
p.name = "James"
p.age = 34
self.assertEqual(p.to_mongo(), {"_cls": "Person", "name": "James",
"age": 34})
self.assertEqual(p.to_mongo().keys(), ["_cls", "name", "age"])
p.save()
self.assertEqual(p.to_mongo().keys(), ["_id", "_cls", "name", "age"])
self.assertEqual(self.Person.objects.first().age, 34)
# Confirm no changes to self.Person
self.assertFalse(hasattr(self.Person, 'age'))
def test_change_scope_of_variable(self):
"""Test changing the scope of a dynamic field has no adverse effects"""
p = self.Person()
p.name = "Dean"
p.misc = 22
p.save()
p = self.Person.objects.get()
p.misc = {'hello': 'world'}
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {'hello': 'world'})
def test_delete_dynamic_field(self):
"""Test deleting a dynamic field works"""
self.Person.drop_collection()
p = self.Person()
p.name = "Dean"
p.misc = 22
p.save()
p = self.Person.objects.get()
p.misc = {'hello': 'world'}
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {'hello': 'world'})
collection = self.db[self.Person._get_collection_name()]
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'misc', 'name'])
del p.misc
p.save()
p = self.Person.objects.get()
self.assertFalse(hasattr(p, 'misc'))
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'name'])
def test_reload_after_unsetting(self):
p = self.Person()
p.misc = 22
p.save()
p.update(unset__misc=1)
p.reload()
def test_reload_dynamic_field(self):
self.Person.objects.delete()
p = self.Person.objects.create()
p.update(age=1)
self.assertEqual(len(p._data), 3)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'id', 'name'])
p.reload()
self.assertEqual(len(p._data), 4)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'age', 'id', 'name'])
def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields"""
p = self.Person()
p.name = "Dean"
p.age = 22
p.save()
self.assertEqual(1, self.Person.objects(age=22).count())
p = self.Person.objects(age=22)
p = p.get()
self.assertEqual(22, p.age)
def test_complex_dynamic_document_queries(self):
class Person(DynamicDocument):
name = StringField()
Person.drop_collection()
p = Person(name="test")
p.age = "ten"
p.save()
p1 = Person(name="test1")
p1.age = "less then ten and a half"
p1.save()
p2 = Person(name="test2")
p2.age = 10
p2.save()
self.assertEqual(Person.objects(age__icontains='ten').count(), 2)
self.assertEqual(Person.objects(age__gte=10).count(), 1)
def test_complex_data_lookups(self):
"""Ensure you can query dynamic document dynamic fields"""
p = self.Person()
p.misc = {'hello': 'world'}
p.save()
self.assertEqual(1, self.Person.objects(misc__hello='world').count())
def test_three_level_complex_data_lookups(self):
"""Ensure you can query three level document dynamic fields"""
p = self.Person.objects.create(
misc={'hello': {'hello2': 'world'}}
)
self.assertEqual(1, self.Person.objects(misc__hello__hello2='world').count())
def test_complex_embedded_document_validation(self):
"""Ensure embedded dynamic documents may be validated"""
class Embedded(DynamicEmbeddedDocument):
content = URLField()
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_doc_1 = Embedded(content='http://mongoengine.org')
embedded_doc_1.validate()
embedded_doc_2 = Embedded(content='this is not a url')
self.assertRaises(ValidationError, embedded_doc_2.validate)
doc.embedded_field_1 = embedded_doc_1
doc.embedded_field_2 = embedded_doc_2
self.assertRaises(ValidationError, doc.validate)
def test_inheritance(self):
"""Ensure that dynamic document plays nice with inheritance"""
class Employee(self.Person):
salary = IntField()
Employee.drop_collection()
self.assertTrue('name' in Employee._fields)
self.assertTrue('salary' in Employee._fields)
self.assertEqual(Employee._get_collection_name(),
self.Person._get_collection_name())
joe_bloggs = Employee()
joe_bloggs.name = "Joe Bloggs"
joe_bloggs.salary = 10
joe_bloggs.age = 20
joe_bloggs.save()
self.assertEqual(1, self.Person.objects(age=20).count())
self.assertEqual(1, Employee.objects(age=20).count())
joe_bloggs = self.Person.objects.first()
self.assertTrue(isinstance(joe_bloggs, Employee))
def test_embedded_dynamic_document(self):
"""Test dynamic embedded documents"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field = embedded_1
self.assertEqual(doc.to_mongo(), {
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2, {'hello': 'world'}]
}
})
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(doc.embedded_field.list_field,
['1', 2, {'hello': 'world'}])
def test_complex_embedded_documents(self):
"""Test complex dynamic embedded documents setups"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_2 = Embedded()
embedded_2.string_field = 'hello'
embedded_2.int_field = 1
embedded_2.dict_field = {'hello': 'world'}
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
embedded_1.list_field = ['1', 2, embedded_2]
doc.embedded_field = embedded_1
self.assertEqual(doc.to_mongo(), {
"embedded_field": {
"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2,
{"_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2, {'hello': 'world'}]}
]
}
})
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(doc.embedded_field.list_field[0], '1')
self.assertEqual(doc.embedded_field.list_field[1], 2)
embedded_field = doc.embedded_field.list_field[2]
self.assertEqual(embedded_field.__class__, Embedded)
self.assertEqual(embedded_field.string_field, "hello")
self.assertEqual(embedded_field.int_field, 1)
self.assertEqual(embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(embedded_field.list_field, ['1', 2,
{'hello': 'world'}])
def test_dynamic_and_embedded(self):
"""Ensure embedded documents play nicely"""
class Address(EmbeddedDocument):
city = StringField()
class Person(DynamicDocument):
name = StringField()
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)
def test_dynamic_embedded_works_with_only(self):
"""Ensure custom fieldnames on a dynamic embedded document are found by qs.only()"""
class Address(DynamicEmbeddedDocument):
city = StringField()
class Person(DynamicDocument):
address = EmbeddedDocumentField(Address)
Person.drop_collection()
Person(name="Eric", address=Address(city="San Francisco", street_number="1337")).save()
self.assertEqual(Person.objects.first().address.street_number, '1337')
self.assertEqual(Person.objects.only('address__street_number').first().address.street_number, '1337')
def test_dynamic_and_embedded_dict_access(self):
"""Ensure embedded dynamic documents work with dict[] style access"""
class Address(EmbeddedDocument):
city = StringField()
class Person(DynamicDocument):
name = StringField()
Person.drop_collection()
Person(name="Ross", address=Address(city="London")).save()
person = Person.objects.first()
person.attrval = "This works"
person["phone"] = "555-1212" # but this should too
# Same thing two levels deep
person["address"]["city"] = "Lundenne"
person.save()
self.assertEqual(Person.objects.first().address.city, "Lundenne")
self.assertEqual(Person.objects.first().phone, "555-1212")
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)
if __name__ == '__main__':
unittest.main()

1020
tests/document/indexes.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,512 @@
# -*- coding: utf-8 -*-
import unittest
import warnings
from datetime import datetime
from tests.fixtures import Base
from mongoengine import Document, EmbeddedDocument, connect
from mongoengine.connection import get_db
from mongoengine.fields import (BooleanField, GenericReferenceField,
IntField, StringField)
__all__ = ('InheritanceTest', )
class InheritanceTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
self.db.drop_collection(collection)
def test_superclasses(self):
"""Ensure that the correct list of superclasses is assembled.
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Fish._superclasses, ('Animal',))
self.assertEqual(Guppy._superclasses, ('Animal', 'Animal.Fish'))
self.assertEqual(Mammal._superclasses, ('Animal',))
self.assertEqual(Dog._superclasses, ('Animal', 'Animal.Mammal'))
self.assertEqual(Human._superclasses, ('Animal', 'Animal.Mammal'))
def test_external_superclasses(self):
"""Ensure that the correct list of super classes is assembled when
importing part of the model.
"""
class Animal(Base): pass
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._superclasses, ('Base', ))
self.assertEqual(Fish._superclasses, ('Base', 'Base.Animal',))
self.assertEqual(Guppy._superclasses, ('Base', 'Base.Animal',
'Base.Animal.Fish'))
self.assertEqual(Mammal._superclasses, ('Base', 'Base.Animal',))
self.assertEqual(Dog._superclasses, ('Base', 'Base.Animal',
'Base.Animal.Mammal'))
self.assertEqual(Human._superclasses, ('Base', 'Base.Animal',
'Base.Animal.Mammal'))
def test_subclasses(self):
"""Ensure that the correct list of _subclasses (subclasses) is
assembled.
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._subclasses, ('Animal',
'Animal.Fish',
'Animal.Fish.Guppy',
'Animal.Mammal',
'Animal.Mammal.Dog',
'Animal.Mammal.Human'))
self.assertEqual(Fish._subclasses, ('Animal.Fish',
'Animal.Fish.Guppy',))
self.assertEqual(Guppy._subclasses, ('Animal.Fish.Guppy',))
self.assertEqual(Mammal._subclasses, ('Animal.Mammal',
'Animal.Mammal.Dog',
'Animal.Mammal.Human'))
self.assertEqual(Human._subclasses, ('Animal.Mammal.Human',))
def test_external_subclasses(self):
"""Ensure that the correct list of _subclasses (subclasses) is
assembled when importing part of the model.
"""
class Animal(Base): pass
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
self.assertEqual(Animal._subclasses, ('Base.Animal',
'Base.Animal.Fish',
'Base.Animal.Fish.Guppy',
'Base.Animal.Mammal',
'Base.Animal.Mammal.Dog',
'Base.Animal.Mammal.Human'))
self.assertEqual(Fish._subclasses, ('Base.Animal.Fish',
'Base.Animal.Fish.Guppy',))
self.assertEqual(Guppy._subclasses, ('Base.Animal.Fish.Guppy',))
self.assertEqual(Mammal._subclasses, ('Base.Animal.Mammal',
'Base.Animal.Mammal.Dog',
'Base.Animal.Mammal.Human'))
self.assertEqual(Human._subclasses, ('Base.Animal.Mammal.Human',))
def test_dynamic_declarations(self):
"""Test that declaring an extra class updates meta data"""
class Animal(Document):
meta = {'allow_inheritance': True}
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ('Animal',))
# Test dynamically adding a class changes the meta data
class Fish(Animal):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ('Animal', 'Animal.Fish'))
self.assertEqual(Fish._superclasses, ('Animal', ))
self.assertEqual(Fish._subclasses, ('Animal.Fish',))
# Test dynamically adding an inherited class changes the meta data
class Pike(Fish):
pass
self.assertEqual(Animal._superclasses, ())
self.assertEqual(Animal._subclasses, ('Animal', 'Animal.Fish',
'Animal.Fish.Pike'))
self.assertEqual(Fish._superclasses, ('Animal', ))
self.assertEqual(Fish._subclasses, ('Animal.Fish', 'Animal.Fish.Pike'))
self.assertEqual(Pike._superclasses, ('Animal', 'Animal.Fish'))
self.assertEqual(Pike._subclasses, ('Animal.Fish.Pike',))
def test_inheritance_meta_data(self):
"""Ensure that document may inherit fields from a superclass document.
"""
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
class Employee(Person):
salary = IntField()
self.assertEqual(['_cls', 'age', 'id', 'name', 'salary'],
sorted(Employee._fields.keys()))
self.assertEqual(Employee._get_collection_name(),
Person._get_collection_name())
def test_inheritance_to_mongo_keys(self):
"""Ensure that document may inherit fields from a superclass document.
"""
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
class Employee(Person):
salary = IntField()
self.assertEqual(['_cls', 'age', 'id', 'name', 'salary'],
sorted(Employee._fields.keys()))
self.assertEqual(Person(name="Bob", age=35).to_mongo().keys(),
['_cls', 'name', 'age'])
self.assertEqual(Employee(name="Bob", age=35, salary=0).to_mongo().keys(),
['_cls', 'name', 'age', 'salary'])
self.assertEqual(Employee._get_collection_name(),
Person._get_collection_name())
def test_indexes_and_multiple_inheritance(self):
""" Ensure that all of the indexes are created for a document with
multiple inheritance.
"""
class A(Document):
a = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['a']
}
class B(Document):
b = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['b']
}
class C(A, B):
pass
A.drop_collection()
B.drop_collection()
C.drop_collection()
C.ensure_indexes()
self.assertEqual(
sorted([idx['key'] for idx in C._get_collection().index_information().values()]),
sorted([[(u'_cls', 1), (u'b', 1)], [(u'_id', 1)], [(u'_cls', 1), (u'a', 1)]])
)
def test_polymorphic_queries(self):
"""Ensure that the correct subclasses are returned from a query
"""
class Animal(Document):
meta = {'allow_inheritance': True}
class Fish(Animal): pass
class Mammal(Animal): pass
class Dog(Mammal): pass
class Human(Mammal): pass
Animal.drop_collection()
Animal().save()
Fish().save()
Mammal().save()
Dog().save()
Human().save()
classes = [obj.__class__ for obj in Animal.objects]
self.assertEqual(classes, [Animal, Fish, Mammal, Dog, Human])
classes = [obj.__class__ for obj in Mammal.objects]
self.assertEqual(classes, [Mammal, Dog, Human])
classes = [obj.__class__ for obj in Human.objects]
self.assertEqual(classes, [Human])
def test_allow_inheritance(self):
"""Ensure that inheritance is disabled by default on simple
classes and that _cls will not be used.
"""
class Animal(Document):
name = StringField()
# can't inherit because Animal didn't explicitly allow inheritance
with self.assertRaises(ValueError):
class Dog(Animal):
pass
# Check that _cls etc aren't present on simple documents
dog = Animal(name='dog').save()
self.assertEqual(dog.to_mongo().keys(), ['_id', 'name'])
collection = self.db[Animal._get_collection_name()]
obj = collection.find_one()
self.assertFalse('_cls' in obj)
def test_cant_turn_off_inheritance_on_subclass(self):
"""Ensure if inheritance is on in a subclass you cant turn it off.
"""
class Animal(Document):
name = StringField()
meta = {'allow_inheritance': True}
with self.assertRaises(ValueError):
class Mammal(Animal):
meta = {'allow_inheritance': False}
def test_allow_inheritance_abstract_document(self):
"""Ensure that abstract documents can set inheritance rules and that
_cls will not be used.
"""
class FinalDocument(Document):
meta = {'abstract': True,
'allow_inheritance': False}
class Animal(FinalDocument):
name = StringField()
with self.assertRaises(ValueError):
class Mammal(Animal):
pass
# Check that _cls isn't present in simple documents
doc = Animal(name='dog')
self.assertFalse('_cls' in doc.to_mongo())
def test_abstract_handle_ids_in_metaclass_properly(self):
class City(Document):
continent = StringField()
meta = {'abstract': True,
'allow_inheritance': False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], 'id')
def test_auto_id_not_set_if_specific_in_parent_class(self):
class City(Document):
continent = StringField()
city_id = IntField(primary_key=True)
meta = {'abstract': True,
'allow_inheritance': False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], 'city_id')
def test_auto_id_vs_non_pk_id_field(self):
class City(Document):
continent = StringField()
id = IntField()
meta = {'abstract': True,
'allow_inheritance': False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 4)
self.assertEqual(berlin._fields_ordered[0], 'auto_id_0')
berlin.save()
self.assertEqual(berlin.pk, berlin.auto_id_0)
def test_abstract_document_creation_does_not_fail(self):
class City(Document):
continent = StringField()
meta = {'abstract': True,
'allow_inheritance': False}
bkk = City(continent='asia')
self.assertEqual(None, bkk.pk)
# TODO: expected error? Shouldn't we create a new error type?
with self.assertRaises(KeyError):
setattr(bkk, 'pk', 1)
def test_allow_inheritance_embedded_document(self):
"""Ensure embedded documents respect inheritance."""
class Comment(EmbeddedDocument):
content = StringField()
with self.assertRaises(ValueError):
class SpecialComment(Comment):
pass
doc = Comment(content='test')
self.assertFalse('_cls' in doc.to_mongo())
class Comment(EmbeddedDocument):
content = StringField()
meta = {'allow_inheritance': True}
doc = Comment(content='test')
self.assertTrue('_cls' in doc.to_mongo())
def test_document_inheritance(self):
"""Ensure mutliple inheritance of abstract documents
"""
class DateCreatedDocument(Document):
meta = {
'allow_inheritance': True,
'abstract': True,
}
class DateUpdatedDocument(Document):
meta = {
'allow_inheritance': True,
'abstract': True,
}
try:
class MyDocument(DateCreatedDocument, DateUpdatedDocument):
pass
except Exception:
self.assertTrue(False, "Couldn't create MyDocument class")
def test_abstract_documents(self):
"""Ensure that a document superclass can be marked as abstract
thereby not using it as the name for the collection."""
defaults = {'index_background': True,
'index_drop_dups': True,
'index_opts': {'hello': 'world'},
'allow_inheritance': True,
'queryset_class': 'QuerySet',
'db_alias': 'myDB',
'shard_key': ('hello', 'world')}
meta_settings = {'abstract': True}
meta_settings.update(defaults)
class Animal(Document):
name = StringField()
meta = meta_settings
class Fish(Animal): pass
class Guppy(Fish): pass
class Mammal(Animal):
meta = {'abstract': True}
class Human(Mammal): pass
for k, v in defaults.iteritems():
for cls in [Animal, Fish, Guppy]:
self.assertEqual(cls._meta[k], v)
self.assertFalse('collection' in Animal._meta)
self.assertFalse('collection' in Mammal._meta)
self.assertEqual(Animal._get_collection_name(), None)
self.assertEqual(Mammal._get_collection_name(), None)
self.assertEqual(Fish._get_collection_name(), 'fish')
self.assertEqual(Guppy._get_collection_name(), 'fish')
self.assertEqual(Human._get_collection_name(), 'human')
# ensure that a subclass of a non-abstract class can't be abstract
with self.assertRaises(ValueError):
class EvilHuman(Human):
evil = BooleanField(default=True)
meta = {'abstract': True}
def test_abstract_embedded_documents(self):
# 789: EmbeddedDocument shouldn't inherit abstract
class A(EmbeddedDocument):
meta = {"abstract": True}
class B(A):
pass
self.assertFalse(B._meta["abstract"])
def test_inherited_collections(self):
"""Ensure that subclassed documents don't override parents'
collections
"""
class Drink(Document):
name = StringField()
meta = {'allow_inheritance': True}
class Drinker(Document):
drink = GenericReferenceField()
try:
warnings.simplefilter("error")
class AcloholicDrink(Drink):
meta = {'collection': 'booze'}
except SyntaxWarning:
warnings.simplefilter("ignore")
class AlcoholicDrink(Drink):
meta = {'collection': 'booze'}
else:
raise AssertionError("SyntaxWarning should be triggered")
warnings.resetwarnings()
Drink.drop_collection()
AlcoholicDrink.drop_collection()
Drinker.drop_collection()
red_bull = Drink(name='Red Bull')
red_bull.save()
programmer = Drinker(drink=red_bull)
programmer.save()
beer = AlcoholicDrink(name='Beer')
beer.save()
real_person = Drinker(drink=beer)
real_person.save()
self.assertEqual(Drinker.objects[0].drink.name, red_bull.name)
self.assertEqual(Drinker.objects[1].drink.name, beer.name)
if __name__ == '__main__':
unittest.main()

3154
tests/document/instance.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
import unittest
import uuid
from nose.plugins.skip import SkipTest
from datetime import datetime
from bson import ObjectId
import pymongo
from mongoengine import *
__all__ = ("TestJson",)
class TestJson(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
def test_json_names(self):
"""
Going to test reported issue:
https://github.com/MongoEngine/mongoengine/issues/654
where the reporter asks for the availability to perform
a to_json with the original class names and not the abreviated
mongodb document keys
"""
class Embedded(EmbeddedDocument):
string = StringField(db_field='s')
class Doc(Document):
string = StringField(db_field='s')
embedded = EmbeddedDocumentField(Embedded, db_field='e')
doc = Doc( string="Hello", embedded=Embedded(string="Inner Hello"))
doc_json = doc.to_json(sort_keys=True, use_db_field=False,separators=(',', ':'))
expected_json = """{"embedded":{"string":"Inner Hello"},"string":"Hello"}"""
self.assertEqual( doc_json, expected_json)
def test_json_simple(self):
class Embedded(EmbeddedDocument):
string = StringField()
class Doc(Document):
string = StringField()
embedded_field = EmbeddedDocumentField(Embedded)
def __eq__(self, other):
return (self.string == other.string and
self.embedded_field == other.embedded_field)
doc = Doc(string="Hi", embedded_field=Embedded(string="Hi"))
doc_json = doc.to_json(sort_keys=True, separators=(',', ':'))
expected_json = """{"embedded_field":{"string":"Hi"},"string":"Hi"}"""
self.assertEqual(doc_json, expected_json)
self.assertEqual(doc, Doc.from_json(doc.to_json()))
def test_json_complex(self):
if pymongo.version_tuple[0] <= 2 and pymongo.version_tuple[1] <= 3:
raise SkipTest("Need pymongo 2.4 as has a fix for DBRefs")
class EmbeddedDoc(EmbeddedDocument):
pass
class Simple(Document):
pass
class Doc(Document):
string_field = StringField(default='1')
int_field = IntField(default=1)
float_field = FloatField(default=1.1)
boolean_field = BooleanField(default=True)
datetime_field = DateTimeField(default=datetime.now)
embedded_document_field = EmbeddedDocumentField(EmbeddedDoc,
default=lambda: EmbeddedDoc())
list_field = ListField(default=lambda: [1, 2, 3])
dict_field = DictField(default=lambda: {"hello": "world"})
objectid_field = ObjectIdField(default=ObjectId)
reference_field = ReferenceField(Simple, default=lambda:
Simple().save())
map_field = MapField(IntField(), default=lambda: {"simple": 1})
decimal_field = DecimalField(default=1.0)
complex_datetime_field = ComplexDateTimeField(default=datetime.now)
url_field = URLField(default="http://mongoengine.org")
dynamic_field = DynamicField(default=1)
generic_reference_field = GenericReferenceField(
default=lambda: Simple().save())
sorted_list_field = SortedListField(IntField(),
default=lambda: [1, 2, 3])
email_field = EmailField(default="ross@example.com")
geo_point_field = GeoPointField(default=lambda: [1, 2])
sequence_field = SequenceField()
uuid_field = UUIDField(default=uuid.uuid4)
generic_embedded_document_field = GenericEmbeddedDocumentField(
default=lambda: EmbeddedDoc())
def __eq__(self, other):
import json
return json.loads(self.to_json()) == json.loads(other.to_json())
doc = Doc()
self.assertEqual(doc, Doc.from_json(doc.to_json()))
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
import unittest
from datetime import datetime
from mongoengine import *
__all__ = ("ValidatorErrorTest",)
class ValidatorErrorTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
def test_to_dict(self):
"""Ensure a ValidationError handles error to_dict correctly.
"""
error = ValidationError('root')
self.assertEqual(error.to_dict(), {})
# 1st level error schema
error.errors = {'1st': ValidationError('bad 1st'), }
self.assertTrue('1st' in error.to_dict())
self.assertEqual(error.to_dict()['1st'], 'bad 1st')
# 2nd level error schema
error.errors = {'1st': ValidationError('bad 1st', errors={
'2nd': ValidationError('bad 2nd'),
})}
self.assertTrue('1st' in error.to_dict())
self.assertTrue(isinstance(error.to_dict()['1st'], dict))
self.assertTrue('2nd' in error.to_dict()['1st'])
self.assertEqual(error.to_dict()['1st']['2nd'], 'bad 2nd')
# moar levels
error.errors = {'1st': ValidationError('bad 1st', errors={
'2nd': ValidationError('bad 2nd', errors={
'3rd': ValidationError('bad 3rd', errors={
'4th': ValidationError('Inception'),
}),
}),
})}
self.assertTrue('1st' in error.to_dict())
self.assertTrue('2nd' in error.to_dict()['1st'])
self.assertTrue('3rd' in error.to_dict()['1st']['2nd'])
self.assertTrue('4th' in error.to_dict()['1st']['2nd']['3rd'])
self.assertEqual(error.to_dict()['1st']['2nd']['3rd']['4th'],
'Inception')
self.assertEqual(error.message, "root(2nd.3rd.4th.Inception: ['1st'])")
def test_model_validation(self):
class User(Document):
username = StringField(primary_key=True)
name = StringField(required=True)
try:
User().validate()
except ValidationError as e:
self.assertTrue("User:None" in e.message)
self.assertEqual(e.to_dict(), {
'username': 'Field is required',
'name': 'Field is required'})
user = User(username="RossC0", name="Ross").save()
user.name = None
try:
user.save()
except ValidationError as e:
self.assertTrue("User:RossC0" in e.message)
self.assertEqual(e.to_dict(), {
'name': 'Field is required'})
def test_fields_rewrite(self):
class BasePerson(Document):
name = StringField()
age = IntField()
meta = {'abstract': True}
class Person(BasePerson):
name = StringField(required=True)
p = Person(age=15)
self.assertRaises(ValidationError, p.validate)
def test_embedded_document_validation(self):
"""Ensure that embedded documents may be validated.
"""
class Comment(EmbeddedDocument):
date = DateTimeField()
content = StringField(required=True)
comment = Comment()
self.assertRaises(ValidationError, comment.validate)
comment.content = 'test'
comment.validate()
comment.date = 4
self.assertRaises(ValidationError, comment.validate)
comment.date = datetime.now()
comment.validate()
self.assertEqual(comment._instance, None)
def test_embedded_db_field_validate(self):
class SubDoc(EmbeddedDocument):
val = IntField(required=True)
class Doc(Document):
id = StringField(primary_key=True)
e = EmbeddedDocumentField(SubDoc, db_field='eb')
try:
Doc(id="bad").validate()
except ValidationError as e:
self.assertTrue("SubDoc:None" in e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
Doc.drop_collection()
Doc(id="test", e=SubDoc(val=15)).save()
doc = Doc.objects.first()
keys = doc._data.keys()
self.assertEqual(2, len(keys))
self.assertTrue('e' in keys)
self.assertTrue('id' in keys)
doc.e.val = "OK"
try:
doc.save()
except ValidationError as e:
self.assertTrue("Doc:test" in e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
def test_embedded_weakref(self):
class SubDoc(EmbeddedDocument):
val = IntField(required=True)
class Doc(Document):
e = EmbeddedDocumentField(SubDoc, db_field='eb')
Doc.drop_collection()
d1 = Doc()
d2 = Doc()
s = SubDoc()
self.assertRaises(ValidationError, s.validate)
d1.e = s
d2.e = s
del d1
self.assertRaises(ValidationError, d2.validate)
def test_parent_reference_in_child_document(self):
"""
Test to ensure a ReferenceField can store a reference to a parent
class when inherited. Issue #954.
"""
class Parent(Document):
meta = {'allow_inheritance': True}
reference = ReferenceField('self')
class Child(Parent):
pass
parent = Parent()
parent.save()
child = Child(reference=parent)
# Saving child should not raise a ValidationError
try:
child.save()
except ValidationError as e:
self.fail("ValidationError raised: %s" % e.message)
def test_parent_reference_set_as_attribute_in_child_document(self):
"""
Test to ensure a ReferenceField can store a reference to a parent
class when inherited and when set via attribute. Issue #954.
"""
class Parent(Document):
meta = {'allow_inheritance': True}
reference = ReferenceField('self')
class Child(Parent):
pass
parent = Parent()
parent.save()
child = Child()
child.reference = parent
# Saving the child should not raise a ValidationError
try:
child.save()
except ValidationError as e:
self.fail("ValidationError raised: %s" % e.message)
if __name__ == '__main__':
unittest.main()

3
tests/fields/__init__.py Normal file
View File

@@ -0,0 +1,3 @@
from fields import *
from file_tests import *
from geo import *

4580
tests/fields/fields.py Normal file

File diff suppressed because it is too large Load Diff

582
tests/fields/file_tests.py Normal file
View File

@@ -0,0 +1,582 @@
# -*- coding: utf-8 -*-
import copy
import os
import unittest
import tempfile
import gridfs
import six
from nose.plugins.skip import SkipTest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.python_support import StringIO
try:
from PIL import Image
HAS_PIL = True
except ImportError:
HAS_PIL = False
from tests.utils import MongoDBTestCase
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), 'mongoengine.png')
TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), 'mongodb_leaf.png')
class FileTest(MongoDBTestCase):
def tearDown(self):
self.db.drop_collection('fs.files')
self.db.drop_collection('fs.chunks')
def test_file_field_optional(self):
# Make sure FileField is optional and not required
class DemoFile(Document):
the_file = FileField()
DemoFile.objects.create()
def test_file_fields(self):
"""Ensure that file fields can be written to and their data retrieved
"""
class PutFile(Document):
the_file = FileField()
PutFile.drop_collection()
text = six.b('Hello, World!')
content_type = 'text/plain'
putfile = PutFile()
putfile.the_file.put(text, content_type=content_type, filename="hello")
putfile.save()
result = PutFile.objects.first()
self.assertTrue(putfile == result)
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello>")
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.delete() # Remove file from GridFS
PutFile.objects.delete()
# Ensure file-like objects are stored
PutFile.drop_collection()
putfile = PutFile()
putstring = StringIO()
putstring.write(text)
putstring.seek(0)
putfile.the_file.put(putstring, content_type=content_type)
putfile.save()
result = PutFile.objects.first()
self.assertTrue(putfile == result)
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.delete()
def test_file_fields_stream(self):
"""Ensure that file fields can be written to and their data retrieved
"""
class StreamFile(Document):
the_file = FileField()
StreamFile.drop_collection()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
content_type = 'text/plain'
streamfile = StreamFile()
streamfile.the_file.new_file(content_type=content_type)
streamfile.the_file.write(text)
streamfile.the_file.write(more_text)
streamfile.the_file.close()
streamfile.save()
result = StreamFile.objects.first()
self.assertTrue(streamfile == result)
self.assertEqual(result.the_file.read(), text + more_text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.seek(0)
self.assertEqual(result.the_file.tell(), 0)
self.assertEqual(result.the_file.read(len(text)), text)
self.assertEqual(result.the_file.tell(), len(text))
self.assertEqual(result.the_file.read(len(more_text)), more_text)
self.assertEqual(result.the_file.tell(), len(text + more_text))
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() is None)
def test_file_fields_stream_after_none(self):
"""Ensure that a file field can be written to after it has been saved as
None
"""
class StreamFile(Document):
the_file = FileField()
StreamFile.drop_collection()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
content_type = 'text/plain'
streamfile = StreamFile()
streamfile.save()
streamfile.the_file.new_file()
streamfile.the_file.write(text)
streamfile.the_file.write(more_text)
streamfile.the_file.close()
streamfile.save()
result = StreamFile.objects.first()
self.assertTrue(streamfile == result)
self.assertEqual(result.the_file.read(), text + more_text)
# self.assertEqual(result.the_file.content_type, content_type)
result.the_file.seek(0)
self.assertEqual(result.the_file.tell(), 0)
self.assertEqual(result.the_file.read(len(text)), text)
self.assertEqual(result.the_file.tell(), len(text))
self.assertEqual(result.the_file.read(len(more_text)), more_text)
self.assertEqual(result.the_file.tell(), len(text + more_text))
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() is None)
def test_file_fields_set(self):
class SetFile(Document):
the_file = FileField()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
SetFile.drop_collection()
setfile = SetFile()
setfile.the_file = text
setfile.save()
result = SetFile.objects.first()
self.assertTrue(setfile == result)
self.assertEqual(result.the_file.read(), text)
# Try replacing file with new one
result.the_file.replace(more_text)
result.save()
result = SetFile.objects.first()
self.assertTrue(setfile == result)
self.assertEqual(result.the_file.read(), more_text)
result.the_file.delete()
def test_file_field_no_default(self):
class GridDocument(Document):
the_file = FileField()
GridDocument.drop_collection()
with tempfile.TemporaryFile() as f:
f.write(six.b("Hello World!"))
f.flush()
# Test without default
doc_a = GridDocument()
doc_a.save()
doc_b = GridDocument.objects.with_id(doc_a.id)
doc_b.the_file.replace(f, filename='doc_b')
doc_b.save()
self.assertNotEqual(doc_b.the_file.grid_id, None)
# Test it matches
doc_c = GridDocument.objects.with_id(doc_b.id)
self.assertEqual(doc_b.the_file.grid_id, doc_c.the_file.grid_id)
# Test with default
doc_d = GridDocument(the_file=six.b(''))
doc_d.save()
doc_e = GridDocument.objects.with_id(doc_d.id)
self.assertEqual(doc_d.the_file.grid_id, doc_e.the_file.grid_id)
doc_e.the_file.replace(f, filename='doc_e')
doc_e.save()
doc_f = GridDocument.objects.with_id(doc_e.id)
self.assertEqual(doc_e.the_file.grid_id, doc_f.the_file.grid_id)
db = GridDocument._get_db()
grid_fs = gridfs.GridFS(db)
self.assertEqual(['doc_b', 'doc_e'], grid_fs.list())
def test_file_uniqueness(self):
"""Ensure that each instance of a FileField is unique
"""
class TestFile(Document):
name = StringField()
the_file = FileField()
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(six.b('Hello, World!'))
test_file.save()
# Second instance
test_file_dupe = TestFile()
data = test_file_dupe.the_file.read() # Should be None
self.assertTrue(test_file.name != test_file_dupe.name)
self.assertTrue(test_file.the_file.read() != data)
TestFile.drop_collection()
def test_file_saving(self):
"""Ensure you can add meta data to file"""
class Animal(Document):
genus = StringField()
family = StringField()
photo = FileField()
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
marmot.photo.put(marmot_photo, content_type='image/jpeg', foo='bar')
marmot.photo.close()
marmot.save()
marmot = Animal.objects.get()
self.assertEqual(marmot.photo.content_type, 'image/jpeg')
self.assertEqual(marmot.photo.foo, 'bar')
def test_file_reassigning(self):
class TestFile(Document):
the_file = FileField()
TestFile.drop_collection()
test_file = TestFile(the_file=open(TEST_IMAGE_PATH, 'rb')).save()
self.assertEqual(test_file.the_file.get().length, 8313)
test_file = TestFile.objects.first()
test_file.the_file = open(TEST_IMAGE2_PATH, 'rb')
test_file.save()
self.assertEqual(test_file.the_file.get().length, 4971)
def test_file_boolean(self):
"""Ensure that a boolean test of a FileField indicates its presence
"""
class TestFile(Document):
the_file = FileField()
TestFile.drop_collection()
test_file = TestFile()
self.assertFalse(bool(test_file.the_file))
test_file.the_file.put(six.b('Hello, World!'), content_type='text/plain')
test_file.save()
self.assertTrue(bool(test_file.the_file))
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.content_type, "text/plain")
def test_file_cmp(self):
"""Test comparing against other types"""
class TestFile(Document):
the_file = FileField()
test_file = TestFile()
self.assertFalse(test_file.the_file in [{"test": 1}])
def test_file_disk_space(self):
""" Test disk space usage when we delete/replace a file """
class TestFile(Document):
the_file = FileField()
text = six.b('Hello, World!')
content_type = 'text/plain'
testfile = TestFile()
testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save()
# Now check fs.files and fs.chunks
db = TestFile._get_db()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 1)
self.assertEquals(len(list(chunks)), 1)
# Deleting the docoument should delete the files
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
# Test case where we don't store a file in the first place
testfile = TestFile()
testfile.save()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
# Test case where we overwrite the file
testfile = TestFile()
testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save()
text = six.b('Bonjour, World!')
testfile.the_file.replace(text, content_type=content_type, filename="hello")
testfile.save()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 1)
self.assertEquals(len(list(chunks)), 1)
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
def test_image_field(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField()
TestImage.drop_collection()
with tempfile.TemporaryFile() as f:
f.write(six.b("Hello World!"))
f.flush()
t = TestImage()
try:
t.image.put(f)
self.fail("Should have raised an invalidation error")
except ValidationError as e:
self.assertEqual("%s" % e, "Invalid image: cannot identify image file %s" % f)
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.save()
t = TestImage.objects.first()
self.assertEqual(t.image.format, 'PNG')
w, h = t.image.size
self.assertEqual(w, 371)
self.assertEqual(h, 76)
t.image.delete()
def test_image_field_reassigning(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestFile(Document):
the_file = ImageField()
TestFile.drop_collection()
test_file = TestFile(the_file=open(TEST_IMAGE_PATH, 'rb')).save()
self.assertEqual(test_file.the_file.size, (371, 76))
test_file = TestFile.objects.first()
test_file.the_file = open(TEST_IMAGE2_PATH, 'rb')
test_file.save()
self.assertEqual(test_file.the_file.size, (45, 101))
def test_image_field_resize(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField(size=(185, 37))
TestImage.drop_collection()
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.save()
t = TestImage.objects.first()
self.assertEqual(t.image.format, 'PNG')
w, h = t.image.size
self.assertEqual(w, 185)
self.assertEqual(h, 37)
t.image.delete()
def test_image_field_resize_force(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField(size=(185, 37, True))
TestImage.drop_collection()
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.save()
t = TestImage.objects.first()
self.assertEqual(t.image.format, 'PNG')
w, h = t.image.size
self.assertEqual(w, 185)
self.assertEqual(h, 37)
t.image.delete()
def test_image_field_thumbnail(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField(thumbnail_size=(92, 18))
TestImage.drop_collection()
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.save()
t = TestImage.objects.first()
self.assertEqual(t.image.thumbnail.format, 'PNG')
self.assertEqual(t.image.thumbnail.width, 92)
self.assertEqual(t.image.thumbnail.height, 18)
t.image.delete()
def test_file_multidb(self):
register_connection('test_files', 'test_files')
class TestFile(Document):
name = StringField()
the_file = FileField(db_alias="test_files",
collection_name="macumba")
TestFile.drop_collection()
# delete old filesystem
get_db("test_files").macumba.files.drop()
get_db("test_files").macumba.chunks.drop()
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(six.b('Hello, World!'),
name="hello.txt")
test_file.save()
data = get_db("test_files").macumba.files.find_one()
self.assertEqual(data.get('name'), 'hello.txt')
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(), six.b('Hello, World!'))
test_file = TestFile.objects.first()
test_file.the_file = six.b('HELLO, WORLD!')
test_file.save()
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(),
six.b('HELLO, WORLD!'))
def test_copyable(self):
class PutFile(Document):
the_file = FileField()
PutFile.drop_collection()
text = six.b('Hello, World!')
content_type = 'text/plain'
putfile = PutFile()
putfile.the_file.put(text, content_type=content_type)
putfile.save()
class TestFile(Document):
name = StringField()
self.assertEqual(putfile, copy.copy(putfile))
self.assertEqual(putfile, copy.deepcopy(putfile))
def test_get_image_by_grid_id(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image1 = ImageField()
image2 = ImageField()
TestImage.drop_collection()
t = TestImage()
t.image1.put(open(TEST_IMAGE_PATH, 'rb'))
t.image2.put(open(TEST_IMAGE2_PATH, 'rb'))
t.save()
test = TestImage.objects.first()
grid_id = test.image1.grid_id
self.assertEqual(1, TestImage.objects(Q(image1=grid_id)
or Q(image2=grid_id)).count())
def test_complex_field_filefield(self):
"""Ensure you can add meta data to file"""
class Animal(Document):
genus = StringField()
family = StringField()
photos = ListField(FileField())
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
photos_field = marmot._fields['photos'].field
new_proxy = photos_field.get_proxy_obj('photos', marmot)
new_proxy.put(marmot_photo, content_type='image/jpeg', foo='bar')
marmot_photo.close()
marmot.photos.append(new_proxy)
marmot.save()
marmot = Animal.objects.get()
self.assertEqual(marmot.photos[0].content_type, 'image/jpeg')
self.assertEqual(marmot.photos[0].foo, 'bar')
self.assertEqual(marmot.photos[0].get().length, 8313)
if __name__ == '__main__':
unittest.main()

387
tests/fields/geo.py Normal file
View File

@@ -0,0 +1,387 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import *
from mongoengine.connection import get_db
__all__ = ("GeoFieldTest", )
class GeoFieldTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def _test_for_expected_error(self, Cls, loc, expected):
try:
Cls(loc=loc).validate()
self.fail('Should not validate the location {0}'.format(loc))
except ValidationError as e:
self.assertEqual(expected, e.to_dict()['loc'])
def test_geopoint_validation(self):
class Location(Document):
loc = GeoPointField()
invalid_coords = [{"x": 1, "y": 2}, 5, "a"]
expected = 'GeoPointField can only accept tuples or lists of (x, y)'
for coord in invalid_coords:
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [[], [1], [1, 2, 3]]
for coord in invalid_coords:
expected = "Value (%s) must be a two-dimensional point" % repr(coord)
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [[{}, {}], ("a", "b")]
for coord in invalid_coords:
expected = "Both values (%s) in point must be float or int" % repr(coord)
self._test_for_expected_error(Location, coord, expected)
def test_point_validation(self):
class Location(Document):
loc = PointField()
invalid_coords = {"x": 1, "y": 2}
expected = 'PointField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": []}
expected = 'PointField type must be "Point"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "Point", "coordinates": [1, 2, 3]}
expected = "Value ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [5, "a"]
expected = "PointField can only accept lists of [x, y]"
for coord in invalid_coords:
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [[], [1], [1, 2, 3]]
for coord in invalid_coords:
expected = "Value (%s) must be a two-dimensional point" % repr(coord)
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [[{}, {}], ("a", "b")]
for coord in invalid_coords:
expected = "Both values (%s) in point must be float or int" % repr(coord)
self._test_for_expected_error(Location, coord, expected)
Location(loc=[1, 2]).validate()
Location(loc={
"type": "Point",
"coordinates": [
81.4471435546875,
23.61432859499169
]}).validate()
def test_linestring_validation(self):
class Location(Document):
loc = LineStringField()
invalid_coords = {"x": 1, "y": 2}
expected = 'LineStringField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'LineStringField type must be "LineString"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "LineString", "coordinates": [[1, 2, 3]]}
expected = "Invalid LineString:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [5, "a"]
expected = "Invalid LineString must contain at least one valid point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[1]]
expected = "Invalid LineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0])
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[1, 2, 3]]
expected = "Invalid LineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0])
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[{}, {}]], [("a", "b")]]
for coord in invalid_coords:
expected = "Invalid LineString:\nBoth values (%s) in point must be float or int" % repr(coord[0])
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[1, 2], [3, 4], [5, 6], [1, 2]]).validate()
def test_polygon_validation(self):
class Location(Document):
loc = PolygonField()
invalid_coords = {"x": 1, "y": 2}
expected = 'PolygonField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'PolygonField type must be "Polygon"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "Polygon", "coordinates": [[[1, 2, 3]]]}
expected = "Invalid Polygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[5, "a"]]]
expected = "Invalid Polygon:\nBoth values ([5, 'a']) in point must be float or int"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[]]]
expected = "Invalid Polygon must contain at least one valid linestring"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1, 2, 3]]]
expected = "Invalid Polygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[{}, {}]], [("a", "b")]]
expected = "Invalid Polygon:\nBoth values ([{}, {}]) in point must be float or int, Both values (('a', 'b')) in point must be float or int"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1, 2], [3, 4]]]
expected = "Invalid Polygon:\nLineStrings must start and end at the same point"
self._test_for_expected_error(Location, invalid_coords, expected)
Location(loc=[[[1, 2], [3, 4], [5, 6], [1, 2]]]).validate()
def test_multipoint_validation(self):
class Location(Document):
loc = MultiPointField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiPointField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'MultiPointField type must be "MultiPoint"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiPoint", "coordinates": [[1, 2, 3]]}
expected = "Value ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[]]
expected = "Invalid MultiPoint must contain at least one valid point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1]], [[1, 2, 3]]]
for coord in invalid_coords:
expected = "Value (%s) must be a two-dimensional point" % repr(coord[0])
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [[[{}, {}]], [("a", "b")]]
for coord in invalid_coords:
expected = "Both values (%s) in point must be float or int" % repr(coord[0])
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[1, 2]]).validate()
Location(loc={
"type": "MultiPoint",
"coordinates": [
[1, 2],
[81.4471435546875, 23.61432859499169]
]}).validate()
def test_multilinestring_validation(self):
class Location(Document):
loc = MultiLineStringField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiLineStringField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'MultiLineStringField type must be "MultiLineString"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiLineString", "coordinates": [[[1, 2, 3]]]}
expected = "Invalid MultiLineString:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [5, "a"]
expected = "Invalid MultiLineString must contain at least one valid linestring"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1]]]
expected = "Invalid MultiLineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0][0])
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1, 2, 3]]]
expected = "Invalid MultiLineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0][0])
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[{}, {}]]], [[("a", "b")]]]
for coord in invalid_coords:
expected = "Invalid MultiLineString:\nBoth values (%s) in point must be float or int" % repr(coord[0][0])
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[[1, 2], [3, 4], [5, 6], [1, 2]]]).validate()
def test_multipolygon_validation(self):
class Location(Document):
loc = MultiPolygonField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiPolygonField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'MultiPolygonField type must be "MultiPolygon"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiPolygon", "coordinates": [[[[1, 2, 3]]]]}
expected = "Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[5, "a"]]]]
expected = "Invalid MultiPolygon:\nBoth values ([5, 'a']) in point must be float or int"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[]]]]
expected = "Invalid MultiPolygon must contain at least one valid Polygon"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[1, 2, 3]]]]
expected = "Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[{}, {}]]], [[("a", "b")]]]
expected = "Invalid MultiPolygon:\nBoth values ([{}, {}]) in point must be float or int, Both values (('a', 'b')) in point must be float or int"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[1, 2], [3, 4]]]]
expected = "Invalid MultiPolygon:\nLineStrings must start and end at the same point"
self._test_for_expected_error(Location, invalid_coords, expected)
Location(loc=[[[[1, 2], [3, 4], [5, 6], [1, 2]]]]).validate()
def test_indexes_geopoint(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
class Event(Document):
title = StringField()
location = GeoPointField()
geo_indicies = Event._geo_indices()
self.assertEqual(geo_indicies, [{'fields': [('location', '2d')]}])
def test_geopoint_embedded_indexes(self):
"""Ensure that indexes are created automatically for GeoPointFields on
embedded documents.
"""
class Venue(EmbeddedDocument):
location = GeoPointField()
name = StringField()
class Event(Document):
title = StringField()
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertEqual(geo_indicies, [{'fields': [('venue.location', '2d')]}])
def test_indexes_2dsphere(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
class Event(Document):
title = StringField()
point = PointField()
line = LineStringField()
polygon = PolygonField()
geo_indicies = Event._geo_indices()
self.assertTrue({'fields': [('line', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('polygon', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('point', '2dsphere')]} in geo_indicies)
def test_indexes_2dsphere_embedded(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
class Venue(EmbeddedDocument):
name = StringField()
point = PointField()
line = LineStringField()
polygon = PolygonField()
class Event(Document):
title = StringField()
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertTrue({'fields': [('venue.line', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('venue.polygon', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('venue.point', '2dsphere')]} in geo_indicies)
def test_geo_indexes_recursion(self):
class Location(Document):
name = StringField()
location = GeoPointField()
class Parent(Document):
name = StringField()
location = ReferenceField(Location)
Location.drop_collection()
Parent.drop_collection()
Parent(name='Berlin').save()
info = Parent._get_collection().index_information()
self.assertFalse('location_2d' in info)
info = Location._get_collection().index_information()
self.assertTrue('location_2d' in info)
self.assertEqual(len(Parent._geo_indices()), 0)
self.assertEqual(len(Location._geo_indices()), 1)
def test_geo_indexes_auto_index(self):
# Test just listing the fields
class Log(Document):
location = PointField(auto_index=False)
datetime = DateTimeField()
meta = {
'indexes': [[("location", "2dsphere"), ("datetime", 1)]]
}
self.assertEqual([], Log._geo_indices())
Log.drop_collection()
Log.ensure_indexes()
info = Log._get_collection().index_information()
self.assertEqual(info["location_2dsphere_datetime_1"]["key"],
[('location', '2dsphere'), ('datetime', 1)])
# Test listing explicitly
class Log(Document):
location = PointField(auto_index=False)
datetime = DateTimeField()
meta = {
'indexes': [
{'fields': [("location", "2dsphere"), ("datetime", 1)]}
]
}
self.assertEqual([], Log._geo_indices())
Log.drop_collection()
Log.ensure_indexes()
info = Log._get_collection().index_information()
self.assertEqual(info["location_2dsphere_datetime_1"]["key"],
[('location', '2dsphere'), ('datetime', 1)])
if __name__ == '__main__':
unittest.main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

View File

Before

Width:  |  Height:  |  Size: 8.1 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@@ -1,6 +1,8 @@
import pickle
from datetime import datetime
from mongoengine import *
from mongoengine import signals
class PickleEmbedded(EmbeddedDocument):
@@ -15,6 +17,41 @@ class PickleTest(Document):
photo = FileField()
class NewDocumentPickleTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
photo = FileField()
new_field = StringField()
class PickleDynamicEmbedded(DynamicEmbeddedDocument):
date = DateTimeField(default=datetime.now)
class PickleDynamicTest(DynamicDocument):
number = IntField()
class PickleSignalsTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
@classmethod
def post_save(self, sender, document, created, **kwargs):
pickled = pickle.dumps(document)
@classmethod
def post_delete(self, sender, document, **kwargs):
pickled = pickle.dumps(document)
signals.post_save.connect(PickleSignalsTest.post_save, sender=PickleSignalsTest)
signals.post_delete.connect(PickleSignalsTest.post_delete, sender=PickleSignalsTest)
class Mixin(object):
name = StringField()

View File

@@ -0,0 +1,6 @@
from transform import *
from field_list import *
from queryset import *
from visitor import *
from geo import *
from modify import *

View File

@@ -0,0 +1,433 @@
import unittest
from mongoengine import *
from mongoengine.queryset import QueryFieldList
__all__ = ("QueryFieldListTest", "OnlyExcludeAllTest")
class QueryFieldListTest(unittest.TestCase):
def test_empty(self):
q = QueryFieldList()
self.assertFalse(q)
q = QueryFieldList(always_include=['_cls'])
self.assertFalse(q)
def test_include_include(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY, _only_called=True)
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1, 'c': 1})
def test_include_exclude(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'a': 1, 'b': 1})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 1})
def test_exclude_exclude(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0, 'c': 0})
def test_exclude_include(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a', 'b'], value=QueryFieldList.EXCLUDE)
self.assertEqual(q.as_dict(), {'a': 0, 'b': 0})
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'c': 1})
def test_always_include(self):
q = QueryFieldList(always_include=['x', 'y'])
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'c': 1})
def test_reset(self):
q = QueryFieldList(always_include=['x', 'y'])
q += QueryFieldList(fields=['a', 'b', 'x'], value=QueryFieldList.EXCLUDE)
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'c': 1})
q.reset()
self.assertFalse(q)
q += QueryFieldList(fields=['b', 'c'], value=QueryFieldList.ONLY)
self.assertEqual(q.as_dict(), {'x': 1, 'y': 1, 'b': 1, 'c': 1})
def test_using_a_slice(self):
q = QueryFieldList()
q += QueryFieldList(fields=['a'], value={"$slice": 5})
self.assertEqual(q.as_dict(), {'a': {"$slice": 5}})
class OnlyExcludeAllTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
Person.drop_collection()
self.Person = Person
def test_mixing_only_exclude(self):
class MyDoc(Document):
a = StringField()
b = StringField()
c = StringField()
d = StringField()
e = StringField()
f = StringField()
include = ['a', 'b', 'c', 'd', 'e']
exclude = ['d', 'e']
only = ['b', 'c']
qs = MyDoc.objects.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(),
{'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {'a': 1, 'b': 1, 'c': 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
qs = MyDoc.objects.exclude(*exclude)
qs = qs.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(), {'a': 1, 'b': 1, 'c': 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
def test_slicing(self):
class MyDoc(Document):
a = ListField()
b = ListField()
c = ListField()
d = ListField()
e = ListField()
f = ListField()
include = ['a', 'b', 'c', 'd', 'e']
exclude = ['d', 'e']
only = ['b', 'c']
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
qs = qs.only(*only)
qs = qs.fields(slice__b=5)
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}, 'c': 1})
qs = qs.fields(slice__c=[5, 1])
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}, 'c': {'$slice': [5, 1]}})
qs = qs.exclude('c')
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}})
def test_mix_slice_with_other_fields(self):
class MyDoc(Document):
a = ListField()
b = ListField()
c = ListField()
qs = MyDoc.objects.fields(a=1, b=0, slice__c=2)
self.assertEqual(qs._loaded_fields.as_dict(),
{'c': {'$slice': 2}, 'a': 1})
def test_only(self):
"""Ensure that QuerySet.only only returns the requested fields.
"""
person = self.Person(name='test', age=25)
person.save()
obj = self.Person.objects.only('name').get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
obj = self.Person.objects.only('age').get()
self.assertEqual(obj.name, None)
self.assertEqual(obj.age, person.age)
obj = self.Person.objects.only('name', 'age').get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, person.age)
obj = self.Person.objects.only(*('id', 'name',)).get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
# Check polymorphism still works
class Employee(self.Person):
salary = IntField(db_field='wage')
employee = Employee(name='test employee', age=40, salary=30000)
employee.save()
obj = self.Person.objects(id=employee.id).only('age').get()
self.assertTrue(isinstance(obj, Employee))
# Check field names are looked up properly
obj = Employee.objects(id=employee.id).only('salary').get()
self.assertEqual(obj.salary, employee.salary)
self.assertEqual(obj.name, None)
def test_only_with_subfields(self):
class User(EmbeddedDocument):
name = StringField()
email = StringField()
class Comment(EmbeddedDocument):
title = StringField()
text = StringField()
class BlogPost(Document):
content = StringField()
author = EmbeddedDocumentField(User)
comments = ListField(EmbeddedDocumentField(Comment))
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...')
post.author = User(name='Test User')
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
post.save()
obj = BlogPost.objects.only('author.name',).get()
self.assertEqual(obj.content, None)
self.assertEqual(obj.author.email, None)
self.assertEqual(obj.author.name, 'Test User')
self.assertEqual(obj.comments, [])
obj = BlogPost.objects.only('content', 'comments.title',).get()
self.assertEqual(obj.content, 'Had a good coffee today...')
self.assertEqual(obj.author, None)
self.assertEqual(obj.comments[0].title, 'I aggree')
self.assertEqual(obj.comments[1].title, 'Coffee')
self.assertEqual(obj.comments[0].text, None)
self.assertEqual(obj.comments[1].text, None)
obj = BlogPost.objects.only('comments',).get()
self.assertEqual(obj.content, None)
self.assertEqual(obj.author, None)
self.assertEqual(obj.comments[0].title, 'I aggree')
self.assertEqual(obj.comments[1].title, 'Coffee')
self.assertEqual(obj.comments[0].text, 'Great post!')
self.assertEqual(obj.comments[1].text, 'I hate coffee')
BlogPost.drop_collection()
def test_exclude(self):
class User(EmbeddedDocument):
name = StringField()
email = StringField()
class Comment(EmbeddedDocument):
title = StringField()
text = StringField()
class BlogPost(Document):
content = StringField()
author = EmbeddedDocumentField(User)
comments = ListField(EmbeddedDocumentField(Comment))
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...')
post.author = User(name='Test User')
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
post.save()
obj = BlogPost.objects.exclude('author', 'comments.text').get()
self.assertEqual(obj.author, None)
self.assertEqual(obj.content, 'Had a good coffee today...')
self.assertEqual(obj.comments[0].title, 'I aggree')
self.assertEqual(obj.comments[0].text, None)
BlogPost.drop_collection()
def test_exclude_only_combining(self):
class Attachment(EmbeddedDocument):
name = StringField()
content = StringField()
class Email(Document):
sender = StringField()
to = StringField()
subject = StringField()
body = StringField()
content_type = StringField()
attachments = ListField(EmbeddedDocumentField(Attachment))
Email.drop_collection()
email = Email(sender='me', to='you', subject='From Russia with Love', body='Hello!', content_type='text/plain')
email.attachments = [
Attachment(name='file1.doc', content='ABC'),
Attachment(name='file2.doc', content='XYZ'),
]
email.save()
obj = Email.objects.exclude('content_type').exclude('body').get()
self.assertEqual(obj.sender, 'me')
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.subject, 'From Russia with Love')
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
obj = Email.objects.only('sender', 'to').exclude('body', 'sender').get()
self.assertEqual(obj.sender, None)
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.subject, None)
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
obj = Email.objects.exclude('attachments.content').exclude('body').only('to', 'attachments.name').get()
self.assertEqual(obj.attachments[0].name, 'file1.doc')
self.assertEqual(obj.attachments[0].content, None)
self.assertEqual(obj.sender, None)
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.subject, None)
self.assertEqual(obj.body, None)
self.assertEqual(obj.content_type, None)
Email.drop_collection()
def test_all_fields(self):
class Email(Document):
sender = StringField()
to = StringField()
subject = StringField()
body = StringField()
content_type = StringField()
Email.drop_collection()
email = Email(sender='me', to='you', subject='From Russia with Love', body='Hello!', content_type='text/plain')
email.save()
obj = Email.objects.exclude('content_type', 'body').only('to', 'body').all_fields().get()
self.assertEqual(obj.sender, 'me')
self.assertEqual(obj.to, 'you')
self.assertEqual(obj.subject, 'From Russia with Love')
self.assertEqual(obj.body, 'Hello!')
self.assertEqual(obj.content_type, 'text/plain')
Email.drop_collection()
def test_slicing_fields(self):
"""Ensure that query slicing an array works.
"""
class Numbers(Document):
n = ListField(IntField())
Numbers.drop_collection()
numbers = Numbers(n=[0, 1, 2, 3, 4, 5, -5, -4, -3, -2, -1])
numbers.save()
# first three
numbers = Numbers.objects.fields(slice__n=3).get()
self.assertEqual(numbers.n, [0, 1, 2])
# last three
numbers = Numbers.objects.fields(slice__n=-3).get()
self.assertEqual(numbers.n, [-3, -2, -1])
# skip 2, limit 3
numbers = Numbers.objects.fields(slice__n=[2, 3]).get()
self.assertEqual(numbers.n, [2, 3, 4])
# skip to fifth from last, limit 4
numbers = Numbers.objects.fields(slice__n=[-5, 4]).get()
self.assertEqual(numbers.n, [-5, -4, -3, -2])
# skip to fifth from last, limit 10
numbers = Numbers.objects.fields(slice__n=[-5, 10]).get()
self.assertEqual(numbers.n, [-5, -4, -3, -2, -1])
# skip to fifth from last, limit 10 dict method
numbers = Numbers.objects.fields(n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.n, [-5, -4, -3, -2, -1])
def test_slicing_nested_fields(self):
"""Ensure that query slicing an embedded array works.
"""
class EmbeddedNumber(EmbeddedDocument):
n = ListField(IntField())
class Numbers(Document):
embedded = EmbeddedDocumentField(EmbeddedNumber)
Numbers.drop_collection()
numbers = Numbers()
numbers.embedded = EmbeddedNumber(n=[0, 1, 2, 3, 4, 5, -5, -4, -3, -2, -1])
numbers.save()
# first three
numbers = Numbers.objects.fields(slice__embedded__n=3).get()
self.assertEqual(numbers.embedded.n, [0, 1, 2])
# last three
numbers = Numbers.objects.fields(slice__embedded__n=-3).get()
self.assertEqual(numbers.embedded.n, [-3, -2, -1])
# skip 2, limit 3
numbers = Numbers.objects.fields(slice__embedded__n=[2, 3]).get()
self.assertEqual(numbers.embedded.n, [2, 3, 4])
# skip to fifth from last, limit 4
numbers = Numbers.objects.fields(slice__embedded__n=[-5, 4]).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2])
# skip to fifth from last, limit 10
numbers = Numbers.objects.fields(slice__embedded__n=[-5, 10]).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
# skip to fifth from last, limit 10 dict method
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
def test_exclude_from_subclasses_docs(self):
class Base(Document):
username = StringField()
meta = {'allow_inheritance': True}
class Anon(Base):
anon = BooleanField()
class User(Base):
password = StringField()
wibble = StringField()
Base.drop_collection()
User(username="mongodb", password="secret").save()
user = Base.objects().exclude("password", "wibble").first()
self.assertEqual(user.password, None)
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
if __name__ == '__main__':
unittest.main()

557
tests/queryset/geo.py Normal file
View File

@@ -0,0 +1,557 @@
import datetime
import unittest
from mongoengine import *
from tests.utils import MongoDBTestCase, needs_mongodb_v3
__all__ = ("GeoQueriesTest",)
class GeoQueriesTest(MongoDBTestCase):
def _create_event_data(self, point_field_class=GeoPointField):
"""Create some sample data re-used in many of the tests below."""
class Event(Document):
title = StringField()
date = DateTimeField()
location = point_field_class()
def __unicode__(self):
return self.title
self.Event = Event
Event.drop_collection()
event1 = Event.objects.create(
title="Coltrane Motion @ Double Door",
date=datetime.datetime.now() - datetime.timedelta(days=1),
location=[-87.677137, 41.909889])
event2 = Event.objects.create(
title="Coltrane Motion @ Bottom of the Hill",
date=datetime.datetime.now() - datetime.timedelta(days=10),
location=[-122.4194155, 37.7749295])
event3 = Event.objects.create(
title="Coltrane Motion @ Empty Bottle",
date=datetime.datetime.now(),
location=[-87.686638, 41.900474])
return event1, event2, event3
def test_near(self):
"""Make sure the "near" operator works."""
event1, event2, event3 = self._create_event_data()
# find all events "near" pitchfork office, chicago.
# note that "near" will show the san francisco event, too,
# although it sorts to last.
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event1, event3, event2])
# ensure ordering is respected by "near"
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
events = events.order_by("-date")
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event3, event1, event2])
def test_near_and_max_distance(self):
"""Ensure the "max_distance" operator works alongside the "near"
operator.
"""
event1, event2, event3 = self._create_event_data()
# find events within 10 degrees of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point,
location__max_distance=10)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# $minDistance was added in MongoDB v2.6, but continued being buggy
# until v3.0; skip for older versions
@needs_mongodb_v3
def test_near_and_min_distance(self):
"""Ensure the "min_distance" operator works alongside the "near"
operator.
"""
event1, event2, event3 = self._create_event_data()
# find events at least 10 degrees away of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point,
location__min_distance=10)
self.assertEqual(events.count(), 2)
def test_within_distance(self):
"""Make sure the "within_distance" operator works."""
event1, event2, event3 = self._create_event_data()
# find events within 5 degrees of pitchfork office, chicago
point_and_distance = [[-87.67892, 41.9120459], 5]
events = self.Event.objects(
location__within_distance=point_and_distance)
self.assertEqual(events.count(), 2)
events = list(events)
self.assertTrue(event2 not in events)
self.assertTrue(event1 in events)
self.assertTrue(event3 in events)
# find events within 10 degrees of san francisco
point_and_distance = [[-122.415579, 37.7566023], 10]
events = self.Event.objects(
location__within_distance=point_and_distance)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# find events within 1 degree of greenpoint, broolyn, nyc, ny
point_and_distance = [[-73.9509714, 40.7237134], 1]
events = self.Event.objects(
location__within_distance=point_and_distance)
self.assertEqual(events.count(), 0)
# ensure ordering is respected by "within_distance"
point_and_distance = [[-87.67892, 41.9120459], 10]
events = self.Event.objects(
location__within_distance=point_and_distance)
events = events.order_by("-date")
self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3)
def test_within_box(self):
"""Ensure the "within_box" operator works."""
event1, event2, event3 = self._create_event_data()
# check that within_box works
box = [(-125.0, 35.0), (-100.0, 40.0)]
events = self.Event.objects(location__within_box=box)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event2.id)
def test_within_polygon(self):
"""Ensure the "within_polygon" operator works."""
event1, event2, event3 = self._create_event_data()
polygon = [
(-87.694445, 41.912114),
(-87.69084, 41.919395),
(-87.681742, 41.927186),
(-87.654276, 41.911731),
(-87.656164, 41.898061),
]
events = self.Event.objects(location__within_polygon=polygon)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event1.id)
polygon2 = [
(-1.742249, 54.033586),
(-1.225891, 52.792797),
(-4.40094, 53.389881)
]
events = self.Event.objects(location__within_polygon=polygon2)
self.assertEqual(events.count(), 0)
def test_2dsphere_near(self):
"""Make sure the "near" operator works with a PointField, which
corresponds to a 2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
# find all events "near" pitchfork office, chicago.
# note that "near" will show the san francisco event, too,
# although it sorts to last.
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event1, event3, event2])
# ensure ordering is respected by "near"
events = self.Event.objects(location__near=[-87.67892, 41.9120459])
events = events.order_by("-date")
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event3, event1, event2])
def test_2dsphere_near_and_max_distance(self):
"""Ensure the "max_distance" operator works alongside the "near"
operator with a 2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
# find events within 10km of san francisco
point = [-122.415579, 37.7566023]
events = self.Event.objects(location__near=point,
location__max_distance=10000)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
# find events within 1km of greenpoint, broolyn, nyc, ny
events = self.Event.objects(location__near=[-73.9509714, 40.7237134],
location__max_distance=1000)
self.assertEqual(events.count(), 0)
# ensure ordering is respected by "near"
events = self.Event.objects(
location__near=[-87.67892, 41.9120459],
location__max_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 2)
self.assertEqual(events[0], event3)
def test_2dsphere_geo_within_box(self):
"""Ensure the "geo_within_box" operator works with a 2dsphere
index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
# check that within_box works
box = [(-125.0, 35.0), (-100.0, 40.0)]
events = self.Event.objects(location__geo_within_box=box)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event2.id)
def test_2dsphere_geo_within_polygon(self):
"""Ensure the "geo_within_polygon" operator works with a
2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
polygon = [
(-87.694445, 41.912114),
(-87.69084, 41.919395),
(-87.681742, 41.927186),
(-87.654276, 41.911731),
(-87.656164, 41.898061),
]
events = self.Event.objects(location__geo_within_polygon=polygon)
self.assertEqual(events.count(), 1)
self.assertEqual(events[0].id, event1.id)
polygon2 = [
(-1.742249, 54.033586),
(-1.225891, 52.792797),
(-4.40094, 53.389881)
]
events = self.Event.objects(location__geo_within_polygon=polygon2)
self.assertEqual(events.count(), 0)
# $minDistance was added in MongoDB v2.6, but continued being buggy
# until v3.0; skip for older versions
@needs_mongodb_v3
def test_2dsphere_near_and_min_max_distance(self):
"""Ensure "min_distace" and "max_distance" operators work well
together with the "near" operator in a 2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
# ensure min_distance and max_distance combine well
events = self.Event.objects(
location__near=[-87.67892, 41.9120459],
location__min_distance=1000,
location__max_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event3)
# ensure ordering is respected by "near" with "min_distance"
events = self.Event.objects(
location__near=[-87.67892, 41.9120459],
location__min_distance=10000
).order_by("-date")
self.assertEqual(events.count(), 1)
self.assertEqual(events[0], event2)
def test_2dsphere_geo_within_center(self):
"""Make sure the "geo_within_center" operator works with a
2dsphere index.
"""
event1, event2, event3 = self._create_event_data(
point_field_class=PointField
)
# find events within 5 degrees of pitchfork office, chicago
point_and_distance = [[-87.67892, 41.9120459], 2]
events = self.Event.objects(
location__geo_within_center=point_and_distance)
self.assertEqual(events.count(), 2)
events = list(events)
self.assertTrue(event2 not in events)
self.assertTrue(event1 in events)
self.assertTrue(event3 in events)
def _test_embedded(self, point_field_class):
"""Helper test method ensuring given point field class works
well in an embedded document.
"""
class Venue(EmbeddedDocument):
location = point_field_class()
name = StringField()
class Event(Document):
title = StringField()
venue = EmbeddedDocumentField(Venue)
Event.drop_collection()
venue1 = Venue(name="The Rock", location=[-87.677137, 41.909889])
venue2 = Venue(name="The Bridge", location=[-122.4194155, 37.7749295])
event1 = Event(title="Coltrane Motion @ Double Door",
venue=venue1).save()
event2 = Event(title="Coltrane Motion @ Bottom of the Hill",
venue=venue2).save()
event3 = Event(title="Coltrane Motion @ Empty Bottle",
venue=venue1).save()
# find all events "near" pitchfork office, chicago.
# note that "near" will show the san francisco event, too,
# although it sorts to last.
events = Event.objects(venue__location__near=[-87.67892, 41.9120459])
self.assertEqual(events.count(), 3)
self.assertEqual(list(events), [event1, event3, event2])
def test_geo_spatial_embedded(self):
"""Make sure GeoPointField works properly in an embedded document."""
self._test_embedded(point_field_class=GeoPointField)
def test_2dsphere_point_embedded(self):
"""Make sure PointField works properly in an embedded document."""
self._test_embedded(point_field_class=PointField)
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
@needs_mongodb_v3
def test_spherical_geospatial_operators(self):
"""Ensure that spherical geospatial queries are working."""
class Point(Document):
location = GeoPointField()
Point.drop_collection()
# These points are one degree apart, which (according to Google Maps)
# is about 110 km apart at this place on the Earth.
north_point = Point(location=[-122, 38]).save() # Near Concord, CA
south_point = Point(location=[-122, 37]).save() # Near Santa Cruz, CA
earth_radius = 6378.009 # in km (needs to be a float for dividing by)
# Finds both points because they are within 60 km of the reference
# point equidistant between them.
points = Point.objects(location__near_sphere=[-122, 37.5])
self.assertEqual(points.count(), 2)
# Same behavior for _within_spherical_distance
points = Point.objects(
location__within_spherical_distance=[
[-122, 37.5],
60 / earth_radius
]
)
self.assertEqual(points.count(), 2)
points = Point.objects(location__near_sphere=[-122, 37.5],
location__max_distance=60 / earth_radius)
self.assertEqual(points.count(), 2)
# Test query works with max_distance, being farer from one point
points = Point.objects(location__near_sphere=[-122, 37.8],
location__max_distance=60 / earth_radius)
close_point = points.first()
self.assertEqual(points.count(), 1)
# Test query works with min_distance, being farer from one point
points = Point.objects(location__near_sphere=[-122, 37.8],
location__min_distance=60 / earth_radius)
self.assertEqual(points.count(), 1)
far_point = points.first()
self.assertNotEqual(close_point, far_point)
# Finds both points, but orders the north point first because it's
# closer to the reference point to the north.
points = Point.objects(location__near_sphere=[-122, 38.5])
self.assertEqual(points.count(), 2)
self.assertEqual(points[0].id, north_point.id)
self.assertEqual(points[1].id, south_point.id)
# Finds both points, but orders the south point first because it's
# closer to the reference point to the south.
points = Point.objects(location__near_sphere=[-122, 36.5])
self.assertEqual(points.count(), 2)
self.assertEqual(points[0].id, south_point.id)
self.assertEqual(points[1].id, north_point.id)
# Finds only one point because only the first point is within 60km of
# the reference point to the south.
points = Point.objects(
location__within_spherical_distance=[
[-122, 36.5],
60 / earth_radius
]
)
self.assertEqual(points.count(), 1)
self.assertEqual(points[0].id, south_point.id)
def test_linestring(self):
class Road(Document):
name = StringField()
line = LineStringField()
Road.drop_collection()
Road(name="66", line=[[40, 5], [41, 6]]).save()
# near
point = {"type": "Point", "coordinates": [40, 5]}
roads = Road.objects.filter(line__near=point["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__near=point).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__near={"$geometry": point}).count()
self.assertEqual(1, roads)
# Within
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
roads = Road.objects.filter(line__geo_within=polygon["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__geo_within=polygon).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__geo_within={"$geometry": polygon}).count()
self.assertEqual(1, roads)
# Intersects
line = {"type": "LineString",
"coordinates": [[40, 5], [40, 6]]}
roads = Road.objects.filter(line__geo_intersects=line["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__geo_intersects=line).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__geo_intersects={"$geometry": line}).count()
self.assertEqual(1, roads)
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
roads = Road.objects.filter(line__geo_intersects=polygon["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__geo_intersects=polygon).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(line__geo_intersects={"$geometry": polygon}).count()
self.assertEqual(1, roads)
def test_polygon(self):
class Road(Document):
name = StringField()
poly = PolygonField()
Road.drop_collection()
Road(name="66", poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]).save()
# near
point = {"type": "Point", "coordinates": [40, 5]}
roads = Road.objects.filter(poly__near=point["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__near=point).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__near={"$geometry": point}).count()
self.assertEqual(1, roads)
# Within
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
roads = Road.objects.filter(poly__geo_within=polygon["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__geo_within=polygon).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__geo_within={"$geometry": polygon}).count()
self.assertEqual(1, roads)
# Intersects
line = {"type": "LineString",
"coordinates": [[40, 5], [41, 6]]}
roads = Road.objects.filter(poly__geo_intersects=line["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__geo_intersects=line).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__geo_intersects={"$geometry": line}).count()
self.assertEqual(1, roads)
polygon = {"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}
roads = Road.objects.filter(poly__geo_intersects=polygon["coordinates"]).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__geo_intersects=polygon).count()
self.assertEqual(1, roads)
roads = Road.objects.filter(poly__geo_intersects={"$geometry": polygon}).count()
self.assertEqual(1, roads)
def test_2dsphere_point_sets_correctly(self):
class Location(Document):
loc = PointField()
Location.drop_collection()
Location(loc=[1,2]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [1, 2]})
Location.objects.update(set__loc=[2,1])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["loc"], {"type": "Point", "coordinates": [2, 1]})
def test_2dsphere_linestring_sets_correctly(self):
class Location(Document):
line = LineStringField()
Location.drop_collection()
Location(line=[[1, 2], [2, 2]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["line"], {"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
Location.objects.update(set__line=[[2, 1], [1, 2]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["line"], {"type": "LineString", "coordinates": [[2, 1], [1, 2]]})
def test_geojson_PolygonField(self):
class Location(Document):
poly = PolygonField()
Location.drop_collection()
Location(poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]]).save()
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["poly"], {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
Location.objects.update(set__poly=[[[40, 4], [40, 6], [41, 6], [40, 4]]])
loc = Location.objects.as_pymongo()[0]
self.assertEqual(loc["poly"], {"type": "Polygon", "coordinates": [[[40, 4], [40, 6], [41, 6], [40, 4]]]})
if __name__ == '__main__':
unittest.main()

99
tests/queryset/modify.py Normal file
View File

@@ -0,0 +1,99 @@
import unittest
from mongoengine import connect, Document, IntField
__all__ = ("FindAndModifyTest",)
class Doc(Document):
id = IntField(primary_key=True)
value = IntField()
class FindAndModifyTest(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
Doc.drop_collection()
def assertDbEqual(self, docs):
self.assertEqual(list(Doc._collection.find().sort("id")), docs)
def test_modify(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(set__value=-1)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_new(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
doc.value = -1
self.assertEqual(new_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_not_existing(self):
Doc(id=0, value=0).save()
self.assertEqual(Doc.objects(id=1).modify(set__value=-1), None)
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_upsert(self):
Doc(id=0, value=0).save()
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
self.assertEqual(old_doc, None)
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_upsert_existing(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_upsert_with_new(self):
Doc(id=0, value=0).save()
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
self.assertEqual(new_doc.to_mongo(), {"_id": 1, "value": 1})
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_remove(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(remove=True)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_find_and_modify_with_remove_not_existing(self):
Doc(id=0, value=0).save()
self.assertEqual(Doc.objects(id=1).modify(remove=True), None)
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_order_by(self):
Doc(id=0, value=3).save()
Doc(id=1, value=2).save()
Doc(id=2, value=1).save()
doc = Doc(id=3, value=0).save()
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([
{"_id": 0, "value": 3}, {"_id": 1, "value": 2},
{"_id": 2, "value": 1}, {"_id": 3, "value": -1}])
def test_modify_with_fields(self):
Doc(id=0, value=0).save()
Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
self.assertEqual(old_doc.to_mongo(), {"_id": 1})
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,78 @@
import pickle
import unittest
from pymongo.mongo_client import MongoClient
from mongoengine import Document, StringField, IntField
from mongoengine.connection import connect
__author__ = 'stas'
class Person(Document):
name = StringField()
age = IntField()
class TestQuerysetPickable(unittest.TestCase):
"""
Test for adding pickling support for QuerySet instances
See issue https://github.com/MongoEngine/mongoengine/issues/442
"""
def setUp(self):
super(TestQuerysetPickable, self).setUp()
connection = connect(db="test") #type: pymongo.mongo_client.MongoClient
connection.drop_database("test")
self.john = Person.objects.create(
name="John",
age=21
)
def test_picke_simple_qs(self):
qs = Person.objects.all()
pickle.dumps(qs)
def _get_loaded(self, qs):
s = pickle.dumps(qs)
return pickle.loads(s)
def test_unpickle(self):
qs = Person.objects.all()
loadedQs = self._get_loaded(qs)
self.assertEqual(qs.count(), loadedQs.count())
#can update loadedQs
loadedQs.update(age=23)
#check
self.assertEqual(Person.objects.first().age, 23)
def test_pickle_support_filtration(self):
Person.objects.create(
name="Alice",
age=22
)
Person.objects.create(
name="Bob",
age=23
)
qs = Person.objects.filter(age__gte=22)
self.assertEqual(qs.count(), 2)
loaded = self._get_loaded(qs)
self.assertEqual(loaded.count(), 2)
self.assertEqual(loaded.filter(name="Bob").first().age, 23)

5099
tests/queryset/queryset.py Normal file

File diff suppressed because it is too large Load Diff

246
tests/queryset/transform.py Normal file
View File

@@ -0,0 +1,246 @@
import unittest
from mongoengine import *
from mongoengine.queryset import Q, transform
__all__ = ("TransformTest",)
class TransformTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
def test_transform_query(self):
"""Ensure that the _transform_query function operates correctly.
"""
self.assertEqual(transform.query(name='test', age=30),
{'name': 'test', 'age': 30})
self.assertEqual(transform.query(age__lt=30),
{'age': {'$lt': 30}})
self.assertEqual(transform.query(age__gt=20, age__lt=50),
{'age': {'$gt': 20, '$lt': 50}})
self.assertEqual(transform.query(age=20, age__gt=50),
{'$and': [{'age': {'$gt': 50}}, {'age': 20}]})
self.assertEqual(transform.query(friend__age__gte=30),
{'friend.age': {'$gte': 30}})
self.assertEqual(transform.query(name__exists=True),
{'name': {'$exists': True}})
def test_transform_update(self):
class DicDoc(Document):
dictField = DictField()
class Doc(Document):
pass
DicDoc.drop_collection()
Doc.drop_collection()
DicDoc().save()
doc = Doc().save()
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
self.assertTrue(isinstance(update[v]["dictField.test"], dict))
# Update special cases
update = transform.update(DicDoc, unset__dictField__test=doc)
self.assertEqual(update["$unset"]["dictField.test"], 1)
update = transform.update(DicDoc, pull__dictField__test=doc)
self.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict))
def test_query_field_name(self):
"""Ensure that the correct field name is used when querying.
"""
class Comment(EmbeddedDocument):
content = StringField(db_field='commentContent')
class BlogPost(Document):
title = StringField(db_field='postTitle')
comments = ListField(EmbeddedDocumentField(Comment),
db_field='postComments')
BlogPost.drop_collection()
data = {'title': 'Post 1', 'comments': [Comment(content='test')]}
post = BlogPost(**data)
post.save()
self.assertTrue('postTitle' in
BlogPost.objects(title=data['title'])._query)
self.assertFalse('title' in
BlogPost.objects(title=data['title'])._query)
self.assertEqual(BlogPost.objects(title=data['title']).count(), 1)
self.assertTrue('_id' in BlogPost.objects(pk=post.id)._query)
self.assertEqual(BlogPost.objects(pk=post.id).count(), 1)
self.assertTrue('postComments.commentContent' in
BlogPost.objects(comments__content='test')._query)
self.assertEqual(BlogPost.objects(comments__content='test').count(), 1)
BlogPost.drop_collection()
def test_query_pk_field_name(self):
"""Ensure that the correct "primary key" field name is used when
querying
"""
class BlogPost(Document):
title = StringField(primary_key=True, db_field='postTitle')
BlogPost.drop_collection()
data = {'title': 'Post 1'}
post = BlogPost(**data)
post.save()
self.assertTrue('_id' in BlogPost.objects(pk=data['title'])._query)
self.assertTrue('_id' in BlogPost.objects(title=data['title'])._query)
self.assertEqual(BlogPost.objects(pk=data['title']).count(), 1)
BlogPost.drop_collection()
def test_chaining(self):
class A(Document):
pass
class B(Document):
a = ReferenceField(A)
A.drop_collection()
B.drop_collection()
a1 = A().save()
a2 = A().save()
B(a=a1).save()
# Works
q1 = B.objects.filter(a__in=[a1, a2], a=a1)._query
# Doesn't work
q2 = B.objects.filter(a__in=[a1, a2])
q2 = q2.filter(a=a1)._query
self.assertEqual(q1, q2)
def test_raw_query_and_Q_objects(self):
"""
Test raw plays nicely
"""
class Foo(Document):
name = StringField()
a = StringField()
b = StringField()
c = StringField()
meta = {
'allow_inheritance': False
}
query = Foo.objects(__raw__={'$nor': [{'name': 'bar'}]})._query
self.assertEqual(query, {'$nor': [{'name': 'bar'}]})
q1 = {'$or': [{'a': 1}, {'b': 1}]}
query = Foo.objects(Q(__raw__=q1) & Q(c=1))._query
self.assertEqual(query, {'$or': [{'a': 1}, {'b': 1}], 'c': 1})
def test_raw_and_merging(self):
class Doc(Document):
meta = {'allow_inheritance': False}
raw_query = Doc.objects(__raw__={
'deleted': False,
'scraped': 'yes',
'$nor': [
{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}
]
})._query
self.assertEqual(raw_query, {
'deleted': False,
'scraped': 'yes',
'$nor': [
{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}
]
})
def test_geojson_PointField(self):
class Location(Document):
loc = PointField()
update = transform.update(Location, set__loc=[1, 2])
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1, 2]}}})
update = transform.update(Location, set__loc={"type": "Point", "coordinates": [1, 2]})
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1, 2]}}})
def test_geojson_LineStringField(self):
class Location(Document):
line = LineStringField()
update = transform.update(Location, set__line=[[1, 2], [2, 2]])
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
update = transform.update(Location, set__line={"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
def test_geojson_PolygonField(self):
class Location(Document):
poly = PolygonField()
update = transform.update(Location, set__poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
update = transform.update(Location, set__poly={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
def test_type(self):
class Doc(Document):
df = DynamicField()
Doc(df=True).save()
Doc(df=7).save()
Doc(df="df").save()
self.assertEqual(Doc.objects(df__type=1).count(), 0) # double
self.assertEqual(Doc.objects(df__type=8).count(), 1) # bool
self.assertEqual(Doc.objects(df__type=2).count(), 1) # str
self.assertEqual(Doc.objects(df__type=16).count(), 1) # int
def test_last_field_name_like_operator(self):
class EmbeddedItem(EmbeddedDocument):
type = StringField()
name = StringField()
class Doc(Document):
item = EmbeddedDocumentField(EmbeddedItem)
Doc.drop_collection()
doc = Doc(item=EmbeddedItem(type="axe", name="Heroic axe"))
doc.save()
self.assertEqual(1, Doc.objects(item__type__="axe").count())
self.assertEqual(1, Doc.objects(item__name__="Heroic axe").count())
Doc.objects(id=doc.id).update(set__item__type__='sword')
self.assertEqual(1, Doc.objects(item__type__="sword").count())
self.assertEqual(0, Doc.objects(item__type__="axe").count())
def test_understandable_error_raised(self):
class Event(Document):
title = StringField()
location = GeoPointField()
box = [(35.0, -125.0), (40.0, -100.0)]
# I *meant* to execute location__within_box=box
events = Event.objects(location__within=box)
with self.assertRaises(InvalidQueryError):
events.count()
if __name__ == '__main__':
unittest.main()

347
tests/queryset/visitor.py Normal file
View File

@@ -0,0 +1,347 @@
import datetime
import re
import unittest
from bson import ObjectId
from mongoengine import *
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import Q
__all__ = ("QTest",)
class QTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
class Person(Document):
name = StringField()
age = IntField()
meta = {'allow_inheritance': True}
Person.drop_collection()
self.Person = Person
def test_empty_q(self):
"""Ensure that empty Q objects won't hurt.
"""
q1 = Q()
q2 = Q(age__gte=18)
q3 = Q()
q4 = Q(name='test')
q5 = Q()
class Person(Document):
name = StringField()
age = IntField()
query = {'$or': [{'age': {'$gte': 18}}, {'name': 'test'}]}
self.assertEqual((q1 | q2 | q3 | q4 | q5).to_query(Person), query)
query = {'age': {'$gte': 18}, 'name': 'test'}
self.assertEqual((q1 & q2 & q3 & q4 & q5).to_query(Person), query)
def test_q_with_dbref(self):
"""Ensure Q objects handle DBRefs correctly"""
connect(db='mongoenginetest')
class User(Document):
pass
class Post(Document):
created_user = ReferenceField(User)
user = User.objects.create()
Post.objects.create(created_user=user)
self.assertEqual(Post.objects.filter(created_user=user).count(), 1)
self.assertEqual(Post.objects.filter(Q(created_user=user)).count(), 1)
def test_and_combination(self):
"""Ensure that Q-objects correctly AND together.
"""
class TestDoc(Document):
x = IntField()
y = StringField()
query = (Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {'$and': [{'x': {'$lt': 7}}, {'x': {'$lt': 3}}]})
query = (Q(y="a") & Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {'$and': [{'y': "a"}, {'x': {'$lt': 7}}, {'x': {'$lt': 3}}]})
# Check normal cases work without an error
query = Q(x__lt=7) & Q(x__gt=3)
q1 = Q(x__lt=7)
q2 = Q(x__gt=3)
query = (q1 & q2).to_query(TestDoc)
self.assertEqual(query, {'x': {'$lt': 7, '$gt': 3}})
# More complex nested example
query = Q(x__lt=100) & Q(y__ne='NotMyString')
query &= Q(y__in=['a', 'b', 'c']) & Q(x__gt=-100)
mongo_query = {
'x': {'$lt': 100, '$gt': -100},
'y': {'$ne': 'NotMyString', '$in': ['a', 'b', 'c']},
}
self.assertEqual(query.to_query(TestDoc), mongo_query)
def test_or_combination(self):
"""Ensure that Q-objects correctly OR together.
"""
class TestDoc(Document):
x = IntField()
q1 = Q(x__lt=3)
q2 = Q(x__gt=7)
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(query, {
'$or': [
{'x': {'$lt': 3}},
{'x': {'$gt': 7}},
]
})
def test_and_or_combination(self):
"""Ensure that Q-objects handle ANDing ORed components.
"""
class TestDoc(Document):
x = IntField()
y = BooleanField()
TestDoc.drop_collection()
query = (Q(x__gt=0) | Q(x__exists=False))
query &= Q(x__lt=100)
self.assertEqual(query.to_query(TestDoc), {'$and': [
{'$or': [{'x': {'$gt': 0}},
{'x': {'$exists': False}}]},
{'x': {'$lt': 100}}]
})
q1 = (Q(x__gt=0) | Q(x__exists=False))
q2 = (Q(x__lt=100) | Q(y=True))
query = (q1 & q2).to_query(TestDoc)
TestDoc(x=101).save()
TestDoc(x=10).save()
TestDoc(y=True).save()
self.assertEqual(query, {
'$and': [
{'$or': [{'x': {'$gt': 0}}, {'x': {'$exists': False}}]},
{'$or': [{'x': {'$lt': 100}}, {'y': True}]}
]
})
self.assertEqual(2, TestDoc.objects(q1 & q2).count())
def test_or_and_or_combination(self):
"""Ensure that Q-objects handle ORing ANDed ORed components. :)
"""
class TestDoc(Document):
x = IntField()
y = BooleanField()
TestDoc.drop_collection()
TestDoc(x=-1, y=True).save()
TestDoc(x=101, y=True).save()
TestDoc(x=99, y=False).save()
TestDoc(x=101, y=False).save()
q1 = (Q(x__gt=0) & (Q(y=True) | Q(y__exists=False)))
q2 = (Q(x__lt=100) & (Q(y=False) | Q(y__exists=False)))
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(query, {
'$or': [
{'$and': [{'x': {'$gt': 0}},
{'$or': [{'y': True}, {'y': {'$exists': False}}]}]},
{'$and': [{'x': {'$lt': 100}},
{'$or': [{'y': False}, {'y': {'$exists': False}}]}]}
]
})
self.assertEqual(2, TestDoc.objects(q1 | q2).count())
def test_multiple_occurence_in_field(self):
class Test(Document):
name = StringField(max_length=40)
title = StringField(max_length=40)
q1 = Q(name__contains='te') | Q(title__contains='te')
q2 = Q(name__contains='12') | Q(title__contains='12')
q3 = q1 & q2
query = q3.to_query(Test)
self.assertEqual(query["$and"][0], q1.to_query(Test))
self.assertEqual(query["$and"][1], q2.to_query(Test))
def test_q_clone(self):
class TestDoc(Document):
x = IntField()
TestDoc.drop_collection()
for i in range(1, 101):
t = TestDoc(x=i)
t.save()
# Check normal cases work without an error
test = TestDoc.objects(Q(x__lt=7) & Q(x__gt=3))
self.assertEqual(test.count(), 3)
test2 = test.clone()
self.assertEqual(test2.count(), 3)
self.assertFalse(test2 == test)
test3 = test2.filter(x=6)
self.assertEqual(test3.count(), 1)
self.assertEqual(test.count(), 3)
def test_q(self):
"""Ensure that Q objects may be used to query for documents.
"""
class BlogPost(Document):
title = StringField()
publish_date = DateTimeField()
published = BooleanField()
BlogPost.drop_collection()
post1 = BlogPost(title='Test 1', publish_date=datetime.datetime(2010, 1, 8), published=False)
post1.save()
post2 = BlogPost(title='Test 2', publish_date=datetime.datetime(2010, 1, 15), published=True)
post2.save()
post3 = BlogPost(title='Test 3', published=True)
post3.save()
post4 = BlogPost(title='Test 4', publish_date=datetime.datetime(2010, 1, 8))
post4.save()
post5 = BlogPost(title='Test 1', publish_date=datetime.datetime(2010, 1, 15))
post5.save()
post6 = BlogPost(title='Test 1', published=False)
post6.save()
# Check ObjectId lookup works
obj = BlogPost.objects(id=post1.id).first()
self.assertEqual(obj, post1)
# Check Q object combination with one does not exist
q = BlogPost.objects(Q(title='Test 5') | Q(published=True))
posts = [post.id for post in q]
published_posts = (post2, post3)
self.assertTrue(all(obj.id in posts for obj in published_posts))
q = BlogPost.objects(Q(title='Test 1') | Q(published=True))
posts = [post.id for post in q]
published_posts = (post1, post2, post3, post5, post6)
self.assertTrue(all(obj.id in posts for obj in published_posts))
# Check Q object combination
date = datetime.datetime(2010, 1, 10)
q = BlogPost.objects(Q(publish_date__lte=date) | Q(published=True))
posts = [post.id for post in q]
published_posts = (post1, post2, post3, post4)
self.assertTrue(all(obj.id in posts for obj in published_posts))
self.assertFalse(any(obj.id in posts for obj in [post5, post6]))
BlogPost.drop_collection()
# Check the 'in' operator
self.Person(name='user1', age=20).save()
self.Person(name='user2', age=20).save()
self.Person(name='user3', age=30).save()
self.Person(name='user4', age=40).save()
self.assertEqual(self.Person.objects(Q(age__in=[20])).count(), 2)
self.assertEqual(self.Person.objects(Q(age__in=[20, 30])).count(), 3)
# Test invalid query objs
with self.assertRaises(InvalidQueryError):
self.Person.objects('user1')
# filter should fail, too
with self.assertRaises(InvalidQueryError):
self.Person.objects.filter('user1')
def test_q_regex(self):
"""Ensure that Q objects can be queried using regexes.
"""
person = self.Person(name='Guido van Rossum')
person.save()
obj = self.Person.objects(Q(name=re.compile('^Gui'))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name=re.compile('^gui'))).first()
self.assertEqual(obj, None)
obj = self.Person.objects(Q(name=re.compile('^gui', re.I))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name__not=re.compile('^bob'))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name__not=re.compile('^Gui'))).first()
self.assertEqual(obj, None)
def test_q_lists(self):
"""Ensure that Q objects query ListFields correctly.
"""
class BlogPost(Document):
tags = ListField(StringField())
BlogPost.drop_collection()
BlogPost(tags=['python', 'mongo']).save()
BlogPost(tags=['python']).save()
self.assertEqual(BlogPost.objects(Q(tags='mongo')).count(), 1)
self.assertEqual(BlogPost.objects(Q(tags='python')).count(), 2)
BlogPost.drop_collection()
def test_q_merge_queries_edge_case(self):
class User(Document):
email = EmailField(required=False)
name = StringField()
User.drop_collection()
pk = ObjectId()
User(email='example@example.com', pk=pk).save()
self.assertEqual(1, User.objects.filter(Q(email='example@example.com') |
Q(name='John Doe')).limit(2).filter(pk=pk).count())
def test_chained_q_or_filtering(self):
class Post(EmbeddedDocument):
name = StringField(required=True)
class Item(Document):
postables = ListField(EmbeddedDocumentField(Post))
Item.drop_collection()
Item(postables=[Post(name="a"), Post(name="b")]).save()
Item(postables=[Post(name="a"), Post(name="c")]).save()
Item(postables=[Post(name="a"), Post(name="b"), Post(name="c")]).save()
self.assertEqual(Item.objects(Q(postables__name="a") & Q(postables__name="b")).count(), 2)
self.assertEqual(Item.objects.filter(postables__name="a").filter(postables__name="b").count(), 2)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,98 +0,0 @@
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())

View File

@@ -1,13 +1,30 @@
import datetime
from pymongo.errors import OperationFailure
try:
import unittest2 as unittest
except ImportError:
import unittest
from nose.plugins.skip import SkipTest
import pymongo
import unittest
import mongoengine.connection
from bson.tz_util import utc
from mongoengine import *
from mongoengine.connection import get_db, get_connection, ConnectionError
from mongoengine import (
connect, register_connection,
Document, DateTimeField
)
from mongoengine.python_support import IS_PYMONGO_3
import mongoengine.connection
from mongoengine.connection import (MongoEngineConnectionError, get_db,
get_connection)
def get_tz_awareness(connection):
if not IS_PYMONGO_3:
return connection.tz_aware
else:
return connection.codec_options.tz_aware
class ConnectionTest(unittest.TestCase):
@@ -18,12 +35,11 @@ class ConnectionTest(unittest.TestCase):
mongoengine.connection._dbs = {}
def test_connect(self):
"""Ensure that the connect() method works properly.
"""
"""Ensure that the connect() method works properly."""
connect('mongoenginetest')
conn = get_connection()
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
@@ -31,11 +47,105 @@ class ConnectionTest(unittest.TestCase):
connect('mongoenginetest2', alias='testdb')
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
def test_connect_in_mocking(self):
"""Ensure that the connect() method works properly in mocking.
"""
try:
import mongomock
except ImportError:
raise SkipTest('you need mongomock installed to run this testcase')
connect('mongoenginetest', host='mongomock://localhost')
conn = get_connection()
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2')
conn = get_connection('testdb2')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3')
conn = get_connection('testdb3')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect('mongoenginetest4', is_mock=True, alias='testdb4')
conn = get_connection('testdb4')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5')
conn = get_connection('testdb5')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6')
conn = get_connection('testdb6')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7')
conn = get_connection('testdb7')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
def test_connect_with_host_list(self):
"""Ensure that the connect() method works when host is a list
Uses mongomock to test w/o needing multiple mongod/mongos processes
"""
try:
import mongomock
except ImportError:
raise SkipTest('you need mongomock installed to run this testcase')
connect(host=['mongomock://localhost'])
conn = get_connection()
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
conn = get_connection('testdb2')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host=['localhost'], is_mock=True, alias='testdb3')
conn = get_connection('testdb3')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host=['mongomock://localhost:27017', 'mongomock://localhost:27018'], alias='testdb4')
conn = get_connection('testdb4')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
conn = get_connection('testdb5')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
conn = get_connection('testdb6')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
def test_disconnect(self):
"""Ensure that the disconnect() method works properly
"""
conn1 = connect('mongoenginetest')
mongoengine.connection.disconnect()
conn2 = connect('mongoenginetest')
self.assertTrue(conn1 is not conn2)
def test_sharing_connections(self):
"""Ensure that connections are shared when the connection settings are exactly the same
"""
connect('mongoenginetests', alias='testdb1')
expected_connection = get_connection('testdb1')
connect('mongoenginetests', alias='testdb2')
actual_connection = get_connection('testdb2')
# Handle PyMongo 3+ Async Connection
if IS_PYMONGO_3:
# Ensure we are connected, throws ServerSelectionTimeoutError otherwise.
# Purposely not catching exception to fail test if thrown.
expected_connection.server_info()
self.assertEqual(expected_connection, actual_connection)
def test_connect_uri(self):
"""Ensure that the connect() method works properly with uri's
"""
"""Ensure that the connect() method works properly with URIs."""
c = connect(db='mongoenginetest', alias='admin')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
@@ -44,41 +154,202 @@ class ConnectionTest(unittest.TestCase):
c.admin.authenticate("admin", "password")
c.mongoenginetest.add_user("username", "password")
self.assertRaises(ConnectionError, connect, "testdb_uri_bad", host='mongodb://test:password@localhost')
if not IS_PYMONGO_3:
self.assertRaises(
MongoEngineConnectionError, connect, 'testdb_uri_bad',
host='mongodb://test:password@localhost'
)
connect("testdb_uri", host='mongodb://username:password@localhost/mongoenginetest')
conn = get_connection()
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
def test_connect_uri_without_db(self):
"""Ensure connect() method works properly if the URI doesn't
include a database name.
"""
connect("mongoenginetest", host='mongodb://localhost/')
conn = get_connection()
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest')
def test_connect_uri_default_db(self):
"""Ensure connect() defaults to the right database name if
the URI and the database_name don't explicitly specify it.
"""
connect(host='mongodb://localhost/')
conn = get_connection()
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
def test_uri_without_credentials_doesnt_override_conn_settings(self):
"""Ensure connect() uses the username & password params if the URI
doesn't explicitly specify them.
"""
c = connect(host='mongodb://localhost/mongoenginetest',
username='user',
password='pass')
# OperationFailure means that mongoengine attempted authentication
# w/ the provided username/password and failed - that's the desired
# behavior. If the MongoDB URI would override the credentials
self.assertRaises(OperationFailure, get_db)
def test_connect_uri_with_authsource(self):
"""Ensure that the connect() method works well with `authSource`
option in the URI.
"""
# Create users
c = connect('mongoenginetest')
c.admin.system.users.remove({})
c.admin.add_user('username2', 'password')
# Authentication fails without "authSource"
if IS_PYMONGO_3:
test_conn = connect(
'mongoenginetest', alias='test1',
host='mongodb://username2:password@localhost/mongoenginetest'
)
self.assertRaises(OperationFailure, test_conn.server_info)
else:
self.assertRaises(
MongoEngineConnectionError,
connect, 'mongoenginetest', alias='test1',
host='mongodb://username2:password@localhost/mongoenginetest'
)
self.assertRaises(MongoEngineConnectionError, get_db, 'test1')
# Authentication succeeds with "authSource"
authd_conn = connect(
'mongoenginetest', alias='test2',
host=('mongodb://username2:password@localhost/'
'mongoenginetest?authSource=admin')
)
db = get_db('test2')
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest')
# Clear all users
authd_conn.admin.system.users.remove({})
def test_register_connection(self):
"""Ensure that connections with different aliases may be registered.
"""
register_connection('testdb', 'mongoenginetest2')
self.assertRaises(ConnectionError, get_connection)
self.assertRaises(MongoEngineConnectionError, get_connection)
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
db = get_db('testdb')
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest2')
def test_connection_kwargs(self):
"""Ensure that connection kwargs get passed to pymongo.
def test_register_connection_defaults(self):
"""Ensure that defaults are used when the host and port are None.
"""
register_connection('testdb', 'mongoenginetest', host=None, port=None)
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
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)
self.assertTrue(get_tz_awareness(conn))
connect('mongoenginetest2', alias='t2')
conn = get_connection('t2')
self.assertFalse(conn.tz_aware)
self.assertFalse(get_tz_awareness(conn))
def test_connection_pool_via_kwarg(self):
"""Ensure we can specify a max connection pool size using
a connection kwarg.
"""
# Use "max_pool_size" or "maxpoolsize" depending on PyMongo version
# (former was changed to the latter as described in
# https://jira.mongodb.org/browse/PYTHON-854).
# TODO remove once PyMongo < 3.0 support is dropped
if pymongo.version_tuple[0] >= 3:
pool_size_kwargs = {'maxpoolsize': 100}
else:
pool_size_kwargs = {'max_pool_size': 100}
conn = connect('mongoenginetest', alias='max_pool_size_via_kwarg', **pool_size_kwargs)
self.assertEqual(conn.max_pool_size, 100)
def test_connection_pool_via_uri(self):
"""Ensure we can specify a max connection pool size using
an option in a connection URI.
"""
if pymongo.version_tuple[0] == 2 and pymongo.version_tuple[1] < 9:
raise SkipTest('maxpoolsize as a URI option is only supported in PyMongo v2.9+')
conn = connect(host='mongodb://localhost/test?maxpoolsize=100', alias='max_pool_size_via_uri')
self.assertEqual(conn.max_pool_size, 100)
def test_write_concern(self):
"""Ensure write concern can be specified in connect() via
a kwarg or as part of the connection URI.
"""
conn1 = connect(alias='conn1', host='mongodb://localhost/testing?w=1&j=true')
conn2 = connect('testing', alias='conn2', w=1, j=True)
if IS_PYMONGO_3:
self.assertEqual(conn1.write_concern.document, {'w': 1, 'j': True})
self.assertEqual(conn2.write_concern.document, {'w': 1, 'j': True})
else:
self.assertEqual(dict(conn1.write_concern), {'w': 1, 'j': True})
self.assertEqual(dict(conn2.write_concern), {'w': 1, 'j': True})
def test_connect_with_replicaset_via_uri(self):
"""Ensure connect() works when specifying a replicaSet via the
MongoDB URI.
"""
if IS_PYMONGO_3:
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
# "localhost:27017 is not a member of replica set local-rs"
with self.assertRaises(MongoEngineConnectionError):
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
def test_connect_with_replicaset_via_kwargs(self):
"""Ensure connect() works when specifying a replicaSet via the
connection kwargs
"""
if IS_PYMONGO_3:
c = connect(replicaset='local-rs')
self.assertEqual(c._MongoClient__options.replica_set_name,
'local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
# "localhost:27017 is not a member of replica set local-rs"
with self.assertRaises(MongoEngineConnectionError):
c = connect(replicaset='local-rs')
def test_datetime(self):
connect('mongoenginetest', tz_aware=True)
@@ -93,6 +364,27 @@ class ConnectionTest(unittest.TestCase):
date_doc = DateDoc.objects.first()
self.assertEqual(d, date_doc.the_date)
def test_multiple_connection_settings(self):
connect('mongoenginetest', alias='t1', host="localhost")
connect('mongoenginetest2', alias='t2', host="127.0.0.1")
mongo_connections = mongoengine.connection._connections
self.assertEqual(len(mongo_connections.items()), 2)
self.assertTrue('t1' in mongo_connections.keys())
self.assertTrue('t2' in mongo_connections.keys())
if not IS_PYMONGO_3:
self.assertEqual(mongo_connections['t1'].host, 'localhost')
self.assertEqual(mongo_connections['t2'].host, '127.0.0.1')
else:
# Handle PyMongo 3+ Async Connection
# Ensure we are connected, throws ServerSelectionTimeoutError otherwise.
# Purposely not catching exception to fail test if thrown.
mongo_connections['t1'].server_info()
mongo_connections['t2'].server_info()
self.assertEqual(mongo_connections['t1'].address[0], 'localhost')
self.assertEqual(mongo_connections['t2'].address[0], '127.0.0.1')
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,202 @@
import unittest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.context_managers import (switch_db, switch_collection,
no_sub_classes, no_dereference,
query_counter)
class ContextManagersTest(unittest.TestCase):
def test_switch_db_context_manager(self):
connect('mongoenginetest')
register_connection('testdb-1', 'mongoenginetest2')
class Group(Document):
name = StringField()
Group.drop_collection()
Group(name="hello - default").save()
self.assertEqual(1, Group.objects.count())
with switch_db(Group, 'testdb-1') as Group:
self.assertEqual(0, Group.objects.count())
Group(name="hello").save()
self.assertEqual(1, Group.objects.count())
Group.drop_collection()
self.assertEqual(0, Group.objects.count())
self.assertEqual(1, Group.objects.count())
def test_switch_collection_context_manager(self):
connect('mongoenginetest')
register_connection('testdb-1', 'mongoenginetest2')
class Group(Document):
name = StringField()
Group.drop_collection()
with switch_collection(Group, 'group1') as Group:
Group.drop_collection()
Group(name="hello - group").save()
self.assertEqual(1, Group.objects.count())
with switch_collection(Group, 'group1') as Group:
self.assertEqual(0, Group.objects.count())
Group(name="hello - group1").save()
self.assertEqual(1, Group.objects.count())
Group.drop_collection()
self.assertEqual(0, Group.objects.count())
self.assertEqual(1, Group.objects.count())
def test_no_dereference_context_manager_object_id(self):
"""Ensure that DBRef items in ListFields aren't dereferenced.
"""
connect('mongoenginetest')
class User(Document):
name = StringField()
class Group(Document):
ref = ReferenceField(User, dbref=False)
generic = GenericReferenceField()
members = ListField(ReferenceField(User, dbref=False))
User.drop_collection()
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
user = User.objects.first()
Group(ref=user, members=User.objects, generic=user).save()
with no_dereference(Group) as NoDeRefGroup:
self.assertTrue(Group._fields['members']._auto_dereference)
self.assertFalse(NoDeRefGroup._fields['members']._auto_dereference)
with no_dereference(Group) as Group:
group = Group.objects.first()
self.assertTrue(all([not isinstance(m, User)
for m in group.members]))
self.assertFalse(isinstance(group.ref, User))
self.assertFalse(isinstance(group.generic, User))
self.assertTrue(all([isinstance(m, User)
for m in group.members]))
self.assertTrue(isinstance(group.ref, User))
self.assertTrue(isinstance(group.generic, User))
def test_no_dereference_context_manager_dbref(self):
"""Ensure that DBRef items in ListFields aren't dereferenced.
"""
connect('mongoenginetest')
class User(Document):
name = StringField()
class Group(Document):
ref = ReferenceField(User, dbref=True)
generic = GenericReferenceField()
members = ListField(ReferenceField(User, dbref=True))
User.drop_collection()
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
user = User.objects.first()
Group(ref=user, members=User.objects, generic=user).save()
with no_dereference(Group) as NoDeRefGroup:
self.assertTrue(Group._fields['members']._auto_dereference)
self.assertFalse(NoDeRefGroup._fields['members']._auto_dereference)
with no_dereference(Group) as Group:
group = Group.objects.first()
self.assertTrue(all([not isinstance(m, User)
for m in group.members]))
self.assertFalse(isinstance(group.ref, User))
self.assertFalse(isinstance(group.generic, User))
self.assertTrue(all([isinstance(m, User)
for m in group.members]))
self.assertTrue(isinstance(group.ref, User))
self.assertTrue(isinstance(group.generic, User))
def test_no_sub_classes(self):
class A(Document):
x = IntField()
y = IntField()
meta = {'allow_inheritance': True}
class B(A):
z = IntField()
class C(B):
zz = IntField()
A.drop_collection()
A(x=10, y=20).save()
A(x=15, y=30).save()
B(x=20, y=40).save()
B(x=30, y=50).save()
C(x=40, y=60).save()
self.assertEqual(A.objects.count(), 5)
self.assertEqual(B.objects.count(), 3)
self.assertEqual(C.objects.count(), 1)
with no_sub_classes(A) as A:
self.assertEqual(A.objects.count(), 2)
for obj in A.objects:
self.assertEqual(obj.__class__, A)
with no_sub_classes(B) as B:
self.assertEqual(B.objects.count(), 2)
for obj in B.objects:
self.assertEqual(obj.__class__, B)
with no_sub_classes(C) as C:
self.assertEqual(C.objects.count(), 1)
for obj in C.objects:
self.assertEqual(obj.__class__, C)
# Confirm context manager exit correctly
self.assertEqual(A.objects.count(), 5)
self.assertEqual(B.objects.count(), 3)
self.assertEqual(C.objects.count(), 1)
def test_query_counter(self):
connect('mongoenginetest')
db = get_db()
db.test.find({})
with query_counter() as q:
self.assertEqual(0, q)
for i in range(1, 51):
db.test.find({}).count()
self.assertEqual(50, q)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,119 @@
import unittest
from mongoengine.base.datastructures import StrictDict, SemiStrictDict
class TestStrictDict(unittest.TestCase):
def strict_dict_class(self, *args, **kwargs):
return StrictDict.create(*args, **kwargs)
def setUp(self):
self.dtype = self.strict_dict_class(("a", "b", "c"))
def test_init(self):
d = self.dtype(a=1, b=1, c=1)
self.assertEqual((d.a, d.b, d.c), (1, 1, 1))
def test_repr(self):
d = self.dtype(a=1, b=2, c=3)
self.assertEqual(repr(d), '{"a": 1, "b": 2, "c": 3}')
# make sure quotes are escaped properly
d = self.dtype(a='"', b="'", c="")
self.assertEqual(repr(d), '{"a": \'"\', "b": "\'", "c": \'\'}')
def test_init_fails_on_nonexisting_attrs(self):
with self.assertRaises(AttributeError):
self.dtype(a=1, b=2, d=3)
def test_eq(self):
d = self.dtype(a=1, b=1, c=1)
dd = self.dtype(a=1, b=1, c=1)
e = self.dtype(a=1, b=1, c=3)
f = self.dtype(a=1, b=1)
g = self.strict_dict_class(("a", "b", "c", "d"))(a=1, b=1, c=1, d=1)
h = self.strict_dict_class(("a", "c", "b"))(a=1, b=1, c=1)
i = self.strict_dict_class(("a", "c", "b"))(a=1, b=1, c=2)
self.assertEqual(d, dd)
self.assertNotEqual(d, e)
self.assertNotEqual(d, f)
self.assertNotEqual(d, g)
self.assertNotEqual(f, d)
self.assertEqual(d, h)
self.assertNotEqual(d, i)
def test_setattr_getattr(self):
d = self.dtype()
d.a = 1
self.assertEqual(d.a, 1)
self.assertRaises(AttributeError, getattr, d, 'b')
def test_setattr_raises_on_nonexisting_attr(self):
d = self.dtype()
with self.assertRaises(AttributeError):
d.x = 1
def test_setattr_getattr_special(self):
d = self.strict_dict_class(["items"])
d.items = 1
self.assertEqual(d.items, 1)
def test_get(self):
d = self.dtype(a=1)
self.assertEqual(d.get('a'), 1)
self.assertEqual(d.get('b', 'bla'), 'bla')
def test_items(self):
d = self.dtype(a=1)
self.assertEqual(d.items(), [('a', 1)])
d = self.dtype(a=1, b=2)
self.assertEqual(d.items(), [('a', 1), ('b', 2)])
def test_mappings_protocol(self):
d = self.dtype(a=1, b=2)
assert dict(d) == {'a': 1, 'b': 2}
assert dict(**d) == {'a': 1, 'b': 2}
class TestSemiSrictDict(TestStrictDict):
def strict_dict_class(self, *args, **kwargs):
return SemiStrictDict.create(*args, **kwargs)
def test_init_fails_on_nonexisting_attrs(self):
# disable irrelevant test
pass
def test_setattr_raises_on_nonexisting_attr(self):
# disable irrelevant test
pass
def test_setattr_getattr_nonexisting_attr_succeeds(self):
d = self.dtype()
d.x = 1
self.assertEqual(d.x, 1)
def test_init_succeeds_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
self.assertEqual((d.a, d.b, d.c, d.x), (1, 1, 1, 2))
def test_iter_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
self.assertEqual(list(d), ['a', 'b', 'c', 'x'])
def test_iteritems_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
self.assertEqual(list(d.iteritems()), [('a', 1), ('b', 1), ('c', 1), ('x', 2)])
def tets_cmp_with_strict_dicts(self):
d = self.dtype(a=1, b=1, c=1)
dd = StrictDict.create(("a", "b", "c"))(a=1, b=1, c=1)
self.assertEqual(d, dd)
def test_cmp_with_strict_dict_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
dd = StrictDict.create(("a", "b", "c", "x"))(a=1, b=1, c=1, x=2)
self.assertEqual(d, dd)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,18 +1,22 @@
from __future__ import with_statement
# -*- coding: utf-8 -*-
import unittest
from bson import DBRef, ObjectId
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.tests import query_counter
from mongoengine.context_managers import query_counter
class FieldTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
@classmethod
def setUpClass(cls):
cls.db = connect(db='mongoenginetest')
@classmethod
def tearDownClass(cls):
cls.db.drop_database('mongoenginetest')
def test_list_item_dereference(self):
"""Ensure that DBRef items in ListFields are dereferenced.
@@ -26,7 +30,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
user = User(name='user %s' % i)
user.save()
@@ -84,7 +88,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
user = User(name='user %s' % i)
user.save()
@@ -123,6 +127,27 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
def test_list_item_dereference_dref_false_stores_as_type(self):
"""Ensure that DBRef items are stored as their type
"""
class User(Document):
my_id = IntField(primary_key=True)
name = StringField()
class Group(Document):
members = ListField(ReferenceField(User, dbref=False))
User.drop_collection()
Group.drop_collection()
user = User(my_id=1, name='user 1').save()
Group(members=User.objects).save()
group = Group.objects.first()
self.assertEqual(Group._get_collection().find_one()['members'], [1])
self.assertEqual(group.members, [user])
def test_handle_old_style_references(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
@@ -135,7 +160,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 26):
for i in range(1, 26):
user = User(name='user %s' % i)
user.save()
@@ -177,6 +202,10 @@ class FieldTest(unittest.TestCase):
raw_data = Group._get_collection().find_one()
self.assertTrue(isinstance(raw_data['author'], DBRef))
self.assertTrue(isinstance(raw_data['members'][0], DBRef))
group = Group.objects.first()
self.assertEqual(group.author, user)
self.assertEqual(group.members, [user])
# Migrate the model definition
class Group(Document):
@@ -185,8 +214,9 @@ class FieldTest(unittest.TestCase):
# Migrate the data
for g in Group.objects():
g.author = g.author
g.members = g.members
# Explicitly mark as changed so resets
g._mark_as_changed('author')
g._mark_as_changed('members')
g.save()
group = Group.objects.first()
@@ -263,9 +293,38 @@ class FieldTest(unittest.TestCase):
self.assertEqual(employee.friends, friends)
self.assertEqual(q, 2)
def test_list_of_lists_of_references(self):
class User(Document):
name = StringField()
class Post(Document):
user_lists = ListField(ListField(ReferenceField(User)))
class SimpleList(Document):
users = ListField(ReferenceField(User))
User.drop_collection()
Post.drop_collection()
SimpleList.drop_collection()
u1 = User.objects.create(name='u1')
u2 = User.objects.create(name='u2')
u3 = User.objects.create(name='u3')
SimpleList.objects.create(users=[u1, u2, u3])
self.assertEqual(SimpleList.objects.all()[0].users, [u1, u2, u3])
Post.objects.create(user_lists=[[u1, u2], [u3]])
self.assertEqual(Post.objects.all()[0].user_lists, [[u1, u2], [u3]])
def test_circular_reference(self):
"""Ensure you can handle circular references
"""
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
class Person(Document):
name = StringField()
relations = ListField(EmbeddedDocumentField('Relation'))
@@ -273,10 +332,6 @@ class FieldTest(unittest.TestCase):
def __repr__(self):
return "<Person: %s>" % self.name
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
Person.drop_collection()
mother = Person(name="Mother")
daughter = Person(name="Daughter")
@@ -337,14 +392,10 @@ class FieldTest(unittest.TestCase):
return "<Person: %s>" % self.name
Person.drop_collection()
paul = Person(name="Paul")
paul.save()
maria = Person(name="Maria")
maria.save()
julia = Person(name='Julia')
julia.save()
anna = Person(name='Anna')
anna.save()
paul = Person(name="Paul").save()
maria = Person(name="Maria").save()
julia = Person(name='Julia').save()
anna = Person(name='Anna').save()
paul.other.friends = [maria, julia, anna]
paul.other.name = "Paul's friends"
@@ -387,7 +438,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -478,7 +529,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -561,15 +612,15 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
user = User(name='user %s' % i)
user.save()
members.append(user)
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -634,7 +685,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -646,9 +697,9 @@ class FieldTest(unittest.TestCase):
members += [a, b, c]
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -730,16 +781,16 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
members += [a]
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -813,7 +864,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -825,9 +876,9 @@ class FieldTest(unittest.TestCase):
members += [a, b, c]
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -899,6 +950,8 @@ class FieldTest(unittest.TestCase):
class Asset(Document):
name = StringField(max_length=250, required=True)
path = StringField()
title = StringField()
parent = GenericReferenceField(default=None)
parents = ListField(GenericReferenceField())
children = ListField(GenericReferenceField())
@@ -976,6 +1029,43 @@ class FieldTest(unittest.TestCase):
self.assertEqual(type(foo.bar), Bar)
self.assertEqual(type(foo.baz), Baz)
def test_document_reload_reference_integrity(self):
"""
Ensure reloading a document with multiple similar id
in different collections doesn't mix them.
"""
class Topic(Document):
id = IntField(primary_key=True)
class User(Document):
id = IntField(primary_key=True)
name = StringField()
class Message(Document):
id = IntField(primary_key=True)
topic = ReferenceField(Topic)
author = ReferenceField(User)
Topic.drop_collection()
User.drop_collection()
Message.drop_collection()
# All objects share the same id, but each in a different collection
topic = Topic(id=1).save()
user = User(id=1, name='user-name').save()
Message(id=1, topic=topic, author=user).save()
concurrent_change_user = User.objects.get(id=1)
concurrent_change_user.name = 'new-name'
concurrent_change_user.save()
self.assertNotEqual(user.name, 'new-name')
msg = Message.objects.get(id=1)
msg.reload()
self.assertEqual(msg.topic, topic)
self.assertEqual(msg.author, user)
self.assertEqual(msg.author.name, 'new-name')
def test_list_lookup_not_checked_in_map(self):
"""Ensure we dereference list data correctly
"""
@@ -997,3 +1087,205 @@ class FieldTest(unittest.TestCase):
msg = Message.objects.get(id=1)
self.assertEqual(0, msg.comments[0].id)
self.assertEqual(1, msg.comments[1].id)
def test_list_item_dereference_dref_false_save_doesnt_cause_extra_queries(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
class Group(Document):
name = StringField()
members = ListField(ReferenceField(User, dbref=False))
User.drop_collection()
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
Group(name="Test", members=User.objects).save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
group_obj.name = "new test"
group_obj.save()
self.assertEqual(q, 2)
def test_list_item_dereference_dref_true_save_doesnt_cause_extra_queries(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
class Group(Document):
name = StringField()
members = ListField(ReferenceField(User, dbref=True))
User.drop_collection()
Group.drop_collection()
for i in range(1, 51):
User(name='user %s' % i).save()
Group(name="Test", members=User.objects).save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
group_obj.name = "new test"
group_obj.save()
self.assertEqual(q, 2)
def test_generic_reference_save_doesnt_cause_extra_queries(self):
class UserA(Document):
name = StringField()
class UserB(Document):
name = StringField()
class UserC(Document):
name = StringField()
class Group(Document):
name = StringField()
members = ListField(GenericReferenceField())
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
members = []
for i in range(1, 51):
a = UserA(name='User A %s' % i).save()
b = UserB(name='User B %s' % i).save()
c = UserC(name='User C %s' % i).save()
members += [a, b, c]
Group(name="test", members=members).save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
group_obj.name = "new test"
group_obj.save()
self.assertEqual(q, 2)
def test_objectid_reference_across_databases(self):
# mongoenginetest - Is default connection alias from setUp()
# Register Aliases
register_connection('testdb-1', 'mongoenginetest2')
class User(Document):
name = StringField()
meta = {"db_alias": "testdb-1"}
class Book(Document):
name = StringField()
author = ReferenceField(User)
# Drops
User.drop_collection()
Book.drop_collection()
user = User(name="Ross").save()
Book(name="MongoEngine for pros", author=user).save()
# Can't use query_counter across databases - so test the _data object
book = Book.objects.first()
self.assertFalse(isinstance(book._data['author'], User))
book.select_related()
self.assertTrue(isinstance(book._data['author'], User))
def test_non_ascii_pk(self):
"""
Ensure that dbref conversion to string does not fail when
non-ascii characters are used in primary key
"""
class Brand(Document):
title = StringField(max_length=255, primary_key=True)
class BrandGroup(Document):
title = StringField(max_length=255, primary_key=True)
brands = ListField(ReferenceField("Brand", dbref=True))
Brand.drop_collection()
BrandGroup.drop_collection()
brand1 = Brand(title="Moschino").save()
brand2 = Brand(title=u"Денис Симачёв").save()
BrandGroup(title="top_brands", brands=[brand1, brand2]).save()
brand_groups = BrandGroup.objects().all()
self.assertEqual(2, len([brand for bg in brand_groups for brand in bg.brands]))
def test_dereferencing_embedded_listfield_referencefield(self):
class Tag(Document):
meta = {'collection': 'tags'}
name = StringField()
class Post(EmbeddedDocument):
body = StringField()
tags = ListField(ReferenceField("Tag", dbref=True))
class Page(Document):
meta = {'collection': 'pages'}
tags = ListField(ReferenceField("Tag", dbref=True))
posts = ListField(EmbeddedDocumentField(Post))
Tag.drop_collection()
Page.drop_collection()
tag = Tag(name='test').save()
post = Post(body='test body', tags=[tag])
Page(tags=[tag], posts=[post]).save()
page = Page.objects.first()
self.assertEqual(page.tags[0], page.posts[0].tags[0])
def test_select_related_follows_embedded_referencefields(self):
class Song(Document):
title = StringField()
class PlaylistItem(EmbeddedDocument):
song = ReferenceField("Song")
class Playlist(Document):
items = ListField(EmbeddedDocumentField("PlaylistItem"))
Playlist.drop_collection()
Song.drop_collection()
songs = [Song.objects.create(title="song %d" % i) for i in range(3)]
items = [PlaylistItem(song=song) for song in songs]
playlist = Playlist.objects.create(items=items)
with query_counter() as q:
self.assertEqual(q, 0)
playlist = Playlist.objects.first().select_related()
songs = [item.song for item in playlist.items]
self.assertEqual(q, 2)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,122 +0,0 @@
from __future__ import with_statement
import unittest
from nose.plugins.skip import SkipTest
from mongoengine.python_support import PY3
from mongoengine import *
try:
from mongoengine.django.shortcuts import get_document_or_404
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
class QuerySetTest(unittest.TestCase):
def setUp(self):
if PY3:
raise SkipTest('django does not have Python 3 support')
connect(db='mongoenginetest')
class Person(Document):
name = StringField()
age = IntField()
self.Person = Person
def test_order_by_in_django_template(self):
"""Ensure that QuerySets are properly ordered in Django template.
"""
self.Person.drop_collection()
self.Person(name="A", age=20).save()
self.Person(name="D", age=10).save()
self.Person(name="B", age=40).save()
self.Person(name="C", age=30).save()
t = Template("{% for o in ol %}{{ o.name }}-{{ o.age }}:{% endfor %}")
d = {"ol": self.Person.objects.order_by('-name')}
self.assertEqual(t.render(Context(d)), u'D-10:C-30:B-40:A-20:')
d = {"ol": self.Person.objects.order_by('+name')}
self.assertEqual(t.render(Context(d)), u'A-20:B-40:C-30:D-10:')
d = {"ol": self.Person.objects.order_by('-age')}
self.assertEqual(t.render(Context(d)), u'B-40:C-30:A-20:D-10:')
d = {"ol": self.Person.objects.order_by('+age')}
self.assertEqual(t.render(Context(d)), u'D-10:A-20:C-30:B-40:')
self.Person.drop_collection()
def test_q_object_filter_in_template(self):
self.Person.drop_collection()
self.Person(name="A", age=20).save()
self.Person(name="D", age=10).save()
self.Person(name="B", age=40).save()
self.Person(name="C", age=30).save()
t = Template("{% for o in ol %}{{ o.name }}-{{ o.age }}:{% endfor %}")
d = {"ol": self.Person.objects.filter(Q(age=10) | Q(name="C"))}
self.assertEqual(t.render(Context(d)), 'D-10:C-30:')
# Check double rendering doesn't throw an error
self.assertEqual(t.render(Context(d)), 'D-10:C-30:')
def test_get_document_or_404(self):
p = self.Person(name="G404")
p.save()
self.assertRaises(Http404, get_document_or_404, self.Person, pk='1234')
self.assertEqual(p, get_document_or_404(self.Person, pk=p.pk))
def test_pagination(self):
"""Ensure that Pagination works as expected
"""
class Page(Document):
name = StringField()
Page.drop_collection()
for i in xrange(1, 11):
Page(name=str(i)).save()
paginator = Paginator(Page.objects.all(), 2)
t = Template("{% for i in page.object_list %}{{ i.name }}:{% endfor %}")
for p in paginator.page_range:
d = {"page": paginator.page(p)}
end = p * 2
start = end - 1
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

View File

@@ -1,533 +0,0 @@
import unittest
from mongoengine import *
from mongoengine.connection import get_db
class DynamicDocTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class Person(DynamicDocument):
name = StringField()
meta = {'allow_inheritance': True}
Person.drop_collection()
self.Person = Person
def test_simple_dynamic_document(self):
"""Ensures simple dynamic documents are saved correctly"""
p = self.Person()
p.name = "James"
p.age = 34
self.assertEqual(p.to_mongo(),
{"_types": ["Person"], "_cls": "Person",
"name": "James", "age": 34}
)
p.save()
self.assertEqual(self.Person.objects.first().age, 34)
# Confirm no changes to self.Person
self.assertFalse(hasattr(self.Person, 'age'))
def test_dynamic_document_delta(self):
"""Ensures simple dynamic documents can delta correctly"""
p = self.Person(name="James", age=34)
self.assertEqual(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {}))
p.doc = 123
del(p.doc)
self.assertEqual(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {'doc': 1}))
def test_change_scope_of_variable(self):
"""Test changing the scope of a dynamic field has no adverse effects"""
p = self.Person()
p.name = "Dean"
p.misc = 22
p.save()
p = self.Person.objects.get()
p.misc = {'hello': 'world'}
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {'hello': 'world'})
def test_delete_dynamic_field(self):
"""Test deleting a dynamic field works"""
self.Person.drop_collection()
p = self.Person()
p.name = "Dean"
p.misc = 22
p.save()
p = self.Person.objects.get()
p.misc = {'hello': 'world'}
p.save()
p = self.Person.objects.get()
self.assertEqual(p.misc, {'hello': 'world'})
collection = self.db[self.Person._get_collection_name()]
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', '_types', 'misc', 'name'])
del(p.misc)
p.save()
p = self.Person.objects.get()
self.assertFalse(hasattr(p, 'misc'))
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', '_types', 'name'])
def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields"""
p = self.Person()
p.name = "Dean"
p.age = 22
p.save()
self.assertEqual(1, self.Person.objects(age=22).count())
p = self.Person.objects(age=22)
p = p.get()
self.assertEqual(22, p.age)
def test_complex_dynamic_document_queries(self):
class Person(DynamicDocument):
name = StringField()
Person.drop_collection()
p = Person(name="test")
p.age = "ten"
p.save()
p1 = Person(name="test1")
p1.age = "less then ten and a half"
p1.save()
p2 = Person(name="test2")
p2.age = 10
p2.save()
self.assertEqual(Person.objects(age__icontains='ten').count(), 2)
self.assertEqual(Person.objects(age__gte=10).count(), 1)
def test_complex_data_lookups(self):
"""Ensure you can query dynamic document dynamic fields"""
p = self.Person()
p.misc = {'hello': 'world'}
p.save()
self.assertEqual(1, self.Person.objects(misc__hello='world').count())
def test_inheritance(self):
"""Ensure that dynamic document plays nice with inheritance"""
class Employee(self.Person):
salary = IntField()
Employee.drop_collection()
self.assertTrue('name' in Employee._fields)
self.assertTrue('salary' in Employee._fields)
self.assertEqual(Employee._get_collection_name(),
self.Person._get_collection_name())
joe_bloggs = Employee()
joe_bloggs.name = "Joe Bloggs"
joe_bloggs.salary = 10
joe_bloggs.age = 20
joe_bloggs.save()
self.assertEqual(1, self.Person.objects(age=20).count())
self.assertEqual(1, Employee.objects(age=20).count())
joe_bloggs = self.Person.objects.first()
self.assertTrue(isinstance(joe_bloggs, Employee))
def test_embedded_dynamic_document(self):
"""Test dynamic embedded documents"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field = embedded_1
self.assertEqual(doc.to_mongo(), {"_types": ['Doc'], "_cls": "Doc",
"embedded_field": {
"_types": ['Embedded'], "_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2, {'hello': 'world'}]
}
})
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(doc.embedded_field.list_field, ['1', 2, {'hello': 'world'}])
def test_complex_embedded_documents(self):
"""Test complex dynamic embedded documents setups"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_2 = Embedded()
embedded_2.string_field = 'hello'
embedded_2.int_field = 1
embedded_2.dict_field = {'hello': 'world'}
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
embedded_1.list_field = ['1', 2, embedded_2]
doc.embedded_field = embedded_1
self.assertEqual(doc.to_mongo(), {"_types": ['Doc'], "_cls": "Doc",
"embedded_field": {
"_types": ['Embedded'], "_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2,
{"_types": ['Embedded'], "_cls": "Embedded",
"string_field": "hello",
"int_field": 1,
"dict_field": {"hello": "world"},
"list_field": ['1', 2, {'hello': 'world'}]}
]
}
})
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc.embedded_field.__class__, Embedded)
self.assertEqual(doc.embedded_field.string_field, "hello")
self.assertEqual(doc.embedded_field.int_field, 1)
self.assertEqual(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(doc.embedded_field.list_field[0], '1')
self.assertEqual(doc.embedded_field.list_field[1], 2)
embedded_field = doc.embedded_field.list_field[2]
self.assertEqual(embedded_field.__class__, Embedded)
self.assertEqual(embedded_field.string_field, "hello")
self.assertEqual(embedded_field.int_field, 1)
self.assertEqual(embedded_field.dict_field, {'hello': 'world'})
self.assertEqual(embedded_field.list_field, ['1', 2, {'hello': 'world'}])
def test_delta_for_dynamic_documents(self):
p = self.Person()
p.name = "Dean"
p.age = 22
p.save()
p.age = 24
self.assertEqual(p.age, 24)
self.assertEqual(p._get_changed_fields(), ['age'])
self.assertEqual(p._delta(), ({'age': 24}, {}))
p = self.Person.objects(age=22).get()
p.age = 24
self.assertEqual(p.age, 24)
self.assertEqual(p._get_changed_fields(), ['age'])
self.assertEqual(p._delta(), ({'age': 24}, {}))
p.save()
self.assertEqual(1, self.Person.objects(age=24).count())
def test_delta(self):
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
doc.string_field = 'hello'
self.assertEqual(doc._get_changed_fields(), ['string_field'])
self.assertEqual(doc._delta(), ({'string_field': 'hello'}, {}))
doc._changed_fields = []
doc.int_field = 1
self.assertEqual(doc._get_changed_fields(), ['int_field'])
self.assertEqual(doc._delta(), ({'int_field': 1}, {}))
doc._changed_fields = []
dict_value = {'hello': 'world', 'ping': 'pong'}
doc.dict_field = dict_value
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
self.assertEqual(doc._delta(), ({'dict_field': dict_value}, {}))
doc._changed_fields = []
list_value = ['1', 2, {'hello': 'world'}]
doc.list_field = list_value
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({'list_field': list_value}, {}))
# Test unsetting
doc._changed_fields = []
doc.dict_field = {}
self.assertEqual(doc._get_changed_fields(), ['dict_field'])
self.assertEqual(doc._delta(), ({}, {'dict_field': 1}))
doc._changed_fields = []
doc.list_field = []
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({}, {'list_field': 1}))
def test_delta_recursive(self):
"""Testing deltaing works with dynamic documents"""
class Embedded(DynamicEmbeddedDocument):
pass
class Doc(DynamicDocument):
pass
Doc.drop_collection()
doc = Doc()
doc.save()
doc = Doc.objects.first()
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._delta(), ({}, {}))
embedded_1 = Embedded()
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
embedded_1.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field = embedded_1
self.assertEqual(doc._get_changed_fields(), ['embedded_field'])
embedded_delta = {
'string_field': 'hello',
'int_field': 1,
'dict_field': {'hello': 'world'},
'list_field': ['1', 2, {'hello': 'world'}]
}
self.assertEqual(doc.embedded_field._delta(), (embedded_delta, {}))
embedded_delta.update({
'_types': ['Embedded'],
'_cls': 'Embedded',
})
self.assertEqual(doc._delta(), ({'embedded_field': embedded_delta}, {}))
doc.save()
doc.reload()
doc.embedded_field.dict_field = {}
self.assertEqual(doc._get_changed_fields(), ['embedded_field.dict_field'])
self.assertEqual(doc.embedded_field._delta(), ({}, {'dict_field': 1}))
self.assertEqual(doc._delta(), ({}, {'embedded_field.dict_field': 1}))
doc.save()
doc.reload()
doc.embedded_field.list_field = []
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field'])
self.assertEqual(doc.embedded_field._delta(), ({}, {'list_field': 1}))
self.assertEqual(doc._delta(), ({}, {'embedded_field.list_field': 1}))
doc.save()
doc.reload()
embedded_2 = Embedded()
embedded_2.string_field = 'hello'
embedded_2.int_field = 1
embedded_2.dict_field = {'hello': 'world'}
embedded_2.list_field = ['1', 2, {'hello': 'world'}]
doc.embedded_field.list_field = ['1', 2, embedded_2]
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field'])
self.assertEqual(doc.embedded_field._delta(), ({
'list_field': ['1', 2, {
'_cls': 'Embedded',
'_types': ['Embedded'],
'string_field': 'hello',
'dict_field': {'hello': 'world'},
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
}]
}, {}))
self.assertEqual(doc._delta(), ({
'embedded_field.list_field': ['1', 2, {
'_cls': 'Embedded',
'_types': ['Embedded'],
'string_field': 'hello',
'dict_field': {'hello': 'world'},
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
}]
}, {}))
doc.save()
doc.reload()
self.assertEqual(doc.embedded_field.list_field[2]._changed_fields, [])
self.assertEqual(doc.embedded_field.list_field[0], '1')
self.assertEqual(doc.embedded_field.list_field[1], 2)
for k in doc.embedded_field.list_field[2]._fields:
self.assertEqual(doc.embedded_field.list_field[2][k], embedded_2[k])
doc.embedded_field.list_field[2].string_field = 'world'
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field.2.string_field'])
self.assertEqual(doc.embedded_field._delta(), ({'list_field.2.string_field': 'world'}, {}))
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.string_field': 'world'}, {}))
doc.save()
doc.reload()
self.assertEqual(doc.embedded_field.list_field[2].string_field, 'world')
# Test multiple assignments
doc.embedded_field.list_field[2].string_field = 'hello world'
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
self.assertEqual(doc._get_changed_fields(), ['embedded_field.list_field'])
self.assertEqual(doc.embedded_field._delta(), ({
'list_field': ['1', 2, {
'_types': ['Embedded'],
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}]}, {}))
self.assertEqual(doc._delta(), ({
'embedded_field.list_field': ['1', 2, {
'_types': ['Embedded'],
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}
]}, {}))
doc.save()
doc.reload()
self.assertEqual(doc.embedded_field.list_field[2].string_field, 'hello world')
# Test list native methods
doc.embedded_field.list_field[2].list_field.pop(0)
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}]}, {}))
doc.save()
doc.reload()
doc.embedded_field.list_field[2].list_field.append(1)
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}, 1]}, {}))
doc.save()
doc.reload()
self.assertEqual(doc.embedded_field.list_field[2].list_field, [2, {'hello': 'world'}, 1])
doc.embedded_field.list_field[2].list_field.sort(key=str)# use str as a key to allow comparing uncomperable types
doc.save()
doc.reload()
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'])
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2.list_field': [1, 2, {}]}, {}))
doc.save()
doc.reload()
del(doc.embedded_field.list_field[2].list_field)
self.assertEqual(doc._delta(), ({}, {'embedded_field.list_field.2.list_field': 1}))
doc.save()
doc.reload()
doc.dict_field = {'embedded': embedded_1}
doc.save()
doc.reload()
doc.dict_field['embedded'].string_field = 'Hello World'
self.assertEqual(doc._get_changed_fields(), ['dict_field.embedded.string_field'])
self.assertEqual(doc._delta(), ({'dict_field.embedded.string_field': 'Hello World'}, {}))
def test_indexes(self):
"""Ensure that indexes are used when meta[indexes] is specified.
"""
class BlogPost(DynamicDocument):
meta = {
'indexes': [
'-date',
('category', '-date')
],
}
BlogPost.drop_collection()
info = BlogPost.objects._collection.index_information()
# _id, '-date', ('cat', 'date')
# NB: there is no index on _types by itself, since
# the indices on -date and tags will both contain
# _types as first element in the key
self.assertEqual(len(info), 3)
# Indexes are lazy so use list() to perform query
list(BlogPost.objects)
info = BlogPost.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertTrue([('_types', 1), ('category', 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)

Some files were not shown because too many files have changed in this diff Show More