Compare commits

...

1585 Commits

Author SHA1 Message Date
erdenezul
cf54d6d6f8 Merge pull request #1967 from erdenezul/bump_version_16_3
bump version 0.16.3
2018-12-06 21:56:31 +08:00
Erdenezul Batmunkh
a03fe234d0 bump version 0.16.3 2018-12-06 16:22:00 +08:00
erdenezul
d88d40cc08 Merge pull request #1966 from tjhall13/1965-fix-position-op-with-nested-list-in-embedded-document
Fix bug #1965 of $position and $push operators do not work with nested list
2018-12-06 14:02:46 +08:00
Trevor Hall
d3b4af116e Add fix to changelog.rst #1965 2018-12-05 23:35:42 -06:00
Trevor Hall
352b23331b Fix bug #1965 of $position and $push operators do not work with list in an EmbeddedDocument. Set key value to joined parts excluding the index at the end. Added test case 2018-12-05 20:18:48 -06:00
Bastien Gérard
bdd6041a5c Merge pull request #1960 from bagerard/bump_version_0_16_2
bump version to 0.16.2
2018-11-21 13:45:13 +01:00
Bastien Gérard
1894003f8a bump version to 0.16.2 2018-11-21 12:45:16 +01:00
erdenezul
220513ae42 Merge pull request #1959 from bagerard/fix_write_concern_default_param
Fix but with save(write_concern=None) - introduced in 0.16.1
2018-11-21 09:15:00 +08:00
Bastien Gérard
fcbabbe357 Fix but with save(write_concern=None) - introduced in 0.16.1 2018-11-21 00:03:08 +01:00
erdenezul
3627969fce Merge pull request #1952 from erdenezul/bump_v0.16.1
bump version 0.16.1
2018-11-14 17:23:10 +08:00
Erdenezul Batmunkh
8807c0dbef bump version 0.16.1 2018-11-14 17:10:17 +08:00
erdenezul
23cc9f6ff8 Merge pull request #1951 from bagerard/fix_cls_in_constructor
Regression bug fix - _cls not set in constructor
2018-11-14 17:02:17 +08:00
Bastien Gérard
e50799e9c4 Merge branch 'master' of github.com:MongoEngine/mongoengine into fix_cls_in_constructor 2018-11-13 21:48:16 +01:00
Bastien Gérard
b92c4844eb Merge pull request #1949 from bagerard/fix_delta_bug
Fix bug in _delta method - setting ListField to empty in DynamicDocument is faulty
2018-11-13 21:44:02 +01:00
Bastien Gérard
c306d42d08 Fix bug #1733 of _delta method (Issue with DynamicDocument and default value) + add test case 2018-11-13 20:57:41 +01:00
Bastien Gérard
e31558318e BugFix - _cls not set in constructor #1950 2018-11-13 20:52:39 +01:00
erdenezul
78a9420f26 Merge pull request #1944 from erdenezul/deprecation_warning_pymongo37
Use insert_one instead of deprecated one #1899
2018-11-13 19:40:22 +08:00
Erdenezul Batmunkh
b47c5b5bfc Adhere imports into existing one #1899 2018-11-12 09:53:39 +08:00
erdenezul
28a312accf Merge pull request #1946 from erdenezul/refactor_write_concern
Refactor write_concern #1945
2018-11-09 09:30:20 +08:00
Erdenezul Batmunkh
611094e92e Refactor write_concern #1945 2018-11-09 09:20:55 +08:00
Erdenezul Batmunkh
2a8579a6a5 Update changelog #1899 2018-11-09 09:12:29 +08:00
Erdenezul Batmunkh
47577f2f47 Update existing document #1899 2018-11-09 01:44:30 +08:00
Erdenezul Batmunkh
34e3e45843 Use insert_one instead of deprecated one #1899 2018-11-08 23:47:12 +08:00
Bastien Gérard
364dc9ddfb Merge pull request #1937 from bagerard/changelog
Bump version to 0.16.0 + update changelog
2018-11-05 09:38:58 +01:00
Bastien Gérard
23324f0f87 minor fix in ImageField docstring 2018-11-04 22:14:27 +01:00
Bastien Gérard
17fa9a3b77 bump version to 0.16.0 2018-11-03 21:48:12 +01:00
Bastien Gérard
424b3ca308 update changelog with changes since 0.15.3 2018-11-03 21:47:56 +01:00
Bastien Gérard
26e2fc8fd4 Merge pull request #1887 from bagerard/fix_changed_fields_issue_same_id_in_nested_doc2
Fix bug where an EmbeddedDocument with the same id as its parent would not be tracked for changes
2018-11-01 22:49:07 +01:00
Bastien Gérard
8e18484898 Add a comment on a suspicious line (#1322) 2018-11-01 22:28:12 +01:00
Bastien Gérard
354cfe0f9c Merge pull request #1935 from bagerard/generic_ref_field_changed
Fix bug when doing modifications to generic-referenced document
2018-10-31 15:53:13 +01:00
Bastien Gérard
983474b2bd Ignore 2 more flake8 warnings (introduced in latest flake8 3.6.0 release) 2018-10-30 23:40:46 +01:00
Bastien Gérard
14d861bcbb Merge branch 'master' of https://github.com/MongoEngine/mongoengine into generic_ref_field_changed 2018-10-30 23:21:19 +01:00
Bastien Gérard
f6cd349a16 Fix bug when using GenericReferenceField, modifications to the referenced document are tracked in the parent #1934 2018-10-30 23:00:05 +01:00
Bastien Gérard
8e1c4dec87 Merge pull request #1846 from bagerard/fix_validator_of_binary_field
fix validator of BinaryField
2018-10-26 22:14:08 +02:00
erdenezul
18b47e4a73 Merge pull request #1932 from bagerard/improve_index_doc
minor improvement to index doc
2018-10-25 10:19:04 +08:00
erdenezul
4f157f50ed Merge pull request #1931 from bagerard/fix_no_dereference_doc_asserts
fix some asserts in no_dereference doc
2018-10-25 10:15:33 +08:00
erdenezul
f44a2f4857 Merge pull request #1843 from terencehonles/add-__repr__-to-Q-and-QCombination
add __repr__ to Q and QCombination
2018-10-25 10:14:05 +08:00
Bastien Gérard
c685ace327 minor improvement to index doc 2018-10-23 23:55:55 +02:00
Bastien Gérard
f23b0faf41 fix some asserts in no_dereference doc 2018-10-23 22:10:34 +02:00
erdenezul
e0e2ca7ccd Merge pull request #1930 from bagerard/improve_doc_indexes
Improve documentation for additional index options
2018-10-23 10:16:27 +08:00
Bastien Gérard
83fe7f7eef Document the index option and the fact that additional options gets forwarded to pymongo's create_index method 2018-10-22 21:54:46 +02:00
erdenezul
1feaa8f2e9 Merge pull request #1927 from possnfiffer/patch-1
Update README.rst
2018-10-22 16:44:36 +08:00
__ROLLER__
598d6bf4c5 Update README.rst
Added information on using pipenv to the Installation section
2018-10-21 12:47:23 -06:00
erdenezul
0afd5a40d6 Merge pull request #1923 from bagerard/update_document_ids_during_bulk_insert
Update the ids of the given documents during bulk insert
2018-10-17 19:03:11 +08:00
Bastien Gérard
26b70e9ed3 fix test 2018-10-16 22:55:55 +02:00
Bastien Gérard
a1a93a4bdd minor additions 2018-10-16 22:35:58 +02:00
Bastien Gérard
4939a7dd7c update input document ids during bulk insert #1919 2018-10-16 22:21:53 +02:00
erdenezul
b0148e7860 Merge pull request #1921 from bagerard/error_referencefield_of_abstract_classes
Fix a bug when using a ReferenceField(AbstractClass)
2018-10-16 07:55:52 +08:00
Bastien Gérard
59a06a242d Fix a bug when using a ReferenceField(AbstractClass) #1920 2018-10-15 22:32:11 +02:00
erdenezul
ffe902605d Merge pull request #1918 from bagerard/improve_error_cant_subclass_document
Improve the error message that mentions that Document cant be subclassed
2018-10-15 11:18:13 +08:00
Bastien Gérard
556f7e85fc Improve the error message that mentions that Document cant be subclassed 2018-10-10 23:13:34 +02:00
erdenezul
45c86be402 Merge pull request #1916 from liuq/master
Bug fix: pre_save_post_validation could, in principle, modify the document
2018-10-10 17:12:39 +08:00
erdenezul
bf34f413de Merge pull request #1912 from bagerard/fix_deprecation_warning_Hashable
Fix deprecation warning about Hashable in py3.7
2018-10-10 16:32:50 +08:00
erdenezul
9b022b187f Merge pull request #1905 from bagerard/rework_in_test
minor rework in test
2018-10-10 16:29:53 +08:00
erdenezul
c3409d64dc Merge pull request #1906 from vainu-arto/fix_listfield_validate
Fix invalid isinstance check in ListField.validate
2018-10-10 15:58:47 +08:00
Arto Jantunen
3c5c3b5026 Fix invalid isinstance check in ListField.validate
Using QuerySet directly would fail if QuerySetNoCache was used. Sadly the
test suite does not have a case where QuerySet would appear here, so adding
a test for this special case is not trivial.
2018-10-10 10:43:29 +03:00
erdenezul
f240f00d84 Merge pull request #1914 from bagerard/improve_code_quality
Improve code quality (mainly based on pylint findings)
2018-10-10 15:40:07 +08:00
Luca Di Gaspero
68c7764c63 Small fix to ensure to use the right document after pre_save_post_validation hook, which might modify the document itself (e.g., for adding a etag). 2018-10-08 17:06:08 +02:00
Bastien Gérard
adfb039ba6 Improve overall code quality (based on pylint findings) 2018-10-07 23:05:18 +02:00
Bastien Gérard
89416d9856 Fix deprecation warning about Hashable in py3.7 2018-10-05 16:17:55 +02:00
Bastien Gérard
9b6c972e0f renamed decorator needs_mongodb_x in test + fixes ref to mongo 2.4 from travis files 2018-10-02 22:03:55 +02:00
erdenezul
55fc04752a Merge pull request #1903 from bagerard/mongodb_32
Fixed tests to allow support of MongoDB 3.2 + Drop Mongo2.4 from CI
2018-10-01 10:37:12 +08:00
Bastien Gérard
96f0919633 - Fixed tests to allow support of MongoDB 3.2
- Replaced MongoDB 2.4 tests in CI by MongoDB 3.2
2018-09-30 21:59:15 +02:00
erdenezul
17b140baf4 Merge pull request #1901 from bagerard/fix_side_effect_no_dereference_on_GenericReferenceField
Fix side effect of no_dereference on GenericReferenceField
2018-09-26 06:45:17 +08:00
Bastien Gérard
45c2151d0f Fix side effect of no_dereference on GenericReferenceField 2018-09-25 22:56:57 +02:00
erdenezul
1887f5b7e7 Merge pull request #1877 from bagerard/improve_ComplexField_validation_edge_case
Handles edge case when EmbeddedDocumentListField receives a Document and not a list
2018-09-18 14:49:24 +08:00
erdenezul
708d1c7a32 Merge pull request #1892 from bagerard/no_dereference_queryset_fix_side_effect
fix side effect from queryset.no_dereference
2018-09-18 14:40:14 +08:00
erdenezul
acf8c3015a Merge pull request #1893 from bagerard/swat_url_fix
(CLONE) fix - allow url with underscore in domain
2018-09-18 14:29:55 +08:00
Bastien Gérard
f83ae5789b fix side effect from queryset's no_dereference #1677 2018-09-16 23:11:45 +02:00
Bastien Gérard
57ccfcfc1b Merge branch 'master' of https://github.com/MongoEngine/mongoengine into swat_url_fix 2018-09-16 23:03:16 +02:00
erdenezul
dd0fdcfdd4 Merge pull request #1889 from bagerard/compatible_pyton_2_3_improvement
improve 2-3 codebase compatibility
2018-09-15 00:44:38 +08:00
erdenezul
5c805be067 Merge pull request #1879 from last-partizan/fix-choices-display
fixed TypeError on translated choices
2018-09-11 18:34:00 +08:00
Sergey Tereschenko
e423380d7f Merge remote-tracking branch 'original-repo/master' into fix-choices-display 2018-09-11 13:24:52 +03:00
erdenezul
4d8bebc917 Merge pull request #1886 from bagerard/patch-4
Updating inheritance doc
2018-09-10 09:12:50 +08:00
Bastien Gérard
4314fa883f improve 2-3 codebase compatibility 2018-09-09 23:32:10 +02:00
Bastien Gérard
d6e39b362b Updating inheritance doc
Fixes #429
2018-09-09 14:52:08 +02:00
Bastien Gérard
f89214f9cf Fixes bug where an EmbeddedDocument that shares the same id of its parent Document could be missing updates when .save was called
Fixes #1768, Fixes #1685
2018-09-09 10:40:51 +02:00
erdenezul
d17cac8210 Merge pull request #1884 from erdenezul/limit_behavior
Clone of Limit behavior pr #1614
2018-09-07 15:48:54 +08:00
Erdenezul Batmunkh
aa49283fa9 fix trailing whitespace 2018-09-07 15:39:55 +08:00
erdenezul
e79ea7a2cf Merge branch 'master' into limit_behaviour 2018-09-07 15:34:23 +08:00
erdenezul
8a1d280f19 Merge pull request #1881 from bagerard/refactoring_poor_assertions_in_tests
Refactored some poor assertions across test suite
2018-09-07 14:59:37 +08:00
erdenezul
6a8eb9562f Merge pull request #1882 from bagerard/fix_doc_index_options
fix doc for meta index_options
2018-09-07 14:37:38 +08:00
erdenezul
8f76e1e344 Merge branch 'master' into refactoring_poor_assertions_in_tests 2018-09-07 14:37:28 +08:00
erdenezul
7b9f084e6b Merge branch 'master' into fix_doc_index_options 2018-09-07 14:27:57 +08:00
erdenezul
5b1693a908 Merge pull request #1871 from bagerard/improve_query_counter
Fix few things related to query_counter context manager
2018-09-07 14:22:12 +08:00
erdenezul
fd7c00da49 Merge pull request #1883 from svanburen/patch-1
Update link to index options
2018-09-07 10:46:39 +08:00
Stefan VanBuren
7fc5ced3af Update link to index options 2018-09-06 16:50:31 -04:00
Bastien Gérard
a86092fb64 fix doc for meta index_options 2018-09-06 22:33:33 +02:00
Bastien Gérard
003827e916 rewrote some poorly written assertions like: assertTrue(isinstance(a, b)) assertTrue(a==b) assertTrue(a!=b) assertTrue(a in b) 2018-09-06 21:47:06 +02:00
Sergey Tereschenko
b15673c525 fixed TypeError on translated choices
join expect strings, but if we use django ugettext_lazy like this:

    choices=[(1, _("One")), (2, _("Two"))]

there will be TypeError: sequence item 0: expected string, __proxy__ found

this commif fixes error by converting label to string
2018-09-05 11:53:15 +03:00
erdenezul
00363303b1 Merge pull request #1875 from bagerard/dax_py3
CLONE - return instead of raising StopIteration
2018-09-05 09:42:07 +08:00
erdenezul
48fbe890f8 Merge pull request #1876 from bagerard/get_rid_of_DictField_basecls
Remove DictField.basecls related code, it is useless
2018-09-05 09:38:52 +08:00
erdenezul
4179877cc7 Merge pull request #1878 from bagerard/fix_complex_datetime_default
Fix ComplexDateTime default value
2018-09-05 09:37:05 +08:00
Bastien Gérard
282b83ac08 Fix default value of ComplexDateTime + fixed descriptor .__get__ for class attribute 2018-09-04 23:48:07 +02:00
Bastien Gérard
193656e71b detect when EmbeddedDocumentListField receives an EmbeddedDocument instance instead of a list. It is a common mistake and the error wasn't meaningful (was fired from _from_son)
(relates to #1464)
2018-09-04 22:26:19 +02:00
Bastien Gérard
a25d127f36 Remove DictField.basecls related code, it is useless 2018-09-04 20:51:06 +02:00
Bastien Gérard
cf9df548ca reverted back to the StopIteration in queryset.next that one didnt have to be changed (test stalled) 2018-09-04 19:18:40 +02:00
Bastien Gérard
f29b93c762 Merge branch 'master' of https://github.com/MongoEngine/mongoengine into dax_py3 2018-09-04 16:06:45 +02:00
erdenezul
032ace40d1 Merge pull request #1668 from erdenezul/read_preference_parse
parse read_preference from conn_host #1665
2018-09-04 21:58:31 +08:00
erdenezul
f74dd1cb3c Merge pull request #1591 from axu2/axu2-docs-patch-3
docs: use explicit register_delete_rule example
2018-09-04 21:50:29 +08:00
erdenezul
29889d1e35 Merge pull request #1868 from bagerard/yalon-master
CLONE - When building a query set from filters that reference the same field several times, do not assume each value is a dict
2018-09-04 21:17:49 +08:00
erdenezul
d6d19c4229 Merge pull request #1872 from bagerard/inc_operator_with_decimal
fix inc/dec operator with DecimalField + improve its doc
2018-09-04 21:07:04 +08:00
Bastien Gérard
ab08e67eaf fix inc/dec operator with decimal 2018-09-04 14:53:55 +02:00
erdenezul
00bf6ac258 Merge pull request #1874 from erdenezul/reduce-cycle-complexity
reduce cycle complexity using logic map
2018-09-04 20:52:26 +08:00
Erdenezul Batmunkh
b65478e7d9 trigger ci 2018-09-04 20:44:44 +08:00
Erdenezul Batmunkh
e83b529f1c flip value before changing op to inc 2018-09-04 20:38:42 +08:00
Erdenezul Batmunkh
408274152b reduce cycle complexity using logic map 2018-09-04 20:24:34 +08:00
Bastien Gérard
8ff82996fb Fix few things related to query_counter context manager:
- Improve doc
- Fix the fact that the context was modifying the initial profiling_level in case it was != 0
- Ignores 'killcursors' to fix flaky test that were impacted by killcursors queries (#1869)
2018-09-04 12:07:13 +02:00
Bastien Gérard
d59c4044b7 Merge branch 'master' of https://github.com/MongoEngine/mongoengine into yalon-master 2018-09-03 11:23:28 +02:00
erdenezul
3574e21e4f Merge pull request #1859 from bagerard/fix_EmbeddedDocumentField_init_with_Document
Ensures EmbeddedDocumentField does not accepts references to Document
2018-09-03 17:22:08 +08:00
erdenezul
5a091956ef Merge pull request #1862 from bagerard/document_name_param_for_indexes
Documented that it is possible to specify a 'name' for the indexes (using the dict definition)
2018-09-03 17:20:17 +08:00
erdenezul
14e9c58444 Merge pull request #1866 from bagerard/no_subclass_fixes
Fixes 2 bugs in no_subclasses context mgr
2018-09-03 17:19:39 +08:00
erdenezul
bfe5b03c69 Merge pull request #1867 from bagerard/pmatos_patch-1
Doc update: Clarify comment in validation example
2018-09-03 07:42:39 +08:00
Bastien Gérard
f96f7f840e Merge branch 'master' of https://github.com/MongoEngine/mongoengine into dax_py3 2018-09-02 15:36:35 +02:00
Bastien Gérard
a3bcf26dce Merge branch 'master' of https://github.com/MongoEngine/mongoengine into pmatos_patch-1 2018-09-02 15:27:37 +02:00
Bastien Gérard
a7852a89cc Fixes 2 bugs in no_subclasses context mgr (__exit__ swallows exception + repair feature) 2018-09-01 23:30:50 +02:00
erdenezul
1b0c761fc0 Merge pull request #1863 from bagerard/fix_string_format_bug
Fix string formatting bug in ReferenceField.validate
2018-09-01 19:52:37 +08:00
Bastien Gérard
5e4e8d4eda Merge branch 'master' of github.com:KCarretto/mongoengine 2018-09-01 12:04:23 +02:00
Bastien Gérard
bd524d2e1e Documented that it is possible to specify a name when using a dict to define an index 2018-09-01 00:12:49 +02:00
erdenezul
60fe919992 Merge pull request #1860 from bagerard/improve_doc_EmbeddedDocumentList.filter
Improve doc of EmbeddedDocumentList.filter,
2018-08-31 11:35:25 +08:00
erdenezul
b90063b170 Merge pull request #1861 from bagerard/minor_improvement_to_DateTimeField_default
minor improvement to DateTimeField doc
2018-08-31 11:32:40 +08:00
Bastien Gérard
d9fce49b08 minor improvement to DateTimeField doc 2018-08-30 22:46:37 +02:00
Bastien Gérard
5dbee2a270 Ensures EmbeddedDocumentField does not accepts references to Document classes in its constructor 2018-08-30 22:06:36 +02:00
Bastien Gérard
4779106139 Improve doc of EmbeddedDocumentList.filter and clarify that it does not supports operators like __lte, __gte, etc 2018-08-30 21:50:03 +02:00
erdenezul
bf2de81873 Merge pull request #1807 from bagerard/lazy_regex_compilation
Implemented lazy regex compiling for issue #1806
2018-08-30 21:33:10 +08:00
erdenezul
28cdedc9aa Merge pull request #1857 from bagerard/fix_doc_of_delete_write_concern
Fix .delete doc of **write_concern
2018-08-30 21:28:59 +08:00
erdenezul
7e90571404 Merge pull request #1858 from bagerard/fix_index_creation_error_swallowed
Fix index creation error that was swallowed by hasattr under python2
2018-08-30 21:28:38 +08:00
erdenezul
42bbe63927 Merge pull request #1856 from bagerard/document_created_in_from_json_doc
Document the 'created' attribute of .from_json
2018-08-30 21:28:04 +08:00
Bastien Gérard
b4860de34d Fix index creation error that was swallowed by hasattr under python2 (#1688) 2018-08-30 10:39:07 +02:00
Bastien Gérard
576f23d5fb Fix .delete doc of **write_concern as suggested by #1779 2018-08-28 22:58:13 +02:00
Bastien Gérard
86548fc7bf Document the attribute of .from_json 2018-08-28 22:42:51 +02:00
Terence D. Honles
b3b4d992fe add Q repr tests 2018-08-27 19:35:47 -07:00
erdenezul
9ad959a478 Merge pull request #1845 from bagerard/clean_dead_code
remove dead code
2018-08-26 08:33:20 +08:00
erdenezul
cc00a321da Merge pull request #1848 from bagerard/patch-4
Update gridfs.rst - make it clear that user must call Document.save()
2018-08-26 08:28:42 +08:00
erdenezul
de74273108 Merge pull request #1851 from bagerard/fix_IndexError_iter_while_modif_list
fix BaseList.__iter__ operator + minor improvements
2018-08-26 08:26:43 +08:00
Bastien Gérard
a7658c7573 fix BaseList.__iter__ operator (#1305) + minor improvements 2018-08-25 16:33:43 +02:00
Bastien Gérard
48a85ee6e0 update related tests 2018-08-20 00:10:27 +02:00
Bastien Gérard
461b789515 relates to (#710)
- Update gridfs.rst to make it clearer that you should save the Document hosting the GridFSProxy after calling .delete() or .replace() on the GridFSProxy
- updated GridFSProxy.__str__ so that it would always print both the filename and the grid_id. This should improve debugging
2018-08-19 23:45:08 +02:00
Bastien Gérard
b71ff6fbb8 fix validator of BinaryField. In fact bson.Binary fails if we give it unicode in input #273 2018-08-18 23:04:46 +02:00
Bastien Gérard
1bcdcce93a remove dead code 2018-08-18 00:03:17 +02:00
Terence D. Honles
c09bfca634 add __repr__ to Q and QCombination 2018-08-15 12:36:13 -07:00
erdenezul
36c5f02bfb Merge pull request #1840 from bagerard/patch-2
Update signals doc - clarification on EmbeddedDocument
2018-08-15 20:41:04 +08:00
erdenezul
eae6e5d9a1 Merge pull request #1841 from bagerard/update_contributing_guidelines_python2_3
Contributing doc update - Clarify python 2 should be used to develop on mongoengine
2018-08-15 20:35:36 +08:00
Bastien Gérard
364813dd73 Clarify that you should use python 2 when developing on mongoengine #1837 2018-08-13 22:34:37 +02:00
Bastien Gérard
1a2b1f283b Update signals doc - clarification on EmbeddedDocument
Since there is a .save() method on EmbeddedDocument, you could be tempted to attach a pre_save event to an EmbeddedDocument (#1720). This update is an attempt to make this clearer.
2018-08-13 22:16:32 +02:00
erdenezul
a0e5cf4ecc Merge pull request #1834 from JoschSan/patch-1
Update connecting.rst
2018-08-08 15:59:09 +08:00
JoschSan
820f7b4d93 Update connecting.rst
My suggest is append the parameter in authentication_source in authentication example, because was included in https://github.com/MongoEngine/mongoengine/pull/590/files
2018-08-07 14:29:15 -04:00
Bastien Gérard
727866f090 fix styling flake8 error from CI 2018-08-05 22:30:51 +02:00
Bastien Gérard
3d45cdc339 Implemented lazy regex compiling in Field classes to improve 'import mongoengine' performance 2018-08-03 23:36:26 +02:00
erdenezul
02a557aa67 Merge pull request #1825 from orsinium/date-field
Date field
2018-07-27 19:32:27 +08:00
Gram
6da27e5976 +changelog 2018-07-27 12:31:27 +03:00
Gram
19a6e324c4 +tests for date field 2018-07-20 17:37:23 +03:00
Gram
62eadbc174 +date field 2018-07-20 17:21:57 +03:00
Erdenezul Batmunkh
ae783d4f45 tweak tox.ini to pass tests 2018-07-16 17:25:29 +08:00
Erdenezul Batmunkh
1241a902e3 fix changelog 2018-07-16 09:49:16 +08:00
Erdenezul Batmunkh
fdba648afb fix pypi trigger 2018-07-16 09:19:50 +08:00
Emmanuel Leblond
b070e7de07 Fix travis's pypi release trigger 2018-06-29 15:47:28 +02:00
Tony Narlock
d0741946c7 Fix a typo (#1813)
an -> a
2018-06-28 17:19:48 +02:00
Tal Yalon
080226dd72 Fix issue #1286 and #844.: when building a query set from filters that reference the same field several times, do not assume each value is a dict 2018-06-22 14:16:17 +03:00
erdenezul
3cb6a5cfac Merge pull request #1575 from zetaben/master
Make queryset aggregates obey read_preference
2018-05-24 22:23:49 +08:00
erdenezul
758971e068 Merge pull request #1799 from erdenezul/ensure_indexes_fails_on_slave
Ensure indexes fails on slave #1338
2018-05-23 16:09:51 +08:00
Erdenezul Batmunkh
8739ab9c66 fix syntax #1338 2018-05-23 15:59:18 +08:00
Erdenezul Batmunkh
e8e47c39d7 add changelog #1338 2018-05-23 15:54:44 +08:00
Erdenezul Batmunkh
446c101018 dont call ensure_indexes on slave #1338 2018-05-23 15:53:30 +08:00
erdenezul
3654591a1b Merge pull request #1584 from etng/master
[fix]validation list field with multi choice values
2018-05-21 16:33:10 +08:00
erdenezul
7fb1c9dd35 Merge branch 'master' into master 2018-05-21 16:22:07 +08:00
erdenezul
0fffaccdf4 Merge pull request #1640 from erdenezul/fix_changed_field_reload
add fix for reload(fields) affect changed fields #1371
2018-05-21 16:16:37 +08:00
Erdenezul Batmunkh
5902b241f9 Merge branch 'fix_changed_field_reload' of github.com:erdenezul/mongoengine into fix_changed_field_reload 2018-05-21 16:07:31 +08:00
Erdenezul Batmunkh
784386fddc add changelog #1371 2018-05-21 16:07:08 +08:00
Erdenezul
d424583cbf fix flake8 error #1371 2018-05-21 16:06:04 +08:00
Erdenezul
290b821a3a add fix for reload(fields) affect changed fields #1371 2018-05-21 16:06:04 +08:00
erdenezul
a0dfa8d421 Merge pull request #1723 from chhsiao1981/dereference-add-check-dbref-type
defensive programming for v as an instance of DBRef in dereference.
2018-05-21 13:00:46 +08:00
erdenezul
ceb00f6748 Merge pull request #1796 from erdenezul/new_bulk_insert_test
modify bulk_insert test for pymongo3
2018-05-21 10:24:03 +08:00
Erdenezul Batmunkh
9bd328e147 query_counter fix 2018-05-21 10:04:59 +08:00
Erdenezul Batmunkh
6fb5c312c3 fix test error 2018-05-21 09:54:19 +08:00
Erdenezul Batmunkh
3f9ff7254f fix queryset tests 2018-05-21 09:46:23 +08:00
Erdenezul Batmunkh
f7a3acfaf4 query profiler test fix 2018-05-21 09:34:44 +08:00
Erdenezul Batmunkh
e4451ccaf8 insert_many uses only one insert 2018-05-21 09:24:40 +08:00
Erdenezul Batmunkh
2adb640821 modify bulk_insert test for pymongo3 2018-05-21 09:19:03 +08:00
erdenezul
765038274c Merge pull request #1691 from rcscott/document-auto-create-index
Add documentation for auto_create_index to the Indexing section of the Docs
2018-05-21 08:55:25 +08:00
erdenezul
2cbdced974 Merge pull request #1793 from erdenezul/insert_pymongo3
use insert_one, insert_many and remove deprecated one #1491
2018-05-20 18:48:14 +08:00
Erdenezul Batmunkh
fc5d9ae100 pymongo3 does not support continue_on_error 2018-05-20 18:39:22 +08:00
Erdenezul Batmunkh
506168ab83 use write_concern class 2018-05-20 18:25:50 +08:00
Erdenezul Batmunkh
088fd6334b bulkwriteerror does not trigger 2018-05-20 18:06:59 +08:00
Erdenezul Batmunkh
94cda90a6e fix syntax 2018-05-20 17:56:19 +08:00
Erdenezul Batmunkh
78601d90c9 fix typo 2018-05-20 17:54:13 +08:00
Erdenezul Batmunkh
fa4ac95ecc catch bulkwriteerror 2018-05-20 17:49:49 +08:00
Erdenezul Batmunkh
dd4d4e23ad call insert method with set_write_concern 2018-05-20 15:51:02 +08:00
Erdenezul Batmunkh
acba86993d set_write_concern pymongo3 2018-05-20 15:43:19 +08:00
Erdenezul Batmunkh
0fc55451c2 fix style and ids need to be an array 2018-05-20 15:40:26 +08:00
Erdenezul Batmunkh
5c0bd8a810 fix inserted_ids 2018-05-20 15:40:26 +08:00
Erdenezul Batmunkh
1aebc95145 use insert_one, insert_many and remove deprecated one #1491 2018-05-20 15:40:26 +08:00
Erdenezul Batmunkh
1d3f20b666 fix style and ids need to be an array 2018-05-20 14:41:25 +08:00
Erdenezul Batmunkh
eb2e106871 Merge branch 'insert_pymongo3' of github.com:erdenezul/mongoengine into insert_pymongo3 2018-05-20 14:33:35 +08:00
Erdenezul Batmunkh
f9a887c8c6 fix inserted_ids 2018-05-20 14:33:12 +08:00
erdenezul
67ab810cb2 Merge branch 'master' into insert_pymongo3 2018-05-20 14:06:16 +08:00
Erdenezul Batmunkh
3e0d84383e use insert_one, insert_many and remove deprecated one #1491 2018-05-20 13:41:20 +08:00
erdenezul
d245ea3eaa Merge pull request #1792 from erdenezul/update_pymongo3
use update_many, update_one #1491
2018-05-20 13:34:03 +08:00
Erdenezul Batmunkh
843fc03bf4 add changelog for update_one,update_many 2018-05-20 13:16:25 +08:00
Erdenezul Batmunkh
c83c635067 fix import order 2018-05-20 13:04:51 +08:00
Erdenezul Batmunkh
f605eb14e8 fix style 2018-05-20 12:54:24 +08:00
Erdenezul Batmunkh
fd02d77c59 drop pymongo 2.x support in update 2018-05-20 12:43:24 +08:00
Erdenezul Batmunkh
0da8fb379d Merge remote-tracking branch 'calgary/issue-1491' into update_pymongo3 2018-05-20 12:38:22 +08:00
Erdenezul Batmunkh
257a43298b use MongoClient.is_mongos in ensure indexes #1759 2018-05-20 12:31:27 +08:00
erdenezul
a2d3bcd571 Merge pull request #1562 from erdenezul/support_multiple_operator
support multiple operator #1510
2018-05-20 12:20:04 +08:00
erdenezul
d4142c2cdd Merge pull request #1790 from erdenezul/compare_index_correct_behavior_text
Compare_indexes correct behavior for text index
2018-05-20 11:46:35 +08:00
Erdenezul Batmunkh
e50d66b303 skip mongodb 2.4 2018-05-20 11:26:30 +08:00
Erdenezul Batmunkh
08b6433843 fix compare_indexes for text indexes #1751 2018-05-20 11:03:13 +08:00
Erdenezul Batmunkh
8cd536aab5 Merge branch 'issue-1751' of github.com:th-erker/mongoengine into missing_text_index_ensure_indexes 2018-05-20 10:06:08 +08:00
erdenezul
2b495c648f Merge pull request #1769 from mindojo-victor/master
Fix deprecation warning for StopIteration
2018-05-20 09:36:19 +08:00
erdenezul
06048b6d71 Merge pull request #1782 from nosahama/patch-1
Updated text-indexes.rst
2018-05-19 09:25:54 +08:00
erdenezul
bb22287336 Merge pull request #1784 from werat/fix_override_comment
Restore comment from cached value after queryset copy
2018-05-19 09:23:58 +08:00
erdenezul
a45942a966 Merge pull request #1789 from chidaobanjiu/patch-1
Fix typo
2018-05-19 09:20:47 +08:00
Benjamin Jiang
85d621846d Fix typo 2018-05-16 11:37:32 +08:00
erdenezul
534acf8df2 Merge pull request #1731 from van4oza/patch-1
Insert null values fix
2018-05-15 14:18:24 +08:00
Andy Yankovsky
5a6d4387ea Restore comment from cached value after cursor copy 2018-05-07 23:17:12 +03:00
erdenezul
317e844886 Merge pull request #1765 from kushalmitruka/master
fixed : `pull` not working for EmbeddedDocumentListField, only working for ListFields #1534
2018-05-03 09:44:54 +08:00
Kushal Mitruka
b1f62a2735 added import in tests/queryset/transform.py for SON object 2018-05-01 21:08:43 +05:30
Kushal Mitruka
65e4fea4ef added test cases for update pull queries 2018-05-01 20:32:38 +05:30
Emmanuel Nosa Evbuomwan
faca8512c5 Updated text-indexes.rst
The search statement under the `Ordering by text score` section uses `search` on the QuerySet object instead of `search_text` and thus raised an `AttributeError`

AttributeError: 'QuerySet' object has no attribute 'search'
2018-04-29 17:32:03 +01:00
erdenezul
2121387aa2 Merge pull request #1737 from CalgaryMichael/remove-pushall
Removing the usage of the '$pushAll' operator
2018-04-17 17:50:52 +08:00
erdenezul
72c4444a60 Merge branch 'master' into remove-pushall 2018-04-17 16:38:08 +08:00
erdenezul
2d8d2e7e6f Merge pull request #1771 from bchrobot/add-lazy-reference-documentation
Add documentation for LazyReference and GenericLazyReference fields.
2018-04-12 23:12:12 +08:00
Benjamin Chrobot
49bff5d544 Add documentation for LazyReference and GenericLazyReference fields. 2018-04-12 10:47:52 -04:00
Victor
806a80cef1 Fixes #1641
There's no need to explicitly raise StopIteration as that's what a bare return statement does for a generator function - so yes they're the same.
As of late 2014 return is correct and raise StopIteration for ending a generator is on a depreciation schedule. See PEP 479 for full details.
https://stackoverflow.com/q/14183803/248296
https://www.python.org/dev/peps/pep-0479/
2018-04-09 17:29:09 +03:00
Kushal Mitruka
c6f0d5e478 fixed pull queries for embeddeddocumentlistfields
Updated mongoengine.queryset.transform.update method to handle EmbeddedDocuementListField during pull operations in DB using mongoegning ORM
fixed : .udpate(pull__emb_doc__emb_doc_list=doc)
2018-04-01 20:11:22 +05:30
erdenezul
bf30aba005 Merge pull request #1763 from malthejorgensen/patch-2
Docs, queryset.update(): Fix backtick mistake
2018-03-31 18:02:00 +08:00
Malthe Jørgensen
727778b730 Docs, queryset.update(): Fix backtick mistake
Code should be marked with double backticks
2018-03-30 20:41:39 +02:00
erdenezul
b081ffce50 Merge pull request #1762 from malthejorgensen/patch-1
queryset.update: `full_result`-argument not clearly described
2018-03-30 08:31:35 +08:00
Malthe Jørgensen
e46779f87b Docs, queryset.update: full_result-arg not clearly described
The documentation for the `full_result`-argument to `queryset.update()`
can be read as returning the update documents/objects, whereas it's
really returning just the full "PyMongo result dictionary".

This commit adds some wording and an example dictionary,
to make it clear what the behavior is.
2018-03-27 14:14:08 +02:00
Stefan Wojcik
dabe8c1bb7 highlight places where ValidationError is raised outside of validate() method 2018-03-14 14:26:46 -04:00
erdenezul
4042f88bd8 Merge pull request #1753 from JohnAD/master
Edit EmbeddedDocumentListField update() doc
2018-03-11 19:35:13 +08:00
John Dupuy
a0947d0c54 Edit EmbeddedDocumentListField update() doc 2018-03-10 23:24:04 -06:00
Thomas Erker
a34fd9ac89 Add testcase for #1751 2018-03-08 11:25:36 +00:00
estein-de
aa68322641 MongoDB wants dates stored in UTC, but the functions used in this documentation to generate datetime objects would use server's local timezone - fix it! (#1662) 2018-02-27 09:43:09 -05:00
KCarretto
2d76aebb8e Fixed formatting issue 2018-02-21 05:02:35 -05:00
erdenezul
7cc1d23bc7 Merge pull request #1655 from werat/fix_update_pull_in
Fix pull+in update queries
2018-02-21 13:36:04 +08:00
Andy Yankovsky
0bd2103a8c Add test for document update 2018-02-20 00:02:12 +03:00
Sangmin In
7d8916b6e9 fix case inconsistent casing of "MongoDB" (#1744) 2018-02-10 10:59:47 -05:00
swathi
8b5df3ca17 fix - allow url with underscore in domain 2018-02-08 12:31:40 -08:00
erdenezul
ffdfe99d37 Merge pull request #1636 from erdenezul/fix_shard_key_modify
Fix Document.modify fail on sharded collection #1569
2018-02-06 07:25:42 +08:00
Ivan Pogrebkov
7efa67e7e6 reverse to 'style fix' 2018-02-05 12:35:06 +03:00
Ivan Pogrebkov
d69808c204 oh, ok... 2018-02-05 12:33:58 +03:00
Ivan Pogrebkov
de360c61dd removed useless lines 2018-02-05 04:26:25 +03:00
Ivan Pogrebkov
6b04ddfad1 >< 2018-02-05 04:24:03 +03:00
Ivan Pogrebkov
0d854ce906 style fix 2018-02-05 03:24:53 +03:00
Calgary Michael
38fdf26405 added tests for push and push_all 2018-02-04 10:23:50 -06:00
Calgary Michael
6835c15d9b fixing bug in previous commit 2018-02-02 22:41:07 -06:00
Calgary Michael
fa38bfd4e8 made set_write_concern python2.7 compatible 2018-02-02 22:30:06 -06:00
Calgary Michael
4d5c6d11ab removed deprecated warning for 'update' method 2018-02-02 22:04:30 -06:00
Calgary Michael
9e80da705a removed usage of 'pushAll' operator 2018-02-02 21:47:04 -06:00
erdenezul
9b04391f82 Merge pull request #1736 from vainu-arto/register-connection-match-connection
Add db parameter to register_connection #606
2018-02-02 16:46:45 +08:00
Arto Jantunen
8f6c0796e3 Add db parameter to register_connection
This is done to make it compatible with the connect function.
2018-02-02 10:26:36 +02:00
erdenezul
326fcf4398 Merge pull request #1735 from esmail/patch-1
Update `post_save` signal documentation to reflect #594
2018-02-02 15:06:17 +08:00
Esmail
fdda27abd1 Update post_save signal documentation to reflect #594 2018-02-01 12:58:10 -05:00
Ivan Pogrebkov
7e8c62104a null=True now usefull 2018-01-26 11:15:12 +03:00
Ivan Pogrebkov
fb213f6e74 Update document.py 2018-01-26 11:12:02 +03:00
Ivan Pogrebkov
22e75c1691 Insert null values fix
https://stackoverflow.com/questions/42601950/how-to-store-a-null-value-in-mongodb-via-mongoengine
2018-01-26 10:55:44 +03:00
Chuan-Heng Hsiao
919f221be9 defensive programming for v as an instance of DBRef when accessing v.collection in dereference 2018-01-11 07:58:24 -05:00
erdenezul
da7d64667e Merge pull request #1571 from erdenezul/reverse_delete_rule_with_pull
add test case for reverse_delete_rule with pull #1519
2017-12-23 11:32:09 +08:00
erdenezul
d19c6a1573 Merge pull request #1714 from erdenezul/1712-cached-reference-push-fix
update fields argument when given #1172
2017-12-23 00:18:19 +08:00
Erdenezul Batmunkh
5cd23039a0 Merge branch '1712-cached-reference-push-fix' of github.com:erdenezul/mongoengine into 1712-cached-reference-push-fix 2017-12-23 00:02:43 +08:00
Erdenezul
19b18d3d0a add changelog #1712 2017-12-23 00:02:03 +08:00
Erdenezul
101947da8b update fields argument when given #1172 2017-12-23 00:00:57 +08:00
Emmanuel Leblond
d3c3c23630 Merge pull request #1717 from touilleMan/fix-pymongo-mongodb2.4
Fix travis tests with mongodb 2.4 & pymongo 3
2017-12-22 16:19:14 +01:00
Erdenezul Batmunkh
abc14316ea Merge branch '1712-cached-reference-push-fix' of github.com:erdenezul/mongoengine into 1712-cached-reference-push-fix 2017-12-22 21:09:07 +08:00
Erdenezul
b66621f9c6 add changelog #1712 2017-12-22 21:08:35 +08:00
Erdenezul
aa5510531d update fields argument when given #1172 2017-12-22 21:07:35 +08:00
Emmanuel Leblond
12b846586c Fix travis tests with mongodb 2.4 & pymongo 3 2017-12-22 13:23:03 +01:00
erdenezul
b705f5b743 Merge pull request #1716 from MongoEngine/revert-1645-inc_cov
Revert "add tests to increase code coverage"
2017-12-22 20:22:37 +08:00
erdenezul
18a5fba42b Revert "add tests to increase code coverage" 2017-12-22 20:19:21 +08:00
erdenezul
b5a3b6f86a Merge pull request #1645 from erdenezul/inc_cov
add tests to increase code coverage
2017-12-22 20:14:31 +08:00
erdenezul
00f2eda576 Merge branch 'master' into 1712-cached-reference-push-fix 2017-12-22 19:45:51 +08:00
erdenezul
c70d252dc3 Merge branch 'master' into 1712-cached-reference-push-fix 2017-12-22 19:44:53 +08:00
Emmanuel Leblond
2f088ce29e Merge pull request #1710 from esmail/patch-1
One-character typo fix ("that" -> "than")
2017-12-22 12:27:47 +01:00
Emmanuel Leblond
ff408c604b Merge pull request #1703 from erdenezul/invalid_instance_genericembedded
fix validation error for invalid embedded document instance #1067
2017-12-22 12:23:31 +01:00
Erdenezul
6621c318db add changelog #1712 2017-12-20 15:28:33 +08:00
Erdenezul
22a8ad2fde update fields argument when given #1172 2017-12-20 15:17:54 +08:00
Esmail
7674dc9b34 One-character typo fix ("that" -> "than") 2017-12-05 15:14:15 -05:00
Erdenezul
9e0ca51c2f remove merge conflict after rebase #1067 2017-12-01 08:40:51 +08:00
Erdenezul
961629d156 add changelog into dev #1067 2017-12-01 08:40:00 +08:00
Erdenezul Batmunkh
2cbebf9c99 move changes to development 2017-12-01 08:38:41 +08:00
Erdenezul Batmunkh
08a4deca17 change description in changelog 2017-12-01 08:37:53 +08:00
Erdenezul Batmunkh
ce9ea7baad add changelog 2017-12-01 08:37:53 +08:00
Erdenezul Batmunkh
b35efb9f72 fix validatione error for invalid embedded document instance #1067 2017-12-01 08:37:53 +08:00
Emmanuel Leblond
c45dfacb41 Update changelog 2017-11-29 16:41:42 +01:00
Emmanuel Leblond
91152a7977 Merge pull request #1704 from touilleMan/lazyref-improvements
Improve LazyReferenceField and GenericLazyReferenceField with nested …
2017-11-29 16:38:11 +01:00
Erdenezul Batmunkh
0ce081323f move changes to development 2017-11-22 22:19:50 +08:00
Erdenezul Batmunkh
79486e3393 change description in changelog 2017-11-22 19:27:35 +08:00
Erdenezul Batmunkh
60758dd76b add changelog 2017-11-22 18:56:12 +08:00
Emmanuel Leblond
e74f659015 Improve LazyReferenceField and GenericLazyReferenceField with nested fields 2017-11-22 11:44:49 +01:00
Erdenezul Batmunkh
c1c09fa6b4 fix validatione error for invalid embedded document instance #1067 2017-11-21 21:56:26 +08:00
Emmanuel Leblond
47c7cb9327 Ignore style I202 rule (see https://github.com/PyCQA/flake8-import-order/issues/122) 2017-11-15 20:44:36 +01:00
Emmanuel Leblond
4d6256e1a1 Merge pull request #1675 from erdenezul/push_list_of_list
use each modifier only with $position  #1673
2017-11-15 08:43:29 +01:00
Emmanuel Leblond
13180d92e3 Merge pull request #1652 from erdenezul/generic_embedded_query
Subfield resolve error in generic_emdedded_document query #1651
2017-11-13 15:29:26 +01:00
kcarretto
6b38ef3c9f Fixed format string issue in mongoengine error message. 2017-11-11 03:36:28 -05:00
Ryan Scott
4f5b0634ad Add documentation for auto_create_index to the Indexing section of the Docs 2017-11-07 16:26:01 -05:00
Emmanuel Leblond
ea25972257 Bump version to 0.15.0 2017-11-06 14:40:59 +01:00
Emmanuel Leblond
b6168898ec Merge pull request #1690 from touilleMan/lazyreference-field
Add LazyReferenceField & GenericLazyReferencField
2017-11-06 14:37:43 +01:00
Emmanuel Leblond
da33cb54fe Correct style 2017-11-06 14:11:11 +01:00
Emmanuel Leblond
35d0458228 Add GenericLazyReferenceField 2017-11-06 12:29:19 +01:00
Emmanuel Leblond
e6c0280b40 Add LazyReferenceField 2017-10-31 18:58:42 +01:00
Mandeep Singh
15451ff42b return instead of raising StopIteration 2017-10-17 00:11:47 +05:30
Erdenezul
9ab856e186 use each modifier only with #1673 2017-10-10 10:34:34 +08:00
Erdenezul
6e2db1ced6 read_preference from parse_uri only PYMONGO_3 #1665 2017-10-03 09:23:17 +08:00
Erdenezul Batmunkh
5c4ce8754e run tests only pymongo3 #1565 2017-10-02 23:15:37 +08:00
Erdenezul Batmunkh
416486c370 use read_preference only pymongo3.x #1665 2017-10-02 23:13:25 +08:00
Erdenezul Batmunkh
2f075be6f8 parse read_preference from conn_host #1665 2017-10-02 22:46:27 +08:00
Stefan Wojcik
a1494c4c93 v0.14.3 version bump 2017-10-01 17:31:10 -04:00
Stefan Wojcik
d79ab5ffeb remove {nospam} from author_email & maintainer_email (PyPI doesnt validate those anymore) 2017-10-01 17:05:28 -04:00
Stefan Wojcik
01526a7b37 v0.14.1 version bump + updated changelog 2017-10-01 16:32:02 -04:00
Stefan Wojcik
091a02f737 minor .travis.yml comment correction [ci skip] 2017-10-01 16:09:10 -04:00
Erdenezul
aa4996ef28 fix bug query subfield in generic_embedded_document #1651 2017-09-15 11:18:24 +08:00
Andy Yankovsky
2f4e2bde6b Update AUTHORS 2017-09-14 21:02:53 +03:00
Andy Yankovsky
e90f6a2fa3 Fix update via pull__something__in=[] 2017-09-14 20:28:15 +03:00
Erdenezul Batmunkh
be8f1b9fdd add failing test for generic_emdedded_document query #1651 2017-09-14 22:24:27 +09:00
Erdenezul Batmunkh
ba99190f53 add tests to increase coverage 2017-09-10 13:09:20 +09:00
Erdenezul Batmunkh
70088704e2 add tests to increase code coverage 2017-09-10 01:37:17 +09:00
Erdenezul
02733e6e58 fix flake8 error #1371 2017-09-02 12:00:57 +09:00
Erdenezul
44732a5dd9 add fix for reload(fields) affect changed fields #1371 2017-09-02 02:05:27 +09:00
Emmanuel Leblond
5bdd35464b Merge pull request #1632 from srinivasreddy/inequality
Add missing dunder method  - __ne__ to the class GridFSProxy class
2017-08-31 10:15:44 +02:00
Erdenezul
1eae97731f Fix Document.modify fail on sharded collection #1569 2017-08-30 12:04:04 +08:00
Emmanuel Leblond
0325a62f18 Merge pull request #1626 from erdenezul/pointfield_aspymongo
added as_pymongo test for PointField #1311
2017-08-28 13:36:35 +02:00
Emmanuel Leblond
3a5538813c Merge pull request #1616 from srinivasreddy/clean_up
Add six.moves.range instead of xrange
2017-08-28 13:34:17 +02:00
Emmanuel Leblond
1f1b4b95ce Merge pull request #1617 from srinivasreddy/python36
Add python 3.6 support
2017-08-28 13:33:56 +02:00
Emmanuel Leblond
8c3ed57ecc Merge pull request #1630 from MongoEngine/improve-perfs
Improve perfs by removing SemiStrictDict
2017-08-28 11:23:35 +02:00
Srinivas Reddy Thatiparthy
dc8a64fa7d Add missing dunder method - __ne__ to the class GridFSProxy class 2017-08-25 22:02:47 +05:30
Erdenezul
0d1e72a764 Merge branch 'pointfield_aspymongo' of github.com:erdenezul/mongoengine into pointfield_aspymongo 2017-08-25 11:15:51 +08:00
Erdenezul
9b3fe09508 added as_pymongo test for PointField #1311 2017-08-25 11:15:03 +08:00
Srinivas Reddy Thatiparthy
7c0cfb1da2 Add six.moves.range instead of xrange 2017-08-25 00:04:04 +05:30
Srinivas Reddy Thatiparthy
66429ce331 Add python 3.6 support 2017-08-25 00:02:39 +05:30
Emmanuel Leblond
bce859569f Remove SemiStrictDict to improve perfs 2017-08-24 20:01:09 +02:00
Emmanuel Leblond
425fb8905b Merge pull request #1631 from MongoEngine/fix_travis_ci
Fix .install_mongodb_on_travis.sh script (all credit to @erdenezul)
2017-08-24 19:48:15 +02:00
Emmanuel Leblond
4f59c7f77f Expose to user mongoengine.base.NON_FIELD_ERRORS
This variable is used to set the field containing the errors raised in a
clean function. Given those function are user-defined, users should be
able to get the name of the field to easily retreive their custom errors.
2017-08-24 19:47:40 +02:00
Emmanuel Leblond
21d1faa793 Fix .install_mongodb_on_travis.sh script (all credit to @erdenezul) 2017-08-24 19:34:22 +02:00
Erdenezul
b9f3991d03 added as_pymongo test for PointField #1311 2017-08-18 09:31:26 +08:00
Paulo Matos
c4de879b20 Clarify comment in validation example 2017-08-11 09:09:33 +02:00
Ali Turki
ee5686e91a test case for #1602 2017-08-07 11:12:15 +01:00
Ali
2a795e9138 QuerySet limit function now returns all docs in cursor when 0 is passed 2017-08-04 11:31:29 +01:00
erdenezul
9a6aa8f8c6 Merge branch 'master' into support_multiple_operator 2017-07-31 22:53:01 +08:00
Stefan Wójcik
3794b181d5 Support for $position with the $push operator (#1566) 2017-07-31 15:36:35 +02:00
Erdenezul Batmunkh
f09256a24e Fix modify tests #1565 2017-07-31 18:49:52 +08:00
Erdenezul Batmunkh
34fca9d6f5 Add clear comment and tests for positional push #1565 2017-07-31 18:32:34 +08:00
Erdenezul Batmunkh
433f10ef93 position support singular value #1565 2017-07-31 05:15:34 +00:00
Bo.Yi
9f02f71c52 [fix]fix some personal hobby 2017-07-16 18:47:20 +08:00
Erdenezul Batmunkh
3dcc9bc143 use explicit tests and fix unneccessary indent #1565 2017-07-13 22:59:21 +08:00
erdenezul
7311895894 Merge branch 'master' into support_position_in_push 2017-07-13 20:08:57 +08:00
Davidrjx
a7cab51369 Use a set literal in _clean_settings (#1585) 2017-07-13 12:07:36 +02:00
Alex Xu
437b11af9a docs: use explicit register_delete_rule example
The previous example of creating bi-directional delete rules was vague since the example defined only one class and the relationship between "Foo" and "Bar" wasn't clear. I added a more explicit example where the relationship between the two classes is explicit.
2017-07-10 16:43:24 -04:00
Bo.Yi
820b5cbb86 [fix]pass test case and fix field type error 2017-07-06 16:07:51 +08:00
Bo.Yi
e6a30f899c [fix]validation list field with multi choice values 2017-07-06 14:57:03 +08:00
Benoit Larroque
0bc6507df3 Make queryset aggregates obey read_preference 2017-06-27 08:49:06 +02:00
Erdenezul Batmunkh
71c3c632d7 add test case for reverse_delete_rule with pull #1519 2017-06-19 06:01:28 +00:00
Erdenezul Batmunkh
fb00b79d19 add docs for positional push operator #1565 2017-06-19 03:28:34 +00:00
Erdenezul Batmunkh
7782aa7379 do not test position push in mongodb_v2.4 #1565 2017-06-19 03:11:59 +00:00
Erdenezul Batmunkh
f3ee4a5dac add tests for push operator #1565 2017-06-19 02:59:17 +00:00
Stefan Wojcik
a8d6e59a7a minor tweaks to code quality in _fields_to_dbfields 2017-06-18 17:25:39 -07:00
Danil
1d4b1870cf to_db_fields fix (#1553) 2017-06-18 17:04:46 -07:00
Erdenezul Batmunkh
f63ad2dd69 dont test in mongoDB v2.4 #1565 2017-06-15 07:36:14 +00:00
Erdenezul Batmunkh
6903eed4e7 support position in 'push' #1565 2017-06-15 06:08:40 +00:00
Erdenezul Batmunkh
b9e922c658 support multiple operator #1510 2017-06-12 04:50:13 +00:00
José Enrique Carrillo Pino
2f1fe5468e Fix empty string casted to datetime today in DateTimeField (#1533) 2017-05-12 12:59:14 -04:00
lanf0n
24d15d4274 fix typo in the save() method's docstring (#1551) 2017-05-11 10:06:36 -04:00
Stefan Wojcik
0bc7aa52d8 more docs tweaks [ci skip] 2017-05-08 00:14:42 -04:00
Stefan Wojcik
e52603b4a7 ver bump to v0.14.0 + changelog/upgrade docs update 2017-05-08 00:12:26 -04:00
Stefan Wójcik
3b88712402 Cleaner as_pymongo (#1549) 2017-05-08 00:02:42 -04:00
Stefan Wojcik
33e9ef2106 dont test pypy3 temporarily 2017-05-07 21:37:38 -04:00
Stefan Wojcik
689fe4ed9a Revert "use a newer pypy3 (https://github.com/travis-ci/travis-ci/issues/6277)"
This reverts commit 944d1c0a4a.
2017-05-07 21:37:14 -04:00
Stefan Wojcik
b82d026f39 Revert "fix tox.ini"
This reverts commit c00914bea2.
2017-05-07 21:37:05 -04:00
Stefan Wojcik
009059def4 revert #1497 2017-05-07 21:29:13 -04:00
Stefan Wójcik
03ff61d113 better db_field validation (#1547) 2017-05-07 21:11:14 -04:00
Stefan Wojcik
c00914bea2 fix tox.ini 2017-05-07 20:32:52 -04:00
Stefan Wojcik
944d1c0a4a use a newer pypy3 (https://github.com/travis-ci/travis-ci/issues/6277) 2017-05-07 19:54:58 -04:00
Stefan Wojcik
2cf23e33e3 Document._get_update_doc helper method 2017-05-07 19:26:10 -04:00
Stefan Wojcik
e2a0b42d03 clarify test_get_changed_fields_query_count 2017-04-30 18:29:22 -04:00
Stefan Wójcik
894e9818ac use an external sphinx rtd theme (#1541)
Externalize Sphinx RTD theme
2017-04-30 15:38:21 -04:00
Stefan Wójcik
de18e256ce clean up the Document._get_collection code (#1540) 2017-04-30 14:35:33 -04:00
Stefan Wójcik
1a3c70ce1b make EmbeddedDocument not hashable by default (#1528) 2017-04-30 13:30:21 -04:00
Artemiy
bd4a603e16 Use De Morgan's laws to simplify an expression. (#1531) 2017-04-20 10:27:37 -04:00
Jason
358b80d782 Make the tutorial slightly more intuitive (#1530) 2017-04-20 09:11:38 -04:00
Stefan Wojcik
824ec42005 bump version to v0.13.0 and fill in the changelog and the upgrade docs 2017-04-16 14:08:46 -04:00
Stefan Wójcik
466935e9a3 Unicode support in EmailField (#1527) 2017-04-16 13:58:58 -04:00
Stefan Wojcik
b52d3e3a7b added one more item to the v0.12.0 changelog 2017-04-07 10:34:04 -04:00
Stefan Wojcik
888a6da4a5 update the changelog and bump the version to v0.12.0 2017-04-07 10:18:39 -04:00
Omer Katz
972ac73dd9 Merge pull request #1497 from userlocalhost/feature/order_guarantee
added a feature to save object data in order
2017-04-07 10:49:39 +03:00
Hiroyasu OHYAMA
d8b238d5f1 Refactored the implementation of DynamicField extension for storing data in order 2017-04-06 00:42:11 +00:00
Omer Katz
63206c3da2 Merge pull request #1520 from ZoetropeLabs/fix/allow-reference-fields-take-object-ids
Allow ReferenceFields to take ObjectIds
2017-04-02 13:57:58 +03:00
Richard Fortescue-Webb
5713de8966 Use the objectid in the test 2017-03-29 11:34:57 +01:00
Richard Fortescue-Webb
58f293fef3 Allow ReferenceFields to take ObjectIds 2017-03-29 10:34:50 +01:00
Hiroyasu OHYAMA
ffbb2c9689 This is Additional tests for the container_class parameter of DynamicField
This tests DynamicField dereference with ordering guarantee.
2017-03-08 14:46:04 +00:00
Hiroyasu OHYAMA
9cd3dcdebf Added a test for the change of the condition in DeReference processing
This checks DBRef conversion using DynamicField with the ordering
guarantee.
2017-03-08 14:45:43 +00:00
Hiroyasu OHYAMA
f2fe58c3c5 Added a condition to store data to ObjectDict when the items type is it
Previous dereference implementation re-contains data as `dict` except
for the predicted type.
But the OrderedDict is not predicted, so the its data would be converted
`dict` implicitly.
As the result, the order of stored data get wrong. And this patch
prevents it.
2017-03-08 14:35:50 +00:00
Stefan Wojcik
b78010aa94 remove test_last_field_name_like_operator (it's a dupe of the same test in tests/queryset/transform.py) 2017-03-05 21:24:46 -05:00
Stefan Wójcik
49035543b9 cleanup BaseQuerySet.__getitem__ (#1502) 2017-03-05 21:17:53 -05:00
Stefan Wójcik
f9ccf635ca Respect db fields in multiple layers of embedded docs (#1501) 2017-03-05 18:20:09 -05:00
Stefan Wojcik
e8ea294964 test negative indexes (closes #1119) 2017-03-05 18:12:01 -05:00
Stefan Wojcik
19ef2be88b fix #937 2017-03-05 00:05:33 -05:00
Stefan Wojcik
30e8b8186f clean up document instance tests 2017-03-02 00:25:56 -05:00
Stefan Wójcik
741643af5f clean up field unit tests (#1498) 2017-03-02 00:05:10 -05:00
Hiroyasu OHYAMA
6aaf9ba470 removed a checking of dict order because this order is not cared (some implementation might be in ordered, but other one is not) 2017-03-01 09:32:28 +00:00
Hiroyasu OHYAMA
5957dc72eb To achive storing object data in order with minimum implementation, I
changed followings.

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

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

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

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

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

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

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

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

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

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

See #930 for more information.

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

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

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

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

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

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

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

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

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

    class Song(Document):
        title = StringField()

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

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

Rebase Log:

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

Excluding Django versions that do not work with Python 3.

Improved formatting of .travis.yml.

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

PIL should not be installed alongside Pillow.

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

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

The build is now installing pymongo versions that actually exist.

openjpeg has a different name on Ubuntu 12.04.

Restoring libz hack.

Also installing all Pillow requirements just in case.

Fixed the build matrix.

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

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

cursor = Collection.objects

	if cursor:
		(...)

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

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

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

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

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

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

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

# query

examples = Example.objects(size=instance_size)

# caused an error

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

```
myobj._get_db()

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

```

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

I felt this like a bug.

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

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

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

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

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

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

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

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

Note: this breakage seems to be new since mongoengine 0.8.
2013-05-30 16:37:40 +12:00
Alice Bevan-McGregor
35f084ba76 Fixed :module: reference in docs and added myself to authors. 2013-05-29 13:23:18 -04:00
Alice Bevan-McGregor
f28f336026 Improved signals documentation and some typo fixes. 2013-05-29 13:17:08 -04:00
Alice Bevan-McGregor
122d75f677 Added pre_save_validation to signal list in documentation. 2013-05-29 12:23:32 -04:00
Alice Bevan-McGregor
12f6a3f5a3 Added tests for pre_save_validation and updated tests for pre_save to encompass created flag. 2013-05-29 12:22:15 -04:00
Alice Bevan-McGregor
5d44e1d6ca Added missing reference in __all__. 2013-05-29 12:12:51 -04:00
Alice Bevan-McGregor
04592c876b Moved pre_save after validation and determination of creation state; added pre_save_validation where pre_save had been. 2013-05-29 12:04:53 -04:00
Paul Swartz
c0571beec8 fix change tracking for ComplexBaseFields 2013-05-28 17:19:46 -04:00
Paul Swartz
1302316eb0 add some tests 2013-05-28 16:08:33 -04:00
Paul Swartz
18d8008b89 if dateutil is available, use it to parse datetimes
In particular, this picks up the default `datetime.isoformat()` output, with
a "T" as the separator.
2013-05-28 15:59:32 -04:00
Stefan Wojcik
4670f09a67 fix __set_state__ 2013-05-27 13:48:02 -07:00
ichuang
159ef12ed7 FileField should pass db_alias to GridFSProxy in __set__ call 2013-05-27 11:19:34 -04:00
Jin Zhang
7a760f5640 Update django.rst 2013-05-25 06:46:23 -06:00
Ryan Witt
2b6c42a56c minor typos 2013-05-24 11:34:15 -03:00
Ryan Witt
ab4ff99105 fix guide link 2013-05-24 11:24:40 -03:00
Stefan Wojcik
774895ec8c dont simplify queries with duplicate conditions 2013-05-23 17:49:28 -07:00
Ross Lawley
c5ce96c391 Fix py3 test 2013-05-23 19:44:05 +00:00
Ross Lawley
b4a98a4000 More upgrade clarifications #331 2013-05-23 19:30:57 +00:00
Ross Lawley
5f0d86f509 Upgrade doc fix (#330) 2013-05-23 19:12:13 +00:00
Ross Lawley
c96a1b00cf Documentation cleanup (#328) 2013-05-23 19:09:05 +00:00
Ross Lawley
1eb6436682 Added get image by grid_id example 2013-05-22 10:29:45 +00:00
Ross Lawley
a84e1f17bb Fixing django tests for py 2.6 2013-05-21 09:42:22 +00:00
bool.dev
5cfd8909a8 Merge remote-tracking branch 'upstream/master' 2013-04-28 13:40:58 +05:30
bool.dev
d92f992c01 Removed merge trackers in code, merged correctly now. 2013-04-14 13:48:11 +05:30
bool.dev
20a5d9051d Merge conflicts resolved. 2013-04-14 13:39:54 +05:30
bool.dev
782d48594a Fixes resolving to db_field from class field name, in distinct() query. 2013-04-04 09:02:30 +05:30
107 changed files with 18797 additions and 7136 deletions

5
.gitignore vendored
View File

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

View File

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

22
.landscape.yml Normal file
View File

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

View File

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

103
AUTHORS
View File

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

View File

@@ -14,38 +14,57 @@ Before starting to write code, look for existing `tickets
<https://github.com/MongoEngine/mongoengine/issues?state=open>`_ or `create one
<https://github.com/MongoEngine/mongoengine/issues>`_ for your specific
issue or feature request. That way you avoid working on something
that might not be of interest or that has already been addressed. If in doubt
that might not be of interest or that has already been addressed. If in doubt
post to the `user group <http://groups.google.com/group/mongoengine-users>`
Supported Interpreters
----------------------
MongoEngine supports CPython 2.6 and newer. Language
MongoEngine supports CPython 2.7 and newer. Language
features not supported by all interpreters can not be used.
Please also ensure that your code is properly converted by
`2to3 <http://docs.python.org/library/2to3.html>`_ for Python 3 support.
The codebase is written in python 2 so you must be using python 2
when developing new features. Compatibility of the library with Python 3
relies on the 2to3 package that gets executed as part of the installation
build. You should ensure that your code is properly converted by
`2to3 <http://docs.python.org/library/2to3.html>`_.
Style Guide
-----------
MongoEngine aims to follow `PEP8 <http://www.python.org/dev/peps/pep-0008/>`_
including 4 space indents and 79 character line limits.
including 4 space indents. When possible we try to stick to 79 character line
limits. However, screens got bigger and an ORM has a strong focus on
readability and if it can help, we accept 119 as maximum line length, in a
similar way as `django does
<https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/#python-style>`_
Testing
-------
All tests are run on `Travis <http://travis-ci.org/MongoEngine/mongoengine>`_
and any pull requests are automatically tested by Travis. Any pull requests
without tests will take longer to be integrated and might be refused.
and any pull requests are automatically tested. Any pull requests without
tests will take longer to be integrated and might be refused.
You may also submit a simple failing test as a pull request if you don't know
how to fix it, it will be easier for other people to work on it and it may get
fixed faster.
General Guidelines
------------------
- Avoid backward breaking changes if at all possible.
- If you *have* to introduce a breaking change, make it very clear in your
pull request's description. Also, describe how users of this package
should adapt to the breaking change in docs/upgrade.rst.
- Write inline documentation for new classes and methods.
- Write tests and make sure they pass (make sure you have a mongod
running on the default port, then execute ``python setup.py test``
running on the default port, then execute ``python setup.py nosetests``
from the cmd line to run the test suite).
- Ensure tests pass on all supported Python, PyMongo, and MongoDB versions.
You can test various Python and PyMongo versions locally by executing
``tox``. For different MongoDB versions, you can rely on our automated
Travis tests.
- Add enhancements or problematic bug fixes to docs/changelog.rst.
- Add yourself to AUTHORS :)
Documentation
@@ -59,3 +78,8 @@ just make your changes to the inline documentation of the appropriate
branch and submit a `pull request <https://help.github.com/articles/using-pull-requests>`_.
You might also use the github `Edit <https://github.com/blog/844-forking-with-the-edit-button>`_
button.
If you want to test your documentation changes locally, you need to install
the ``sphinx`` and ``sphinx_rtd_theme`` packages. Once these are installed,
go to the ``docs`` directory, run ``make html`` and inspect the updated docs
by running ``open _build/html/index.html``.

View File

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

View File

@@ -1,118 +1,42 @@
#!/usr/bin/env python
"""
Simple benchmark comparing PyMongo and MongoEngine.
Sample run on a mid 2015 MacBook Pro (commit b282511):
Benchmarking...
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
2.58979988098
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo write_concern={"w": 0}
1.26657605171
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
8.4351580143
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries without continual assign - MongoEngine
7.20191693306
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine - write_concern={"w": 0}, cascade = True
6.31104588509
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False, cascade=True
6.07083487511
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False
5.97704291344
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force_insert=True, write_concern={"w": 0}, validate=False
5.9111430645
"""
import timeit
def cprofile_main():
from pymongo import Connection
connection = Connection()
connection.drop_database('timeit_test')
connection.disconnect()
from mongoengine import Document, DictField, connect
connect("timeit_test")
class Noddy(Document):
fields = DictField()
for i in xrange(1):
noddy = Noddy()
for j in range(20):
noddy.fields["key" + str(j)] = "value " + str(j)
noddy.save()
def main():
"""
0.4 Performance Figures ...
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.86744189262
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
6.23374891281
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
5.33027005196
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
pass - No Cascade
0.5.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.89597702026
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
21.7735359669
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
19.8670389652
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
pass - No Cascade
0.6.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.81559205055
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
10.0446798801
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
9.51354718208
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
9.02567505836
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force=True
8.44933390617
0.7.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.78801012039
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
9.73050498962
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False
8.33456707001
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, safe=False, validate=False, cascade=False
8.37778115273
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force=True
8.36906409264
0.8.X
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo
3.69964408875
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - Pymongo write_concern={"w": 0}
3.5526599884
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine
7.00959801674
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries without continual assign - MongoEngine
5.60943293571
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine - write_concern={"w": 0}, cascade=True
6.715102911
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False, cascade=True
5.50644683838
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False
4.69851183891
----------------------------------------------------------------------------------------------------
Creating 10000 dictionaries - MongoEngine, force_insert=True, write_concern={"w": 0}, validate=False
4.68946313858
----------------------------------------------------------------------------------------------------
"""
print("Benchmarking...")
setup = """
from pymongo import MongoClient
@@ -127,7 +51,31 @@ connection = MongoClient()
db = connection.timeit_test
noddy = db.noddy
for i in xrange(10000):
for i in range(10000):
example = {'fields': {}}
for j in range(20):
example['fields']['key' + str(j)] = 'value ' + str(j)
noddy.save(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))
stmt = """
from pymongo import MongoClient
from pymongo.write_concern import WriteConcern
connection = MongoClient()
db = connection.get_database('timeit_test', write_concern=WriteConcern(w=0))
noddy = db.noddy
for i in range(10000):
example = {'fields': {}}
for j in range(20):
example['fields']["key"+str(j)] = "value "+str(j)
@@ -138,49 +86,26 @@ myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - Pymongo"""
print("-" * 100)
print("""Creating 10000 dictionaries - Pymongo write_concern={"w": 0}""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
stmt = """
from pymongo import MongoClient
connection = MongoClient()
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.save(example, write_concern={"w": 0})
myNoddys = noddy.find()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - Pymongo write_concern={"w": 0}"""
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
setup = """
from pymongo import MongoClient
connection = MongoClient()
connection.drop_database('timeit_test')
connection.disconnect()
connection.close()
from mongoengine import Document, DictField, connect
connect("timeit_test")
connect('timeit_test')
class Noddy(Document):
fields = DictField()
"""
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
@@ -190,13 +115,13 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
fields = {}
for j in range(20):
@@ -208,13 +133,13 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries without continual assign - MongoEngine"""
print("-" * 100)
print("""Creating 10000 dictionaries without continual assign - MongoEngine""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
@@ -224,13 +149,13 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine - write_concern={"w": 0}, cascade = True"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine - write_concern={"w": 0}, cascade = True""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
@@ -240,13 +165,13 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False, cascade=True"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False, cascade=True""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
@@ -256,13 +181,13 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine, write_concern={"w": 0}, validate=False""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
stmt = """
for i in xrange(10000):
for i in range(10000):
noddy = Noddy()
for j in range(20):
noddy.fields["key"+str(j)] = "value "+str(j)
@@ -272,11 +197,11 @@ myNoddys = Noddy.objects()
[n for n in myNoddys] # iterate
"""
print "-" * 100
print """Creating 10000 dictionaries - MongoEngine, force_insert=True, write_concern={"w": 0}, validate=False"""
print("-" * 100)
print("""Creating 10000 dictionaries - MongoEngine, force_insert=True, write_concern={"w": 0}, validate=False""")
t = timeit.Timer(stmt=stmt, setup=setup)
print t.timeit(1)
print(t.timeit(1))
if __name__ == "__main__":
main()
main()

View File

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

View File

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

View File

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

View File

@@ -34,26 +34,40 @@ Documents
.. autoclass:: mongoengine.ValidationError
:members:
.. autoclass:: mongoengine.FieldDoesNotExist
Context Managers
================
.. autoclass:: mongoengine.context_managers.switch_db
.. autoclass:: mongoengine.context_managers.switch_collection
.. autoclass:: mongoengine.context_managers.no_dereference
.. autoclass:: mongoengine.context_managers.query_counter
Querying
========
.. autoclass:: mongoengine.queryset.QuerySet
:members:
.. automodule:: mongoengine.queryset
:synopsis: Queryset level operations
.. automethod:: mongoengine.queryset.QuerySet.__call__
.. autoclass:: mongoengine.queryset.QuerySet
:members:
:inherited-members:
.. autofunction:: mongoengine.queryset.queryset_manager
.. automethod:: QuerySet.__call__
.. autoclass:: mongoengine.queryset.QuerySetNoCache
:members:
.. automethod:: mongoengine.queryset.QuerySetNoCache.__call__
.. autofunction:: mongoengine.queryset.queryset_manager
Fields
======
.. autoclass:: mongoengine.base.fields.BaseField
.. autoclass:: mongoengine.fields.StringField
.. autoclass:: mongoengine.fields.URLField
.. autoclass:: mongoengine.fields.EmailField
@@ -68,11 +82,15 @@ Fields
.. autoclass:: mongoengine.fields.GenericEmbeddedDocumentField
.. autoclass:: mongoengine.fields.DynamicField
.. autoclass:: mongoengine.fields.ListField
.. autoclass:: mongoengine.fields.EmbeddedDocumentListField
.. autoclass:: mongoengine.fields.SortedListField
.. autoclass:: mongoengine.fields.DictField
.. autoclass:: mongoengine.fields.MapField
.. autoclass:: mongoengine.fields.ReferenceField
.. autoclass:: mongoengine.fields.LazyReferenceField
.. autoclass:: mongoengine.fields.GenericReferenceField
.. autoclass:: mongoengine.fields.GenericLazyReferenceField
.. autoclass:: mongoengine.fields.CachedReferenceField
.. autoclass:: mongoengine.fields.BinaryField
.. autoclass:: mongoengine.fields.FileField
.. autoclass:: mongoengine.fields.ImageField
@@ -83,7 +101,30 @@ Fields
.. autoclass:: mongoengine.fields.PointField
.. autoclass:: mongoengine.fields.LineStringField
.. autoclass:: mongoengine.fields.PolygonField
.. autoclass:: mongoengine.fields.MultiPointField
.. autoclass:: mongoengine.fields.MultiLineStringField
.. autoclass:: mongoengine.fields.MultiPolygonField
.. autoclass:: mongoengine.fields.GridFSError
.. autoclass:: mongoengine.fields.GridFSProxy
.. autoclass:: mongoengine.fields.ImageGridFsProxy
.. autoclass:: mongoengine.fields.ImproperlyConfigured
Embedded Document Querying
==========================
.. versionadded:: 0.9
Additional queries for Embedded Documents are available when using the
:class:`~mongoengine.EmbeddedDocumentListField` to store a list of embedded
documents.
A list of embedded documents is returned as a special list with the
following methods:
.. autoclass:: mongoengine.base.datastructures.EmbeddedDocumentList
:members:
Misc
====
.. autofunction:: mongoengine.common._import_class

View File

@@ -2,6 +2,404 @@
Changelog
=========
Development
===========
- (Fill this out as you fix issues and develop your features).
=================
Changes in 0.16.3
=================
- Fix $push with $position operator not working with lists in embedded document #1965
=================
Changes in 0.16.2
=================
- Fix .save() that fails when called with write_concern=None (regression of 0.16.1) #1958
=================
Changes in 0.16.1
=================
- Fix `_cls` that is not set properly in Document constructor (regression) #1950
- Fix bug in _delta method - Update of a ListField depends on an unrelated dynamic field update #1733
- Remove deprecated `save()` method and used `insert_one()` #1899
=================
Changes in 0.16.0
=================
- Various improvements to the doc
- Improvement to code quality
- POTENTIAL BREAKING CHANGES:
- EmbeddedDocumentField will no longer accept references to Document classes in its constructor #1661
- Get rid of the `basecls` parameter from the DictField constructor (dead code) #1876
- default value of ComplexDateTime is now None (and no longer the current datetime) #1368
- Fix unhashable TypeError when referencing a Document with a compound key in an EmbeddedDocument #1685
- Fix bug where an EmbeddedDocument with the same id as its parent would not be tracked for changes #1768
- Fix the fact that bulk `insert()` was not setting primary keys of inserted documents instances #1919
- Fix bug when referencing the abstract class in a ReferenceField #1920
- Allow modification to the document made in pre_save_post_validation to be taken into account #1202
- Replaced MongoDB 2.4 tests in CI by MongoDB 3.2 #1903
- Fix side effects of using queryset.`no_dereference` on other documents #1677
- Fix TypeError when using lazy django translation objects as translated choices #1879
- Improve 2-3 codebase compatibility #1889
- Fix the support for changing the default value of ComplexDateTime #1368
- Improves error message in case an EmbeddedDocumentListField receives an EmbeddedDocument instance
instead of a list #1877
- Fix the Decimal operator inc/dec #1517 #1320
- Ignore killcursors queries in `query_counter` context manager #1869
- Fix the fact that `query_counter` was modifying the initial profiling_level in case it was != 0 #1870
- Repaired the `no_sub_classes` context manager + fix the fact that it was swallowing exceptions #1865
- Fix index creation error that was swallowed by hasattr under python2 #1688
- QuerySet limit function behaviour: Passing 0 as parameter will return all the documents in the cursor #1611
- bulk insert updates the ids of the input documents instances #1919
- Fix an harmless bug related to GenericReferenceField where modifications in the generic-referenced document
were tracked in the parent #1934
- Improve validator of BinaryField #273
- Implemented lazy regex compiling in Field classes to improve 'import mongoengine' performance #1806
- Updated GridFSProxy.__str__ so that it would always print both the filename and grid_id #710
- Add __repr__ to Q and QCombination #1843
- fix bug in BaseList.__iter__ operator (was occuring when modifying a BaseList while iterating over it) #1676
- Added field `DateField`#513
Changes in 0.15.3
=================
- Subfield resolve error in generic_emdedded_document query #1651 #1652
- use each modifier only with $position #1673 #1675
- Improve LazyReferenceField and GenericLazyReferenceField with nested fields #1704
- Fix validation error instance in GenericEmbeddedDocumentField #1067
- Update cached fields when fields argument is given #1712
- Add a db parameter to register_connection for compatibility with connect
- Use insert_one, insert_many in Document.insert #1491
- Use new update_one, update_many on document/queryset update #1491
- Use insert_one, insert_many in Document.insert #1491
- Fix reload(fields) affect changed fields #1371
- Fix Read-only access to database fails when trying to create indexes #1338
Changes in 0.15.0
=================
- Add LazyReferenceField and GenericLazyReferenceField to address #1230
Changes in 0.14.1
=================
- Removed SemiStrictDict and started using a regular dict for `BaseDocument._data` #1630
- Added support for the `$position` param in the `$push` operator #1566
- Fixed `DateTimeField` interpreting an empty string as today #1533
- Added a missing `__ne__` method to the `GridFSProxy` class #1632
- Fixed `BaseQuerySet._fields_to_db_fields` #1553
Changes in 0.14.0
=================
- BREAKING CHANGE: Removed the `coerce_types` param from `QuerySet.as_pymongo` #1549
- POTENTIAL BREAKING CHANGE: Made EmbeddedDocument not hashable by default #1528
- Improved code quality #1531, #1540, #1541, #1547
Changes in 0.13.0
=================
- POTENTIAL BREAKING CHANGE: Added Unicode support to the `EmailField`, see
docs/upgrade.rst for details.
Changes in 0.12.0
=================
- POTENTIAL BREAKING CHANGE: Fixed limit/skip/hint/batch_size chaining #1476
- POTENTIAL BREAKING CHANGE: Changed a public `QuerySet.clone_into` method to a private `QuerySet._clone_into` #1476
- Fixed the way `Document.objects.create` works with duplicate IDs #1485
- Fixed connecting to a replica set with PyMongo 2.x #1436
- Fixed using sets in field choices #1481
- Fixed deleting items from a `ListField` #1318
- Fixed an obscure error message when filtering by `field__in=non_iterable`. #1237
- Fixed behavior of a `dec` update operator #1450
- Added a `rename` update operator #1454
- Added validation for the `db_field` parameter #1448
- Fixed the error message displayed when querying an `EmbeddedDocumentField` by an invalid value #1440
- Fixed the error message displayed when validating unicode URLs #1486
- Raise an error when trying to save an abstract document #1449
Changes in 0.11.0
=================
- BREAKING CHANGE: Renamed `ConnectionError` to `MongoEngineConnectionError` since the former is a built-in exception name in Python v3.x. #1428
- BREAKING CHANGE: Dropped Python 2.6 support. #1428
- BREAKING CHANGE: `from mongoengine.base import ErrorClass` won't work anymore for any error from `mongoengine.errors` (e.g. `ValidationError`). Use `from mongoengine.errors import ErrorClass instead`. #1428
- BREAKING CHANGE: Accessing a broken reference will raise a `DoesNotExist` error. In the past it used to return `None`. #1334
- Fixed absent rounding for DecimalField when `force_string` is set. #1103
Changes in 0.10.8
=================
- Added support for QuerySet.batch_size (#1426)
- Fixed query set iteration within iteration #1427
- Fixed an issue where specifying a MongoDB URI host would override more information than it should #1421
- Added ability to filter the generic reference field by ObjectId and DBRef #1425
- Fixed delete cascade for models with a custom primary key field #1247
- Added ability to specify an authentication mechanism (e.g. X.509) #1333
- Added support for falsey primary keys (e.g. doc.pk = 0) #1354
- Fixed QuerySet#sum/average for fields w/ explicit db_field #1417
- Fixed filtering by embedded_doc=None #1422
- Added support for cursor.comment #1420
- Fixed doc.get_<field>_display #1419
- Fixed __repr__ method of the StrictDict #1424
- Added a deprecation warning for Python 2.6
Changes in 0.10.7
=================
- Dropped Python 3.2 support #1390
- Fixed the bug where dynamic doc has index inside a dict field #1278
- Fixed: ListField minus index assignment does not work #1128
- Fixed cascade delete mixing among collections #1224
- Add `signal_kwargs` argument to `Document.save`, `Document.delete` and `BaseQuerySet.insert` to be passed to signals calls #1206
- Raise `OperationError` when trying to do a `drop_collection` on document with no collection set.
- count on ListField of EmbeddedDocumentField fails. #1187
- Fixed long fields stored as int32 in Python 3. #1253
- MapField now handles unicodes keys correctly. #1267
- ListField now handles negative indicies correctly. #1270
- Fixed AttributeError when initializing EmbeddedDocument with positional args. #681
- Fixed no_cursor_timeout error with pymongo 3.0+ #1304
- Replaced map-reduce based QuerySet.sum/average with aggregation-based implementations #1336
- Fixed support for `__` to escape field names that match operators names in `update` #1351
- Fixed BaseDocument#_mark_as_changed #1369
- Added support for pickling QuerySet instances. #1397
- Fixed connecting to a list of hosts #1389
- Fixed a bug where accessing broken references wouldn't raise a DoesNotExist error #1334
- Fixed not being able to specify use_db_field=False on ListField(EmbeddedDocumentField) instances #1218
- Improvements to the dictionary fields docs #1383
Changes in 0.10.6
=================
- Add support for mocking MongoEngine based on mongomock. #1151
- Fixed not being able to run tests on Windows. #1153
- Allow creation of sparse compound indexes. #1114
- count on ListField of EmbeddedDocumentField fails. #1187
Changes in 0.10.5
=================
- Fix for reloading of strict with special fields. #1156
Changes in 0.10.4
=================
- SaveConditionError is now importable from the top level package. #1165
- upsert_one method added. #1157
Changes in 0.10.3
=================
- Fix `read_preference` (it had chaining issues with PyMongo 2.x and it didn't work at all with PyMongo 3.x) #1042
Changes in 0.10.2
=================
- Allow shard key to point to a field in an embedded document. #551
- Allow arbirary metadata in fields. #1129
- ReferenceFields now support abstract document types. #837
Changes in 0.10.1
=================
- Fix infinite recursion with CASCADE delete rules under specific conditions. #1046
- Fix CachedReferenceField bug when loading cached docs as DBRef but failing to save them. #1047
- Fix ignored chained options #842
- Document save's save_condition error raises `SaveConditionError` exception #1070
- Fix Document.reload for DynamicDocument. #1050
- StrictDict & SemiStrictDict are shadowed at init time. #1105
- Fix ListField minus index assignment does not work. #1119
- Remove code that marks field as changed when the field has default but not existed in database #1126
- Remove test dependencies (nose and rednose) from install dependencies list. #1079
- Recursively build query when using elemMatch operator. #1130
- Fix instance back references for lists of embedded documents. #1131
Changes in 0.10.0
=================
- Django support was removed and will be available as a separate extension. #958
- Allow to load undeclared field with meta attribute 'strict': False #957
- Support for PyMongo 3+ #946
- Removed get_or_create() deprecated since 0.8.0. #300
- Improve Document._created status when switch collection and db #1020
- Queryset update doesn't go through field validation #453
- Added support for specifying authentication source as option `authSource` in URI. #967
- Fixed mark_as_changed to handle higher/lower level fields changed. #927
- ListField of embedded docs doesn't set the _instance attribute when iterating over it #914
- Support += and *= for ListField #595
- Use sets for populating dbrefs to dereference
- Fixed unpickled documents replacing the global field's list. #888
- Fixed storage of microseconds in ComplexDateTimeField and unused separator option. #910
- Don't send a "cls" option to ensureIndex (related to https://jira.mongodb.org/browse/SERVER-769)
- Fix for updating sorting in SortedListField. #978
- Added __ support to escape field name in fields lookup keywords that match operators names #949
- Fix for issue where FileField deletion did not free space in GridFS.
- No_dereference() not respected on embedded docs containing reference. #517
- Document save raise an exception if save_condition fails #1005
- Fixes some internal _id handling issue. #961
- Updated URL and Email Field regex validators, added schemes argument to URLField validation. #652
- Capped collection multiple of 256. #1011
- Added `BaseQuerySet.aggregate_sum` and `BaseQuerySet.aggregate_average` methods.
- Fix for delete with write_concern {'w': 0}. #1008
- Allow dynamic lookup for more than two parts. #882
- Added support for min_distance on geo queries. #831
- Allow to add custom metadata to fields #705
Changes in 0.9.0
================
- Update FileField when creating a new file #714
- Added `EmbeddedDocumentListField` for Lists of Embedded Documents. #826
- ComplexDateTimeField should fall back to None when null=True #864
- Request Support for $min, $max Field update operators #863
- `BaseDict` does not follow `setdefault` #866
- Add support for $type operator # 766
- Fix tests for pymongo 2.8+ #877
- No module named 'django.utils.importlib' (Django dev) #872
- Field Choices Now Accept Subclasses of Documents
- Ensure Indexes before Each Save #812
- Generate Unique Indices for Lists of EmbeddedDocuments #358
- Sparse fields #515
- write_concern not in params of Collection#remove #801
- Better BaseDocument equality check when not saved #798
- OperationError: Shard Keys are immutable. Tried to update id even though the document is not yet saved #771
- with_limit_and_skip for count should default like in pymongo #759
- Fix storing value of precision attribute in DecimalField #787
- Set attribute to None does not work (at least for fields with default values) #734
- Querying by a field defined in a subclass raises InvalidQueryError #744
- Add Support For MongoDB 2.6.X's maxTimeMS #778
- abstract shouldn't be inherited in EmbeddedDocument # 789
- Allow specifying the '_cls' as a field for indexes #397
- Stop ensure_indexes running on a secondaries unless connection is through mongos #746
- Not overriding default values when loading a subset of fields #399
- Saving document doesn't create new fields in existing collection #620
- Added `Queryset.aggregate` wrapper to aggregation framework #703
- Added support to show original model fields on to_json calls instead of db_field #697
- Added Queryset.search_text to Text indexes searchs #700
- Fixed tests for Django 1.7 #696
- Follow ReferenceFields in EmbeddedDocuments with select_related #690
- Added preliminary support for text indexes #680
- Added `elemMatch` operator as well - `match` is too obscure #653
- Added support for progressive JPEG #486 #548
- Allow strings to be used in index creation #675
- Fixed EmbeddedDoc weakref proxy issue #592
- Fixed nested reference field distinct error #583
- Fixed change tracking on nested MapFields #539
- Dynamic fields in embedded documents now visible to queryset.only() / qs.exclude() #425 #507
- Add authentication_source option to register_connection #178 #464 #573 #580 #590
- Implemented equality between Documents and DBRefs #597
- Fixed ReferenceField inside nested ListFields dereferencing problem #368
- Added the ability to reload specific document fields #100
- Added db_alias support and fixes for custom map/reduce output #586
- post_save signal now has access to delta information about field changes #594 #589
- Don't query with $orderby for qs.get() #600
- Fix id shard key save issue #636
- Fixes issue with recursive embedded document errors #557
- Fix clear_changed_fields() clearing unsaved documents bug #602
- Removing support for Django 1.4.x, pymongo 2.5.x, pymongo 2.6.x.
- Removing support for Python < 2.6.6
- Fixed $maxDistance location for geoJSON $near queries with MongoDB 2.6+ #664
- QuerySet.modify() and Document.modify() methods to provide find_and_modify() like behaviour #677 #773
- Added support for the using() method on a queryset #676
- PYPY support #673
- Connection pooling #674
- Avoid to open all documents from cursors in an if stmt #655
- Ability to clear the ordering #657
- Raise NotUniqueError in Document.update() on pymongo.errors.DuplicateKeyError #626
- Slots - memory improvements #625
- Fixed incorrectly split a query key when it ends with "_" #619
- Geo docs updates #613
- Workaround a dateutil bug #608
- Conditional save for atomic-style operations #511
- Allow dynamic dictionary-style field access #559
- Increase email field length to accommodate new TLDs #726
- index_cls is ignored when deciding to set _cls as index prefix #733
- Make 'db' argument to connection optional #737
- Allow atomic update for the entire `DictField` #742
- Added MultiPointField, MultiLineField, MultiPolygonField
- Fix multiple connections aliases being rewritten #748
- Fixed a few instances where reverse_delete_rule was written as reverse_delete_rules. #791
- Make `in_bulk()` respect `no_dereference()` #775
- Handle None from model __str__; Fixes #753 #754
- _get_changed_fields fix for embedded documents with id field. #925
Changes in 0.8.7
================
- Calling reload on deleted / nonexistent documents raises DoesNotExist (#538)
- Stop ensure_indexes running on a secondaries (#555)
- Fix circular import issue with django auth (#531) (#545)
Changes in 0.8.6
================
- Fix django auth import (#531)
Changes in 0.8.5
================
- Fix multi level nested fields getting marked as changed (#523)
- Django 1.6 login fix (#522) (#527)
- Django 1.6 session fix (#509)
- EmbeddedDocument._instance is now set when setting the attribute (#506)
- Fixed EmbeddedDocument with ReferenceField equality issue (#502)
- Fixed GenericReferenceField serialization order (#499)
- Fixed count and none bug (#498)
- Fixed bug with .only() and DictField with digit keys (#496)
- Added user_permissions to Django User object (#491, #492)
- Fix updating Geo Location fields (#488)
- Fix handling invalid dict field value (#485)
- Added app_label to MongoUser (#484)
- Use defaults when host and port are passed as None (#483)
- Fixed distinct casting issue with ListField of EmbeddedDocuments (#470)
- Fixed Django 1.6 sessions (#454, #480)
Changes in 0.8.4
================
- Remove database name necessity in uri connection schema (#452)
- Fixed "$pull" semantics for nested ListFields (#447)
- Allow fields to be named the same as query operators (#445)
- Updated field filter logic - can now exclude subclass fields (#443)
- Fixed dereference issue with embedded listfield referencefields (#439)
- Fixed slice when using inheritance causing fields to be excluded (#437)
- Fixed ._get_db() attribute after a Document.switch_db() (#441)
- Dynamic Fields store and recompose Embedded Documents / Documents correctly (#449)
- Handle dynamic fieldnames that look like digits (#434)
- Added get_user_document and improve mongo_auth module (#423)
- Added str representation of GridFSProxy (#424)
- Update transform to handle docs erroneously passed to unset (#416)
- Fixed indexing - turn off _cls (#414)
- Fixed dereference threading issue in ComplexField.__get__ (#412)
- Fixed QuerySetNoCache.count() caching (#410)
- Don't follow references in _get_changed_fields (#422, #417)
- Allow args and kwargs to be passed through to_json (#420)
Changes in 0.8.3
================
- Fixed EmbeddedDocuments with `id` also storing `_id` (#402)
- Added get_proxy_object helper to filefields (#391)
- Added QuerySetNoCache and QuerySet.no_cache() for lower memory consumption (#365)
- Fixed sum and average mapreduce dot notation support (#375, #376, #393)
- Fixed as_pymongo to return the id (#386)
- Document.select_related() now respects `db_alias` (#377)
- Reload uses shard_key if applicable (#384)
- Dynamic fields are ordered based on creation and stored in _fields_ordered (#396)
**Potential breaking change:** http://docs.mongoengine.org/en/latest/upgrade.html#to-0-8-3
- Fixed pickling dynamic documents `_dynamic_fields` (#387)
- Fixed ListField setslice and delslice dirty tracking (#390)
- Added Django 1.5 PY3 support (#392)
- Added match ($elemMatch) support for EmbeddedDocuments (#379)
- Fixed weakref being valid after reload (#374)
- Fixed queryset.get() respecting no_dereference (#373)
- Added full_result kwarg to update (#380)
Changes in 0.8.2
================
- Added compare_indexes helper (#361)
- Fixed cascading saves which weren't turned off as planned (#291)
- Fixed Datastructures so instances are a Document or EmbeddedDocument (#363)
- Improved cascading saves write performance (#361)
- Fixed ambiguity and differing behaviour regarding field defaults (#349)
- ImageFields now include PIL error messages if invalid error (#353)
- Added lock when calling doc.Delete() for when signals have no sender (#350)
- Reload forces read preference to be PRIMARY (#355)
- Querysets are now lest restrictive when querying duplicate fields (#332, #333)
- FileField now honouring db_alias (#341)
- Removed customised __set__ change tracking in ComplexBaseField (#344)
- Removed unused var in _get_changed_fields (#347)
- Added pre_save_post_validation signal (#345)
- DateTimeField now auto converts valid datetime isostrings into dates (#343)
- DateTimeField now uses dateutil for parsing if available (#343)
- Fixed Doc.objects(read_preference=X) not setting read preference (#352)
- Django session ttl index expiry fixed (#329)
- Fixed pickle.loads (#342)
- Documentation fixes
Changes in 0.8.1
================
- Fixed Python 2.6 django auth importlib issue (#326)
@@ -14,7 +412,7 @@ Changes in 0.8.0
- Added `get_next_value` preview for SequenceFields (#319)
- Added no_sub_classes context manager and queryset helper (#312)
- Querysets now utilises a local cache
- Changed __len__ behavour in the queryset (#247, #311)
- Changed __len__ behaviour in the queryset (#247, #311)
- Fixed querying string versions of ObjectIds issue with ReferenceField (#307)
- Added $setOnInsert support for upserts (#308)
- Upserts now possible with just query parameters (#309)
@@ -65,7 +463,7 @@ Changes in 0.8.0
- Uses getlasterror to test created on updated saves (#163)
- Fixed inheritance and unique index creation (#140)
- Fixed reverse delete rule with inheritance (#197)
- Fixed validation for GenericReferences which havent been dereferenced
- Fixed validation for GenericReferences which haven't been dereferenced
- Added switch_db context manager (#106)
- Added switch_db method to document instances (#106)
- Added no_dereference context manager (#82) (#61)
@@ -147,11 +545,11 @@ Changes in 0.7.2
- Update index spec generation so its not destructive (#113)
Changes in 0.7.1
=================
================
- Fixed index spec inheritance (#111)
Changes in 0.7.0
=================
================
- Updated queryset.delete so you can use with skip / limit (#107)
- Updated index creation allows kwargs to be passed through refs (#104)
- Fixed Q object merge edge case (#109)
@@ -232,7 +630,7 @@ Changes in 0.6.12
- Fixes error with _delta handling DBRefs
Changes in 0.6.11
==================
=================
- Fixed inconsistency handling None values field attrs
- Fixed map_field embedded db_field issue
- Fixed .save() _delta issue with DbRefs
@@ -312,7 +710,7 @@ Changes in 0.6.1
- Fix for replicaSet connections
Changes in 0.6
================
==============
- Added FutureWarning to inherited classes not declaring 'allow_inheritance' as the default will change in 0.7
- Added support for covered indexes when inheritance is off
@@ -400,8 +798,8 @@ Changes in v0.5
- Updated default collection naming convention
- Added Document Mixin support
- Fixed queryet __repr__ mid iteration
- Added hint() support, so cantell Mongo the proper index to use for the query
- Fixed issue with inconsitent setting of _cls breaking inherited referencing
- Added hint() support, so can tell Mongo the proper index to use for the query
- Fixed issue with inconsistent setting of _cls breaking inherited referencing
- Added help_text and verbose_name to fields to help with some form libs
- Updated item_frequencies to handle embedded document lookups
- Added delta tracking now only sets / unsets explicitly changed fields

View File

@@ -17,6 +17,10 @@ class Post(Document):
tags = ListField(StringField(max_length=30))
comments = ListField(EmbeddedDocumentField(Comment))
# bugfix
meta = {'allow_inheritance': True}
class TextPost(Post):
content = StringField()
@@ -41,26 +45,27 @@ post2.link_url = 'http://tractiondigital.com/labs/mongoengine/docs'
post2.tags = ['mongoengine']
post2.save()
print 'ALL POSTS'
print
print('ALL POSTS')
print()
for post in Post.objects:
print post.title
print '=' * post.title.count()
print(post.title)
#print '=' * post.title.count()
print("=" * 20)
if isinstance(post, TextPost):
print post.content
print(post.content)
if isinstance(post, LinkPost):
print 'Link:', post.link_url
print('Link:', post.link_url)
print
print
print()
print()
print 'POSTS TAGGED \'MONGODB\''
print
print('POSTS TAGGED \'MONGODB\'')
print()
for post in Post.objects(tags='mongodb'):
print post.title
print
print(post.title)
print()
num_posts = Post.objects(tags='mongodb').count()
print 'Found %d posts with tag "mongodb"' % num_posts
print('Found %d posts with tag "mongodb"' % num_posts)

View File

@@ -13,6 +13,10 @@
import sys, os
import sphinx_rtd_theme
import mongoengine
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -44,7 +48,6 @@ copyright = u'2009, MongoEngine Authors'
# |version| and |release|, also used in various other places throughout the
# built documents.
#
import mongoengine
# The short X.Y version.
version = mongoengine.get_version()
# The full version, including alpha/beta/rc tags.
@@ -92,15 +95,17 @@ pygments_style = 'sphinx'
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'nature'
html_theme = 'sphinx_rtd_theme'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
html_theme_options = {
'canonical_url': 'http://docs.mongoengine.org/en/latest/'
}
# Add any paths that contain custom themes here, relative to this directory.
html_theme_path = ['_themes']
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
@@ -116,7 +121,7 @@ html_theme_path = ['_themes']
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
html_favicon = "favicon.ico"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -199,4 +204,3 @@ latex_documents = [
#latex_use_modindex = True
autoclass_content = 'both'

View File

@@ -2,138 +2,18 @@
Django Support
==============
.. note:: Updated to support Django 1.5
.. note:: Django support has been split from the main MongoEngine
repository. The *legacy* Django extension may be found bundled with the
0.9 release of MongoEngine.
Connecting
==========
In your **settings.py** file, ignore the standard database settings (unless you
also plan to use the ORM in your project), and instead call
:func:`~mongoengine.connect` somewhere in the settings module.
.. note::
If you are not using another Database backend you may need to add a dummy
database backend to ``settings.py`` eg::
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.dummy'
}
}
Help Wanted!
------------
Authentication
==============
MongoEngine includes a Django authentication backend, which uses MongoDB. The
:class:`~mongoengine.django.auth.User` model is a MongoEngine
:class:`~mongoengine.Document`, but implements most of the methods and
attributes that the standard Django :class:`User` model does - so the two are
moderately compatible. Using this backend will allow you to store users in
MongoDB but still use many of the Django authentication infrastucture (such as
the :func:`login_required` decorator and the :func:`authenticate` function). To
enable the MongoEngine auth backend, add the following to you **settings.py**
file::
AUTHENTICATION_BACKENDS = (
'mongoengine.django.auth.MongoEngineBackend',
)
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
Custom User model
=================
Django 1.5 introduced `Custom user Models
<https://docs.djangoproject.com/en/dev/topics/auth/customizing/#auth-custom-user>`
which can be used as an alternative the Mongoengine authentication backend.
The main advantage of this option is that other components relying on
:mod:`django.contrib.auth` and supporting the new swappable user model are more
likely to work. For example, you can use the ``createsuperuser`` management
command as usual.
To enable the custom User model in Django, add ``'mongoengine.django.mongo_auth'``
in your ``INSTALLED_APPS`` and set ``'mongo_auth.MongoUser'`` as the custom user
user model to use. In your **settings.py** file you will have::
INSTALLED_APPS = (
...
'django.contrib.auth',
'mongoengine.django.mongo_auth',
...
)
AUTH_USER_MODEL = 'mongo_auth.MongoUser'
An additional ``MONGOENGINE_USER_DOCUMENT`` setting enables you to replace the
:class:`~mongoengine.django.auth.User` class with another class of your choice::
MONGOENGINE_USER_DOCUMENT = 'mongoengine.django.auth.User'
The custom :class:`User` must be a :class:`~mongoengine.Document` class, but
otherwise has the same requirements as a standard custom user model,
as specified in the `Django Documentation
<https://docs.djangoproject.com/en/dev/topics/auth/customizing/>`.
In particular, the custom class must define :attr:`USERNAME_FIELD` and
:attr:`REQUIRED_FIELDS` attributes.
Sessions
========
Django allows the use of different backend stores for its sessions. MongoEngine
provides a MongoDB-based session backend for Django, which allows you to use
sessions in you Django application with just MongoDB. To enable the MongoEngine
session backend, ensure that your settings module has
``'django.contrib.sessions.middleware.SessionMiddleware'`` in the
``MIDDLEWARE_CLASSES`` field and ``'django.contrib.sessions'`` in your
``INSTALLED_APPS``. From there, all you need to do is add the following line
into you settings module::
SESSION_ENGINE = 'mongoengine.django.sessions'
Django provides session cookie, which expires after ```SESSION_COOKIE_AGE``` seconds, but doesnt delete cookie at sessions backend, so ``'mongoengine.django.sessions'`` supports `mongodb TTL
<http://docs.mongodb.org/manual/tutorial/expire-data/>`_.
.. versionadded:: 0.2.1
Storage
=======
With MongoEngine's support for GridFS via the :class:`~mongoengine.fields.FileField`,
it is useful to have a Django file storage backend that wraps this. The new
storage module is called :class:`~mongoengine.django.storage.GridFSStorage`.
Using it is very similar to using the default FileSystemStorage.::
from mongoengine.django.storage import GridFSStorage
fs = GridFSStorage()
filename = fs.save('hello.txt', 'Hello, World!')
All of the `Django Storage API methods
<http://docs.djangoproject.com/en/dev/ref/files/storage/>`_ have been
implemented except :func:`path`. If the filename provided already exists, an
underscore and a number (before # the file extension, if one exists) will be
appended to the filename until the generated filename doesn't exist. The
:func:`save` method will return the new filename.::
>>> fs.exists('hello.txt')
True
>>> fs.open('hello.txt').read()
'Hello, World!'
>>> fs.size('hello.txt')
13
>>> fs.url('hello.txt')
'http://your_media_url/hello.txt'
>>> fs.open('hello.txt').name
'hello.txt'
>>> fs.listdir()
([], [u'hello.txt'])
All files will be saved and retrieved in GridFS via the :class::`FileDocument`
document, allowing easy access to the files without the GridFSStorage
backend.::
>>> from mongoengine.django.storage import FileDocument
>>> FileDocument.objects()
[<FileDocument: FileDocument object>]
.. versionadded:: 0.4
The MongoEngine team is looking for help contributing and maintaining a new
Django extension for MongoEngine! If you have Django experience and would like
to help contribute to the project, please get in touch on the
`mailing list <http://groups.google.com/group/mongoengine-users>`_ or by
simply contributing on
`GitHub <https://github.com/MongoEngine/django-mongoengine>`_.

View File

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

View File

@@ -4,7 +4,7 @@ Defining documents
In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When
working with relational databases, rows are stored in **tables**, which have a
strict **schema** that the rows follow. MongoDB stores documents in
**collections** rather than tables - the principle difference is that no schema
**collections** rather than tables --- the principal difference is that no schema
is enforced at a database level.
Defining a document's schema
@@ -22,14 +22,14 @@ objects** as class attributes to the document class::
class Page(Document):
title = StringField(max_length=200, required=True)
date_modified = DateTimeField(default=datetime.datetime.now)
date_modified = DateTimeField(default=datetime.datetime.utcnow)
As BSON (the binary format for storing data in mongodb) is order dependent,
documents are serialized based on their field order.
Dynamic document schemas
========================
One of the benefits of MongoDb is dynamic schemas for a collection, whilst data
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
should be planned and organised (after all explicit is better than implicit!)
there are scenarios where having dynamic / expando style documents is desirable.
@@ -54,7 +54,7 @@ be saved ::
There is one caveat on Dynamic Documents: fields cannot start with `_`
Dynamic fields are stored in alphabetical order *after* any declared fields.
Dynamic fields are stored in creation order *after* any declared fields.
Fields
======
@@ -75,10 +75,12 @@ are as follows:
* :class:`~mongoengine.fields.DynamicField`
* :class:`~mongoengine.fields.EmailField`
* :class:`~mongoengine.fields.EmbeddedDocumentField`
* :class:`~mongoengine.fields.EmbeddedDocumentListField`
* :class:`~mongoengine.fields.FileField`
* :class:`~mongoengine.fields.FloatField`
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
* :class:`~mongoengine.fields.GenericReferenceField`
* :class:`~mongoengine.fields.GenericLazyReferenceField`
* :class:`~mongoengine.fields.GeoPointField`
* :class:`~mongoengine.fields.ImageField`
* :class:`~mongoengine.fields.IntField`
@@ -86,11 +88,18 @@ are as follows:
* :class:`~mongoengine.fields.MapField`
* :class:`~mongoengine.fields.ObjectIdField`
* :class:`~mongoengine.fields.ReferenceField`
* :class:`~mongoengine.fields.LazyReferenceField`
* :class:`~mongoengine.fields.SequenceField`
* :class:`~mongoengine.fields.SortedListField`
* :class:`~mongoengine.fields.StringField`
* :class:`~mongoengine.fields.URLField`
* :class:`~mongoengine.fields.UUIDField`
* :class:`~mongoengine.fields.PointField`
* :class:`~mongoengine.fields.LineStringField`
* :class:`~mongoengine.fields.PolygonField`
* :class:`~mongoengine.fields.MultiPointField`
* :class:`~mongoengine.fields.MultiLineStringField`
* :class:`~mongoengine.fields.MultiPolygonField`
Field arguments
---------------
@@ -100,9 +109,6 @@ arguments can be set on all fields:
:attr:`db_field` (Default: None)
The MongoDB field name.
:attr:`name` (Default: None)
The mongoengine field name.
:attr:`required` (Default: False)
If set to True and the field is not set on the document instance, a
:class:`~mongoengine.ValidationError` will be raised when the document is
@@ -111,7 +117,7 @@ arguments can be set on all fields:
:attr:`default` (Default: None)
A value to use when no value is set for this field.
The definion of default parameters follow `the general rules on Python
The definition of default parameters follow `the general rules on Python
<http://docs.python.org/reference/compound_stmts.html#function-definitions>`__,
which means that some care should be taken when dealing with default mutable objects
(like in :class:`~mongoengine.fields.ListField` or :class:`~mongoengine.fields.DictField`)::
@@ -129,6 +135,7 @@ arguments can be set on all fields:
# instead to just an object
values = ListField(IntField(), default=[1,2,3])
.. note:: Unsetting a field with a default value will revert back to the default.
:attr:`unique` (Default: False)
When True, no documents in the collection will have the same value for this
@@ -142,8 +149,10 @@ arguments can be set on all fields:
When True, use this field as a primary key for the collection. `DictField`
and `EmbeddedDocuments` both support being the primary key for a document.
.. note:: If set, this field is also accessible through the `pk` field.
:attr:`choices` (Default: None)
An iterable (e.g. a list or tuple) of choices to which the value of this
An iterable (e.g. list, tuple or set) of choices to which the value of this
field should be limited.
Can be either be a nested tuples of value (stored in mongo) and a
@@ -166,16 +175,16 @@ arguments can be set on all fields:
class Shirt(Document):
size = StringField(max_length=3, choices=SIZE)
:attr:`help_text` (Default: None)
Optional help text to output with the field - used by form libraries
:attr:`verbose_name` (Default: None)
Optional human-readable name for the field - used by form libraries
:attr:`**kwargs` (Optional)
You can supply additional metadata as arbitrary additional keyword
arguments. You can not override existing attributes, however. Common
choices include `help_text` and `verbose_name`, commonly used by form and
widget libraries.
List fields
-----------
MongoDB allows the storage of lists of items. To add a list of items to a
MongoDB allows storing lists of items. To add a list of items to a
:class:`~mongoengine.Document`, use the :class:`~mongoengine.fields.ListField` field
type. :class:`~mongoengine.fields.ListField` takes another field object as its first
argument, which specifies which type elements may be stored within the list::
@@ -207,9 +216,9 @@ document class as the first argument::
Dictionary Fields
-----------------
Often, an embedded document may be used instead of a dictionary -- generally
this is recommended as dictionaries don't support validation or custom field
types. However, sometimes you will not know the structure of what you want to
Often, an embedded document may be used instead of a dictionary generally
embedded documents are recommended as dictionaries dont support validation
or custom field types. However, sometimes you will not know the structure of what you want to
store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate::
class SurveyResponse(Document):
@@ -217,7 +226,7 @@ store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate
user = ReferenceField(User)
answers = DictField()
survey_response = SurveyResponse(date=datetime.now(), user=request.user)
survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
response_form = ResponseForm(request.POST)
survey_response.answers = response_form.cleaned_data()
survey_response.save()
@@ -292,6 +301,12 @@ instance of the object to the query::
# Find all pages that both Bob and John have authored
Page.objects(authors__all=[bob, john])
# Remove Bob from the authors for a page.
Page.objects(id='...').update_one(pull__authors=bob)
# Add John to the authors for a page.
Page.objects(id='...').update_one(push__authors=john)
Dealing with deletion of referred documents
'''''''''''''''''''''''''''''''''''''''''''
@@ -303,12 +318,12 @@ reference with a delete rule specification. A delete rule is specified by
supplying the :attr:`reverse_delete_rule` attributes on the
:class:`ReferenceField` definition, like this::
class Employee(Document):
class ProfilePage(Document):
...
profile_page = ReferenceField('ProfilePage', reverse_delete_rule=mongoengine.NULLIFY)
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
The declaration in this example means that when an :class:`Employee` object is
removed, the :class:`ProfilePage` that belongs to that employee is removed as
removed, the :class:`ProfilePage` that references that employee is removed as
well. If a whole batch of employees is removed, all profile pages that are
linked are removed as well.
@@ -324,7 +339,7 @@ Its value can take any of the following constants:
Any object's fields still referring to the object being deleted are removed
(using MongoDB's "unset" operation), effectively nullifying the relationship.
:const:`mongoengine.CASCADE`
Any object containing fields that are refererring to the object being deleted
Any object containing fields that are referring to the object being deleted
are deleted first.
:const:`mongoengine.PULL`
Removes the reference to the object (using MongoDB's "pull" operation)
@@ -348,11 +363,6 @@ Its value can take any of the following constants:
In Django, be sure to put all apps that have such delete rule declarations in
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
.. warning::
Signals are not triggered when doing cascading updates / deletes - if this
is required you must manually handle the update / delete.
Generic reference fields
''''''''''''''''''''''''
A second kind of reference field also exists,
@@ -391,7 +401,7 @@ MongoEngine allows you to specify that a field should be unique across a
collection by providing ``unique=True`` to a :class:`~mongoengine.fields.Field`\ 's
constructor. If you try to save a document that has the same value for a unique
field as a document that is already in the database, a
:class:`~mongoengine.OperationError` will be raised. You may also specify
:class:`~mongoengine.NotUniqueError` will be raised. You may also specify
multi-field uniqueness constraints by using :attr:`unique_with`, which may be
either a single field name, or a list or tuple of field names::
@@ -403,7 +413,7 @@ either a single field name, or a list or tuple of field names::
Skipping Document validation on save
------------------------------------
You can also skip the whole document validation process by setting
``validate=False`` when caling the :meth:`~mongoengine.document.Document.save`
``validate=False`` when calling the :meth:`~mongoengine.document.Document.save`
method::
class Recipient(Document):
@@ -418,7 +428,7 @@ Document collections
====================
Document classes that inherit **directly** from :class:`~mongoengine.Document`
will have their own **collection** in the database. The name of the collection
is by default the name of the class, coverted to lowercase (so in the example
is by default the name of the class, converted to lowercase (so in the example
above, the collection would be called `page`). If you need to change the name
of the collection (e.g. to use MongoEngine with an existing database), then
create a class dictionary attribute called :attr:`meta` on your document, and
@@ -435,8 +445,10 @@ A :class:`~mongoengine.Document` may use a **Capped Collection** by specifying
:attr:`max_documents` and :attr:`max_size` in the :attr:`meta` dictionary.
:attr:`max_documents` is the maximum number of documents that is allowed to be
stored in the collection, and :attr:`max_size` is the maximum size of the
collection in bytes. If :attr:`max_size` is not specified and
:attr:`max_documents` is, :attr:`max_size` defaults to 10000000 bytes (10MB).
collection in bytes. :attr:`max_size` is rounded up to the next multiple of 256
by MongoDB internally and mongoengine before. Use also a multiple of 256 to
avoid confusions. If :attr:`max_size` is not specified and
:attr:`max_documents` is, :attr:`max_size` defaults to 10485760 bytes (10MB).
The following example shows a :class:`Log` document that will be limited to
1000 entries and 2MB of disk space::
@@ -444,6 +456,8 @@ The following example shows a :class:`Log` document that will be limited to
ip_address = StringField()
meta = {'max_documents': 1000, 'max_size': 2000000}
.. defining-indexes_
Indexes
=======
@@ -451,18 +465,36 @@ You can specify indexes on collections to make querying faster. This is done
by creating a list of index specifications called :attr:`indexes` in the
:attr:`~mongoengine.Document.meta` dictionary, where an index specification may
either be a single field name, a tuple containing multiple field names, or a
dictionary containing a full index definition. A direction may be specified on
fields by prefixing the field name with a **+** or a **-** sign. Note that
direction only matters on multi-field indexes. ::
dictionary containing a full index definition.
A direction may be specified on fields by prefixing the field name with a
**+** (for ascending) or a **-** sign (for descending). Note that direction
only matters on multi-field indexes. Text indexes may be specified by prefixing
the field name with a **$**. Hashed indexes may be specified by prefixing
the field name with a **#**::
class Page(Document):
category = IntField()
title = StringField()
rating = StringField()
created = DateTimeField()
meta = {
'indexes': ['title', ('title', '-rating')]
'indexes': [
'title',
'$title', # text index
'#title', # hashed index
('title', '-rating'),
('category', '_cls'),
{
'fields': ['created'],
'expireAfterSeconds': 3600
}
]
}
If a dictionary is passed then the following options are available:
If a dictionary is passed then additional options become available. Valid options include,
but are not limited to:
:attr:`fields` (Default: None)
The fields to index. Specified in the same format as described above.
@@ -483,10 +515,55 @@ If a dictionary is passed then the following options are available:
Allows you to automatically expire data from a collection by setting the
time in seconds to expire the a field.
:attr:`name` (Optional)
Allows you to specify a name for the index
:attr:`collation` (Optional)
Allows to create case insensitive indexes (MongoDB v3.4+ only)
.. note::
Additional options are forwarded as **kwargs to pymongo's create_index method.
Inheritance adds extra fields indices see: :ref:`document-inheritance`.
Global index default options
----------------------------
There are a few top level defaults for all indexes that can be set::
class Page(Document):
title = StringField()
rating = StringField()
meta = {
'index_opts': {},
'index_background': True,
'index_cls': False,
'auto_create_index': True,
'index_drop_dups': True,
}
:attr:`index_opts` (Optional)
Set any default index options - see the `full options list <https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex>`_
:attr:`index_background` (Optional)
Set the default value for if an index should be indexed in the background
:attr:`index_cls` (Optional)
A way to turn off a specific index for _cls.
:attr:`auto_create_index` (Optional)
When this is True (default), MongoEngine will ensure that the correct
indexes exist in MongoDB each time a command is run. This can be disabled
in systems where indexes are managed separately. Disabling this will improve
performance.
:attr:`index_drop_dups` (Optional)
Set the default value for if an index should drop duplicates
Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
and has no effect
Compound Indexes and Indexing sub documents
-------------------------------------------
@@ -496,10 +573,11 @@ field name to the index definition.
Sometimes its more efficient to index parts of Embedded / dictionary fields,
in this case use 'dot' notation to identify the value to index eg: `rank.title`
.. _geospatial-indexes:
Geospatial indexes
------------------
The best geo index for mongodb is the new "2dsphere", which has an improved
spherical model and provides better performance and more options when querying.
The following fields will explicitly add a "2dsphere" index:
@@ -507,6 +585,9 @@ The following fields will explicitly add a "2dsphere" index:
- :class:`~mongoengine.fields.PointField`
- :class:`~mongoengine.fields.LineStringField`
- :class:`~mongoengine.fields.PolygonField`
- :class:`~mongoengine.fields.MultiPointField`
- :class:`~mongoengine.fields.MultiLineStringField`
- :class:`~mongoengine.fields.MultiPolygonField`
As "2dsphere" indexes can be part of a compound index, you may not want the
automatic index but would prefer a compound index. In this example we turn off
@@ -554,13 +635,26 @@ collection after a given period. See the official
documentation for more information. A common usecase might be session data::
class Session(Document):
created = DateTimeField(default=datetime.now)
created = DateTimeField(default=datetime.utcnow)
meta = {
'indexes': [
{'fields': ['created'], 'expireAfterSeconds': 3600}
]
}
.. warning:: TTL indexes happen on the MongoDB server and not in the application
code, therefore no signals will be fired on document deletion.
If you need signals to be fired on deletion, then you must handle the
deletion of Documents in your application code.
Comparing Indexes
-----------------
Use :func:`mongoengine.Document.compare_indexes` to compare actual indexes in
the database to those that your document definitions define. This is useful
for maintenance purposes and ensuring you have the correct indexes for your
schema.
Ordering
========
A default ordering can be specified for your
@@ -605,11 +699,11 @@ Shard keys
==========
If your collection is sharded, then you need to specify the shard key as a tuple,
using the :attr:`shard_key` attribute of :attr:`-mongoengine.Document.meta`.
using the :attr:`shard_key` attribute of :attr:`~mongoengine.Document.meta`.
This ensures that the shard key is sent with the query when calling the
:meth:`~mongoengine.document.Document.save` or
:meth:`~mongoengine.document.Document.update` method on an existing
:class:`-mongoengine.Document` instance::
:class:`~mongoengine.Document` instance::
class LogEntry(Document):
machine = StringField()
@@ -631,7 +725,7 @@ 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 - all you need do is
convenient and efficient retrieval of related documents -- all you need do is
set :attr:`allow_inheritance` to True in the :attr:`meta` data for a
document.::
@@ -645,13 +739,15 @@ document.::
class DatedPage(Page):
date = DateTimeField()
.. note:: From 0.8 onwards you must declare :attr:`allow_inheritance` defaults
.. note:: From 0.8 onwards :attr:`allow_inheritance` defaults
to False, meaning you must set it to True to use inheritance.
Setting :attr:`allow_inheritance` to True should also be used in
:class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it
Working with existing data
--------------------------
As MongoEngine no longer defaults to needing :attr:`_cls` you can quickly and
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and
easily get working with existing data. Just define the document to match
the expected schema in your database ::
@@ -668,3 +764,25 @@ defining all possible field types.
If you use :class:`~mongoengine.Document` and the database contains data that
isn't defined then that data will be stored in the `document._data` dictionary.
Abstract classes
================
If you want to add some extra functionality to a group of Document classes but
you don't need or want the overhead of inheritance you can use the
:attr:`abstract` attribute of :attr:`~mongoengine.Document.meta`.
This won't turn on :ref:`document-inheritance` but will allow you to keep your
code DRY::
class BaseDocument(Document):
meta = {
'abstract': True,
}
def check_permissions(self):
...
class User(BaseDocument):
...
Now the User class will have access to the inherited `check_permissions` method
and won't store any of the extra `_cls` information.

View File

@@ -2,7 +2,7 @@
Documents instances
===================
To create a new document object, create an instance of the relevant document
class, providing values for its fields as its constructor keyword arguments.
class, providing values for its fields as constructor keyword arguments.
You may provide values for any of the fields on the document::
>>> page = Page(title="Test Page")
@@ -32,11 +32,11 @@ already exist, then any changes will be updated atomically. For example::
Changes to documents are tracked and on the whole perform ``set`` operations.
* ``list_field.push(0)`` - *sets* the resulting list
* ``del(list_field)`` - *unsets* whole list
* ``list_field.push(0)`` --- *sets* the resulting list
* ``del(list_field)`` --- *unsets* whole list
With lists its preferable to use ``Doc.update(push__list_field=0)`` as
this stops the whole list being updated - stopping any race conditions.
this stops the whole list being updated --- stopping any race conditions.
.. seealso::
:ref:`guide-atomic-updates`
@@ -57,7 +57,8 @@ document values for example::
def clean(self):
"""Ensures that only published essays have a `pub_date` and
automatically sets the pub_date if published and not set"""
automatically sets `pub_date` if essay is published and `pub_date`
is not set"""
if self.status == 'Draft' and self.pub_date is not None:
msg = 'Draft entries should not have a publication date.'
raise ValidationError(msg)
@@ -74,7 +75,7 @@ Cascading Saves
If your document contains :class:`~mongoengine.fields.ReferenceField` or
:class:`~mongoengine.fields.GenericReferenceField` objects, then by default the
:meth:`~mongoengine.Document.save` method will not save any changes to
those objects. If you want all references to also be saved also, noting each
those objects. If you want all references to be saved also, noting each
save is a separate query, then passing :attr:`cascade` as True
to the save method will cascade any saves.
@@ -113,12 +114,13 @@ you may still use :attr:`id` to access the primary key if you want::
>>> bob.id == bob.email == 'bob@example.com'
True
You can also access the document's "primary key" using the :attr:`pk` field; in
is an alias to :attr:`id`::
You can also access the document's "primary key" using the :attr:`pk` field,
it's an alias to :attr:`id`::
>>> page = Page(title="Another Test Page")
>>> page.save()
>>> page.id == page.pk
True
.. note::

View File

@@ -20,7 +20,7 @@ a document is created to store details about animals, including a photo::
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open('marmot.jpg', 'r')
marmot_photo = open('marmot.jpg', 'rb')
marmot.photo.put(marmot_photo, content_type = 'image/jpeg')
marmot.save()
@@ -46,14 +46,15 @@ slightly different manner. First, a new file must be created by calling the
marmot.photo.write('some_more_image_data')
marmot.photo.close()
marmot.photo.save()
marmot.save()
Deletion
--------
Deleting stored files is achieved with the :func:`delete` method::
marmot.photo.delete()
marmot.photo.delete() # Deletes the GridFS document
marmot.save() # Saves the GridFS reference (being None) contained in the marmot instance
.. warning::
@@ -70,5 +71,6 @@ 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')
another_marmot = open('another_marmot.png', 'rb')
marmot.photo.replace(another_marmot, content_type='image/png') # Replaces the GridFS document
marmot.save() # Replaces the GridFS reference contained in marmot instance

View File

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

View File

@@ -2,13 +2,13 @@
Installing MongoEngine
======================
To use MongoEngine, you will need to download `MongoDB <http://mongodb.org/>`_
To use MongoEngine, you will need to download `MongoDB <http://mongodb.com/>`_
and ensure it is running in an accessible location. You will also need
`PyMongo <http://api.mongodb.org/python>`_ to use MongoEngine, but if you
install MongoEngine using setuptools, then the dependencies will be handled for
you.
MongoEngine is available on PyPI, so to use it you can use :program:`pip`:
MongoEngine is available on PyPI, so you can use :program:`pip`:
.. code-block:: console

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

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

View File

@@ -15,11 +15,10 @@ fetch documents from the database::
.. note::
Once the iteration finishes (when :class:`StopIteration` is raised),
:meth:`~mongoengine.queryset.QuerySet.rewind` will be called so that the
:class:`~mongoengine.queryset.QuerySet` may be iterated over again. The
results of the first iteration are *not* cached, so the database will be hit
each time the :class:`~mongoengine.queryset.QuerySet` is iterated over.
As of MongoEngine 0.8 the querysets utilise a local cache. So iterating
it multiple times will only cause a single query. If this is not the
desired behaviour you can call :class:`~mongoengine.QuerySet.no_cache`
(version **0.8.3+**) to return a non-caching queryset.
Filtering queries
=================
@@ -40,10 +39,18 @@ syntax::
# been written by a user whose 'country' field is set to 'uk'
uk_pages = Page.objects(author__country='uk')
.. note::
(version **0.9.1+**) if your field name is like mongodb operator name (for example
type, lte, lt...) and you want to place it at the end of lookup keyword
mongoengine automatically prepend $ to it. To avoid this use __ at the end of
your lookup keyword. For example if your field name is ``type`` and you want to
query by this field you must use ``.objects(user__type__="admin")`` instead of
``.objects(user__type="admin")``
Query operators
===============
Operators other than equality may also be used in queries; just attach the
Operators other than equality may also be used in queries --- just attach the
operator name to a key with a double-underscore::
# Only find users whose age is 18 or less
@@ -85,19 +92,20 @@ expressions:
Geo queries
-----------
There are a few special operators for performing geographical queries. The following
were added in 0.8 for: :class:`~mongoengine.fields.PointField`,
There are a few special operators for performing geographical queries.
The following were added in MongoEngine 0.8 for
:class:`~mongoengine.fields.PointField`,
:class:`~mongoengine.fields.LineStringField` and
:class:`~mongoengine.fields.PolygonField`:
* ``geo_within`` -- Check if a geometry is within a polygon. For ease of use
it accepts either a geojson geometry or just the polygon coordinates eg::
* ``geo_within`` -- check if a geometry is within a polygon. For ease of use
it accepts either a geojson geometry or just the polygon coordinates eg::
loc.objects(point__geo_with=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
loc.objects(point__geo_with={"type": "Polygon",
loc.objects(point__geo_within=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
loc.objects(point__geo_within={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
* ``geo_within_box`` - simplified geo_within searching with a box eg::
* ``geo_within_box`` -- simplified geo_within searching with a box eg::
loc.objects(point__geo_within_box=[(-125.0, 35.0), (-100.0, 40.0)])
loc.objects(point__geo_within_box=[<bottom left coordinates>, <upper right coordinates>])
@@ -133,23 +141,22 @@ were added in 0.8 for: :class:`~mongoengine.fields.PointField`,
loc.objects(poly__geo_intersects={"type": "Polygon",
"coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]})
* ``near`` -- Find all the locations near a given point::
* ``near`` -- find all the locations near a given point::
loc.objects(point__near=[40, 5])
loc.objects(point__near={"type": "Point", "coordinates": [40, 5]})
You can also set the maximum distance in meters as well::
You can also set the maximum and/or the minimum distance in meters as well::
loc.objects(point__near=[40, 5], point__max_distance=1000)
loc.objects(point__near=[40, 5], point__min_distance=100)
The older 2D indexes are still supported with the
:class:`~mongoengine.fields.GeoPointField`:
* ``within_distance`` -- provide a list containing a point and a maximum
distance (e.g. [(41.342, -87.653), 5])
* ``within_spherical_distance`` -- Same as above but using the spherical geo model
* ``within_spherical_distance`` -- same as above but using the spherical geo model
(e.g. [(41.342, -87.653), 5/earth_radius])
* ``near`` -- order the documents by how close they are to a given point
* ``near_sphere`` -- Same as above but using the spherical geo model
@@ -162,7 +169,8 @@ The older 2D indexes are still supported with the
* ``max_distance`` -- can be added to your location queries to set a maximum
distance.
* ``min_distance`` -- can be added to your location queries to set a minimum
distance.
Querying lists
--------------
@@ -199,12 +207,14 @@ However, this doesn't map well to the syntax so you can also use a capital S ins
Post.objects(comments__by="joe").update(inc__comments__S__votes=1)
.. note:: Due to Mongo currently the $ operator only applies to the first matched item in the query.
.. note::
Due to :program:`Mongo`, currently the $ operator only applies to the
first matched item in the query.
Raw queries
-----------
It is possible to provide a raw PyMongo query as a query parameter, which will
It is possible to provide a raw :mod:`PyMongo` query as a query parameter, which will
be integrated directly into the query. This is done using the ``__raw__``
keyword argument::
@@ -214,12 +224,12 @@ keyword argument::
Limiting and skipping results
=============================
Just as with traditional ORMs, you may limit the number of results returned, or
Just as with traditional ORMs, you may limit the number of results returned or
skip a number or results in you query.
:meth:`~mongoengine.queryset.QuerySet.limit` and
:meth:`~mongoengine.queryset.QuerySet.skip` and methods are available on
:class:`~mongoengine.queryset.QuerySet` objects, but the prefered syntax for
achieving this is using array-slicing syntax::
:class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax
is preferred for achieving this::
# Only the first 5 people
users = User.objects[:5]
@@ -227,7 +237,7 @@ achieving this is using array-slicing syntax::
# All except for the first 5 people
users = User.objects[5:]
# 5 users, starting from the 10th user found
# 5 users, starting from the 11th user found
users = User.objects[10:15]
You may also index the query to retrieve a single result. If an item at that
@@ -253,23 +263,17 @@ To retrieve a result that should be unique in the collection, use
no document matches the query, and
:class:`~mongoengine.queryset.MultipleObjectsReturned`
if more than one document matched the query. These exceptions are merged into
your document defintions eg: `MyDoc.DoesNotExist`
your document definitions eg: `MyDoc.DoesNotExist`
A variation of this method exists,
:meth:`~mongoengine.queryset.Queryset.get_or_create`, that will create a new
document with the query arguments if no documents match the query. An
additional keyword argument, :attr:`defaults` may be provided, which will be
used as default values for the new document, in the case that it should need
to be created::
>>> a, created = User.objects.get_or_create(name='User A', defaults={'age': 30})
>>> b, created = User.objects.get_or_create(name='User A', defaults={'age': 40})
>>> a.name == b.name and a.age == b.age
True
A variation of this method, get_or_create() existed, but it was unsafe. It
could not be made safe, because there are no transactions in mongoDB. Other
approaches should be investigated, to ensure you don't accidentally duplicate
data when using something similar to this method. Therefore it was deprecated
in 0.8 and removed in 0.10.
Default Document queries
========================
By default, the objects :attr:`~mongoengine.Document.objects` attribute on a
By default, the objects :attr:`~Document.objects` attribute on a
document returns a :class:`~mongoengine.queryset.QuerySet` that doesn't filter
the collection -- it returns all objects. This may be changed by defining a
method on a document that modifies a queryset. The method should accept two
@@ -312,7 +316,7 @@ Should you want to add custom methods for interacting with or filtering
documents, extending the :class:`~mongoengine.queryset.QuerySet` class may be
the way to go. To use a custom :class:`~mongoengine.queryset.QuerySet` class on
a document, set ``queryset_class`` to the custom class in a
:class:`~mongoengine.Document`\ s ``meta`` dictionary::
:class:`~mongoengine.Document`'s ``meta`` dictionary::
class AwesomerQuerySet(QuerySet):
@@ -336,12 +340,19 @@ Javascript code that is executed on the database server.
Counting results
----------------
Just as with limiting and skipping results, there is a method on
:class:`~mongoengine.queryset.QuerySet` objects --
:meth:`~mongoengine.queryset.QuerySet.count`, but there is also a more Pythonic
way of achieving this::
Just as with limiting and skipping results, there is a method on a
:class:`~mongoengine.queryset.QuerySet` object --
:meth:`~mongoengine.queryset.QuerySet.count`::
num_users = len(User.objects)
num_users = User.objects.count()
You could technically use ``len(User.objects)`` to get the same result, but it
would be significantly slower than :meth:`~mongoengine.queryset.QuerySet.count`.
When you execute a server-side count query, you let MongoDB do the heavy
lifting and you receive a single integer over the wire. Meanwhile, len()
retrieves all the results, places them in a local cache, and finally counts
them. If we compare the performance of the two operations, len() is much slower
than :meth:`~mongoengine.queryset.QuerySet.count`.
Further aggregation
-------------------
@@ -445,14 +456,14 @@ data. To turn off dereferencing of the results of a query use
:func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so::
post = Post.objects.no_dereference().first()
assert(isinstance(post.author, ObjectId))
assert(isinstance(post.author, DBRef))
You can also turn off all dereferencing for a fixed period by using the
:class:`~mongoengine.context_managers.no_dereference` context manager::
with no_dereference(Post) as Post:
post = Post.objects.first()
assert(isinstance(post.author, ObjectId))
assert(isinstance(post.author, DBRef))
# Outside the context manager dereferencing occurs.
assert(isinstance(post.author, User))
@@ -473,6 +484,8 @@ operators. To use a :class:`~mongoengine.queryset.Q` object, pass it in as the
first positional argument to :attr:`Document.objects` when you filter it by
calling it with keyword arguments::
from mongoengine.queryset.visitor import Q
# Get published posts
Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now()))
@@ -489,23 +502,28 @@ calling it with keyword arguments::
Atomic updates
==============
Documents may be updated atomically by using the
:meth:`~mongoengine.queryset.QuerySet.update_one` and
:meth:`~mongoengine.queryset.QuerySet.update` methods on a
:meth:`~mongoengine.queryset.QuerySet`. There are several different "modifiers"
that you may use with these methods:
:meth:`~mongoengine.queryset.QuerySet.update_one`,
:meth:`~mongoengine.queryset.QuerySet.update` and
:meth:`~mongoengine.queryset.QuerySet.modify` methods on a
:class:`~mongoengine.queryset.QuerySet` or
:meth:`~mongoengine.Document.modify` and
:meth:`~mongoengine.Document.save` (with :attr:`save_condition` argument) on a
:class:`~mongoengine.Document`.
There are several different "modifiers" that you may use with these methods:
* ``set`` -- set a particular value
* ``unset`` -- delete a particular value (since MongoDB v1.3+)
* ``unset`` -- delete a particular value (since MongoDB v1.3)
* ``inc`` -- increment a value by a given amount
* ``dec`` -- decrement a value by a given amount
* ``pop`` -- remove the last item from a list
* ``push`` -- append a value to a list
* ``push_all`` -- append several values to a list
* ``pop`` -- remove the first or last element of a list
* ``pop`` -- remove the first or last element of a list `depending on the value`_
* ``pull`` -- remove a value from a list
* ``pull_all`` -- remove several values from a list
* ``add_to_set`` -- add value to a list only if its not in the list already
.. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/
The syntax for atomic updates is similar to the querying syntax, but the
modifier comes before the field, not after it::
@@ -524,6 +542,13 @@ modifier comes before the field, not after it::
>>> post.tags
['database', 'nosql']
.. note::
If no modifier operator is specified the default will be ``$set``. So the following sentences are identical::
>>> BlogPost.objects(id=post.id).update(title='Example Post')
>>> BlogPost.objects(id=post.id).update(set__title='Example Post')
.. note::
In version 0.5 the :meth:`~mongoengine.Document.save` runs atomic updates
@@ -540,6 +565,15 @@ cannot use the `$` syntax in keyword arguments it has been mapped to `S`::
>>> post.tags
['database', 'mongodb']
From MongoDB version 2.6, push operator supports $position value which allows
to push values with index.
>>> post = BlogPost(title="Test", tags=["mongo"])
>>> post.save()
>>> post.update(push__tags__0=["database", "code"])
>>> post.reload()
>>> post.tags
['database', 'code', 'mongo']
.. note::
Currently only top level lists are handled, future versions of mongodb /
pymongo plan to support nested positional operators. See `The $ positional
@@ -582,7 +616,7 @@ Some variables are made available in the scope of the Javascript function:
The following example demonstrates the intended usage of
:meth:`~mongoengine.queryset.QuerySet.exec_js` by defining a function that sums
over a field on a document (this functionality is already available throught
over a field on a document (this functionality is already available through
:meth:`~mongoengine.queryset.QuerySet.sum` but is shown here for sake of
example)::

View File

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

View File

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

View File

@@ -14,7 +14,7 @@ MongoDB. To install it, simply run
MongoEngine.
:doc:`guide/index`
The Full guide to MongoEngine - from modeling documents to storing files,
The Full guide to MongoEngine --- from modeling documents to storing files,
from querying for data to firing signals and *everything* between.
:doc:`apireference`

View File

@@ -3,11 +3,10 @@ Tutorial
========
This tutorial introduces **MongoEngine** by means of example --- we will walk
through how to create a simple **Tumblelog** application. A Tumblelog is a type
of blog where posts are not constrained to being conventional text-based posts.
As well as text-based entries, users may post images, links, videos, etc. For
simplicity's sake, we'll stick to text, image and link entries in our
application. As the purpose of this tutorial is to introduce MongoEngine, we'll
through how to create a simple **Tumblelog** application. A tumblelog is a
blog that supports mixed media content, including text, images, links, video,
audio, etc. For simplicity's sake, we'll stick to text, image, and link
entries. As the purpose of this tutorial is to introduce MongoEngine, we'll
focus on the data-modelling side of the application, leaving out a user
interface.
@@ -16,14 +15,14 @@ Getting started
Before we start, make sure that a copy of MongoDB is running in an accessible
location --- running it locally will be easier, but if that is not an option
then it may be run on a remote server. If you haven't installed mongoengine,
then it may be run on a remote server. If you haven't installed MongoEngine,
simply use pip to install it like so::
$ pip install mongoengine
Before we can start using MongoEngine, we need to tell it how to connect to our
instance of :program:`mongod`. For this we use the :func:`~mongoengine.connect`
function. If running locally the only argument we need to provide is the name
function. If running locally, the only argument we need to provide is the name
of the MongoDB database to use::
from mongoengine import *
@@ -39,18 +38,18 @@ Defining our documents
MongoDB is *schemaless*, which means that no schema is enforced by the database
--- we may add and remove fields however we want and MongoDB won't complain.
This makes life a lot easier in many regards, especially when there is a change
to the data model. However, defining schemata for our documents can help to
iron out bugs involving incorrect types or missing fields, and also allow us to
to the data model. However, defining schemas for our documents can help to iron
out bugs involving incorrect types or missing fields, and also allow us to
define utility methods on our documents in the same way that traditional
:abbr:`ORMs (Object-Relational Mappers)` do.
In our Tumblelog application we need to store several different types of
information. We will need to have a collection of **users**, so that we may
information. We will need to have a collection of **users**, so that we may
link posts to an individual. We also need to store our different types of
**posts** (eg: text, image and link) in the database. To aid navigation of our
Tumblelog, posts may have **tags** associated with them, so that the list of
posts shown to the user may be limited to posts that have been assigned a
specific tag. Finally, it would be nice if **comments** could be added to
specific tag. Finally, it would be nice if **comments** could be added to
posts. We'll start with **users**, as the other document models are slightly
more involved.
@@ -65,7 +64,7 @@ which fields a :class:`User` may have, and what types of data they might store::
first_name = StringField(max_length=50)
last_name = StringField(max_length=50)
This looks similar to how a the structure of a table would be defined in a
This looks similar to how the structure of a table would be defined in a
regular ORM. The key difference is that this schema will never be passed on to
MongoDB --- this will only be enforced at the application level, making future
changes easy to manage. Also, the User documents will be stored in a
@@ -78,7 +77,7 @@ Now we'll think about how to store the rest of the information. If we were
using a relational database, we would most likely have a table of **posts**, a
table of **comments** and a table of **tags**. To associate the comments with
individual posts, we would put a column in the comments table that contained a
foreign key to the posts table. We'd also need a link table to provide the
foreign key to the posts table. We'd also need a link table to provide the
many-to-many relationship between posts and tags. Then we'd need to address the
problem of storing the specialised post-types (text, image and link). There are
several ways we can achieve this, but each of them have their problems --- none
@@ -87,7 +86,7 @@ of them stand out as particularly intuitive solutions.
Posts
^^^^^
Happily mongoDB *isn't* a relational database, so we're not going to do it that
Happily MongoDB *isn't* a relational database, so we're not going to do it that
way. As it turns out, we can use MongoDB's schemaless nature to provide us with
a much nicer solution. We will store all of the posts in *one collection* and
each post type will only store the fields it needs. If we later want to add
@@ -96,7 +95,7 @@ using* the new fields we need to support video posts. This fits with the
Object-Oriented principle of *inheritance* nicely. We can think of
:class:`Post` as a base class, and :class:`TextPost`, :class:`ImagePost` and
:class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports
this kind of modelling out of the box --- all you need do is turn on inheritance
this kind of modeling out of the box --- all you need do is turn on inheritance
by setting :attr:`allow_inheritance` to True in the :attr:`meta`::
class Post(Document):
@@ -128,8 +127,8 @@ link table, we can just store a list of tags in each post. So, for both
efficiency and simplicity's sake, we'll store the tags as strings directly
within the post, rather than storing references to tags in a separate
collection. Especially as tags are generally very short (often even shorter
than a document's id), this denormalisation won't impact very strongly on the
size of our database. So let's take a look that the code our modified
than a document's id), this denormalization won't impact the size of the
database very strongly. Let's take a look at the code of our modified
:class:`Post` class::
class Post(Document):
@@ -141,7 +140,7 @@ The :class:`~mongoengine.fields.ListField` object that is used to define a Post'
takes a field object as its first argument --- this means that you can have
lists of any type of field (including lists).
.. note:: We don't need to modify the specialised post types as they all
.. note:: We don't need to modify the specialized post types as they all
inherit from :class:`Post`.
Comments
@@ -149,12 +148,12 @@ Comments
A comment is typically associated with *one* post. In a relational database, to
display a post with its comments, we would have to retrieve the post from the
database, then query the database again for the comments associated with the
database and then query the database again for the comments associated with the
post. This works, but there is no real reason to be storing the comments
separately from their associated posts, other than to work around the
relational model. Using MongoDB we can store the comments as a list of
*embedded documents* directly on a post document. An embedded document should
be treated no differently that a regular document; it just doesn't have its own
be treated no differently than a regular document; it just doesn't have its own
collection in the database. Using MongoEngine, we can define the structure of
embedded documents, along with utility methods, in exactly the same way we do
with regular documents::
@@ -207,7 +206,10 @@ object::
ross.last_name = 'Lawley'
ross.save()
Now that we've got our user in the database, let's add a couple of posts::
Assign another user to a variable called ``john``, just like we did above with
``ross``.
Now that we've got our users in the database, let's add a couple of posts::
post1 = TextPost(title='Fun with MongoEngine', author=john)
post1.content = 'Took a look at MongoEngine today, looks pretty cool.'
@@ -219,8 +221,8 @@ Now that we've got our user in the database, let's add a couple of posts::
post2.tags = ['mongoengine']
post2.save()
.. note:: If you change a field on a object that has already been saved, then
call :meth:`save` again, the document will be updated.
.. note:: If you change a field on an object that has already been saved and
then call :meth:`save` again, the document will be updated.
Accessing our data
==================
@@ -232,17 +234,17 @@ used to access the documents in the database collection associated with that
class. So let's see how we can get our posts' titles::
for post in Post.objects:
print post.title
print(post.title)
Retrieving type-specific information
------------------------------------
This will print the titles of our posts, one on each line. But What if we want
This will print the titles of our posts, one on each line. But what if we want
to access the type-specific data (link_url, content, etc.)? One way is simply
to use the :attr:`objects` attribute of a subclass of :class:`Post`::
for post in TextPost.objects:
print post.content
print(post.content)
Using TextPost's :attr:`objects` attribute only returns documents that were
created using :class:`TextPost`. Actually, there is a more general rule here:
@@ -259,16 +261,14 @@ instances of :class:`Post` --- they were instances of the subclass of
practice::
for post in Post.objects:
print post.title
print '=' * len(post.title)
print(post.title)
print('=' * len(post.title))
if isinstance(post, TextPost):
print post.content
print(post.content)
if isinstance(post, LinkPost):
print 'Link:', post.link_url
print
print('Link: {}'.format(post.link_url))
This would print the title of each post, followed by the content if it was a
text post, and "Link: <url>" if it was a link post.
@@ -283,7 +283,7 @@ your query. Let's adjust our query so that only posts with the tag "mongodb"
are returned::
for post in Post.objects(tags='mongodb'):
print post.title
print(post.title)
There are also methods available on :class:`~mongoengine.queryset.QuerySet`
objects that allow different results to be returned, for example, calling
@@ -292,11 +292,11 @@ the first matched by the query you provide. Aggregation functions may also be
used on :class:`~mongoengine.queryset.QuerySet` objects::
num_posts = Post.objects(tags='mongodb').count()
print 'Found %d posts with tag "mongodb"' % num_posts
print('Found {} posts with tag "mongodb"'.format(num_posts))
Learning more about mongoengine
Learning more about MongoEngine
-------------------------------
If you got this far you've made a great start, so well done! The next step on
your mongoengine journey is the `full user guide <guide/index>`_, where you
can learn indepth about how to use mongoengine and mongodb.
If you got this far you've made a great start, so well done! The next step on
your MongoEngine journey is the `full user guide <guide/index.html>`_, where
you can learn in-depth about how to use MongoEngine and MongoDB.

View File

@@ -2,12 +2,116 @@
Upgrading
#########
Development
***********
(Fill this out whenever you introduce breaking changes to MongoEngine)
URLField's constructor no longer takes `verify_exists`
0.15.0
******
0.14.0
******
This release includes a few bug fixes and a significant code cleanup. The most
important change is that `QuerySet.as_pymongo` no longer supports a
`coerce_types` mode. If you used it in the past, a) please let us know of your
use case, b) you'll need to override `as_pymongo` to get the desired outcome.
This release also makes the EmbeddedDocument not hashable by default. If you
use embedded documents in sets or dictionaries, you might have to override
`__hash__` and implement a hashing logic specific to your use case. See #1528
for the reason behind this change.
0.13.0
******
This release adds Unicode support to the `EmailField` and changes its
structure significantly. Previously, email addresses containing Unicode
characters didn't work at all. Starting with v0.13.0, domains with Unicode
characters are supported out of the box, meaning some emails that previously
didn't pass validation now do. Make sure the rest of your application can
accept such email addresses. Additionally, if you subclassed the `EmailField`
in your application and overrode `EmailField.EMAIL_REGEX`, you will have to
adjust your code to override `EmailField.USER_REGEX`, `EmailField.DOMAIN_REGEX`,
and potentially `EmailField.UTF8_USER_REGEX`.
0.12.0
******
This release includes various fixes for the `BaseQuerySet` methods and how they
are chained together. Since version 0.10.1 applying limit/skip/hint/batch_size
to an already-existing queryset wouldn't modify the underlying PyMongo cursor.
This has been fixed now, so you'll need to make sure that your code didn't rely
on the broken implementation.
Additionally, a public `BaseQuerySet.clone_into` has been renamed to a private
`_clone_into`. If you directly used that method in your code, you'll need to
rename its occurrences.
0.11.0
******
This release includes a major rehaul of MongoEngine's code quality and
introduces a few breaking changes. It also touches many different parts of
the package and although all the changes have been tested and scrutinized,
you're encouraged to thorougly test the upgrade.
First breaking change involves renaming `ConnectionError` to `MongoEngineConnectionError`.
If you import or catch this exception, you'll need to rename it in your code.
Second breaking change drops Python v2.6 support. If you run MongoEngine on
that Python version, you'll need to upgrade it first.
Third breaking change drops an old backward compatibility measure where
`from mongoengine.base import ErrorClass` would work on top of
`from mongoengine.errors import ErrorClass` (where `ErrorClass` is e.g.
`ValidationError`). If you import any exceptions from `mongoengine.base`,
change it to `mongoengine.errors`.
0.10.8
******
This version fixed an issue where specifying a MongoDB URI host would override
more information than it should. These changes are minor, but they still
subtly modify the connection logic and thus you're encouraged to test your
MongoDB connection before shipping v0.10.8 in production.
0.10.7
******
`QuerySet.aggregate_sum` and `QuerySet.aggregate_average` are dropped. Use
`QuerySet.sum` and `QuerySet.average` instead which use the aggreation framework
by default from now on.
0.9.0
*****
The 0.8.7 package on pypi was corrupted. If upgrading from 0.8.7 to 0.9.0 please follow: ::
pip uninstall pymongo
pip uninstall mongoengine
pip install pymongo==2.8
pip install mongoengine
0.8.7
*****
Calling reload on deleted / nonexistent documents now raises a DoesNotExist
exception.
0.8.2 to 0.8.3
**************
Minor change that may impact users:
DynamicDocument fields are now stored in creation order after any declared
fields. Previously they were stored alphabetically.
0.7 to 0.8
**********
There have been numerous backwards breaking changes in 0.8. The reasons for
these are ensure that MongoEngine has sane defaults going forward and
performs the best it can out the box. Where possible there have been
these are to ensure that MongoEngine has sane defaults going forward and that it
performs the best it can out of the box. Where possible there have been
FutureWarnings to help get you ready for the change, but that hasn't been
possible for the whole of the release.
@@ -61,7 +165,7 @@ inherited classes like so: ::
Document Definition
-------------------
The default for inheritance has changed - its now off by default and
The default for inheritance has changed - it is now off by default and
:attr:`_cls` will not be stored automatically with the class. So if you extend
your :class:`~mongoengine.Document` or :class:`~mongoengine.EmbeddedDocuments`
you will need to declare :attr:`allow_inheritance` in the meta data like so: ::
@@ -71,7 +175,7 @@ you will need to declare :attr:`allow_inheritance` in the meta data like so: ::
meta = {'allow_inheritance': True}
Previously, if you had data the database that wasn't defined in the Document
Previously, if you had data in the database that wasn't defined in the Document
definition, it would set it as an attribute on the document. This is no longer
the case and the data is set only in the ``document._data`` dictionary: ::
@@ -91,10 +195,17 @@ the case and the data is set only in the ``document._data`` dictionary: ::
File "<stdin>", line 1, in <module>
AttributeError: 'Animal' object has no attribute 'size'
The Document class has introduced a reserved function `clean()`, which will be
called before saving the document. If your document class happens to have a method
with the same name, please try to rename it.
def clean(self):
pass
ReferenceField
--------------
ReferenceFields now store ObjectId's by default - this is more efficient than
ReferenceFields now store ObjectIds by default - this is more efficient than
DBRefs as we already know what Document types they reference::
# Old code
@@ -116,13 +227,17 @@ eg::
# Mark all ReferenceFields as dirty and save
for p in Person.objects:
p._mark_as_dirty('parent')
p._mark_as_dirty('friends')
p._mark_as_changed('parent')
p._mark_as_changed('friends')
p.save()
`An example test migration for ReferenceFields is available on github
<https://github.com/MongoEngine/mongoengine/blob/master/tests/migration/refrencefield_dbref_to_object_id.py>`_.
.. Note:: Internally mongoengine handles ReferenceFields the same, so they are
converted to DBRef on loading and ObjectIds or DBRefs depending on settings
on storage.
UUIDField
---------
@@ -136,16 +251,16 @@ UUIDFields now default to storing binary values::
class Animal(Document):
uuid = UUIDField(binary=False)
To migrate all the uuid's you need to touch each object and mark it as dirty
To migrate all the uuids you need to touch each object and mark it as dirty
eg::
# Doc definition
class Animal(Document):
uuid = UUIDField()
# Mark all ReferenceFields as dirty and save
# Mark all UUIDFields as dirty and save
for a in Animal.objects:
a._mark_as_dirty('uuid')
a._mark_as_changed('uuid')
a.save()
`An example test migration for UUIDFields is available on github
@@ -154,7 +269,7 @@ eg::
DecimalField
------------
DecimalField now store floats - previous it was storing strings and that
DecimalFields now store floats - previously it was storing strings and that
made it impossible to do comparisons when querying correctly.::
# Old code
@@ -165,19 +280,19 @@ made it impossible to do comparisons when querying correctly.::
class Person(Document):
balance = DecimalField(force_string=True)
To migrate all the uuid's you need to touch each object and mark it as dirty
To migrate all the DecimalFields you need to touch each object and mark it as dirty
eg::
# Doc definition
class Person(Document):
balance = DecimalField()
# Mark all ReferenceFields as dirty and save
# Mark all DecimalField's as dirty and save
for p in Person.objects:
p._mark_as_dirty('balance')
p._mark_as_changed('balance')
p.save()
.. note:: DecimalField's have also been improved with the addition of precision
.. note:: DecimalFields have also been improved with the addition of precision
and rounding. See :class:`~mongoengine.fields.DecimalField` for more information.
`An example test migration for DecimalFields is available on github
@@ -186,7 +301,7 @@ eg::
Cascading Saves
---------------
To improve performance document saves will no longer automatically cascade.
Any changes to a Documents references will either have to be saved manually or
Any changes to a Document's references will either have to be saved manually or
you will have to explicitly tell it to cascade on save::
# At the class level:
@@ -228,7 +343,7 @@ update your code like so: ::
# Update example a) assign queryset after a change:
mammals = Animal.objects(type="mammal")
carnivores = mammals.filter(order="Carnivora") # Reassign the new queryset so fitler can be applied
carnivores = mammals.filter(order="Carnivora") # Reassign the new queryset so filter can be applied
[m for m in carnivores] # This will return all carnivores
# Update example b) chain the queryset:
@@ -236,7 +351,7 @@ update your code like so: ::
[m for m in mammals] # This will return all carnivores
Len iterates the queryset
--------------------------
-------------------------
If you ever did `len(queryset)` it previously did a `count()` under the covers,
this caused some unusual issues. As `len(queryset)` is most often used by
@@ -249,13 +364,13 @@ queryset you should upgrade to use count::
len(Animal.objects(type="mammal"))
# New code
Animal.objects(type="mammal").count())
Animal.objects(type="mammal").count()
.only() now inline with .exclude()
----------------------------------
The behaviour of `.only()` was highly ambious, now it works in the mirror fashion
The behaviour of `.only()` was highly ambiguous, now it works in mirror fashion
to `.exclude()`. Chaining `.only()` calls will increase the fields required::
# Old code
@@ -419,7 +534,7 @@ main areas of changed are: choices in fields, map_reduce and collection names.
Choice options:
===============
Are now expected to be an iterable of tuples, with the first element in each
Are now expected to be an iterable of tuples, with the first element in each
tuple being the actual value to be stored. The second element is the
human-readable name for the option.
@@ -441,8 +556,8 @@ such the following have been changed:
Default collection naming
=========================
Previously it was just lowercase, its now much more pythonic and readable as
its lowercase and underscores, previously ::
Previously it was just lowercase, it's now much more pythonic and readable as
it's lowercase and underscores, previously ::
class MyAceDocument(Document):
pass
@@ -509,5 +624,5 @@ Alternatively, you can rename your collections eg ::
mongodb 1.8 > 2.0 +
===================
Its been reported that indexes may need to be recreated to the newer version of indexes.
It's been reported that indexes may need to be recreated to the newer version of indexes.
To do this drop indexes and call ``ensure_indexes`` on each model.

View File

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

View File

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

View File

@@ -1,19 +1,25 @@
from mongoengine.errors import NotRegistered
__all__ = ('ALLOW_INHERITANCE', 'get_document', '_document_registry')
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
UPDATE_OPERATORS = {'set', 'unset', 'inc', 'dec', 'mul',
'pop', 'push', 'push_all', 'pull',
'pull_all', 'add_to_set', 'set_on_insert',
'min', 'max', 'rename'}
ALLOW_INHERITANCE = False
_document_registry = {}
def get_document(name):
"""Get a document class by name."""
doc = _document_registry.get(name, None)
if not doc:
# Possible old style name
single_end = name.split('.')[-1]
compound_end = '.%s' % single_end
possible_match = [k for k in _document_registry.keys()
possible_match = [k for k in _document_registry
if k.endswith(compound_end) or k == single_end]
if len(possible_match) == 1:
doc = _document_registry.get(possible_match.pop(), None)

View File

@@ -1,45 +1,60 @@
import weakref
from mongoengine.common import _import_class
__all__ = ("BaseDict", "BaseList")
from bson import DBRef
import six
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
class BaseDict(dict):
"""A special dict so we can watch any changes
"""
"""A special dict so we can watch any changes."""
_dereferenced = False
_instance = None
_name = None
def __init__(self, dict_items, instance, name):
self._instance = weakref.proxy(instance)
self._name = name
return super(BaseDict, self).__init__(dict_items)
BaseDocument = _import_class('BaseDocument')
def __getitem__(self, *args, **kwargs):
value = super(BaseDict, self).__getitem__(*args, **kwargs)
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseDict, self).__init__(dict_items)
def __getitem__(self, key, *args, **kwargs):
value = super(BaseDict, self).__getitem__(key)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, None, '%s.%s' % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList):
value = BaseList(value, None, '%s.%s' % (self._name, key))
super(BaseDict, self).__setitem__(key, value)
value._instance = self._instance
return value
def __setitem__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).__setitem__(*args, **kwargs)
def __setitem__(self, key, value, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__setitem__(key, value)
def __delete__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).__delete__(*args, **kwargs)
def __delitem__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).__delitem__(*args, **kwargs)
def __delitem__(self, key, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__delitem__(key)
def __delattr__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).__delattr__(*args, **kwargs)
def __delattr__(self, key, *args, **kwargs):
self._mark_as_changed(key)
return super(BaseDict, self).__delattr__(key)
def __getstate__(self):
self.instance = None
@@ -52,7 +67,7 @@ class BaseDict(dict):
def clear(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).clear(*args, **kwargs)
return super(BaseDict, self).clear()
def pop(self, *args, **kwargs):
self._mark_as_changed()
@@ -60,45 +75,77 @@ class BaseDict(dict):
def popitem(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).popitem(*args, **kwargs)
return super(BaseDict, self).popitem()
def setdefault(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).setdefault(*args, **kwargs)
def update(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseDict, self).update(*args, **kwargs)
def _mark_as_changed(self):
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
self._instance._mark_as_changed(self._name)
if key:
self._instance._mark_as_changed('%s.%s' % (self._name, key))
else:
self._instance._mark_as_changed(self._name)
class BaseList(list):
"""A special list so we can watch any changes
"""
"""A special list so we can watch any changes."""
_dereferenced = False
_instance = None
_name = None
def __init__(self, list_items, instance, name):
self._instance = weakref.proxy(instance)
self._name = name
return super(BaseList, self).__init__(list_items)
BaseDocument = _import_class('BaseDocument')
def __getitem__(self, *args, **kwargs):
value = super(BaseList, self).__getitem__(*args, **kwargs)
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseList, self).__init__(list_items)
def __getitem__(self, key, *args, **kwargs):
value = super(BaseList, self).__getitem__(key)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, None, '%s.%s' % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
elif isinstance(value, list) and not isinstance(value, BaseList):
value = BaseList(value, None, '%s.%s' % (self._name, key))
super(BaseList, self).__setitem__(key, value)
value._instance = self._instance
return value
def __setitem__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__setitem__(*args, **kwargs)
def __iter__(self):
for v in super(BaseList, self).__iter__():
yield v
def __delitem__(self, *args, **kwargs):
def __setitem__(self, key, value, *args, **kwargs):
if isinstance(key, slice):
self._mark_as_changed()
else:
self._mark_as_changed(key)
return super(BaseList, self).__setitem__(key, value)
def __delitem__(self, key):
self._mark_as_changed()
return super(BaseList, self).__delitem__(*args, **kwargs)
return super(BaseList, self).__delitem__(key)
def __setslice__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__setslice__(*args, **kwargs)
def __delslice__(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).__delslice__(*args, **kwargs)
def __getstate__(self):
self.instance = None
@@ -109,6 +156,14 @@ class BaseList(list):
self = state
return self
def __iadd__(self, other):
self._mark_as_changed()
return super(BaseList, self).__iadd__(other)
def __imul__(self, other):
self._mark_as_changed()
return super(BaseList, self).__imul__(other)
def append(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).append(*args, **kwargs)
@@ -129,14 +184,305 @@ class BaseList(list):
self._mark_as_changed()
return super(BaseList, self).remove(*args, **kwargs)
def reverse(self, *args, **kwargs):
def reverse(self):
self._mark_as_changed()
return super(BaseList, self).reverse(*args, **kwargs)
return super(BaseList, self).reverse()
def sort(self, *args, **kwargs):
self._mark_as_changed()
return super(BaseList, self).sort(*args, **kwargs)
def _mark_as_changed(self):
def _mark_as_changed(self, key=None):
if hasattr(self._instance, '_mark_as_changed'):
self._instance._mark_as_changed(self._name)
if key:
self._instance._mark_as_changed(
'%s.%s' % (self._name, key % len(self))
)
else:
self._instance._mark_as_changed(self._name)
class EmbeddedDocumentList(BaseList):
@classmethod
def __match_all(cls, embedded_doc, kwargs):
"""Return True if a given embedded doc matches all the filter
kwargs. If it doesn't return False.
"""
for key, expected_value in kwargs.items():
doc_val = getattr(embedded_doc, key)
if doc_val != expected_value and six.text_type(doc_val) != expected_value:
return False
return True
@classmethod
def __only_matches(cls, embedded_docs, kwargs):
"""Return embedded docs that match the filter kwargs."""
if not kwargs:
return embedded_docs
return [doc for doc in embedded_docs if cls.__match_all(doc, kwargs)]
def __init__(self, list_items, instance, name):
super(EmbeddedDocumentList, self).__init__(list_items, instance, name)
self._instance = instance
def filter(self, **kwargs):
"""
Filters the list by only including embedded documents with the
given keyword arguments.
This method only supports simple comparison (e.g: .filter(name='John Doe'))
and does not support operators like __gte, __lte, __icontains like queryset.filter does
:param kwargs: The keyword arguments corresponding to the fields to
filter on. *Multiple arguments are treated as if they are ANDed
together.*
:return: A new ``EmbeddedDocumentList`` containing the matching
embedded documents.
Raises ``AttributeError`` if a given keyword is not a valid field for
the embedded document class.
"""
values = self.__only_matches(self, kwargs)
return EmbeddedDocumentList(values, self._instance, self._name)
def exclude(self, **kwargs):
"""
Filters the list by excluding embedded documents with the given
keyword arguments.
:param kwargs: The keyword arguments corresponding to the fields to
exclude on. *Multiple arguments are treated as if they are ANDed
together.*
:return: A new ``EmbeddedDocumentList`` containing the non-matching
embedded documents.
Raises ``AttributeError`` if a given keyword is not a valid field for
the embedded document class.
"""
exclude = self.__only_matches(self, kwargs)
values = [item for item in self if item not in exclude]
return EmbeddedDocumentList(values, self._instance, self._name)
def count(self):
"""
The number of embedded documents in the list.
:return: The length of the list, equivalent to the result of ``len()``.
"""
return len(self)
def get(self, **kwargs):
"""
Retrieves an embedded document determined by the given keyword
arguments.
:param kwargs: The keyword arguments corresponding to the fields to
search on. *Multiple arguments are treated as if they are ANDed
together.*
:return: The embedded document matched by the given keyword arguments.
Raises ``DoesNotExist`` if the arguments used to query an embedded
document returns no results. ``MultipleObjectsReturned`` if more
than one result is returned.
"""
values = self.__only_matches(self, kwargs)
if len(values) == 0:
raise DoesNotExist(
'%s matching query does not exist.' % self._name
)
elif len(values) > 1:
raise MultipleObjectsReturned(
'%d items returned, instead of 1' % len(values)
)
return values[0]
def first(self):
"""Return the first embedded document in the list, or ``None``
if empty.
"""
if len(self) > 0:
return self[0]
def create(self, **values):
"""
Creates a new embedded document and saves it to the database.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:param values: A dictionary of values for the embedded document.
:return: The new embedded document instance.
"""
name = self._name
EmbeddedClass = self._instance._fields[name].field.document_type_obj
self._instance[self._name].append(EmbeddedClass(**values))
return self._instance[self._name][-1]
def save(self, *args, **kwargs):
"""
Saves the ancestor document.
:param args: Arguments passed up to the ancestor Document's save
method.
:param kwargs: Keyword arguments passed up to the ancestor Document's
save method.
"""
self._instance.save(*args, **kwargs)
def delete(self):
"""
Deletes the embedded documents from the database.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:return: The number of entries deleted.
"""
values = list(self)
for item in values:
self._instance[self._name].remove(item)
return len(values)
def update(self, **update):
"""
Updates the embedded documents with the given replacement values. This
function does not support mongoDB update operators such as ``inc__``.
.. note::
The embedded document changes are not automatically saved
to the database after calling this method.
:param update: A dictionary of update values to apply to each
embedded document.
:return: The number of entries updated.
"""
if len(update) == 0:
return 0
values = list(self)
for item in values:
for k, v in update.items():
setattr(item, k, v)
return len(values)
class StrictDict(object):
__slots__ = ()
_special_fields = {'get', 'pop', 'iteritems', 'items', 'keys', 'create'}
_classes = {}
def __init__(self, **kwargs):
for k, v in kwargs.iteritems():
setattr(self, k, v)
def __getitem__(self, key):
key = '_reserved_' + key if key in self._special_fields else key
try:
return getattr(self, key)
except AttributeError:
raise KeyError(key)
def __setitem__(self, key, value):
key = '_reserved_' + key if key in self._special_fields else key
return setattr(self, key, value)
def __contains__(self, key):
return hasattr(self, key)
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
def pop(self, key, default=None):
v = self.get(key, default)
try:
delattr(self, key)
except AttributeError:
pass
return v
def iteritems(self):
for key in self:
yield key, self[key]
def items(self):
return [(k, self[k]) for k in iter(self)]
def iterkeys(self):
return iter(self)
def keys(self):
return list(iter(self))
def __iter__(self):
return (key for key in self.__slots__ if hasattr(self, key))
def __len__(self):
return len(list(self.iteritems()))
def __eq__(self, other):
return self.items() == other.items()
def __ne__(self, other):
return self.items() != other.items()
@classmethod
def create(cls, allowed_keys):
allowed_keys_tuple = tuple(('_reserved_' + k if k in cls._special_fields else k) for k in allowed_keys)
allowed_keys = frozenset(allowed_keys_tuple)
if allowed_keys not in cls._classes:
class SpecificStrictDict(cls):
__slots__ = allowed_keys_tuple
def __repr__(self):
return '{%s}' % ', '.join('"{0!s}": {1!r}'.format(k, v) for k, v in self.items())
cls._classes[allowed_keys] = SpecificStrictDict
return cls._classes[allowed_keys]
class LazyReference(DBRef):
__slots__ = ('_cached_doc', 'passthrough', 'document_type')
def fetch(self, force=False):
if not self._cached_doc or force:
self._cached_doc = self.document_type.objects.get(pk=self.pk)
if not self._cached_doc:
raise DoesNotExist('Trying to dereference unknown document %s' % (self))
return self._cached_doc
@property
def pk(self):
return self.id
def __init__(self, document_type, pk, cached_doc=None, passthrough=False):
self.document_type = document_type
self._cached_doc = cached_doc
self.passthrough = passthrough
super(LazyReference, self).__init__(self.document_type._get_collection_name(), pk)
def __getitem__(self, name):
if not self.passthrough:
raise KeyError()
document = self.fetch()
return document[name]
def __getattr__(self, name):
if not object.__getattribute__(self, 'passthrough'):
raise AttributeError()
document = self.fetch()
try:
return document[name]
except KeyError:
raise AttributeError()
def __repr__(self):
return "<LazyReference(%s, %r)>" % (self.document_type, self.pk)

File diff suppressed because it is too large Load Diff

View File

@@ -4,14 +4,17 @@ import weakref
from bson import DBRef, ObjectId, SON
import pymongo
import six
from mongoengine.base.common import UPDATE_OPERATORS
from mongoengine.base.datastructures import (BaseDict, BaseList,
EmbeddedDocumentList)
from mongoengine.common import _import_class
from mongoengine.errors import ValidationError
from mongoengine.base.common import ALLOW_INHERITANCE
from mongoengine.base.datastructures import BaseDict, BaseList
__all__ = ("BaseField", "ComplexBaseField", "ObjectIdField", "GeoJsonBaseField")
__all__ = ('BaseField', 'ComplexBaseField', 'ObjectIdField',
'GeoJsonBaseField')
class BaseField(object):
@@ -20,7 +23,6 @@ class BaseField(object):
.. versionchanged:: 0.5 - added verbose and help text
"""
name = None
_geo_index = False
_auto_gen = False # Call `generate` to generate a value
@@ -34,11 +36,39 @@ class BaseField(object):
def __init__(self, db_field=None, name=None, required=False, default=None,
unique=False, unique_with=None, primary_key=False,
validation=None, choices=None, verbose_name=None,
help_text=None):
validation=None, choices=None, null=False, sparse=False,
**kwargs):
"""
:param db_field: The database field to store this field in
(defaults to the name of the field)
:param name: Deprecated - use db_field
:param required: If the field is required. Whether it has to have a
value or not. Defaults to False.
:param default: (optional) The default value for this field if no value
has been set (or if the value has been unset). It can be a
callable.
:param unique: Is the field value unique or not. Defaults to False.
:param unique_with: (optional) The other field this field should be
unique with.
:param primary_key: Mark this field as the primary key. Defaults to False.
:param validation: (optional) A callable to validate the value of the
field. Generally this is deprecated in favour of the
`FIELD.validate` method
:param choices: (optional) The valid choices
:param null: (optional) If the field value can be null. If no and there is a default value
then the default value is set
:param sparse: (optional) `sparse=True` combined with `unique=True` and `required=False`
means that uniqueness won't be enforced for `None` values
:param **kwargs: (optional) Arbitrary indirection-free metadata for
this field can be supplied as additional keyword arguments and
accessed as attributes of the field. Must not conflict with any
existing attributes. Common metadata includes `verbose_name` and
`help_text`.
"""
self.db_field = (db_field or name) if not primary_key else '_id'
if name:
msg = "Fields' 'name' attribute deprecated in favour of 'db_field'"
msg = 'Field\'s "name" attribute deprecated in favour of "db_field"'
warnings.warn(msg, DeprecationWarning)
self.required = required or primary_key
self.default = default
@@ -47,8 +77,37 @@ class BaseField(object):
self.primary_key = primary_key
self.validation = validation
self.choices = choices
self.verbose_name = verbose_name
self.help_text = help_text
self.null = null
self.sparse = sparse
self._owner_document = None
# Make sure db_field is a string (if it's explicitly defined).
if (
self.db_field is not None and
not isinstance(self.db_field, six.string_types)
):
raise TypeError('db_field should be a string.')
# Make sure db_field doesn't contain any forbidden characters.
if isinstance(self.db_field, six.string_types) and (
'.' in self.db_field or
'\0' in self.db_field or
self.db_field.startswith('$')
):
raise ValueError(
'field names cannot contain dots (".") or null characters '
'("\\0"), and they must not start with a dollar sign ("$").'
)
# Detect and report conflicts between metadata and base properties.
conflicts = set(dir(self)) & set(kwargs)
if conflicts:
raise TypeError('%s already has attribute(s): %s' % (
self.__class__.__name__, ', '.join(conflicts)))
# Assign metadata to the instance
# This efficient method is available because no __slots__ are defined.
self.__dict__.update(kwargs)
# Adjust the appropriate creation counter, and save our local copy.
if self.db_field == '_id':
@@ -59,84 +118,109 @@ class BaseField(object):
BaseField.creation_counter += 1
def __get__(self, instance, owner):
"""Descriptor for retrieving a value from a field in a document. Do
any necessary conversion between Python and MongoDB types.
"""Descriptor for retrieving a value from a field in a document.
"""
if instance is None:
# Document class being used rather than a document object
return self
# Get value from document instance if available, if not use default
value = instance._data.get(self.name)
if value is None:
value = self.default
# Allow callable default values
if callable(value):
value = value()
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = weakref.proxy(instance)
return value
# Get value from document instance if available
return instance._data.get(self.name)
def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document.
"""
# If setting to None and there is a default
# Then set the value to the default value
if value is None:
if self.null:
value = None
elif self.default is not None:
value = self.default
if callable(value):
value = value()
if instance._initialised:
try:
if (self.name not in instance._data or
instance._data[self.name] != value):
instance._data[self.name] != value):
instance._mark_as_changed(self.name)
except:
except Exception:
# Values cant be compared eg: naive and tz datetimes
# So mark it as changed
instance._mark_as_changed(self.name)
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument):
value._instance = weakref.proxy(instance)
elif isinstance(value, (list, tuple)):
for v in value:
if isinstance(v, EmbeddedDocument):
v._instance = weakref.proxy(instance)
instance._data[self.name] = value
def error(self, message="", errors=None, field_name=None):
"""Raises a ValidationError.
"""
def error(self, message='', errors=None, field_name=None):
"""Raise a ValidationError."""
field_name = field_name if field_name else self.name
raise ValidationError(message, errors=errors, field_name=field_name)
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type.
"""
"""Convert a MongoDB-compatible type to a Python type."""
return value
def to_mongo(self, value):
"""Convert a Python type to a MongoDB-compatible type.
"""
"""Convert a Python type to a MongoDB-compatible type."""
return self.to_python(value)
def _to_mongo_safe_call(self, value, use_db_field=True, fields=None):
"""Helper method to call to_mongo with proper inputs."""
f_inputs = self.to_mongo.__code__.co_varnames
ex_vars = {}
if 'fields' in f_inputs:
ex_vars['fields'] = fields
if 'use_db_field' in f_inputs:
ex_vars['use_db_field'] = use_db_field
return self.to_mongo(value, **ex_vars)
def prepare_query_value(self, op, value):
"""Prepare a value that is being used in a query for PyMongo.
"""
"""Prepare a value that is being used in a query for PyMongo."""
if op in UPDATE_OPERATORS:
self.validate(value)
return value
def validate(self, value, clean=True):
"""Perform validation on a value.
"""
"""Perform validation on a value."""
pass
def _validate(self, value, **kwargs):
def _validate_choices(self, value):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
# check choices
choice_list = self.choices
if isinstance(next(iter(choice_list)), (list, tuple)):
# next(iter) is useful for sets
choice_list = [k for k, _ in choice_list]
# Choices which are other types of Documents
if isinstance(value, (Document, EmbeddedDocument)):
if not any(isinstance(value, c) for c in choice_list):
self.error(
'Value must be an instance of %s' % (
six.text_type(choice_list)
)
)
# Choices which are types other than Documents
else:
values = value if isinstance(value, (list, tuple)) else [value]
if len(set(values) - set(choice_list)):
self.error('Value must be one of %s' % six.text_type(choice_list))
def _validate(self, value, **kwargs):
# Check the Choices Constraint
if self.choices:
is_cls = isinstance(value, (Document, EmbeddedDocument))
value_to_check = value.__class__ if is_cls else value
err_msg = 'an instance' if is_cls else 'one'
if isinstance(self.choices[0], (list, tuple)):
option_keys = [k for k, v in self.choices]
if value_to_check not in option_keys:
msg = ('Value must be %s of %s' %
(err_msg, unicode(option_keys)))
self.error(msg)
elif value_to_check not in self.choices:
msg = ('Value must be %s of %s' %
(err_msg, unicode(self.choices)))
self.error(msg)
self._validate_choices(value)
# check validation argument
if self.validation is not None:
@@ -149,6 +233,17 @@ class BaseField(object):
self.validate(value, **kwargs)
@property
def owner_document(self):
return self._owner_document
def _set_owner_document(self, owner_document):
self._owner_document = owner_document
@owner_document.setter
def owner_document(self, owner_document):
self._set_owner_document(owner_document)
class ComplexBaseField(BaseField):
"""Handles complex fields, such as lists / dictionaries.
@@ -161,24 +256,27 @@ class ComplexBaseField(BaseField):
"""
field = None
__dereference = False
def __get__(self, instance, owner):
"""Descriptor to automatically dereference references.
"""
"""Descriptor to automatically dereference references."""
if instance is None:
# Document class being used rather than a document object
return self
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
dereference = (self._auto_dereference and
(self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))))
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
self._auto_dereference = instance._fields[self.name]._auto_dereference
if not self.__dereference and instance._initialised and dereference:
instance._data[self.name] = self._dereference(
auto_dereference = instance._fields[self.name]._auto_dereference
dereference = (auto_dereference and
(self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))))
_dereference = _import_class('DeReference')()
if instance._initialised and dereference and instance._data.get(self.name):
instance._data[self.name] = _dereference(
instance._data.get(self.name), max_depth=1, instance=instance,
name=self.name
)
@@ -186,18 +284,21 @@ class ComplexBaseField(BaseField):
value = super(ComplexBaseField, self).__get__(instance, owner)
# Convert lists / values so we can watch for any changes on them
if (isinstance(value, (list, tuple)) and
not isinstance(value, BaseList)):
value = BaseList(value, instance, self.name)
if isinstance(value, (list, tuple)):
if (issubclass(type(self), EmbeddedDocumentListField) and
not isinstance(value, EmbeddedDocumentList)):
value = EmbeddedDocumentList(value, instance, self.name)
elif not isinstance(value, BaseList):
value = BaseList(value, instance, self.name)
instance._data[self.name] = value
elif isinstance(value, dict) and not isinstance(value, BaseDict):
value = BaseDict(value, instance, self.name)
instance._data[self.name] = value
if (self._auto_dereference and instance._initialised and
isinstance(value, (BaseList, BaseDict))
and not value._dereferenced):
value = self._dereference(
if (auto_dereference and instance._initialised and
isinstance(value, (BaseList, BaseDict)) and
not value._dereferenced):
value = _dereference(
value, max_depth=1, instance=instance, name=self.name
)
value._dereferenced = True
@@ -205,35 +306,33 @@ class ComplexBaseField(BaseField):
return value
def __set__(self, instance, value):
"""Descriptor for assigning a value to a field in a document.
"""
instance._data[self.name] = value
instance._mark_as_changed(self.name)
def to_python(self, value):
"""Convert a MongoDB-compatible type to a Python type.
"""
Document = _import_class('Document')
if isinstance(value, basestring):
"""Convert a MongoDB-compatible type to a Python type."""
if isinstance(value, six.string_types):
return value
if hasattr(value, 'to_python'):
return value.to_python()
BaseDocument = _import_class('BaseDocument')
if isinstance(value, BaseDocument):
# Something is wrong, return the value as it is
return value
is_list = False
if not hasattr(value, 'items'):
try:
is_list = True
value = dict([(k, v) for k, v in enumerate(value)])
value = {idx: v for idx, v in enumerate(value)}
except TypeError: # Not iterable return the value
return value
if self.field:
value_dict = dict([(key, self.field.to_python(item))
for key, item in value.items()])
self.field._auto_dereference = self._auto_dereference
value_dict = {key: self.field.to_python(item)
for key, item in value.items()}
else:
Document = _import_class('Document')
value_dict = {}
for k, v in value.items():
if isinstance(v, Document):
@@ -249,27 +348,26 @@ class ComplexBaseField(BaseField):
value_dict[k] = self.to_python(v)
if is_list: # Convert back to a list
return [v for k, v in sorted(value_dict.items(),
return [v for _, v in sorted(value_dict.items(),
key=operator.itemgetter(0))]
return value_dict
def to_mongo(self, value):
"""Convert a Python type to a MongoDB-compatible type.
"""
Document = _import_class("Document")
EmbeddedDocument = _import_class("EmbeddedDocument")
GenericReferenceField = _import_class("GenericReferenceField")
def to_mongo(self, value, use_db_field=True, fields=None):
"""Convert a Python type to a MongoDB-compatible type."""
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
GenericReferenceField = _import_class('GenericReferenceField')
if isinstance(value, basestring):
if isinstance(value, six.string_types):
return value
if hasattr(value, 'to_mongo'):
if isinstance(value, Document):
return GenericReferenceField().to_mongo(value)
cls = value.__class__
val = value.to_mongo()
# If we its a document thats not inherited add _cls
if (isinstance(value, EmbeddedDocument)):
val = value.to_mongo(use_db_field, fields)
# If it's a document that is not inherited add _cls
if isinstance(value, EmbeddedDocument):
val['_cls'] = cls.__name__
return val
@@ -277,13 +375,15 @@ class ComplexBaseField(BaseField):
if not hasattr(value, 'items'):
try:
is_list = True
value = dict([(k, v) for k, v in enumerate(value)])
value = {k: v for k, v in enumerate(value)}
except TypeError: # Not iterable return the value
return value
if self.field:
value_dict = dict([(key, self.field.to_mongo(item))
for key, item in value.iteritems()])
value_dict = {
key: self.field._to_mongo_safe_call(item, use_db_field, fields)
for key, item in value.iteritems()
}
else:
value_dict = {}
for k, v in value.iteritems():
@@ -297,9 +397,7 @@ class ComplexBaseField(BaseField):
# any _cls data so make it a generic reference allows
# us to dereference
meta = getattr(v, '_meta', {})
allow_inheritance = (
meta.get('allow_inheritance', ALLOW_INHERITANCE)
is True)
allow_inheritance = meta.get('allow_inheritance')
if not allow_inheritance and not self.field:
value_dict[k] = GenericReferenceField().to_mongo(v)
else:
@@ -307,22 +405,21 @@ class ComplexBaseField(BaseField):
value_dict[k] = DBRef(collection, v.pk)
elif hasattr(v, 'to_mongo'):
cls = v.__class__
val = v.to_mongo()
# If we its a document thats not inherited add _cls
if (isinstance(v, (Document, EmbeddedDocument))):
val = v.to_mongo(use_db_field, fields)
# If it's a document that is not inherited add _cls
if isinstance(v, (Document, EmbeddedDocument)):
val['_cls'] = cls.__name__
value_dict[k] = val
else:
value_dict[k] = self.to_mongo(v)
value_dict[k] = self.to_mongo(v, use_db_field, fields)
if is_list: # Convert back to a list
return [v for k, v in sorted(value_dict.items(),
return [v for _, v in sorted(value_dict.items(),
key=operator.itemgetter(0))]
return value_dict
def validate(self, value):
"""If field is provided ensure the value is valid.
"""
"""If field is provided ensure the value is valid."""
errors = {}
if self.field:
if hasattr(value, 'iteritems') or hasattr(value, 'items'):
@@ -332,9 +429,9 @@ class ComplexBaseField(BaseField):
for k, v in sequence:
try:
self.field._validate(v)
except ValidationError, error:
except ValidationError as error:
errors[k] = error.errors or error
except (ValueError, AssertionError), error:
except (ValueError, AssertionError) as error:
errors[k] = error
if errors:
@@ -358,35 +455,25 @@ class ComplexBaseField(BaseField):
self.field.owner_document = owner_document
self._owner_document = owner_document
def _get_owner_document(self, owner_document):
self._owner_document = owner_document
owner_document = property(_get_owner_document, _set_owner_document)
@property
def _dereference(self,):
if not self.__dereference:
DeReference = _import_class("DeReference")
self.__dereference = DeReference() # Cached
return self.__dereference
class ObjectIdField(BaseField):
"""A field wrapper around MongoDB's ObjectIds.
"""
"""A field wrapper around MongoDB's ObjectIds."""
def to_python(self, value):
if not isinstance(value, ObjectId):
value = ObjectId(value)
try:
if not isinstance(value, ObjectId):
value = ObjectId(value)
except Exception:
pass
return value
def to_mongo(self, value):
if not isinstance(value, ObjectId):
try:
return ObjectId(unicode(value))
except Exception, e:
return ObjectId(six.text_type(value))
except Exception as e:
# e.message attribute has been deprecated since Python 2.6
self.error(unicode(e))
self.error(six.text_type(e))
return value
def prepare_query_value(self, op, value):
@@ -394,36 +481,37 @@ class ObjectIdField(BaseField):
def validate(self, value):
try:
ObjectId(unicode(value))
except:
ObjectId(six.text_type(value))
except Exception:
self.error('Invalid Object ID')
class GeoJsonBaseField(BaseField):
"""A geo json field storing a geojson style object.
.. versionadded:: 0.8
"""
_geo_index = pymongo.GEOSPHERE
_type = "GeoBase"
_type = 'GeoBase'
def __init__(self, auto_index=True, *args, **kwargs):
"""
:param auto_index: Automatically create a "2dsphere" index. Defaults
to `True`.
:param bool auto_index: Automatically create a '2dsphere' index.\
Defaults to `True`.
"""
self._name = "%sField" % self._type
self._name = '%sField' % self._type
if not auto_index:
self._geo_index = False
super(GeoJsonBaseField, self).__init__(*args, **kwargs)
def validate(self, value):
"""Validate the GeoJson object based on its type
"""
"""Validate the GeoJson object based on its type."""
if isinstance(value, dict):
if set(value.keys()) == set(['type', 'coordinates']):
if set(value.keys()) == {'type', 'coordinates'}:
if value['type'] != self._type:
self.error('%s type must be "%s"' % (self._name, self._type))
self.error('%s type must be "%s"' %
(self._name, self._type))
return self.validate(value['coordinates'])
else:
self.error('%s can only accept a valid GeoJson dictionary'
@@ -433,20 +521,20 @@ class GeoJsonBaseField(BaseField):
self.error('%s can only accept lists of [x, y]' % self._name)
return
validate = getattr(self, "_validate_%s" % self._type.lower())
validate = getattr(self, '_validate_%s' % self._type.lower())
error = validate(value)
if error:
self.error(error)
def _validate_polygon(self, value):
def _validate_polygon(self, value, top_level=True):
if not isinstance(value, (list, tuple)):
return 'Polygons must contain list of linestrings'
# Quick and dirty validator
try:
value[0][0][0]
except:
return "Invalid Polygon must contain at least one valid linestring"
except (TypeError, IndexError):
return 'Invalid Polygon must contain at least one valid linestring'
errors = []
for val in value:
@@ -456,18 +544,21 @@ class GeoJsonBaseField(BaseField):
if error and error not in errors:
errors.append(error)
if errors:
return "Invalid Polygon:\n%s" % ", ".join(errors)
if top_level:
return 'Invalid Polygon:\n%s' % ', '.join(errors)
else:
return '%s' % ', '.join(errors)
def _validate_linestring(self, value, top_level=True):
"""Validates a linestring"""
"""Validate a linestring."""
if not isinstance(value, (list, tuple)):
return 'LineStrings must contain list of coordinate pairs'
# Quick and dirty validator
try:
value[0][0]
except:
return "Invalid LineString must contain at least one valid point"
except (TypeError, IndexError):
return 'Invalid LineString must contain at least one valid point'
errors = []
for val in value:
@@ -476,21 +567,81 @@ class GeoJsonBaseField(BaseField):
errors.append(error)
if errors:
if top_level:
return "Invalid LineString:\n%s" % ", ".join(errors)
return 'Invalid LineString:\n%s' % ', '.join(errors)
else:
return "%s" % ", ".join(errors)
return '%s' % ', '.join(errors)
def _validate_point(self, value):
"""Validate each set of coords"""
if not isinstance(value, (list, tuple)):
return 'Points must be a list of coordinate pairs'
elif not len(value) == 2:
return "Value (%s) must be a two-dimensional point" % repr(value)
return 'Value (%s) must be a two-dimensional point' % repr(value)
elif (not isinstance(value[0], (float, int)) or
not isinstance(value[1], (float, int))):
return "Both values (%s) in point must be float or int" % repr(value)
return 'Both values (%s) in point must be float or int' % repr(value)
def _validate_multipoint(self, value):
if not isinstance(value, (list, tuple)):
return 'MultiPoint must be a list of Point'
# Quick and dirty validator
try:
value[0][0]
except (TypeError, IndexError):
return 'Invalid MultiPoint must contain at least one valid point'
errors = []
for point in value:
error = self._validate_point(point)
if error and error not in errors:
errors.append(error)
if errors:
return '%s' % ', '.join(errors)
def _validate_multilinestring(self, value, top_level=True):
if not isinstance(value, (list, tuple)):
return 'MultiLineString must be a list of LineString'
# Quick and dirty validator
try:
value[0][0][0]
except (TypeError, IndexError):
return 'Invalid MultiLineString must contain at least one valid linestring'
errors = []
for linestring in value:
error = self._validate_linestring(linestring, False)
if error and error not in errors:
errors.append(error)
if errors:
if top_level:
return 'Invalid MultiLineString:\n%s' % ', '.join(errors)
else:
return '%s' % ', '.join(errors)
def _validate_multipolygon(self, value):
if not isinstance(value, (list, tuple)):
return 'MultiPolygon must be a list of Polygon'
# Quick and dirty validator
try:
value[0][0][0][0]
except (TypeError, IndexError):
return 'Invalid MultiPolygon must contain at least one valid Polygon'
errors = []
for polygon in value:
error = self._validate_polygon(polygon, False)
if error and error not in errors:
errors.append(error)
if errors:
return 'Invalid MultiPolygon:\n%s' % ', '.join(errors)
def to_mongo(self, value):
if isinstance(value, dict):
return value
return SON([("type", self._type), ("coordinates", value)])
return SON([('type', self._type), ('coordinates', value)])

View File

@@ -1,34 +1,34 @@
import warnings
import pymongo
import six
from mongoengine.base.common import _document_registry
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
from mongoengine.common import _import_class
from mongoengine.errors import InvalidDocumentError
from mongoengine.python_support import PY3
from mongoengine.queryset import (DO_NOTHING, DoesNotExist,
MultipleObjectsReturned,
QuerySet, QuerySetManager)
QuerySetManager)
from mongoengine.base.common import _document_registry, ALLOW_INHERITANCE
from mongoengine.base.fields import BaseField, ComplexBaseField, ObjectIdField
__all__ = ('DocumentMetaclass', 'TopLevelDocumentMetaclass')
class DocumentMetaclass(type):
"""Metaclass for all documents.
"""
"""Metaclass for all documents."""
def __new__(cls, name, bases, attrs):
flattened_bases = cls._get_bases(bases)
super_new = super(DocumentMetaclass, cls).__new__
# TODO lower complexity of this method
def __new__(mcs, name, bases, attrs):
flattened_bases = mcs._get_bases(bases)
super_new = super(DocumentMetaclass, mcs).__new__
# If a base class just call super
metaclass = attrs.get('my_metaclass')
if metaclass and issubclass(metaclass, DocumentMetaclass):
return super_new(cls, name, bases, attrs)
return super_new(mcs, name, bases, attrs)
attrs['_is_document'] = attrs.get('_is_document', False)
attrs['_cached_reference_fields'] = []
# EmbeddedDocuments could have meta data for inheritance
if 'meta' in attrs:
@@ -44,6 +44,12 @@ class DocumentMetaclass(type):
elif hasattr(base, '_meta'):
meta.merge(base._meta)
attrs['_meta'] = meta
attrs['_meta']['abstract'] = False # 789: EmbeddedDocument shouldn't inherit abstract
# If allow_inheritance is True, add a "_cls" string field to the attrs
if attrs['_meta'].get('allow_inheritance'):
StringField = _import_class('StringField')
attrs['_cls'] = StringField()
# Handle document Fields
@@ -83,19 +89,21 @@ class DocumentMetaclass(type):
# Ensure no duplicate db_fields
duplicate_db_fields = [k for k, v in field_names.items() if v > 1]
if duplicate_db_fields:
msg = ("Multiple db_fields defined for: %s " %
", ".join(duplicate_db_fields))
msg = ('Multiple db_fields defined for: %s ' %
', '.join(duplicate_db_fields))
raise InvalidDocumentError(msg)
# Set _fields and db_field maps
attrs['_fields'] = doc_fields
attrs['_db_field_map'] = dict([(k, getattr(v, 'db_field', k))
for k, v in doc_fields.iteritems()])
attrs['_db_field_map'] = {k: getattr(v, 'db_field', k)
for k, v in doc_fields.items()}
attrs['_reverse_db_field_map'] = {
v: k for k, v in attrs['_db_field_map'].items()
}
attrs['_fields_ordered'] = tuple(i[1] for i in sorted(
(v.creation_counter, v.name)
for v in doc_fields.itervalues()))
attrs['_reverse_db_field_map'] = dict(
(v, k) for k, v in attrs['_db_field_map'].iteritems())
#
# Set document hierarchy
@@ -104,18 +112,17 @@ class DocumentMetaclass(type):
class_name = [name]
for base in flattened_bases:
if (not getattr(base, '_is_base_cls', True) and
not getattr(base, '_meta', {}).get('abstract', True)):
# Collate heirarchy for _cls and _subclasses
not getattr(base, '_meta', {}).get('abstract', True)):
# Collate hierarchy for _cls and _subclasses
class_name.append(base.__name__)
if hasattr(base, '_meta'):
# Warn if allow_inheritance isn't set and prevent
# inheritance of classes where inheritance is set to False
allow_inheritance = base._meta.get('allow_inheritance',
ALLOW_INHERITANCE)
if (allow_inheritance is not True and
not base._meta.get('abstract')):
raise ValueError('Document %s may not be subclassed' %
allow_inheritance = base._meta.get('allow_inheritance')
if not allow_inheritance and not base._meta.get('abstract'):
raise ValueError('Document %s may not be subclassed. '
'To enable inheritance, use the "allow_inheritance" meta attribute.' %
base.__name__)
# Get superclasses from last base superclass
@@ -132,15 +139,16 @@ class DocumentMetaclass(type):
attrs['_types'] = attrs['_subclasses'] # TODO depreciate _types
# Create the new_class
new_class = super_new(cls, name, bases, attrs)
new_class = super_new(mcs, name, bases, attrs)
# Set _subclasses
for base in document_bases:
if _cls not in base._subclasses:
base._subclasses += (_cls,)
base._types = base._subclasses # TODO depreciate _types
base._types = base._subclasses # TODO depreciate _types
Document, EmbeddedDocument, DictField = cls._import_classes()
(Document, EmbeddedDocument, DictField,
CachedReferenceField) = mcs._import_classes()
if issubclass(new_class, Document):
new_class._collection = None
@@ -155,8 +163,8 @@ class DocumentMetaclass(type):
# module continues to use im_func and im_self, so the code below
# copies __func__ into im_func and __self__ into im_self for
# classmethod objects in Document derived classes.
if PY3:
for key, val in new_class.__dict__.items():
if six.PY3:
for val in new_class.__dict__.values():
if isinstance(val, classmethod):
f = val.__get__(new_class)
if hasattr(f, '__func__') and not hasattr(f, 'im_func'):
@@ -167,15 +175,30 @@ class DocumentMetaclass(type):
# Handle delete rules
for field in new_class._fields.itervalues():
f = field
f.owner_document = new_class
if f.owner_document is None:
f.owner_document = new_class
delete_rule = getattr(f, 'reverse_delete_rule', DO_NOTHING)
if isinstance(f, CachedReferenceField):
if issubclass(new_class, EmbeddedDocument):
raise InvalidDocumentError('CachedReferenceFields is not '
'allowed in EmbeddedDocuments')
if not f.document_type:
raise InvalidDocumentError(
'Document is not available to sync')
if f.auto_sync:
f.start_listener()
f.document_type._cached_reference_fields.append(f)
if isinstance(f, ComplexBaseField) and hasattr(f, 'field'):
delete_rule = getattr(f.field,
'reverse_delete_rule',
DO_NOTHING)
if isinstance(f, DictField) and delete_rule != DO_NOTHING:
msg = ("Reverse delete rules are not supported "
"for %s (field: %s)" %
msg = ('Reverse delete rules are not supported '
'for %s (field: %s)' %
(field.__class__.__name__, field.name))
raise InvalidDocumentError(msg)
@@ -183,47 +206,45 @@ class DocumentMetaclass(type):
if delete_rule != DO_NOTHING:
if issubclass(new_class, EmbeddedDocument):
msg = ("Reverse delete rules are not supported for "
"EmbeddedDocuments (field: %s)" % field.name)
msg = ('Reverse delete rules are not supported for '
'EmbeddedDocuments (field: %s)' % field.name)
raise InvalidDocumentError(msg)
f.document_type.register_delete_rule(new_class,
field.name, delete_rule)
if (field.name and hasattr(Document, field.name) and
EmbeddedDocument not in new_class.mro()):
msg = ("%s is a document method and not a valid "
"field name" % field.name)
EmbeddedDocument not in new_class.mro()):
msg = ('%s is a document method and not a valid '
'field name' % field.name)
raise InvalidDocumentError(msg)
return new_class
def add_to_class(self, name, value):
setattr(self, name, value)
@classmethod
def _get_bases(cls, bases):
def _get_bases(mcs, bases):
if isinstance(bases, BasesTuple):
return bases
seen = []
bases = cls.__get_bases(bases)
bases = mcs.__get_bases(bases)
unique_bases = (b for b in bases if not (b in seen or seen.append(b)))
return BasesTuple(unique_bases)
@classmethod
def __get_bases(cls, bases):
def __get_bases(mcs, bases):
for base in bases:
if base is object:
continue
yield base
for child_base in cls.__get_bases(base.__bases__):
for child_base in mcs.__get_bases(base.__bases__):
yield child_base
@classmethod
def _import_classes(cls):
def _import_classes(mcs):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
DictField = _import_class('DictField')
return (Document, EmbeddedDocument, DictField)
CachedReferenceField = _import_class('CachedReferenceField')
return Document, EmbeddedDocument, DictField, CachedReferenceField
class TopLevelDocumentMetaclass(DocumentMetaclass):
@@ -231,12 +252,12 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
collection in the database.
"""
def __new__(cls, name, bases, attrs):
flattened_bases = cls._get_bases(bases)
super_new = super(TopLevelDocumentMetaclass, cls).__new__
def __new__(mcs, name, bases, attrs):
flattened_bases = mcs._get_bases(bases)
super_new = super(TopLevelDocumentMetaclass, mcs).__new__
# Set default _meta data if base class, otherwise get user defined meta
if (attrs.get('my_metaclass') == TopLevelDocumentMetaclass):
if attrs.get('my_metaclass') == TopLevelDocumentMetaclass:
# defaults
attrs['_meta'] = {
'abstract': True,
@@ -249,13 +270,18 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
'index_drop_dups': False,
'index_opts': None,
'delete_rules': None,
# allow_inheritance can be True, False, and None. True means
# "allow inheritance", False means "don't allow inheritance",
# None means "do whatever your parent does, or don't allow
# inheritance if you're a top-level class".
'allow_inheritance': None,
}
attrs['_is_base_cls'] = True
attrs['_meta'].update(attrs.get('meta', {}))
else:
attrs['_meta'] = attrs.get('meta', {})
# Explictly set abstract to false unless set
# Explicitly set abstract to false unless set
attrs['_meta']['abstract'] = attrs['_meta'].get('abstract', False)
attrs['_is_base_cls'] = False
@@ -270,28 +296,28 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
# Clean up top level meta
if 'meta' in attrs:
del(attrs['meta'])
del attrs['meta']
# Find the parent document class
parent_doc_cls = [b for b in flattened_bases
if b.__class__ == TopLevelDocumentMetaclass]
if b.__class__ == TopLevelDocumentMetaclass]
parent_doc_cls = None if not parent_doc_cls else parent_doc_cls[0]
# Prevent classes setting collection different to their parents
# If parent wasn't an abstract class
if (parent_doc_cls and 'collection' in attrs.get('_meta', {})
and not parent_doc_cls._meta.get('abstract', True)):
msg = "Trying to set a collection on a subclass (%s)" % name
warnings.warn(msg, SyntaxWarning)
del(attrs['_meta']['collection'])
if (parent_doc_cls and 'collection' in attrs.get('_meta', {}) and
not parent_doc_cls._meta.get('abstract', True)):
msg = 'Trying to set a collection on a subclass (%s)' % name
warnings.warn(msg, SyntaxWarning)
del attrs['_meta']['collection']
# Ensure abstract documents have abstract bases
if attrs.get('_is_base_cls') or attrs['_meta'].get('abstract'):
if (parent_doc_cls and
not parent_doc_cls._meta.get('abstract', False)):
msg = "Abstract document cannot have non-abstract base"
not parent_doc_cls._meta.get('abstract', False)):
msg = 'Abstract document cannot have non-abstract base'
raise ValueError(msg)
return super_new(cls, name, bases, attrs)
return super_new(mcs, name, bases, attrs)
# Merge base class metas.
# Uses a special MetaDict that handles various merging rules
@@ -305,19 +331,23 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
# Set collection in the meta if its callable
if (getattr(base, '_is_document', False) and
not base._meta.get('abstract')):
not base._meta.get('abstract')):
collection = meta.get('collection', None)
if callable(collection):
meta['collection'] = collection(base)
meta.merge(attrs.get('_meta', {})) # Top level meta
# Only simple classes (direct subclasses of Document)
# may set allow_inheritance to False
# Only simple classes (i.e. direct subclasses of Document) may set
# allow_inheritance to False. If the base Document allows inheritance,
# none of its subclasses can override allow_inheritance to False.
simple_class = all([b._meta.get('abstract')
for b in flattened_bases if hasattr(b, '_meta')])
if (not simple_class and meta['allow_inheritance'] is False and
not meta['abstract']):
if (
not simple_class and
meta['allow_inheritance'] is False and
not meta['abstract']
):
raise ValueError('Only direct subclasses of Document may set '
'"allow_inheritance" to False')
@@ -328,7 +358,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
attrs['_meta'] = meta
# Call super and get the new class
new_class = super_new(cls, name, bases, attrs)
new_class = super_new(mcs, name, bases, attrs)
meta = new_class._meta
@@ -358,11 +388,20 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
new_class.id = field
# Set primary key if not defined by the document
new_class._auto_id_field = getattr(parent_doc_cls,
'_auto_id_field', False)
if not new_class._meta.get('id_field'):
new_class._meta['id_field'] = 'id'
new_class._fields['id'] = ObjectIdField(db_field='_id')
new_class._fields['id'].name = 'id'
new_class.id = new_class._fields['id']
# After 0.10, find not existing names, instead of overwriting
id_name, id_db_name = mcs.get_auto_id_names(new_class)
new_class._auto_id_field = True
new_class._meta['id_field'] = id_name
new_class._fields[id_name] = ObjectIdField(db_field=id_db_name)
new_class._fields[id_name].name = id_name
new_class.id = new_class._fields[id_name]
new_class._db_field_map[id_name] = id_db_name
new_class._reverse_db_field_map[id_db_name] = id_name
# Prepend id field to _fields_ordered
new_class._fields_ordered = (id_name, ) + new_class._fields_ordered
# Merge in exceptions with parent hierarchy
exceptions_to_merge = (DoesNotExist, MultipleObjectsReturned)
@@ -370,13 +409,27 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
for exc in exceptions_to_merge:
name = exc.__name__
parents = tuple(getattr(base, name) for base in flattened_bases
if hasattr(base, name)) or (exc,)
if hasattr(base, name)) or (exc,)
# Create new exception and set to new_class
exception = type(name, parents, {'__module__': module})
setattr(new_class, name, exception)
return new_class
@classmethod
def get_auto_id_names(mcs, new_class):
id_name, id_db_name = ('id', '_id')
if id_name not in new_class._fields and \
id_db_name not in (v.db_field for v in new_class._fields.values()):
return id_name, id_db_name
id_basename, id_db_basename, i = 'auto_id', '_auto_id', 0
while id_name in new_class._fields or \
id_db_name in (v.db_field for v in new_class._fields.values()):
id_name = '{0}_{1}'.format(id_basename, i)
id_db_name = '{0}_{1}'.format(id_db_basename, i)
i += 1
return id_name, id_db_name
class MetaDict(dict):
"""Custom dictionary for meta classes.

22
mongoengine/base/utils.py Normal file
View File

@@ -0,0 +1,22 @@
import re
class LazyRegexCompiler(object):
"""Descriptor to allow lazy compilation of regex"""
def __init__(self, pattern, flags=0):
self._pattern = pattern
self._flags = flags
self._compiled_regex = None
@property
def compiled_regex(self):
if self._compiled_regex is None:
self._compiled_regex = re.compile(self._pattern, self._flags)
return self._compiled_regex
def __get__(self, instance, owner):
return self.compiled_regex
def __set__(self, instance, value):
raise AttributeError("Can not set attribute LazyRegexCompiler")

View File

@@ -1,22 +1,43 @@
_class_registry_cache = {}
_field_list_cache = []
def _import_class(cls_name):
"""Cached mechanism for imports"""
"""Cache mechanism for imports.
Due to complications of circular imports mongoengine needs to do lots of
inline imports in functions. This is inefficient as classes are
imported repeated throughout the mongoengine code. This is
compounded by some recursive functions requiring inline imports.
:mod:`mongoengine.common` provides a single point to import all these
classes. Circular imports aren't an issue as it dynamically imports the
class when first needed. Subsequent calls to the
:func:`~mongoengine.common._import_class` can then directly retrieve the
class from the :data:`mongoengine.common._class_registry_cache`.
"""
if cls_name in _class_registry_cache:
return _class_registry_cache.get(cls_name)
doc_classes = ('Document', 'DynamicEmbeddedDocument', 'EmbeddedDocument',
'MapReduceDocument')
field_classes = ('DictField', 'DynamicField', 'EmbeddedDocumentField',
'FileField', 'GenericReferenceField',
'GenericEmbeddedDocumentField', 'GeoPointField',
'PointField', 'LineStringField', 'PolygonField',
'ReferenceField', 'StringField', 'ComplexBaseField')
# Field Classes
if not _field_list_cache:
from mongoengine.fields import __all__ as fields
_field_list_cache.extend(fields)
from mongoengine.base.fields import __all__ as fields
_field_list_cache.extend(fields)
field_classes = _field_list_cache
queryset_classes = ('OperationError',)
deref_classes = ('DeReference',)
if cls_name in doc_classes:
if cls_name == 'BaseDocument':
from mongoengine.base import document as module
import_classes = ['BaseDocument']
elif cls_name in doc_classes:
from mongoengine import document as module
import_classes = doc_classes
elif cls_name in field_classes:

View File

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

View File

@@ -1,14 +1,15 @@
from contextlib import contextmanager
from pymongo.write_concern import WriteConcern
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.queryset import QuerySet
__all__ = ("switch_db", "switch_collection", "no_dereference",
"no_sub_classes", "query_counter")
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
'no_sub_classes', 'query_counter', 'set_write_concern')
class switch_db(object):
""" switch_db alias context manager.
"""switch_db alias context manager.
Example ::
@@ -19,15 +20,14 @@ class switch_db(object):
class Group(Document):
name = StringField()
Group(name="test").save() # Saves in the default db
Group(name='test').save() # Saves in the default db
with switch_db(Group, 'testdb-1') as Group:
Group(name="hello testdb!").save() # Saves in testdb-1
Group(name='hello testdb!').save() # Saves in testdb-1
"""
def __init__(self, cls, db_alias):
""" Construct the switch_db context manager
"""Construct the switch_db context manager
:param cls: the class to change the registered db
:param db_alias: the name of the specific database to use
@@ -35,37 +35,36 @@ class switch_db(object):
self.cls = cls
self.collection = cls._get_collection()
self.db_alias = db_alias
self.ori_db_alias = cls._meta.get("db_alias", DEFAULT_CONNECTION_NAME)
self.ori_db_alias = cls._meta.get('db_alias', DEFAULT_CONNECTION_NAME)
def __enter__(self):
""" change the db_alias and clear the cached collection """
self.cls._meta["db_alias"] = self.db_alias
"""Change the db_alias and clear the cached collection."""
self.cls._meta['db_alias'] = self.db_alias
self.cls._collection = None
return self.cls
def __exit__(self, t, value, traceback):
""" Reset the db_alias and collection """
self.cls._meta["db_alias"] = self.ori_db_alias
"""Reset the db_alias and collection."""
self.cls._meta['db_alias'] = self.ori_db_alias
self.cls._collection = self.collection
class switch_collection(object):
""" switch_collection alias context manager.
"""switch_collection alias context manager.
Example ::
class Group(Document):
name = StringField()
Group(name="test").save() # Saves in the default db
Group(name='test').save() # Saves in the default db
with switch_collection(Group, 'group1') as Group:
Group(name="hello testdb!").save() # Saves in group1 collection
Group(name='hello testdb!').save() # Saves in group1 collection
"""
def __init__(self, cls, collection_name):
""" Construct the switch_collection context manager
"""Construct the switch_collection context manager.
:param cls: the class to change the registered db
:param collection_name: the name of the collection to use
@@ -76,7 +75,7 @@ class switch_collection(object):
self.collection_name = collection_name
def __enter__(self):
""" change the _get_collection_name and clear the cached collection """
"""Change the _get_collection_name and clear the cached collection."""
@classmethod
def _get_collection_name(cls):
@@ -87,24 +86,23 @@ class switch_collection(object):
return self.cls
def __exit__(self, t, value, traceback):
""" Reset the collection """
"""Reset the collection."""
self.cls._collection = self.ori_collection
self.cls._get_collection_name = self.ori_get_collection_name
class no_dereference(object):
""" no_dereference context manager.
"""no_dereference context manager.
Turns off all dereferencing in Documents for the duration of the context
manager::
with no_dereference(Group) as Group:
Group.objects.find()
"""
def __init__(self, cls):
""" Construct the no_dereference context manager.
"""Construct the no_dereference context manager.
:param cls: the class to turn dereferencing off on
"""
@@ -120,107 +118,130 @@ class no_dereference(object):
ComplexBaseField))]
def __enter__(self):
""" change the objects default and _auto_dereference values"""
"""Change the objects default and _auto_dereference values."""
for field in self.deref_fields:
self.cls._fields[field]._auto_dereference = False
return self.cls
def __exit__(self, t, value, traceback):
""" Reset the default and _auto_dereference values"""
"""Reset the default and _auto_dereference values."""
for field in self.deref_fields:
self.cls._fields[field]._auto_dereference = True
return self.cls
class no_sub_classes(object):
""" no_sub_classes context manager.
"""no_sub_classes context manager.
Only returns instances of this class and no sub (inherited) classes::
with no_sub_classes(Group) as Group:
Group.objects.find()
"""
def __init__(self, cls):
""" Construct the no_sub_classes context manager.
"""Construct the no_sub_classes context manager.
:param cls: the class to turn querying sub classes on
"""
self.cls = cls
self.cls_initial_subclasses = None
def __enter__(self):
""" change the objects default and _auto_dereference values"""
self.cls._all_subclasses = self.cls._subclasses
self.cls._subclasses = (self.cls,)
"""Change the objects default and _auto_dereference values."""
self.cls_initial_subclasses = self.cls._subclasses
self.cls._subclasses = (self.cls._class_name,)
return self.cls
def __exit__(self, t, value, traceback):
""" Reset the default and _auto_dereference values"""
self.cls._subclasses = self.cls._all_subclasses
delattr(self.cls, '_all_subclasses')
return self.cls
class QuerySetNoDeRef(QuerySet):
"""Special no_dereference QuerySet"""
def __dereference(items, max_depth=1, instance=None, name=None):
return items
"""Reset the default and _auto_dereference values."""
self.cls._subclasses = self.cls_initial_subclasses
class query_counter(object):
""" Query_counter context manager to get the number of queries. """
"""Query_counter context manager to get the number of queries.
This works by updating the `profiling_level` of the database so that all queries get logged,
resetting the db.system.profile collection at the beginnig of the context and counting the new entries.
This was designed for debugging purpose. In fact it is a global counter so queries issued by other threads/processes
can interfere with it
Be aware that:
- Iterating over large amount of documents (>101) makes pymongo issue `getmore` queries to fetch the next batch of
documents (https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/#cursor-batches)
- Some queries are ignored by default by the counter (killcursors, db.system.indexes)
"""
def __init__(self):
""" Construct the query_counter. """
self.counter = 0
"""Construct the query_counter
"""
self.db = get_db()
self.initial_profiling_level = None
self._ctx_query_counter = 0 # number of queries issued by the context
def __enter__(self):
""" On every with block we need to drop the profile collection. """
self._ignored_query = {
'ns':
{'$ne': '%s.system.indexes' % self.db.name},
'op': # MONGODB < 3.2
{'$ne': 'killcursors'},
'command.killCursors': # MONGODB >= 3.2
{'$exists': False}
}
def _turn_on_profiling(self):
self.initial_profiling_level = self.db.profiling_level()
self.db.set_profiling_level(0)
self.db.system.profile.drop()
self.db.set_profiling_level(2)
def _resets_profiling(self):
self.db.set_profiling_level(self.initial_profiling_level)
def __enter__(self):
self._turn_on_profiling()
return self
def __exit__(self, t, value, traceback):
""" Reset the profiling level. """
self.db.set_profiling_level(0)
self._resets_profiling()
def __eq__(self, value):
""" == Compare querycounter. """
return value == self._get_count()
counter = self._get_count()
return value == counter
def __ne__(self, value):
""" != Compare querycounter. """
return not self.__eq__(value)
def __lt__(self, value):
""" < Compare querycounter. """
return self._get_count() < value
def __le__(self, value):
""" <= Compare querycounter. """
return self._get_count() <= value
def __gt__(self, value):
""" > Compare querycounter. """
return self._get_count() > value
def __ge__(self, value):
""" >= Compare querycounter. """
return self._get_count() >= value
def __int__(self):
""" int representation. """
return self._get_count()
def __repr__(self):
""" repr query_counter as the number of queries. """
"""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
"""Get the number of queries by counting the current number of entries in db.system.profile
and substracting the queries issued by this context. In fact everytime this is called, 1 query is
issued so we need to balance that
"""
count = self.db.system.profile.find(self._ignored_query).count() - self._ctx_query_counter
self._ctx_query_counter += 1 # Account for the query we just issued to gather the information
return count
@contextmanager
def set_write_concern(collection, write_concerns):
combined_concerns = dict(collection.write_concern.document.items())
combined_concerns.update(write_concerns)
yield collection.with_options(write_concern=WriteConcern(**combined_concerns))

View File

@@ -1,18 +1,20 @@
from bson import DBRef, SON
import six
from base import (BaseDict, BaseList, TopLevelDocumentMetaclass, get_document)
from fields import (ReferenceField, ListField, DictField, MapField)
from connection import get_db
from queryset import QuerySet
from document import Document
from mongoengine.base import (BaseDict, BaseList, EmbeddedDocumentList,
TopLevelDocumentMetaclass, get_document)
from mongoengine.base.datastructures import LazyReference
from mongoengine.connection import get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.fields import DictField, ListField, MapField, ReferenceField
from mongoengine.queryset import QuerySet
class DeReference(object):
def __call__(self, items, max_depth=1, instance=None, name=None):
"""
Cheaply dereferences the items to a set depth.
Also handles the convertion of complex data types.
Also handles the conversion of complex data types.
:param items: The iterable (dict, list, queryset) to be dereferenced.
:param max_depth: The maximum depth to recurse to
@@ -22,7 +24,7 @@ class DeReference(object):
:class:`~mongoengine.base.ComplexBaseField`
:param get: A boolean determining if being called by __get__
"""
if items is None or isinstance(items, basestring):
if items is None or isinstance(items, six.string_types):
return items
# cheapest way to convert a queryset to a list
@@ -33,9 +35,10 @@ class DeReference(object):
self.max_depth = max_depth
doc_type = None
if instance and isinstance(instance, (Document, TopLevelDocumentMetaclass)):
if instance and isinstance(instance, (Document, EmbeddedDocument,
TopLevelDocumentMetaclass)):
doc_type = instance._fields.get(name)
if hasattr(doc_type, 'field'):
while hasattr(doc_type, 'field'):
doc_type = doc_type.field
if isinstance(doc_type, ReferenceField):
@@ -45,20 +48,30 @@ class DeReference(object):
if is_list and all([i.__class__ == doc_type for i in items]):
return items
elif not is_list and all([i.__class__ == doc_type
for i in items.values()]):
elif not is_list and all(
[i.__class__ == doc_type for i in items.values()]):
return items
elif not field.dbref:
if not hasattr(items, 'items'):
items = [field.to_python(v)
if not isinstance(v, (DBRef, Document)) else v
for v in items]
def _get_items(items):
new_items = []
for v in items:
if isinstance(v, list):
new_items.append(_get_items(v))
elif not isinstance(v, (DBRef, Document)):
new_items.append(field.to_python(v))
else:
new_items.append(v)
return new_items
items = _get_items(items)
else:
items = dict([
(k, field.to_python(v))
if not isinstance(v, (DBRef, Document)) else (k, v)
for k, v in items.iteritems()]
)
items = {
k: (v if isinstance(v, (DBRef, Document))
else field.to_python(v))
for k, v in items.iteritems()
}
self.reference_map = self._find_references(items)
self.object_map = self._fetch_objects(doc_type=doc_type)
@@ -76,36 +89,42 @@ class DeReference(object):
return reference_map
# Determine the iterator to use
if not hasattr(items, 'items'):
iterator = enumerate(items)
if isinstance(items, dict):
iterator = items.values()
else:
iterator = items.iteritems()
iterator = items
# Recursively find dbreferences
depth += 1
for k, item in iterator:
if isinstance(item, Document):
for item in iterator:
if isinstance(item, (Document, EmbeddedDocument)):
for field_name, field in item._fields.iteritems():
v = item._data.get(field_name, None)
if isinstance(v, (DBRef)):
reference_map.setdefault(field.document_type, []).append(v.id)
if isinstance(v, LazyReference):
# LazyReference inherits DBRef but should not be dereferenced here !
continue
elif isinstance(v, DBRef):
reference_map.setdefault(field.document_type, set()).add(v.id)
elif isinstance(v, (dict, SON)) and '_ref' in v:
reference_map.setdefault(get_document(v['_cls']), []).append(v['_ref'].id)
reference_map.setdefault(get_document(v['_cls']), set()).add(v['_ref'].id)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
field_cls = getattr(getattr(field, 'field', None), 'document_type', None)
references = self._find_references(v, depth)
for key, refs in references.iteritems():
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
key = field_cls
reference_map.setdefault(key, []).extend(refs)
elif isinstance(item, (DBRef)):
reference_map.setdefault(item.collection, []).append(item.id)
reference_map.setdefault(key, set()).update(refs)
elif isinstance(item, LazyReference):
# LazyReference inherits DBRef but should not be dereferenced here !
continue
elif isinstance(item, DBRef):
reference_map.setdefault(item.collection, set()).add(item.id)
elif isinstance(item, (dict, SON)) and '_ref' in item:
reference_map.setdefault(get_document(item['_cls']), []).append(item['_ref'].id)
reference_map.setdefault(get_document(item['_cls']), set()).add(item['_ref'].id)
elif isinstance(item, (dict, list, tuple)) and depth - 1 <= self.max_depth:
references = self._find_references(item, depth - 1)
for key, refs in references.iteritems():
reference_map.setdefault(key, []).extend(refs)
reference_map.setdefault(key, set()).update(refs)
return reference_map
@@ -113,34 +132,43 @@ class DeReference(object):
"""Fetch all references and convert to their document objects
"""
object_map = {}
for col, dbrefs in self.reference_map.iteritems():
keys = object_map.keys()
refs = list(set([dbref for dbref in dbrefs if unicode(dbref).encode('utf-8') not in keys]))
if hasattr(col, 'objects'): # We have a document class for the refs
references = col.objects.in_bulk(refs)
for collection, dbrefs in self.reference_map.iteritems():
# we use getattr instead of hasattr because hasattr swallows any exception under python2
# so it could hide nasty things without raising exceptions (cfr bug #1688))
ref_document_cls_exists = (getattr(collection, 'objects', None) is not None)
if ref_document_cls_exists:
col_name = collection._get_collection_name()
refs = [dbref for dbref in dbrefs
if (col_name, dbref) not in object_map]
references = collection.objects.in_bulk(refs)
for key, doc in references.iteritems():
object_map[key] = doc
object_map[(col_name, key)] = doc
else: # Generic reference: use the refs data to convert to document
if isinstance(doc_type, (ListField, DictField, MapField,)):
if isinstance(doc_type, (ListField, DictField, MapField)):
continue
refs = [dbref for dbref in dbrefs
if (collection, dbref) not in object_map]
if doc_type:
references = doc_type._get_db()[col].find({'_id': {'$in': refs}})
references = doc_type._get_db()[collection].find({'_id': {'$in': refs}})
for ref in references:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
object_map[(collection, doc.id)] = doc
else:
references = get_db()[col].find({'_id': {'$in': refs}})
references = get_db()[collection].find({'_id': {'$in': refs}})
for ref in references:
if '_cls' in ref:
doc = get_document(ref["_cls"])._from_son(ref)
doc = get_document(ref['_cls'])._from_son(ref)
elif doc_type is None:
doc = get_document(
''.join(x.capitalize()
for x in col.split('_')))._from_son(ref)
for x in collection.split('_')))._from_son(ref)
else:
doc = doc_type._from_son(ref)
object_map[doc.id] = doc
object_map[(collection, doc.id)] = doc
return object_map
def _attach_objects(self, items, depth=0, instance=None, name=None):
@@ -166,14 +194,22 @@ class DeReference(object):
if isinstance(items, (dict, SON)):
if '_ref' in items:
return self.object_map.get(items['_ref'].id, items)
return self.object_map.get(
(items['_ref'].collection, items['_ref'].id), items)
elif '_cls' in items:
doc = get_document(items['_cls'])._from_son(items)
_cls = doc._data.pop('_cls', None)
del items['_cls']
doc._data = self._attach_objects(doc._data, depth, doc, None)
if _cls is not None:
doc._data['_cls'] = _cls
return doc
if not hasattr(items, 'items'):
is_list = True
list_type = BaseList
if isinstance(items, EmbeddedDocumentList):
list_type = EmbeddedDocumentList
as_tuple = isinstance(items, tuple)
iterator = enumerate(items)
data = []
@@ -191,25 +227,27 @@ class DeReference(object):
if k in self.object_map and not is_list:
data[k] = self.object_map[k]
elif isinstance(v, Document):
for field_name, field in v._fields.iteritems():
elif isinstance(v, (Document, EmbeddedDocument)):
for field_name in v._fields:
v = data[k]._data.get(field_name, None)
if isinstance(v, (DBRef)):
data[k]._data[field_name] = self.object_map.get(v.id, v)
if isinstance(v, DBRef):
data[k]._data[field_name] = self.object_map.get(
(v.collection, v.id), v)
elif isinstance(v, (dict, SON)) and '_ref' in v:
data[k]._data[field_name] = self.object_map.get(v['_ref'].id, v)
elif isinstance(v, dict) and depth <= self.max_depth:
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=name)
elif isinstance(v, (list, tuple)) and depth <= self.max_depth:
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=name)
data[k]._data[field_name] = self.object_map.get(
(v['_ref'].collection, v['_ref'].id), v)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = six.text_type('{0}.{1}.{2}').format(name, k, field_name)
data[k]._data[field_name] = self._attach_objects(v, depth, instance=instance, name=item_name)
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=name)
elif hasattr(v, 'id'):
data[k] = self.object_map.get(v.id, v)
item_name = '%s.%s' % (name, k) if name else name
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
elif isinstance(v, DBRef) and hasattr(v, 'id'):
data[k] = self.object_map.get((v.collection, v.id), v)
if instance and name:
if is_list:
return tuple(data) if as_tuple else BaseList(data, instance, name)
return tuple(data) if as_tuple else list_type(data, instance, name)
return BaseDict(data, instance, name)
depth += 1
return data

View File

@@ -1,402 +0,0 @@
from mongoengine import *
from django.utils.encoding import smart_str
from django.contrib.auth.models import _user_has_perm, _user_get_all_permissions, _user_has_module_perms
from django.db import models
from django.contrib.contenttypes.models import ContentTypeManager
from django.contrib import auth
from django.contrib.auth.models import AnonymousUser
from django.utils.translation import ugettext_lazy as _
try:
from django.contrib.auth.hashers import check_password, make_password
except ImportError:
"""Handle older versions of Django"""
from django.utils.hashcompat import md5_constructor, sha_constructor
def get_hexdigest(algorithm, salt, raw_password):
raw_password, salt = smart_str(raw_password), smart_str(salt)
if algorithm == 'md5':
return md5_constructor(salt + raw_password).hexdigest()
elif algorithm == 'sha1':
return sha_constructor(salt + raw_password).hexdigest()
raise ValueError('Got unknown password algorithm type in password')
def check_password(raw_password, password):
algo, salt, hash = password.split('$')
return hash == get_hexdigest(algo, salt, raw_password)
def make_password(raw_password):
from random import random
algo = 'sha1'
salt = get_hexdigest(algo, str(random()), str(random()))[:5]
hash = get_hexdigest(algo, salt, raw_password)
return '%s$%s$%s' % (algo, salt, hash)
from .utils import datetime_now
REDIRECT_FIELD_NAME = 'next'
class ContentType(Document):
name = StringField(max_length=100)
app_label = StringField(max_length=100)
model = StringField(max_length=100, verbose_name=_('python model class name'),
unique_with='app_label')
objects = ContentTypeManager()
class Meta:
verbose_name = _('content type')
verbose_name_plural = _('content types')
# db_table = 'django_content_type'
# ordering = ('name',)
# unique_together = (('app_label', 'model'),)
def __unicode__(self):
return self.name
def model_class(self):
"Returns the Python model class for this type of content."
from django.db import models
return models.get_model(self.app_label, self.model)
def get_object_for_this_type(self, **kwargs):
"""
Returns an object of this type for the keyword arguments given.
Basically, this is a proxy around this object_type's get_object() model
method. The ObjectNotExist exception, if thrown, will not be caught,
so code that calls this method should catch it.
"""
return self.model_class()._default_manager.using(self._state.db).get(**kwargs)
def natural_key(self):
return (self.app_label, self.model)
class SiteProfileNotAvailable(Exception):
pass
class PermissionManager(models.Manager):
def get_by_natural_key(self, codename, app_label, model):
return self.get(
codename=codename,
content_type=ContentType.objects.get_by_natural_key(app_label, model)
)
class Permission(Document):
"""The permissions system provides a way to assign permissions to specific
users and groups of users.
The permission system is used by the Django admin site, but may also be
useful in your own code. The Django admin site uses permissions as follows:
- The "add" permission limits the user's ability to view the "add"
form and add an object.
- The "change" permission limits a user's ability to view the change
list, view the "change" form and change an object.
- The "delete" permission limits the ability to delete an object.
Permissions are set globally per type of object, not per specific object
instance. It is possible to say "Mary may change news stories," but it's
not currently possible to say "Mary may change news stories, but only the
ones she created herself" or "Mary may only change news stories that have
a certain status or publication date."
Three basic permissions -- add, change and delete -- are automatically
created for each Django model.
"""
name = StringField(max_length=50, verbose_name=_('username'))
content_type = ReferenceField(ContentType)
codename = StringField(max_length=100, verbose_name=_('codename'))
# FIXME: don't access field of the other class
# unique_with=['content_type__app_label', 'content_type__model'])
objects = PermissionManager()
class Meta:
verbose_name = _('permission')
verbose_name_plural = _('permissions')
# unique_together = (('content_type', 'codename'),)
# ordering = ('content_type__app_label', 'content_type__model', 'codename')
def __unicode__(self):
return u"%s | %s | %s" % (
unicode(self.content_type.app_label),
unicode(self.content_type),
unicode(self.name))
def natural_key(self):
return (self.codename,) + self.content_type.natural_key()
natural_key.dependencies = ['contenttypes.contenttype']
class Group(Document):
"""Groups are a generic way of categorizing users to apply permissions,
or some other label, to those users. A user can belong to any number of
groups.
A user in a group automatically has all the permissions granted to that
group. For example, if the group Site editors has the permission
can_edit_home_page, any user in that group will have that permission.
Beyond permissions, groups are a convenient way to categorize users to
apply some label, or extended functionality, to them. For example, you
could create a group 'Special users', and you could write code that would
do special things to those users -- such as giving them access to a
members-only portion of your site, or sending them members-only
e-mail messages.
"""
name = StringField(max_length=80, unique=True, verbose_name=_('name'))
permissions = ListField(ReferenceField(Permission, verbose_name=_('permissions'), required=False))
class Meta:
verbose_name = _('group')
verbose_name_plural = _('groups')
def __unicode__(self):
return self.name
class UserManager(models.Manager):
def create_user(self, username, email, password=None):
"""
Creates and saves a User with the given username, e-mail and password.
"""
now = datetime_now()
# Normalize the address by lowercasing the domain part of the email
# address.
try:
email_name, domain_part = email.strip().split('@', 1)
except ValueError:
pass
else:
email = '@'.join([email_name, domain_part.lower()])
user = self.model(username=username, email=email, is_staff=False,
is_active=True, is_superuser=False, last_login=now,
date_joined=now)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, username, email, password):
u = self.create_user(username, email, password)
u.is_staff = True
u.is_active = True
u.is_superuser = True
u.save(using=self._db)
return u
def make_random_password(self, length=10, allowed_chars='abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'):
"Generates a random password with the given length and given allowed_chars"
# Note that default value of allowed_chars does not have "I" or letters
# that look like it -- just to avoid confusion.
from random import choice
return ''.join([choice(allowed_chars) for i in range(length)])
class User(Document):
"""A User document that aims to mirror most of the API specified by Django
at http://docs.djangoproject.com/en/dev/topics/auth/#users
"""
username = StringField(max_length=30, required=True,
verbose_name=_('username'),
help_text=_("Required. 30 characters or fewer. Letters, numbers and @/./+/-/_ characters"))
first_name = StringField(max_length=30,
verbose_name=_('first name'))
last_name = StringField(max_length=30,
verbose_name=_('last name'))
email = EmailField(verbose_name=_('e-mail address'))
password = StringField(max_length=128,
verbose_name=_('password'),
help_text=_("Use '[algo]$[iterations]$[salt]$[hexdigest]' or use the <a href=\"password/\">change password form</a>."))
is_staff = BooleanField(default=False,
verbose_name=_('staff status'),
help_text=_("Designates whether the user can log into this admin site."))
is_active = BooleanField(default=True,
verbose_name=_('active'),
help_text=_("Designates whether this user should be treated as active. Unselect this instead of deleting accounts."))
is_superuser = BooleanField(default=False,
verbose_name=_('superuser status'),
help_text=_("Designates that this user has all permissions without explicitly assigning them."))
last_login = DateTimeField(default=datetime_now,
verbose_name=_('last login'))
date_joined = DateTimeField(default=datetime_now,
verbose_name=_('date joined'))
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
meta = {
'allow_inheritance': True,
'indexes': [
{'fields': ['username'], 'unique': True, 'sparse': True}
]
}
def __unicode__(self):
return self.username
def get_full_name(self):
"""Returns the users first and last names, separated by a space.
"""
full_name = u'%s %s' % (self.first_name or '', self.last_name or '')
return full_name.strip()
def is_anonymous(self):
return False
def is_authenticated(self):
return True
def set_password(self, raw_password):
"""Sets the user's password - always use this rather than directly
assigning to :attr:`~mongoengine.django.auth.User.password` as the
password is hashed before storage.
"""
self.password = make_password(raw_password)
self.save()
return self
def check_password(self, raw_password):
"""Checks the user's password against a provided password - always use
this rather than directly comparing to
:attr:`~mongoengine.django.auth.User.password` as the password is
hashed before storage.
"""
return check_password(raw_password, self.password)
@classmethod
def create_user(cls, username, password, email=None):
"""Create (and save) a new user with the given username, password and
email address.
"""
now = datetime_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_group_permissions(self, obj=None):
"""
Returns a list of permission strings that this user has through his/her
groups. This method queries all available auth backends. If an object
is passed in, only permissions matching this object are returned.
"""
permissions = set()
for backend in auth.get_backends():
if hasattr(backend, "get_group_permissions"):
permissions.update(backend.get_group_permissions(self, obj))
return permissions
def get_all_permissions(self, obj=None):
return _user_get_all_permissions(self, obj)
def has_perm(self, perm, obj=None):
"""
Returns True if the user has the specified permission. This method
queries all available auth backends, but returns immediately if any
backend returns True. Thus, a user who has permission from a single
auth backend is assumed to have permission in general. If an object is
provided, permissions for this specific object are checked.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
# Otherwise we need to check the backends.
return _user_has_perm(self, perm, obj)
def has_module_perms(self, app_label):
"""
Returns True if the user has any permissions in the given app label.
Uses pretty much the same logic as has_perm, above.
"""
# Active superusers have all permissions.
if self.is_active and self.is_superuser:
return True
return _user_has_module_perms(self, app_label)
def email_user(self, subject, message, from_email=None):
"Sends an e-mail to this User."
from django.core.mail import send_mail
send_mail(subject, message, from_email, [self.email])
def get_profile(self):
"""
Returns site-specific profile for this user. Raises
SiteProfileNotAvailable if this site does not allow profiles.
"""
if not hasattr(self, '_profile_cache'):
from django.conf import settings
if not getattr(settings, 'AUTH_PROFILE_MODULE', False):
raise SiteProfileNotAvailable('You need to set AUTH_PROFILE_MO'
'DULE in your project settings')
try:
app_label, model_name = settings.AUTH_PROFILE_MODULE.split('.')
except ValueError:
raise SiteProfileNotAvailable('app_label and model_name should'
' be separated by a dot in the AUTH_PROFILE_MODULE set'
'ting')
try:
model = models.get_model(app_label, model_name)
if model is None:
raise SiteProfileNotAvailable('Unable to load the profile '
'model, check AUTH_PROFILE_MODULE in your project sett'
'ings')
self._profile_cache = model._default_manager.using(self._state.db).get(user__id__exact=self.id)
self._profile_cache.user = self
except (ImportError, ImproperlyConfigured):
raise SiteProfileNotAvailable
return self._profile_cache
class MongoEngineBackend(object):
"""Authenticate using MongoEngine and mongoengine.django.auth.User.
"""
supports_object_permissions = False
supports_anonymous_user = False
supports_inactive_user = False
def authenticate(self, username=None, password=None):
user = User.objects(username=username).first()
if user:
if password and user.check_password(password):
backend = auth.get_backends()[0]
user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
return user
return None
def get_user(self, user_id):
return User.objects.with_id(user_id)
def get_user(userid):
"""Returns a User object from an id (User.id). Django's equivalent takes
request, but taking an id instead leaves it up to the developer to store
the id in any way they want (session, signed cookie, etc.)
"""
if not userid:
return AnonymousUser()
return MongoEngineBackend().get_user(userid) or AnonymousUser()

View File

@@ -1,89 +0,0 @@
from django.conf import settings
from django.contrib.auth.models import UserManager
from django.core.exceptions import ImproperlyConfigured
from django.db import models
from django.utils.importlib import import_module
from django.utils.translation import ugettext_lazy as _
MONGOENGINE_USER_DOCUMENT = getattr(
settings, 'MONGOENGINE_USER_DOCUMENT', 'mongoengine.django.auth.User')
class MongoUserManager(UserManager):
"""A User manager wich allows the use of MongoEngine documents in Django.
To use the manager, you must tell django.contrib.auth to use MongoUser as
the user model. In you settings.py, you need:
INSTALLED_APPS = (
...
'django.contrib.auth',
'mongoengine.django.mongo_auth',
...
)
AUTH_USER_MODEL = 'mongo_auth.MongoUser'
Django will use the model object to access the custom Manager, which will
replace the original queryset with MongoEngine querysets.
By default, mongoengine.django.auth.User will be used to store users. You
can specify another document class in MONGOENGINE_USER_DOCUMENT in your
settings.py.
The User Document class has the same requirements as a standard custom user
model: https://docs.djangoproject.com/en/dev/topics/auth/customizing/
In particular, the User Document class must define USERNAME_FIELD and
REQUIRED_FIELDS.
`AUTH_USER_MODEL` has been added in Django 1.5.
"""
def contribute_to_class(self, model, name):
super(MongoUserManager, self).contribute_to_class(model, name)
self.dj_model = self.model
self.model = self._get_user_document()
self.dj_model.USERNAME_FIELD = self.model.USERNAME_FIELD
username = models.CharField(_('username'), max_length=30, unique=True)
username.contribute_to_class(self.dj_model, self.dj_model.USERNAME_FIELD)
self.dj_model.REQUIRED_FIELDS = self.model.REQUIRED_FIELDS
for name in self.dj_model.REQUIRED_FIELDS:
field = models.CharField(_(name), max_length=30)
field.contribute_to_class(self.dj_model, name)
def _get_user_document(self):
try:
name = MONGOENGINE_USER_DOCUMENT
dot = name.rindex('.')
module = import_module(name[:dot])
return getattr(module, name[dot + 1:])
except ImportError:
raise ImproperlyConfigured("Error importing %s, please check "
"settings.MONGOENGINE_USER_DOCUMENT"
% name)
def get(self, *args, **kwargs):
try:
return self.get_query_set().get(*args, **kwargs)
except self.model.DoesNotExist:
# ModelBackend expects this exception
raise self.dj_model.DoesNotExist
@property
def db(self):
raise NotImplementedError
def get_empty_query_set(self):
return self.model.objects.none()
def get_query_set(self):
return self.model.objects
class MongoUser(models.Model):
objects = MongoUserManager()

View File

@@ -1,102 +0,0 @@
from django.conf import settings
from django.contrib.sessions.backends.base import SessionBase, CreateError
from django.core.exceptions import SuspiciousOperation
from django.utils.encoding import force_unicode
from mongoengine.document import Document
from mongoengine import fields
from mongoengine.queryset import OperationError
from mongoengine.connection import DEFAULT_CONNECTION_NAME
from .utils import datetime_now
MONGOENGINE_SESSION_DB_ALIAS = getattr(
settings, 'MONGOENGINE_SESSION_DB_ALIAS',
DEFAULT_CONNECTION_NAME)
# a setting for the name of the collection used to store sessions
MONGOENGINE_SESSION_COLLECTION = getattr(
settings, 'MONGOENGINE_SESSION_COLLECTION',
'django_session')
# a setting for whether session data is stored encoded or not
MONGOENGINE_SESSION_DATA_ENCODE = getattr(
settings, 'MONGOENGINE_SESSION_DATA_ENCODE',
True)
class MongoSession(Document):
session_key = fields.StringField(primary_key=True, max_length=40)
session_data = fields.StringField() if MONGOENGINE_SESSION_DATA_ENCODE \
else fields.DictField()
expire_date = fields.DateTimeField()
meta = {
'collection': MONGOENGINE_SESSION_COLLECTION,
'db_alias': MONGOENGINE_SESSION_DB_ALIAS,
'allow_inheritance': False,
'indexes': [
{
'fields': ['expire_date'],
'expireAfterSeconds': settings.SESSION_COOKIE_AGE
}
]
}
def get_decoded(self):
return SessionStore().decode(self.session_data)
class SessionStore(SessionBase):
"""A MongoEngine-based session store for Django.
"""
def load(self):
try:
s = MongoSession.objects(session_key=self.session_key,
expire_date__gt=datetime_now)[0]
if MONGOENGINE_SESSION_DATA_ENCODE:
return self.decode(force_unicode(s.session_data))
else:
return s.session_data
except (IndexError, SuspiciousOperation):
self.create()
return {}
def exists(self, session_key):
return bool(MongoSession.objects(session_key=session_key).first())
def create(self):
while True:
self._session_key = self._get_new_session_key()
try:
self.save(must_create=True)
except CreateError:
continue
self.modified = True
self._session_cache = {}
return
def save(self, must_create=False):
if self.session_key is None:
self._session_key = self._get_new_session_key()
s = MongoSession(session_key=self.session_key)
if MONGOENGINE_SESSION_DATA_ENCODE:
s.session_data = self.encode(self._get_session(no_load=must_create))
else:
s.session_data = self._get_session(no_load=must_create)
s.expire_date = self.get_expiry_date()
try:
s.save(force_insert=must_create)
except OperationError:
if must_create:
raise CreateError
raise
def delete(self, session_key=None):
if session_key is None:
if self.session_key is None:
return
session_key = self.session_key
MongoSession.objects(session_key=session_key).delete()

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
#coding: utf-8
from nose.plugins.skip import SkipTest
from mongoengine.python_support import PY3
from mongoengine import connect
try:
from django.test import TestCase
from django.conf import settings
except Exception as err:
if PY3:
from unittest import TestCase
# Dummy value so no error
class settings:
MONGO_DATABASE_NAME = 'dummy'
else:
raise err
class MongoTestCase(TestCase):
def setUp(self):
if PY3:
raise SkipTest('django does not have Python 3 support')
"""
TestCase class that clear the collection between the tests
"""
db_name = 'test_%s' % settings.MONGO_DATABASE_NAME
def __init__(self, methodName='runtest'):
self.db = connect(self.db_name).get_db()
super(MongoTestCase, self).__init__(methodName)
def _post_teardown(self):
super(MongoTestCase, self)._post_teardown()
for collection in self.db.collection_names():
if collection == 'system.indexes':
continue
self.db.drop_collection(collection)

View File

@@ -1,6 +0,0 @@
try:
# django >= 1.4
from django.utils.timezone import now as datetime_now
except ImportError:
from datetime import datetime
datetime_now = datetime.now

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,11 @@
from collections import defaultdict
from mongoengine.python_support import txt_type
import six
__all__ = ('NotRegistered', 'InvalidDocumentError', 'LookUpError',
'DoesNotExist', 'MultipleObjectsReturned', 'InvalidQueryError',
'OperationError', 'NotUniqueError', 'ValidationError')
'OperationError', 'NotUniqueError', 'FieldDoesNotExist',
'ValidationError', 'SaveConditionError')
class NotRegistered(Exception):
@@ -40,6 +40,21 @@ class NotUniqueError(OperationError):
pass
class SaveConditionError(OperationError):
pass
class FieldDoesNotExist(Exception):
"""Raised when trying to set a field
not declared in a :class:`~mongoengine.Document`
or an :class:`~mongoengine.EmbeddedDocument`.
To avoid this behavior on data loading,
you should set the :attr:`strict` to ``False``
in the :attr:`meta` dictionary.
"""
class ValidationError(AssertionError):
"""Validation exception.
@@ -55,13 +70,14 @@ class ValidationError(AssertionError):
field_name = None
_message = None
def __init__(self, message="", **kwargs):
def __init__(self, message='', **kwargs):
super(ValidationError, self).__init__(message)
self.errors = kwargs.get('errors', {})
self.field_name = kwargs.get('field_name')
self.message = message
def __str__(self):
return txt_type(self.message)
return six.text_type(self.message)
def __repr__(self):
return '%s(%s,)' % (self.__class__.__name__, self.message)
@@ -95,16 +111,20 @@ class ValidationError(AssertionError):
errors_dict = {}
if not source:
return errors_dict
if isinstance(source, dict):
for field_name, error in source.iteritems():
errors_dict[field_name] = build_dict(error)
elif isinstance(source, ValidationError) and source.errors:
return build_dict(source.errors)
else:
return unicode(source)
return six.text_type(source)
return errors_dict
if not self.errors:
return {}
return build_dict(self.errors)
def _format_errors(self):
@@ -113,14 +133,14 @@ class ValidationError(AssertionError):
def generate_key(value, prefix=''):
if isinstance(value, list):
value = ' '.join([generate_key(k) for k in value])
if isinstance(value, dict):
elif isinstance(value, dict):
value = ' '.join(
[generate_key(v, k) for k, v in value.iteritems()])
[generate_key(v, k) for k, v in value.iteritems()])
results = "%s.%s" % (prefix, value) if prefix else value
results = '%s.%s' % (prefix, value) if prefix else value
return results
error_dict = defaultdict(list)
for k, v in self.to_dict().iteritems():
error_dict[generate_key(v)].append(k)
return ' '.join(["%s: %s" % (k, v) for k, v in error_dict.iteritems()])
return ' '.join(['%s: %s' % (k, v) for k, v in error_dict.iteritems()])

File diff suppressed because it is too large Load Diff

View File

@@ -1,61 +1,28 @@
"""Helper functions and types to aid with Python 2.5 - 3 support."""
"""
Helper functions, constants, and types to aid with Python v2.7 - v3.x and
PyMongo v2.7 - v3.x support.
"""
import pymongo
import six
import sys
PY3 = sys.version_info[0] == 3
PY25 = sys.version_info[:2] == (2, 5)
UNICODE_KWARGS = int(''.join([str(x) for x in sys.version_info[:3]])) > 264
IS_PYMONGO_3 = pymongo.version_tuple[0] >= 3
if PY3:
import codecs
from io import BytesIO as StringIO
# return s converted to binary. b('test') should be equivalent to b'test'
def b(s):
return codecs.latin_1_encode(s)[0]
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
StringIO = six.BytesIO
bin_type = bytes
txt_type = str
else:
# Additionally for Py2, try to use the faster cStringIO, if available
if not six.PY3:
try:
from cStringIO import StringIO
import cStringIO
except ImportError:
from StringIO import StringIO
# Conversion to binary only necessary in Python 3
def b(s):
return s
bin_type = str
txt_type = unicode
str_types = (bin_type, txt_type)
if PY25:
def product(*args, **kwds):
pools = map(tuple, args) * kwds.get('repeat', 1)
result = [[]]
for pool in pools:
result = [x + [y] for x in result for y in pool]
for prod in result:
yield tuple(prod)
reduce = reduce
else:
from itertools import product
from functools import reduce
# For use with Python 2.5
# converts all keys from unicode to str for d and all nested dictionaries
def to_str_keys_recursive(d):
if isinstance(d, list):
for val in d:
if isinstance(val, (dict, list)):
to_str_keys_recursive(val)
elif isinstance(d, dict):
for key, val in d.items():
if isinstance(val, (dict, list)):
to_str_keys_recursive(val)
if isinstance(key, unicode):
d[str(key)] = d.pop(key)
pass
else:
raise ValueError("non list/dict parameter not allowed")
StringIO = cStringIO.StringIO
if six.PY3:
from collections.abc import Hashable
else:
# raises DeprecationWarnings in Python >=3.7
from collections import Hashable

View File

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

1912
mongoengine/queryset/base.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
__all__ = ('QueryFieldList',)
@@ -55,7 +54,8 @@ class QueryFieldList(object):
if self.always_include:
if self.value is self.ONLY and self.fields:
self.fields = self.fields.union(self.always_include)
if sorted(self.slice.keys()) != sorted(self.fields):
self.fields = self.fields.union(self.always_include)
else:
self.fields -= self.always_include
@@ -63,11 +63,13 @@ class QueryFieldList(object):
self._only_called = True
return self
def __nonzero__(self):
def __bool__(self):
return bool(self.fields)
__nonzero__ = __bool__ # For Py2 support
def as_dict(self):
field_list = dict((field, self.value) for field in self.fields)
field_list = {field: self.value for field in self.fields}
if self.slice:
field_list.update(self.slice)
if self._id is not None:

View File

@@ -29,14 +29,14 @@ class QuerySetManager(object):
Document.objects is accessed.
"""
if instance is not None:
# Document class being used rather than a document object
# Document object being used rather than a document class
return self
# owner is the document that contains the QuerySetManager
queryset_class = owner._meta.get('queryset_class', self.default)
queryset = queryset_class(owner, owner._get_collection())
if self.get_queryset:
arg_count = self.get_queryset.func_code.co_argcount
arg_count = self.get_queryset.__code__.co_argcount
if arg_count == 1:
queryset = self.get_queryset(queryset)
elif arg_count == 2:

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,11 +1,9 @@
import copy
from mongoengine.errors import InvalidQueryError
from mongoengine.python_support import product, reduce
from mongoengine.queryset import transform
__all__ = ('Q',)
__all__ = ('Q', 'QNode')
class QNodeVisitor(object):
@@ -23,8 +21,12 @@ class QNodeVisitor(object):
return query
class DuplicateQueryConditionsError(InvalidQueryError):
pass
class SimplificationVisitor(QNodeVisitor):
"""Simplifies query trees by combinging unnecessary 'and' connection nodes
"""Simplifies query trees by combining unnecessary 'and' connection nodes
into a single Q-object.
"""
@@ -33,7 +35,11 @@ class SimplificationVisitor(QNodeVisitor):
# The simplification only applies to 'simple' queries
if all(isinstance(node, Q) for node in combination.children):
queries = [n.query for n in combination.children]
return Q(**self._query_conjunction(queries))
try:
return Q(**self._query_conjunction(queries))
except DuplicateQueryConditionsError:
# Cannot be simplified
pass
return combination
def _query_conjunction(self, queries):
@@ -47,8 +53,7 @@ class SimplificationVisitor(QNodeVisitor):
# to a single field
intersection = ops.intersection(query_ops)
if intersection:
msg = 'Duplicate query conditions: '
raise InvalidQueryError(msg + ', '.join(intersection))
raise DuplicateQueryConditionsError()
query_ops.update(ops)
combined_query.update(copy.deepcopy(query))
@@ -64,9 +69,9 @@ class QueryCompilerVisitor(QNodeVisitor):
self.document = document
def visit_combination(self, combination):
operator = "$and"
operator = '$and'
if combination.operation == combination.OR:
operator = "$or"
operator = '$or'
return {operator: combination.children}
def visit_query(self, query):
@@ -74,8 +79,7 @@ class QueryCompilerVisitor(QNodeVisitor):
class QNode(object):
"""Base class for nodes in query trees.
"""
"""Base class for nodes in query trees."""
AND = 0
OR = 1
@@ -89,7 +93,8 @@ class QNode(object):
raise NotImplementedError
def _combine(self, other, operation):
"""Combine this node with another node into a QCombination object.
"""Combine this node with another node into a QCombination
object.
"""
if getattr(other, 'empty', True):
return self
@@ -111,8 +116,8 @@ class QNode(object):
class QCombination(QNode):
"""Represents the combination of several conditions by a given logical
operator.
"""Represents the combination of several conditions by a given
logical operator.
"""
def __init__(self, operation, children):
@@ -122,11 +127,14 @@ class QCombination(QNode):
# If the child is a combination of the same type, we can merge its
# children directly into this combinations children
if isinstance(node, QCombination) and node.operation == operation:
# self.children += node.children
self.children.append(node)
self.children += node.children
else:
self.children.append(node)
def __repr__(self):
op = ' & ' if self.operation is self.AND else ' | '
return '(%s)' % op.join([repr(node) for node in self.children])
def accept(self, visitor):
for i in range(len(self.children)):
if isinstance(self.children[i], QNode):
@@ -147,6 +155,9 @@ class Q(QNode):
def __init__(self, **query):
self.query = query
def __repr__(self):
return 'Q(**%s)' % repr(self.query)
def accept(self, visitor):
return visitor.visit_query(self)

View File

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

View File

@@ -5,7 +5,7 @@
%define srcname mongoengine
Name: python-%{srcname}
Version: 0.8.1
Version: 0.8.7
Release: 1%{?dist}
Summary: A Python Document-Object Mapper for working with MongoDB

View File

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

View File

@@ -1,11 +1,11 @@
[nosetests]
verbosity = 3
detailed-errors = 1
#with-coverage = 1
#cover-erase = 1
#cover-html = 1
#cover-html-dir = ../htmlcov
#cover-package = mongoengine
py3where = build
where = tests
#tests = document/__init__.py
verbosity=2
detailed-errors=1
#tests=tests
cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201,I202,W504, W605
exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests
max-complexity=47
application-import-names=mongoengine,tests

View File

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

View File

@@ -1,5 +1,4 @@
from all_warnings import AllWarnings
from document import *
from queryset import *
from fields import *
from migration import *
from .all_warnings import AllWarnings
from .document import *
from .queryset import *
from .fields import *

View File

@@ -3,8 +3,6 @@ This test has been put into a module. This is because it tests warnings that
only get triggered on first hit. This way we can ensure its imported into the
top level and called first by the test suite.
"""
import sys
sys.path[0:0] = [""]
import unittest
import warnings

View File

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

View File

@@ -1,12 +1,11 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import unittest
from mongoengine import *
from mongoengine.queryset import NULLIFY, PULL
from mongoengine.connection import get_db
from tests.utils import requires_mongodb_gte_26
__all__ = ("ClassMethodsTest", )
@@ -36,9 +35,9 @@ class ClassMethodsTest(unittest.TestCase):
def test_definition(self):
"""Ensure that document may be defined using fields.
"""
self.assertEqual(['age', 'id', 'name'],
self.assertEqual(['_cls', 'age', 'id', 'name'],
sorted(self.Person._fields.keys()))
self.assertEqual(["IntField", "ObjectIdField", "StringField"],
self.assertEqual(["IntField", "ObjectIdField", "StringField", "StringField"],
sorted([x.__class__.__name__ for x in
self.Person._fields.values()]))
@@ -67,10 +66,10 @@ class ClassMethodsTest(unittest.TestCase):
"""
collection_name = 'person'
self.Person(name='Test').save()
self.assertTrue(collection_name in self.db.collection_names())
self.assertIn(collection_name, self.db.collection_names())
self.Person.drop_collection()
self.assertFalse(collection_name in self.db.collection_names())
self.assertNotIn(collection_name, self.db.collection_names())
def test_register_delete_rule(self):
"""Ensure that register delete rule adds a delete rule to the document
@@ -85,6 +84,173 @@ class ClassMethodsTest(unittest.TestCase):
self.assertEqual(self.Person._meta['delete_rules'],
{(Job, 'employee'): NULLIFY})
def test_compare_indexes(self):
""" Ensure that the indexes are properly created and that
compare_indexes identifies the missing/extra indexes
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
tags = StringField()
meta = {
'indexes': [('author', 'title')]
}
BlogPost.drop_collection()
BlogPost.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPost.ensure_index(['author', 'description'])
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('author', 1), ('description', 1)]] })
BlogPost._get_collection().drop_index('author_1_description_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPost._get_collection().drop_index('author_1_title_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('author', 1), ('title', 1)]], 'extra': [] })
def test_compare_indexes_inheritance(self):
""" Ensure that the indexes are properly created and that
compare_indexes identifies the missing/extra indexes for subclassed
documents (_cls included)
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
class BlogPostWithTags(BlogPost):
tags = StringField()
tag_list = ListField(StringField())
meta = {
'indexes': [('author', 'tags')]
}
BlogPost.drop_collection()
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPostWithTags.ensure_index(['author', 'tag_list'])
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [[('_cls', 1), ('author', 1), ('tag_list', 1)]] })
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tag_list_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
BlogPostWithTags._get_collection().drop_index('_cls_1_author_1_tags_1')
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [[('_cls', 1), ('author', 1), ('tags', 1)]], 'extra': [] })
def test_compare_indexes_multiple_subclasses(self):
""" Ensure that compare_indexes behaves correctly if called from a
class, which base class has multiple subclasses
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
class BlogPostWithTags(BlogPost):
tags = StringField()
tag_list = ListField(StringField())
meta = {
'indexes': [('author', 'tags')]
}
class BlogPostWithCustomField(BlogPost):
custom = DictField()
meta = {
'indexes': [('author', 'custom')]
}
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
BlogPostWithCustomField.ensure_indexes()
self.assertEqual(BlogPost.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPostWithTags.compare_indexes(), { 'missing': [], 'extra': [] })
self.assertEqual(BlogPostWithCustomField.compare_indexes(), { 'missing': [], 'extra': [] })
@requires_mongodb_gte_26
def test_compare_indexes_for_text_indexes(self):
""" Ensure that compare_indexes behaves correctly for text indexes """
class Doc(Document):
a = StringField()
b = StringField()
meta = {'indexes': [
{'fields': ['$a', "$b"],
'default_language': 'english',
'weights': {'a': 10, 'b': 2}
}
]}
Doc.drop_collection()
Doc.ensure_indexes()
actual = Doc.compare_indexes()
expected = {'missing': [], 'extra': []}
self.assertEqual(actual, expected)
def test_list_indexes_inheritance(self):
""" ensure that all of the indexes are listed regardless of the super-
or sub-class that we call it from
"""
class BlogPost(Document):
author = StringField()
title = StringField()
description = StringField()
meta = {
'allow_inheritance': True
}
class BlogPostWithTags(BlogPost):
tags = StringField()
meta = {
'indexes': [('author', 'tags')]
}
class BlogPostWithTagsAndExtraText(BlogPostWithTags):
extra_text = StringField()
meta = {
'indexes': [('author', 'tags', 'extra_text')]
}
BlogPost.drop_collection()
BlogPost.ensure_indexes()
BlogPostWithTags.ensure_indexes()
BlogPostWithTagsAndExtraText.ensure_indexes()
self.assertEqual(BlogPost.list_indexes(),
BlogPostWithTags.list_indexes())
self.assertEqual(BlogPost.list_indexes(),
BlogPostWithTagsAndExtraText.list_indexes())
self.assertEqual(BlogPost.list_indexes(),
[[('_cls', 1), ('author', 1), ('tags', 1)],
[('_cls', 1), ('author', 1), ('tags', 1), ('extra_text', 1)],
[(u'_id', 1)], [('_cls', 1)]])
def test_register_delete_rule_inherited(self):
class Vaccine(Document):
@@ -174,7 +340,7 @@ class ClassMethodsTest(unittest.TestCase):
meta = {'collection': collection_name}
Person(name="Test User").save()
self.assertTrue(collection_name in self.db.collection_names())
self.assertIn(collection_name, self.db.collection_names())
user_obj = self.db[collection_name].find_one()
self.assertEqual(user_obj['name'], "Test User")
@@ -183,7 +349,7 @@ class ClassMethodsTest(unittest.TestCase):
self.assertEqual(user_obj.name, "Test User")
Person.drop_collection()
self.assertFalse(collection_name in self.db.collection_names())
self.assertNotIn(collection_name, self.db.collection_names())
def test_collection_name_and_primary(self):
"""Ensure that a collection with a specified name may be used.

View File

@@ -1,8 +1,7 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import unittest
from bson import SON
from mongoengine import *
from mongoengine.connection import get_db
@@ -92,6 +91,7 @@ class DeltaTest(unittest.TestCase):
def delta_recursive(self, DocClass, EmbeddedClass):
class Embedded(EmbeddedClass):
id = StringField()
string_field = StringField()
int_field = IntField()
dict_field = DictField()
@@ -113,6 +113,7 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(doc._delta(), ({}, {}))
embedded_1 = Embedded()
embedded_1.id = "010101"
embedded_1.string_field = 'hello'
embedded_1.int_field = 1
embedded_1.dict_field = {'hello': 'world'}
@@ -122,6 +123,7 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(doc._get_changed_fields(), ['embedded_field'])
embedded_delta = {
'id': "010101",
'string_field': 'hello',
'int_field': 1,
'dict_field': {'hello': 'world'},
@@ -206,22 +208,21 @@ class DeltaTest(unittest.TestCase):
doc.embedded_field.list_field[2].string_field = 'hello world'
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
self.assertEqual(doc._get_changed_fields(),
['embedded_field.list_field'])
self.assertEqual(doc.embedded_field._delta(), ({
'list_field': ['1', 2, {
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}]}, {}))
self.assertEqual(doc._delta(), ({
'embedded_field.list_field': ['1', 2, {
['embedded_field.list_field.2'])
self.assertEqual(doc.embedded_field._delta(), ({'list_field.2': {
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}
]}, {}))
}, {}))
self.assertEqual(doc._delta(), ({'embedded_field.list_field.2': {
'_cls': 'Embedded',
'string_field': 'hello world',
'int_field': 1,
'list_field': ['1', 2, {'hello': 'world'}],
'dict_field': {'hello': 'world'}}
}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].string_field,
@@ -250,13 +251,13 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(doc.embedded_field.list_field[2].list_field,
[1, 2, {'hello': 'world'}])
del(doc.embedded_field.list_field[2].list_field[2]['hello'])
del doc.embedded_field.list_field[2].list_field[2]['hello']
self.assertEqual(doc._delta(),
({'embedded_field.list_field.2.list_field': [1, 2, {}]}, {}))
({}, {'embedded_field.list_field.2.list_field.2.hello': 1}))
doc.save()
doc = doc.reload(10)
del(doc.embedded_field.list_field[2].list_field)
del doc.embedded_field.list_field[2].list_field
self.assertEqual(doc._delta(),
({}, {'embedded_field.list_field.2.list_field': 1}))
@@ -312,29 +313,24 @@ class DeltaTest(unittest.TestCase):
self.circular_reference_deltas_2(DynamicDocument, Document)
self.circular_reference_deltas_2(DynamicDocument, DynamicDocument)
def circular_reference_deltas_2(self, DocClass1, DocClass2):
def circular_reference_deltas_2(self, DocClass1, DocClass2, dbref=True):
class Person(DocClass1):
name = StringField()
owns = ListField(ReferenceField('Organization'))
employer = ReferenceField('Organization')
owns = ListField(ReferenceField('Organization', dbref=dbref))
employer = ReferenceField('Organization', dbref=dbref)
class Organization(DocClass2):
name = StringField()
owner = ReferenceField('Person')
employees = ListField(ReferenceField('Person'))
owner = ReferenceField('Person', dbref=dbref)
employees = ListField(ReferenceField('Person', dbref=dbref))
Person.drop_collection()
Organization.drop_collection()
person = Person(name="owner")
person.save()
employee = Person(name="employee")
employee.save()
organization = Organization(name="company")
organization.save()
person = Person(name="owner").save()
employee = Person(name="employee").save()
organization = Organization(name="company").save()
person.owns.append(organization)
organization.owner = person
@@ -354,6 +350,8 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(o.owner, p)
self.assertEqual(e.employer, o)
return person, organization, employee
def test_delta_db_field(self):
self.delta_db_field(Document)
self.delta_db_field(DynamicDocument)
@@ -550,22 +548,21 @@ class DeltaTest(unittest.TestCase):
doc.embedded_field.list_field[2].string_field = 'hello world'
doc.embedded_field.list_field[2] = doc.embedded_field.list_field[2]
self.assertEqual(doc._get_changed_fields(),
['db_embedded_field.db_list_field'])
self.assertEqual(doc.embedded_field._delta(), ({
'db_list_field': ['1', 2, {
['db_embedded_field.db_list_field.2'])
self.assertEqual(doc.embedded_field._delta(), ({'db_list_field.2': {
'_cls': 'Embedded',
'db_string_field': 'hello world',
'db_int_field': 1,
'db_list_field': ['1', 2, {'hello': 'world'}],
'db_dict_field': {'hello': 'world'}}]}, {}))
'db_dict_field': {'hello': 'world'}}}, {}))
self.assertEqual(doc._delta(), ({
'db_embedded_field.db_list_field': ['1', 2, {
'db_embedded_field.db_list_field.2': {
'_cls': 'Embedded',
'db_string_field': 'hello world',
'db_int_field': 1,
'db_list_field': ['1', 2, {'hello': 'world'}],
'db_dict_field': {'hello': 'world'}}
]}, {}))
}, {}))
doc.save()
doc = doc.reload(10)
self.assertEqual(doc.embedded_field.list_field[2].string_field,
@@ -594,14 +591,13 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(doc.embedded_field.list_field[2].list_field,
[1, 2, {'hello': 'world'}])
del(doc.embedded_field.list_field[2].list_field[2]['hello'])
del doc.embedded_field.list_field[2].list_field[2]['hello']
self.assertEqual(doc._delta(),
({'db_embedded_field.db_list_field.2.db_list_field':
[1, 2, {}]}, {}))
({}, {'db_embedded_field.db_list_field.2.db_list_field.2.hello': 1}))
doc.save()
doc = doc.reload(10)
del(doc.embedded_field.list_field[2].list_field)
del doc.embedded_field.list_field[2].list_field
self.assertEqual(doc._delta(), ({},
{'db_embedded_field.db_list_field.2.db_list_field': 1}))
@@ -613,13 +609,13 @@ class DeltaTest(unittest.TestCase):
Person.drop_collection()
p = Person(name="James", age=34)
self.assertEqual(p._delta(), ({'age': 34, 'name': 'James',
'_cls': 'Person'}, {}))
self.assertEqual(p._delta(), (
SON([('_cls', 'Person'), ('name', 'James'), ('age', 34)]), {}))
p.doc = 123
del(p.doc)
self.assertEqual(p._delta(), ({'age': 34, 'name': 'James',
'_cls': 'Person'}, {'doc': 1}))
del p.doc
self.assertEqual(p._delta(), (
SON([('_cls', 'Person'), ('name', 'James'), ('age', 34)]), {}))
p = Person()
p.name = "Dean"
@@ -631,14 +627,14 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(p._get_changed_fields(), ['age'])
self.assertEqual(p._delta(), ({'age': 24}, {}))
p = self.Person.objects(age=22).get()
p = Person.objects(age=22).get()
p.age = 24
self.assertEqual(p.age, 24)
self.assertEqual(p._get_changed_fields(), ['age'])
self.assertEqual(p._delta(), ({'age': 24}, {}))
p.save()
self.assertEqual(1, self.Person.objects(age=24).count())
self.assertEqual(1, Person.objects(age=24).count())
def test_dynamic_delta(self):
@@ -685,6 +681,187 @@ class DeltaTest(unittest.TestCase):
self.assertEqual(doc._get_changed_fields(), ['list_field'])
self.assertEqual(doc._delta(), ({}, {'list_field': 1}))
def test_delta_with_dbref_true(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, True)
employee.name = 'test'
self.assertEqual(organization._get_changed_fields(), [])
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertEqual({}, updates)
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertIn('employees', updates)
def test_delta_with_dbref_false(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, False)
employee.name = 'test'
self.assertEqual(organization._get_changed_fields(), [])
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertEqual({}, updates)
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertIn('employees', updates)
def test_nested_nested_fields_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(MapField(EmbeddedDocumentField(EmbeddedDoc)))
name = StringField()
MyDoc.drop_collection()
mydoc = MyDoc(name='testcase1', subs={'a': {'b': EmbeddedDoc(name='foo')}}).save()
mydoc = MyDoc.objects.first()
subdoc = mydoc.subs['a']['b']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a.b.name"], mydoc._get_changed_fields())
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
def test_lower_level_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(EmbeddedDocumentField(EmbeddedDoc))
MyDoc.drop_collection()
MyDoc().save()
mydoc = MyDoc.objects.first()
mydoc.subs['a'] = EmbeddedDoc()
self.assertEqual(["subs.a"], mydoc._get_changed_fields())
subdoc = mydoc.subs['a']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a"], mydoc._get_changed_fields())
mydoc.save()
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
def test_upper_level_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):
name = StringField()
class MyDoc(Document):
subs = MapField(EmbeddedDocumentField(EmbeddedDoc))
MyDoc.drop_collection()
MyDoc(subs={'a': EmbeddedDoc(name='foo')}).save()
mydoc = MyDoc.objects.first()
subdoc = mydoc.subs['a']
subdoc.name = 'bar'
self.assertEqual(["name"], subdoc._get_changed_fields())
self.assertEqual(["subs.a.name"], mydoc._get_changed_fields())
mydoc.subs['a'] = EmbeddedDoc()
self.assertEqual(["subs.a"], mydoc._get_changed_fields())
mydoc.save()
mydoc._clear_changed_fields()
self.assertEqual([], mydoc._get_changed_fields())
def test_referenced_object_changed_attributes(self):
"""Ensures that when you save a new reference to a field, the referenced object isn't altered"""
class Organization(Document):
name = StringField()
class User(Document):
name = StringField()
org = ReferenceField('Organization', required=True)
Organization.drop_collection()
User.drop_collection()
org1 = Organization(name='Org 1')
org1.save()
org2 = Organization(name='Org 2')
org2.save()
user = User(name='Fred', org=org1)
user.save()
org1.reload()
org2.reload()
user.reload()
self.assertEqual(org1.name, 'Org 1')
self.assertEqual(org2.name, 'Org 2')
self.assertEqual(user.name, 'Fred')
user.name = 'Harold'
user.org = org2
org2.name = 'New Org 2'
self.assertEqual(org2.name, 'New Org 2')
user.save()
org2.save()
self.assertEqual(org2.name, 'New Org 2')
org2.reload()
self.assertEqual(org2.name, 'New Org 2')
def test_delta_for_nested_map_fields(self):
class UInfoDocument(Document):
phone = StringField()
class EmbeddedRole(EmbeddedDocument):
type = StringField()
class EmbeddedUser(EmbeddedDocument):
name = StringField()
roles = MapField(field=EmbeddedDocumentField(EmbeddedRole))
rolist = ListField(field=EmbeddedDocumentField(EmbeddedRole))
info = ReferenceField(UInfoDocument)
class Doc(Document):
users = MapField(field=EmbeddedDocumentField(EmbeddedUser))
num = IntField(default=-1)
Doc.drop_collection()
doc = Doc(num=1)
doc.users["007"] = EmbeddedUser(name="Agent007")
doc.save()
uinfo = UInfoDocument(phone="79089269066")
uinfo.save()
d = Doc.objects(num=1).first()
d.users["007"]["roles"]["666"] = EmbeddedRole(type="superadmin")
d.users["007"]["rolist"].append(EmbeddedRole(type="oops"))
d.users["007"]["info"] = uinfo
delta = d._delta()
self.assertEqual(True, "users.007.roles.666" in delta[0])
self.assertEqual(True, "users.007.rolist" in delta[0])
self.assertEqual(True, "users.007.info" in delta[0])
self.assertEqual('superadmin', delta[0]["users.007.roles.666"]["type"])
self.assertEqual('oops', delta[0]["users.007.rolist"][0]["type"])
self.assertEqual(uinfo.id, delta[0]["users.007.info"])
if __name__ == '__main__':
unittest.main()

View File

@@ -1,6 +1,4 @@
import unittest
import sys
sys.path[0:0] = [""]
from mongoengine import *
from mongoengine.connection import get_db
@@ -72,7 +70,7 @@ class DynamicTest(unittest.TestCase):
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'misc', 'name'])
del(p.misc)
del p.misc
p.save()
p = self.Person.objects.get()
@@ -81,6 +79,25 @@ class DynamicTest(unittest.TestCase):
obj = collection.find_one()
self.assertEqual(sorted(obj.keys()), ['_cls', '_id', 'name'])
def test_reload_after_unsetting(self):
p = self.Person()
p.misc = 22
p.save()
p.update(unset__misc=1)
p.reload()
def test_reload_dynamic_field(self):
self.Person.objects.delete()
p = self.Person.objects.create()
p.update(age=1)
self.assertEqual(len(p._data), 3)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'id', 'name'])
p.reload()
self.assertEqual(len(p._data), 4)
self.assertEqual(sorted(p._data.keys()), ['_cls', 'age', 'id', 'name'])
def test_dynamic_document_queries(self):
"""Ensure we can query dynamic fields"""
p = self.Person()
@@ -122,6 +139,13 @@ class DynamicTest(unittest.TestCase):
self.assertEqual(1, self.Person.objects(misc__hello='world').count())
def test_three_level_complex_data_lookups(self):
"""Ensure you can query three level document dynamic fields"""
p = self.Person.objects.create(
misc={'hello': {'hello2': 'world'}}
)
self.assertEqual(1, self.Person.objects(misc__hello__hello2='world').count())
def test_complex_embedded_document_validation(self):
"""Ensure embedded dynamic documents may be validated"""
class Embedded(DynamicEmbeddedDocument):
@@ -150,8 +174,8 @@ class DynamicTest(unittest.TestCase):
Employee.drop_collection()
self.assertTrue('name' in Employee._fields)
self.assertTrue('salary' in Employee._fields)
self.assertIn('name', Employee._fields)
self.assertIn('salary', Employee._fields)
self.assertEqual(Employee._get_collection_name(),
self.Person._get_collection_name())
@@ -165,7 +189,7 @@ class DynamicTest(unittest.TestCase):
self.assertEqual(1, Employee.objects(age=20).count())
joe_bloggs = self.Person.objects.first()
self.assertTrue(isinstance(joe_bloggs, Employee))
self.assertIsInstance(joe_bloggs, Employee)
def test_embedded_dynamic_document(self):
"""Test dynamic embedded documents"""
@@ -292,6 +316,58 @@ class DynamicTest(unittest.TestCase):
person.save()
self.assertEqual(Person.objects.first().age, 35)
def test_dynamic_embedded_works_with_only(self):
"""Ensure custom fieldnames on a dynamic embedded document are found by qs.only()"""
class Address(DynamicEmbeddedDocument):
city = StringField()
class Person(DynamicDocument):
address = EmbeddedDocumentField(Address)
Person.drop_collection()
Person(name="Eric", address=Address(city="San Francisco", street_number="1337")).save()
self.assertEqual(Person.objects.first().address.street_number, '1337')
self.assertEqual(Person.objects.only('address__street_number').first().address.street_number, '1337')
def test_dynamic_and_embedded_dict_access(self):
"""Ensure embedded dynamic documents work with dict[] style access"""
class Address(EmbeddedDocument):
city = StringField()
class Person(DynamicDocument):
name = StringField()
Person.drop_collection()
Person(name="Ross", address=Address(city="London")).save()
person = Person.objects.first()
person.attrval = "This works"
person["phone"] = "555-1212" # but this should too
# Same thing two levels deep
person["address"]["city"] = "Lundenne"
person.save()
self.assertEqual(Person.objects.first().address.city, "Lundenne")
self.assertEqual(Person.objects.first().phone, "555-1212")
person = Person.objects.first()
person.address = Address(city="Londinium")
person.save()
self.assertEqual(Person.objects.first().address.city, "Londinium")
person = Person.objects.first()
person["age"] = 35
person.save()
self.assertEqual(Person.objects.first().age, 35)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,16 +1,14 @@
# -*- coding: utf-8 -*-
import unittest
import sys
sys.path[0:0] = [""]
import os
import pymongo
from nose.plugins.skip import SkipTest
from datetime import datetime
from nose.plugins.skip import SkipTest
from pymongo.errors import OperationFailure
import pymongo
from mongoengine import *
from mongoengine.connection import get_db, get_connection
from mongoengine.connection import get_db
from tests.utils import get_mongodb_version, requires_mongodb_gte_26, MONGODB_32, MONGODB_3
__all__ = ("IndexesTest", )
@@ -18,8 +16,9 @@ __all__ = ("IndexesTest", )
class IndexesTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.connection = connect(db='mongoenginetest')
self.db = get_db()
self.mongodb_version = get_mongodb_version()
class Person(Document):
name = StringField()
@@ -32,10 +31,7 @@ class IndexesTest(unittest.TestCase):
self.Person = Person
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
self.db.drop_collection(collection)
self.connection.drop_database(self.db)
def test_indexes_document(self):
"""Ensure that indexes are used when meta[indexes] is specified for
@@ -74,7 +70,7 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(len(info), 4)
info = [value['key'] for key, value in info.iteritems()]
for expected in expected_specs:
self.assertTrue(expected['fields'] in info)
self.assertIn(expected['fields'], info)
def _index_test_inheritance(self, InheritFrom):
@@ -106,7 +102,7 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(len(info), 4)
info = [value['key'] for key, value in info.iteritems()]
for expected in expected_specs:
self.assertTrue(expected['fields'] in info)
self.assertIn(expected['fields'], info)
class ExtendedBlogPost(BlogPost):
title = StringField()
@@ -121,7 +117,7 @@ class IndexesTest(unittest.TestCase):
info = ExtendedBlogPost.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
for expected in expected_specs:
self.assertTrue(expected['fields'] in info)
self.assertIn(expected['fields'], info)
def test_indexes_document_inheritance(self):
"""Ensure that indexes are used when meta[indexes] is specified for
@@ -143,7 +139,7 @@ class IndexesTest(unittest.TestCase):
meta = {
'indexes': [
{
'fields': ('title',),
'fields': ('title',),
},
],
'allow_inheritance': True,
@@ -156,6 +152,35 @@ class IndexesTest(unittest.TestCase):
self.assertEqual([{'fields': [('_cls', 1), ('title', 1)]}],
A._meta['index_specs'])
def test_index_no_cls(self):
"""Ensure index specs are inhertited correctly"""
class A(Document):
title = StringField()
meta = {
'indexes': [
{'fields': ('title',), 'cls': False},
],
'allow_inheritance': True,
'index_cls': False
}
self.assertEqual([('title', 1)], A._meta['index_specs'][0]['fields'])
A._get_collection().drop_indexes()
A.ensure_indexes()
info = A._get_collection().index_information()
self.assertEqual(len(info.keys()), 2)
class B(A):
c = StringField()
d = StringField()
meta = {
'indexes': [{'fields': ['c']}, {'fields': ['d'], 'cls': True}],
'allow_inheritance': True
}
self.assertEqual([('c', 1)], B._meta['index_specs'][1]['fields'])
self.assertEqual([('_cls', 1), ('d', 1)], B._meta['index_specs'][2]['fields'])
def test_build_index_spec_is_not_destructive(self):
class MyDoc(Document):
@@ -201,7 +226,7 @@ class IndexesTest(unittest.TestCase):
list(Person.objects)
info = Person.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertTrue([('rank.title', 1)] in info)
self.assertIn([('rank.title', 1)], info)
def test_explicit_geo2d_index(self):
"""Ensure that geo2d indexes work when created via meta[indexes]
@@ -221,7 +246,7 @@ class IndexesTest(unittest.TestCase):
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertTrue([('location.point', '2d')] in info)
self.assertIn([('location.point', '2d')], info)
def test_explicit_geo2d_index_embedded(self):
"""Ensure that geo2d indexes work when created via meta[indexes]
@@ -244,7 +269,61 @@ class IndexesTest(unittest.TestCase):
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertTrue([('current.location.point', '2d')] in info)
self.assertIn([('current.location.point', '2d')], info)
def test_explicit_geosphere_index(self):
"""Ensure that geosphere indexes work when created via meta[indexes]
"""
class Place(Document):
location = DictField()
meta = {
'allow_inheritance': True,
'indexes': [
'(location.point',
]
}
self.assertEqual([{'fields': [('location.point', '2dsphere')]}],
Place._meta['index_specs'])
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertIn([('location.point', '2dsphere')], info)
def test_explicit_geohaystack_index(self):
"""Ensure that geohaystack indexes work when created via meta[indexes]
"""
raise SkipTest('GeoHaystack index creation is not supported for now'
'from meta, as it requires a bucketSize parameter.')
class Place(Document):
location = DictField()
name = StringField()
meta = {
'indexes': [
(')location.point', 'name')
]
}
self.assertEqual([{'fields': [('location.point', 'geoHaystack'), ('name', 1)]}],
Place._meta['index_specs'])
Place.ensure_indexes()
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertIn([('location.point', 'geoHaystack')], info)
def test_create_geohaystack_index(self):
"""Ensure that geohaystack indexes can be created
"""
class Place(Document):
location = DictField()
name = StringField()
Place.create_index({'fields': (')location.point', 'name')}, bucketSize=10)
info = Place._get_collection().index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info)
def test_dictionary_indexes(self):
"""Ensure that indexes are used when meta[indexes] contains
@@ -277,7 +356,7 @@ class IndexesTest(unittest.TestCase):
value.get('unique', False),
value.get('sparse', False))
for key, value in info.iteritems()]
self.assertTrue(([('addDate', -1)], True, True) in info)
self.assertIn(([('addDate', -1)], True, True), info)
BlogPost.drop_collection()
@@ -333,7 +412,6 @@ class IndexesTest(unittest.TestCase):
User.ensure_indexes()
info = User.objects._collection.index_information()
self.assertEqual(sorted(info.keys()), ['_cls_1_user_guid_1', '_id_'])
User.drop_collection()
def test_embedded_document_index(self):
"""Tests settings an index on an embedded document
@@ -355,7 +433,6 @@ class IndexesTest(unittest.TestCase):
info = BlogPost.objects._collection.index_information()
self.assertEqual(sorted(info.keys()), ['_id_', 'date.yr_-1'])
BlogPost.drop_collection()
def test_list_embedded_document_index(self):
"""Ensure list embedded documents can be indexed
@@ -382,7 +459,6 @@ class IndexesTest(unittest.TestCase):
post1 = BlogPost(title="Embedded Indexes tests in place",
tags=[Tag(name="about"), Tag(name="time")])
post1.save()
BlogPost.drop_collection()
def test_recursive_embedded_objects_dont_break_indexes(self):
@@ -403,6 +479,7 @@ class IndexesTest(unittest.TestCase):
class Test(Document):
a = IntField()
b = IntField()
meta = {
'indexes': ['a'],
@@ -414,16 +491,35 @@ class IndexesTest(unittest.TestCase):
obj = Test(a=1)
obj.save()
IS_MONGODB_3 = get_mongodb_version() >= MONGODB_3
# Need to be explicit about covered indexes as mongoDB doesn't know if
# the documents returned might have more keys in that here.
query_plan = Test.objects(id=obj.id).exclude('a').explain()
self.assertFalse(query_plan['indexOnly'])
if not IS_MONGODB_3:
self.assertFalse(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IDHACK')
query_plan = Test.objects(id=obj.id).only('id').explain()
self.assertTrue(query_plan['indexOnly'])
if not IS_MONGODB_3:
self.assertTrue(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IDHACK')
query_plan = Test.objects(a=1).only('a').exclude('id').explain()
self.assertTrue(query_plan['indexOnly'])
if not IS_MONGODB_3:
self.assertTrue(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IXSCAN')
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('stage'), 'PROJECTION')
query_plan = Test.objects(a=1).explain()
if not IS_MONGODB_3:
self.assertFalse(query_plan['indexOnly'])
else:
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('inputStage').get('stage'), 'IXSCAN')
self.assertEqual(query_plan.get('queryPlanner').get('winningPlan').get('stage'), 'FETCH')
def test_index_on_id(self):
@@ -445,34 +541,45 @@ class IndexesTest(unittest.TestCase):
[('categories', 1), ('_id', 1)])
def test_hint(self):
MONGO_VER = self.mongodb_version
TAGS_INDEX_NAME = 'tags_1'
class BlogPost(Document):
tags = ListField(StringField())
meta = {
'indexes': [
'tags',
{
'fields': ['tags'],
'name': TAGS_INDEX_NAME
}
],
}
BlogPost.drop_collection()
for i in xrange(0, 10):
tags = [("tag %i" % n) for n in xrange(0, i % 2)]
for i in range(10):
tags = [("tag %i" % n) for n in range(i % 2)]
BlogPost(tags=tags).save()
self.assertEqual(BlogPost.objects.count(), 10)
self.assertEqual(BlogPost.objects.hint().count(), 10)
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
# PyMongo 3.0 bug only, works correctly with 2.X and 3.0.1+ versions
if pymongo.version != '3.0':
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
def invalid_index():
BlogPost.objects.hint('tags')
self.assertRaises(TypeError, invalid_index)
if MONGO_VER == MONGODB_32:
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
# and you use hint on an index name that does not exist
with self.assertRaises(OperationFailure):
BlogPost.objects.hint([('ZZ', 1)]).count()
else:
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
def invalid_index_2():
return BlogPost.objects.hint(('tags', 1))
self.assertRaises(TypeError, invalid_index_2)
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME ).count(), 10)
with self.assertRaises(Exception):
BlogPost.objects.hint(('tags', 1)).next()
def test_unique(self):
"""Ensure that uniqueness constraints are applied to fields.
@@ -517,8 +624,6 @@ class IndexesTest(unittest.TestCase):
post3 = BlogPost(title='test3', date=Date(year=2010), slug='test')
self.assertRaises(OperationError, post3.save)
BlogPost.drop_collection()
def test_unique_embedded_document(self):
"""Ensure that uniqueness constraints are applied to fields on embedded documents.
"""
@@ -546,8 +651,36 @@ class IndexesTest(unittest.TestCase):
sub=SubDocument(year=2010, slug='test'))
self.assertRaises(NotUniqueError, post3.save)
def test_unique_embedded_document_in_list(self):
"""
Ensure that the uniqueness constraints are applied to fields in
embedded documents, even when the embedded documents in in a
list field.
"""
class SubDocument(EmbeddedDocument):
year = IntField(db_field='yr')
slug = StringField(unique=True)
class BlogPost(Document):
title = StringField()
subs = ListField(EmbeddedDocumentField(SubDocument))
BlogPost.drop_collection()
post1 = BlogPost(
title='test1', subs=[
SubDocument(year=2009, slug='conflict'),
SubDocument(year=2009, slug='conflict')
]
)
post1.save()
post2 = BlogPost(
title='test2', subs=[SubDocument(year=2014, slug='conflict')]
)
self.assertRaises(NotUniqueError, post2.save)
def test_unique_with_embedded_document_and_embedded_unique(self):
"""Ensure that uniqueness constraints are applied to fields on
embedded documents. And work with unique_with as well.
@@ -581,8 +714,6 @@ class IndexesTest(unittest.TestCase):
sub=SubDocument(year=2009, slug='test-1'))
self.assertRaises(NotUniqueError, post3.save)
BlogPost.drop_collection()
def test_ttl_indexes(self):
class Log(Document):
@@ -595,14 +726,6 @@ class IndexesTest(unittest.TestCase):
Log.drop_collection()
if pymongo.version_tuple[0] < 2 and pymongo.version_tuple[1] < 3:
raise SkipTest('pymongo needs to be 2.3 or higher for this test')
connection = get_connection()
version_array = connection.server_info()['versionArray']
if version_array[0] < 2 and version_array[1] < 2:
raise SkipTest('MongoDB needs to be 2.2 or higher for this test')
# Indexes are lazy so use list() to perform query
list(Log.objects)
info = Log.objects._collection.index_information()
@@ -630,13 +753,11 @@ class IndexesTest(unittest.TestCase):
raise AssertionError("We saved a dupe!")
except NotUniqueError:
pass
Customer.drop_collection()
def test_unique_and_primary(self):
def test_primary_save_duplicate_update_existing_object(self):
"""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.
"""
class User(Document):
name = StringField(primary_key=True, unique=True)
password = StringField()
@@ -652,8 +773,23 @@ class IndexesTest(unittest.TestCase):
self.assertEqual(User.objects.count(), 1)
self.assertEqual(User.objects.get().password, 'secret2')
def test_unique_and_primary_create(self):
"""Create a new record with a duplicate primary key
throws an exception
"""
class User(Document):
name = StringField(primary_key=True)
password = StringField()
User.drop_collection()
User.objects.create(name='huangz', password='secret')
with self.assertRaises(NotUniqueError):
User.objects.create(name='huangz', password='secret2')
self.assertEqual(User.objects.count(), 1)
self.assertEqual(User.objects.get().password, 'secret')
def test_index_with_pk(self):
"""Ensure you can use `pk` as part of a query"""
@@ -672,7 +808,7 @@ class IndexesTest(unittest.TestCase):
info = BlogPost.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
index_item = [('_id', 1), ('comments.comment_id', 1)]
self.assertTrue(index_item in info)
self.assertIn(index_item, info)
def test_compound_key_embedded(self):
@@ -680,33 +816,210 @@ class IndexesTest(unittest.TestCase):
name = StringField(required=True)
term = StringField(required=True)
class Report(Document):
class ReportEmbedded(Document):
key = EmbeddedDocumentField(CompoundKey, primary_key=True)
text = StringField()
Report.drop_collection()
my_key = CompoundKey(name="n", term="ok")
report = Report(text="OK", key=my_key).save()
report = ReportEmbedded(text="OK", key=my_key).save()
self.assertEqual({'text': 'OK', '_id': {'term': 'ok', 'name': 'n'}},
report.to_mongo())
self.assertEqual(report, Report.objects.get(pk=my_key))
self.assertEqual(report, ReportEmbedded.objects.get(pk=my_key))
def test_compound_key_dictfield(self):
class Report(Document):
class ReportDictField(Document):
key = DictField(primary_key=True)
text = StringField()
Report.drop_collection()
my_key = {"name": "n", "term": "ok"}
report = Report(text="OK", key=my_key).save()
report = ReportDictField(text="OK", key=my_key).save()
self.assertEqual({'text': 'OK', '_id': {'term': 'ok', 'name': 'n'}},
report.to_mongo())
self.assertEqual(report, Report.objects.get(pk=my_key))
# We can't directly call ReportDictField.objects.get(pk=my_key),
# because dicts are unordered, and if the order in MongoDB is
# different than the one in `my_key`, this test will fail.
self.assertEqual(report, ReportDictField.objects.get(pk__name=my_key['name']))
self.assertEqual(report, ReportDictField.objects.get(pk__term=my_key['term']))
def test_string_indexes(self):
class MyDoc(Document):
provider_ids = DictField()
meta = {
"indexes": ["provider_ids.foo", "provider_ids.bar"],
}
info = MyDoc.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertIn([('provider_ids.foo', 1)], info)
self.assertIn([('provider_ids.bar', 1)], info)
def test_sparse_compound_indexes(self):
class MyDoc(Document):
provider_ids = DictField()
meta = {
"indexes": [{'fields': ("provider_ids.foo", "provider_ids.bar"),
'sparse': True}],
}
info = MyDoc.objects._collection.index_information()
self.assertEqual([('provider_ids.foo', 1), ('provider_ids.bar', 1)],
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
@requires_mongodb_gte_26
def test_text_indexes(self):
class Book(Document):
title = DictField()
meta = {
"indexes": ["$title"],
}
indexes = Book.objects._collection.index_information()
self.assertIn("title_text", indexes)
key = indexes["title_text"]["key"]
self.assertIn(('_fts', 'text'), key)
def test_hashed_indexes(self):
class Book(Document):
ref_id = StringField()
meta = {
"indexes": ["#ref_id"],
}
indexes = Book.objects._collection.index_information()
self.assertIn("ref_id_hashed", indexes)
self.assertIn(('ref_id', 'hashed'), indexes["ref_id_hashed"]["key"])
def test_indexes_after_database_drop(self):
"""
Test to ensure that indexes are re-created on a collection even
after the database has been dropped.
Issue #812
"""
# Use a new connection and database since dropping the database could
# cause concurrent tests to fail.
connection = connect(db='tempdatabase',
alias='test_indexes_after_database_drop')
class BlogPost(Document):
title = StringField()
slug = StringField(unique=True)
meta = {'db_alias': 'test_indexes_after_database_drop'}
try:
BlogPost.drop_collection()
# Create Post #1
post1 = BlogPost(title='test1', slug='test')
post1.save()
# Drop the Database
connection.drop_database('tempdatabase')
# Re-create Post #1
post1 = BlogPost(title='test1', slug='test')
post1.save()
# Create Post #2
post2 = BlogPost(title='test2', slug='test')
self.assertRaises(NotUniqueError, post2.save)
finally:
# Drop the temporary database at the end
connection.drop_database('tempdatabase')
def test_index_dont_send_cls_option(self):
"""
Ensure that 'cls' option is not sent through ensureIndex. We shouldn't
send internal MongoEngine arguments that are not a part of the index
spec.
This is directly related to the fact that MongoDB doesn't validate the
options that are passed to ensureIndex. For more details, see:
https://jira.mongodb.org/browse/SERVER-769
"""
class TestDoc(Document):
txt = StringField()
meta = {
'allow_inheritance': True,
'indexes': [
{'fields': ('txt',), 'cls': False}
]
}
class TestChildDoc(TestDoc):
txt2 = StringField()
meta = {
'indexes': [
{'fields': ('txt2',), 'cls': False}
]
}
TestDoc.drop_collection()
TestDoc.ensure_indexes()
TestChildDoc.ensure_indexes()
index_info = TestDoc._get_collection().index_information()
for key in index_info:
del index_info[key]['v'] # drop the index version - we don't care about that here
if 'ns' in index_info[key]:
del index_info[key]['ns'] # drop the index namespace - we don't care about that here, MongoDB 3+
if 'dropDups' in index_info[key]:
del index_info[key]['dropDups'] # drop the index dropDups - it is deprecated in MongoDB 3+
self.assertEqual(index_info, {
'txt_1': {
'key': [('txt', 1)],
'background': False
},
'_id_': {
'key': [('_id', 1)],
},
'txt2_1': {
'key': [('txt2', 1)],
'background': False
},
'_cls_1': {
'key': [('_cls', 1)],
'background': False,
}
})
def test_compound_index_underscore_cls_not_overwritten(self):
"""
Test that the compound index doesn't get another _cls when it is specified
"""
class TestDoc(Document):
shard_1 = StringField()
txt_1 = StringField()
meta = {
'collection': 'test',
'allow_inheritance': True,
'sparse': True,
'shard_key': 'shard_1',
'indexes': [
('shard_1', '_cls', 'txt_1'),
]
}
TestDoc.drop_collection()
TestDoc.ensure_indexes()
index_info = TestDoc._get_collection().index_information()
self.assertIn('shard_1_1__cls_1_txt_1_1', index_info)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,17 +1,12 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import unittest
import warnings
from datetime import datetime
from tests.fixtures import Base
from mongoengine import Document, EmbeddedDocument, connect
from mongoengine import (BooleanField, Document, EmbeddedDocument,
EmbeddedDocumentField, GenericReferenceField,
IntField, ReferenceField, StringField, connect)
from mongoengine.connection import get_db
from mongoengine.fields import (BooleanField, GenericReferenceField,
IntField, StringField)
from tests.fixtures import Base
__all__ = ('InheritanceTest', )
@@ -28,6 +23,27 @@ class InheritanceTest(unittest.TestCase):
continue
self.db.drop_collection(collection)
def test_constructor_cls(self):
# Ensures _cls is properly set during construction
# and when object gets reloaded (prevent regression of #1950)
class EmbedData(EmbeddedDocument):
data = StringField()
meta = {'allow_inheritance': True}
class DataDoc(Document):
name = StringField()
embed = EmbeddedDocumentField(EmbedData)
meta = {'allow_inheritance': True}
test_doc = DataDoc(name='test', embed=EmbedData(data='data'))
assert test_doc._cls == 'DataDoc'
assert test_doc.embed._cls == 'EmbedData'
test_doc.save()
saved_doc = DataDoc.objects.with_id(test_doc.id)
assert test_doc._cls == saved_doc._cls
assert test_doc.embed._cls == saved_doc.embed._cls
test_doc.delete()
def test_superclasses(self):
"""Ensure that the correct list of superclasses is assembled.
"""
@@ -163,7 +179,7 @@ class InheritanceTest(unittest.TestCase):
class Employee(Person):
salary = IntField()
self.assertEqual(['age', 'id', 'name', 'salary'],
self.assertEqual(['_cls', 'age', 'id', 'name', 'salary'],
sorted(Employee._fields.keys()))
self.assertEqual(Employee._get_collection_name(),
Person._get_collection_name())
@@ -180,7 +196,7 @@ class InheritanceTest(unittest.TestCase):
class Employee(Person):
salary = IntField()
self.assertEqual(['age', 'id', 'name', 'salary'],
self.assertEqual(['_cls', 'age', 'id', 'name', 'salary'],
sorted(Employee._fields.keys()))
self.assertEqual(Person(name="Bob", age=35).to_mongo().keys(),
['_cls', 'name', 'age'])
@@ -189,6 +205,41 @@ class InheritanceTest(unittest.TestCase):
self.assertEqual(Employee._get_collection_name(),
Person._get_collection_name())
def test_indexes_and_multiple_inheritance(self):
""" Ensure that all of the indexes are created for a document with
multiple inheritance.
"""
class A(Document):
a = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['a']
}
class B(Document):
b = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['b']
}
class C(A, B):
pass
A.drop_collection()
B.drop_collection()
C.drop_collection()
C.ensure_indexes()
self.assertEqual(
sorted([idx['key'] for idx in C._get_collection().index_information().values()]),
sorted([[(u'_cls', 1), (u'b', 1)], [(u'_id', 1)], [(u'_cls', 1), (u'a', 1)]])
)
def test_polymorphic_queries(self):
"""Ensure that the correct subclasses are returned from a query
"""
@@ -218,18 +269,17 @@ class InheritanceTest(unittest.TestCase):
self.assertEqual(classes, [Human])
def test_allow_inheritance(self):
"""Ensure that inheritance may be disabled on simple classes and that
_cls and _subclasses will not be used.
"""Ensure that inheritance is disabled by default on simple
classes and that _cls will not be used.
"""
class Animal(Document):
name = StringField()
def create_dog_class():
# can't inherit because Animal didn't explicitly allow inheritance
with self.assertRaises(ValueError) as cm:
class Dog(Animal):
pass
self.assertRaises(ValueError, create_dog_class)
self.assertIn("Document Animal may not be subclassed", str(cm.exception))
# Check that _cls etc aren't present on simple documents
dog = Animal(name='dog').save()
@@ -237,20 +287,19 @@ class InheritanceTest(unittest.TestCase):
collection = self.db[Animal._get_collection_name()]
obj = collection.find_one()
self.assertFalse('_cls' in obj)
self.assertNotIn('_cls', obj)
def test_cant_turn_off_inheritance_on_subclass(self):
"""Ensure if inheritance is on in a subclass you cant turn it off
"""Ensure if inheritance is on in a subclass you cant turn it off.
"""
class Animal(Document):
name = StringField()
meta = {'allow_inheritance': True}
def create_mammal_class():
with self.assertRaises(ValueError) as cm:
class Mammal(Animal):
meta = {'allow_inheritance': False}
self.assertRaises(ValueError, create_mammal_class)
self.assertEqual(str(cm.exception), 'Only direct subclasses of Document may set "allow_inheritance" to False')
def test_allow_inheritance_abstract_document(self):
"""Ensure that abstract documents can set inheritance rules and that
@@ -263,37 +312,131 @@ class InheritanceTest(unittest.TestCase):
class Animal(FinalDocument):
name = StringField()
def create_mammal_class():
with self.assertRaises(ValueError) as cm:
class Mammal(Animal):
pass
self.assertRaises(ValueError, create_mammal_class)
# Check that _cls isn't present in simple documents
doc = Animal(name='dog')
self.assertFalse('_cls' in doc.to_mongo())
self.assertNotIn('_cls', doc.to_mongo())
def test_using_abstract_class_in_reference_field(self):
# Ensures no regression of #1920
class AbstractHuman(Document):
meta = {'abstract': True}
class Dad(AbstractHuman):
name = StringField()
class Home(Document):
dad = ReferenceField(AbstractHuman) # Referencing the abstract class
address = StringField()
dad = Dad(name='5').save()
Home(dad=dad, address='street').save()
home = Home.objects.first()
home.address = 'garbage'
home.save() # Was failing with ValidationError
def test_abstract_class_referencing_self(self):
# Ensures no regression of #1920
class Human(Document):
meta = {'abstract': True}
creator = ReferenceField('self', dbref=True)
class User(Human):
name = StringField()
user = User(name='John').save()
user2 = User(name='Foo', creator=user).save()
user2 = User.objects.with_id(user2.id)
user2.name = 'Bar'
user2.save() # Was failing with ValidationError
def test_abstract_handle_ids_in_metaclass_properly(self):
class City(Document):
continent = StringField()
meta = {'abstract': True,
'allow_inheritance': False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], 'id')
def test_auto_id_not_set_if_specific_in_parent_class(self):
class City(Document):
continent = StringField()
city_id = IntField(primary_key=True)
meta = {'abstract': True,
'allow_inheritance': False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 3)
self.assertEqual(berlin._fields_ordered[0], 'city_id')
def test_auto_id_vs_non_pk_id_field(self):
class City(Document):
continent = StringField()
id = IntField()
meta = {'abstract': True,
'allow_inheritance': False}
class EuropeanCity(City):
name = StringField()
berlin = EuropeanCity(name='Berlin', continent='Europe')
self.assertEqual(len(berlin._db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._reverse_db_field_map), len(berlin._fields_ordered))
self.assertEqual(len(berlin._fields_ordered), 4)
self.assertEqual(berlin._fields_ordered[0], 'auto_id_0')
berlin.save()
self.assertEqual(berlin.pk, berlin.auto_id_0)
def test_abstract_document_creation_does_not_fail(self):
class City(Document):
continent = StringField()
meta = {'abstract': True,
'allow_inheritance': False}
city = City(continent='asia')
self.assertEqual(None, city.pk)
# TODO: expected error? Shouldn't we create a new error type?
with self.assertRaises(KeyError):
setattr(city, 'pk', 1)
def test_allow_inheritance_embedded_document(self):
"""Ensure embedded documents respect inheritance
"""
"""Ensure embedded documents respect inheritance."""
class Comment(EmbeddedDocument):
content = StringField()
def create_special_comment():
with self.assertRaises(ValueError):
class SpecialComment(Comment):
pass
self.assertRaises(ValueError, create_special_comment)
doc = Comment(content='test')
self.assertFalse('_cls' in doc.to_mongo())
self.assertNotIn('_cls', doc.to_mongo())
class Comment(EmbeddedDocument):
content = StringField()
meta = {'allow_inheritance': True}
doc = Comment(content='test')
self.assertTrue('_cls' in doc.to_mongo())
self.assertIn('_cls', doc.to_mongo())
def test_document_inheritance(self):
"""Ensure mutliple inheritance of abstract documents
@@ -313,7 +456,7 @@ class InheritanceTest(unittest.TestCase):
try:
class MyDocument(DateCreatedDocument, DateUpdatedDocument):
pass
except:
except Exception:
self.assertTrue(False, "Couldn't create MyDocument class")
def test_abstract_documents(self):
@@ -346,8 +489,8 @@ class InheritanceTest(unittest.TestCase):
for cls in [Animal, Fish, Guppy]:
self.assertEqual(cls._meta[k], v)
self.assertFalse('collection' in Animal._meta)
self.assertFalse('collection' in Mammal._meta)
self.assertNotIn('collection', Animal._meta)
self.assertNotIn('collection', Mammal._meta)
self.assertEqual(Animal._get_collection_name(), None)
self.assertEqual(Mammal._get_collection_name(), None)
@@ -356,11 +499,21 @@ class InheritanceTest(unittest.TestCase):
self.assertEqual(Guppy._get_collection_name(), 'fish')
self.assertEqual(Human._get_collection_name(), 'human')
def create_bad_abstract():
# ensure that a subclass of a non-abstract class can't be abstract
with self.assertRaises(ValueError):
class EvilHuman(Human):
evil = BooleanField(default=True)
meta = {'abstract': True}
self.assertRaises(ValueError, create_bad_abstract)
def test_abstract_embedded_documents(self):
# 789: EmbeddedDocument shouldn't inherit abstract
class A(EmbeddedDocument):
meta = {"abstract": True}
class B(A):
pass
self.assertFalse(B._meta["abstract"])
def test_inherited_collections(self):
"""Ensure that subclassed documents don't override parents'

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,3 @@
import sys
sys.path[0:0] = [""]
import unittest
import uuid
@@ -20,6 +17,28 @@ class TestJson(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
def test_json_names(self):
"""
Going to test reported issue:
https://github.com/MongoEngine/mongoengine/issues/654
where the reporter asks for the availability to perform
a to_json with the original class names and not the abreviated
mongodb document keys
"""
class Embedded(EmbeddedDocument):
string = StringField(db_field='s')
class Doc(Document):
string = StringField(db_field='s')
embedded = EmbeddedDocumentField(Embedded, db_field='e')
doc = Doc( string="Hello", embedded=Embedded(string="Inner Hello"))
doc_json = doc.to_json(sort_keys=True, use_db_field=False,separators=(',', ':'))
expected_json = """{"embedded":{"string":"Inner Hello"},"string":"Hello"}"""
self.assertEqual( doc_json, expected_json)
def test_json_simple(self):
class Embedded(EmbeddedDocument):
@@ -29,8 +48,16 @@ class TestJson(unittest.TestCase):
string = StringField()
embedded_field = EmbeddedDocumentField(Embedded)
def __eq__(self, other):
return (self.string == other.string and
self.embedded_field == other.embedded_field)
doc = Doc(string="Hi", embedded_field=Embedded(string="Hi"))
doc_json = doc.to_json(sort_keys=True, separators=(',', ':'))
expected_json = """{"embedded_field":{"string":"Hi"},"string":"Hi"}"""
self.assertEqual(doc_json, expected_json)
self.assertEqual(doc, Doc.from_json(doc.to_json()))
def test_json_complex(self):
@@ -73,6 +100,10 @@ class TestJson(unittest.TestCase):
generic_embedded_document_field = GenericEmbeddedDocumentField(
default=lambda: EmbeddedDoc())
def __eq__(self, other):
import json
return json.loads(self.to_json()) == json.loads(other.to_json())
doc = Doc()
self.assertEqual(doc, Doc.from_json(doc.to_json()))

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import unittest
from datetime import datetime
@@ -23,16 +20,16 @@ class ValidatorErrorTest(unittest.TestCase):
# 1st level error schema
error.errors = {'1st': ValidationError('bad 1st'), }
self.assertTrue('1st' in error.to_dict())
self.assertIn('1st', error.to_dict())
self.assertEqual(error.to_dict()['1st'], 'bad 1st')
# 2nd level error schema
error.errors = {'1st': ValidationError('bad 1st', errors={
'2nd': ValidationError('bad 2nd'),
})}
self.assertTrue('1st' in error.to_dict())
self.assertTrue(isinstance(error.to_dict()['1st'], dict))
self.assertTrue('2nd' in error.to_dict()['1st'])
self.assertIn('1st', error.to_dict())
self.assertIsInstance(error.to_dict()['1st'], dict)
self.assertIn('2nd', error.to_dict()['1st'])
self.assertEqual(error.to_dict()['1st']['2nd'], 'bad 2nd')
# moar levels
@@ -43,10 +40,10 @@ class ValidatorErrorTest(unittest.TestCase):
}),
}),
})}
self.assertTrue('1st' in error.to_dict())
self.assertTrue('2nd' in error.to_dict()['1st'])
self.assertTrue('3rd' in error.to_dict()['1st']['2nd'])
self.assertTrue('4th' in error.to_dict()['1st']['2nd']['3rd'])
self.assertIn('1st', error.to_dict())
self.assertIn('2nd', error.to_dict()['1st'])
self.assertIn('3rd', error.to_dict()['1st']['2nd'])
self.assertIn('4th', error.to_dict()['1st']['2nd']['3rd'])
self.assertEqual(error.to_dict()['1st']['2nd']['3rd']['4th'],
'Inception')
@@ -60,8 +57,8 @@ class ValidatorErrorTest(unittest.TestCase):
try:
User().validate()
except ValidationError, e:
self.assertTrue("User:None" in e.message)
except ValidationError as e:
self.assertIn("User:None", e.message)
self.assertEqual(e.to_dict(), {
'username': 'Field is required',
'name': 'Field is required'})
@@ -70,8 +67,8 @@ class ValidatorErrorTest(unittest.TestCase):
user.name = None
try:
user.save()
except ValidationError, e:
self.assertTrue("User:RossC0" in e.message)
except ValidationError as e:
self.assertIn("User:RossC0", e.message)
self.assertEqual(e.to_dict(), {
'name': 'Field is required'})
@@ -118,8 +115,8 @@ class ValidatorErrorTest(unittest.TestCase):
try:
Doc(id="bad").validate()
except ValidationError, e:
self.assertTrue("SubDoc:None" in e.message)
except ValidationError as e:
self.assertIn("SubDoc:None", e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
@@ -130,17 +127,88 @@ class ValidatorErrorTest(unittest.TestCase):
doc = Doc.objects.first()
keys = doc._data.keys()
self.assertEqual(2, len(keys))
self.assertTrue('e' in keys)
self.assertTrue('id' in keys)
self.assertIn('e', keys)
self.assertIn('id', keys)
doc.e.val = "OK"
try:
doc.save()
except ValidationError, e:
self.assertTrue("Doc:test" in e.message)
except ValidationError as e:
self.assertIn("Doc:test", e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
def test_embedded_weakref(self):
class SubDoc(EmbeddedDocument):
val = IntField(required=True)
class Doc(Document):
e = EmbeddedDocumentField(SubDoc, db_field='eb')
Doc.drop_collection()
d1 = Doc()
d2 = Doc()
s = SubDoc()
self.assertRaises(ValidationError, s.validate)
d1.e = s
d2.e = s
del d1
self.assertRaises(ValidationError, d2.validate)
def test_parent_reference_in_child_document(self):
"""
Test to ensure a ReferenceField can store a reference to a parent
class when inherited. Issue #954.
"""
class Parent(Document):
meta = {'allow_inheritance': True}
reference = ReferenceField('self')
class Child(Parent):
pass
parent = Parent()
parent.save()
child = Child(reference=parent)
# Saving child should not raise a ValidationError
try:
child.save()
except ValidationError as e:
self.fail("ValidationError raised: %s" % e.message)
def test_parent_reference_set_as_attribute_in_child_document(self):
"""
Test to ensure a ReferenceField can store a reference to a parent
class when inherited and when set via attribute. Issue #954.
"""
class Parent(Document):
meta = {'allow_inheritance': True}
reference = ReferenceField('self')
class Child(Parent):
pass
parent = Parent()
parent.save()
child = Child()
child.reference = parent
# Saving the child should not raise a ValidationError
try:
child.save()
except ValidationError as e:
self.fail("ValidationError raised: %s" % e.message)
if __name__ == '__main__':
unittest.main()

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,28 +1,30 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import copy
import os
import unittest
import tempfile
import gridfs
import six
from nose.plugins.skip import SkipTest
from mongoengine import *
from mongoengine.connection import get_db
from mongoengine.python_support import PY3, b, StringIO
from mongoengine.python_support import StringIO
try:
from PIL import Image
HAS_PIL = True
except ImportError:
HAS_PIL = False
from tests.utils import MongoDBTestCase
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__), 'mongoengine.png')
TEST_IMAGE2_PATH = os.path.join(os.path.dirname(__file__), 'mongodb_leaf.png')
class FileTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class FileTest(MongoDBTestCase):
def tearDown(self):
self.db.drop_collection('fs.files')
@@ -43,15 +45,16 @@ class FileTest(unittest.TestCase):
PutFile.drop_collection()
text = b('Hello, World!')
text = six.b('Hello, World!')
content_type = 'text/plain'
putfile = PutFile()
putfile.the_file.put(text, content_type=content_type)
putfile.the_file.put(text, content_type=content_type, filename="hello")
putfile.save()
result = PutFile.objects.first()
self.assertTrue(putfile == result)
self.assertEqual(putfile, result)
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello (%s)>" % result.the_file.grid_id)
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.delete() # Remove file from GridFS
@@ -68,7 +71,7 @@ class FileTest(unittest.TestCase):
putfile.save()
result = PutFile.objects.first()
self.assertTrue(putfile == result)
self.assertEqual(putfile, result)
self.assertEqual(result.the_file.read(), text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.delete()
@@ -81,8 +84,8 @@ class FileTest(unittest.TestCase):
StreamFile.drop_collection()
text = b('Hello, World!')
more_text = b('Foo Bar')
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
content_type = 'text/plain'
streamfile = StreamFile()
@@ -93,7 +96,7 @@ class FileTest(unittest.TestCase):
streamfile.save()
result = StreamFile.objects.first()
self.assertTrue(streamfile == result)
self.assertEqual(streamfile, result)
self.assertEqual(result.the_file.read(), text + more_text)
self.assertEqual(result.the_file.content_type, content_type)
result.the_file.seek(0)
@@ -105,15 +108,51 @@ class FileTest(unittest.TestCase):
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() == None)
self.assertTrue(result.the_file.read() is None)
def test_file_fields_stream_after_none(self):
"""Ensure that a file field can be written to after it has been saved as
None
"""
class StreamFile(Document):
the_file = FileField()
StreamFile.drop_collection()
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
content_type = 'text/plain'
streamfile = StreamFile()
streamfile.save()
streamfile.the_file.new_file()
streamfile.the_file.write(text)
streamfile.the_file.write(more_text)
streamfile.the_file.close()
streamfile.save()
result = StreamFile.objects.first()
self.assertEqual(streamfile, result)
self.assertEqual(result.the_file.read(), text + more_text)
# self.assertEqual(result.the_file.content_type, content_type)
result.the_file.seek(0)
self.assertEqual(result.the_file.tell(), 0)
self.assertEqual(result.the_file.read(len(text)), text)
self.assertEqual(result.the_file.tell(), len(text))
self.assertEqual(result.the_file.read(len(more_text)), more_text)
self.assertEqual(result.the_file.tell(), len(text + more_text))
result.the_file.delete()
# Ensure deleted file returns None
self.assertTrue(result.the_file.read() is None)
def test_file_fields_set(self):
class SetFile(Document):
the_file = FileField()
text = b('Hello, World!')
more_text = b('Foo Bar')
text = six.b('Hello, World!')
more_text = six.b('Foo Bar')
SetFile.drop_collection()
@@ -122,7 +161,7 @@ class FileTest(unittest.TestCase):
setfile.save()
result = SetFile.objects.first()
self.assertTrue(setfile == result)
self.assertEqual(setfile, result)
self.assertEqual(result.the_file.read(), text)
# Try replacing file with new one
@@ -130,7 +169,7 @@ class FileTest(unittest.TestCase):
result.save()
result = SetFile.objects.first()
self.assertTrue(setfile == result)
self.assertEqual(setfile, result)
self.assertEqual(result.the_file.read(), more_text)
result.the_file.delete()
@@ -142,7 +181,7 @@ class FileTest(unittest.TestCase):
GridDocument.drop_collection()
with tempfile.TemporaryFile() as f:
f.write(b("Hello World!"))
f.write(six.b("Hello World!"))
f.flush()
# Test without default
@@ -159,7 +198,7 @@ class FileTest(unittest.TestCase):
self.assertEqual(doc_b.the_file.grid_id, doc_c.the_file.grid_id)
# Test with default
doc_d = GridDocument(the_file=b(''))
doc_d = GridDocument(the_file=six.b(''))
doc_d.save()
doc_e = GridDocument.objects.with_id(doc_d.id)
@@ -185,15 +224,15 @@ class FileTest(unittest.TestCase):
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(b('Hello, World!'))
test_file.the_file.put(six.b('Hello, World!'))
test_file.save()
# Second instance
test_file_dupe = TestFile()
data = test_file_dupe.the_file.read() # Should be None
self.assertTrue(test_file.name != test_file_dupe.name)
self.assertTrue(test_file.the_file.read() != data)
self.assertNotEqual(test_file.name, test_file_dupe.name)
self.assertNotEqual(test_file.the_file.read(), data)
TestFile.drop_collection()
@@ -239,7 +278,7 @@ class FileTest(unittest.TestCase):
test_file = TestFile()
self.assertFalse(bool(test_file.the_file))
test_file.the_file.put(b('Hello, World!'), content_type='text/plain')
test_file.the_file.put(six.b('Hello, World!'), content_type='text/plain')
test_file.save()
self.assertTrue(bool(test_file.the_file))
@@ -252,17 +291,93 @@ class FileTest(unittest.TestCase):
the_file = FileField()
test_file = TestFile()
self.assertFalse(test_file.the_file in [{"test": 1}])
self.assertNotIn(test_file.the_file, [{"test": 1}])
def test_file_disk_space(self):
""" Test disk space usage when we delete/replace a file """
class TestFile(Document):
the_file = FileField()
text = six.b('Hello, World!')
content_type = 'text/plain'
testfile = TestFile()
testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save()
# Now check fs.files and fs.chunks
db = TestFile._get_db()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 1)
self.assertEquals(len(list(chunks)), 1)
# Deleting the docoument should delete the files
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
# Test case where we don't store a file in the first place
testfile = TestFile()
testfile.save()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
# Test case where we overwrite the file
testfile = TestFile()
testfile.the_file.put(text, content_type=content_type, filename="hello")
testfile.save()
text = six.b('Bonjour, World!')
testfile.the_file.replace(text, content_type=content_type, filename="hello")
testfile.save()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 1)
self.assertEquals(len(list(chunks)), 1)
testfile.delete()
files = db.fs.files.find()
chunks = db.fs.chunks.find()
self.assertEquals(len(list(files)), 0)
self.assertEquals(len(list(chunks)), 0)
def test_image_field(self):
if PY3:
raise SkipTest('PIL does not have Python 3 support')
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField()
TestImage.drop_collection()
with tempfile.TemporaryFile() as f:
f.write(six.b("Hello World!"))
f.flush()
t = TestImage()
try:
t.image.put(f)
self.fail("Should have raised an invalidation error")
except ValidationError as e:
self.assertEqual("%s" % e, "Invalid image: cannot identify image file %s" % f)
t = TestImage()
t.image.put(open(TEST_IMAGE_PATH, 'rb'))
t.save()
@@ -278,8 +393,8 @@ class FileTest(unittest.TestCase):
t.image.delete()
def test_image_field_reassigning(self):
if PY3:
raise SkipTest('PIL does not have Python 3 support')
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestFile(Document):
the_file = ImageField()
@@ -294,8 +409,8 @@ class FileTest(unittest.TestCase):
self.assertEqual(test_file.the_file.size, (45, 101))
def test_image_field_resize(self):
if PY3:
raise SkipTest('PIL does not have Python 3 support')
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField(size=(185, 37))
@@ -317,8 +432,8 @@ class FileTest(unittest.TestCase):
t.image.delete()
def test_image_field_resize_force(self):
if PY3:
raise SkipTest('PIL does not have Python 3 support')
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField(size=(185, 37, True))
@@ -340,8 +455,8 @@ class FileTest(unittest.TestCase):
t.image.delete()
def test_image_field_thumbnail(self):
if PY3:
raise SkipTest('PIL does not have Python 3 support')
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image = ImageField(thumbnail_size=(92, 18))
@@ -377,16 +492,23 @@ class FileTest(unittest.TestCase):
# First instance
test_file = TestFile()
test_file.name = "Hello, World!"
test_file.the_file.put(b('Hello, World!'),
test_file.the_file.put(six.b('Hello, World!'),
name="hello.txt")
test_file.save()
data = get_db("test_files").macumba.files.find_one()
self.assertEqual(data.get('name'), 'hello.txt')
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(), six.b('Hello, World!'))
test_file = TestFile.objects.first()
test_file.the_file = six.b('HELLO, WORLD!')
test_file.save()
test_file = TestFile.objects.first()
self.assertEqual(test_file.the_file.read(),
b('Hello, World!'))
six.b('HELLO, WORLD!'))
def test_copyable(self):
class PutFile(Document):
@@ -394,7 +516,7 @@ class FileTest(unittest.TestCase):
PutFile.drop_collection()
text = b('Hello, World!')
text = six.b('Hello, World!')
content_type = 'text/plain'
putfile = PutFile()
@@ -407,6 +529,54 @@ class FileTest(unittest.TestCase):
self.assertEqual(putfile, copy.copy(putfile))
self.assertEqual(putfile, copy.deepcopy(putfile))
def test_get_image_by_grid_id(self):
if not HAS_PIL:
raise SkipTest('PIL not installed')
class TestImage(Document):
image1 = ImageField()
image2 = ImageField()
TestImage.drop_collection()
t = TestImage()
t.image1.put(open(TEST_IMAGE_PATH, 'rb'))
t.image2.put(open(TEST_IMAGE2_PATH, 'rb'))
t.save()
test = TestImage.objects.first()
grid_id = test.image1.grid_id
self.assertEqual(1, TestImage.objects(Q(image1=grid_id)
or Q(image2=grid_id)).count())
def test_complex_field_filefield(self):
"""Ensure you can add meta data to file"""
class Animal(Document):
genus = StringField()
family = StringField()
photos = ListField(FileField())
Animal.drop_collection()
marmot = Animal(genus='Marmota', family='Sciuridae')
marmot_photo = open(TEST_IMAGE_PATH, 'rb') # Retrieve a photo from disk
photos_field = marmot._fields['photos'].field
new_proxy = photos_field.get_proxy_obj('photos', marmot)
new_proxy.put(marmot_photo, content_type='image/jpeg', foo='bar')
marmot_photo.close()
marmot.photos.append(new_proxy)
marmot.save()
marmot = Animal.objects.get()
self.assertEqual(marmot.photos[0].content_type, 'image/jpeg')
self.assertEqual(marmot.photos[0].foo, 'bar')
self.assertEqual(marmot.photos[0].get().length, 8313)
if __name__ == '__main__':
unittest.main()

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import unittest
from mongoengine import *
@@ -19,8 +16,8 @@ class GeoFieldTest(unittest.TestCase):
def _test_for_expected_error(self, Cls, loc, expected):
try:
Cls(loc=loc).validate()
self.fail()
except ValidationError, e:
self.fail('Should not validate the location {0}'.format(loc))
except ValidationError as e:
self.assertEqual(expected, e.to_dict()['loc'])
def test_geopoint_validation(self):
@@ -75,6 +72,12 @@ class GeoFieldTest(unittest.TestCase):
self._test_for_expected_error(Location, coord, expected)
Location(loc=[1, 2]).validate()
Location(loc={
"type": "Point",
"coordinates": [
81.4471435546875,
23.61432859499169
]}).validate()
def test_linestring_validation(self):
class Location(Document):
@@ -109,7 +112,7 @@ class GeoFieldTest(unittest.TestCase):
expected = "Invalid LineString:\nBoth values (%s) in point must be float or int" % repr(coord[0])
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[1, 2], [3, 4], [5, 6], [1,2]]).validate()
Location(loc=[[1, 2], [3, 4], [5, 6], [1, 2]]).validate()
def test_polygon_validation(self):
class Location(Document):
@@ -149,6 +152,117 @@ class GeoFieldTest(unittest.TestCase):
Location(loc=[[[1, 2], [3, 4], [5, 6], [1, 2]]]).validate()
def test_multipoint_validation(self):
class Location(Document):
loc = MultiPointField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiPointField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'MultiPointField type must be "MultiPoint"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiPoint", "coordinates": [[1, 2, 3]]}
expected = "Value ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[]]
expected = "Invalid MultiPoint must contain at least one valid point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1]], [[1, 2, 3]]]
for coord in invalid_coords:
expected = "Value (%s) must be a two-dimensional point" % repr(coord[0])
self._test_for_expected_error(Location, coord, expected)
invalid_coords = [[[{}, {}]], [("a", "b")]]
for coord in invalid_coords:
expected = "Both values (%s) in point must be float or int" % repr(coord[0])
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[1, 2]]).validate()
Location(loc={
"type": "MultiPoint",
"coordinates": [
[1, 2],
[81.4471435546875, 23.61432859499169]
]}).validate()
def test_multilinestring_validation(self):
class Location(Document):
loc = MultiLineStringField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiLineStringField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'MultiLineStringField type must be "MultiLineString"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiLineString", "coordinates": [[[1, 2, 3]]]}
expected = "Invalid MultiLineString:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [5, "a"]
expected = "Invalid MultiLineString must contain at least one valid linestring"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1]]]
expected = "Invalid MultiLineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0][0])
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[1, 2, 3]]]
expected = "Invalid MultiLineString:\nValue (%s) must be a two-dimensional point" % repr(invalid_coords[0][0])
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[{}, {}]]], [[("a", "b")]]]
for coord in invalid_coords:
expected = "Invalid MultiLineString:\nBoth values (%s) in point must be float or int" % repr(coord[0][0])
self._test_for_expected_error(Location, coord, expected)
Location(loc=[[[1, 2], [3, 4], [5, 6], [1, 2]]]).validate()
def test_multipolygon_validation(self):
class Location(Document):
loc = MultiPolygonField()
invalid_coords = {"x": 1, "y": 2}
expected = 'MultiPolygonField can only accept a valid GeoJson dictionary or lists of (x, y)'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MadeUp", "coordinates": [[]]}
expected = 'MultiPolygonField type must be "MultiPolygon"'
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = {"type": "MultiPolygon", "coordinates": [[[[1, 2, 3]]]]}
expected = "Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[5, "a"]]]]
expected = "Invalid MultiPolygon:\nBoth values ([5, 'a']) in point must be float or int"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[]]]]
expected = "Invalid MultiPolygon must contain at least one valid Polygon"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[1, 2, 3]]]]
expected = "Invalid MultiPolygon:\nValue ([1, 2, 3]) must be a two-dimensional point"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[{}, {}]]], [[("a", "b")]]]
expected = "Invalid MultiPolygon:\nBoth values ([{}, {}]) in point must be float or int, Both values (('a', 'b')) in point must be float or int"
self._test_for_expected_error(Location, invalid_coords, expected)
invalid_coords = [[[[1, 2], [3, 4]]]]
expected = "Invalid MultiPolygon:\nLineStrings must start and end at the same point"
self._test_for_expected_error(Location, invalid_coords, expected)
Location(loc=[[[[1, 2], [3, 4], [5, 6], [1, 2]]]]).validate()
def test_indexes_geopoint(self):
"""Ensure that indexes are created automatically for GeoPointFields.
"""
@@ -184,9 +298,9 @@ class GeoFieldTest(unittest.TestCase):
polygon = PolygonField()
geo_indicies = Event._geo_indices()
self.assertTrue({'fields': [('line', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('polygon', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('point', '2dsphere')]} in geo_indicies)
self.assertIn({'fields': [('line', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('polygon', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('point', '2dsphere')]}, geo_indicies)
def test_indexes_2dsphere_embedded(self):
"""Ensure that indexes are created automatically for GeoPointFields.
@@ -202,9 +316,9 @@ class GeoFieldTest(unittest.TestCase):
venue = EmbeddedDocumentField(Venue)
geo_indicies = Event._geo_indices()
self.assertTrue({'fields': [('venue.line', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('venue.polygon', '2dsphere')]} in geo_indicies)
self.assertTrue({'fields': [('venue.point', '2dsphere')]} in geo_indicies)
self.assertIn({'fields': [('venue.line', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('venue.polygon', '2dsphere')]}, geo_indicies)
self.assertIn({'fields': [('venue.point', '2dsphere')]}, geo_indicies)
def test_geo_indexes_recursion(self):
@@ -219,12 +333,11 @@ class GeoFieldTest(unittest.TestCase):
Location.drop_collection()
Parent.drop_collection()
list(Parent.objects)
collection = Parent._get_collection()
info = collection.index_information()
self.assertFalse('location_2d' in info)
Parent(name='Berlin').save()
info = Parent._get_collection().index_information()
self.assertNotIn('location_2d', info)
info = Location._get_collection().index_information()
self.assertIn('location_2d', info)
self.assertEqual(len(Parent._geo_indices()), 0)
self.assertEqual(len(Location._geo_indices()), 1)

View File

@@ -17,6 +17,23 @@ class PickleTest(Document):
photo = FileField()
class NewDocumentPickleTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))
embedded = EmbeddedDocumentField(PickleEmbedded)
lists = ListField(StringField())
photo = FileField()
new_field = StringField()
class PickleDynamicEmbedded(DynamicEmbeddedDocument):
date = DateTimeField(default=datetime.now)
class PickleDynamicTest(DynamicDocument):
number = IntField()
class PickleSignalsTest(Document):
number = IntField()
string = StringField(choices=(('One', '1'), ('Two', '2')))

View File

@@ -1,8 +0,0 @@
from convert_to_new_inheritance_model import *
from decimalfield_as_float import *
from refrencefield_dbref_to_object_id import *
from turn_off_inheritance import *
from uuidfield_to_binary import *
if __name__ == '__main__':
unittest.main()

View File

@@ -1,51 +0,0 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import Document, connect
from mongoengine.connection import get_db
from mongoengine.fields import StringField
__all__ = ('ConvertToNewInheritanceModel', )
class ConvertToNewInheritanceModel(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
self.db.drop_collection(collection)
def test_how_to_convert_to_the_new_inheritance_model(self):
"""Demonstrates migrating from 0.7 to 0.8
"""
# 1. Declaration of the class
class Animal(Document):
name = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['name']
}
# 2. Remove _types
collection = Animal._get_collection()
collection.update({}, {"$unset": {"_types": 1}}, multi=True)
# 3. Confirm extra data is removed
count = collection.find({'_types': {"$exists": True}}).count()
self.assertEqual(0, count)
# 4. Remove indexes
info = collection.index_information()
indexes_to_drop = [key for key, value in info.iteritems()
if '_types' in dict(value['key'])]
for index in indexes_to_drop:
collection.drop_index(index)
# 5. Recreate indexes
Animal.ensure_indexes()

View File

@@ -1,50 +0,0 @@
# -*- coding: utf-8 -*-
import unittest
import decimal
from decimal import Decimal
from mongoengine import Document, connect
from mongoengine.connection import get_db
from mongoengine.fields import StringField, DecimalField, ListField
__all__ = ('ConvertDecimalField', )
class ConvertDecimalField(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def test_how_to_convert_decimal_fields(self):
"""Demonstrates migrating from 0.7 to 0.8
"""
# 1. Old definition - using dbrefs
class Person(Document):
name = StringField()
money = DecimalField(force_string=True)
monies = ListField(DecimalField(force_string=True))
Person.drop_collection()
Person(name="Wilson Jr", money=Decimal("2.50"),
monies=[Decimal("2.10"), Decimal("5.00")]).save()
# 2. Start the migration by changing the schema
# Change DecimalField - add precision and rounding settings
class Person(Document):
name = StringField()
money = DecimalField(precision=2, rounding=decimal.ROUND_HALF_UP)
monies = ListField(DecimalField(precision=2,
rounding=decimal.ROUND_HALF_UP))
# 3. Loop all the objects and mark parent as changed
for p in Person.objects:
p._mark_as_changed('money')
p._mark_as_changed('monies')
p.save()
# 4. Confirmation of the fix!
wilson = Person.objects(name="Wilson Jr").as_pymongo()[0]
self.assertTrue(isinstance(wilson['money'], float))
self.assertTrue(all([isinstance(m, float) for m in wilson['monies']]))

View File

@@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import Document, connect
from mongoengine.connection import get_db
from mongoengine.fields import StringField, ReferenceField, ListField
__all__ = ('ConvertToObjectIdsModel', )
class ConvertToObjectIdsModel(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def test_how_to_convert_to_object_id_reference_fields(self):
"""Demonstrates migrating from 0.7 to 0.8
"""
# 1. Old definition - using dbrefs
class Person(Document):
name = StringField()
parent = ReferenceField('self', dbref=True)
friends = ListField(ReferenceField('self', dbref=True))
Person.drop_collection()
p1 = Person(name="Wilson", parent=None).save()
f1 = Person(name="John", parent=None).save()
f2 = Person(name="Paul", parent=None).save()
f3 = Person(name="George", parent=None).save()
f4 = Person(name="Ringo", parent=None).save()
Person(name="Wilson Jr", parent=p1, friends=[f1, f2, f3, f4]).save()
# 2. Start the migration by changing the schema
# Change ReferenceField as now dbref defaults to False
class Person(Document):
name = StringField()
parent = ReferenceField('self')
friends = ListField(ReferenceField('self'))
# 3. Loop all the objects and mark parent as changed
for p in Person.objects:
p._mark_as_changed('parent')
p._mark_as_changed('friends')
p.save()
# 4. Confirmation of the fix!
wilson = Person.objects(name="Wilson Jr").as_pymongo()[0]
self.assertEqual(p1.id, wilson['parent'])
self.assertEqual([f1.id, f2.id, f3.id, f4.id], wilson['friends'])

View File

@@ -1,62 +0,0 @@
# -*- coding: utf-8 -*-
import unittest
from mongoengine import Document, connect
from mongoengine.connection import get_db
from mongoengine.fields import StringField
__all__ = ('TurnOffInheritanceTest', )
class TurnOffInheritanceTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def tearDown(self):
for collection in self.db.collection_names():
if 'system.' in collection:
continue
self.db.drop_collection(collection)
def test_how_to_turn_off_inheritance(self):
"""Demonstrates migrating from allow_inheritance = True to False.
"""
# 1. Old declaration of the class
class Animal(Document):
name = StringField()
meta = {
'allow_inheritance': True,
'indexes': ['name']
}
# 2. Turn off inheritance
class Animal(Document):
name = StringField()
meta = {
'allow_inheritance': False,
'indexes': ['name']
}
# 3. Remove _types and _cls
collection = Animal._get_collection()
collection.update({}, {"$unset": {"_types": 1, "_cls": 1}}, multi=True)
# 3. Confirm extra data is removed
count = collection.find({"$or": [{'_types': {"$exists": True}},
{'_cls': {"$exists": True}}]}).count()
assert count == 0
# 4. Remove indexes
info = collection.index_information()
indexes_to_drop = [key for key, value in info.iteritems()
if '_types' in dict(value['key'])
or '_cls' in dict(value['key'])]
for index in indexes_to_drop:
collection.drop_index(index)
# 5. Recreate indexes
Animal.ensure_indexes()

View File

@@ -1,48 +0,0 @@
# -*- coding: utf-8 -*-
import unittest
import uuid
from mongoengine import Document, connect
from mongoengine.connection import get_db
from mongoengine.fields import StringField, UUIDField, ListField
__all__ = ('ConvertToBinaryUUID', )
class ConvertToBinaryUUID(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
def test_how_to_convert_to_binary_uuid_fields(self):
"""Demonstrates migrating from 0.7 to 0.8
"""
# 1. Old definition - using dbrefs
class Person(Document):
name = StringField()
uuid = UUIDField(binary=False)
uuids = ListField(UUIDField(binary=False))
Person.drop_collection()
Person(name="Wilson Jr", uuid=uuid.uuid4(),
uuids=[uuid.uuid4(), uuid.uuid4()]).save()
# 2. Start the migration by changing the schema
# Change UUIDFIeld as now binary defaults to True
class Person(Document):
name = StringField()
uuid = UUIDField()
uuids = ListField(UUIDField())
# 3. Loop all the objects and mark parent as changed
for p in Person.objects:
p._mark_as_changed('uuid')
p._mark_as_changed('uuids')
p.save()
# 4. Confirmation of the fix!
wilson = Person.objects(name="Wilson Jr").as_pymongo()[0]
self.assertTrue(isinstance(wilson['uuid'], uuid.UUID))
self.assertTrue(all([isinstance(u, uuid.UUID) for u in wilson['uuids']]))

View File

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

View File

@@ -1,6 +1,3 @@
import sys
sys.path[0:0] = [""]
import unittest
from mongoengine import *
@@ -95,7 +92,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
exclude = ['d', 'e']
only = ['b', 'c']
qs = MyDoc.objects.fields(**dict(((i, 1) for i in include)))
qs = MyDoc.objects.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(),
{'a': 1, 'b': 1, 'c': 1, 'd': 1, 'e': 1})
qs = qs.only(*only)
@@ -103,14 +100,14 @@ class OnlyExcludeAllTest(unittest.TestCase):
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
qs = MyDoc.objects.fields(**dict(((i, 1) for i in include)))
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
self.assertEqual(qs._loaded_fields.as_dict(), {'a': 1, 'b': 1, 'c': 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
qs = MyDoc.objects.exclude(*exclude)
qs = qs.fields(**dict(((i, 1) for i in include)))
qs = qs.fields(**{i: 1 for i in include})
self.assertEqual(qs._loaded_fields.as_dict(), {'a': 1, 'b': 1, 'c': 1})
qs = qs.only(*only)
self.assertEqual(qs._loaded_fields.as_dict(), {'b': 1, 'c': 1})
@@ -129,7 +126,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
exclude = ['d', 'e']
only = ['b', 'c']
qs = MyDoc.objects.fields(**dict(((i, 1) for i in include)))
qs = MyDoc.objects.fields(**{i: 1 for i in include})
qs = qs.exclude(*exclude)
qs = qs.only(*only)
qs = qs.fields(slice__b=5)
@@ -144,6 +141,16 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertEqual(qs._loaded_fields.as_dict(),
{'b': {'$slice': 5}})
def test_mix_slice_with_other_fields(self):
class MyDoc(Document):
a = ListField()
b = ListField()
c = ListField()
qs = MyDoc.objects.fields(a=1, b=0, slice__c=2)
self.assertEqual(qs._loaded_fields.as_dict(),
{'c': {'$slice': 2}, 'a': 1})
def test_only(self):
"""Ensure that QuerySet.only only returns the requested fields.
"""
@@ -162,6 +169,10 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, person.age)
obj = self.Person.objects.only(*('id', 'name',)).get()
self.assertEqual(obj.name, person.name)
self.assertEqual(obj.age, None)
# Check polymorphism still works
class Employee(self.Person):
salary = IntField(db_field='wage')
@@ -170,7 +181,7 @@ class OnlyExcludeAllTest(unittest.TestCase):
employee.save()
obj = self.Person.objects(id=employee.id).only('age').get()
self.assertTrue(isinstance(obj, Employee))
self.assertIsInstance(obj, Employee)
# Check field names are looked up properly
obj = Employee.objects(id=employee.id).only('salary').get()
@@ -186,14 +197,18 @@ class OnlyExcludeAllTest(unittest.TestCase):
title = StringField()
text = StringField()
class VariousData(EmbeddedDocument):
some = BooleanField()
class BlogPost(Document):
content = StringField()
author = EmbeddedDocumentField(User)
comments = ListField(EmbeddedDocumentField(Comment))
various = MapField(field=EmbeddedDocumentField(VariousData))
BlogPost.drop_collection()
post = BlogPost(content='Had a good coffee today...')
post = BlogPost(content='Had a good coffee today...', various={'test_dynamic':{'some': True}})
post.author = User(name='Test User')
post.comments = [Comment(title='I aggree', text='Great post!'), Comment(title='Coffee', text='I hate coffee')]
post.save()
@@ -204,6 +219,9 @@ class OnlyExcludeAllTest(unittest.TestCase):
self.assertEqual(obj.author.name, 'Test User')
self.assertEqual(obj.comments, [])
obj = BlogPost.objects.only('various.test_dynamic.some').get()
self.assertEqual(obj.various["test_dynamic"].some, True)
obj = BlogPost.objects.only('content', 'comments.title',).get()
self.assertEqual(obj.content, 'Had a good coffee today...')
self.assertEqual(obj.author, None)
@@ -395,5 +413,28 @@ class OnlyExcludeAllTest(unittest.TestCase):
numbers = Numbers.objects.fields(embedded__n={"$slice": [-5, 10]}).get()
self.assertEqual(numbers.embedded.n, [-5, -4, -3, -2, -1])
def test_exclude_from_subclasses_docs(self):
class Base(Document):
username = StringField()
meta = {'allow_inheritance': True}
class Anon(Base):
anon = BooleanField()
class User(Base):
password = StringField()
wibble = StringField()
Base.drop_collection()
User(username="mongodb", password="secret").save()
user = Base.objects().exclude("password", "wibble").first()
self.assertEqual(user.password, None)
self.assertRaises(LookUpError, Base.objects.exclude, "made_up")
if __name__ == '__main__':
unittest.main()

View File

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

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

@@ -0,0 +1,132 @@
import unittest
from mongoengine import connect, Document, IntField, StringField, ListField
from tests.utils import requires_mongodb_gte_26
__all__ = ("FindAndModifyTest",)
class Doc(Document):
id = IntField(primary_key=True)
value = IntField()
class FindAndModifyTest(unittest.TestCase):
def setUp(self):
connect(db="mongoenginetest")
Doc.drop_collection()
def assertDbEqual(self, docs):
self.assertEqual(list(Doc._collection.find().sort("id")), docs)
def test_modify(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(set__value=-1)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_new(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
new_doc = Doc.objects(id=1).modify(set__value=-1, new=True)
doc.value = -1
self.assertEqual(new_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_not_existing(self):
Doc(id=0, value=0).save()
self.assertEqual(Doc.objects(id=1).modify(set__value=-1), None)
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_upsert(self):
Doc(id=0, value=0).save()
old_doc = Doc.objects(id=1).modify(set__value=1, upsert=True)
self.assertEqual(old_doc, None)
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_upsert_existing(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(set__value=-1, upsert=True)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
def test_modify_with_upsert_with_new(self):
Doc(id=0, value=0).save()
new_doc = Doc.objects(id=1).modify(upsert=True, new=True, set__value=1)
self.assertEqual(new_doc.to_mongo(), {"_id": 1, "value": 1})
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": 1}])
def test_modify_with_remove(self):
Doc(id=0, value=0).save()
doc = Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).modify(remove=True)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_find_and_modify_with_remove_not_existing(self):
Doc(id=0, value=0).save()
self.assertEqual(Doc.objects(id=1).modify(remove=True), None)
self.assertDbEqual([{"_id": 0, "value": 0}])
def test_modify_with_order_by(self):
Doc(id=0, value=3).save()
Doc(id=1, value=2).save()
Doc(id=2, value=1).save()
doc = Doc(id=3, value=0).save()
old_doc = Doc.objects().order_by("-id").modify(set__value=-1)
self.assertEqual(old_doc.to_json(), doc.to_json())
self.assertDbEqual([
{"_id": 0, "value": 3}, {"_id": 1, "value": 2},
{"_id": 2, "value": 1}, {"_id": 3, "value": -1}])
def test_modify_with_fields(self):
Doc(id=0, value=0).save()
Doc(id=1, value=1).save()
old_doc = Doc.objects(id=1).only("id").modify(set__value=-1)
self.assertEqual(old_doc.to_mongo(), {"_id": 1})
self.assertDbEqual([{"_id": 0, "value": 0}, {"_id": 1, "value": -1}])
@requires_mongodb_gte_26
def test_modify_with_push(self):
class BlogPost(Document):
tags = ListField(StringField())
BlogPost.drop_collection()
blog = BlogPost.objects.create()
# Push a new tag via modify with new=False (default).
BlogPost(id=blog.id).modify(push__tags='code')
self.assertEqual(blog.tags, [])
blog.reload()
self.assertEqual(blog.tags, ['code'])
# Push a new tag via modify with new=True.
blog = BlogPost.objects(id=blog.id).modify(push__tags='java', new=True)
self.assertEqual(blog.tags, ['code', 'java'])
# Push a new tag with a positional argument.
blog = BlogPost.objects(id=blog.id).modify(
push__tags__0='python',
new=True)
self.assertEqual(blog.tags, ['python', 'code', 'java'])
# Push multiple new tags with a positional argument.
blog = BlogPost.objects(id=blog.id).modify(
push__tags__1=['go', 'rust'],
new=True)
self.assertEqual(blog.tags, ['python', 'go', 'rust', 'code', 'java'])
if __name__ == '__main__':
unittest.main()

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,11 +1,9 @@
import sys
sys.path[0:0] = [""]
import unittest
from bson.son import SON
from mongoengine import *
from mongoengine.queryset import Q
from mongoengine.queryset import transform
from mongoengine.queryset import Q, transform
__all__ = ("TransformTest",)
@@ -31,6 +29,48 @@ class TransformTest(unittest.TestCase):
self.assertEqual(transform.query(name__exists=True),
{'name': {'$exists': True}})
def test_transform_update(self):
class LisDoc(Document):
foo = ListField(StringField())
class DicDoc(Document):
dictField = DictField()
class Doc(Document):
pass
LisDoc.drop_collection()
DicDoc.drop_collection()
Doc.drop_collection()
DicDoc().save()
doc = Doc().save()
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
self.assertIsInstance(update[v]["dictField.test"], dict)
# Update special cases
update = transform.update(DicDoc, unset__dictField__test=doc)
self.assertEqual(update["$unset"]["dictField.test"], 1)
update = transform.update(DicDoc, pull__dictField__test=doc)
self.assertIsInstance(update["$pull"]["dictField"]["test"], dict)
update = transform.update(LisDoc, pull__foo__in=['a'])
self.assertEqual(update, {'$pull': {'foo': {'$in': ['a']}}})
def test_transform_update_push(self):
"""Ensure the differences in behvaior between 'push' and 'push_all'"""
class BlogPost(Document):
tags = ListField(StringField())
update = transform.update(BlogPost, push__tags=['mongo', 'db'])
self.assertEqual(update, {'$push': {'tags': ['mongo', 'db']}})
update = transform.update(BlogPost, push_all__tags=['mongo', 'db'])
self.assertEqual(update, {'$push': {'tags': {'$each': ['mongo', 'db']}}})
def test_query_field_name(self):
"""Ensure that the correct field name is used when querying.
"""
@@ -48,17 +88,15 @@ class TransformTest(unittest.TestCase):
post = BlogPost(**data)
post.save()
self.assertTrue('postTitle' in
BlogPost.objects(title=data['title'])._query)
self.assertIn('postTitle', BlogPost.objects(title=data['title'])._query)
self.assertFalse('title' in
BlogPost.objects(title=data['title'])._query)
self.assertEqual(BlogPost.objects(title=data['title']).count(), 1)
self.assertTrue('_id' in BlogPost.objects(pk=post.id)._query)
self.assertIn('_id', BlogPost.objects(pk=post.id)._query)
self.assertEqual(BlogPost.objects(pk=post.id).count(), 1)
self.assertTrue('postComments.commentContent' in
BlogPost.objects(comments__content='test')._query)
self.assertIn('postComments.commentContent', BlogPost.objects(comments__content='test')._query)
self.assertEqual(BlogPost.objects(comments__content='test').count(), 1)
BlogPost.drop_collection()
@@ -76,8 +114,8 @@ class TransformTest(unittest.TestCase):
post = BlogPost(**data)
post.save()
self.assertTrue('_id' in BlogPost.objects(pk=data['title'])._query)
self.assertTrue('_id' in BlogPost.objects(title=data['title'])._query)
self.assertIn('_id', BlogPost.objects(pk=data['title'])._query)
self.assertIn('_id', BlogPost.objects(title=data['title'])._query)
self.assertEqual(BlogPost.objects(pk=data['title']).count(), 1)
BlogPost.drop_collection()
@@ -131,16 +169,119 @@ class TransformTest(unittest.TestCase):
class Doc(Document):
meta = {'allow_inheritance': False}
raw_query = Doc.objects(__raw__={'deleted': False,
'scraped': 'yes',
'$nor': [{'views.extracted': 'no'},
{'attachments.views.extracted':'no'}]
})._query
raw_query = Doc.objects(__raw__={
'deleted': False,
'scraped': 'yes',
'$nor': [
{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}
]
})._query
expected = {'deleted': False, 'scraped': 'yes',
'$nor': [{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}]}
self.assertEqual(expected, raw_query)
self.assertEqual(raw_query, {
'deleted': False,
'scraped': 'yes',
'$nor': [
{'views.extracted': 'no'},
{'attachments.views.extracted': 'no'}
]
})
def test_geojson_PointField(self):
class Location(Document):
loc = PointField()
update = transform.update(Location, set__loc=[1, 2])
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1, 2]}}})
update = transform.update(Location, set__loc={"type": "Point", "coordinates": [1, 2]})
self.assertEqual(update, {'$set': {'loc': {"type": "Point", "coordinates": [1, 2]}}})
def test_geojson_LineStringField(self):
class Location(Document):
line = LineStringField()
update = transform.update(Location, set__line=[[1, 2], [2, 2]])
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
update = transform.update(Location, set__line={"type": "LineString", "coordinates": [[1, 2], [2, 2]]})
self.assertEqual(update, {'$set': {'line': {"type": "LineString", "coordinates": [[1, 2], [2, 2]]}}})
def test_geojson_PolygonField(self):
class Location(Document):
poly = PolygonField()
update = transform.update(Location, set__poly=[[[40, 5], [40, 6], [41, 6], [40, 5]]])
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
update = transform.update(Location, set__poly={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]})
self.assertEqual(update, {'$set': {'poly': {"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}}})
def test_type(self):
class Doc(Document):
df = DynamicField()
Doc(df=True).save()
Doc(df=7).save()
Doc(df="df").save()
self.assertEqual(Doc.objects(df__type=1).count(), 0) # double
self.assertEqual(Doc.objects(df__type=8).count(), 1) # bool
self.assertEqual(Doc.objects(df__type=2).count(), 1) # str
self.assertEqual(Doc.objects(df__type=16).count(), 1) # int
def test_last_field_name_like_operator(self):
class EmbeddedItem(EmbeddedDocument):
type = StringField()
name = StringField()
class Doc(Document):
item = EmbeddedDocumentField(EmbeddedItem)
Doc.drop_collection()
doc = Doc(item=EmbeddedItem(type="axe", name="Heroic axe"))
doc.save()
self.assertEqual(1, Doc.objects(item__type__="axe").count())
self.assertEqual(1, Doc.objects(item__name__="Heroic axe").count())
Doc.objects(id=doc.id).update(set__item__type__='sword')
self.assertEqual(1, Doc.objects(item__type__="sword").count())
self.assertEqual(0, Doc.objects(item__type__="axe").count())
def test_understandable_error_raised(self):
class Event(Document):
title = StringField()
location = GeoPointField()
box = [(35.0, -125.0), (40.0, -100.0)]
# I *meant* to execute location__within_box=box
events = Event.objects(location__within=box)
with self.assertRaises(InvalidQueryError):
events.count()
def test_update_pull_for_list_fields(self):
"""
Test added to check pull operation in update for
EmbeddedDocumentListField which is inside a EmbeddedDocumentField
"""
class Word(EmbeddedDocument):
word = StringField()
index = IntField()
class SubDoc(EmbeddedDocument):
heading = ListField(StringField())
text = EmbeddedDocumentListField(Word)
class MainDoc(Document):
title = StringField()
content = EmbeddedDocumentField(SubDoc)
word = Word(word='abc', index=1)
update = transform.update(MainDoc, pull__content__text=word)
self.assertEqual(update, {'$pull': {'content.text': SON([('word', u'abc'), ('index', 1)])}})
update = transform.update(MainDoc, pull__content__heading='xyz')
self.assertEqual(update, {'$pull': {'content.heading': 'xyz'}})
if __name__ == '__main__':

View File

@@ -1,14 +1,12 @@
import sys
sys.path[0:0] = [""]
import datetime
import re
import unittest
from bson import ObjectId
from datetime import datetime
from mongoengine import *
from mongoengine.queryset import Q
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import Q
__all__ = ("QTest",)
@@ -68,11 +66,11 @@ class QTest(unittest.TestCase):
x = IntField()
y = StringField()
# Check than an error is raised when conflicting queries are anded
def invalid_combination():
query = Q(x__lt=7) & Q(x__lt=3)
query.to_query(TestDoc)
self.assertRaises(InvalidQueryError, invalid_combination)
query = (Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {'$and': [{'x': {'$lt': 7}}, {'x': {'$lt': 3}}]})
query = (Q(y="a") & Q(x__lt=7) & Q(x__lt=3)).to_query(TestDoc)
self.assertEqual(query, {'$and': [{'y': "a"}, {'x': {'$lt': 7}}, {'x': {'$lt': 3}}]})
# Check normal cases work without an error
query = Q(x__lt=7) & Q(x__gt=3)
@@ -132,12 +130,12 @@ class QTest(unittest.TestCase):
TestDoc(x=10).save()
TestDoc(y=True).save()
self.assertEqual(query,
{'$and': [
{'$or': [{'x': {'$gt': 0}}, {'x': {'$exists': False}}]},
{'$or': [{'x': {'$lt': 100}}, {'y': True}]}
]})
self.assertEqual(query, {
'$and': [
{'$or': [{'x': {'$gt': 0}}, {'x': {'$exists': False}}]},
{'$or': [{'x': {'$lt': 100}}, {'y': True}]}
]
})
self.assertEqual(2, TestDoc.objects(q1 & q2).count())
def test_or_and_or_combination(self):
@@ -157,15 +155,14 @@ class QTest(unittest.TestCase):
q2 = (Q(x__lt=100) & (Q(y=False) | Q(y__exists=False)))
query = (q1 | q2).to_query(TestDoc)
self.assertEqual(query,
{'$or': [
self.assertEqual(query, {
'$or': [
{'$and': [{'x': {'$gt': 0}},
{'$or': [{'y': True}, {'y': {'$exists': False}}]}]},
{'$and': [{'x': {'$lt': 100}},
{'$or': [{'y': False}, {'y': {'$exists': False}}]}]}
]}
)
]
})
self.assertEqual(2, TestDoc.objects(q1 | q2).count())
def test_multiple_occurence_in_field(self):
@@ -188,7 +185,7 @@ class QTest(unittest.TestCase):
x = IntField()
TestDoc.drop_collection()
for i in xrange(1, 101):
for i in range(1, 101):
t = TestDoc(x=i)
t.save()
@@ -199,7 +196,7 @@ class QTest(unittest.TestCase):
test2 = test.clone()
self.assertEqual(test2.count(), 3)
self.assertFalse(test2 == test)
self.assertNotEqual(test2, test)
test3 = test2.filter(x=6)
self.assertEqual(test3.count(), 1)
@@ -215,19 +212,19 @@ class QTest(unittest.TestCase):
BlogPost.drop_collection()
post1 = BlogPost(title='Test 1', publish_date=datetime(2010, 1, 8), published=False)
post1 = BlogPost(title='Test 1', publish_date=datetime.datetime(2010, 1, 8), published=False)
post1.save()
post2 = BlogPost(title='Test 2', publish_date=datetime(2010, 1, 15), published=True)
post2 = BlogPost(title='Test 2', publish_date=datetime.datetime(2010, 1, 15), published=True)
post2.save()
post3 = BlogPost(title='Test 3', published=True)
post3.save()
post4 = BlogPost(title='Test 4', publish_date=datetime(2010, 1, 8))
post4 = BlogPost(title='Test 4', publish_date=datetime.datetime(2010, 1, 8))
post4.save()
post5 = BlogPost(title='Test 1', publish_date=datetime(2010, 1, 15))
post5 = BlogPost(title='Test 1', publish_date=datetime.datetime(2010, 1, 15))
post5.save()
post6 = BlogPost(title='Test 1', published=False)
@@ -250,7 +247,7 @@ class QTest(unittest.TestCase):
self.assertTrue(all(obj.id in posts for obj in published_posts))
# Check Q object combination
date = datetime(2010, 1, 10)
date = datetime.datetime(2010, 1, 10)
q = BlogPost.objects(Q(publish_date__lte=date) | Q(published=True))
posts = [post.id for post in q]
@@ -271,12 +268,13 @@ class QTest(unittest.TestCase):
self.assertEqual(self.Person.objects(Q(age__in=[20, 30])).count(), 3)
# Test invalid query objs
def wrong_query_objs():
with self.assertRaises(InvalidQueryError):
self.Person.objects('user1')
def wrong_query_objs_filter():
self.Person.objects('user1')
self.assertRaises(InvalidQueryError, wrong_query_objs)
self.assertRaises(InvalidQueryError, wrong_query_objs_filter)
# filter should fail, too
with self.assertRaises(InvalidQueryError):
self.Person.objects.filter('user1')
def test_q_regex(self):
"""Ensure that Q objects can be queried using regexes.
@@ -284,7 +282,6 @@ class QTest(unittest.TestCase):
person = self.Person(name='Guido van Rossum')
person.save()
import re
obj = self.Person.objects(Q(name=re.compile('^Gui'))).first()
self.assertEqual(obj, person)
obj = self.Person.objects(Q(name=re.compile('^gui'))).first()
@@ -299,6 +296,18 @@ class QTest(unittest.TestCase):
obj = self.Person.objects(Q(name__not=re.compile('^Gui'))).first()
self.assertEqual(obj, None)
def test_q_repr(self):
self.assertEqual(repr(Q()), 'Q(**{})')
self.assertEqual(repr(Q(name='test')), "Q(**{'name': 'test'})")
self.assertEqual(
repr(Q(name='test') & Q(age__gte=18)),
"(Q(**{'name': 'test'}) & Q(**{'age__gte': 18}))")
self.assertEqual(
repr(Q(name='test') | Q(age__gte=18)),
"(Q(**{'name': 'test'}) | Q(**{'age__gte': 18}))")
def test_q_lists(self):
"""Ensure that Q objects query ListFields correctly.
"""
@@ -325,10 +334,26 @@ class QTest(unittest.TestCase):
pk = ObjectId()
User(email='example@example.com', pk=pk).save()
self.assertEqual(1, User.objects.filter(
Q(email='example@example.com') |
Q(name='John Doe')
).limit(2).filter(pk=pk).count())
self.assertEqual(1, User.objects.filter(Q(email='example@example.com') |
Q(name='John Doe')).limit(2).filter(pk=pk).count())
def test_chained_q_or_filtering(self):
class Post(EmbeddedDocument):
name = StringField(required=True)
class Item(Document):
postables = ListField(EmbeddedDocumentField(Post))
Item.drop_collection()
Item(postables=[Post(name="a"), Post(name="b")]).save()
Item(postables=[Post(name="a"), Post(name="c")]).save()
Item(postables=[Post(name="a"), Post(name="b"), Post(name="c")]).save()
self.assertEqual(Item.objects(Q(postables__name="a") & Q(postables__name="b")).count(), 2)
self.assertEqual(Item.objects.filter(postables__name="a").filter(postables__name="b").count(), 2)
if __name__ == '__main__':
unittest.main()

View File

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

View File

@@ -1,5 +1,3 @@
import sys
sys.path[0:0] = [""]
import unittest
from mongoengine import *
@@ -79,7 +77,7 @@ class ContextManagersTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
User(name='user %s' % i).save()
user = User.objects.first()
@@ -91,15 +89,15 @@ class ContextManagersTest(unittest.TestCase):
with no_dereference(Group) as Group:
group = Group.objects.first()
self.assertTrue(all([not isinstance(m, User)
for m in group.members]))
self.assertFalse(isinstance(group.ref, User))
self.assertFalse(isinstance(group.generic, User))
for m in group.members:
self.assertNotIsInstance(m, User)
self.assertNotIsInstance(group.ref, User)
self.assertNotIsInstance(group.generic, User)
self.assertTrue(all([isinstance(m, User)
for m in group.members]))
self.assertTrue(isinstance(group.ref, User))
self.assertTrue(isinstance(group.generic, User))
for m in group.members:
self.assertIsInstance(m, User)
self.assertIsInstance(group.ref, User)
self.assertIsInstance(group.generic, User)
def test_no_dereference_context_manager_dbref(self):
"""Ensure that DBRef items in ListFields aren't dereferenced.
@@ -117,7 +115,7 @@ class ContextManagersTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
User(name='user %s' % i).save()
user = User.objects.first()
@@ -131,19 +129,17 @@ class ContextManagersTest(unittest.TestCase):
group = Group.objects.first()
self.assertTrue(all([not isinstance(m, User)
for m in group.members]))
self.assertFalse(isinstance(group.ref, User))
self.assertFalse(isinstance(group.generic, User))
self.assertNotIsInstance(group.ref, User)
self.assertNotIsInstance(group.generic, User)
self.assertTrue(all([isinstance(m, User)
for m in group.members]))
self.assertTrue(isinstance(group.ref, User))
self.assertTrue(isinstance(group.generic, User))
self.assertIsInstance(group.ref, User)
self.assertIsInstance(group.generic, User)
def test_no_sub_classes(self):
class A(Document):
x = IntField()
y = IntField()
meta = {'allow_inheritance': True}
class B(A):
@@ -154,29 +150,29 @@ class ContextManagersTest(unittest.TestCase):
A.drop_collection()
A(x=10, y=20).save()
A(x=15, y=30).save()
B(x=20, y=40).save()
B(x=30, y=50).save()
C(x=40, y=60).save()
A(x=10).save()
A(x=15).save()
B(x=20).save()
B(x=30).save()
C(x=40).save()
self.assertEqual(A.objects.count(), 5)
self.assertEqual(B.objects.count(), 3)
self.assertEqual(C.objects.count(), 1)
with no_sub_classes(A) as A:
with no_sub_classes(A):
self.assertEqual(A.objects.count(), 2)
for obj in A.objects:
self.assertEqual(obj.__class__, A)
with no_sub_classes(B) as B:
with no_sub_classes(B):
self.assertEqual(B.objects.count(), 2)
for obj in B.objects:
self.assertEqual(obj.__class__, B)
with no_sub_classes(C) as C:
with no_sub_classes(C):
self.assertEqual(C.objects.count(), 1)
for obj in C.objects:
@@ -187,18 +183,124 @@ class ContextManagersTest(unittest.TestCase):
self.assertEqual(B.objects.count(), 3)
self.assertEqual(C.objects.count(), 1)
def test_no_sub_classes_modification_to_document_class_are_temporary(self):
class A(Document):
x = IntField()
meta = {'allow_inheritance': True}
class B(A):
z = IntField()
self.assertEqual(A._subclasses, ('A', 'A.B'))
with no_sub_classes(A):
self.assertEqual(A._subclasses, ('A',))
self.assertEqual(A._subclasses, ('A', 'A.B'))
self.assertEqual(B._subclasses, ('A.B',))
with no_sub_classes(B):
self.assertEqual(B._subclasses, ('A.B',))
self.assertEqual(B._subclasses, ('A.B',))
def test_no_subclass_context_manager_does_not_swallow_exception(self):
class User(Document):
name = StringField()
with self.assertRaises(TypeError):
with no_sub_classes(User):
raise TypeError()
def test_query_counter_does_not_swallow_exception(self):
with self.assertRaises(TypeError):
with query_counter() as q:
raise TypeError()
def test_query_counter_temporarily_modifies_profiling_level(self):
connect('mongoenginetest')
db = get_db()
initial_profiling_level = db.profiling_level()
try:
NEW_LEVEL = 1
db.set_profiling_level(NEW_LEVEL)
self.assertEqual(db.profiling_level(), NEW_LEVEL)
with query_counter() as q:
self.assertEqual(db.profiling_level(), 2)
self.assertEqual(db.profiling_level(), NEW_LEVEL)
except Exception:
db.set_profiling_level(initial_profiling_level) # Ensures it gets reseted no matter the outcome of the test
raise
def test_query_counter(self):
connect('mongoenginetest')
db = get_db()
db.test.find({})
collection = db.query_counter
collection.drop()
def issue_1_count_query():
collection.find({}).count()
def issue_1_insert_query():
collection.insert_one({'test': 'garbage'})
def issue_1_find_query():
collection.find_one()
counter = 0
with query_counter() as q:
self.assertEqual(q, counter)
self.assertEqual(q, counter) # Ensures previous count query did not get counted
for _ in range(10):
issue_1_insert_query()
counter += 1
self.assertEqual(q, counter)
for _ in range(4):
issue_1_find_query()
counter += 1
self.assertEqual(q, counter)
for _ in range(3):
issue_1_count_query()
counter += 1
self.assertEqual(q, counter)
def test_query_counter_counts_getmore_queries(self):
connect('mongoenginetest')
db = get_db()
collection = db.query_counter
collection.drop()
many_docs = [{'test': 'garbage %s' % i} for i in range(150)]
collection.insert_many(many_docs) # first batch of documents contains 101 documents
with query_counter() as q:
self.assertEqual(0, q)
self.assertEqual(q, 0)
list(collection.find())
self.assertEqual(q, 2) # 1st select + 1 getmore
for i in xrange(1, 51):
db.test.find({}).count()
def test_query_counter_ignores_particular_queries(self):
connect('mongoenginetest')
db = get_db()
self.assertEqual(50, q)
collection = db.query_counter
collection.insert_many([{'test': 'garbage %s' % i} for i in range(10)])
with query_counter() as q:
self.assertEqual(q, 0)
cursor = collection.find()
self.assertEqual(q, 0) # cursor wasn't opened yet
_ = next(cursor) # opens the cursor and fires the find query
self.assertEqual(q, 1)
cursor.close() # issues a `killcursors` query that is ignored by the context
self.assertEqual(q, 1)
_ = db.system.indexes.find_one() # queries on db.system.indexes are ignored as well
self.assertEqual(q, 1)
if __name__ == '__main__':
unittest.main()

View File

@@ -0,0 +1,95 @@
import unittest
from mongoengine.base.datastructures import StrictDict, BaseList
class TestBaseList(unittest.TestCase):
def test_iter_simple(self):
values = [True, False, True, False]
base_list = BaseList(values, instance=None, name='my_name')
self.assertEqual(values, list(base_list))
def test_iter_allow_modification_while_iterating_withou_error(self):
# regular list allows for this, thus this subclass must comply to that
base_list = BaseList([True, False, True, False], instance=None, name='my_name')
for idx, val in enumerate(base_list):
if val:
base_list.pop(idx)
class TestStrictDict(unittest.TestCase):
def strict_dict_class(self, *args, **kwargs):
return StrictDict.create(*args, **kwargs)
def setUp(self):
self.dtype = self.strict_dict_class(("a", "b", "c"))
def test_init(self):
d = self.dtype(a=1, b=1, c=1)
self.assertEqual((d.a, d.b, d.c), (1, 1, 1))
def test_repr(self):
d = self.dtype(a=1, b=2, c=3)
self.assertEqual(repr(d), '{"a": 1, "b": 2, "c": 3}')
# make sure quotes are escaped properly
d = self.dtype(a='"', b="'", c="")
self.assertEqual(repr(d), '{"a": \'"\', "b": "\'", "c": \'\'}')
def test_init_fails_on_nonexisting_attrs(self):
with self.assertRaises(AttributeError):
self.dtype(a=1, b=2, d=3)
def test_eq(self):
d = self.dtype(a=1, b=1, c=1)
dd = self.dtype(a=1, b=1, c=1)
e = self.dtype(a=1, b=1, c=3)
f = self.dtype(a=1, b=1)
g = self.strict_dict_class(("a", "b", "c", "d"))(a=1, b=1, c=1, d=1)
h = self.strict_dict_class(("a", "c", "b"))(a=1, b=1, c=1)
i = self.strict_dict_class(("a", "c", "b"))(a=1, b=1, c=2)
self.assertEqual(d, dd)
self.assertNotEqual(d, e)
self.assertNotEqual(d, f)
self.assertNotEqual(d, g)
self.assertNotEqual(f, d)
self.assertEqual(d, h)
self.assertNotEqual(d, i)
def test_setattr_getattr(self):
d = self.dtype()
d.a = 1
self.assertEqual(d.a, 1)
self.assertRaises(AttributeError, getattr, d, 'b')
def test_setattr_raises_on_nonexisting_attr(self):
d = self.dtype()
with self.assertRaises(AttributeError):
d.x = 1
def test_setattr_getattr_special(self):
d = self.strict_dict_class(["items"])
d.items = 1
self.assertEqual(d.items, 1)
def test_get(self):
d = self.dtype(a=1)
self.assertEqual(d.get('a'), 1)
self.assertEqual(d.get('b', 'bla'), 'bla')
def test_items(self):
d = self.dtype(a=1)
self.assertEqual(d.items(), [('a', 1)])
d = self.dtype(a=1, b=2)
self.assertEqual(d.items(), [('a', 1), ('b', 2)])
def test_mappings_protocol(self):
d = self.dtype(a=1, b=2)
assert dict(d) == {'a': 1, 'b': 2}
assert dict(**d) == {'a': 1, 'b': 2}
if __name__ == '__main__':
unittest.main()

View File

@@ -1,6 +1,4 @@
# -*- coding: utf-8 -*-
import sys
sys.path[0:0] = [""]
import unittest
from bson import DBRef, ObjectId
@@ -12,9 +10,13 @@ from mongoengine.context_managers import query_counter
class FieldTest(unittest.TestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
@classmethod
def setUpClass(cls):
cls.db = connect(db='mongoenginetest')
@classmethod
def tearDownClass(cls):
cls.db.drop_database('mongoenginetest')
def test_list_item_dereference(self):
"""Ensure that DBRef items in ListFields are dereferenced.
@@ -28,7 +30,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
user = User(name='user %s' % i)
user.save()
@@ -86,7 +88,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
user = User(name='user %s' % i)
user.save()
@@ -158,7 +160,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 26):
for i in range(1, 26):
user = User(name='user %s' % i)
user.save()
@@ -198,8 +200,8 @@ class FieldTest(unittest.TestCase):
group = Group(author=user, members=[user]).save()
raw_data = Group._get_collection().find_one()
self.assertTrue(isinstance(raw_data['author'], DBRef))
self.assertTrue(isinstance(raw_data['members'][0], DBRef))
self.assertIsInstance(raw_data['author'], DBRef)
self.assertIsInstance(raw_data['members'][0], DBRef)
group = Group.objects.first()
self.assertEqual(group.author, user)
@@ -222,8 +224,8 @@ class FieldTest(unittest.TestCase):
self.assertEqual(group.members, [user])
raw_data = Group._get_collection().find_one()
self.assertTrue(isinstance(raw_data['author'], ObjectId))
self.assertTrue(isinstance(raw_data['members'][0], ObjectId))
self.assertIsInstance(raw_data['author'], ObjectId)
self.assertIsInstance(raw_data['members'][0], ObjectId)
def test_recursive_reference(self):
"""Ensure that ReferenceFields can reference their own documents.
@@ -291,9 +293,38 @@ class FieldTest(unittest.TestCase):
self.assertEqual(employee.friends, friends)
self.assertEqual(q, 2)
def test_list_of_lists_of_references(self):
class User(Document):
name = StringField()
class Post(Document):
user_lists = ListField(ListField(ReferenceField(User)))
class SimpleList(Document):
users = ListField(ReferenceField(User))
User.drop_collection()
Post.drop_collection()
SimpleList.drop_collection()
u1 = User.objects.create(name='u1')
u2 = User.objects.create(name='u2')
u3 = User.objects.create(name='u3')
SimpleList.objects.create(users=[u1, u2, u3])
self.assertEqual(SimpleList.objects.all()[0].users, [u1, u2, u3])
Post.objects.create(user_lists=[[u1, u2], [u3]])
self.assertEqual(Post.objects.all()[0].user_lists, [[u1, u2], [u3]])
def test_circular_reference(self):
"""Ensure you can handle circular references
"""
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
class Person(Document):
name = StringField()
relations = ListField(EmbeddedDocumentField('Relation'))
@@ -301,10 +332,6 @@ class FieldTest(unittest.TestCase):
def __repr__(self):
return "<Person: %s>" % self.name
class Relation(EmbeddedDocument):
name = StringField()
person = ReferenceField('Person')
Person.drop_collection()
mother = Person(name="Mother")
daughter = Person(name="Daughter")
@@ -411,7 +438,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -442,7 +469,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Document select_related
with query_counter() as q:
@@ -458,7 +485,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@@ -475,7 +502,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
UserA.drop_collection()
UserB.drop_collection()
@@ -502,7 +529,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -533,7 +560,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Document select_related
with query_counter() as q:
@@ -549,7 +576,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@@ -566,7 +593,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for m in group_obj.members:
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
UserA.drop_collection()
UserB.drop_collection()
@@ -585,15 +612,15 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
user = User(name='user %s' % i)
user.save()
members.append(user)
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -606,7 +633,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, User))
self.assertIsInstance(m, User)
# Document select_related
with query_counter() as q:
@@ -619,7 +646,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, User))
self.assertIsInstance(m, User)
# Queryset select_related
with query_counter() as q:
@@ -633,7 +660,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, User))
self.assertIsInstance(m, User)
User.drop_collection()
Group.drop_collection()
@@ -658,7 +685,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -670,9 +697,9 @@ class FieldTest(unittest.TestCase):
members += [a, b, c]
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -688,7 +715,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Document select_related
with query_counter() as q:
@@ -704,7 +731,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@@ -721,7 +748,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
Group.objects.delete()
Group().save()
@@ -754,16 +781,16 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
members += [a]
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -779,7 +806,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, UserA))
self.assertIsInstance(m, UserA)
# Document select_related
with query_counter() as q:
@@ -795,7 +822,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, UserA))
self.assertIsInstance(m, UserA)
# Queryset select_related
with query_counter() as q:
@@ -812,7 +839,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
for k, m in group_obj.members.iteritems():
self.assertTrue(isinstance(m, UserA))
self.assertIsInstance(m, UserA)
UserA.drop_collection()
Group.drop_collection()
@@ -837,7 +864,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i)
a.save()
@@ -849,9 +876,9 @@ class FieldTest(unittest.TestCase):
members += [a, b, c]
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
group = Group(members=dict([(str(u.id), u) for u in members]))
group = Group(members={str(u.id): u for u in members})
group.save()
with query_counter() as q:
@@ -867,7 +894,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Document select_related
with query_counter() as q:
@@ -883,7 +910,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
# Queryset select_related
with query_counter() as q:
@@ -900,7 +927,7 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 4)
for k, m in group_obj.members.iteritems():
self.assertTrue('User' in m.__class__.__name__)
self.assertIn('User', m.__class__.__name__)
Group.objects.delete()
Group().save()
@@ -923,6 +950,8 @@ class FieldTest(unittest.TestCase):
class Asset(Document):
name = StringField(max_length=250, required=True)
path = StringField()
title = StringField()
parent = GenericReferenceField(default=None)
parents = ListField(GenericReferenceField())
children = ListField(GenericReferenceField())
@@ -1000,6 +1029,42 @@ class FieldTest(unittest.TestCase):
self.assertEqual(type(foo.bar), Bar)
self.assertEqual(type(foo.baz), Baz)
def test_document_reload_reference_integrity(self):
"""
Ensure reloading a document with multiple similar id
in different collections doesn't mix them.
"""
class Topic(Document):
id = IntField(primary_key=True)
class User(Document):
id = IntField(primary_key=True)
name = StringField()
class Message(Document):
id = IntField(primary_key=True)
topic = ReferenceField(Topic)
author = ReferenceField(User)
Topic.drop_collection()
User.drop_collection()
Message.drop_collection()
# All objects share the same id, but each in a different collection
topic = Topic(id=1).save()
user = User(id=1, name='user-name').save()
Message(id=1, topic=topic, author=user).save()
concurrent_change_user = User.objects.get(id=1)
concurrent_change_user.name = 'new-name'
concurrent_change_user.save()
self.assertNotEqual(user.name, 'new-name')
msg = Message.objects.get(id=1)
msg.reload()
self.assertEqual(msg.topic, topic)
self.assertEqual(msg.author, user)
self.assertEqual(msg.author.name, 'new-name')
def test_list_lookup_not_checked_in_map(self):
"""Ensure we dereference list data correctly
"""
@@ -1035,7 +1100,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
User(name='user %s' % i).save()
Group(name="Test", members=User.objects).save()
@@ -1064,7 +1129,7 @@ class FieldTest(unittest.TestCase):
User.drop_collection()
Group.drop_collection()
for i in xrange(1, 51):
for i in range(1, 51):
User(name='user %s' % i).save()
Group(name="Test", members=User.objects).save()
@@ -1101,7 +1166,7 @@ class FieldTest(unittest.TestCase):
Group.drop_collection()
members = []
for i in xrange(1, 51):
for i in range(1, 51):
a = UserA(name='User A %s' % i).save()
b = UserB(name='User B %s' % i).save()
c = UserC(name='User C %s' % i).save()
@@ -1121,37 +1186,32 @@ class FieldTest(unittest.TestCase):
self.assertEqual(q, 2)
def test_tuples_as_tuples(self):
"""
Ensure that tuples remain tuples when they are
inside a ComplexBaseField
"""
from mongoengine.base import BaseField
def test_objectid_reference_across_databases(self):
# mongoenginetest - Is default connection alias from setUp()
# Register Aliases
register_connection('testdb-1', 'mongoenginetest2')
class EnumField(BaseField):
class User(Document):
name = StringField()
meta = {"db_alias": "testdb-1"}
def __init__(self, **kwargs):
super(EnumField, self).__init__(**kwargs)
class Book(Document):
name = StringField()
author = ReferenceField(User)
def to_mongo(self, value):
return value
# Drops
User.drop_collection()
Book.drop_collection()
def to_python(self, value):
return tuple(value)
user = User(name="Ross").save()
Book(name="MongoEngine for pros", author=user).save()
class TestDoc(Document):
items = ListField(EnumField())
# Can't use query_counter across databases - so test the _data object
book = Book.objects.first()
self.assertNotIsInstance(book._data['author'], User)
TestDoc.drop_collection()
tuples = [(100, 'Testing')]
doc = TestDoc()
doc.items = tuples
doc.save()
x = TestDoc.objects().get()
self.assertTrue(x is not None)
self.assertTrue(len(x.items) == 1)
self.assertTrue(tuple(x.items[0]) in tuples)
self.assertTrue(x.items[0] in tuples)
book.select_related()
self.assertIsInstance(book._data['author'], User)
def test_non_ascii_pk(self):
"""
@@ -1176,6 +1236,55 @@ class FieldTest(unittest.TestCase):
self.assertEqual(2, len([brand for bg in brand_groups for brand in bg.brands]))
def test_dereferencing_embedded_listfield_referencefield(self):
class Tag(Document):
meta = {'collection': 'tags'}
name = StringField()
class Post(EmbeddedDocument):
body = StringField()
tags = ListField(ReferenceField("Tag", dbref=True))
class Page(Document):
meta = {'collection': 'pages'}
tags = ListField(ReferenceField("Tag", dbref=True))
posts = ListField(EmbeddedDocumentField(Post))
Tag.drop_collection()
Page.drop_collection()
tag = Tag(name='test').save()
post = Post(body='test body', tags=[tag])
Page(tags=[tag], posts=[post]).save()
page = Page.objects.first()
self.assertEqual(page.tags[0], page.posts[0].tags[0])
def test_select_related_follows_embedded_referencefields(self):
class Song(Document):
title = StringField()
class PlaylistItem(EmbeddedDocument):
song = ReferenceField("Song")
class Playlist(Document):
items = ListField(EmbeddedDocumentField("PlaylistItem"))
Playlist.drop_collection()
Song.drop_collection()
songs = [Song.objects.create(title="song %d" % i) for i in range(3)]
items = [PlaylistItem(song=song) for song in songs]
playlist = Playlist.objects.create(items=items)
with query_counter() as q:
self.assertEqual(q, 0)
playlist = Playlist.objects.first().select_related()
songs = [item.song for item in playlist.items]
self.assertEqual(q, 2)
if __name__ == '__main__':
unittest.main()

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