Compare commits

...

847 Commits

Author SHA1 Message Date
Ross Lawley
5f4b70f3a9 Version bump 2012-04-18 10:30:14 +01:00
Ross Lawley
51b429e5b0 Updated changelog 2012-04-18 10:28:54 +01:00
Ross Lawley
360624eb6e Merge branch 'dev' of github.com:hmarr/mongoengine into dev 2012-04-18 10:28:06 +01:00
Ross Lawley
d9d2291837 Merge branch 'master' into dev 2012-04-18 10:27:57 +01:00
Ross Lawley
cbdf816232 Merge branch 'master' of github.com:hmarr/mongoengine 2012-04-18 10:23:02 +01:00
Ross Lawley
2d71eb8a18 Added support back for Django 1.3 as well as 1.4 2012-04-18 10:22:26 +01:00
Ross Lawley
64d2532ce9 Merge pull request #484 from dcrosta/replica-set-connection
refactor get_connection

Thanks @dcrosta
2012-04-18 00:51:30 -07:00
Dan Crosta
0376910f33 refactor get_connection
In the previous version, the requested ReadPreference was ignored in the
case that the user specified a MongoDB URI. This rearranges the code to
ensure that only those values which we explicitly parse out of the URI
override values set as keyword arguments.

This leaves open the possibility of conflicts between the URI and the
kwargs -- we should consider whether to raise an exception if, e.g.,
username is specified as a kwarg *and* in the URI.
2012-04-17 19:50:22 -04:00
Ross Lawley
6d503119a1 Merge pull request #482 from wpjunior/patch-7
Small fixes for ReferenceField
2012-04-16 05:25:42 -07:00
Wilson Júnior
bfae93e57e small fixes for ReferenceField 2012-04-13 04:56:20 -03:00
Ross Lawley
d0e42a4798 Merge branch 'master' into dev 2012-03-27 01:47:48 +01:00
Ross Lawley
2a34358abc Updated connection refs #474 2012-03-27 01:47:17 +01:00
Ross Lawley
fd2bb8ea45 Merge pull request #474 from samuelclay/patch-1
The port is defaulted in to conn_settings, so discard the port since hos...
2012-03-26 17:17:26 -07:00
Ross Lawley
98e5daa0e0 Added mostlystatic to the AUTHORS 2012-03-27 00:49:34 +01:00
Samuel Clay
ad2e119282 The port is defaulted in to conn_settings, so discard the port since hosts_or_uri must be used. 2012-03-26 16:48:37 -07:00
Ross Lawley
c20c30d8d1 Merge pull request #471 from mostlystatic/master
Simple fix to unknown connection alias error message
2012-03-26 16:47:46 -07:00
mostlystatic
66d215c9c1 Fix for unknown connection alias error message. 2012-03-24 20:01:40 +00:00
Ross Lawley
46e088d379 Merge branch 'master' into dev 2012-03-24 19:07:17 +00:00
Ross Lawley
bbdd15161a 0.6.3 release 2012-03-24 19:06:08 +00:00
Ross Lawley
ea9dc8cfb8 Merge branch 'master' of github.com:hmarr/mongoengine 2012-03-24 19:03:44 +00:00
Ross Lawley
6bd2ccc9bf UPdated authors 2012-03-24 19:03:24 +00:00
Ross Lawley
56327c6b58 Merge pull request #470 from arbaal/master
Patches for Django 1.4 compability
2012-03-24 12:02:44 -07:00
Nils Hasenbanck
712e8a51e4 Merge branch 'master' of github.com:arbaal/mongoengine
Conflicts:
	mongoengine/django/sessions.py

Signed-off-by: Nils Hasenbanck <nils@hasenbanck.de>
2012-03-24 19:49:02 +01:00
Nils Hasenbanck
421f324f9e Fixed the exception when saving a new session
The session was not created for some reason. Now it is.

Signed-off-by: Nils Hasenbanck <nils@hasenbanck.de>
2012-03-24 19:43:01 +01:00
Nils Hasenbanck
8fe4a70299 Fixed the exception when saving a new session
Signed-off-by: Nils Hasenbanck <nils@hasenbanck.de>
2012-03-24 19:24:42 +01:00
Nils Hasenbanck
3af6d0dbfd Replaces deprecated hasher with new django 1.4 hasher
This way we can even use the new hasher configuration
django 1.4 provides.

Signed-off-by: Nils Hasenbanck <nils@hasenbanck.de>
2012-03-24 11:08:00 +01:00
Nils Hasenbanck
e2bef076d3 Fixed the session backend for django 1.4
Signed-off-by: Nils Hasenbanck <nils@hasenbanck.de>
2012-03-24 11:07:37 +01:00
Ross Lawley
1bf9f28f4b Merge branch 'master' into dev 2012-03-22 15:49:57 +00:00
Ross Lawley
f1e7b97a93 Updated changelog 2012-03-22 15:48:54 +00:00
Ross Lawley
8cfe13ad90 Merge branch 'master' into dev 2012-03-22 15:46:32 +00:00
Ross Lawley
0f420abc8e Added test for listfields containing embedded documents
Added Adam to the authors - thanks for the patch
fixes #466
2012-03-22 15:44:22 +00:00
Ross Lawley
3b5b715567 Merge branch 'dev' of https://github.com/aparrish/mongoengine into 466 2012-03-22 15:32:45 +00:00
Adam Parrish
520051af25 preparing values in a ListField won't mangle embedded documents any more 2012-03-21 11:03:49 -07:00
Ross Lawley
fd18a48608 Rst fix 2012-03-15 16:36:04 +00:00
Ross Lawley
64860c6287 Fix signals documentation 2012-03-15 16:27:31 +00:00
Ross Lawley
58635b24ba Updated changelog 2012-03-12 10:35:02 +00:00
Ross Lawley
3ec9dfc108 Merge branch 'master' into dev 2012-03-12 10:33:08 +00:00
Ross Lawley
bd1572f11a Fixed upgrade docs and instructions 2012-03-12 10:31:51 +00:00
Ross Lawley
95c58bd793 Merge branch 'master' into dev 2012-03-08 12:40:20 +00:00
Ross Lawley
65591c7727 Version Bump 2012-03-08 12:40:07 +00:00
Ross Lawley
737cbf5f60 Updated docs and added fix for _types and positional operator
Bumped version to 0.6.2
2012-03-08 12:39:25 +00:00
Ross Lawley
4c67cbb4b7 Updated upgrade docs. Reported issues with older pymongo and sharding 2012-03-06 12:31:14 +00:00
Ross Lawley
ed2cc2a60b Adding Jacob Peddicord to Authors 2012-03-05 16:20:37 +00:00
Ross Lawley
859e9b3cc4 Version bump 2012-03-05 16:16:37 +00:00
Ross Lawley
c34e79fad9 Fix replicaset connection 2012-03-05 16:15:06 +00:00
Ross Lawley
82446d641e Updated the spec 2012-03-05 14:28:33 +00:00
Ross Lawley
9451c9f331 Updated Readme - points to readthedocs 2012-03-05 13:14:36 +00:00
Ross Lawley
61411bb259 Doc updates 2012-03-05 12:26:44 +00:00
Ross Lawley
fcdb0eff8f Added for read the docs 2012-03-05 12:21:53 +00:00
Ross Lawley
30d9347272 Updated readme 2012-03-05 12:20:59 +00:00
Ross Lawley
7564bbdee8 Fix docs 2012-03-05 11:43:54 +00:00
Ross Lawley
69251e5000 Updated docs 2012-03-05 11:35:12 +00:00
Ross Lawley
6ecdc7b59d Added FutureWarning for inherited classes not declaring allow_inheritance
Refs #437
2012-03-05 11:25:13 +00:00
Ross Lawley
b7d0d8f0cc Added warning to SortedListField 2012-03-05 11:20:22 +00:00
Ross Lawley
df52ed1162 Merge pull request #449 from kajic/uri-fix
Uri connection fix
2012-03-05 00:23:18 -08:00
Ross Lawley
aa6370dd5d Merge pull request #448 from kajic/delta-fix
Thanks Kajic
2012-03-05 00:23:04 -08:00
Robert Kajic
c272b7901f Fix for bug where changes to a a embedded document field are not recorded if the root document was just created+saved. 2012-03-02 15:35:15 +01:00
Robert Kajic
c61de6540a Don't ignore kwargs for uri style connections 2012-03-02 15:33:54 +01:00
Robert Kajic
3c7bf50089 Make uri style connections use parameters not specified in the uri, as well as other keyword arguments 2012-03-02 15:33:38 +01:00
Ross Lawley
32fc4152a7 Enable covered indexes for simple documents.
Refs #444
2012-03-02 13:42:24 +00:00
Ross Lawley
bdf7187d5c Added a limit to .get 2012-02-29 14:57:24 +00:00
Ross Lawley
1639576203 Bumped version 2012-02-29 12:05:47 +00:00
Ross Lawley
ae20c785ea Updated docs 2012-02-29 12:04:43 +00:00
Ross Lawley
a2eb876f8c No longer always upsert on save
closes #407
2012-02-29 11:39:10 +00:00
Ross Lawley
5a1eaa0a98 Updated new deref test 2012-02-29 11:23:43 +00:00
Ross Lawley
398fd4a548 Merge branch 'cleaned_dev' of https://github.com/Ankhbayar/mongoengine into test 2012-02-29 11:13:48 +00:00
Ross Lawley
44b9fb66e1 Updates must have an operation
Closes #387
2012-02-29 11:04:09 +00:00
Ross Lawley
2afa2171f9 Updated Changelog 2012-02-29 10:32:58 +00:00
Ross Lawley
1d7ea71c0d DeReference is now used in a thread safe manner
No global / module instance is needed
Fixes #399
2012-02-29 10:31:33 +00:00
Ross Lawley
2a391f0f16 Raise an error if trying to perform a join
You can't join across reference fields, so raise an error
if someone tries to.
2012-02-29 10:10:51 +00:00
Ross Lawley
e9b8093dac Updated changelog 2012-02-29 10:09:16 +00:00
Ross Lawley
6a229cfbc5 Updates can now take raw queries 2012-02-24 15:48:32 +00:00
Ross Lawley
3300f409ba Update Authors and changelist 2012-02-24 10:34:51 +00:00
Ross Lawley
4466005363 Merge branch 'dev' of github.com:hmarr/mongoengine into dev 2012-02-24 10:29:06 +00:00
Ross Lawley
296ef5bddf Merge branch 'geo2d' into dev 2012-02-24 10:28:52 +00:00
Ross Lawley
1f2a432e82 Merge pull request #439 from kajic/dev
register_connection bugfix
2012-02-24 02:12:55 -08:00
Robert Kajic
855933ab2a uri_dict will have the 'database' key even if the database wasn't present in the uri. We must check it's value as well. 2012-02-24 00:07:57 +01:00
Ross Lawley
ece8d25187 Added replicaset connection support
Provide replicaSet=NAME in your connection.
fixes #423
2012-02-17 17:09:48 +00:00
Ross Lawley
589a720162 Updated changelog 2012-02-17 17:09:14 +00:00
Ross Lawley
a59b518cf2 Updates to imports for future pymongo 2.2 2012-02-17 11:18:25 +00:00
Ross Lawley
a15352a4f8 Merge branch 'dev' of github.com:hmarr/mongoengine into dev 2012-02-17 09:41:41 +00:00
Ross Lawley
df65f3fc3f base no longer expects meta to have allow_inheritance
Closes #430 #431
2012-02-17 09:41:01 +00:00
Robert Kajic
734986c1b5 Documentation on geospatial indexes and how to create them explicitly 2012-02-16 10:41:47 +01:00
Анхбаяр Лхагвадорж
4a9ed5f2f2 Fix derefcence failed some case. 2012-02-02 18:33:12 +08:00
Ross Lawley
088f229865 Merge pull request #421 from linuxnow/integration/dev
Integration/dev
2012-02-01 04:05:06 -08:00
Pau Aliagas
cb2cb851e2 Fix formatting typo in changelog entry 2012-02-01 12:29:05 +01:00
Robert Kajic
d3962c4f7d Added support for creating a geo2d index by prefixing the field name with a * 2012-01-31 22:31:24 +01:00
Ross Lawley
0301135f96 Added uri style connection handling 2012-01-30 10:24:45 +00:00
Ross Lawley
f59aa922ea Added more .scalar tests 2012-01-27 12:20:47 +00:00
Ross Lawley
f60a49d6f6 Added .scalar to Queryset
More efficient than the previous .values_list implementation Ref #393
Reverted some of the .values_list code thats no longer needed.

Closes #415
2012-01-27 11:45:12 +00:00
Alice Bevan-McGregor
9a190eb00d Added ability to have scalar return values instead of partially-populated Document instances. 2012-01-27 11:45:11 +00:00
Ross Lawley
6bad4bd415 Merge pull request #412 from faulkner/dict-update
Add dict.update() support to BaseDict.

Thx Faulkner :)
2012-01-16 03:32:45 -08:00
Chris Faulkner
50d9b0b796 Add dict.update() support to BaseDict. 2012-01-16 19:13:03 +08:00
Ross Lawley
12f884e3ac Fixes typo in documents - thanks Shalabh
Closes #406
2012-01-16 09:11:00 +00:00
Ross Lawley
02b1aa7355 Added Ashwin Purohit to authors
Refs #410
2012-01-16 09:06:18 +00:00
Ross Lawley
90bfa608dd Merge branch 'master' into dev
Conflicts:
	docs/guide/signals.rst
	python-mongoengine.spec
2012-01-16 09:05:38 +00:00
Ross Lawley
13f38b1c1d Merge pull request #410 from purohit/master
typo fix
2012-01-16 01:04:29 -08:00
Ross Lawley
1afe7240f4 Fixed pagination limit / skip bug
fixes #398
2012-01-16 09:03:12 +00:00
Ashwin Purohit
7a41155178 typo in signals guide 2012-01-14 23:21:43 -08:00
Ross Lawley
39a20ea471 Merge pull request #403 from wpjunior/patch-5
Fixes for None values in QuerySet.values_list
2012-01-06 01:17:27 -08:00
Wilson Júnior
d8855a4a0f fixes for None values in QuerySet.values_list 2012-01-05 13:35:32 -02:00
Ross Lawley
de8da78042 Update docs/changelog.rst 2012-01-03 20:42:24 +00:00
Ross Lawley
318b42dff2 Merge pull request #393 from wpjunior/queryset_select
select method in Queryset
2012-01-03 12:40:49 -08:00
Ross Lawley
0018674b62 Update docs/changelog.rst 2012-01-03 20:37:33 +00:00
Ross Lawley
82913e8d69 Merge pull request #396 from wpjunior/mixin_inheritance
Mixin inheritance
2012-01-03 12:36:45 -08:00
Wilson Júnior
0d867a108d mixin inheritance 2011-12-19 11:31:42 -02:00
Wilson Júnior
5ee4b4a5ac added count/len for ListResult 2011-12-16 11:49:20 -02:00
Wilson Júnior
62219d9648 changed name 2011-12-16 11:07:38 -02:00
Ross Lawley
6d9bfff19c Started work on performance
Added an initial benchmark.py
Much more performant than 0.5.2 but still work todo.
2011-12-16 12:41:47 +00:00
Ross Lawley
7614b92197 Fixes super in BaseDict
Closes #395
2011-12-15 09:16:35 +00:00
Wilson Júnior
7c1afd0031 tests for db_field 2011-12-13 11:56:35 -02:00
Wilson Júnior
ca7b2371fb added support for dereferences 2011-12-13 11:54:19 -02:00
Wilson Júnior
ed5fba6b0f support for embedded fields 2011-12-13 07:46:49 -02:00
Ross Lawley
2b3b3bf652 Prelim PyPy support
Refs: #392
2011-12-12 16:26:10 +00:00
Ross Lawley
11daf706df Added Sharding support
Added shard_key meta, so save() and update() passes shard keys
to the pymongo query.  Also made shard key fields immutable.

Closes #388 and #389
2011-12-12 16:13:19 +00:00
Wilson Júnior
4a269eb2c4 added .select method 2011-12-12 13:39:37 -02:00
Ross Lawley
9b3899476c Allow arbitary kwargs to be passed to pymongo
Fix pymongo 2.1+ check
Closes #390 closes #378
2011-12-12 09:14:50 +00:00
Ross Lawley
febb3d7e3d Updated connection - so handles < pymongo 2.1
Updated docs

Refs #378
2011-12-09 08:39:50 -08:00
Ross Lawley
83e3c5c7d8 Updated connection for pymongo 2.1 support
closes #378
2011-12-09 08:26:39 -08:00
Ross Lawley
3c271845c9 Merge pull request #386 from wpjunior/patch-4
Custom db_alias support for MongoEngine DjangoSession
2011-12-07 07:56:43 -08:00
Wilson Júnior
56c4292164 added custom db_alias support for MongoEngine DjangoSession 2011-12-07 13:36:35 -02:00
Ross Lawley
2531ade3bb Added David to Authors
refs #380
2011-12-07 03:01:56 -08:00
Ross Lawley
3e2f035400 Abstract documents can now declare indexes
fixes #380
2011-12-07 02:15:50 -08:00
Ross Lawley
e7bcb5e366 Updated docs and Authors list re: db_alias 2011-12-07 01:46:11 -08:00
Ross Lawley
112e921ce2 Syntax cleaning 2011-12-07 01:34:36 -08:00
Ross Lawley
216f15602b Fixing test 2011-12-07 01:17:35 -08:00
Ross Lawley
fbe1901e65 Added some tests #384 2011-12-07 01:16:45 -08:00
Ross Lawley
8d2bc444bb db_alias using in model, queryset, reference fields, derefrence. 2011-12-07 01:16:36 -08:00
Ross Lawley
cf4a45da11 Dynamic Documents now support string query lookups 2011-12-06 06:38:25 -08:00
Ross Lawley
be78209f94 Added test showing you can add index for dynamic docs 2011-12-05 04:44:40 -08:00
Ross Lawley
45b5bf73fe Added Jan to the contributors list
Refs #135 #381
2011-12-05 04:17:51 -08:00
Ross Lawley
84f9e44b6c Fixed GridFS documents can now be pickled
Refs #135 #381
2011-12-05 04:16:57 -08:00
Ross Lawley
700bc1b4bb Multiple fields with the same db_field now raises Exception
Closes #329
2011-12-02 08:44:15 -08:00
Ross Lawley
beef2ede25 Merge pull request #313 from linuxnow/integration/master
add .gitignore and spec file to master
2011-12-02 08:15:24 -08:00
Ross Lawley
9bfc838029 Updated Docs and bumped version
Hopefully nearer 0.6
closes #368
2011-12-02 08:14:25 -08:00
Ross Lawley
e9d7353294 Updated with_id to raise Error if used with a filter.
Closes #365
2011-12-02 07:11:06 -08:00
Ross Lawley
a6948771d8 Added ReferencField handling with .distinct()
Closes #356
2011-12-02 06:47:58 -08:00
Ross Lawley
403977cd49 Added test for saving references unnecessarily.
Refs #359
2011-12-02 06:40:57 -08:00
Ross Lawley
153538cef9 Added test for saving false on dynamic documents
Refs #282
Closes #311
2011-12-02 06:34:51 -08:00
Ross Lawley
9f1196e982 Merge branch 'dev' of github.com:hmarr/mongoengine into dev 2011-12-02 06:04:12 -08:00
Ross Lawley
6419a8d09a Fixed False BooleanField marked as unset by _delta()
Closes #282
2011-12-02 06:03:15 -08:00
Ross Lawley
769cee3d64 Merge pull request #379 from wpjunior/patch-3
Small improvements for item_frequencies
2011-12-02 05:19:49 -08:00
Wilson Júnior
fc460b775e Small improvements for item_frequencies 2011-12-02 09:46:51 -02:00
Ross Lawley
ba59e498de Custom __instancecheck__ no longer needed
Would be needed if calling a classmethod in __new__
but as we dont support reverse_delete_rules on embedded
documents there is no longer the need for it.

Refs #227
2011-12-02 02:52:06 -08:00
Ross Lawley
939bd2bb1f Updated Documentation 2011-12-02 02:49:16 -08:00
Ross Lawley
e231f71b4a EmbeddedDocuments dont support Reverse Delete Rules
Now throws an InvalidDocumentError

Refs #227
2011-12-02 02:46:55 -08:00
Ross Lawley
d06c5f036b Cleaned up _transform_query
Refs #354 #376
2011-12-02 00:37:32 -08:00
Ross Lawley
071562d755 Fixed issue with dynamic documents deltas
Closes #377
2011-12-02 00:11:25 -08:00
Ross Lawley
391f659af1 Updated docs re: reverse delete rules
refs #254
2011-12-01 08:16:13 -08:00
Ross Lawley
8a44232bfc Added Reverse Delete Rule support to ListFields
DictFields and MapFields aren't supported and raise an
InvalidDocument Error

Closes #254
2011-12-01 07:57:24 -08:00
Ross Lawley
9188f9bf62 Added custom cascade kwarg options
Allows the user to overwrite any default kwargs

Closes #295
2011-11-30 08:54:33 -08:00
Ross Lawley
0187a0e113 Handle updating and getting None values
Fixes updating a field to None, so it works in a similar
fashion as unsetting it via save()

Updated to handle null data from the database

Fixes #362
2011-11-30 08:12:44 -08:00
Ross Lawley
beacfae400 Removed use of _get_subclasses favouring get_document
_get_subclasses not actually required and causes issues
where Base Classes aren't imported but dont actually
need to be.

Fixes #271
2011-11-30 07:55:33 -08:00
Ross Lawley
fdc385ea33 Allow dynamic data to be deleted
Fixes #374
2011-11-30 03:06:46 -08:00
Ross Lawley
8b97808931 Added docs for elemMatch 2011-11-30 02:30:29 -08:00
Ross Lawley
179c4a10c8 Merge branch 'elemmatch' of https://github.com/wpjunior/mongoengine into elemmatch 2011-11-30 02:26:54 -08:00
Ross Lawley
6cef571bfb Added Reverse option to SortedLists
Thanks Stephen Young for the patch
closes #364
2011-11-30 02:15:47 -08:00
Ross Lawley
fbe8b28b2e Merge branch 'bug/318' into dev
Conflicts:
	AUTHORS
2011-11-29 03:46:11 -08:00
Ross Lawley
a8d91a56bf Fixes circular list references
The depth deduciton for _fields was over zealous
now max_depth is honoured/

Fixes #373
2011-11-29 03:43:49 -08:00
Ross Lawley
8d7291506e Updated Authors 2011-11-29 01:44:23 -08:00
Wilson Júnior
d9005ac2fc added elemMatch support 2011-11-28 14:45:57 -02:00
Ross Lawley
c775c0a80c Circular references with EmbeddedDocumentField fix
Fixes #345
2011-11-28 08:23:28 -08:00
Ross Lawley
700e2cd93d Updated changelog 2011-11-28 08:16:36 -08:00
Ross Lawley
083f00be84 Fixes passed in Constructor data for complexfields
Fixes #355
2011-11-28 08:09:17 -08:00
Ross Lawley
d00859ecfd Updated changelog - DictField fix 2011-11-28 07:07:26 -08:00
Ross Lawley
4e73566c11 Updated changelog - optional cascasde saves 2011-11-28 07:06:56 -08:00
Ross Lawley
208a467b24 Added dictfield check for Int keys
Fixes #371
2011-11-28 07:05:54 -08:00
Ross Lawley
e1bb453f32 Configurable cascading saves
Updated cascading save logic - can now add meta or pass
cascade to save().  Also Cleaned up reset changed fields logic
as well, so less looping

Refs: #370 #349
2011-11-28 06:51:17 -08:00
Ross Lawley
4607b08be5 Making BaseDict / List more robust 2011-11-28 06:35:19 -08:00
Ross Lawley
aa5c776f3d Copy and paste == brainless 2011-11-28 06:21:45 -08:00
Ross Lawley
0075c0a1e8 Gracefully handle when self.observer is absent
After pickles / deepcopying etc..
2011-11-28 05:54:03 -08:00
Ross Lawley
83fff80b0f Cleaned up dereferencing
Dereferencing now respects max_depth, so should be more performant.
Reload is chainable and can be passed a max_depth for dereferencing
Added an Observer for ComplexBaseFields.

Refs #324 #323 #289
Closes #320
2011-11-25 08:36:47 -08:00
Ross Lawley
5e553ffaf7 Added reconnect back into the syntax
forces a disconnect.
2011-11-24 00:59:43 -08:00
Ross Lawley
6d185b7f7a Merge pull request #366 from wpjunior/filemultidb
db_alias support for FileFields
2011-11-22 12:17:35 -08:00
Ross Lawley
e80144e9f2 Added multidb support
No change required to upgrade to multiple databases. Aliases are used
to describe the database and these can be manually registered or fall
through to a default alias using connect.

Made get_connection and get_db first class members of the connection class.
Old style _get_connection and _get_db still supported.

Refs: #84 #87 #93 #215
2011-11-22 08:01:14 -08:00
Wilson Júnior
fa4b820931 added support for db_alias in FileFields 2011-11-22 13:40:01 -02:00
Ross Lawley
63c5a4dd65 Fixes saving document schemas that have changed
Ensures that form defaults which are documents are
automatically marked as changed, so schemas can evolve
without migration issues.

[#360]
2011-11-22 07:34:08 -08:00
Adam Parrish
34646a414c Fixes bug using positional operator to update embedded documents.
append_field wasn't getting reset to True in the loop, so fields wouldn't
get appended to clean_fields after str was encountered

[#354]
2011-11-11 01:10:00 -08:00
Ross Lawley
5aeee9deb2 Added PIL to spec file
[#314]
2011-11-04 01:55:46 -07:00
Ross Lawley
4c1509a62a Updated docs re choices
[#284] [#314]
2011-11-04 01:54:30 -07:00
Ross Lawley
bfdaae944d Merge branch 'dev' of github.com:hmarr/mongoengine into dev 2011-11-04 01:46:45 -07:00
Ross Lawley
4e44198bbd Clean up of choices code and added tests
[#284] [#314]
2011-11-04 01:45:44 -07:00
Ross Lawley
a4e8177b76 Merge branch 'master' of https://github.com/KarimAllah/mongoengine into choices
Conflicts:
	mongoengine/base.py
2011-11-04 01:34:58 -07:00
Ross Lawley
81bf5cb78b Merge pull request #348 from wpjunior/patch-2
fixes for __repr__ documents
2011-11-02 04:54:08 -07:00
Wilson Júnior
a9fc476fb8 fixed errors in repr if unicode string is found 2011-11-02 09:38:26 -02:00
Ross Lawley
26f0c06624 Merge branch 'get_or_create_optizations' of https://github.com/wpjunior/mongoengine into get_or_create_optimizations 2011-11-01 02:21:37 -07:00
Ross Lawley
59bd72a888 Added tests for __repr__ fix 2011-11-01 02:15:31 -07:00
Ross Lawley
7d808b483e Merge branch 'master' of https://github.com/grubberr/mongoengine into slice 2011-11-01 02:14:44 -07:00
Ross Lawley
3ee60affa9 Renamed schema for errors
Now is `to_dict()` as is more explicit
[refs #344 #328]
2011-11-01 01:51:58 -07:00
Ross Lawley
558b8123b5 Merge branch 'validation-schema' of https://github.com/n1k0/mongoengine into validation-schema
Conflicts:
	mongoengine/base.py
	mongoengine/fields.py
2011-11-01 01:45:32 -07:00
Ross Lawley
ecdf2ae5c7 Updated docs and Authors 2011-11-01 01:20:47 -07:00
Ross Lawley
aa9ed614ad Merge branch 'master' of https://github.com/mjtamlyn/mongoengine into transform_update 2011-11-01 01:19:30 -07:00
Sergey Chvalyuk
1acdb880fc fixing #336 2011-10-28 00:23:13 +03:00
Ross Lawley
7cd22aaf83 Removed debug print 2011-10-27 01:18:32 -07:00
Ross Lawley
5eb63cfa30 Updated changelog 2011-10-27 01:14:51 -07:00
Ross Lawley
5dc998ed52 Merge branch 'dev-bulkinsertsignal' of https://github.com/colinhowe/mongoengine into bulk 2011-10-27 01:13:59 -07:00
Wilson Júnior
8074094568 optimizations for get_or_create 2011-10-27 06:02:59 -02:00
Ross Lawley
56d1139d71 Added ImageField Support
Thanks to @wpjunior for the patch
Closes [#298]
2011-10-27 00:58:47 -07:00
Ross Lawley
165cdc8840 Updated changelog 2011-10-27 00:35:34 -07:00
Ross Lawley
c42aef74de Merge pull request #326 from wpjunior/fixes-325
Fixes for #325 issue - Thanks @wpjunior
2011-10-27 00:33:36 -07:00
Ross Lawley
634e1f661f Updated docs / upgrade notes 2011-10-27 00:31:48 -07:00
Nicolas Perriault
a1db437c42 got rid of assert for validation; ValidationError now extends AssertionError for BC purpose 2011-10-25 22:38:43 +02:00
Nicolas Perriault
b8e2bdc99f simpler raising of ValidatioError 2011-10-25 20:04:39 +02:00
Ross Lawley
52d4ea7d78 Merge pull request #316 from wpjunior/fixes-315
Fixes for #315 issue
2011-10-25 03:47:19 -07:00
Nicolas Perriault
7db5335420 fixed URLField.validate() wasn't using BaseField.error() to raise a ValidationError 2011-10-25 10:53:58 +02:00
Nicolas Perriault
62480fe940 added a ValidatorError.schema properties which contains a dict representation of the whole validation error schema 2011-10-24 17:15:34 +02:00
Nicolas Perriault
3d7b30da77 first version of BC validation schema 2011-10-24 01:02:31 +02:00
Wilson Júnior
8e87648d53 added tests for get_or_create 2011-10-19 09:44:49 -02:00
Wilson Júnior
f842c90007 Merge branches 'master' and 'fixes-325' into fixes-325 2011-10-19 06:57:39 -02:00
Wilson Júnior
7f2b686ab5 added drop_collection for test 2011-10-19 06:55:05 -02:00
Wilson Júnior
b09c52fc7e fixes for #325 issue 2011-10-19 06:30:41 -02:00
Pau Aliagas
202d6e414f Update spec file 2011-10-13 20:29:36 +02:00
Wilson Júnior
3d817f145c fixes for #315 issue 2011-10-12 18:28:40 -03:00
Pau Aliagas
181e191fee Add some more files to ignore in .gitignore 2011-10-12 11:00:40 +02:00
Pau Aliagas
79ecf027dd Add dependencies to spec file
Add spec file for rpm-based systems
2011-10-12 11:00:02 +02:00
Ross Lawley
76d771d20f Merge branch 'master' into dev
Conflicts:
	AUTHORS
	docs/changelog.rst
	mongoengine/base.py
2011-10-12 00:35:01 -07:00
Ross Lawley
4d5f602ee7 Bumped the version 2011-10-12 00:31:02 -07:00
Ross Lawley
452bbcc19b Ported fix for Circular Reference bug to Master
Ready for a 0.5.2 release
2011-10-12 00:30:12 -07:00
Ross Lawley
24b8650026 Merge remote branch 'origin/dev' into dev 2011-10-12 00:18:37 -07:00
Ross Lawley
269e6e29d6 Updated Authors 2011-10-12 00:18:12 -07:00
Ross Lawley
c4b0002ddb Fixed typo 2011-10-11 14:59:58 +02:00
Ross Lawley
53598781b8 Facepalm - mutable default argument in method.. 2011-10-11 12:44:41 +02:00
Ross Lawley
0624cdd6e4 Fixes collection creation post drop_collection
Thanks to Julien Rebetez for the original patch
closes [#285]
2011-10-11 02:26:33 -07:00
Ross Lawley
5fb9d61d28 Merge remote branch 'origin/dev' into dev 2011-10-11 00:15:23 -07:00
Ross Lawley
7b1860d17b Fixes tree based circular references
Thanks to jpfarias for the fix.
Also normalised the other circular checks.
2011-10-10 09:16:32 -07:00
Ross Lawley
8797565606 UPdated changelog 2011-10-08 08:13:53 -07:00
Ross Lawley
3d97c41fe9 Merge pull request #305 from linuxnow/integration/error_msgs
Add field name to validation exception messages
2011-10-08 08:13:40 -07:00
Ross Lawley
5edfeb2e29 Merge pull request #303 from linuxnow/integration/uuid
Add UUIDField
2011-10-08 08:08:45 -07:00
Ross Lawley
268908b3b2 Improvements to .get() efficiency
Closes #307 and #290
2011-10-08 08:06:23 -07:00
Ross Lawley
fb70b47acb Merge branch 'master' into dev
Conflicts:
	docs/changelog.rst
2011-10-08 07:33:52 -07:00
Ross Lawley
591149b1f0 Bumped version for 0.5.1 release 2011-10-08 07:31:24 -07:00
Ross Lawley
9a0a0b1bd4 Ported bugfix for circular references regression
Refs #294
2011-10-08 07:29:12 -07:00
Marc Tamlyn
219d316b49 Fix iteration on querysets.
If iteration of a queryset was interrupted (by a break, or a caught
error), the next iterator would start from the second element as the
cursor had already moved to the first. This is fixed by adding a rewind
into the __iter__ method.
2011-10-05 13:26:57 +01:00
Pau Aliagas
3aa2233b5d Add field name to exception messages 2011-10-04 18:35:32 +02:00
Pau Aliagas
d59862ae6e Merge remote-tracking branch 'upstream/dev' into integration/uuid 2011-10-04 16:02:58 +02:00
Pau Aliagas
0a03f9a31a Add unit tests for UUIDField 2011-10-04 15:59:56 +02:00
Ross Lawley
dca135190a Fixed changelog 2011-10-04 04:28:30 -07:00
Ross Lawley
aedcf3dc81 Merge branch 'listfield' into dev
Conflicts:
	docs/changelog.rst
2011-10-04 04:28:04 -07:00
Ross Lawley
6961a9494f Updates to ComplexFields
Required now means they cannot be empty [#302]
2011-10-04 04:26:56 -07:00
Ross Lawley
6d70ef1a08 Updated changelog [#304] 2011-10-04 03:18:39 -07:00
Ross Lawley
e1fc15875d Merge remote branch 'origin/dev' into dev 2011-10-04 03:10:10 -07:00
Ross Lawley
94ae1388b1 Updated .gitignore 2011-10-04 02:59:00 -07:00
Ross Lawley
17728d4e74 Added tests for empty lists 2011-10-04 02:57:50 -07:00
Ross Lawley
417aa743ca Merge pull request #304 from linuxnow/integration/specfile
Add spec file for rpm-based distributions
2011-10-04 02:27:05 -07:00
Pau Aliagas
2f26f7a827 Add dependencies to spec file
Add spec file for rpm-based systems
2011-10-04 10:33:26 +02:00
Pau Aliagas
09f9c59b3d Add some more files to ignore in .gitignore 2011-10-04 10:24:44 +02:00
Pau Aliagas
bec6805296 Add UUIDField 2011-10-04 10:20:41 +02:00
Pau Aliagas
d99c7c20cc Don't allow empty lists when they are required
When using ListField, an empty list is added as the default value.
But when you mark this field as required, you expect it not to be empty,
so this patch makes sure that this is duly checked.
2011-10-04 10:12:21 +02:00
Ross Lawley
60b6ad3fcf Added test for listfield fix
Added Pau Aliagas to authors
[closes #299]
2011-10-03 05:30:23 -07:00
Pau Aliagas
9b4d0f6450 Make sure that ListFields are not strings 2011-10-03 05:29:24 -07:00
Ross Lawley
1a2c74391c Added grubberr to AUTHORS
[Refs #296]
2011-10-03 05:18:25 -07:00
Sergey Chvalyuk
08288e591c small optimizing fix 2011-10-03 05:16:17 -07:00
Ross Lawley
823cf421fa Fixes to circular references.
Removes infinite looping
refs #294
2011-09-29 14:07:30 -07:00
Ross Lawley
3799f27734 Merge branch 'master' of https://github.com/kuno/mongoengine into kuno 2011-09-28 01:51:06 -07:00
Ross Lawley
a7edd8602c Added support for expando style dynamic documents.
Added two new classes: DynamicDocument and DynamicEmbeddedDocument
for handling expando style setting of attributes.

[closes #112]
2011-09-28 01:39:39 -07:00
Karim Allah
c081aca794 Fixing dereferencing when the dereferenced-document wasn't found. 2011-09-25 18:58:40 +02:00
kuno
2ca6648227 fixed indentation error in signal docs 2011-09-20 21:30:20 +08:00
Ross Lawley
1af54f93f5 Merge pull request #287 from wpjunior/fixitem_frequencies
Fix item_frequencies
2011-09-20 06:04:30 -07:00
Wilson Júnior
a9cacd2e06 fixed embedded null item_frequencies 2011-09-20 08:56:30 -03:00
Ross Lawley
f7fbb3d2f6 Relaxed field name checking on embedded documents 2011-09-20 03:45:11 -07:00
Karim Allah
adb7bbeea0 Being compatible with non-django style chioces 2011-09-18 19:48:33 +02:00
Ross Lawley
89c44cd14e Added missing fields to the api documentation 2011-09-11 08:09:16 +01:00
Ross Lawley
8105bfd8b3 Updated changelog for 0.5 release 2011-09-10 13:00:34 +01:00
Harry Marr
de5b678da3 Merge branch 'dev' 2011-09-10 11:47:31 +01:00
Harry Marr
66c53f949b Version bump to 0.5 2011-09-10 11:47:16 +01:00
Harry Marr
fdc34869ca Merge branch 'dev' 2011-09-10 11:43:10 +01:00
Harry Marr
88b1a29719 Typo fix 2011-09-10 11:54:43 +02:00
Colin Howe
b91db87ae0 Pre and post bulk-insert signals 2011-09-09 19:17:40 +01:00
Ross Lawley
050542c29b Added InvalidDocumentError
Ensures defined documents are valid and users don't override core methods
by accident.

fixes #275
2011-09-09 17:36:40 +01:00
Ross Lawley
60f0491f62 Updated changelog 2011-09-09 17:35:44 +01:00
Ross Lawley
b8a5791de6 Updates to documents
[#245]
2011-09-09 14:33:27 +01:00
Ross Lawley
2bc3948726 Merge remote branch 'wpjunior/genericembeddedfield' into genericembeddedfield 2011-09-09 06:07:07 -07:00
Ross Lawley
ee7d370751 Bumped the version 2011-09-09 05:52:43 -07:00
Ross Lawley
a6449a7b2c Updates to documentation in prep for 0.5 2011-09-09 05:45:56 -07:00
Ross Lawley
bc9a09f52e Document updates 2011-09-09 04:21:32 -07:00
Ross Lawley
1631788ab6 Now Raise an exception if subclasses are missing at querytime.
Beats returning None thanks to #aid for mentioning it on IRC
2011-08-24 13:37:39 +01:00
Ross Lawley
dd49d1d4bb Added choices note to upgrade docs 2011-08-24 13:37:20 +01:00
Ross Lawley
d6c54c7c2a Merge remote branch 'origin/dev' into dev 2011-08-18 08:32:33 +01:00
Ross Lawley
bda716ef9d Improved update in test case for removing inheritance 2011-08-18 08:30:52 +01:00
Ross Lawley
d83d226396 Merge pull request #265 from dcrosta/keeptemp
Update docs for removal of keep_temp
2011-08-17 13:49:55 -07:00
Ross Lawley
91a0e499d9 Updated changelog and authors
Refs #263
2011-08-17 21:48:41 +01:00
Ross Lawley
f549d8c0bc Merge pull request #263 from johnarnfield/dev
Added polygon support and unit tests
2011-08-17 13:46:40 -07:00
Dan Crosta
10c30f2224 remove keep_temp from map_reduce
fixes #258
2011-08-17 16:44:40 -04:00
Ross Lawley
11621c6f5a Removed keeptemp from map_reduce as 0.5 requires pymongo 1.11
Closes #258
2011-08-17 21:38:46 +01:00
Ross Lawley
97ac7e5476 Remove old pymongo version checks
Closes #264
2011-08-17 21:34:35 +01:00
John Arnfield
b037fb3e21 Added version check to the polygon test to ensure server version >= 1.9 2011-08-17 21:23:40 +01:00
John Arnfield
10bc93dfa6 Commas help too :) 2011-08-17 20:15:47 +01:00
John Arnfield
88cb8f3963 left some conflict markers in - oops 2011-08-17 20:14:24 +01:00
John Arnfield
bd005575c4 Added geospatial polygon lookups & tests 2011-08-17 20:11:05 +01:00
John Arnfield
ca3b004921 Added tests for polygon queries 2011-08-17 20:04:38 +01:00
Ross Lawley
8071b23bff Updated upgrade.rst 2011-08-17 14:17:06 +01:00
Ross Lawley
4bfed7e719 Merge pull request #259 from wpjunior/moretests
More tests for ListField(EmbeddedDocumentField
2011-08-17 06:14:52 -07:00
Wilson Júnior
b76590dc01 more tests for embedded lists 2011-08-17 09:32:04 -03:00
Wilson Júnior
4e462ffdb5 Merge branch 'dev' of git://github.com/hmarr/mongoengine into dev 2011-08-17 09:24:17 -03:00
Ross Lawley
3c8cbcfee7 Added tests for showing how to set embedded document indexes
refs #257
2011-08-16 16:50:50 +01:00
Ross Lawley
2a8543b3b7 Updated changelog 2011-08-16 15:26:11 +01:00
Ross Lawley
fd2e40d735 Updated changelog 2011-08-16 15:24:37 +01:00
Dan Crosta
5f05843403 prefer to use map-reduce to db.eval where possible 2011-08-16 08:20:06 -04:00
Ross Lawley
8bdb42827c Updated AUTHORS
Thanks to all those that have contributed to MongoEngine
2011-08-16 11:33:41 +01:00
Ross Lawley
f6961ae9c1 Merge remote branch 'origin/dev' into dev 2011-08-16 10:33:38 +01:00
Ross Lawley
3f301f6b0f Finishing touches to where implementation - thanks to dcrosta
Refs #242
2011-08-16 10:32:21 +01:00
Ross Lawley
89ad7ef1ab Merge branch 'dev' into feature/where 2011-08-16 09:54:56 +01:00
Dan Crosta
81b69648ef docstring for where() 2011-08-15 16:56:51 -04:00
Ross Lawley
672a5f190b Merge pull request #255 from colinhowe/dev
check for presence of _geo_indices on field class before referencing
2011-08-15 06:39:38 -07:00
Wilson Júnior
447dd62c03 Merge branch 'dev' of git://github.com/hmarr/mongoengine into dev 2011-08-15 08:06:33 -03:00
Ross Lawley
c4db3b6cf2 Merge pull request #247 from spantaleev/double-save-fix
Prevent double saving when doing a forced insert.
2011-08-15 02:28:59 -07:00
Ross Lawley
2b1eb620fc Merge pull request #253 from wpjunior/listreferences
Dereference fails (allow_inheritance = False)
2011-08-15 02:27:40 -07:00
Gareth Lloyd
4abfcb0188 check for presence of _geo_indices on field class before referencing 2011-08-15 10:01:48 +01:00
Ross Lawley
048826f6f0 Merge pull request #252 from wpjunior/djangotranslations
Django translation for auth.py
2011-08-15 01:15:28 -07:00
Wilson Júnior
5446476d99 Merge branch 'listreferences' of https://github.com/wpjunior/mongoengine into dev 2011-08-09 15:06:22 -03:00
Wilson Júnior
331f8b8ae7 fixes dereference for documents (allow_inheritance = False) 2011-08-09 14:31:26 -03:00
Wilson Júnior
63ee4fef1a Translations for django/auth.py 2011-08-05 11:03:47 -03:00
John Arnfield
376ca717fa Added support for within_polygon for spatial queries 2011-07-30 22:01:24 +01:00
Slavi Pantaleev
7913ed1841 Prevent double saving when doing a forced insert.
When doing save(force_insert=True) on a document missing an _id field,
the document was first getting inserted and then being saved a second
time.

Also refactatored the code a bit to make the intent
(insert/update/delta-update) cleaner, especially since
the `created` variable name was so confusing.
2011-07-30 00:52:37 +03:00
Ross Lawley
3f3f93b0fa Fixing delta bug for dict fields 2011-07-29 15:48:29 +01:00
Wilson Júnior
6471c6e133 added GenericEmbeddedDocumentField 2011-07-27 08:45:15 -03:00
Ross Lawley
e3cbeb9df0 Merge pull request #243 from wpjunior/sequencefield_fixes
Sequencefield fixes
2011-07-22 06:34:43 -07:00
Wilson Júnior
130fb9916d fixes for SequenceField 2011-07-22 10:19:41 -03:00
Ross Lawley
ac72722e57 Fixing bug setting a value that equates to false 2011-07-22 13:51:11 +01:00
Wilson Júnior
382b9a61a8 Merge branch 'dev' of git://github.com/hmarr/mongoengine into db_field_maps_tweaks 2011-07-22 09:25:59 -03:00
Dan Crosta
13afead9fb add where() method to QuerySet 2011-07-20 12:41:20 -04:00
Ross Lawley
72aa191e70 Stop abstract classes being used in the document_registry 2011-07-20 11:58:13 +01:00
Ross Lawley
0d1804461d Updated handling setting of object managers and inheritance 2011-07-19 22:12:09 +01:00
Wilson Júnior
273412fda1 tweaks for _db_field_map 2011-07-19 14:48:38 -03:00
Wilson Júnior
49764b51dc tweaks for _db_field_map 2011-07-19 14:43:32 -03:00
Ross Lawley
5834fa840c Tweaked SequenceField so that it doesn't increment on creation.
[refs #238]
2011-07-19 16:51:26 +01:00
Ross Lawley
5eb895b952 Merge remote branch 'origin/dev' into feature/sequence_field 2011-07-19 16:04:42 +01:00
Ross Lawley
d5fb3a9167 Merge pull request #240 from wpjunior/db_field_fixes
Awesome - scarily similar to the patch I started!
2011-07-19 07:37:57 -07:00
Wilson Júnior
cb324595ef fixerrors 2011-07-19 07:36:35 -03:00
Wilson Júnior
fa39789bac added SequenceField 2011-07-18 12:44:28 -03:00
Ross Lawley
bbd3a6961e Fixed typo in tutorial
[closes #235] Thanks @mulka
2011-07-18 08:35:29 +01:00
Ross Lawley
6eb0387a78 Merge pull request #234 from dcrosta/get-or-404
Get or 404 now handles validation errors as well.
Thanks @dcrosta
2011-07-14 22:38:46 -07:00
Dan Crosta
b3ef67a544 get_document_or_404 raises 404 if given an invalid ObjectId
(and possibly on other errors, not sure what else raises
ValidationError)
2011-07-14 18:43:11 -04:00
Ross Lawley
72995a4b3e Fixed changing default values to False for embedded items 2011-07-13 16:06:40 +01:00
Ross Lawley
7395ce5b22 Updating changelog 2011-07-13 16:05:17 +01:00
Ross Lawley
a4c197a83c Added update() convenience method to a document
Thanks to @dcrosta for the initial code
[closes #229]
2011-07-13 14:15:46 +01:00
Ross Lawley
7a3412dc13 Added helper for reseting the index cache 2011-07-13 09:54:41 +01:00
Ross Lawley
e079924632 Added extra test for update / update_one
[closes #231]
2011-07-12 14:43:21 +01:00
Leo Honkanen
7f0d3638ba guard against potentially destructive updates with no update parameters 2011-07-12 16:10:47 +03:00
Ross Lawley
cace665858 _delta checking didn't handle db_field_names at all
Fixed and added tests, thanks to @wpjunior and @iapain for initial test cases
[fixes #226]
2011-07-12 10:20:36 +01:00
Ross Lawley
2a8d001213 Improvements to indexes and efficiencies
Thanks to @dcrosta for the patches
closes #225
2011-07-11 17:02:23 +01:00
Ross Lawley
a2b0266e01 Merge branch 'dev' into indexpatches 2011-07-11 16:58:17 +01:00
Ross Lawley
1452d3fac5 Fixed item_frequency methods to handle null values
[fixes #216]
2011-07-11 16:50:31 +01:00
Ross Lawley
031c507fde Merge pull request #224 from dcrosta/user-unique-index
User unique index - thanks to @dcrosta
2011-07-11 08:08:52 -07:00
Ross Lawley
0fb629e24c Added cascading deletes
Also ensured that unsetting works when not the default value of a field
2011-07-11 16:01:48 +01:00
Dan Crosta
0847687fd1 don't create extra index on _types (fix #222)
mongodb will use an index that begins with _types to service queries
against _types, so the extra index is only needed if no other fields are
indexed in the document. to be safe, we explicitly check all indexes to
see if any begins with _types, and only then prevent creation of the
additional index on _types.
2011-07-11 10:15:55 -04:00
Dan Crosta
859de712b4 only create indexes on first collection access (fix #223) 2011-07-11 09:44:28 -04:00
Dan Crosta
803164a993 add unique index on User.username 2011-07-11 08:38:43 -04:00
Ross Lawley
147e33c3ca Merge remote branch 'origin/dev' into dev 2011-07-11 09:23:38 +01:00
Ross Lawley
dc5a613bc7 Fixes conversion of null genericreferences in querysets
closes #211
2011-07-11 09:19:27 +01:00
Ross Lawley
16390c1dec Merge pull request #217 from farazdagi/fix/typo-in-quering-db-docs
Fix/typo in quering db docs
2011-07-02 12:18:32 -07:00
Victor Farazdagi
4e6f91ae77 Typo fixed in "Quering The Db" guide. 2011-07-02 19:48:21 +04:00
Ross Lawley
556e620c7a Fixes recursion error when resetting changed fields
Fixes #214  - thanks to wpjunior for the test case
2011-07-01 08:44:46 +01:00
Ross Lawley
8e1d701c27 Fixed infinite recursion bug in _geo_indices()
Fixes #213  Thanks to joshink for the bug report
2011-06-30 10:32:05 +01:00
Ross Lawley
d51d95a28e Merge pull request #212 from zakj/dev
Added GridFSProxy.__nonzero__ - thanks to zakj for the fix
2011-06-30 01:57:00 -07:00
Zak Johnson
3d15a3b3e2 Add GridFSProxy.__nonzero__
For documents that do not have a value set for a given field, most field types
return None (or [] in the case of ListField). This makes it easy to test
whether a field has been set using "if doc.field". FileFields, on the other
hand, always return a GridFSProxy. Adding GridFSProxy.__nonzero__ which simply
checks for a grid_id allows the same boolean-test pattern for FileFields, as
well.
2011-06-29 20:48:39 -07:00
Ross Lawley
84e611b91e Tweak to dereferencing 2011-06-27 16:46:39 +01:00
Ross Lawley
4036e9fe34 Moved private method to make class more readable 2011-06-27 13:17:41 +01:00
Ross Lawley
b039a2293f Updated documentation about dereferencing
Refs #206
2011-06-27 12:42:26 +01:00
Ross Lawley
87f486c4f1 Added select_related() and refactored dereferencing
Added a dereference class to handle both select_related
 / recursive dereferencing and fetching dereference.

Refs #206
2011-06-27 12:25:49 +01:00
Ross Lawley
14be7ba2e2 Added support for the $ positional operator
closes #205
2011-06-21 14:50:11 +01:00
Ross Lawley
09c32a63ce Fixes bug with appending post save - due to lists not being reset 2011-06-21 12:34:14 +01:00
Ross Lawley
08ba51f714 Updated geo_index checking to be recursive
Fixes #127 - Embedded Documents can declare geo indexes and have
them created automatically
2011-06-20 15:41:23 +01:00
Ross Lawley
e3cd398f70 Changed default collection naming
Also added upgrade text
2011-06-20 14:00:06 +01:00
Ross Lawley
f41c5217c6 Added a cleaner way to get collection names
Also handles dynamic collection naming - refs #180.
2011-06-20 11:48:12 +01:00
Ross Lawley
1b0323bc22 Added document mixin support
For extendable / reusable documents
Fixes #204
2011-06-20 09:44:53 +01:00
Ross Lawley
e04e5f42ef Added test inheriting document from another file works
Closes #28
2011-06-20 08:46:40 +01:00
Ross Lawley
c24bc77c17 Fixes depreciation warnings in Django Auth.
Closes #156
2011-06-17 15:07:27 +01:00
Ross Lawley
99f923e27f Fixed queryset repr mid iteration
Closes #144
2011-06-17 15:04:07 +01:00
Ross Lawley
f3d265bbe0 Added to_dbref
Thanks to Ankhbayar for the initial code
Closes #202
2011-06-17 14:23:40 +01:00
Ross Lawley
5e7efcc8c2 Added 'hint' support, telling Mongo the proper index to use for the query.
Judicious use of hints can greatly improve query performance. When doing a query
on multiple fields (at least one of which is indexed) pass the indexed field as
a hint to the query. Hinting will not do anything if the corresponding index
does not exist.  The last hint applied to this cursor takes precedence over all
others.

Closes #203
2011-06-17 12:43:28 +01:00
Ross Lawley
62c8823e64 Fixing requirements
Test requirements are not install requirements!
2011-06-17 11:39:53 +01:00
Ross Lawley
5cc9188c5b Improved validation of (Generic)Reference fields 2011-06-16 15:25:09 +01:00
Ross Lawley
5e8604967c Fixes for django Q query rendering bug
Ensures that the QNodes haven't already been processed
Fixes #185
2011-06-16 15:00:10 +01:00
Ross Lawley
cae3f3eeff Fixes pickling issue with choice fields
Removes the dynamic __get_field_display partials before pickling
2011-06-16 12:50:45 +01:00
Ross Lawley
22a7ee5885 Handle old named (referenced) docs
Refs #199
2011-06-16 09:56:54 +01:00
Ross Lawley
658b85d327 Inconsistent setting of '_cls' broke inherited document referencing
Fixes #199
2011-06-15 16:51:49 +01:00
Ross Lawley
967e72723b Added note to item_frequencies method.
Current implementation is relatively simple, for complex schemas
the user will have to write their own map reduce.
2011-06-15 14:55:38 +01:00
Ross Lawley
5411cc5573 Updated changelog 2011-06-15 11:30:10 +01:00
Ross Lawley
ffb3e8b7b9 Added help_text and verbose_name to fields
closes #192
2011-06-15 11:28:41 +01:00
Ross Lawley
94cad89e32 Fixes to item_frequencies - now handles path lookups
fixes #194
2011-06-15 11:22:27 +01:00
Ross Lawley
0338ac17b1 Fixes multiple assignment issue preventing saves
Thanks to @wpjunior for the ticket and testcase
Also fixed bug in BaseList
fixes #195
2011-06-15 08:57:46 +01:00
Ross Lawley
cb1dfdfac6 Fixes to signals
The sender is the class of the document not the instance - easier to
hook into
2011-06-14 16:56:04 +01:00
Ross Lawley
576db9ca88 Fixes DateTimeField handling of date objects.
Fixes #191
2011-06-14 15:09:03 +01:00
Ross Lawley
4c2b83d9ca Remove errant __delete__ method 2011-06-14 15:00:26 +01:00
Ross Lawley
7cb24446ec Merge remote branch 'origin/dev' into dev 2011-06-14 14:36:30 +01:00
Ross Lawley
0ed79a839d Added delta tracking to documents.
All saves on exisiting items do set / unset operations only on changed fields.
* Note lists and dicts generally do set operations for things like pop() del[key]
  As there is no easy map to unset and explicitly matches the new list / dict

fixes #18
2011-06-14 14:35:38 +01:00
Ross Lawley
e518c51de3 Merge pull request #193 from colinhowe/dev
Improved Validation warnings - Thanks Colin Howe
2011-06-13 12:52:31 -07:00
Ross Lawley
ea35fb1c54 More robust _present_fields additions 2011-06-13 12:49:09 +01:00
Ross Lawley
7b29378319 Fixes issue converting to mongo 2011-06-13 12:40:12 +01:00
Colin Howe
82fbe7128f Improve validation warnings 2011-06-10 17:31:42 +01:00
Colin Howe
c1fadcac85 Merge remote branch 'upstream/dev' into dev 2011-06-10 16:50:44 +01:00
Ross Lawley
fd7f882011 Save no longer tramples over documents now sets or unsets explicit fields.
Fixes #146, refs #18
Thanks @zhangcheng for the initial code
2011-06-09 16:09:06 +01:00
Ross Lawley
fb09fde209 Updated changelog 2011-06-09 14:26:52 +01:00
Ross Lawley
b2848b8519 Added ComplexDateTimeField
Thanks to @pelletier for the code.
Refs #187
2011-06-09 14:20:21 +01:00
Ross Lawley
417bb1b35d Added regression test for #185 2011-06-09 12:15:36 +01:00
Ross Lawley
199b4eb860 Added django_tests and regression test for order_by
Refs #190
2011-06-09 12:08:37 +01:00
Ross Lawley
a66417e9d0 pep8 update 2011-06-09 11:31:47 +01:00
Ross Lawley
b9255f73c3 Updated docs 2011-06-09 11:28:57 +01:00
Ross Lawley
4b9bacf731 Added ComplexBaseField
* Handles the efficient lazy dereferencing of DBrefs.
* Handles complex nested values in ListFields and DictFields
* Allows for both strictly declared ListFields and DictFields where the embedded
value must be of a field type or no restrictions where the values can be a mix
of field types / values.
* Handles DBrefences of documents where allow_inheritance = False.
2011-06-09 11:25:24 +01:00
Ross Lawley
602d7dad00 Improvements to Abstract Base Classes
Added test example highlighting what to do to migrate a class from
complex (allows inheritance) to simple.
2011-06-08 17:10:26 +01:00
Ross Lawley
d32dd9ff62 Added _get_FIELD_display() for handy choice field display lookups
closes #188
2011-06-08 13:07:08 +01:00
Ross Lawley
28b7ef2304 Merge remote branch 'origin/dev' into dev 2011-06-08 13:06:47 +01:00
Ross Lawley
6dc2672dba Updated changelog 2011-06-08 13:03:42 +01:00
Ross Lawley
9a949984ee Merge pull request #189 from colinhowe/dev-notypesindex
Thanks @colinhowe!
2011-06-08 04:40:54 -07:00
Colin Howe
aa32d43014 Pydoc update 2011-06-08 12:36:32 +01:00
Colin Howe
4174918476 Merge in upstream dev 2011-06-08 12:33:36 +01:00
Colin Howe
6081fc6faf Merge remote branch 'upstream/dev' into dev 2011-06-08 12:24:06 +01:00
Colin Howe
7c62fdc0b8 Allow for types to never be auto-prepended to indices 2011-06-08 12:20:58 +01:00
Ross Lawley
3c88faa889 Updated slave_okay syntax
Now inline with .timeout() and .snapshot().
Made them chainable - so its easier to use and added tests for cursor_args
2011-06-08 12:06:26 +01:00
Ross Lawley
d15f5ccbf4 Added _slave_okay to clone 2011-06-08 10:41:08 +01:00
Ross Lawley
cfcd77b193 Added tests displaying datetime behaviour.
Updated datetimefield documentation
2011-06-08 10:33:56 +01:00
Ross Lawley
525c25b9f6 Merge branch 'master' into dev 2011-06-07 15:15:06 +01:00
Ross Lawley
c059ad47f2 Updated django docs refs #186 2011-06-07 15:14:41 +01:00
Ross Lawley
48fd6c1344 Merge remote branch 'origin/master' into dev 2011-06-07 14:38:04 +01:00
Ross Lawley
1ee50922d9 Merge pull request #186 from kuno/master
fixed import path typo in django document
2011-06-07 06:37:34 -07:00
kuno
d63bf0abde fixed import path typo in django documents 2011-06-07 20:19:29 +08:00
Ross Lawley
711db45c02 Changelist updated 2011-06-06 14:36:44 +01:00
Ross Lawley
55e20bda12 Added slave_okay syntax to querysets.
* slave_okay (optional): if True, allows this query to be run against a replica secondary.
2011-06-06 14:35:46 +01:00
Ross Lawley
56f00a64d7 Added bulk insert method.
Updated changelog and added tests / query_counter tests
2011-06-06 12:37:06 +01:00
Ross Lawley
8553022b0e Merge branch 'dev' into feature/dev-indexes 2011-06-06 11:40:21 +01:00
Ross Lawley
74b5043ef9 Added signals documentation 2011-06-06 11:39:58 +01:00
Ross Lawley
0e45078116 Added Blinker signal support 2011-06-06 11:34:43 +01:00
Ross Lawley
7e87ed79ab Merge branch 'feature/blinker_signals' into dev 2011-06-06 11:10:12 +01:00
Ross Lawley
7312db5c25 Updated docs / authors.
Thanks @jorgebastida for the awesome query_counter test context manager.
2011-06-06 11:07:27 +01:00
Ross Lawley
ec7effa0ef Added DereferenceBaseField class
Handles the lazy dereferencing of all items in a list / dict.
Improves query efficiency by an order of magnitude.
2011-06-06 11:04:06 +01:00
Colin Howe
9a2cf206b2 Documentation for new-style indices 2011-05-29 13:38:54 +01:00
Colin Howe
40df08c74c Fix QuerySet.ensure_index for new index specs 2011-05-29 13:33:00 +01:00
Ross Lawley
5d778648e6 Inital tests for dereferencing improvements 2011-05-27 11:33:40 +01:00
Colin Howe
1fa47206aa Support for sparse indexes and omitting types from indexes 2011-05-26 19:39:41 +01:00
Colin Howe
6f5bd7b0b9 Test needs a connection... 2011-05-26 18:54:52 +01:00
Ross Lawley
c903af032f Added inline_map_reduce functionality
Also added map_reduce method for calculating item_frequencies
Closes #183
2011-05-26 15:44:43 +01:00
Ross Lawley
9dd3504765 Updated changelog 2011-05-26 11:56:56 +01:00
Ross Lawley
97a1310344 Tweakin test 2011-05-26 11:11:00 +01:00
Alistair Roche
bf6f03a412 Improved MapFields setting 2011-05-25 17:25:39 +01:00
Ross Lawley
5ab13518db Added test confirming order_by reference field doesnt work 2011-05-25 13:50:52 +01:00
Ross Lawley
eb892241ee Added regression test for editting embedded documents
Closes #35
2011-05-25 13:31:01 +01:00
Ross Lawley
fac3f038a8 Added regression test for issue with unset and pop
Closes #118
2011-05-25 12:20:56 +01:00
Ross Lawley
b1cdd1eb26 Updated docs regarding ReferenceFields
Closes #149
2011-05-25 12:01:41 +01:00
Ross Lawley
60c8254f58 Tweaks to item_frequencies
Updated to use a ternary statement and added tests
Refs #124 #122

Thanks to @nickvlku for the code.
2011-05-25 11:10:42 +01:00
Ross Lawley
2ce70448b0 Merge branch 'dev' into pull_124 2011-05-25 09:54:56 +01:00
Ross Lawley
3861103585 Updated connection exception to provide more info on the cause.
Fixes #178
2011-05-25 09:36:25 +01:00
Ross Lawley
0708d1bedc Run all tests... 2011-05-25 09:34:50 +01:00
Ross Lawley
c3a8840435 Blinker signals added 2011-05-24 20:27:19 +01:00
Ross Lawley
3246cf8bdd Merge pull request #177 from sbook/feature/update_lists
Added the ability to update individual items in ListFields.

Example: 
  
   Message.objects(pk=12).update(set__comments__5__body="Testing 123")
2011-05-24 06:13:10 -07:00
Ross Lawley
7ecf84395a Improved DictFields
Allow searching multiple levels deep in DictFields
Allow DictField entries containing strings to use matching operators

Thanks again to @theojulien for the initial code #108
2011-05-24 14:07:58 +01:00
Ross Lawley
32bab13a8a Added MapField, similar to DictField
Similar to DictField except the value of each entry is always of a certain
(declared) field type.

Thanks again to @theojulienne for the code #108
2011-05-24 12:50:48 +01:00
Ross Lawley
088c40f9f2 Added Abstract Base Classes
Thanks to @theojulienne for the code :) #108
2011-05-24 12:30:12 +01:00
Alistair Roche
305fd4b232 Fixed whitespace 2011-05-24 11:44:43 +01:00
Alistair Roche
fe5111743d Merge branch 'dev' into feature/update_lists
Conflicts:
	mongoengine/queryset.py
2011-05-24 11:33:44 +01:00
Alistair Roche
8427877bd2 Merge remote branch 'upstream/dev' into dev 2011-05-24 11:32:23 +01:00
Alistair Roche
118c0deb7a Fixed list-indexing syntax; created tests. 2011-05-24 11:31:44 +01:00
Ross Lawley
1126c85903 Added Custom Objects Managers
Managers can now be directly declared in a Document eg::

```python
    class CustomQuerySetManager(QuerySetManager):

        @staticmethod
        def get_queryset(doc_cls, queryset):
            return queryset(is_published=True)

    class Post(Document):
        is_published = BooleanField(default=False)
        published = CustomQuerySetManager()
```

Refactored the name of the `_manager_func` to `get_queryset` to mark it as
part the public API.  If declaring a Manager with a get_queryset method, it
should be a staticmethod, that accepts the document_class and the queryset.

Note - you can still use decorators in fact in the example below,
we effectively do the same thing as the first example and is much less verbose.

```python

    class Post(Document):
        is_published = BooleanField(default=False)

        @queryset_manager
        def published(doc_cls, queryset):
            return queryset(is_published=True)
```

Thanks to @theojulienne for the initial impetus and code sample #108
2011-05-24 11:26:46 +01:00
Alistair Roche
13935fc335 Merge branch 'dev' into feature/update_lists 2011-05-24 08:58:38 +01:00
Alistair Roche
36034ee15f 'set__comments__0__body="asdf"' syntax works 2011-05-23 18:27:01 +01:00
Ross Lawley
1b72ea9cc1 Fixed detection of unique=True in embedded documents.
Added some more test cases - thanks to @heyman for the initial
test case.

Closes #172
Refs #171
2011-05-20 16:09:03 +01:00
Ross Lawley
04953351f1 Merge branch 'feature/slicing_fields' into dev 2011-05-20 14:18:48 +01:00
Ross Lawley
07e71d9ce9 Regression test for collection names an primary ordering
Closes #91
2011-05-20 14:18:16 +01:00
Ross Lawley
5f53cda3ab Added regression test for #94 2011-05-20 10:55:01 +01:00
Ross Lawley
9260ff9e83 Updated docs and added a NotRegistered exception
For handling GenericReferences that reference documents that haven't
been imported.

Closes #170
2011-05-20 10:22:22 +01:00
Ross Lawley
08d1689268 Updated to handle the converntional api style for slicing a field
Added testcase to demonstrate embedded slicing as well.

Refs #167
2011-05-20 09:47:41 +01:00
Ross Lawley
40b69baa29 Implementing Write Concern
Added write_options dict to save, update, update_one and get_or_create.
Thanks to justquick for the initial ticket and code.

Refs #132
2011-05-19 16:49:00 +01:00
Ross Lawley
b3251818cc Added regression test for custom queryset ordering
Closes #126
2011-05-19 13:04:14 +01:00
Ross Lawley
da8a057ede Added test showing documents can be pickled
Refs #135
2011-05-19 12:41:38 +01:00
Ross Lawley
efba9ef52a Merge remote branch 'srackham/gridfs-read-seek' into gridfs-read-seek 2011-05-19 10:14:51 +01:00
Ross Lawley
fb61c9a765 Regression test for mysterious uniqueness constraint when inserting into mongoengine
Closes #143  Thanks to tfausak for the test case.
2011-05-19 09:55:34 +01:00
Ross Lawley
95c2643f63 Added test showing primary=True behaviour.
If you set a field as primary, then unexpected behaviour can occur.
You won't create a duplicate but you will update an existing document.

Closes #138
2011-05-18 20:31:28 +01:00
Ross Lawley
fc2aff342b Unique indexes are created before user declared indexes
This ensures that indexes are created with the unique flag, if a user
declares the index, that would automatically be declared by the `unique_indexes`
logic.

Thanks to btubbs for the test case.
Fixes #129
2011-05-18 17:37:41 +01:00
Ross Lawley
371dbf009f Updated QuerySet to allow more granular fields control.
Added a fields method and tests showing the retrival of subranges of
List Fields.

Refs #167
2011-05-18 16:39:19 +01:00
Ross Lawley
5d5a84dbcf Spacing issue cleaned up 2011-05-18 16:24:35 +01:00
Ross Lawley
7526272f84 Added test example of updating an embedded field
Closes #139
2011-05-18 12:27:33 +01:00
Ross Lawley
5cbc76ea81 Pep8 2011-05-18 12:26:51 +01:00
Ross Lawley
7ba40062d3 Fixes ordering with custom db field names
Closes #125
2011-05-18 12:18:33 +01:00
Ross Lawley
1781c4638b Changed how the connection identity key is made
Uses the current thread identity as well as the process idenity to form
the key.

Fixes #151
2011-05-18 11:41:23 +01:00
Ross Lawley
1a049ee49d Added regression test case for mongoengine/issues/155 2011-05-18 11:06:14 +01:00
Ross Lawley
31521ccff5 Added queryset clone support and tests, thanks to hensom
Fixes #130
2011-05-18 10:30:07 +01:00
Ross Lawley
e3b4563c2b Merge remote branch 'hensom/master' into dev 2011-05-18 10:06:16 +01:00
Ross Lawley
c3f5ed0e0e Merge branch 'master' into dev 2011-05-18 10:06:02 +01:00
Ross Lawley
378b52321b Merge commit '18baa2dd7a4e909169b694cb6ec36214c5a51506' 2011-05-18 09:17:05 +01:00
Ross Lawley
98436f271e Merge branch 'master' into dev 2011-05-18 08:13:58 +01:00
Ross Lawley
a76008e440 Merge pull request #164 from glyphobet/master
spherical geospatial operators.
Thanks again to glyphobet
2011-05-16 08:18:22 -07:00
Matt Chisholm
6cf0cf9e7d slight reordering of test code for clarity 2011-05-10 12:31:31 +02:00
Matt Chisholm
608f08c267 Implement spherical geospatial query operators & unit tests
fixes https://github.com/hmarr/mongoengine/issues/163
2011-05-10 12:28:56 +02:00
Ross Lawley
bd3340c73f Merge pull request #162 from glyphobet/master
Comments in unit tests should say "degrees," not "miles":

MongoDB docs are confusing, only the spherical queries $nearSphere and $centerSphere use radians. The $near and $center queries, which are the ones that are used in these mongoengine tests, use the same units as the coordinates. For distances on the earth, this means they use degrees. (This is also why the 2d indexes can be used for other 2d data, not just points on the earth.)

Here's a mongo shell session demonstrating that $near and $center use degrees, and that $nearSphere and $centerSphere are the ones that use radians:  https://gist.github.com/964126

So, these mongoengine tests are using $near and $center, which use degrees, not miles, kilometers, or radians.

Thanks glyphobet for the clarification and taking time to explain it!
2011-05-10 02:15:17 -07:00
Nick Vlku Jr
c379ff883a Merge remote branch 'upstream/master' into dev 2011-05-10 00:13:15 -04:00
Matt Chisholm
3b7a8ce449 change comments to reflect that the geospatial queries use degrees, not miles 2011-05-09 22:29:27 +02:00
Ross Lawley
e9ad04f763 Merge pull request #160 from Ankhbayar/master
Added __hash__, __ne__ with test. 
Thanks Ankhbayar
2011-05-09 02:28:41 -07:00
Ross Lawley
f0277736e2 Updated queryset to handle latest version of pymongo
map_reduce now requires an output.
Reverted previous _lookup_field change, until a test case
is produced for the incorrect behaviour.
2011-05-09 10:22:37 +01:00
Ross Lawley
49c978ad9e Merge remote branch 'upstream/master' 2011-05-09 09:01:19 +01:00
Harry Marr
eeae1b4aea Merge pull request #148 from srackham/drop-close-warning
Drop gridfs close warning
2011-05-08 06:41:28 -07:00
Harry Marr
9432d1a194 Merge pull request #158 from glyphobet/master
tiny documentation fix
2011-05-08 06:40:29 -07:00
Harry Marr
3b2dbf1897 Merge pull request #159 from gregglind/patch-1
Tiny spelling correction / clarification.
2011-05-08 06:39:45 -07:00
Gregg Lind
9c1ad5f631 Tiny spelling correction / clarification. 2011-05-04 18:01:06 -07:00
Matt Chisholm
c2fef4e791 error in documentation; need to use keyword arg to create Comment object 2011-05-03 23:50:20 +02:00
Alistair Roche
76cbb66843 Fixed error with _lookup_field
It was failing when given multiple fields
2011-04-28 14:31:19 +01:00
Анхбаяр Лхагвадорж
96dbeea171 Added __hash__, __ne__ with test. 2011-04-12 20:23:16 +08:00
Stuart Rackham
829df581f0 Drop gridfs close warning 2011-04-04 15:19:57 +12:00
Stuart Rackham
bd84d08b95 Fixed misspelt variable name. 2011-04-04 13:44:36 +12:00
Stuart Rackham
2c7469c62a Additional file-like behavior for FileField (optional size arg for read
method; fixed seek and tell methods for reading files).
2011-04-03 15:21:00 +12:00
Nick Vlku
6f7d7537f2 Added a test to verify that if a native JS function is put in as a tag, item_frequencies no longer fails (added the word 'watch' as a tag) 2011-03-06 18:59:29 -05:00
Harry Marr
d7c9694be0 Merge branch 'master' of https://github.com/radev/mongoengine into dev 2011-03-06 13:23:30 +00:00
Harry Marr
69171282e9 Merge branch 'master' of https://github.com/lig/mongoengine into dev
Conflicts:
	.gitignore
2011-03-06 13:11:21 +00:00
Michael Henson
53d66b7267 Added QuerySet.clone() to support copying querysets 2011-01-27 23:51:10 -05:00
Nick Vlku
ba9813e5a3 Fixed Issue 122: item_frequencies doesn't work if tag is also the name of a native js function
Did this by checking if the item is a native function, if it is I set it to an initial numeric value.  Future occurrences of the tag count correctly.
2011-01-09 22:30:18 -05:00
Harry Marr
ce8b3ea0a1 Merge remote branch 'nvie/dev' into dev 2011-01-09 23:29:11 +00:00
Harry Marr
559fc46037 Merge branch 'master' of https://github.com/sshwsfc/mongoengine into sshwsfc-dev 2011-01-09 23:21:22 +00:00
Harry Marr
10c0b035ae Merge remote branch 'aleszoulek/dev' into dev 2011-01-09 23:19:41 +00:00
Harry Marr
37818d2d72 Merge branch 'dev' of https://github.com/alefnula/mongoengine into alefnula-dev 2011-01-09 21:00:03 +00:00
Harry Marr
357dd0e7cc Merge remote branch 'iapain/master' into dev 2011-01-09 20:52:39 +00:00
Serge Matveenko
34b923b7ac Fix Issue#115: Possibility to bypass class_name check in queryset. 2010-12-21 18:29:51 +03:00
Serge Matveenko
846f5a868f Fix Issue#116: Use cls instead of User in create_user. 2010-12-21 18:16:00 +03:00
Serge Matveenko
0acb2d904d Add hidden (.*) files to .gitignore but not the .gitignore itself. 2010-12-21 18:11:33 +03:00
Vincent Driessen
03a757bc6e Add a safety note on using the new delete rules. 2010-12-21 01:19:27 -08:00
Vincent Driessen
0f68df3b4a Fix line width. 2010-12-20 05:55:50 -08:00
Vincent Driessen
07ef58c1a7 Rename delete_rule -> reverse_delete_rule. 2010-12-20 05:55:46 -08:00
Vincent Driessen
52f5deb456 Add documentation for the delete_rule argument. 2010-12-20 05:23:27 -08:00
Vincent Driessen
e05e6b89f3 Add safe_update parameter to updates. 2010-12-16 11:54:34 +01:00
Vincent Driessen
ffc8b21f67 Some tests broke over the default None value. 2010-12-14 03:50:49 -08:00
Vincent Driessen
16e1f72e65 Avoid confusing semantics when comparing delete rules. 2010-12-14 03:39:14 -08:00
Vincent Driessen
620f4a222e Don't check for DO_NOTHING in the delete rule registration method.
It is already checked before it is invoked.  This saves the ugly import
of DO_NOTHING inside document.py.
2010-12-14 02:03:26 -08:00
Vincent Driessen
f30fd71c5e Refactor: put the delete rule constants into the queryset module, too. 2010-12-13 13:42:01 -08:00
Vincent Driessen
3b55deb472 Remove unused meta data. 2010-12-13 13:25:49 -08:00
Vincent Driessen
4d5164c580 Use multiple objects in the test.
This is to ensure only the intended subset is deleted and not all
objects.
2010-12-13 13:24:20 -08:00
Vincent Driessen
5b118f64ec Add tests for nullification and denial on the queryset. 2010-12-13 12:54:26 -08:00
Vincent Driessen
07dae64d66 More the deletion code over to the QuerySet object.
The Document object doens't have any delete_rule specific code anymore,
and leverages the QuerySet's ability to deny/cascade/nullify its
relations.
2010-12-13 12:36:24 -08:00
Viktor Kerkez
501f033712 Merge remote branch 'hmarr/dev' into dev 2010-12-11 01:40:28 +01:00
Igor Ivanov
a68cb20266 Allow 0 or "" to be used as valid _id value. 2010-12-09 08:38:47 -08:00
Vincent Driessen
3c98a4bff5 Remove accidentally left behind debugging message. 2010-12-06 00:07:30 -08:00
Vincent Driessen
20eb920cb4 Change test docstring. 2010-12-06 00:06:03 -08:00
Vincent Driessen
b06d794854 Implementation of DENY rules. 2010-12-05 23:43:19 -08:00
Vincent Driessen
f3da5bc092 Fix: potential NameError bug in test case. 2010-12-05 23:03:40 -08:00
Vincent Driessen
d21434dfd6 Make the nullification an atomic operation.
This shortcut works now, since hmarr fixed the unset bug in dev.
2010-12-05 22:41:16 -08:00
Vincent Driessen
ad1aa5bd3e Add tests that need to be satisfied. 2010-12-05 22:24:28 -08:00
Vincent Driessen
dd21ce9eac Initial implementation of the NULLIFY rule. 2010-12-05 22:24:27 -08:00
Vincent Driessen
bba3aeb4fa Actually *use* the register_delete_rule classmethod, since it's there. 2010-12-05 22:24:27 -08:00
Vincent Driessen
86233bcdf5 Added initial implementation of cascading document deletion.
The current implementation is still very basic and needs some polish.
The essence of it is that each Document gets a new meta attribute called
"delete_rules" that is a dictionary containing (documentclass,
fieldname) as key and the actual delete rule as a value.  (Possible
values are DO_NOTHING, NULLIFY, CASCADE and DENY.  Of those, only
CASCADE is currently implented.)
2010-12-05 22:24:27 -08:00
Vincent Driessen
4f3eacd72c Fix: whitespace.
This broke my Vim auto-folds.
2010-12-05 22:24:27 -08:00
Vincent Driessen
67fcdca6d4 Fix: PyFlakes pointed out this missing import. 2010-12-05 22:24:27 -08:00
Vincent Driessen
62cc8d2ab3 Fix: redefinition of "datetime" from line 6. 2010-12-05 22:24:27 -08:00
Harry Marr
3a0523dd79 Fixed issue with unset operation 2010-12-05 21:43:04 +00:00
Harry Marr
cec8b67b08 Added test for unsetting fields 2010-12-05 20:47:24 +00:00
sshwsfc
ca8c3981c4 2010-11-18 22:35:11 -08:00
sshwsfc
ca56785cbc add some prepare_query_value method for fields 2010-11-18 21:33:05 -08:00
Deepak Thukral
b12c34334c added test case for issue 103 2010-11-18 20:44:51 +01:00
Viktor Kerkez
9c8411b251 Choice field test updated 2010-11-11 18:19:35 +01:00
Ales Zoulek
66baa4eb61 QS.all_fields - resets previous .only() and .exlude() 2010-11-10 22:01:27 +01:00
Deepak Thukral
89646439e7 fixed typo in error message 2010-11-10 21:02:59 +01:00
Ales Zoulek
bda4776a18 added Queryset.exclude() + tests 2010-11-05 12:57:48 +01:00
Harry Marr
c6058fafed Merge branch 'dev' of github.com:hmarr/mongoengine into dev 2010-11-02 13:44:19 +00:00
Harry Marr
11950eabea Merge branch 'master' of http://github.com/aleszoulek/mongoengine into dev 2010-11-02 13:43:33 +00:00
Viktor Kerkez
e1282028a5 Added django style choices 2010-11-01 14:54:55 +01:00
Harry Marr
6b880aa8b3 Fixed order-then-filter issue 2010-11-01 00:43:30 +00:00
Ales Zoulek
a3830be4c9 QuerySet.only() supports subfields + tests 2010-10-28 01:13:24 +02:00
Viktor Kerkez
ef15733efe Added creation_counter to BaseField in order to provied form modules with a way to sort fields i order user specified them (same technique is used in Django) 2010-10-23 22:35:37 +02:00
Viktor Kerkez
f0c5dd1bce Small fix for Python 2.5 2010-10-23 22:33:03 +02:00
Harry Marr
e868f37c60 Merge branch 'master' of http://github.com/benmur/mongoengine into dev 2010-10-20 13:31:47 +01:00
Rached Ben Mustapha
18baa2dd7a fix calling a queryset after skip and limit have been set 2010-10-19 22:40:36 +00:00
Rached Ben Mustapha
2560145551 add a failing test for the pagination bug 2010-10-19 22:23:08 +00:00
Harry Marr
3b88a4f728 Fixed a couple of errors in the docs 2010-10-19 12:28:34 +01:00
Harry Marr
69989365c7 Merge branch 'v0.4' 2010-10-18 13:56:07 +01:00
Harry Marr
2b9c526b47 Merge branch 'v0.4' of http://github.com/schallis/mongoengine into v0.4 2010-10-18 13:55:57 +01:00
Steve Challis
d7c42861fb Minor GridFS corrections 2010-10-18 10:25:06 +01:00
Harry Marr
e9d478ed9f Merge branch 'master' of http://github.com/cyberdelia/mongoengine into v0.4 2010-10-18 10:22:56 +01:00
Harry Marr
d6cb5b9abe Removed invalid connection tests 2010-10-18 10:21:23 +01:00
Steve Challis
5580b003b5 Added self to AUTHORS 2010-10-18 01:30:32 +01:00
Steve Challis
67736c849d Finished GridFS Documentation
* Also made GridFS replace test pass
2010-10-18 00:55:44 +01:00
Steve Challis
39e27735cc Merge branch 'v0.4' of git://github.com/hmarr/mongoengine into v0.4
Conflicts:
	docs/changelog.rst
	mongoengine/base.py
	mongoengine/queryset.py
2010-10-17 23:48:20 +01:00
Harry Marr
0902b95764 Added support for recursive embedded documents 2010-10-18 00:27:40 +01:00
Steve Challis
dc7181a3fd Begun GridFS documentation 2010-10-17 23:43:58 +01:00
Harry Marr
e93c4c87d8 Fixed inheritance collection issue 2010-10-17 17:41:20 +01:00
Harry Marr
dcec61e9b2 Raise AttributeError when necessary on QuerySet[] 2010-10-17 16:36:22 +01:00
Harry Marr
007f116bfa Increment version to 0.4 2010-10-17 15:42:31 +01:00
Harry Marr
6817f3b7ba Updated docs for v0.4 2010-10-17 15:40:49 +01:00
Harry Marr
36993029ad Removed old Q-object implementation 2010-10-17 14:22:45 +01:00
Harry Marr
012352cf24 Added snapshot and timeout methods to QuerySet 2010-10-17 14:21:55 +01:00
Harry Marr
26723992e3 Combined Q-object tests 2010-10-17 14:14:05 +01:00
Harry Marr
3591593ac7 Fixed GenericReferenceField query issue 2010-10-17 13:55:48 +01:00
Harry Marr
d3c2dfbaee Merge branch 'master' of http://github.com/ixc/mongoengine into v0.4
Conflicts:
	mongoengine/fields.py
	mongoengine/queryset.py
2010-10-17 13:54:16 +01:00
Harry Marr
b2b4456f74 Merge branch 'new-q-objects' into v0.4 2010-10-17 13:42:29 +01:00
Harry Marr
f666141981 Added test for list of referencefields 2010-10-17 13:23:11 +01:00
Harry Marr
fb4c4e3e08 Merge branch 'master' of http://github.com/jaimebuelta/mongoengine into v0.4 2010-10-17 13:06:41 +01:00
Harry Marr
34fa5cd241 Added some imports for PyMongo 1.9 compatibility. 2010-10-16 16:03:26 +01:00
Jaime
833fa3d94d Added note about the use of default parameters 2010-10-06 20:02:14 +01:00
Harry Marr
92471445ec Fix changing databases
Conflicts:

	mongoengine/connection.py
	mongoengine/queryset.py
2010-10-05 00:46:13 +01:00
Harry Marr
3acfd90720 Added some imports for PyMongo 1.9 compatibility. 2010-10-04 14:58:00 +01:00
Harry Marr
4742328b90 Delete stale cursor when query is filtered. Closes #62. 2010-10-04 12:11:01 +01:00
Harry Marr
b4c54b1b62 Added support for the $not operator 2010-10-04 11:41:49 +01:00
Harry Marr
76cb851c40 Replaced old Q-object with new, revamped Q-object 2010-10-04 11:41:07 +01:00
Harry Marr
3fcc0e9789 Combining OR nodes works, fixed other Q-object bugs 2010-10-04 02:10:37 +01:00
Harry Marr
8e65154201 Added a tree transformer, got complex ANDs working 2010-10-04 00:06:42 +01:00
Harry Marr
c0f7c4ca2d Fixed error in empty property on QCombination 2010-10-03 23:22:36 +01:00
Harry Marr
db2f64c290 Made query-tree code a bit clearer 2010-10-03 23:01:44 +01:00
Harry Marr
a3c46fec07 Compilation of combinations - simple $or now works 2010-10-03 21:26:26 +01:00
Harry Marr
62388cb740 Started work on new Q-object implementation 2010-10-03 21:08:28 +01:00
Timothée Peignier
9c9903664a add support for pk property in documents and filters 2010-10-03 18:50:35 +02:00
Harry Marr
556eed0151 QuerySet.distinct respects query. Closes #64. 2010-10-03 15:22:47 +01:00
Harry Marr
4012722a8d QuerySet.item_frequencies works with non-list fields 2010-10-03 15:01:45 +01:00
Harry Marr
4c68bc6c96 Merge branch 'master' of http://github.com/sibsibsib/mongoengine into v0.4
Conflicts:
	mongoengine/queryset.py
	tests/queryset.py
2010-10-03 01:57:22 +01:00
Harry Marr
159923fae2 Made lists of recursive reference fields possible 2010-10-03 01:48:42 +01:00
sib
72c7a010ff added unit test for addToSet 2010-09-30 03:05:15 -03:00
sib
2c8f004103 added update operator for addToSet 2010-09-30 02:53:44 -03:00
Steve Challis
67a9b358a0 Merge branch 'v0.4' of git://github.com/hmarr/mongoengine into v0.4 2010-09-29 23:39:09 +01:00
Steve Challis
b5eb3ea1cd Added a Django storage backend.
- New GridFSStorage storage backend
- New FileDocument document for storing files in GridFS
- Whitespace cleaned up in various files
2010-09-29 23:36:58 +01:00
Harry Marr
98bc0a7c10 Raise AttributeError when necessary on GridFSProxy 2010-09-25 22:47:09 +01:00
Harry Marr
bb24879149 Moved custom queryset test to correct place 2010-09-19 20:00:53 +01:00
Harry Marr
3d6ee0ce00 Merge branch 'master' of http://github.com/n1k0/mongoengine into v0.4 2010-09-19 19:55:57 +01:00
Harry Marr
ee72845701 Merge branch 'v0.4' of http://github.com/samuelclay/mongoengine into v0.4 2010-09-19 19:53:12 +01:00
Harry Marr
d158727154 Merge branch 'v0.4' of github.com:hmarr/mongoengine into v0.4 2010-09-19 17:18:39 +01:00
Harry Marr
73092dcb33 QuerySet update method returns num affected docs 2010-09-19 17:17:37 +01:00
Samuel Clay
91ddd310ba Merge branch 'v0.4' of github.com:samuelclay/mongoengine into v0.4 2010-09-16 17:21:21 -04:00
Samuel Clay
20dd7562e0 Adding multiprocessing support to mongoengine by using the identity of the process to define the connection. Each 'thread' gets its own pymongo connection. 2010-09-16 17:19:58 -04:00
Greg Turner
b7e84031e3 Escape strings for regex query. 2010-09-16 14:37:18 +10:00
Harry Marr
f11ee1f9cf Added support for using custom QuerySet classes 2010-09-15 09:47:13 +01:00
Nicolas Perriault
449f5a00dc added a 'validate' option to Document.save() +docs +tests 2010-09-11 17:45:57 +02:00
Steve Challis
bd1bf9ba24 Merge branch 'v0.4' of git://github.com/hmarr/mongoengine into v0.4
Conflicts:
	mongoengine/fields.py
	tests/fields.py
2010-08-31 00:25:10 +01:00
Harry Marr
2af5f3c56e Added support for querying by array position. Closes #36. 2010-08-31 00:24:30 +01:00
Harry Marr
1849f75ad0 Made GridFSProxy a bit stricter / safer 2010-08-31 00:23:59 +01:00
Harry Marr
32e66b29f4 Fixed FileField problem caused by shared objects 2010-08-30 22:12:05 +01:00
Harry Marr
69012e8ad1 Fixed incorrect $pull test 2010-08-30 19:59:49 +01:00
Harry Marr
17642c8a8c Fixed QuerySet.average issue that ignored 0 2010-08-30 19:48:17 +01:00
Mircea Pasoi
f1aec68f23 Inherit index options. 2010-08-30 17:40:01 +01:00
Mircea Pasoi
3e30d71263 Support for background and drop_dups indexing options. 2010-08-30 17:39:41 +01:00
Mircea Pasoi
266f33adc4 Bug fix for gridfs FileField. 2010-08-30 17:38:28 +01:00
Mircea Pasoi
dcc8d22cec Proper unique index name when using db_field. 2010-08-30 17:38:07 +01:00
Harry Marr
9540555b26 Merge branch 'master' of git://github.com/flosch/mongoengine into v0.4 2010-08-30 17:29:59 +01:00
Florian Schlachter
185e7a6a7e Better way of checking if new_class has an 'objects' attribute. 2010-08-30 18:38:41 +02:00
Florian Schlachter
c39f315ddc Merge remote branch 'hmarr/v0.4' 2010-08-30 18:38:12 +02:00
Harry Marr
5b230b90b9 Doc fix (all operator) 2010-08-30 15:34:29 +01:00
Harry Marr
1ed9a36d0a Added test for pop operation 2010-08-30 14:02:02 +01:00
Florian Schlachter
e0911a5fe0 Replaced slow exception handling with has_key. 2010-08-30 14:58:58 +02:00
Harry Marr
3297578e8d Merge branch 'master' of http://github.com/richardhenry/mongoengine into v0.4 2010-08-30 13:57:42 +01:00
Harry Marr
954d5c16d8 Merge branch 'master' of http://github.com/serverplot/mongoengine into v0.4 2010-08-30 13:56:28 +01:00
Florian Schlachter
95efa39b52 Added *.egg to .gitignore. 2010-08-30 14:56:18 +02:00
Florian Schlachter
c27ccc91d2 Merge remote branch 'hmarr/v0.4'
Conflicts:
	tests/fields.py
2010-08-30 14:55:49 +02:00
Harry Marr
4fb6fcabef Added test for overriding objects 2010-08-30 13:54:20 +01:00
Harry Marr
2635e41f69 Merge branch 'master' of http://github.com/theojulienne/mongoengine into v0.4 2010-08-30 13:54:07 +01:00
Harry Marr
ba01817ee3 Merge branch 'master' of http://github.com/vandersonmota/mongoengine into v0.4 2010-08-30 13:34:43 +01:00
Florian Schlachter
1e1d7073c8 Merge remote branch 'hmarr/v0.4'
Conflicts:
	tests/fields.py
2010-08-30 14:33:04 +02:00
Harry Marr
40eb23a97a Merge branch 'master' of git://github.com/flosch/mongoengine into v0.4
Conflicts:
	tests/fields.py
2010-08-30 13:21:10 +01:00
Harry Marr
f4711699e4 Merge branch 'master' of http://github.com/danielhasselrot/mongoengine into v0.4
Conflicts:
	mongoengine/fields.py
2010-08-30 13:03:23 +01:00
Harry Marr
3b62cf80cd Fixed {Dict,List}Field default issue. Closes #46. 2010-08-30 13:00:34 +01:00
Harry Marr
d99c5973c3 Fixed Q object DBRef test bug 2010-08-30 12:52:24 +01:00
Richard Henry
7de9adc6b1 Adding support for pop operations to QuerySet.update and QuerySet.update_one 2010-08-28 09:36:25 +01:00
sp
17addbefe2 made it more like Django's user model 2010-08-18 16:50:52 -04:00
Greg Turner
d274576b47 Fixed premature return for query gen 2010-08-13 22:30:36 +10:00
Greg Turner
6373e20696 Better error reporting on a validation error for a list. 2010-08-13 22:28:26 +10:00
Greg Turner
809fe44b43 Added a __raw__ parameter for passing query dictionaries directly to pymongo 2010-08-12 15:14:20 +10:00
Greg Turner
198ccc028a made list queries work with regexes (e.g. istartswith) 2010-08-06 20:29:09 +10:00
Theo Julienne
b96e27a7e4 Allow documents to override the 'objects' QuerySetManager 2010-07-30 22:09:00 +10:00
Timothée Peignier
1147ac4350 ignore virtualenv directory 2010-07-27 01:30:16 +08:00
flosch
21d267cb11 Now order_by() works like queries for referencing deeper fields (replacing . with __). old: order_by('mydoc.myattr') / new: order_by('mydoc__myattr'). Closes #45 2010-07-26 17:28:59 +02:00
flosch
6791f205af Style fix. 2010-07-26 16:50:09 +02:00
flosch
7ab2e21c10 Handle unsafe expressions when using startswith/endswith/contains with unsafe expressions. Closes #58 2010-07-26 16:42:10 +02:00
flosch
2f991ac6f1 Added all() method to get all document instances from a document. Extended the FileField's tests with testing on empty filefield. 2010-07-25 19:02:15 +02:00
flosch
9411b38508 Removed unnecessary comment. 2010-07-25 18:45:49 +02:00
flosch
386c48b116 Typo. 2010-07-25 18:43:11 +02:00
flosch
9d82911f63 Added tests for #46. 2010-07-25 18:38:24 +02:00
flosch
51065e7a4d Closes #46 by instantiating a new default instance for every field by request. 2010-07-25 18:33:33 +02:00
flosch
327452622e Handle DBRefs correctly within Q objects. Closes #55 2010-07-25 18:22:26 +02:00
flosch
13316e5380 Introduced new Document.objects.create, like django has. It creates a new object, saves it and returns the new object instance. 2010-07-25 17:35:09 +02:00
Harry Marr
9f98025b8c Added QuerySet.distinct. Closes #44. 2010-07-25 15:29:02 +01:00
Harry Marr
564f950037 Merge branch 'master' of git://github.com/flosch/mongoengine into v0.4 2010-07-25 15:09:45 +01:00
Harry Marr
be651caa68 Removed a couple of sneaky print statements 2010-07-25 15:02:37 +01:00
Florian Schlachter
aa00feb6a5 FileField's values are now optional. When no value is applied, no File object is created and referenced. 2010-07-20 22:46:00 +02:00
Florian Schlachter
03c0fd9ada Make default value of DictField an empty dict instead of None. 2010-07-19 19:01:53 +02:00
Daniel Hasselrot
6093e88eeb Made list store empty list by default 2010-07-19 08:07:03 +08:00
Florian Schlachter
ec519f20fa Makes the tests compatible to pymongo 1.7+. Not backwards compatible! 2010-07-19 01:32:28 +02:00
Florian Schlachter
d3495896fa Merge branch 'master' of github.com:flosch/mongoengine 2010-07-19 01:12:16 +02:00
Florian Schlachter
323c86308a Merge remote branch 'hmarr/v0.4'
Conflicts:
	mongoengine/fields.py
	tests/fields.py
2010-07-19 01:11:28 +02:00
martin
f9057e1a28 Fixed bug in FileField, proxy was not getting the grid_id set 2010-07-19 07:03:45 +08:00
Florian Schlachter
9596a25bb9 Fixed documentation bug. 2010-07-19 00:56:16 +02:00
Steve Challis
47bfeec115 Tidied code, added replace() method to FileField 2010-07-19 06:53:21 +08:00
martin
6bfd6c322b Fixed bug with GeoLocationField 2010-07-19 06:52:57 +08:00
Steve Challis
0512dd4c25 Added new FileField with GridFS support
The API is similar to that of PyMongo and most of the same operations are
possible.

The FileField can be written too with put(), write() or by using the assignment
operator. All three cases are demonstrated in the tests. Metadata can be added
to a FileField by assigning keyword arguments when using put() or new_file().
2010-07-19 06:52:11 +08:00
Daniel Hasselrot
acbc741037 Made list store empty list by default 2010-07-15 18:20:29 +02:00
Harry Marr
c2163ecee5 Added test for Geo indexes 2010-07-07 15:12:14 +01:00
Harry Marr
71689fcf23 Got within_box working for Geo fields 2010-07-07 15:00:46 +01:00
Harry Marr
1c334141ee Merge branch 'geo' of git://github.com/blackbrrr/mongoengine into v0.4
Conflicts:
	mongoengine/fields.py
	mongoengine/queryset.py
2010-07-07 14:53:25 +01:00
Daniel Hasselrot
b89d71bfa5 Do not convert None objects 2010-07-06 14:17:30 +02:00
Daniel Hasselrot
3179c4e4ac Now only removes _id if none, for real 2010-07-06 11:25:49 +02:00
Daniel Hasselrot
f5e39c0064 Allowed _id to be missing when converting to mongo 2010-07-06 10:25:31 +02:00
vandersonmota
86e2797c57 added a TestCase for tests that uses mongoDB 2010-06-09 22:28:30 -03:00
Steve Challis
39b749432a Tidied code, added replace() method to FileField 2010-06-03 08:27:21 +01:00
Steve Challis
0ad343484f Added new FileField with GridFS support
The API is similar to that of PyMongo and most of the same operations are
possible.

The FileField can be written too with put(), write() or by using the assignment
operator. All three cases are demonstrated in the tests. Metadata can be added
to a FileField by assigning keyword arguments when using put() or new_file().
2010-06-02 20:53:39 +01:00
Harry Marr
196606438c Fixed Q-object list query issue 2010-05-30 18:34:06 +01:00
Harry Marr
6896818bfd Added docs for exact, iexact 2010-05-30 17:40:01 +01:00
Flavio Amieiro
eb4f0ad7fb Merge branch 'master' of git://github.com/hmarr/mongoengine 2010-05-29 11:52:08 -03:00
Harry Marr
467e61bcc1 Documentation fix 2010-05-28 02:28:42 +01:00
Flavio Amieiro
a2c78c9063 Add 'exact' and 'iexact' match operators for QuerySets 2010-05-26 20:24:57 -03:00
Harry Marr
b23353e376 Fixed inherited document primary key issue 2010-05-24 23:03:30 +01:00
Harry Marr
b8e9790de3 Merge branch 'master' of git://github.com/flosch/mongoengine 2010-05-22 15:59:41 +01:00
Harry Marr
e37e8d9e65 Merge branch 'master' of git://github.com/blackbrrr/mongoengine 2010-05-22 15:53:54 +01:00
Stephan Jaekel
f657432be3 always ignore empty Q objects, if the new Q is empty, the old one will be returned. 2010-05-14 14:35:27 +02:00
Stephan Jaekel
80c2895e56 Merge branch 'master' of http://github.com/flosch/mongoengine 2010-05-14 14:22:29 +02:00
Stephan Jaekel
88da998532 added test for empty Q objects 2010-05-14 14:21:58 +02:00
Stephan Jaekel
225972e151 Added some handy shortcuts for django users. 2010-05-14 14:03:18 +02:00
Stephan Jaekel
4972bdb383 ignore empty Q objects when combining Q objects. 2010-05-14 14:02:39 +02:00
Florian Schlachter
11c7a15067 Added test for DictField's basecls. 2010-05-14 13:49:13 +02:00
Florian Schlachter
9df725165b Added a possibility to define a base class for fields from a DictField (instead of using BaseField). This is important if you want to use field-based query abilities like StringField's startswith/endswith/contains. Just define `basecls´ when defining your DictField. Example:
class Test(Document):

    name = StringField()
    translations = DictField(basecls=StringField)

Without basecls defined:

> Test.objects(translations__german__startswith='Deutsch')
[]

With basecls set to StringField:

> Test.objects(translations__german__startswith='Deutsch')
[<Test: Test object>]
2010-05-14 13:35:45 +02:00
Florian Schlachter
682326c130 documentation bug fixed 2010-04-30 18:04:58 +02:00
Matt Dennewitz
86575cb035 can't use unicode strings for __init__ kwargs 2010-04-19 09:39:03 -05:00
Florian Schlachter
eecc6188a7 fixes issue #41 since unicode kwargs is an feature of python 2.6.5 and above. 2010-04-19 11:34:09 +02:00
Harry Marr
3b4df4615a Fixed MRO error that occured on document inheritance 2010-04-17 21:45:11 +01:00
Harry Marr
edfda6ad5b BinaryField returns str not unicode 2010-04-17 21:24:06 +01:00
Florian Schlachter
3c7e8be2e7 Removed create_default since it can be achieved with the default argument (like default=MyEmbeddedDocument since default takes callables too). 2010-04-17 16:59:09 +02:00
Florian Schlachter
416fcba846 Merge remote branch 'hmarr/master'
Conflicts:
	mongoengine/base.py
2010-04-17 01:42:26 +02:00
Florian Schlachter
e196e229cd Accepting a tuple for validation argument. 2010-04-17 01:36:45 +02:00
Florian Schlachter
da57572409 Introduced new create_default field argument. If set to true, mongoengine will automagically create an instance of the desired document class (useful if using EmbeddedDocumentField for example):
class SubDoc(EmbeddedDocument):
        url = URLField()

    class MyDoc(Document):
        subdoc = EmbeddedDocumentField(SubDoc, create_default=True)

With create_default MyDoc().subdoc is automatically instantiated. Hint: default=SubDoc() WON'T work (that's why I've introduced create_default)
2010-04-17 01:23:14 +02:00
Florian Schlachter
ef172712da bugfix 2010-04-16 22:25:45 +02:00
Florian Schlachter
170c56bcb9 introduced min_length for a StringField 2010-04-16 18:13:11 +02:00
Florian Schlachter
f3ca9fa4c5 Make validation-lists possible. Example:
class Doc(Document):
        country = StringField(validation=['DE', 'AT', 'CH'])
2010-04-16 18:00:51 +02:00
Florian Schlachter
48facec524 Fixes tiny documentation error. Adds possibility to add custom validation methods to fields, e. g.:
class Customer(Document):
        country = StringField(validation=lambda value: value in ['DE', 'AT', 'CH'])

Replaced some str() with unicode() for i18n reasons.
2010-04-16 16:59:34 +02:00
Don Spaulding
ee0c75a26d Add choices keyword argument to BaseField.__init__() 2010-04-15 17:59:35 -05:00
Don Spaulding
e9c92f30ba Add description of each of the keyword arguments to BaseField.__init__(), adds description for choices too. 2010-04-15 17:57:23 -05:00
Florian Schlachter
0a074e52e0 Merge remote branch 'hmarr/master'
Conflicts:
	mongoengine/fields.py
2010-04-15 23:10:34 +02:00
Don Spaulding
da3f4c30e2 Fix doc typos 2010-04-14 22:40:56 -05:00
Harry Marr
2b08ca7c99 Merge branch 'SortedListField' of git://github.com/joshourisman/mongoengine 2010-04-12 17:41:09 +01:00
Josh Ourisman
c8e466a160 Moved SortedListField stuff into its own branch 2010-04-12 12:31:52 -04:00
Timothée Peignier
a39685d98c make get_or_create returns a tuple with the retrieved or created object and a boolean specifying whether a new object was created 2010-04-12 16:21:29 +01:00
Harry Marr
90200dbe9c Fixed DecimalField bug 2010-04-12 15:59:20 +01:00
Florian Schlachter
2304dac8e3 added GeoLocationField with auto index-creation for GEO2D 2010-03-30 00:04:39 +02:00
Florian Schlachter
38b2919c0d added emailfield 2010-03-29 22:02:33 +02:00
Deepak Thukral
207fd9fcb7 keeping import policy in mind 2010-03-29 11:27:50 +02:00
Deepak Thukral
fbcf58c48f updated documentation 2010-03-29 11:25:17 +02:00
Deepak Thukral
8f4a579df9 DoesNotExist and MultipleObjectsReturned now contributes Document class 2010-03-28 22:22:36 +02:00
Matt Dennewitz
600ca3bcf9 renamed 'test_near' to 'test_geospatial_operators', updated added ordering checks to test 2010-03-23 00:57:26 -05:00
Matt Dennewitz
a4d2f22fd2 added 'geo_indexes' to TopLevelDocumentMetaclass; added GeoPointField, a glorified [lat float, lng float] container; added geo lookup operators to QuerySet; added initial geo tests 2010-03-23 00:14:01 -05:00
Harry Marr
00c8d7e6f5 Bump to v0.3 2010-03-17 16:50:13 +00:00
Harry Marr
0d89e967f2 Merge branch 'mapreduce' of git://github.com/blackbrrr/mongoengine 2010-03-17 16:44:24 +00:00
blackbrrr
447f8d0113 MapReduceDocument.object works with custom primary keys. test included. 2010-03-17 11:31:17 -05:00
Harry Marr
60802796cb Made ListField validation exceptions more helpful 2010-03-17 15:10:10 +00:00
Harry Marr
5b42578cb1 Added ~ field name substitution to mapreduce funcs 2010-03-17 14:06:31 +00:00
Harry Marr
25a0a5364a Deprecated 'name' arg for fields in favour of 'db_field' 2010-03-17 13:47:23 +00:00
Harry Marr
047cc218a6 Merge branch 'mapreduce' of git://github.com/blackbrrr/mongoengine
Conflicts:
	mongoengine/queryset.py
2010-03-17 12:31:08 +00:00
Harry Marr
39fc862676 Merge branch 'upsert' of git://github.com/blackbrrr/mongoengine 2010-03-17 12:30:18 +00:00
blackbrrr
f47d926f29 touched up comments in advanced map/reduce test 2010-03-17 00:56:34 -05:00
blackbrrr
f4d0938e3d rewrite simple map/reduce test 2010-03-17 00:51:01 -05:00
blackbrrr
f156da4ec2 bumped version 2010-03-17 00:50:44 -05:00
blackbrrr
0c1e5da9a8 added mongoengine.MapReduceDocument to api ref 2010-03-17 00:50:07 -05:00
blackbrrr
d6b317c552 Merge branch 'master' into mapreduce 2010-03-17 00:34:29 -05:00
blackbrrr
01826c6876 Merge branch 'master' of github.com:blackbrrr/mongoengine into mapreduce 2010-03-17 00:34:19 -05:00
blackbrrr
0b62c9d2f6 Merge branch 'master' of git://github.com/hmarr/mongoengine 2010-03-17 00:34:00 -05:00
Deepak Thukral
72161a9b71 no message in expection in future version of python 2010-03-11 21:10:04 +01:00
Deepak Thukral
df8f4e7251 no message in expection in future version of python 2010-03-11 20:42:27 +01:00
blackbrrr
aa13ab37c4 fixed field_js merge artifact 2010-03-09 15:31:52 -06:00
blackbrrr
acda64a837 fixed field_js merge artifact 2010-03-09 15:31:28 -06:00
blackbrrr
49a001a93a re-added missing QuerySet._ordering 2010-03-09 15:28:55 -06:00
blackbrrr
22a6ec7794 merged conflicts 2010-03-09 15:19:14 -06:00
blackbrrr
26c6e4997c added 'upsert' arg to QuerySet.update and QuerySet.update_one 2010-03-08 21:59:54 -06:00
Harry Marr
d7086fc4a3 Added rewind behaviour and BinaryField to docs 2010-03-08 22:23:40 +00:00
Harry Marr
92150e07d3 Merge branch 'binary-fields' of git://github.com/benmur/mongoengine 2010-03-08 22:17:21 +00:00
Harry Marr
ac3c857e1a Added rewind to QuerySet, which is implicitly called when iteration finishes 2010-03-08 22:15:40 +00:00
Harry Marr
48e313fb44 Merge branch 'master' of git://github.com/iapain/mongoengine 2010-03-08 21:59:42 +00:00
Harry Marr
5390117275 Modified AUTHORS 2010-03-08 21:58:19 +00:00
Rached Ben Mustapha
0b3af2052f implement binary field size validation 2010-03-08 17:06:52 +01:00
Rached Ben Mustapha
bb19ba3eb6 Drop collection at the end of the test 2010-03-08 16:43:43 +01:00
Rached Ben Mustapha
879bf08d18 Simple implementation of BinaryField 2010-03-08 16:42:23 +01:00
Deepak Thukral
b99421e7ee added date_joined and normalization to email address 2010-03-05 07:55:12 +01:00
blackbrrr
3b6d8fab47 added AUTHORS file 2010-03-01 12:26:03 -06:00
Harry Marr
53c0cdc0c1 Added recursive and undefined document ref docs 2010-02-28 23:29:42 +00:00
Harry Marr
58f877de1a Added recursive / document name references 2010-02-28 23:16:51 +00:00
Harry Marr
95a7b33fb4 Changed how GenericReferenceFields are stored / queried 2010-02-28 23:15:21 +00:00
Harry Marr
81dd5adccf GenericReferenceField docs 2010-02-28 21:30:54 +00:00
Harry Marr
94e86a0be1 Merge branch 'gfk' of git://github.com/blackbrrr/mongoengine 2010-02-28 21:08:32 +00:00
Harry Marr
5b2dbfe007 Added tests for URLField and DecimalField 2010-02-28 18:25:40 +00:00
Harry Marr
4451843a39 Added docs for QuerySet.only 2010-02-28 17:52:29 +00:00
Harry Marr
5e2c5fa97b Merge branch 'regex-query-shortcuts' 2010-02-28 17:38:03 +00:00
blackbrrr
018b206177 added support for GenericReferenceField to ListField. could be cleaner, perhaps. 2010-02-26 17:38:38 -06:00
blackbrrr
03d31b1890 added global model registry and GenericReferenceField, a ReferenceField not bound to a particular model 2010-02-26 16:59:12 -06:00
Harry Marr
265776566e QuerySet.only field name translation and polymorphism fix 2010-02-26 19:43:26 +00:00
Harry Marr
6e77e32855 Fixed Q object ObjectId comparison issue 2010-02-26 17:13:19 +00:00
Harry Marr
0b1c506626 Added Q object support for regexes (inc. operator shortcuts) 2010-02-26 16:46:07 +00:00
Harry Marr
719a653375 Added match operator docs 2010-02-26 13:48:00 +00:00
Harry Marr
66520c77f8 Added regex match operators with test 2010-02-26 13:43:45 +00:00
Harry Marr
ab2d019349 Added server-side js docs 2010-02-26 13:23:15 +00:00
Harry Marr
d0e0b291df Implementation and tests for exec_js field substitution 2010-02-25 17:20:52 +00:00
Harry Marr
200e9eca92 Merge branch 'only_fields' of git://github.com/blackbrrr/mongoengine 2010-02-24 20:23:59 +00:00
Harry Marr
634f771547 QuerySet repr now uses limit and skip 2010-02-24 17:01:31 +00:00
Harry Marr
2996f8919d Limits of size 0 now return no results 2010-02-24 16:07:26 +00:00
blackbrrr
1b68efe7c7 updated QuerySet.only docstring 2010-02-24 09:52:39 -06:00
blackbrrr
a19a7b976c updated advanced map/reduce test to include scope; misc cleanup in queryset 2010-02-23 22:26:05 -06:00
Harry Marr
145b0c33fc Support 1-arg queryset managers, but warn about deprecation 2010-02-23 18:27:14 +00:00
blackbrrr
8b1a39f2c1 added QuerySet.only 2010-02-23 00:24:28 -06:00
Harry Marr
6dbc051409 Fixed in_bulk test 2010-02-23 01:14:00 +00:00
Harry Marr
c148a5bbfc Merge branch 'master' of git://github.com/ack/mongoengine 2010-02-23 01:12:56 +00:00
Deepak Thukral
90d9bd9723 added natural object comparision 2010-02-21 12:13:58 +01:00
Deepak Thukral
bc7e6ccf53 set_password returns user object, comp. with django 1.2 2010-02-21 11:51:21 +01:00
Albert Choi
6cab002214 missing DecimalField 2010-02-15 14:25:49 -08:00
blackbrrr
3762a69537 added QuerySet.in_bulk, bulk querying with ObjectIds 2010-02-14 21:02:55 -06:00
blackbrrr
348f7b5dfc merged master, fixed 1 merge conflict 2010-02-14 17:23:38 -06:00
blackbrrr
008a62e4e9 updated map/reduce documentation 2010-02-12 16:07:44 -06:00
blackbrrr
a4c5fa57e0 updated notes in map_reduce_advanced queryset test 2010-02-12 15:53:28 -06:00
blackbrrr
9be6c41af7 map/reduce result objects now only have 'key', 'value', and 'object' properties; MapReduceDocument.key_object now returns proper Document subclass; added finalize with Reddit ranking simulation; MapReduceDocuments now yielded; 2010-02-12 14:39:08 -06:00
blackbrrr
5c311eefb1 fixed merge conflict in queryset test 2010-02-12 09:59:09 -06:00
blackbrrr
d0ceb74a2e removed unused fields and tests 2010-02-12 09:57:09 -06:00
Harry Marr
ea1fe6a538 Fixed set/unset issue with ListFields 2010-02-12 11:21:51 +00:00
Florian Schlachter
a93509c9b3 Merge remote branch 'hmarr/master' 2010-02-12 10:19:47 +01:00
Harry Marr
210e9e23af Dereferencing of referenced documents within lists 2010-02-12 02:31:41 +00:00
blackbrrr
c4513f0286 merged master 2010-02-11 15:43:37 -06:00
Florian Schlachter
1114572b47 Merge remote branch 'hmarr/master' 2010-02-10 14:25:33 +01:00
Harry Marr
b2588d1c4f Changed neq to ne, fixed Q object in and nin 2010-02-10 12:35:41 +00:00
blackbrrr
69d3e0c4b6 added map/reduce support via QuerySet.map_reduce. map_reduce operations respect query specs and ordering, but ordering is currently only applied to map/reduce collection. map/reduce may eventually require its own QuerySet to avoid slicing conflicts. results are returned as lists of MapReduceDocument objects, dynamic objects representing the query. tests and documentation included. considered in the neighborhood of 'good start'. 2010-02-09 14:56:15 -06:00
Florian Schlachter
e2414d8fea Merge remote branch 'hmarr/master' 2010-02-05 00:36:26 +01:00
Florian Schlachter
24db0d1499 return db-object to allow low-level access from outside via connect() 2010-02-05 00:35:49 +01:00
Harry Marr
89f505bb13 Removed pool_size from connect, minor tidyup 2010-02-04 01:44:52 +00:00
Florian Schlachter
df5b1f3806 Merge remote branch 'hmarr/master' 2010-02-03 02:26:26 +01:00
Harry Marr
755deb3ffe Added DictField 2010-02-03 01:22:25 +00:00
Florian Schlachter
59f8c9f38e make mongoengine more international :) using unicode-strings; str(err) raises errors if it contains non-ascii chars/umlauts 2010-02-02 21:48:47 +01:00
Florian Schlachter
69e9b5d55e fixed unicode-bug; replaced str(err) with err.message 2010-02-02 21:44:11 +01:00
Florian Schlachter
a2d8b0ffbe Merge remote branch 'hmarr/master' 2010-02-02 18:49:52 +01:00
Harry Marr
0bbf3a3d76 Fixed EmbeddedDocument validation bug 2010-02-02 17:37:09 +00:00
Florian Schlachter
10de19d38b be kind and also accept an integer for a float field (so e.g. mymodel.floatfield = 9 is possible, instead of mymodel.floatfield = 9.0) 2010-01-31 18:06:25 +01:00
Florian Schlachter
73aff806f3 reset to master, keep working on the dirty-fields-patch in another branch 2010-01-31 18:00:01 +01:00
Florian Schlachter
963a223e7e Merge remote branch 'hmarr/master'
Conflicts:
	mongoengine/queryset.py
2010-01-31 17:43:56 +01:00
Florian Schlachter
bbfc2f416e keep track of dirty fields is still work in progress; EmbeddedDocuments still aren't tracked (TBD) 2010-01-31 15:43:40 +01:00
Harry Marr
e05d31eaaf Added get{,_or_create} docs 2010-01-31 13:47:27 +00:00
Florian Schlachter
431f006751 new save() method updates only dirty fields. fixes issue #18 2010-01-31 14:40:00 +01:00
Harry Marr
ffc9d7b152 Merge branch 'master' of git://github.com/flosch/mongoengine
Added unit test for get_or_create, merged flosch's get with
punteney's get.

Conflicts:
	mongoengine/queryset.py
2010-01-31 13:24:50 +00:00
Harry Marr
79604180db Merge branch 'master' of git://github.com/punteney/mongoengine 2010-01-31 13:11:20 +00:00
Florian Schlachter
7d6e117f68 added get-method to fetch exactly one document from the collection. catching pymongo's ObjectId-errors and raising mongoengine's ValidationError instead. 2010-01-31 01:11:37 +01:00
Florian Schlachter
b3cc2f990a improved get_or_create 2010-01-30 22:01:43 +01:00
Florian Schlachter
8d953f0bcb Added get_or_create-method 2010-01-30 21:12:46 +01:00
Harry Marr
5cac52720c Fixed querying on ReferenceFields using primary key 2010-01-27 15:57:11 +00:00
Harry Marr
bca6119db8 Minor tidyup 2010-01-26 19:36:19 +00:00
Harry Marr
568000805f EmbeddedDocuments may now be non-polymorphic 2010-01-25 01:00:04 +00:00
blackbrrr
3fb6307596 Merge branch 'master' of git://github.com/hmarr/mongoengine 2010-01-23 18:41:52 -06:00
blackbrrr
7aa0031dec reset 2010-01-23 15:39:26 -06:00
Harry Marr
2585f1b724 queryset_manager funcs now accept doc as arg 2010-01-23 17:16:01 +00:00
Harry Marr
470e08f616 exec_js functions now acknowledge Q objects 2010-01-23 03:05:27 +00:00
blackbrrr
f1e51f9708 Merge branch 'master' of git://github.com/hmarr/mongoengine into deferred_fields 2010-01-19 12:27:14 -06:00
James Punteney
e0becc109d Adding tests to test the get query 2010-01-16 14:51:13 -05:00
James Punteney
47e4dd40cd Making the query actually get called for get 2010-01-16 13:24:10 -05:00
James Punteney
c38faebc25 Adding a get method to the queryset that raises exceptions if more or less than one item is returned 2010-01-16 13:21:16 -05:00
Harry Marr
21b7d8f8ea Bump to v0.2.2 2010-01-16 16:42:51 +00:00
Harry Marr
3357b55fbf Indexing on ListFields now works properly 2010-01-16 15:35:01 +00:00
Harry Marr
f01add9ef5 Moved sections from user guide to separate pages 2010-01-16 14:06:45 +00:00
blackbrrr
b0b8e11c60 Merge branch 'master' into deferred_fields 2010-01-14 11:39:09 -06:00
blackbrrr
7e0fcb9e65 groundwork for deferred fields 2010-01-14 11:39:03 -06:00
blackbrrr
972235cf06 added build, dist, egg dirs to .gitignore 2010-01-14 11:37:07 -06:00
blackbrrr
b3c9a76619 Merge branch 'master' of git://github.com/hmarr/mongoengine 2010-01-14 11:32:39 -06:00
blackbrrr
5f84d6f8f8 added URLField, DecimalField, tests. 2010-01-14 11:32:28 -06:00
blackbrrr
1cdeb8130d ObjectIdField.to_python returns pymongo.objectid.ObjectId 2010-01-14 11:32:01 -06:00
Harry Marr
ce69428cc6 Moved validate() to BaseDocument 2010-01-13 16:41:57 +00:00
Harry Marr
1818cf7114 Added q_objs to __call__ args 2010-01-12 18:33:33 +00:00
blackbrrr
b375c41586 added Q object support to QuerySet.filter 2010-01-12 12:19:59 -06:00
blackbrrr
d85ee4e051 fixed merge conflict in BaseField.__init__ 2010-01-12 12:19:30 -06:00
blackbrrr
cfc394963f added queryset chaining via 'filter' method. test included. 2010-01-12 12:16:15 -06:00
48 changed files with 15683 additions and 1381 deletions

16
.gitignore vendored
View File

@@ -1,4 +1,16 @@
*.pyc
.*.swp
.*
!.gitignore
*~
*.py[co]
.*.sw[po]
*.egg
docs/.build
docs/_build
build/
dist/
mongoengine.egg-info/
env/
.settings
.project
.pydevproject
tests/bugfix.py

102
AUTHORS Normal file
View File

@@ -0,0 +1,102 @@
The PRIMARY AUTHORS are (and/or have been):
Ross Lawley <ross.lawley@gmail.com>
Harry Marr <harry@hmarr.com>
Matt Dennewitz <mattdennewitz@gmail.com>
Deepak Thukral <iapain@yahoo.com>
Florian Schlachter <flori@n-schlachter.de>
Steve Challis <steve@stevechallis.com>
Wilson Júnior <wilsonpjunior@gmail.com>
Dan Crosta https://github.com/dcrosta
CONTRIBUTORS
Dervived 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
* Steve Challis
* flosch
* Deepak Thukral
* Colin Howe
* Wilson Júnior
* Alistair Roche
* Dan Crosta
* Viktor Kerkez
* Stephan Jaekel
* Rached Ben Mustapha
* Greg Turner
* Daniel Hasselrot
* Mircea Pasoi
* Matt Chisholm
* James Punteney
* TimothéePeignier
* Stuart Rackham
* Serge Matveenko
* Matt Dennewitz
* Don Spaulding
* Ales Zoulek
* sshwsfc
* sib
* Samuel Clay
* Nick Vlku
* martin
* Flavio Amieiro
* Анхбаяр Лхагвадорж
* Zak Johnson
* Victor Farazdagi
* vandersonmota
* Theo Julienne
* sp
* Slavi Pantaleev
* Richard Henry
* Nicolas Perriault
* Nick Vlku Jr
* Michael Henson
* Leo Honkanen
* kuno
* Josh Ourisman
* Jaime
* Igor Ivanov
* Gregg Lind
* Gareth Lloyd
* Albert Choi
* John Arnfield
* grubberr
* Paul Aliagas
* Paul Cunnane
* Julien Rebetez
* Marc Tamlyn
* Karim Allah
* Adam Parrish
* jpfarias
* jonrscott
* Alice Zoë Bevan-McGregor
* Stephen Young
* tkloc
* aid
* yamaneko1212
* dave mankoff
* Alexander G. Morano
* jwilder
* Joe Shaw
* Adam Flynn
* Ankhbayar
* Jan Schrewe
* David Koblas
* Crittercism
* Alvin Liang
* andrewmlevy
* Chris Faulkner
* Ashwin Purohit
* Shalabh Aggarwal
* Chris Williams
* Robert Kajic
* Jacob Peddicord
* Nils Hasenbanck
* mostlystatic

View File

@@ -1,6 +1,6 @@
include MANIFEST.in
include README.rst
include LICENSE
include AUTHORS
recursive-include docs *
prune docs/_build/*
recursive-include tests *
recursive-exclude * *.pyc *.swp
prune docs/_build

View File

@@ -3,20 +3,21 @@ MongoEngine
===========
:Info: MongoEngine is an ORM-like layer on top of PyMongo.
:Author: Harry Marr (http://github.com/hmarr)
:Maintainer: Ross Lawley (http://github.com/rozza)
About
=====
MongoEngine is a Python Object-Document Mapper for working with MongoDB.
Documentation available at http://hmarr.com/mongoengine/ - there is currently
a `tutorial <http://hmarr.com/mongoengine/tutorial.html>`_, a `user guide
<http://hmarr.com/mongoengine/userguide.html>`_ and an `API reference
<http://hmarr.com/mongoengine/apireference.html>`_.
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
<http://readthedocs.org/docs/mongoengine-odm/en/latest/userguide.html>`_ and an `API reference
<http://readthedocs.org/docs/mongoengine-odm/en/latest/apireference.html>`_.
Installation
============
If you have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
you can use ``easy_install mongoengine``. Otherwise, you can download the
source from `GitHub <http://github.com/hmarr/mongoengine>`_ and run ``python
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
@@ -82,8 +83,16 @@ 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``.
Community
=========
- `MongoEngine Users mailing list
<http://groups.google.com/group/mongoengine-users>`_
- `MongoEngine Developers mailing list
<http://groups.google.com/group/mongoengine-dev>`_
- `#mongoengine IRC channel <irc://irc.freenode.net/mongoengine>`_
Contributing
============
The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_ - to
The source is available on `GitHub <http://github.com/MongoEngine/mongoengine>`_ - to
contribute to the project, fork it on GitHub and send a pull request, all
contributions and suggestions are welcome!

182
benchmark.py Normal file
View File

@@ -0,0 +1,182 @@
#!/usr/bin/env python
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
1.1141769886
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
2.37724113464
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
1.92479610443
0.5.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
1.10552310944
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
16.5169169903
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
14.9446101189
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
14.912801981
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force=True
14.9617750645
Performance
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
1.10072994232
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
5.27341103554
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
4.49365401268
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
4.43459296227
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force=True
4.40114378929
"""
setup = """
from pymongo import Connection
connection = Connection()
connection.drop_database('timeit_test')
"""
stmt = """
from pymongo import Connection
connection = Connection()
db = connection.timeit_test
noddy = db.noddy
for i in xrange(10000):
example = {'fields': {}}
for j in range(20):
example['fields']["key"+str(j)] = "value "+str(j)
noddy.insert(example)
myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - Pymongo"""
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
setup = """
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()
"""
stmt = """
for i in xrange(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save()
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine"""
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
stmt = """
for i in xrange(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(safe=False, validate=False)
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, safe=False, validate=False"""
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
stmt = """
for i in xrange(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
noddy.save(safe=False, validate=False, cascade=False)
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False"""
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
stmt = """
for i in xrange(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)
myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, force=True"""
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
if __name__ == "__main__":
main()

View File

@@ -6,6 +6,7 @@ Connecting
==========
.. autofunction:: mongoengine.connect
.. autofunction:: mongoengine.register_connection
Documents
=========
@@ -15,12 +16,21 @@ Documents
.. attribute:: objects
A :class:`~mongoengine.queryset.QuerySet` object that is created lazily
A :class:`~mongoengine.queryset.QuerySet` object that is created lazily
on access.
.. autoclass:: mongoengine.EmbeddedDocument
:members:
.. autoclass:: mongoengine.DynamicDocument
:members:
.. autoclass:: mongoengine.DynamicEmbeddedDocument
:members:
.. autoclass:: mongoengine.document.MapReduceDocument
:members:
Querying
========
@@ -28,26 +38,31 @@ Querying
:members:
.. automethod:: mongoengine.queryset.QuerySet.__call__
.. autofunction:: mongoengine.queryset.queryset_manager
Fields
======
.. autoclass:: mongoengine.StringField
.. autoclass:: mongoengine.URLField
.. autoclass:: mongoengine.EmailField
.. autoclass:: mongoengine.IntField
.. autoclass:: mongoengine.FloatField
.. autoclass:: mongoengine.BooleanField
.. autoclass:: mongoengine.DecimalField
.. autoclass:: mongoengine.DateTimeField
.. autoclass:: mongoengine.EmbeddedDocumentField
.. autoclass:: mongoengine.ComplexDateTimeField
.. autoclass:: mongoengine.ListField
.. autoclass:: mongoengine.SortedListField
.. autoclass:: mongoengine.DictField
.. autoclass:: mongoengine.MapField
.. autoclass:: mongoengine.ObjectIdField
.. autoclass:: mongoengine.ReferenceField
.. autoclass:: mongoengine.GenericReferenceField
.. autoclass:: mongoengine.EmbeddedDocumentField
.. autoclass:: mongoengine.GenericEmbeddedDocumentField
.. autoclass:: mongoengine.BooleanField
.. autoclass:: mongoengine.FileField
.. autoclass:: mongoengine.BinaryField
.. autoclass:: mongoengine.GeoPointField
.. autoclass:: mongoengine.SequenceField

View File

@@ -2,25 +2,253 @@
Changelog
=========
Changes in 0.6.4
================
- Refactored connection / fixed replicasetconnection
- Bug fix for unknown connection alias error message
- Sessions support Django 1.3 and Django 1.4
- Minor fix for ReferenceField
Changes in 0.6.3
================
- Updated sessions for Django 1.4
- Bug fix for updates where listfields contain embedded documents
- Bug fix for collection naming and mixins
Changes in 0.6.2
================
- Updated documentation for ReplicaSet connections
- Hack round _types issue with SERVER-5247 - querying other arrays may also cause problems.
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
- No longer always upsert on save for items with a '_id'
- Error raised if update doesn't have an operation
- DeReferencing is now thread safe
- Errors raised if trying to perform a join in a query
- Updates can now take __raw__ queries
- Added custom 2D index declarations
- Added replicaSet connection support
- Updated deprecated imports from pymongo (safe for pymongo 2.2)
- Added uri support for connections
- Added scalar for efficiently returning partial data values (aliased to values_list)
- Fixed limit skip bug
- Improved Inheritance / Mixin
- Added sharding support
- Added pymongo 2.1 support
- Fixed Abstract documents can now declare indexes
- Added db_alias support to individual documents
- Fixed GridFS documents can now be pickled
- Added Now raises an InvalidDocumentError when declaring multiple fields with the same db_field
- Added InvalidQueryError when calling with_id with a filter
- Added support for DBRefs in distinct()
- Fixed issue saving False booleans
- Fixed issue with dynamic documents deltas
- Added Reverse Delete Rule support to ListFields - MapFields aren't supported
- Added customisable cascade kwarg options
- Fixed Handle None values for non-required fields
- Removed Document._get_subclasses() - no longer required
- Fixed bug requiring subclasses when not actually needed
- Fixed deletion of dynamic data
- Added support for the $elementMatch operator
- Added reverse option to SortedListFields
- Fixed dereferencing - multi directional list dereferencing
- Fixed issue creating indexes with recursive embedded documents
- Fixed recursive lookup in _unique_with_indexes
- Fixed passing ComplexField defaults to constructor for ReferenceFields
- Fixed validation of DictField Int keys
- Added optional cascade saving
- Fixed dereferencing - max_depth now taken into account
- Fixed document mutation saving issue
- Fixed positional operator when replacing embedded documents
- Added Non-Django Style choices back (you can have either)
- Fixed __repr__ of a sliced queryset
- Added recursive validation error of documents / complex fields
- Fixed breaking during queryset iteration
- Added pre and post bulk-insert signals
- Added ImageField - requires PIL
- Fixed Reference Fields can be None in get_or_create / queries
- Fixed accessing pk on an embedded document
- Fixed calling a queryset after drop_collection now recreates the collection
- Add field name to validation exception messages
- Added UUID field
- Improved efficiency of .get()
- Updated ComplexFields so if required they won't accept empty lists / dicts
- Added spec file for rpm-based distributions
- Fixed ListField so it doesnt accept strings
- Added DynamicDocument and EmbeddedDynamicDocument classes for expando schemas
Changes in v0.5.2
=================
- A Robust Circular reference bugfix
Changes in v0.5.1
=================
- Fixed simple circular reference bug
Changes in v0.5
===============
- Added InvalidDocumentError - so Document core methods can't be overwritten
- Added GenericEmbeddedDocument - so you can embed any type of embeddable document
- Added within_polygon support - for those with mongodb 1.9
- Updated sum / average to use map_reduce as db.eval doesn't work in sharded environments
- Added where() - filter to allowing users to specify query expressions as Javascript
- Added SequenceField - for creating sequential counters
- Added update() convenience method to a document
- Added cascading saves - so changes to Referenced documents are saved on .save()
- Added select_related() support
- Added support for the positional operator
- Updated geo index checking to be recursive and check in embedded documents
- 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 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
- Fixed saving so sets updated values rather than overwrites
- Added ComplexDateTimeField - Handles datetimes correctly with microseconds
- Added ComplexBaseField - for improved flexibility and performance
- Added get_FIELD_display() method for easy choice field displaying
- Added queryset.slave_okay(enabled) method
- Updated queryset.timeout(enabled) and queryset.snapshot(enabled) to be chainable
- Added insert method for bulk inserts
- Added blinker signal support
- Added query_counter context manager for tests
- Added map_reduce method item_frequencies and set as default (as db.eval doesn't work in sharded environments)
- Added inline_map_reduce option to map_reduce
- Updated connection exception so it provides more info on the cause.
- Added searching multiple levels deep in ``DictField``
- Added ``DictField`` entries containing strings to use matching operators
- Added ``MapField``, similar to ``DictField``
- Added Abstract Base Classes
- Added Custom Objects Managers
- Added sliced subfields updating
- Added ``NotRegistered`` exception if dereferencing ``Document`` not in the registry
- Added a write concern for ``save``, ``update``, ``update_one`` and ``get_or_create``
- Added slicing / subarray fetching controls
- Fixed various unique index and other index issues
- Fixed threaded connection issues
- Added spherical geospatial query operators
- Updated queryset to handle latest version of pymongo
map_reduce now requires an output.
- Added ``Document`` __hash__, __ne__ for pickling
- Added ``FileField`` optional size arg for read method
- Fixed ``FileField`` seek and tell methods for reading files
- Added ``QuerySet.clone`` to support copying querysets
- Fixed item_frequencies when using name thats the same as a native js function
- Added reverse delete rules
- Fixed issue with unset operation
- Fixed Q-object bug
- Added ``QuerySet.all_fields`` resets previous .only() and .exclude()
- Added ``QuerySet.exclude``
- Added django style choices
- Fixed order and filter issue
- Added ``QuerySet.only`` subfield support
- Added creation_counter to ``BaseField`` allowing fields to be sorted in the
way the user has specified them
- Fixed various errors
- Added many tests
Changes in v0.4
===============
- Added ``GridFSStorage`` Django storage backend
- Added ``FileField`` for GridFS support
- New Q-object implementation, which is no longer based on Javascript
- Added ``SortedListField``
- Added ``EmailField``
- Added ``GeoPointField``
- Added ``exact`` and ``iexact`` match operators to ``QuerySet``
- Added ``get_document_or_404`` and ``get_list_or_404`` Django shortcuts
- Added new query operators for Geo queries
- Added ``not`` query operator
- Added new update operators: ``pop`` and ``add_to_set``
- Added ``__raw__`` query parameter
- Added support for custom querysets
- Fixed document inheritance primary key issue
- Added support for querying by array element position
- Base class can now be defined for ``DictField``
- Fixed MRO error that occured on document inheritance
- Added ``QuerySet.distinct``, ``QuerySet.create``, ``QuerySet.snapshot``,
``QuerySet.timeout`` and ``QuerySet.all``
- Subsequent calls to ``connect()`` now work
- Introduced ``min_length`` for ``StringField``
- Fixed multi-process connection issue
- Other minor fixes
Changes in v0.3
===============
- Added MapReduce support
- Added ``contains``, ``startswith`` and ``endswith`` query operators (and
case-insensitive versions that are prefixed with 'i')
- Deprecated fields' ``name`` parameter, replaced with ``db_field``
- Added ``QuerySet.only`` for only retrieving specific fields
- Added ``QuerySet.in_bulk()`` for bulk querying using ids
- ``QuerySet``\ s now have a ``rewind()`` method, which is called automatically
when the iterator is exhausted, allowing ``QuerySet``\ s to be reused
- Added ``DictField``
- Added ``URLField``
- Added ``DecimalField``
- Added ``BinaryField``
- Added ``GenericReferenceField``
- Added ``get()`` and ``get_or_create()`` methods to ``QuerySet``
- ``ReferenceField``\ s may now reference the document they are defined on
(recursive references) and documents that have not yet been defined
- ``Document`` objects may now be compared for equality (equal if _ids are
equal and documents are of same type)
- ``QuerySet`` update methods now have an ``upsert`` parameter
- Added field name substitution for Javascript code (allows the user to use the
Python names for fields in JS, which are later substituted for the real field
names)
- ``Q`` objects now support regex querying
- Fixed bug where referenced documents within lists weren't properly
dereferenced
- ``ReferenceField``\ s may now be queried using their _id
- Fixed bug where ``EmbeddedDocuments`` couldn't be non-polymorphic
- ``queryset_manager`` functions now accept two arguments -- the document class
as the first and the queryset as the second
- Fixed bug where ``QuerySet.exec_js`` ignored ``Q`` objects
- Other minor fixes
Changes in v0.2.2
=================
- Fixed bug that prevented indexes from being used on ``ListField``\ s
- ``Document.filter()`` added as an alias to ``Document.__call__()``
- ``validate()`` may now be used on ``EmbeddedDocument``\ s
Changes in v0.2.1
=================
- Added a MongoEngine backend for Django sessions
- Added force_insert to Document.save()
- Improved querying syntax for ListField and EmbeddedDocumentField
- Added support for user-defined primary keys (_ids in MongoDB)
- Added ``force_insert`` to ``Document.save()``
- Improved querying syntax for ``ListField`` and ``EmbeddedDocumentField``
- Added support for user-defined primary keys (``_id`` in MongoDB)
Changes in v0.2
===============
- Added Q class for building advanced queries
- Added QuerySet methods for atomic updates to documents
- Fields may now specify ``unique=True`` to enforce uniqueness across a collection
- Added ``Q`` class for building advanced queries
- Added ``QuerySet`` methods for atomic updates to documents
- Fields may now specify ``unique=True`` to enforce uniqueness across a
collection
- Added option for default document ordering
- Fixed bug in index definitions
Changes in v0.1.3
=================
- Added Django authentication backend
- Added Document.meta support for indexes, which are ensured just before
- Added ``Document.meta`` support for indexes, which are ensured just before
querying takes place
- A few minor bugfixes
@@ -30,8 +258,8 @@ Changes in v0.1.2
- Query values may be processed before before being used in queries
- Made connections lazy
- Fixed bug in Document dictionary-style access
- Added BooleanField
- Added Document.reload method
- Added ``BooleanField``
- Added ``Document.reload()`` method
Changes in v0.1.1

View File

@@ -22,7 +22,7 @@ sys.path.append(os.path.abspath('..'))
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc']
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
@@ -38,7 +38,7 @@ master_doc = 'index'
# General information about the project.
project = u'MongoEngine'
copyright = u'2009, Harry Marr'
copyright = u'2009-2012, 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
@@ -121,7 +121,7 @@ html_theme_path = ['_themes']
# 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,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
#html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.

View File

@@ -2,32 +2,36 @@
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
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
: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
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::
AUTHENTICATION_BACKENDS = (
'mongoengine.django.auth.MongoEngineBackend',
)
The :mod:`~mongoengine.django.auth` module also contains a
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
@@ -40,3 +44,47 @@ session backend, ensure that your settings module has
into you settings module::
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

65
docs/guide/connecting.rst Normal file
View File

@@ -0,0 +1,65 @@
.. _guide-connecting:
=====================
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::
from mongoengine import connect
connect('project1', username='webapp', password='pwd123')
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
: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
:func:`~mongoengine.connect`::
connect('project1', host='mongodb://localhost/database_name')
ReplicaSets
===========
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.
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.
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::
class User(Document):
name = StringField()
meta = {"db_alias": "user-db"}
class Book(Document):
name = StringField()
meta = {"db_alias": "book-db"}
class AuthorBooks(Document):
author = ReferenceField(User)
book = ReferenceField(Book)
meta = {"db_alias": "users-books-db"}

View File

@@ -0,0 +1,563 @@
==================
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
is enforced at a database level.
Defining a document's schema
============================
MongoEngine allows you to define schemata for documents as this helps to reduce
coding errors, and allows for utility methods to be defined on fields which may
be present.
To define a schema for a document, create a class that inherits from
:class:`~mongoengine.Document`. Fields are specified by adding **field
objects** as class attributes to the document class::
from mongoengine import *
import datetime
class Page(Document):
title = StringField(max_length=200, required=True)
date_modified = DateTimeField(default=datetime.datetime.now)
Dynamic document schemas
========================
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.
:class:`~mongoengine.DynamicDocument` documents work in the same way as
:class:`~mongoengine.Document` but any data / attributes set to them will also
be saved ::
from mongoengine import *
class Page(DynamicDocument):
title = StringField(max_length=200, required=True)
# Create a new page and add tags
>>> page = Page(title='Using MongoEngine')
>>> page.tags = ['mongodb', 'mongoengine']
>>> page.save()
>>> Page.objects(tags='mongoengine').count()
>>> 1
..note::
There is one caveat on Dynamic Documents: fields cannot start with `_`
Fields
======
By default, fields are not required. To make a field mandatory, set the
:attr:`required` keyword argument of a field to ``True``. Fields also may have
validation constraints available (such as :attr:`max_length` in the example
above). Fields may also take default values, which will be used if a value is
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.StringField`
* :class:`~mongoengine.URLField`
* :class:`~mongoengine.EmailField`
* :class:`~mongoengine.IntField`
* :class:`~mongoengine.FloatField`
* :class:`~mongoengine.DecimalField`
* :class:`~mongoengine.DateTimeField`
* :class:`~mongoengine.ComplexDateTimeField`
* :class:`~mongoengine.ListField`
* :class:`~mongoengine.SortedListField`
* :class:`~mongoengine.DictField`
* :class:`~mongoengine.MapField`
* :class:`~mongoengine.ObjectIdField`
* :class:`~mongoengine.ReferenceField`
* :class:`~mongoengine.GenericReferenceField`
* :class:`~mongoengine.EmbeddedDocumentField`
* :class:`~mongoengine.GenericEmbeddedDocumentField`
* :class:`~mongoengine.BooleanField`
* :class:`~mongoengine.FileField`
* :class:`~mongoengine.BinaryField`
* :class:`~mongoengine.GeoPointField`
* :class:`~mongoengine.SequenceField`
Field arguments
---------------
Each field type can be customized by keyword arguments. The following keyword
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.base.ValidationError` will be raised when the document is
validated.
: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
<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`)::
class ExampleFirst(Document):
# Default an empty list
values = ListField(IntField(), default=list)
class ExampleSecond(Document):
# Default a set of values
values = ListField(IntField(), default=lambda: [1,2,3])
class ExampleDangerous(Document):
# This can make an .append call to add values to the default (and all the following objects),
# instead to just an object
values = ListField(IntField(), default=[1,2,3])
:attr:`unique` (Default: False)
When True, no documents in the collection will have the same value for this
field.
:attr:`unique_with` (Default: None)
A field name (or list of field names) that when taken together with this
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.
:attr:`choices` (Default: None)
An iterable (e.g. a list or tuple) 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
human readable key ::
SIZE = (('S', 'Small'),
('M', 'Medium'),
('L', 'Large'),
('XL', 'Extra Large'),
('XXL', 'Extra Extra Large'))
class Shirt(Document):
size = StringField(max_length=3, choices=SIZE)
Or a flat iterable just containing values ::
SIZE = ('S', 'M', 'L', 'XL', 'XXL')
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
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
argument, which specifies which type elements may be stored within the list::
class Page(Document):
tags = ListField(StringField(max_length=50))
Embedded documents
------------------
MongoDB has the ability to embed documents within other documents. Schemata may
be defined for these embedded documents, just as they may be for regular
documents. To create an embedded document, just define a document as usual, but
inherit from :class:`~mongoengine.EmbeddedDocument` rather than
:class:`~mongoengine.Document`::
class Comment(EmbeddedDocument):
content = StringField()
To embed the document within another document, use the
:class:`~mongoengine.EmbeddedDocumentField` field type, providing the embedded
document class as the first argument::
class Page(Document):
comments = ListField(EmbeddedDocumentField(Comment))
comment1 = Comment(content='Good work!')
comment2 = Comment(content='Nice article!')
page = Page(comments=[comment1, comment2])
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::
class SurveyResponse(Document):
date = DateTimeField()
user = ReferenceField(User)
answers = DictField()
survey_response = SurveyResponse(date=datetime.now(), user=request.user)
response_form = ResponseForm(request.POST)
survey_response.answers = response_form.cleaned_data()
survey_response.save()
Dictionaries can store complex data, other dictionaries, lists, references to
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
first argument to the constructor, then simply assign document objects to the
field::
class User(Document):
name = StringField()
class Page(Document):
content = StringField()
author = ReferenceField(User)
john = User(name="John Smith")
john.save()
post = Page(content="Test Page")
post.author = john
post.save()
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
being defined, use the string ``'self'`` in place of the document class as the
argument to :class:`~mongoengine.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::
class Employee(Document):
name = StringField()
boss = ReferenceField('self')
profile_page = ReferenceField('ProfilePage')
class ProfilePage(Document):
content = StringField()
Dealing with deletion of referred documents
'''''''''''''''''''''''''''''''''''''''''''
By default, MongoDB doesn't check the integrity of your data, so deleting
documents that other documents still hold references to will lead to consistency
issues. Mongoengine's :class:`ReferenceField` adds some functionality to
safeguard against these kinds of database integrity problems, providing each
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):
...
profile_page = ReferenceField('ProfilePage', reverse_delete_rule=mongoengine.NULLIFY)
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
well. If a whole batch of employees is removed, all profile pages that are
linked are removed as well.
Its value can take any of the following constants:
:const:`mongoengine.DO_NOTHING`
This is the default and won't do anything. Deletes are fast, but may cause
database inconsistency or dangling references.
:const:`mongoengine.DENY`
Deletion is denied if there still exist references to the object being
deleted.
:const:`mongoengine.NULLIFY`
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
are deleted first.
.. warning::
A safety note on setting up these delete rules! Since the delete rules are
not recorded on the database level by MongoDB itself, but instead at runtime,
in-memory, by the MongoEngine module, it is of the upmost importance
that the module that declares the relationship is loaded **BEFORE** the
delete is invoked.
If, for example, the :class:`Employee` object lives in the
:mod:`payroll` app, and the :class:`ProfilePage` in the :mod:`people`
app, it is extremely important that the :mod:`people` app is loaded
before any employee is removed, because otherwise, MongoEngine could
never know this relationship exists.
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.
Generic reference fields
''''''''''''''''''''''''
A second kind of reference field also exists,
:class:`~mongoengine.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::
class Link(Document):
url = StringField()
class Post(Document):
title = StringField()
class Bookmark(Document):
bookmark_object = GenericReferenceField()
link = Link(url='http://hmarr.com/mongoengine/')
link.save()
post = Post(title='Using MongoEngine')
post.save()
Bookmark(bookmark_object=link).save()
Bookmark(bookmark_object=post).save()
.. note::
Using :class:`~mongoengine.GenericReferenceField`\ s is slightly less
efficient than the standard :class:`~mongoengine.ReferenceField`\ s, so if
you will only be referencing one document type, prefer the standard
:class:`~mongoengine.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
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
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::
class User(Document):
username = StringField(unique=True)
first_name = StringField()
last_name = StringField(unique_with='first_name')
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`
method::
class Recipient(Document):
name = StringField()
email = EmailField()
recipient = Recipient(name='admin', email='root@localhost')
recipient.save() # will raise a ValidationError while
recipient.save(validate=False) # won't
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
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
set :attr:`collection` to the name of the collection that you want your
document class to use::
class Page(Document):
title = StringField(max_length=200, required=True)
meta = {'collection': 'cmsPage'}
Capped collections
------------------
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).
The following example shows a :class:`Log` document that will be limited to
1000 entries and 2MB of disk space::
class Log(Document):
ip_address = StringField()
meta = {'max_documents': 1000, 'max_size': 2000000}
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. ::
class Page(Document):
title = StringField()
rating = StringField()
meta = {
'indexes': ['title', ('title', '-rating')]
}
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:`sparse` (Default: False)
Whether the index should be sparse.
:attr:`unique` (Default: False)
Whether the index should be sparse.
.. warning::
Inheritance adds extra indices.
If don't need inheritance for a document turn inheritance off - see :ref:`document-inheritance`.
Geospatial indexes
---------------------------
Geospatial indexes will be automatically created for all
:class:`~mongoengine.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
point. To create a geospatial index you must prefix the field with the
***** sign. ::
class Place(Document):
location = DictField()
meta = {
'indexes': [
'*location.point',
],
}
Ordering
========
A default ordering can be specified for your
:class:`~mongoengine.queryset.QuerySet` using the :attr:`ordering` attribute of
:attr:`~mongoengine.Document.meta`. Ordering will be applied when the
:class:`~mongoengine.queryset.QuerySet` is created, and can be overridden by
subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
from datetime import datetime
class BlogPost(Document):
title = StringField()
published_date = DateTimeField()
meta = {
'ordering': ['-published_date']
}
blog_post_1 = BlogPost(title="Blog Post #1")
blog_post_1.published_date = datetime(2010, 1, 5, 0, 0 ,0)
blog_post_2 = BlogPost(title="Blog Post #2")
blog_post_2.published_date = datetime(2010, 1, 6, 0, 0 ,0)
blog_post_3 = BlogPost(title="Blog Post #3")
blog_post_3.published_date = datetime(2010, 1, 7, 0, 0 ,0)
blog_post_1.save()
blog_post_2.save()
blog_post_3.save()
# get the "first" BlogPost using default ordering
# from BlogPost.meta.ordering
latest_post = BlogPost.objects.first()
assert latest_post.title == "Blog Post #3"
# override default ordering, order BlogPosts by "published_date"
first_post = BlogPost.objects.order_by("+published_date").first()
assert first_post.title == "Blog Post #1"
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`.
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 LogEntry(Document):
machine = StringField()
app = StringField()
timestamp = DateTimeField()
data = StringField()
meta = {
'shard_key': ('machine', 'timestamp',)
}
.. _document-inheritance:
Document inheritance
====================
To create a specialised type of a :class:`~mongoengine.Document` you have
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::
# Stored in a collection named 'page'
class Page(Document):
title = StringField(max_length=200, required=True)
meta = {'allow_inheritance': True}
# Also stored in the collection named 'page'
class DatedPage(Page):
date = DateTimeField()
.. note:: From 0.7 onwards you must declare `allow_inheritance` in the document meta.
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::
# 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,
}

View File

@@ -0,0 +1,95 @@
===================
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.
You may provide values for any of the fields on the document::
>>> page = Page(title="Test Page")
>>> page.title
'Test Page'
You may also assign values to the document's fields using standard object
attribute syntax::
>>> page.title = "Example Page"
>>> page.title
'Example Page'
Saving and deleting documents
=============================
MongoEngine tracks changes to documents to provide efficient saving. To save
the document to the database, call the :meth:`~mongoengine.Document.save` method.
If the document does not exist in the database, it will be created. If it does
already exist, then any changes will be updated atomically. For example::
>>> page = Page(title="Test Page")
>>> page.save() # Performs an insert
>>> page.title = "My Page"
>>> page.save() # Performs an atomic set on the title field.
.. note::
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
.. seealso::
:ref:`guide-atomic-updates`
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.
Deleting documents
------------------
To delete a document, call the :meth:`~mongoengine.Document.delete` method.
Note that this will only work if the document exists in the database and has a
valid :attr:`id`.
Document IDs
============
Each document in the database has a unique id. This may be accessed through the
:attr:`id` attribute on :class:`~mongoengine.Document` objects. Usually, the id
will be generated automatically by the database server when the object is save,
meaning that you may only access the :attr:`id` field once a document has been
saved::
>>> page = Page(title="Test Page")
>>> page.id
>>> page.save()
>>> page.id
ObjectId('123456789abcdef000000000')
Alternatively, you may define one of your own fields to be the document's
"primary key" by providing ``primary_key=True`` as a keyword argument to a
field's constructor. Under the hood, MongoEngine will use this field as the
:attr:`id`; in fact :attr:`id` is actually aliased to your primary key field so
you may still use :attr:`id` to access the primary key if you want::
>>> class User(Document):
... email = StringField(primary_key=True)
... name = StringField()
...
>>> bob = User(email='bob@example.com', name='Bob')
>>> bob.save()
>>> 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`::
>>> page = Page(title="Another Test Page")
>>> page.save()
>>> page.id == page.pk
.. note::
If you define your own primary key field, the field implicitly becomes
required, so a :class:`ValidationError` will be thrown if you don't provide
it.

84
docs/guide/gridfs.rst Normal file
View File

@@ -0,0 +1,84 @@
======
GridFS
======
.. versionadded:: 0.4
Writing
-------
GridFS support comes in the form of the :class:`~mongoengine.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,
a document is created to store details about animals, including a photo::
class Animal(Document):
genus = StringField()
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.save()
Retrieval
---------
So using the :class:`~mongoengine.FileField` is just like using any other
field. The file can also be retrieved just as easily::
marmot = Animal.objects(genus='Marmota').first()
photo = marmot.photo.read()
content_type = marmot.photo.content_type
Streaming
---------
Streaming data into a :class:`~mongoengine.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`::
marmot.photo.new_file()
marmot.photo.write('some_image_data')
marmot.photo.write('some_more_image_data')
marmot.photo.close()
marmot.photo.save()
Deletion
--------
Deleting stored files is achieved with the :func:`delete` method::
marmot.photo.delete()
.. note::
The FileField in a Document actually only stores the ID of a file in a
separate GridFS collection. This means that deleting a document
with a defined FileField does not actually delete the file. You must be
careful to delete any files in a Document as above before deleting the
Document itself.
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')
marmot.photo.replace(another_marmot, content_type='image/png')

14
docs/guide/index.rst Normal file
View File

@@ -0,0 +1,14 @@
==========
User Guide
==========
.. toctree::
:maxdepth: 2
installing
connecting
defining-documents
document-instances
querying
gridfs
signals

31
docs/guide/installing.rst Normal file
View File

@@ -0,0 +1,31 @@
======================
Installing MongoEngine
======================
To use MongoEngine, you will need to download `MongoDB <http://mongodb.org/>`_
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`:
.. code-block:: console
$ pip install mongoengine
Alternatively, if you don't have setuptools installed, `download it from PyPi
<http://pypi.python.org/pypi/mongoengine/>`_ and run
.. code-block:: console
$ 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:
.. code-block:: console
$ git clone git://github.com/hmarr/mongoengine
$ cd mongoengine
$ python setup.py install

545
docs/guide/querying.rst Normal file
View File

@@ -0,0 +1,545 @@
=====================
Querying the database
=====================
:class:`~mongoengine.Document` classes have an :attr:`objects` attribute, which
is used for accessing the objects in the database associated with the class.
The :attr:`objects` attribute is actually a
:class:`~mongoengine.queryset.QuerySetManager`, which creates and returns a new
:class:`~mongoengine.queryset.QuerySet` object on access. The
:class:`~mongoengine.queryset.QuerySet` object may be iterated over to
fetch documents from the database::
# Prints out the names of all the users in the database
for user in User.objects:
print user.name
.. 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.
Filtering queries
=================
The query may be filtered by calling the
:class:`~mongoengine.queryset.QuerySet` object with field lookup keyword
arguments. The keys in the keyword arguments correspond to fields on the
:class:`~mongoengine.Document` you are querying::
# This will return a QuerySet that will only iterate over users whose
# 'country' field is set to 'uk'
uk_users = User.objects(country='uk')
Fields on embedded documents may also be referred to using field lookup syntax
by using a double-underscore in place of the dot in object attribute access
syntax::
# This will return a QuerySet that will only iterate over pages that have
# been written by a user whose 'country' field is set to 'uk'
uk_pages = Page.objects(author__country='uk')
Query operators
===============
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
young_users = Users.objects(age__lte=18)
Available operators are as follows:
* ``ne`` -- not equal to
* ``lt`` -- less than
* ``lte`` -- less than or equal to
* ``gt`` -- greater than
* ``gte`` -- greater than or equal to
* ``not`` -- negate a standard check, may be used before other operators (e.g.
``Q(age__not__mod=5)``)
* ``in`` -- value is in list (a list of values should be provided)
* ``nin`` -- value is not in list (a list of values should be provided)
* ``mod`` -- ``value % x == y``, where ``x`` and ``y`` are two provided values
* ``all`` -- every item in list of values provided is in array
* ``size`` -- the size of the array is
* ``exists`` -- value for field exists
The following operators are available as shortcuts to querying with regular
expressions:
* ``exact`` -- string field exactly matches value
* ``iexact`` -- string field exactly matches value (case insensitive)
* ``contains`` -- string field contains value
* ``icontains`` -- string field contains value (case insensitive)
* ``startswith`` -- string field starts with value
* ``istartswith`` -- string field starts with value (case insensitive)
* ``endswith`` -- string field ends with value
* ``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:
* ``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
(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
* ``within_box`` -- filter documents to those within a given bounding box (e.g.
[(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
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
lists that contain that item will be matched::
class Page(Document):
tags = ListField(StringField())
# This will match all pages that have the word 'coding' as an item in the
# 'tags' list
Page.objects(tags='coding')
It is possible to query by position in a list by using a numerical value as a
query operator. So if you wanted to find all pages whose first tag was ``db``,
you could use the following query::
Page.objects(tags__0='db')
If you only want to fetch part of a list eg: you want to paginate a list, then
the `slice` operator is required::
# comments - skip 5, limit 10
Page.objects.fields(slice__comments=[5, 10])
For updating documents, if you don't know the position in a list, you can use
the $ positional operator ::
Post.objects(comments__by="joe").update(**{'inc__comments__$__votes': 1})
However, this doesn't map well to the syntax so you can also use a capital S instead ::
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.
Raw queries
-----------
It is possible to provide a raw PyMongo query as a query parameter, which will
be integrated directly into the query. This is done using the ``__raw__``
keyword argument::
Page.objects(__raw__={'tags': 'coding'})
.. versionadded:: 0.4
Limiting and skipping results
=============================
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::
# Only the first 5 people
users = User.objects[:5]
# All except for the first 5 people
users = User.objects[5:]
# 5 users, starting from the 10th user found
users = User.objects[10:15]
You may also index the query to retrieve a single result. If an item at that
index does not exists, an :class:`IndexError` will be raised. A shortcut for
retrieving the first result and returning :attr:`None` if no result exists is
provided (:meth:`~mongoengine.queryset.QuerySet.first`)::
>>> # Make sure there are no users
>>> User.drop_collection()
>>> User.objects[0]
IndexError: list index out of range
>>> User.objects.first() == None
True
>>> User(name='Test User').save()
>>> User.objects[0] == User.objects.first()
True
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.
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
Default Document queries
========================
By default, the objects :attr:`~mongoengine.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
arguments -- :attr:`doc_cls` and :attr:`queryset`. The first argument is the
:class:`~mongoengine.Document` class that the method is defined on (in this
sense, the method is more like a :func:`classmethod` than a regular method),
and the second argument is the initial queryset. The method needs to be
decorated with :func:`~mongoengine.queryset.queryset_manager` in order for it
to be recognised. ::
class BlogPost(Document):
title = StringField()
date = DateTimeField()
@queryset_manager
def objects(doc_cls, queryset):
# This may actually also be done by defining a default ordering for
# the document, but this illustrates the use of manager methods
return queryset.order_by('-date')
You don't need to call your method :attr:`objects` -- you may define as many
custom manager methods as you like::
class BlogPost(Document):
title = StringField()
published = BooleanField()
@queryset_manager
def live_posts(doc_cls, queryset):
return queryset.filter(published=True)
BlogPost(title='test1', published=False).save()
BlogPost(title='test2', published=True).save()
assert len(BlogPost.objects) == 2
assert len(BlogPost.live_posts) == 1
Custom QuerySets
================
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 AwesomerQuerySet(QuerySet):
pass
class Page(Document):
meta = {'queryset_class': AwesomerQuerySet}
.. versionadded:: 0.4
Aggregation
===========
MongoDB provides some aggregation methods out of the box, but there are not as
many as you typically get with an RDBMS. MongoEngine provides a wrapper around
the built-in methods and provides some of its own, which are implemented as
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::
num_users = len(User.objects)
Further aggregation
-------------------
You may sum over the values of a specific field on documents using
:meth:`~mongoengine.queryset.QuerySet.sum`::
yearly_expense = Employee.objects.sum('salary')
.. note::
If the field isn't present on a document, that document will be ignored from
the sum.
To get the average (mean) of a field on a collection of documents, use
:meth:`~mongoengine.queryset.QuerySet.average`::
mean_age = User.objects.average('age')
As MongoDB provides native lists, MongoEngine provides a helper method to get a
dictionary of the frequencies of items in lists across an entire collection --
:meth:`~mongoengine.queryset.QuerySet.item_frequencies`. An example of its use
would be generating "tag-clouds"::
class Article(Document):
tag = ListField(StringField())
# After adding some tagged articles...
tag_freqs = Article.objects.item_frequencies('tag', normalize=True)
from operator import itemgetter
top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10]
Query efficiency and performance
================================
There are a couple of methods to improve efficiency when querying, reducing the
information returned by the query or efficient dereferencing .
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
: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
retrieve as its arguments. Note that if fields that are not downloaded are
accessed, their default value (or :attr:`None` if no default value is provided)
will be given::
>>> class Film(Document):
... title = StringField()
... year = IntField()
... rating = IntField(default=3)
...
>>> Film(title='The Shawshank Redemption', year=1994, rating=5).save()
>>> f = Film.objects.only('title').first()
>>> f.title
'The Shawshank Redemption'
>>> f.year # None
>>> f.rating # default value
3
.. note::
The :meth:`~mongoengine.queryset.QuerySet.exclude` is the opposite of
:meth:`~mongoengine.queryset.QuerySet.only` if you want to exclude a field.
If you later need the missing fields, just call
:meth:`~mongoengine.Document.reload` on your document.
Getting related data
--------------------
When iterating the results of :class:`~mongoengine.ListField` or
:class:`~mongoengine.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
expensive as the number of queries to MongoDB can quickly rise.
To limit the number of queries use
:func:`~mongoengine.queryset.QuerySet.select_related` which converts the
QuerySet to a list and dereferences as efficiently as possible. By default
:func:`~mongoengine.queryset.QuerySet.select_related` only dereferences any
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.
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
possible in MongoEngine through the :class:`~mongoengine.queryset.Q` class.
A :class:`~mongoengine.queryset.Q` object represents part of a query, and
can be initialised using the same keyword-argument syntax you use to query
documents. To build a complex query, you may combine
:class:`~mongoengine.queryset.Q` objects using the ``&`` (and) and ``|`` (or)
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::
# 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))
.. _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:
* ``set`` -- set a particular value
* ``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
* ``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
The syntax for atomic updates is similar to the querying syntax, but the
modifier comes before the field, not after it::
>>> post = BlogPost(title='Test', page_views=0, tags=['database'])
>>> post.save()
>>> BlogPost.objects(id=post.id).update_one(inc__page_views=1)
>>> post.reload() # the document has been changed, so we need to reload it
>>> post.page_views
1
>>> BlogPost.objects(id=post.id).update_one(set__title='Example Post')
>>> post.reload()
>>> post.title
'Example Post'
>>> BlogPost.objects(id=post.id).update_one(push__tags='nosql')
>>> post.reload()
>>> post.tags
['database', 'nosql']
.. note ::
In version 0.5 the :meth:`~mongoengine.Document.save` runs atomic updates
on changed documents by tracking changes to that document.
The positional operator allows you to update list items without knowing the
index position, therefore making the update a single atomic operation. As we
cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
>>> post = BlogPost(title='Test', page_views=0, tags=['database', 'mongo'])
>>> post.save()
>>> BlogPost.objects(id=post.id, tags='mongo').update(set__tags__S='mongodb')
>>> post.reload()
>>> post.tags
['database', 'mongodb']
.. 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>`_.
Server-side javascript execution
================================
Javascript functions may be written and sent to the server for execution. The
result of this is the return value of the Javascript function. This
functionality is accessed through the
:meth:`~mongoengine.queryset.QuerySet.exec_js` method on
:meth:`~mongoengine.queryset.QuerySet` objects. Pass in a string containing a
Javascript function as the first argument.
The remaining positional arguments are names of fields that will be passed into
you Javascript function as its arguments. This allows functions to be written
that may be executed on any field in a collection (e.g. the
:meth:`~mongoengine.queryset.QuerySet.sum` method, which accepts the name of
the field to sum over as its argument). Note that field names passed in in this
manner are automatically translated to the names used on the database (set
using the :attr:`name` keyword argument to a field constructor).
Keyword arguments to :meth:`~mongoengine.queryset.QuerySet.exec_js` are
combined into an object called :attr:`options`, which is available in the
Javascript function. This may be used for defining specific parameters for your
function.
Some variables are made available in the scope of the Javascript function:
* ``collection`` -- the name of the collection that corresponds to the
:class:`~mongoengine.Document` class that is being used; this should be
used to get the :class:`Collection` object from :attr:`db` in Javascript
code
* ``query`` -- the query that has been generated by the
:class:`~mongoengine.queryset.QuerySet` object; this may be passed into
the :meth:`find` method on a :class:`Collection` object in the Javascript
function
* ``options`` -- an object containing the keyword arguments passed into
:meth:`~mongoengine.queryset.QuerySet.exec_js`
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
:meth:`~mongoengine.queryset.QuerySet.sum` but is shown here for sake of
example)::
def sum_field(document, field_name, include_negatives=True):
code = """
function(sumField) {
var total = 0.0;
db[collection].find(query).forEach(function(doc) {
var val = doc[sumField];
if (val >= 0.0 || options.includeNegatives) {
total += val;
}
});
return total;
}
"""
options = {'includeNegatives': include_negatives}
return document.objects.exec_js(code, field_name, **options)
As fields in MongoEngine may use different names in the database (set using the
:attr:`db_field` keyword argument to a :class:`Field` constructor), a mechanism
exists for replacing MongoEngine field names with the database field names in
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,
should be used before the name of the field on the embedded document. The
following example shows how the substitutions are made::
class Comment(EmbeddedDocument):
content = StringField(db_field='body')
class BlogPost(Document):
title = StringField(db_field='doctitle')
comments = ListField(EmbeddedDocumentField(Comment), name='cs')
# Returns a list of dictionaries. Each dictionary contains a value named
# "document", which corresponds to the "title" field on a BlogPost, and
# "comment", which corresponds to an individual comment. The substitutions
# made are shown in the comments.
BlogPost.objects.exec_js("""
function() {
var comments = [];
db[collection].find(query).forEach(function(doc) {
// doc[~comments] -> doc["cs"]
var docComments = doc[~comments];
for (var i = 0; i < docComments.length; i++) {
// doc[~comments][i] -> doc["cs"][i]
var comment = doc[~comments][i];
comments.push({
// doc[~title] -> doc["doctitle"]
'document': doc[~title],
// comment[~comments.content] -> comment["body"]
'comment': comment[~comments.content]
});
}
});
return comments;
}
""")

53
docs/guide/signals.rst Normal file
View File

@@ -0,0 +1,53 @@
.. _signals:
Signals
=======
.. versionadded:: 0.5
.. note::
Signal support is provided by the excellent `blinker`_ library and
will gracefully fall back if it is not available.
The following document signals exist in MongoEngine and are pretty self-explanatory:
* `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`
Example usage::
from mongoengine import *
from mongoengine import signals
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)
@classmethod
def post_save(cls, sender, document, **kwargs):
logging.debug("Post Save: %s" % document.name)
if 'created' in kwargs:
if kwargs['created']:
logging.debug("Created")
else:
logging.debug("Updated")
signals.pre_save.connect(Author.pre_save, sender=Author)
signals.post_save.connect(Author.post_save, sender=Author)
.. _blinker: http://pypi.python.org/pypi/blinker

View File

@@ -1,27 +1,67 @@
==============================
MongoEngine User Documentation
=======================================
==============================
MongoEngine is an Object-Document Mapper, written in Python for working with
**MongoEngine** is an Object-Document Mapper, written in Python for working with
MongoDB. To install it, simply run
.. code-block:: console
# easy_install mongoengine
# pip install -U mongoengine
The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_.
:doc:`tutorial`
Start here for a quick overview.
:doc:`guide/index`
The Full guide to MongoEngine
:doc:`apireference`
The complete API documentation.
:doc:`upgrade`
How to upgrade MongoEngine.
:doc:`django`
Using MongoEngine and Django
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>`_.
Contributing
------------
The source is available on `GitHub <http://github.com/hmarr/mongoengine>`_ and
contributions are always encouraged. Contributions can be as simple as
minor tweaks to this documentation. To contribute, fork the project on
`GitHub <http://github.com/hmarr/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::
:maxdepth: 2
:hidden:
tutorial
userguide
guide/index
apireference
django
changelog
upgrade
Indices and tables
==================
------------------
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View File

@@ -22,7 +22,7 @@ function. 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`.
@@ -112,7 +112,7 @@ 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
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
:class:`Post` class::
@@ -152,6 +152,26 @@ We can then store a list of comment documents in our post document::
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
Handling deletions of references
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The :class:`~mongoengine.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::
class Post(Document):
title = StringField(max_length=120, required=True)
author = ReferenceField(User, reverse_delete_rule=CASCADE)
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
See :class:`~mongoengine.ReferenceField` for more information.
..note::
MapFields and DictFields currently don't support automatic handling of
deleted references
Adding data to our Tumblelog
============================
Now that we've defined how our documents will be structured, let's start adding
@@ -250,5 +270,5 @@ 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 % posts with tag "mongodb"' % num_posts
print 'Found %d posts with tag "mongodb"' % num_posts

115
docs/upgrade.rst Normal file
View File

@@ -0,0 +1,115 @@
=========
Upgrading
=========
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.
Reverse Delete Rules in Embedded Documents, MapFields and DictFields now throw
an InvalidDocument error as they aren't currently supported.
Document._get_subclasses - Is no longer used and the class method has been removed.
Document.objects.with_id - now raises an InvalidQueryError if used with a filter.
FutureWarning - A future warning has been added to all inherited classes that
don't define `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
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.
More methods now use map_reduce as db.eval is not supported for sharding as such
the following have been changed:
* :meth:`~mongoengine.queryset.QuerySet.sum`
* :meth:`~mongoengine.queryset.QuerySet.average`
* :meth:`~mongoengine.queryset.QuerySet.item_frequencies`
Default collection naming
-------------------------
Previously it was just lowercase, its now much more pythonic and readable as its
lowercase and underscores, previously ::
class MyAceDocument(Document):
pass
MyAceDocument._meta['collection'] == myacedocument
In 0.5 this will change to ::
class MyAceDocument(Document):
pass
MyAceDocument._get_collection_name() == my_ace_document
To upgrade use a Mixin class to set meta like so ::
class BaseMixin(object):
meta = {
'collection': lambda c: c.__name__.lower()
}
class MyAceDocument(Document, BaseMixin):
pass
MyAceDocument._get_collection_name() == "myacedocument"
Alternatively, you can rename your collections eg ::
from mongoengine.connection import _get_db
from mongoengine.base import _document_registry
def rename_collections():
db = _get_db()
failure = False
collection_names = [d._get_collection_name() for d in _document_registry.values()]
for new_style_name in collection_names:
if not new_style_name: # embedded documents don't have collections
continue
old_style_name = new_style_name.replace('_', '')
if old_style_name == new_style_name:
continue # Nothing to do
existing = db.collection_names()
if old_style_name in existing:
if new_style_name in existing:
failure = True
print "FAILED to rename: %s to %s (already exists)" % (
old_style_name, new_style_name)
else:
db[old_style_name].rename(new_style_name)
print "Renamed: %s to %s" % (old_style_name, new_style_name)
if failure:
print "Upgrading collection names failed"
else:
print "Upgraded collection names"

View File

@@ -1,534 +0,0 @@
==========
User Guide
==========
Installing
==========
MongoEngine is available on PyPI, so to use it you can use
:program:`easy_install`
.. code-block:: console
# easy_install mongoengine
Alternatively, if you don't have setuptools installed, `download it from PyPi
<http://pypi.python.org/pypi/mongoengine/>`_ and run
.. code-block:: console
# python setup.py install
.. _guide-connecting:
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::
from mongoengine import connect
connect('project1', username='webapp', password='pwd123')
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
:func:`~mongoengine.connect`::
connect('project1', host='192.168.1.35', port=12345)
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
is enforced at a database level.
Defining a document's schema
----------------------------
MongoEngine allows you to define schemata for documents as this helps to reduce
coding errors, and allows for utility methods to be defined on fields which may
be present.
To define a schema for a document, create a class that inherits from
:class:`~mongoengine.Document`. Fields are specified by adding **field
objects** as class attributes to the document class::
from mongoengine import *
import datetime
class Page(Document):
title = StringField(max_length=200, required=True)
date_modified = DateTimeField(default=datetime.now)
Fields
------
By default, fields are not required. To make a field mandatory, set the
:attr:`required` keyword argument of a field to ``True``. Fields also may have
validation constraints available (such as :attr:`max_length` in the example
above). Fields may also take default values, which will be used if a value is
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.StringField`
* :class:`~mongoengine.IntField`
* :class:`~mongoengine.FloatField`
* :class:`~mongoengine.DateTimeField`
* :class:`~mongoengine.ListField`
* :class:`~mongoengine.ObjectIdField`
* :class:`~mongoengine.EmbeddedDocumentField`
* :class:`~mongoengine.ReferenceField`
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
argument, which specifies which type elements may be stored within the list::
class Page(Document):
tags = ListField(StringField(max_length=50))
Embedded documents
^^^^^^^^^^^^^^^^^^
MongoDB has the ability to embed documents within other documents. Schemata may
be defined for these embedded documents, just as they may be for regular
documents. To create an embedded document, just define a document as usual, but
inherit from :class:`~mongoengine.EmbeddedDocument` rather than
:class:`~mongoengine.Document`::
class Comment(EmbeddedDocument):
content = StringField()
To embed the document within another document, use the
:class:`~mongoengine.EmbeddedDocumentField` field type, providing the embedded
document class as the first argument::
class Page(Document):
comments = ListField(EmbeddedDocumentField(Comment))
comment1 = Comment('Good work!')
comment2 = Comment('Nice article!')
page = Page(comments=[comment1, comment2])
Reference fields
^^^^^^^^^^^^^^^^
References may be stored to other documents in the database using the
:class:`~mongoengine.ReferenceField`. Pass in another document class as the
first argument to the constructor, then simply assign document objects to the
field::
class User(Document):
name = StringField()
class Page(Document):
content = StringField()
author = ReferenceField(User)
john = User(name="John Smith")
john.save()
post = Page(content="Test Page")
post.author = john
post.save()
The :class:`User` object is automatically turned into a reference behind the
scenes, and dereferenced when the :class:`Page` object is retrieved.
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
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
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::
class User(Document):
username = StringField(unique=True)
first_name = StringField()
last_name = StringField(unique_with='last_name')
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
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
set :attr:`collection` to the name of the collection that you want your
document class to use::
class Page(Document):
title = StringField(max_length=200, required=True)
meta = {'collection': 'cmsPage'}
Capped collections
^^^^^^^^^^^^^^^^^^
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).
The following example shows a :class:`Log` document that will be limited to
1000 entries and 2MB of disk space::
class Log(Document):
ip_address = StringField()
meta = {'max_documents': 1000, 'max_size': 2000000}
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, or a tuple containing multiple field names. 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. ::
class Page(Document):
title = StringField()
rating = StringField()
meta = {
'indexes': ['title', ('title', '-rating')]
}
Ordering
--------
A default ordering can be specified for your
:class:`~mongoengine.queryset.QuerySet` using the :attr:`ordering` attribute of
:attr:`~mongoengine.Document.meta`. Ordering will be applied when the
:class:`~mongoengine.queryset.QuerySet` is created, and can be overridden by
subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
from datetime import datetime
class BlogPost(Document):
title = StringField()
published_date = DateTimeField()
meta = {
'ordering': ['-published_date']
}
blog_post_1 = BlogPost(title="Blog Post #1")
blog_post_1.published_date = datetime(2010, 1, 5, 0, 0 ,0))
blog_post_2 = BlogPost(title="Blog Post #2")
blog_post_2.published_date = datetime(2010, 1, 6, 0, 0 ,0))
blog_post_3 = BlogPost(title="Blog Post #3")
blog_post_3.published_date = datetime(2010, 1, 7, 0, 0 ,0))
blog_post_1.save()
blog_post_2.save()
blog_post_3.save()
# get the "first" BlogPost using default ordering
# from BlogPost.meta.ordering
latest_post = BlogPost.objects.first()
self.assertEqual(latest_post.title, "Blog Post #3")
# override default ordering, order BlogPosts by "published_date"
first_post = BlogPost.objects.order_by("+published_date").first()
self.assertEqual(first_post.title, "Blog Post #1")
Document inheritance
--------------------
To create a specialised type of a :class:`~mongoengine.Document` you have
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::
# Stored in a collection named 'page'
class Page(Document):
title = StringField(max_length=200, required=True)
# Also stored in the collection named 'page'
class DatedPage(Page):
date = DateTimeField()
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::
# 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,
}
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.
You may provide values for any of the fields on the document::
>>> page = Page(title="Test Page")
>>> page.title
'Test Page'
You may also assign values to the document's fields using standard object
attribute syntax::
>>> page.title = "Example Page"
>>> page.title
'Example Page'
Saving and deleting documents
-----------------------------
To save the document to the database, call the
:meth:`~mongoengine.Document.save` method. If the document does not exist in
the database, it will be created. If it does already exist, it will be
updated.
To delete a document, call the :meth:`~mongoengine.Document.delete` method.
Note that this will only work if the document exists in the database and has a
valide :attr:`id`.
Document IDs
------------
Each document in the database has a unique id. This may be accessed through the
:attr:`id` attribute on :class:`~mongoengine.Document` objects. Usually, the id
will be generated automatically by the database server when the object is save,
meaning that you may only access the :attr:`id` field once a document has been
saved::
>>> page = Page(title="Test Page")
>>> page.id
>>> page.save()
>>> page.id
ObjectId('123456789abcdef000000000')
Alternatively, you may define one of your own fields to be the document's
"primary key" by providing ``primary_key=True`` as a keyword argument to a
field's constructor. Under the hood, MongoEngine will use this field as the
:attr:`id`; in fact :attr:`id` is actually aliased to your primary key field so
you may still use :attr:`id` to access the primary key if you want::
>>> class User(Document):
... email = StringField(primary_key=True)
... name = StringField()
...
>>> bob = User(email='bob@example.com', name='Bob')
>>> bob.save()
>>> bob.id == bob.email == 'bob@example.com'
True
.. note::
If you define your own primary key field, the field implicitly becomes
required, so a :class:`ValidationError` will be thrown if you don't provide
it.
Querying the database
=====================
:class:`~mongoengine.Document` classes have an :attr:`objects` attribute, which
is used for accessing the objects in the database associated with the class.
The :attr:`objects` attribute is actually a
:class:`~mongoengine.queryset.QuerySetManager`, which creates and returns a new
a new :class:`~mongoengine.queryset.QuerySet` object on access. The
:class:`~mongoengine.queryset.QuerySet` object may may be iterated over to
fetch documents from the database::
# Prints out the names of all the users in the database
for user in User.objects:
print user.name
Filtering queries
-----------------
The query may be filtered by calling the
:class:`~mongoengine.queryset.QuerySet` object with field lookup keyword
arguments. The keys in the keyword arguments correspond to fields on the
:class:`~mongoengine.Document` you are querying::
# This will return a QuerySet that will only iterate over users whose
# 'country' field is set to 'uk'
uk_users = User.objects(country='uk')
Fields on embedded documents may also be referred to using field lookup syntax
by using a double-underscore in place of the dot in object attribute access
syntax::
# This will return a QuerySet that will only iterate over pages that have
# been written by a user whose 'country' field is set to 'uk'
uk_pages = Page.objects(author__country='uk')
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
lists that contain that item will be matched::
class Page(Document):
tags = ListField(StringField())
# This will match all pages that have the word 'coding' as an item in the
# 'tags' list
Page.objects(tags='coding')
Query operators
---------------
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
young_users = Users.objects(age__lte=18)
Available operators are as follows:
* ``neq`` -- not equal to
* ``lt`` -- less than
* ``lte`` -- less than or equal to
* ``gt`` -- greater than
* ``gte`` -- greater than or equal to
* ``in`` -- value is in list (a list of values should be provided)
* ``nin`` -- value is not in list (a list of values should be provided)
* ``mod`` -- ``value % x == y``, where ``x`` and ``y`` are two provided values
* ``all`` -- every item in array is in list of values provided
* ``size`` -- the size of the array is
* ``exists`` -- value for field exists
Limiting and skipping results
-----------------------------
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::
# Only the first 5 people
users = User.objects[:5]
# All except for the first 5 people
users = User.objects[5:]
# 5 users, starting from the 10th user found
users = User.objects[10:15]
Aggregation
-----------
MongoDB provides some aggregation methods out of the box, but there are not as
many as you typically get with an RDBMS. MongoEngine provides a wrapper around
the built-in methods and provides some of its own, which are implemented as
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::
num_users = len(User.objects)
Further aggregation
^^^^^^^^^^^^^^^^^^^
You may sum over the values of a specific field on documents using
:meth:`~mongoengine.queryset.QuerySet.sum`::
yearly_expense = Employee.objects.sum('salary')
.. note::
If the field isn't present on a document, that document will be ignored from
the sum.
To get the average (mean) of a field on a collection of documents, use
:meth:`~mongoengine.queryset.QuerySet.average`::
mean_age = User.objects.average('age')
As MongoDB provides native lists, MongoEngine provides a helper method to get a
dictionary of the frequencies of items in lists across an entire collection --
:meth:`~mongoengine.queryset.QuerySet.item_frequencies`. An example of its use
would be generating "tag-clouds"::
class Article(Document):
tag = ListField(StringField())
# After adding some tagged articles...
tag_freqs = Article.objects.item_frequencies('tag', normalize=True)
from operator import itemgetter
top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10]
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
possible in MongoEngine through the :class:`~mongoengine.queryset.Q` class.
A :class:`~mongoengine.queryset.Q` object represents part of a query, and
can be initialised using the same keyword-argument syntax you use to query
documents. To build a complex query, you may combine
:class:`~mongoengine.queryset.Q` objects using the ``&`` (and) and ``|`` (or)
operators. To use :class:`~mongoengine.queryset.Q` objects, pass them in
as positional arguments to :attr:`Document.objects` when you filter it by
calling it with keyword arguments::
# 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::
Only use these advanced queries if absolutely necessary as they will execute
significantly slower than regular queries. This is because they are not
natively supported by MongoDB -- they are compiled to Javascript and sent
to the server for execution.
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:
* ``set`` -- set a particular value
* ``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
* ``push`` -- append a value to a list
* ``push_all`` -- append several values to a list
* ``pull`` -- remove a value from a list
* ``pull_all`` -- remove several values from a list
The syntax for atomic updates is similar to the querying syntax, but the
modifier comes before the field, not after it::
>>> post = BlogPost(title='Test', page_views=0, tags=['database'])
>>> post.save()
>>> BlogPost.objects(id=post.id).update_one(inc__page_views=1)
>>> post.reload() # the document has been changed, so we need to reload it
>>> post.page_views
1
>>> BlogPost.objects(id=post.id).update_one(set__title='Example Post')
>>> post.reload()
>>> post.title
'Example Post'
>>> BlogPost.objects(id=post.id).update_one(push__tags='nosql')
>>> post.reload()
>>> post.tags
['database', 'nosql']

View File

@@ -6,13 +6,14 @@ import connection
from connection import *
import queryset
from queryset import *
import signals
from signals import *
__all__ = (document.__all__ + fields.__all__ + connection.__all__ +
queryset.__all__)
queryset.__all__ + signals.__all__)
__author__ = 'Harry Marr'
VERSION = (0, 6, 4)
VERSION = (0, 2, 1)
def get_version():
version = '%s.%s' % (VERSION[0], VERSION[1])
@@ -21,4 +22,3 @@ def get_version():
return version
__version__ = get_version()

File diff suppressed because it is too large Load Diff

View File

@@ -1,62 +1,159 @@
from pymongo import Connection
import pymongo
from pymongo import Connection, ReplicaSetConnection, uri_parser
__all__ = ['ConnectionError', 'connect']
__all__ = ['ConnectionError', 'connect', 'register_connection',
'DEFAULT_CONNECTION_NAME']
_connection_settings = {
'host': 'localhost',
'port': 27017,
'pool_size': 1,
}
_connection = None
_db_name = None
_db_username = None
_db_password = None
_db = None
DEFAULT_CONNECTION_NAME = 'default'
class ConnectionError(Exception):
pass
def _get_connection():
global _connection
# Connect to the database if not already connected
if _connection is None:
try:
_connection = Connection(**_connection_settings)
except:
raise ConnectionError('Cannot connect to the database')
return _connection
_connection_settings = {}
_connections = {}
_dbs = {}
def _get_db():
global _db, _connection
# Connect if not already connected
if _connection is None:
_connection = _get_connection()
if _db is None:
# _db_name will be None if the user hasn't called connect()
if _db_name is None:
raise ConnectionError('Not connected to the database')
def register_connection(alias, name, host='localhost', port=27017,
is_slave=False, read_preference=False, slaves=None,
username=None, password=None, **kwargs):
"""Add a connection.
# Get DB from current connection and authenticate if necessary
_db = _connection[_db_name]
if _db_username and _db_password:
_db.authenticate(_db_username, _db_password)
:param alias: the name that will be used to refer to this connection
throughout MongoEngine
: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 username: username to authenticate with
:param password: password to authenticate with
:param kwargs: allow ad-hoc parameters to be passed into the pymongo driver
return _db
def connect(db, username=None, password=None, **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.
"""
global _connection_settings, _db_name, _db_username, _db_password
_connection_settings.update(kwargs)
_db_name = db
_db_username = username
_db_password = password
global _connection_settings
conn_settings = {
'name': name,
'host': host,
'port': port,
'is_slave': is_slave,
'slaves': slaves or [],
'username': username,
'password': password,
'read_preference': read_preference
}
# 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,
})
_connection_settings[alias] = conn_settings
def disconnect(alias=DEFAULT_CONNECTION_NAME):
global _connections
global _dbs
if alias in _connections:
get_connection(alias=alias).disconnect()
del _connections[alias]
if alias in _dbs:
del _dbs[alias]
def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
global _connections
# 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 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)
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)
connection_class = Connection
if 'replicaSet' in conn_settings:
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)
connection_class = ReplicaSetConnection
try:
_connections[alias] = connection_class(**conn_settings)
except Exception, e:
raise ConnectionError("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']]
# Authenticate if necessary
if conn_settings['username'] and conn_settings['password']:
_dbs[alias].authenticate(conn_settings['username'],
conn_settings['password'])
return _dbs[alias]
def connect(db, 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
`alias` to connect to a different instance of :program:`mongod`.
.. 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

188
mongoengine/dereference.py Normal file
View File

@@ -0,0 +1,188 @@
from bson import DBRef, SON
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
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.
:param items: The iterable (dict, list, queryset) to be dereferenced.
:param max_depth: The maximum depth to recurse to
:param instance: The owning instance used for tracking changes by
:class:`~mongoengine.base.ComplexBaseField`
:param name: The name of the field, used for tracking changes by
:class:`~mongoengine.base.ComplexBaseField`
:param get: A boolean determining if being called by __get__
"""
if items is None or isinstance(items, basestring):
return items
# cheapest way to convert a queryset to a list
# list(queryset) uses a count() query to determine length
if isinstance(items, QuerySet):
items = [i for i in items]
self.max_depth = max_depth
doc_type = None
if instance and instance._fields:
doc_type = instance._fields[name].field
if isinstance(doc_type, ReferenceField):
doc_type = doc_type.document_type
if all([i.__class__ == doc_type for i in items]):
return items
self.reference_map = self._find_references(items)
self.object_map = self._fetch_objects(doc_type=doc_type)
return self._attach_objects(items, 0, instance, name)
def _find_references(self, items, depth=0):
"""
Recursively finds all db references to be dereferenced
:param items: The iterable (dict, list, queryset)
:param depth: The current depth of recursion
"""
reference_map = {}
if not items or depth >= self.max_depth:
return reference_map
# Determine the iterator to use
if not hasattr(items, 'items'):
iterator = enumerate(items)
else:
iterator = items.iteritems()
# Recursively find dbreferences
depth += 1
for k, item in iterator:
if hasattr(item, '_fields'):
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)
elif isinstance(v, (dict, SON)) and '_ref' in v:
reference_map.setdefault(get_document(v['_cls']), []).append(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)
elif isinstance(item, (dict, SON)) and '_ref' in item:
reference_map.setdefault(get_document(item['_cls']), []).append(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)
return reference_map
def _fetch_objects(self, doc_type=None):
"""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 key, doc in references.iteritems():
object_map[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}})
for ref in references:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
else:
references = get_db()[col].find({'_id': {'$in': refs}})
for ref in references:
if '_cls' in ref:
doc = get_document(ref["_cls"])._from_son(ref)
else:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
return object_map
def _attach_objects(self, items, depth=0, instance=None, name=None):
"""
Recursively finds all db references to be dereferenced
:param items: The iterable (dict, list, queryset)
:param depth: The current depth of recursion
:param instance: The owning instance used for tracking changes by
:class:`~mongoengine.base.ComplexBaseField`
:param name: The name of the field, used for tracking changes by
:class:`~mongoengine.base.ComplexBaseField`
"""
if not items:
if isinstance(items, (BaseDict, BaseList)):
return items
if instance:
if isinstance(items, dict):
return BaseDict(items, instance, name)
else:
return BaseList(items, instance, name)
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:
doc = get_document(items['_cls'])._from_son(items)
doc._data = self._attach_objects(doc._data, depth, doc, name)
return doc
if not hasattr(items, 'items'):
is_list = True
iterator = enumerate(items)
data = []
else:
is_list = False
iterator = items.iteritems()
data = {}
depth += 1
for k, v in iterator:
if is_list:
data.append(v)
else:
data[k] = v
if k in self.object_map:
data[k] = self.object_map[k]
elif hasattr(v, '_fields'):
for field_name, field in v._fields.iteritems():
v = data[k]._data.get(field_name, None)
if isinstance(v, (DBRef)):
data[k]._data[field_name] = self.object_map.get(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)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=name)
elif hasattr(v, 'id'):
data[k] = self.object_map.get(v.id, v)
if instance and name:
if is_list:
return BaseList(data, instance, name)
return BaseDict(data, instance, name)
depth += 1
return data

View File

@@ -1,35 +1,77 @@
from mongoengine import *
from django.utils.hashcompat import md5_constructor, sha_constructor
from django.utils.encoding import smart_str
from django.contrib.auth.models import AnonymousUser
import datetime
from mongoengine import *
from django.utils.encoding import smart_str
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"""
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'
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')
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)
first_name = StringField(max_length=30)
last_name = StringField(max_length=30)
email = StringField()
password = StringField(max_length=128)
is_staff = BooleanField(default=False)
is_active = BooleanField(default=True)
is_superuser = BooleanField(default=False)
last_login = DateTimeField(default=datetime.datetime.now)
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 = {
'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.
@@ -48,11 +90,9 @@ class User(Document):
assigning to :attr:`~mongoengine.django.auth.User.password` as the
password is hashed before storage.
"""
from random import random
algo = 'sha1'
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
hash = get_hexdigest(algo, salt, raw_password)
self.password = '%s$%s$%s' % (algo, salt, hash)
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
@@ -60,24 +100,42 @@ class User(Document):
:attr:`~mongoengine.django.auth.User.password` as the password is
hashed before storage.
"""
algo, salt, hash = self.password.split('$')
return hash == get_hexdigest(algo, salt, raw_password)
return check_password(raw_password, self.password)
@classmethod
def create_user(cls, username, password, email=None):
"""Create (and save) a new user with the given username, password and
email address.
"""
user = User(username=username, email=email)
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:

View File

@@ -1,3 +1,6 @@
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
@@ -5,16 +8,22 @@ 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
from datetime import datetime
MONGOENGINE_SESSION_DB_ALIAS = getattr(
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
DEFAULT_CONNECTION_NAME)
class MongoSession(Document):
session_key = fields.StringField(primary_key=True, max_length=40)
session_data = fields.StringField()
expire_date = fields.DateTimeField()
meta = {'collection': 'django_session', 'allow_inheritance': False}
meta = {'collection': 'django_session',
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
'allow_inheritance': False}
class SessionStore(SessionBase):
@@ -45,6 +54,8 @@ class SessionStore(SessionBase):
return
def save(self, must_create=False):
if self.session_key is None:
self.create()
s = MongoSession(session_key=self.session_key)
s.session_data = self.encode(self._get_session(no_load=must_create))
s.expire_date = self.get_expiry_date()

View File

@@ -0,0 +1,46 @@
from django.http import Http404
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):
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:
raise Http404('No %s matches the given query.' % queryset._document._class_name)
return obj_list

View File

@@ -0,0 +1,112 @@
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

@@ -0,0 +1,21 @@
#coding: utf-8
from django.test import TestCase
from django.conf import settings
from mongoengine import connect
class MongoTestCase(TestCase):
"""
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)
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)

View File

@@ -1,12 +1,18 @@
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
ValidationError)
from queryset import OperationError
from connection import _get_db
import pymongo
from bson.dbref import DBRef
from mongoengine import signals
from base import (DocumentMetaclass, TopLevelDocumentMetaclass, BaseDocument,
BaseDict, BaseList)
from queryset import OperationError
from connection import get_db, DEFAULT_CONNECTION_NAME
__all__ = ['Document', 'EmbeddedDocument', 'DynamicDocument',
'DynamicEmbeddedDocument', 'OperationError', 'InvalidCollectionError']
__all__ = ['Document', 'EmbeddedDocument', 'ValidationError', 'OperationError']
class InvalidCollectionError(Exception):
pass
class EmbeddedDocument(BaseDocument):
@@ -15,9 +21,24 @@ class EmbeddedDocument(BaseDocument):
fields on :class:`~mongoengine.Document`\ s through the
:class:`~mongoengine.EmbeddedDocumentField` field type.
"""
__metaclass__ = DocumentMetaclass
def __init__(self, *args, **kwargs):
super(EmbeddedDocument, self).__init__(*args, **kwargs)
self._changed_fields = []
def __delattr__(self, *args, **kwargs):
"""Handle deletions of fields"""
field_name = args[0]
if field_name in self._fields:
default = self._fields[field_name].default
if callable(default):
default = default()
setattr(self, field_name, default)
else:
super(EmbeddedDocument, self).__delattr__(*args, **kwargs)
class Document(BaseDocument):
"""The base class used for defining the structure and properties of
@@ -40,96 +61,367 @@ class Document(BaseDocument):
presence of `_cls` and `_types`, set :attr:`allow_inheritance` to
``False`` in the :attr:`meta` dictionary.
A :class:`~mongoengine.Document` may use a **Capped Collection** by
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
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).
Indexes may be created by specifying :attr:`indexes` in the :attr:`meta`
dictionary. The value should be a list of field names or tuples of field
dictionary. The value should be a list of field names or tuples of field
names. Index direction may be specified by prefixing the field names with
a **+** or **-** sign.
"""
By default, _types will be added to the start of every index (that
doesn't contain a list) if allow_inheritence is True. This can be
disabled by either setting types to False on the specific index or
by setting index_types to False on the meta dictionary for the document.
"""
__metaclass__ = TopLevelDocumentMetaclass
def save(self, safe=True, force_insert=False):
@apply
def pk():
"""Primary key alias
"""
def fget(self):
return getattr(self, self._meta['id_field'])
def fset(self, value):
return setattr(self, self._meta['id_field'], value)
return property(fget, fset)
@classmethod
def _get_db(cls):
"""Some Model using other db_alias"""
return get_db(cls._meta.get("db_alias", DEFAULT_CONNECTION_NAME ))
@classmethod
def _get_collection(cls):
"""Returns the collection for the document."""
if not hasattr(cls, '_collection') or cls._collection is None:
db = cls._get_db()
collection_name = cls._get_collection_name()
# Create collection as a capped collection if specified
if cls._meta['max_size'] or cls._meta['max_documents']:
# Get max document limit and max byte size from meta
max_size = cls._meta['max_size'] or 10000000 # 10MB default
max_documents = cls._meta['max_documents']
if collection_name in db.collection_names():
cls._collection = db[collection_name]
# The collection already exists, check if its capped
# options match the specified capped options
options = cls._collection.options()
if options.get('max') != max_documents or \
options.get('size') != max_size:
msg = ('Cannot create collection "%s" as a capped '
'collection as it already exists') % cls._collection
raise InvalidCollectionError(msg)
else:
# Create the collection as a capped collection
opts = {'capped': True, 'size': max_size}
if max_documents:
opts['max'] = max_documents
cls._collection = db.create_collection(
collection_name, **opts
)
else:
cls._collection = db[collection_name]
return cls._collection
def save(self, safe=True, force_insert=False, validate=True, write_options=None,
cascade=None, cascade_kwargs=None, _refs=None):
"""Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be
created.
If ``safe=True`` and the operation is unsuccessful, an
If ``safe=True`` and the operation is unsuccessful, an
:class:`~mongoengine.OperationError` will be raised.
:param safe: check if the operation succeeded before returning
:param force_insert: only try to create a new document, don't allow
:param force_insert: only try to create a new document, don't allow
updates of existing documents
:param validate: validates the document; set to ``False`` to skip.
:param write_options: Extra keyword arguments are passed down to
:meth:`~pymongo.collection.Collection.save` OR
:meth:`~pymongo.collection.Collection.insert`
which will be used as options for the resultant ``getLastError`` command.
For example, ``save(..., w=2, fsync=True)`` will wait until at least two servers
have recorded the write and will force an fsync on each server being written to.
:param cascade: Sets the flag for cascading saves. You can set a default by setting
"cascade" in the document __meta__
:param cascade_kwargs: optional kwargs dictionary to be passed throw to cascading saves
:param _refs: A list of processed references used in cascading saves
.. versionchanged:: 0.5
In existing documents it only saves changed fields using set / unset
Saves are cascaded and any :class:`~bson.dbref.DBRef` objects
that have changes are saved as well.
.. versionchanged:: 0.6
Cascade saves are optional = defaults to True, if you want fine grain
control then you can turn off using document meta['cascade'] = False
Also you can pass different kwargs to the cascade save using cascade_kwargs
which overwrites the existing kwargs with custom values
"""
self.validate()
signals.pre_save.send(self.__class__, document=self)
if validate:
self.validate()
if not write_options:
write_options = {}
doc = self.to_mongo()
created = force_insert or '_id' not in doc
try:
collection = self.__class__.objects._collection
if force_insert:
object_id = collection.insert(doc, safe=safe)
if created:
if force_insert:
object_id = collection.insert(doc, safe=safe, **write_options)
else:
object_id = collection.save(doc, safe=safe, **write_options)
else:
object_id = collection.save(doc, safe=safe)
object_id = doc['_id']
updates, removals = self._delta()
# Need to add shard key to query, or you get an error
select_dict = {'_id': object_id}
shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key:
actual_key = self._db_field_map.get(k, k)
select_dict[actual_key] = doc[actual_key]
upsert = self._created
if updates:
collection.update(select_dict, {"$set": updates}, upsert=upsert, safe=safe, **write_options)
if removals:
collection.update(select_dict, {"$unset": removals}, upsert=upsert, safe=safe, **write_options)
cascade = self._meta.get('cascade', True) if cascade is None else cascade
if cascade:
kwargs = {
"safe": safe,
"force_insert": force_insert,
"validate": validate,
"write_options": write_options,
"cascade": cascade
}
if cascade_kwargs: # Allow granular control over cascades
kwargs.update(cascade_kwargs)
kwargs['_refs'] = _refs
self.cascade_save(**kwargs)
except pymongo.errors.OperationFailure, err:
message = 'Could not save document (%s)'
if 'duplicate key' in str(err):
message = 'Tried to save duplicate unique keys (%s)'
raise OperationError(message % str(err))
if u'duplicate key' in unicode(err):
message = u'Tried to save duplicate unique keys (%s)'
raise OperationError(message % unicode(err))
id_field = self._meta['id_field']
self[id_field] = self._fields[id_field].to_python(object_id)
self._changed_fields = []
self._created = False
signals.post_save.send(self.__class__, document=self, created=created)
def cascade_save(self, *args, **kwargs):
"""Recursively saves any references / generic references on an object"""
from fields import ReferenceField, GenericReferenceField
_refs = kwargs.get('_refs', []) or []
for name, cls in self._fields.items():
if not isinstance(cls, (ReferenceField, GenericReferenceField)):
continue
ref = getattr(self, name)
if not ref:
continue
ref_id = "%s,%s" % (ref.__class__.__name__, str(ref._data))
if ref and ref_id not in _refs:
_refs.append(ref_id)
kwargs["_refs"] = _refs
ref.save(**kwargs)
ref._changed_fields = []
def update(self, **kwargs):
"""Performs an update on the :class:`~mongoengine.Document`
A convenience wrapper to :meth:`~mongoengine.QuerySet.update`.
Raises :class:`OperationError` if called on an object that has not yet
been saved.
"""
if not self.pk:
raise OperationError('attempt to update a document not yet saved')
# Need to add shard key to query, or you get an error
select_dict = {'pk': self.pk}
shard_key = self.__class__._meta.get('shard_key', tuple())
for k in shard_key:
select_dict[k] = getattr(self, k)
return self.__class__.objects(**select_dict).update_one(**kwargs)
def delete(self, safe=False):
"""Delete the :class:`~mongoengine.Document` from the database. This
will only take effect if the document has been previously saved.
:param safe: check if the operation succeeded before returning
"""
id_field = self._meta['id_field']
object_id = self._fields[id_field].to_mongo(self[id_field])
try:
self.__class__.objects(**{id_field: object_id}).delete(safe=safe)
except pymongo.errors.OperationFailure, err:
raise OperationError('Could not delete document (%s)' % str(err))
signals.pre_delete.send(self.__class__, document=self)
def reload(self):
try:
self.__class__.objects(pk=self.pk).delete(safe=safe)
except pymongo.errors.OperationFailure, err:
message = u'Could not delete document (%s)' % err.message
raise OperationError(message)
signals.post_delete.send(self.__class__, document=self)
def select_related(self, max_depth=1):
"""Handles dereferencing of :class:`~bson.dbref.DBRef` objects to
a maximum depth in order to cut down the number queries to mongodb.
.. versionadded:: 0.5
"""
from dereference import DeReference
self._data = DeReference()(self._data, max_depth)
return self
def reload(self, max_depth=1):
"""Reloads all attributes from the database.
.. versionadded:: 0.1.2
.. versionchanged:: 0.6 Now chainable
"""
id_field = self._meta['id_field']
obj = self.__class__.objects(**{id_field: self[id_field]}).first()
obj = self.__class__.objects(
**{id_field: self[id_field]}
).first().select_related(max_depth=max_depth)
for field in self._fields:
setattr(self, field, obj[field])
setattr(self, field, self._reload(field, obj[field]))
if self._dynamic:
for name in self._dynamic_fields.keys():
setattr(self, name, self._reload(name, obj._data[name]))
self._changed_fields = obj._changed_fields
return obj
def validate(self):
"""Ensure that all fields' values are valid and that required fields
are present.
def _reload(self, key, value):
"""Used by :meth:`~mongoengine.Document.reload` to ensure the
correct instance is linked to self.
"""
# Get a list of tuples of field names and their current values
fields = [(field, getattr(self, name))
for name, field in self._fields.items()]
if isinstance(value, BaseDict):
value = [(k, self._reload(k, v)) for k, v in value.items()]
value = BaseDict(value, self, key)
elif isinstance(value, BaseList):
value = [self._reload(key, v) for v in value]
value = BaseList(value, self, key)
elif isinstance(value, (EmbeddedDocument, DynamicEmbeddedDocument)):
value._changed_fields = []
return value
# Ensure that each field is matched to a valid value
for field, value in fields:
if value is not None:
try:
field.validate(value)
except (ValueError, AttributeError, AssertionError), e:
raise ValidationError('Invalid value for field of type "' +
field.__class__.__name__ + '"')
elif field.required:
raise ValidationError('Field "%s" is required' % field.name)
def to_dbref(self):
"""Returns an instance of :class:`~bson.dbref.DBRef` useful in
`__raw__` queries."""
if not self.pk:
msg = "Only saved documents can have a valid dbref"
raise OperationError(msg)
return DBRef(self.__class__._get_collection_name(), self.pk)
@classmethod
def register_delete_rule(cls, document_cls, field_name, rule):
"""This method registers the delete rules to apply when removing this
object.
"""
cls._meta['delete_rules'][(document_cls, field_name)] = rule
@classmethod
def drop_collection(cls):
"""Drops the entire collection associated with this
:class:`~mongoengine.Document` type from the database.
"""
db = _get_db()
db.drop_collection(cls._meta['collection'])
from mongoengine.queryset import QuerySet
db = cls._get_db()
db.drop_collection(cls._get_collection_name())
QuerySet._reset_already_indexed(cls)
class DynamicDocument(Document):
"""A Dynamic Document class allowing flexible, expandable and uncontrolled
schemas. As a :class:`~mongoengine.Document` subclass, acts in the same
way as an ordinary document but has expando style properties. Any data
passed or set against the :class:`~mongoengine.DynamicDocument` that is
not a field is automatically converted into a
:class:`~mongoengine.BaseDynamicField` and data can be attributed to that
field.
..note::
There is one caveat on Dynamic Documents: fields cannot start with `_`
"""
__metaclass__ = TopLevelDocumentMetaclass
_dynamic = True
def __delattr__(self, *args, **kwargs):
"""Deletes the attribute by setting to None and allowing _delta to unset
it"""
field_name = args[0]
if field_name in self._dynamic_fields:
setattr(self, field_name, None)
else:
super(DynamicDocument, self).__delattr__(*args, **kwargs)
class DynamicEmbeddedDocument(EmbeddedDocument):
"""A Dynamic Embedded Document class allowing flexible, expandable and
uncontrolled schemas. See :class:`~mongoengine.DynamicDocument` for more
information about dynamic documents.
"""
__metaclass__ = DocumentMetaclass
_dynamic = True
def __delattr__(self, *args, **kwargs):
"""Deletes the attribute by setting to None and allowing _delta to unset
it"""
field_name = args[0]
setattr(self, field_name, None)
class MapReduceDocument(object):
"""A document returned from a map/reduce query.
:param collection: An instance of :class:`~pymongo.Collection`
:param key: Document/result key, often an instance of
:class:`~bson.objectid.ObjectId`. If supplied as
an ``ObjectId`` found in the given ``collection``,
the object can be accessed via the ``object`` property.
:param value: The result(s) for this key.
.. versionadded:: 0.3
"""
def __init__(self, document, collection, key, value):
self._document = document
self._collection = collection
self.key = key
self.value = value
@property
def object(self):
"""Lazy-load the object referenced by ``self.key``. ``self.key``
should be the ``primary_key``.
"""
id_field = self._document()._meta['id_field']
id_field_type = type(id_field)
if not isinstance(self.key, id_field_type):
try:
self.key = id_field_type(self.key)
except:
raise Exception("Could not cast key as %s" % \
id_field_type.__name__)
if not hasattr(self, "_key_object"):
self._key_object = self._document.objects.with_id(self.key)
return self._key_object
return self._key_object

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

46
mongoengine/signals.py Normal file
View File

@@ -0,0 +1,46 @@
# -*- coding: utf-8 -*-
__all__ = ['pre_init', 'post_init', 'pre_save', 'post_save',
'pre_delete', 'post_delete']
signals_available = False
try:
from blinker import Namespace
signals_available = True
except ImportError:
class Namespace(object):
def signal(self, name, doc=None):
return _FakeSignal(name, doc)
class _FakeSignal(object):
"""If blinker is unavailable, create a fake class with the same
interface that allows sending of signals but will fail with an
error on anything else. Instead of doing anything on send, it
will just ignore the arguments and do nothing instead.
"""
def __init__(self, name, doc=None):
self.name = name
self.__doc__ = doc
def _fail(self, *args, **kwargs):
raise RuntimeError('signalling support is unavailable '
'because the blinker library is '
'not installed.')
send = lambda *a, **kw: None
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()
pre_init = _signals.signal('pre_init')
post_init = _signals.signal('post_init')
pre_save = _signals.signal('pre_save')
post_save = _signals.signal('post_save')
pre_delete = _signals.signal('pre_delete')
post_delete = _signals.signal('post_delete')
pre_bulk_insert = _signals.signal('pre_bulk_insert')
post_bulk_insert = _signals.signal('post_bulk_insert')

59
mongoengine/tests.py Normal file
View File

@@ -0,0 +1,59 @@
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

62
python-mongoengine.spec Normal file
View File

@@ -0,0 +1,62 @@
# sitelib for noarch packages, sitearch for others (remove the unneeded one)
%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")}
%{!?python_sitearch: %global python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))")}
%define srcname mongoengine
Name: python-%{srcname}
Version: 0.6.4
Release: 1%{?dist}
Summary: A Python Document-Object Mapper for working with MongoDB
Group: Development/Libraries
License: MIT
URL: https://github.com/MongoEngine/mongoengine
Source0: %{srcname}-%{version}.tar.bz2
BuildRequires: python-devel
BuildRequires: python-setuptools
Requires: mongodb
Requires: pymongo
Requires: python-blinker
Requires: python-imaging
%description
MongoEngine is an ORM-like layer on top of PyMongo.
%prep
%setup -q -n %{srcname}-%{version}
%build
# Remove CFLAGS=... for noarch packages (unneeded)
CFLAGS="$RPM_OPT_FLAGS" %{__python} setup.py build
%install
rm -rf $RPM_BUILD_ROOT
%{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT
%clean
rm -rf $RPM_BUILD_ROOT
%files
%defattr(-,root,root,-)
%doc docs AUTHORS LICENSE README.rst
# For noarch packages: sitelib
%{python_sitelib}/*
# For arch-specific packages: sitearch
# %{python_sitearch}/*
%changelog
* Mon Mar 05 2012 Ross Lawley <ross.lawley@gmail.com> 0.6
- 0.6 released
* Thu Oct 27 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.3-1
- Update to latest dev version
- Add PIL dependency for ImageField
* Wed Oct 12 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.2-1
- Update version
* Fri Sep 23 2011 Pau Aliagas <linuxnow@gmail.com> 0.5.0-1
- Initial version

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
pymongo

View File

@@ -15,7 +15,7 @@ def get_version(version_tuple):
version = '%s.%s' % (version, version_tuple[2])
return version
# Dirty hack to get version number from monogengine/__init__.py - we can't
# Dirty hack to get version number from monogengine/__init__.py - we can't
# import it as it depends on PyMongo and PyMongo isn't installed until this
# file is read
init = os.path.join(os.path.dirname(__file__), 'mongoengine', '__init__.py')
@@ -38,7 +38,9 @@ setup(name='mongoengine',
packages=find_packages(),
author='Harry Marr',
author_email='harry.marr@{nospam}gmail.com',
url='http://hmarr.com/mongoengine/',
maintainer="Ross Lawley",
maintainer_email="ross.lawley@{nospam}gmail.com",
url='http://mongoengine.org/',
license='MIT',
include_package_data=True,
description=DESCRIPTION,
@@ -47,4 +49,5 @@ setup(name='mongoengine',
classifiers=CLASSIFIERS,
install_requires=['pymongo'],
test_suite='tests',
tests_require=['blinker', 'django>=1.3', 'PIL']
)

70
tests/connection.py Normal file
View File

@@ -0,0 +1,70 @@
import unittest
import pymongo
import mongoengine.connection
from mongoengine import *
from mongoengine.connection import get_db, get_connection, ConnectionError
class ConnectionTest(unittest.TestCase):
def tearDown(self):
mongoengine.connection._connection_settings = {}
mongoengine.connection._connections = {}
mongoengine.connection._dbs = {}
def test_connect(self):
"""Ensure that the connect() method works properly.
"""
connect('mongoenginetest')
conn = get_connection()
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest')
connect('mongoenginetest2', alias='testdb')
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
def test_connect_uri(self):
"""Ensure that the connect() method works properly with uri's
"""
c = connect(db='mongoenginetest', alias='admin')
c.admin.system.users.remove({})
c.mongoenginetest.system.users.remove({})
c.admin.add_user("admin", "password")
c.admin.authenticate("admin", "password")
c.mongoenginetest.add_user("username", "password")
self.assertRaises(ConnectionError, 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))
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest')
def test_register_connection(self):
"""Ensure that connections with different aliases may be registered.
"""
register_connection('testdb', 'mongoenginetest2')
self.assertRaises(ConnectionError, get_connection)
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.connection.Connection))
db = get_db('testdb')
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertEqual(db.name, 'mongoenginetest2')
if __name__ == '__main__':
unittest.main()

812
tests/dereference.py Normal file
View File

@@ -0,0 +1,812 @@
import unittest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.tests import query_counter
class FieldTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def test_list_item_dereference(self):
"""Ensure that DBRef items in ListFields are dereferenced.
"""
class User(Document):
name = StringField()
class Group(Document):
members = ListField(ReferenceField(User))
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
user = User(name='user %s' % i)
user.save()
group = Group(members=User.objects)
group.save()
group = Group(members=User.objects)
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 2)
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 2)
[m for m in group_obj.members]
self.assertEqual(q, 2)
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 2)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 2)
User.drop_collection()
Group.drop_collection()
def test_recursive_reference(self):
"""Ensure that ReferenceFields can reference their own documents.
"""
class Employee(Document):
name = StringField()
boss = ReferenceField('self')
friends = ListField(ReferenceField('self'))
Employee.drop_collection()
bill = Employee(name='Bill Lumbergh')
bill.save()
michael = Employee(name='Michael Bolton')
michael.save()
samir = Employee(name='Samir Nagheenanajar')
samir.save()
friends = [michael, samir]
peter = Employee(name='Peter Gibbons', boss=bill, friends=friends)
peter.save()
Employee(name='Funky Gibbon', boss=bill, friends=friends).save()
Employee(name='Funky Gibbon', boss=bill, friends=friends).save()
Employee(name='Funky Gibbon', boss=bill, friends=friends).save()
with query_counter() as q:
self.assertEqual(q, 0)
peter = Employee.objects.with_id(peter.id)
self.assertEqual(q, 1)
peter.boss
self.assertEqual(q, 2)
peter.friends
self.assertEqual(q, 3)
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
peter = Employee.objects.with_id(peter.id).select_related()
self.assertEqual(q, 2)
self.assertEquals(peter.boss, bill)
self.assertEqual(q, 2)
self.assertEquals(peter.friends, friends)
self.assertEqual(q, 2)
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
employees = Employee.objects(boss=bill).select_related()
self.assertEqual(q, 2)
for employee in employees:
self.assertEquals(employee.boss, bill)
self.assertEqual(q, 2)
self.assertEquals(employee.friends, friends)
self.assertEqual(q, 2)
def test_circular_reference(self):
"""Ensure you can handle circular references
"""
class Person(Document):
name = StringField()
relations = ListField(EmbeddedDocumentField('Relation'))
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")
mother.save()
daughter.save()
daughter_rel = Relation(name="Daughter", person=daughter)
mother.relations.append(daughter_rel)
mother.save()
mother_rel = Relation(name="Daughter", person=mother)
self_rel = Relation(name="Self", person=daughter)
daughter.relations.append(mother_rel)
daughter.relations.append(self_rel)
daughter.save()
self.assertEquals("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
def test_circular_reference_on_self(self):
"""Ensure you can handle circular references
"""
class Person(Document):
name = StringField()
relations = ListField(ReferenceField('self'))
def __repr__(self):
return "<Person: %s>" % self.name
Person.drop_collection()
mother = Person(name="Mother")
daughter = Person(name="Daughter")
mother.save()
daughter.save()
mother.relations.append(daughter)
mother.save()
daughter.relations.append(mother)
daughter.relations.append(daughter)
daughter.save()
self.assertEquals("[<Person: Mother>, <Person: Daughter>]", "%s" % Person.objects())
def test_circular_tree_reference(self):
"""Ensure you can handle circular references with more than one level
"""
class Other(EmbeddedDocument):
name = StringField()
friends = ListField(ReferenceField('Person'))
class Person(Document):
name = StringField()
other = EmbeddedDocumentField(Other, default=lambda: Other())
def __repr__(self):
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.other.friends = [maria, julia, anna]
paul.other.name = "Paul's friends"
paul.save()
maria.other.friends = [paul, julia, anna]
maria.other.name = "Maria's friends"
maria.save()
julia.other.friends = [paul, maria, anna]
julia.other.name = "Julia's friends"
julia.save()
anna.other.friends = [paul, maria, julia]
anna.other.name = "Anna's friends"
anna.save()
self.assertEquals(
"[<Person: Paul>, <Person: Maria>, <Person: Julia>, <Person: Anna>]",
"%s" % Person.objects()
)
def test_generic_reference(self):
class UserA(Document):
name = StringField()
class UserB(Document):
name = StringField()
class UserC(Document):
name = StringField()
class Group(Document):
members = ListField(GenericReferenceField())
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
members = []
for i in xrange(1, 51):
a = UserA(name='User A %s' % i)
a.save()
b = UserB(name='User B %s' % i)
b.save()
c = UserC(name='User C %s' % i)
c.save()
members += [a, b, c]
group = Group(members=members)
group.save()
group = Group(members=members)
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 4)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
def test_list_field_complex(self):
class UserA(Document):
name = StringField()
class UserB(Document):
name = StringField()
class UserC(Document):
name = StringField()
class Group(Document):
members = ListField()
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
members = []
for i in xrange(1, 51):
a = UserA(name='User A %s' % i)
a.save()
b = UserB(name='User B %s' % i)
b.save()
c = UserC(name='User C %s' % i)
c.save()
members += [a, b, c]
group = Group(members=members)
group.save()
group = Group(members=members)
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 4)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
def test_map_field_reference(self):
class User(Document):
name = StringField()
class Group(Document):
members = MapField(ReferenceField(User))
User.drop_collection()
Group.drop_collection()
members = []
for i in xrange(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.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, User))
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 2)
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, User))
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 2)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, User))
User.drop_collection()
Group.drop_collection()
def test_dict_field(self):
class UserA(Document):
name = StringField()
class UserB(Document):
name = StringField()
class UserC(Document):
name = StringField()
class Group(Document):
members = DictField()
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
members = []
for i in xrange(1, 51):
a = UserA(name='User A %s' % i)
a.save()
b = UserB(name='User B %s' % i)
b.save()
c = UserC(name='User C %s' % i)
c.save()
members += [a, b, c]
group = Group(members=dict([(str(u.id), u) for u in members]))
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 4)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
Group.objects.delete()
Group().save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 1)
self.assertEqual(group_obj.members, {})
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
def test_dict_field_no_field_inheritance(self):
class UserA(Document):
name = StringField()
meta = {'allow_inheritance': False}
class Group(Document):
members = DictField()
UserA.drop_collection()
Group.drop_collection()
members = []
for i in xrange(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.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 2)
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, UserA))
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 2)
[m for m in group_obj.members]
self.assertEqual(q, 2)
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, UserA))
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 2)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 2)
[m for m in group_obj.members]
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, UserA))
UserA.drop_collection()
Group.drop_collection()
def test_generic_reference_map_field(self):
class UserA(Document):
name = StringField()
class UserB(Document):
name = StringField()
class UserC(Document):
name = StringField()
class Group(Document):
members = MapField(GenericReferenceField())
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
members = []
for i in xrange(1, 51):
a = UserA(name='User A %s' % i)
a.save()
b = UserB(name='User B %s' % i)
b.save()
c = UserC(name='User C %s' % i)
c.save()
members += [a, b, c]
group = Group(members=dict([(str(u.id), u) for u in members]))
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group.save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
# Document select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first().select_related()
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
self.assertEqual(q, 0)
group_objs = Group.objects.select_related()
self.assertEqual(q, 4)
for group_obj in group_objs:
[m for m in group_obj.members]
self.assertEqual(q, 4)
[m for m in group_obj.members]
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
Group.objects.delete()
Group().save()
with query_counter() as q:
self.assertEqual(q, 0)
group_obj = Group.objects.first()
self.assertEqual(q, 1)
[m for m in group_obj.members]
self.assertEqual(q, 1)
UserA.drop_collection()
UserB.drop_collection()
UserC.drop_collection()
Group.drop_collection()
def test_multidirectional_lists(self):
class Asset(Document):
name = StringField(max_length=250, required=True)
parent = GenericReferenceField(default=None)
parents = ListField(GenericReferenceField())
children = ListField(GenericReferenceField())
Asset.drop_collection()
root = Asset(name='', path="/", title="Site Root")
root.save()
company = Asset(name='company', title='Company', parent=root, parents=[root])
company.save()
root.children = [company]
root.save()
root = root.reload()
self.assertEquals(root.children, [company])
self.assertEquals(company.parents, [root])
def test_dict_in_dbref_instance(self):
class Person(Document):
name = StringField(max_length=250, required=True)
class Room(Document):
number = StringField(max_length=250, required=True)
staffs_with_position = ListField(DictField())
Person.drop_collection()
Room.drop_collection()
bob = Person.objects.create(name='Bob')
bob.save()
sarah = Person.objects.create(name='Sarah')
sarah.save()
room_101 = Room.objects.create(number="101")
room_101.staffs_with_position = [
{'position_key': 'window', 'staff': sarah},
{'position_key': 'door', 'staff': bob.to_dbref()}]
room_101.save()
room = Room.objects.first().select_related()
self.assertEquals(room.staffs_with_position[0]['staff'], sarah)
self.assertEquals(room.staffs_with_position[1]['staff'], bob)

90
tests/django_tests.py Normal file
View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import *
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()
class QuerySetTest(unittest.TestCase):
def setUp(self):
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))

File diff suppressed because it is too large Load Diff

502
tests/dynamic_document.py Normal file
View File

@@ -0,0 +1,502 @@
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.assertEquals(p.to_mongo(),
{"_types": ["Person"], "_cls": "Person",
"name": "James", "age": 34}
)
p.save()
self.assertEquals(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.assertEquals(p._delta(), ({'_types': ['Person'], 'age': 34, 'name': 'James', '_cls': 'Person'}, {}))
p.doc = 123
del(p.doc)
self.assertEquals(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.assertEquals(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.assertEquals(p.misc, {'hello': 'world'})
collection = self.db[self.Person._get_collection_name()]
obj = collection.find_one()
self.assertEquals(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.assertEquals(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.assertEquals(1, self.Person.objects(age=22).count())
p = self.Person.objects(age=22)
p = p.get()
self.assertEquals(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.assertEquals(Person.objects(age__icontains='ten').count(), 2)
self.assertEquals(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.assertEquals(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.assertEquals(1, self.Person.objects(age=20).count())
self.assertEquals(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.assertEquals(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.assertEquals(doc.embedded_field.__class__, Embedded)
self.assertEquals(doc.embedded_field.string_field, "hello")
self.assertEquals(doc.embedded_field.int_field, 1)
self.assertEquals(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEquals(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.assertEquals(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.assertEquals(doc.embedded_field.__class__, Embedded)
self.assertEquals(doc.embedded_field.string_field, "hello")
self.assertEquals(doc.embedded_field.int_field, 1)
self.assertEquals(doc.embedded_field.dict_field, {'hello': 'world'})
self.assertEquals(doc.embedded_field.list_field[0], '1')
self.assertEquals(doc.embedded_field.list_field[1], 2)
embedded_field = doc.embedded_field.list_field[2]
self.assertEquals(embedded_field.__class__, Embedded)
self.assertEquals(embedded_field.string_field, "hello")
self.assertEquals(embedded_field.int_field, 1)
self.assertEquals(embedded_field.dict_field, {'hello': 'world'})
self.assertEquals(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.assertEquals(p.age, 24)
self.assertEquals(p._get_changed_fields(), ['age'])
self.assertEquals(p._delta(), ({'age': 24}, {}))
p = self.Person.objects(age=22).get()
p.age = 24
self.assertEquals(p.age, 24)
self.assertEquals(p._get_changed_fields(), ['age'])
self.assertEquals(p._delta(), ({'age': 24}, {}))
p.save()
self.assertEquals(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.assertEquals(doc._get_changed_fields(), [])
self.assertEquals(doc._delta(), ({}, {}))
doc.string_field = 'hello'
self.assertEquals(doc._get_changed_fields(), ['string_field'])
self.assertEquals(doc._delta(), ({'string_field': 'hello'}, {}))
doc._changed_fields = []
doc.int_field = 1
self.assertEquals(doc._get_changed_fields(), ['int_field'])
self.assertEquals(doc._delta(), ({'int_field': 1}, {}))
doc._changed_fields = []
dict_value = {'hello': 'world', 'ping': 'pong'}
doc.dict_field = dict_value
self.assertEquals(doc._get_changed_fields(), ['dict_field'])
self.assertEquals(doc._delta(), ({'dict_field': dict_value}, {}))
doc._changed_fields = []
list_value = ['1', 2, {'hello': 'world'}]
doc.list_field = list_value
self.assertEquals(doc._get_changed_fields(), ['list_field'])
self.assertEquals(doc._delta(), ({'list_field': list_value}, {}))
# Test unsetting
doc._changed_fields = []
doc.dict_field = {}
self.assertEquals(doc._get_changed_fields(), ['dict_field'])
self.assertEquals(doc._delta(), ({}, {'dict_field': 1}))
doc._changed_fields = []
doc.list_field = []
self.assertEquals(doc._get_changed_fields(), ['list_field'])
self.assertEquals(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.assertEquals(doc._get_changed_fields(), [])
self.assertEquals(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.assertEquals(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.assertEquals(doc.embedded_field._delta(), (embedded_delta, {}))
embedded_delta.update({
'_types': ['Embedded'],
'_cls': 'Embedded',
})
self.assertEquals(doc._delta(), ({'embedded_field': embedded_delta}, {}))
doc.save()
doc.reload()
doc.embedded_field.dict_field = {}
self.assertEquals(doc._get_changed_fields(), ['embedded_field.dict_field'])
self.assertEquals(doc.embedded_field._delta(), ({}, {'dict_field': 1}))
self.assertEquals(doc._delta(), ({}, {'embedded_field.dict_field': 1}))
doc.save()
doc.reload()
doc.embedded_field.list_field = []
self.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field'])
self.assertEquals(doc.embedded_field._delta(), ({}, {'list_field': 1}))
self.assertEquals(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.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field'])
self.assertEquals(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.assertEquals(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.assertEquals(doc.embedded_field.list_field[2]._changed_fields, [])
self.assertEquals(doc.embedded_field.list_field[0], '1')
self.assertEquals(doc.embedded_field.list_field[1], 2)
for k in doc.embedded_field.list_field[2]._fields:
self.assertEquals(doc.embedded_field.list_field[2][k], embedded_2[k])
doc.embedded_field.list_field[2].string_field = 'world'
self.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field.2.string_field'])
self.assertEquals(doc.embedded_field._delta(), ({'list_field.2.string_field': 'world'}, {}))
self.assertEquals(doc._delta(), ({'embedded_field.list_field.2.string_field': 'world'}, {}))
doc.save()
doc.reload()
self.assertEquals(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.assertEquals(doc._get_changed_fields(), ['embedded_field.list_field'])
self.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(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.assertEquals(doc._delta(), ({'embedded_field.list_field.2.list_field': [2, {'hello': 'world'}, 1]}, {}))
doc.save()
doc.reload()
self.assertEquals(doc.embedded_field.list_field[2].list_field, [2, {'hello': 'world'}, 1])
doc.embedded_field.list_field[2].list_field.sort()
doc.save()
doc.reload()
self.assertEquals(doc.embedded_field.list_field[2].list_field, [1, 2, {'hello': 'world'}])
del(doc.embedded_field.list_field[2].list_field[2]['hello'])
self.assertEquals(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.assertEquals(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.assertEquals(doc._get_changed_fields(), ['dict_field.embedded.string_field'])
self.assertEquals(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)

File diff suppressed because it is too large Load Diff

23
tests/fixtures.py Normal file
View File

@@ -0,0 +1,23 @@
from datetime import datetime
from mongoengine import *
class PickleEmbedded(EmbeddedDocument):
date = DateTimeField(default=datetime.now)
class PickleTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
photo = FileField()
class Mixin(object):
name = StringField()
class Base(Document):
meta = {'allow_inheritance': True}

BIN
tests/mongoengine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

File diff suppressed because it is too large Load Diff

230
tests/signals.py Normal file
View File

@@ -0,0 +1,230 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import *
from mongoengine import signals
signal_output = []
class SignalTests(unittest.TestCase):
"""
Testing signals before/after saving and deleting.
"""
def get_signal_output(self, fn, *args, **kwargs):
# Flush any existing signal output
global signal_output
signal_output = []
fn(*args, **kwargs)
return signal_output
def setUp(self):
connect(db='mongoenginetest')
class Author(Document):
name = StringField()
def __unicode__(self):
return self.name
@classmethod
def pre_init(cls, sender, document, *args, **kwargs):
signal_output.append('pre_init signal, %s' % cls.__name__)
signal_output.append(str(kwargs['values']))
@classmethod
def post_init(cls, sender, document, **kwargs):
signal_output.append('post_init signal, %s' % document)
@classmethod
def pre_save(cls, sender, document, **kwargs):
signal_output.append('pre_save signal, %s' % document)
@classmethod
def post_save(cls, sender, document, **kwargs):
signal_output.append('post_save signal, %s' % document)
if 'created' in kwargs:
if kwargs['created']:
signal_output.append('Is created')
else:
signal_output.append('Is updated')
@classmethod
def pre_delete(cls, sender, document, **kwargs):
signal_output.append('pre_delete signal, %s' % document)
@classmethod
def post_delete(cls, sender, document, **kwargs):
signal_output.append('post_delete signal, %s' % document)
@classmethod
def pre_bulk_insert(cls, sender, documents, **kwargs):
signal_output.append('pre_bulk_insert signal, %s' % documents)
@classmethod
def post_bulk_insert(cls, sender, documents, **kwargs):
signal_output.append('post_bulk_insert signal, %s' % documents)
if kwargs.get('loaded', False):
signal_output.append('Is loaded')
else:
signal_output.append('Not loaded')
self.Author = Author
class Another(Document):
name = StringField()
def __unicode__(self):
return self.name
@classmethod
def pre_init(cls, sender, document, **kwargs):
signal_output.append('pre_init Another signal, %s' % cls.__name__)
signal_output.append(str(kwargs['values']))
@classmethod
def post_init(cls, sender, document, **kwargs):
signal_output.append('post_init Another signal, %s' % document)
@classmethod
def pre_save(cls, sender, document, **kwargs):
signal_output.append('pre_save Another signal, %s' % document)
@classmethod
def post_save(cls, sender, document, **kwargs):
signal_output.append('post_save Another signal, %s' % document)
if 'created' in kwargs:
if kwargs['created']:
signal_output.append('Is created')
else:
signal_output.append('Is updated')
@classmethod
def pre_delete(cls, sender, document, **kwargs):
signal_output.append('pre_delete Another signal, %s' % document)
@classmethod
def post_delete(cls, sender, document, **kwargs):
signal_output.append('post_delete Another signal, %s' % document)
self.Another = Another
# Save up the number of connected signals so that we can check at the end
# that all the signals we register get properly unregistered
self.pre_signals = (
len(signals.pre_init.receivers),
len(signals.post_init.receivers),
len(signals.pre_save.receivers),
len(signals.post_save.receivers),
len(signals.pre_delete.receivers),
len(signals.post_delete.receivers),
len(signals.pre_bulk_insert.receivers),
len(signals.post_bulk_insert.receivers),
)
signals.pre_init.connect(Author.pre_init, sender=Author)
signals.post_init.connect(Author.post_init, sender=Author)
signals.pre_save.connect(Author.pre_save, sender=Author)
signals.post_save.connect(Author.post_save, sender=Author)
signals.pre_delete.connect(Author.pre_delete, sender=Author)
signals.post_delete.connect(Author.post_delete, sender=Author)
signals.pre_bulk_insert.connect(Author.pre_bulk_insert, sender=Author)
signals.post_bulk_insert.connect(Author.post_bulk_insert, sender=Author)
signals.pre_init.connect(Another.pre_init, sender=Another)
signals.post_init.connect(Another.post_init, sender=Another)
signals.pre_save.connect(Another.pre_save, sender=Another)
signals.post_save.connect(Another.post_save, sender=Another)
signals.pre_delete.connect(Another.pre_delete, sender=Another)
signals.post_delete.connect(Another.post_delete, sender=Another)
def tearDown(self):
signals.pre_init.disconnect(self.Author.pre_init)
signals.post_init.disconnect(self.Author.post_init)
signals.post_delete.disconnect(self.Author.post_delete)
signals.pre_delete.disconnect(self.Author.pre_delete)
signals.post_save.disconnect(self.Author.post_save)
signals.pre_save.disconnect(self.Author.pre_save)
signals.pre_bulk_insert.disconnect(self.Author.pre_bulk_insert)
signals.post_bulk_insert.disconnect(self.Author.post_bulk_insert)
signals.pre_init.disconnect(self.Another.pre_init)
signals.post_init.disconnect(self.Another.post_init)
signals.post_delete.disconnect(self.Another.post_delete)
signals.pre_delete.disconnect(self.Another.pre_delete)
signals.post_save.disconnect(self.Another.post_save)
signals.pre_save.disconnect(self.Another.pre_save)
# Check that all our signals got disconnected properly.
post_signals = (
len(signals.pre_init.receivers),
len(signals.post_init.receivers),
len(signals.pre_save.receivers),
len(signals.post_save.receivers),
len(signals.pre_delete.receivers),
len(signals.post_delete.receivers),
len(signals.pre_bulk_insert.receivers),
len(signals.post_bulk_insert.receivers),
)
self.assertEqual(self.pre_signals, post_signals)
def test_model_signals(self):
""" Model saves should throw some signals. """
def create_author():
a1 = self.Author(name='Bill Shakespeare')
def bulk_create_author_with_load():
a1 = self.Author(name='Bill Shakespeare')
self.Author.objects.insert([a1], load_bulk=True)
def bulk_create_author_without_load():
a1 = self.Author(name='Bill Shakespeare')
self.Author.objects.insert([a1], load_bulk=False)
self.assertEqual(self.get_signal_output(create_author), [
"pre_init signal, Author",
"{'name': 'Bill Shakespeare'}",
"post_init signal, Bill Shakespeare",
])
a1 = self.Author(name='Bill Shakespeare')
self.assertEqual(self.get_signal_output(a1.save), [
"pre_save signal, Bill Shakespeare",
"post_save signal, Bill Shakespeare",
"Is created"
])
a1.reload()
a1.name='William Shakespeare'
self.assertEqual(self.get_signal_output(a1.save), [
"pre_save signal, William Shakespeare",
"post_save signal, William Shakespeare",
"Is updated"
])
self.assertEqual(self.get_signal_output(a1.delete), [
'pre_delete signal, William Shakespeare',
'post_delete signal, William Shakespeare',
])
signal_output = self.get_signal_output(bulk_create_author_with_load)
# The output of this signal is not entirely deterministic. The reloaded
# object will have an object ID. Hence, we only check part of the output
self.assertEquals(signal_output[3],
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]")
self.assertEquals(signal_output[-2:],
["post_bulk_insert signal, [<Author: Bill Shakespeare>]",
"Is loaded",])
self.assertEqual(self.get_signal_output(bulk_create_author_without_load), [
"pre_init signal, Author",
"{'name': 'Bill Shakespeare'}",
"post_init signal, Bill Shakespeare",
"pre_bulk_insert signal, [<Author: Bill Shakespeare>]",
"post_bulk_insert signal, [<Author: Bill Shakespeare>]",
"Not loaded",
])
self.Author.objects.delete()