Compare commits

...

358 Commits

Author SHA1 Message Date
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
67 changed files with 3634 additions and 1199 deletions

View File

@@ -1,23 +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.4" ]; then
echo "deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen" | sudo tee /etc/apt/sources.list.d/mongodb.list
sudo apt-get update
sudo apt-get install mongodb-10gen=2.4.14
sudo service mongodb start
elif [ "$MONGODB" = "2.6" ]; then
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 precise/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb.list
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.4, 2.6, or 3.0."
echo "Invalid MongoDB version, expected 2.6, 3.0, or 3.2"
exit 1
fi;
mkdir db
1>db/logs mongod --dbpath=db &

View File

@@ -2,12 +2,10 @@
# PyMongo combinations. However, that would result in an overly long build
# with a very large number of jobs, hence we only test a subset of all the
# combinations:
# * MongoDB v2.4 & v3.0 are only tested against Python v2.7 & v3.5.
# * MongoDB v2.4 is tested against PyMongo v2.7 & v3.x.
# * MongoDB v3.0 is tested against PyMongo v3.x.
# * MongoDB v2.6 is currently the "main" version tested against Python v2.7,
# v3.5, PyPy & PyPy3, and PyMongo v2.7, v2.8 & v3.x.
#
# 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
@@ -15,12 +13,11 @@ language: python
python:
- 2.7
- 3.5
- 3.6
- pypy
env:
- MONGODB=2.6 PYMONGO=2.7
- MONGODB=2.6 PYMONGO=2.8
- MONGODB=2.6 PYMONGO=3.0
- MONGODB=2.6 PYMONGO=3.x
matrix:
# Finish the build as soon as one job fails
@@ -28,20 +25,22 @@ matrix:
include:
- python: 2.7
env: MONGODB=2.4 PYMONGO=2.7
env: MONGODB=3.0 PYMONGO=3.5
- python: 2.7
env: MONGODB=2.4 PYMONGO=3.0
- python: 2.7
env: MONGODB=3.0 PYMONGO=3.0
env: MONGODB=3.2 PYMONGO=3.x
- python: 3.5
env: MONGODB=2.4 PYMONGO=2.7
env: MONGODB=3.0 PYMONGO=3.5
- python: 3.5
env: MONGODB=2.4 PYMONGO=3.0
- python: 3.5
env: MONGODB=3.0 PYMONGO=3.0
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:
- sudo apt-get install python-dev python3-dev libopenjpeg-dev zlib1g-dev libjpeg-turbo8-dev
@@ -90,11 +89,11 @@ deploy:
distributions: "sdist bdist_wheel"
# only deploy on tagged commits (aka GitHub releases) and only for the
# parent repo's builds running Python 2.7 along with dev PyMongo (we run
# 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.0"
condition: "$PYMONGO = 3.x"
python: 2.7

View File

@@ -243,3 +243,7 @@ that much better:
* 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)

View File

@@ -22,8 +22,11 @@ Supported Interpreters
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
-----------

View File

@@ -26,19 +26,21 @@ an `API reference <https://mongoengine-odm.readthedocs.io/apireference.html>`_.
Supported MongoDB Versions
==========================
MongoEngine is currently tested against MongoDB v2.4, v2.6, and v3.0. Future
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.2+.
problems with MongoDB v3.4+.
Installation
============
We recommend the use of `virtualenv <https://virtualenv.pypa.io/>`_ and of
`pip <https://pip.pypa.io/>`_. You can then use ``pip install -U mongoengine``.
You may also have `setuptools <http://peak.telecommunity.com/DevCenter/setuptools>`_
and thus you can use ``easy_install -U mongoengine``. Otherwise, you can download the
source from `GitHub <http://github.com/MongoEngine/mongoengine>`_ and run ``python
setup.py install``.
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
============

View File

@@ -87,7 +87,9 @@ Fields
.. 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

View File

@@ -6,6 +6,81 @@ Development
===========
- (Fill this out as you fix issues and develop your features).
=================
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

View File

@@ -45,27 +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)
#print '=' * post.title.count()
print "=" * 20
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

@@ -18,10 +18,10 @@ 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 -- just supply the URI as
the :attr:`host` to

View File

@@ -22,7 +22,7 @@ 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.
@@ -80,6 +80,7 @@ are as follows:
* :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`
@@ -87,6 +88,7 @@ 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`
@@ -224,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()
@@ -490,7 +492,9 @@ the field name with a **#**::
]
}
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.
@@ -511,8 +515,15 @@ 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
@@ -524,15 +535,16 @@ There are a few top level defaults for all indexes that can be set::
title = StringField()
rating = StringField()
meta = {
'index_options': {},
'index_opts': {},
'index_background': True,
'index_cls': False,
'auto_create_index': True,
'index_drop_dups': True,
'index_cls': False
}
:attr:`index_options` (Optional)
Set any default index options - see the `full options list <http://docs.mongodb.org/manual/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex>`_
:attr:`index_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
@@ -540,10 +552,15 @@ There are a few top level defaults for all indexes that can be set::
: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
.. note:: Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
Since MongoDB 3.0 drop_dups is not supported anymore. Raises a Warning
and has no effect
@@ -618,7 +635,7 @@ 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}
@@ -725,6 +742,9 @@ document.::
.. 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

View File

@@ -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)

View File

@@ -53,7 +53,8 @@ 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::
@@ -71,4 +72,5 @@ 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', 'rb')
marmot.photo.replace(another_marmot, content_type='image/png')
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

@@ -456,14 +456,14 @@ data. To turn off dereferencing of the results of a query use
:func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so::
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))
@@ -565,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

View File

@@ -43,10 +43,10 @@ Available signals include:
has taken place but before saving.
`post_save`
Called within :meth:`~mongoengine.Document.save` after all actions
(validation, insert/update, cascades, clearing dirty flags) have completed
successfully. Passed the additional boolean keyword argument `created` to
indicate if the save was an insert or an update.
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
@@ -113,6 +113,10 @@ handlers within your subclass::
signals.pre_save.connect(Author.pre_save, sender=Author)
signals.post_save.connect(Author.post_save, sender=Author)
.. warning::
Note that EmbeddedDocument only supports pre/post_init signals. pre/post_save, etc should be attached to Document's class only. Attaching pre_save to an EmbeddedDocument is ignored silently.
Finally, you can also use this small decorator to quickly create a number of
signals and attach them to your :class:`~mongoengine.Document` or
:class:`~mongoengine.EmbeddedDocument` subclasses as class decorators::

View File

@@ -48,4 +48,4 @@ Ordering by text score
::
objects = News.objects.search('mongo').order_by('$text_score')
objects = News.objects.search_text('mongo').order_by('$text_score')

View File

@@ -86,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
@@ -153,7 +153,7 @@ post. This works, but there is no real reason to be storing the comments
separately from their associated posts, other than to work around the
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::

View File

@@ -6,6 +6,11 @@ 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

View File

@@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) +
list(signals.__all__) + list(errors.__all__))
VERSION = (0, 14, 0)
VERSION = (0, 16, 2)
def get_version():

View File

@@ -15,7 +15,7 @@ __all__ = (
'UPDATE_OPERATORS', '_document_registry', 'get_document',
# datastructures
'BaseDict', 'BaseList', 'EmbeddedDocumentList',
'BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference',
# document
'BaseDocument',

View File

@@ -3,9 +3,10 @@ from mongoengine.errors import NotRegistered
__all__ = ('UPDATE_OPERATORS', 'get_document', '_document_registry')
UPDATE_OPERATORS = set(['set', 'unset', 'inc', 'dec', 'pop', 'push',
'push_all', 'pull', 'pull_all', 'add_to_set',
'set_on_insert', 'min', 'max', 'rename'])
UPDATE_OPERATORS = {'set', 'unset', 'inc', 'dec', 'mul',
'pop', 'push', 'push_all', 'pull',
'pull_all', 'add_to_set', 'set_on_insert',
'min', 'max', 'rename'}
_document_registry = {}
@@ -18,7 +19,7 @@ def get_document(name):
# 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,12 +1,12 @@
import itertools
import weakref
from bson import DBRef
import six
from mongoengine.common import _import_class
from mongoengine.errors import DoesNotExist, MultipleObjectsReturned
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList')
__all__ = ('BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference')
class BaseDict(dict):
@@ -17,10 +17,9 @@ class BaseDict(dict):
_name = None
def __init__(self, dict_items, instance, name):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
BaseDocument = _import_class('BaseDocument')
if isinstance(instance, (Document, EmbeddedDocument)):
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseDict, self).__init__(dict_items)
@@ -31,11 +30,11 @@ class BaseDict(dict):
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif not isinstance(value, BaseDict) and isinstance(value, dict):
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 not isinstance(value, BaseList) and isinstance(value, list):
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
@@ -102,10 +101,9 @@ class BaseList(list):
_name = None
def __init__(self, list_items, instance, name):
Document = _import_class('Document')
EmbeddedDocument = _import_class('EmbeddedDocument')
BaseDocument = _import_class('BaseDocument')
if isinstance(instance, (Document, EmbeddedDocument)):
if isinstance(instance, BaseDocument):
self._instance = weakref.proxy(instance)
self._name = name
super(BaseList, self).__init__(list_items)
@@ -116,19 +114,19 @@ class BaseList(list):
EmbeddedDocument = _import_class('EmbeddedDocument')
if isinstance(value, EmbeddedDocument) and value._instance is None:
value._instance = self._instance
elif not isinstance(value, BaseDict) and isinstance(value, dict):
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 not isinstance(value, BaseList) and isinstance(value, list):
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 __iter__(self):
for i in xrange(self.__len__()):
yield self[i]
for v in super(BaseList, self).__iter__():
yield v
def __setitem__(self, key, value, *args, **kwargs):
if isinstance(key, slice):
@@ -137,7 +135,7 @@ class BaseList(list):
self._mark_as_changed(key)
return super(BaseList, self).__setitem__(key, value)
def __delitem__(self, key, *args, **kwargs):
def __delitem__(self, key):
self._mark_as_changed()
return super(BaseList, self).__delitem__(key)
@@ -186,7 +184,7 @@ 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()
@@ -233,6 +231,9 @@ class EmbeddedDocumentList(BaseList):
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.*
@@ -350,7 +351,8 @@ class EmbeddedDocumentList(BaseList):
def update(self, **update):
"""
Updates the embedded documents with the given update values.
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
@@ -372,7 +374,7 @@ class EmbeddedDocumentList(BaseList):
class StrictDict(object):
__slots__ = ()
_special_fields = set(['get', 'pop', 'iteritems', 'items', 'keys', 'create'])
_special_fields = {'get', 'pop', 'iteritems', 'items', 'keys', 'create'}
_classes = {}
def __init__(self, **kwargs):
@@ -447,40 +449,40 @@ class StrictDict(object):
return cls._classes[allowed_keys]
class SemiStrictDict(StrictDict):
__slots__ = ('_extras', )
_classes = {}
class LazyReference(DBRef):
__slots__ = ('_cached_doc', 'passthrough', 'document_type')
def __getattr__(self, attr):
try:
super(SemiStrictDict, self).__getattr__(attr)
except AttributeError:
try:
return self.__getattribute__('_extras')[attr]
except KeyError as e:
raise AttributeError(e)
def 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
def __setattr__(self, attr, value):
try:
super(SemiStrictDict, self).__setattr__(attr, value)
except AttributeError:
try:
self._extras[attr] = value
except AttributeError:
self._extras = {attr: value}
@property
def pk(self):
return self.id
def __delattr__(self, attr):
try:
super(SemiStrictDict, self).__delattr__(attr)
except AttributeError:
try:
del self._extras[attr]
except KeyError as e:
raise AttributeError(e)
def __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 __iter__(self):
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:
extras_iter = iter(self.__getattribute__('_extras'))
except AttributeError:
extras_iter = ()
return itertools.chain(super(SemiStrictDict, self).__iter__(), extras_iter)
return document[name]
except KeyError:
raise AttributeError()
def __repr__(self):
return "<LazyReference(%s, %r)>" % (self.document_type, self.pk)

View File

@@ -1,6 +1,5 @@
import copy
import numbers
from collections import Hashable
from functools import partial
from bson import ObjectId, json_util
@@ -13,13 +12,15 @@ from mongoengine import signals
from mongoengine.base.common import get_document
from mongoengine.base.datastructures import (BaseDict, BaseList,
EmbeddedDocumentList,
SemiStrictDict, StrictDict)
LazyReference,
StrictDict)
from mongoengine.base.fields import ComplexBaseField
from mongoengine.common import _import_class
from mongoengine.errors import (FieldDoesNotExist, InvalidDocumentError,
LookUpError, OperationError, ValidationError)
from mongoengine.python_support import Hashable
__all__ = ('BaseDocument',)
__all__ = ('BaseDocument', 'NON_FIELD_ERRORS')
NON_FIELD_ERRORS = '__all__'
@@ -79,8 +80,7 @@ class BaseDocument(object):
if self.STRICT and not self._dynamic:
self._data = StrictDict.create(allowed_keys=self._fields_ordered)()
else:
self._data = SemiStrictDict.create(
allowed_keys=self._fields_ordered)()
self._data = {}
self._dynamic_fields = SON()
@@ -100,13 +100,11 @@ class BaseDocument(object):
for key, value in values.iteritems():
if key in self._fields or key == '_id':
setattr(self, key, value)
elif self._dynamic:
else:
dynamic_data[key] = value
else:
FileField = _import_class('FileField')
for key, value in values.iteritems():
if key == '__auto_convert':
continue
key = self._reverse_db_field_map.get(key, key)
if key in self._fields or key in ('id', 'pk', '_cls'):
if __auto_convert and value is not None:
@@ -147,7 +145,7 @@ class BaseDocument(object):
if not hasattr(self, name) and not name.startswith('_'):
DynamicField = _import_class('DynamicField')
field = DynamicField(db_field=name)
field = DynamicField(db_field=name, null=True)
field.name = name
self._dynamic_fields[name] = field
self._fields_ordered += (name,)
@@ -304,7 +302,7 @@ class BaseDocument(object):
data['_cls'] = self._class_name
# only root fields ['test1.a', 'test2'] => ['test1', 'test2']
root_fields = set([f.split('.')[0] for f in fields])
root_fields = {f.split('.')[0] for f in fields}
for field_name in self:
if root_fields and field_name not in root_fields:
@@ -337,7 +335,7 @@ class BaseDocument(object):
value = field.generate()
self._data[field_name] = value
if value is not None:
if (value is not None) or (field.null):
if use_db_field:
data[field.db_field] = value
else:
@@ -406,7 +404,15 @@ class BaseDocument(object):
@classmethod
def from_json(cls, json_data, created=False):
"""Converts json data to an unsaved document instance"""
"""Converts json data to a Document instance
:param json_data: The json data to load into the Document
:param created: If True, the document will be considered as a brand new document
If False and an id is provided, it will consider that the data being
loaded corresponds to what's already in the database (This has an impact of subsequent call to .save())
If False and no id is provided, it will consider the data as a new document
(default ``False``)
"""
return cls._from_son(json_util.loads(json_data), created=created)
def __expand_dynamic_values(self, name, value):
@@ -489,7 +495,7 @@ class BaseDocument(object):
else:
data = getattr(data, part, None)
if hasattr(data, '_changed_fields'):
if not isinstance(data, LazyReference) and hasattr(data, '_changed_fields'):
if getattr(data, '_is_document', False):
continue
@@ -497,7 +503,13 @@ class BaseDocument(object):
self._changed_fields = []
def _nestable_types_changed_fields(self, changed_fields, key, data, inspected):
def _nestable_types_changed_fields(self, changed_fields, base_key, data):
"""Inspect nested data for changed fields
:param changed_fields: Previously collected changed fields
:param base_key: The base key that must be used to prepend changes to this data
:param data: data to inspect for changes
"""
# Loop list / dict fields as they contain documents
# Determine the iterator to use
if not hasattr(data, 'items'):
@@ -505,68 +517,60 @@ class BaseDocument(object):
else:
iterator = data.iteritems()
for index, value in iterator:
list_key = '%s%s.' % (key, index)
for index_or_key, value in iterator:
item_key = '%s%s.' % (base_key, index_or_key)
# don't check anything lower if this key is already marked
# as changed.
if list_key[:-1] in changed_fields:
if item_key[:-1] in changed_fields:
continue
if hasattr(value, '_get_changed_fields'):
changed = value._get_changed_fields(inspected)
changed_fields += ['%s%s' % (list_key, k)
for k in changed if k]
changed = value._get_changed_fields()
changed_fields += ['%s%s' % (item_key, k) for k in changed if k]
elif isinstance(value, (list, tuple, dict)):
self._nestable_types_changed_fields(
changed_fields, list_key, value, inspected)
changed_fields, item_key, value)
def _get_changed_fields(self, inspected=None):
def _get_changed_fields(self):
"""Return a list of all fields that have explicitly been changed.
"""
EmbeddedDocument = _import_class('EmbeddedDocument')
DynamicEmbeddedDocument = _import_class('DynamicEmbeddedDocument')
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
SortedListField = _import_class('SortedListField')
changed_fields = []
changed_fields += getattr(self, '_changed_fields', [])
inspected = inspected or set()
if hasattr(self, 'id') and isinstance(self.id, Hashable):
if self.id in inspected:
return changed_fields
inspected.add(self.id)
for field_name in self._fields_ordered:
db_field_name = self._db_field_map.get(field_name, field_name)
key = '%s.' % db_field_name
data = self._data.get(field_name, None)
field = self._fields.get(field_name)
if hasattr(data, 'id'):
if data.id in inspected:
continue
if isinstance(field, ReferenceField):
if db_field_name in changed_fields:
# Whole field already marked as changed, no need to go further
continue
elif (
isinstance(data, (EmbeddedDocument, DynamicEmbeddedDocument)) and
db_field_name not in changed_fields
):
if isinstance(field, ReferenceField): # Don't follow referenced documents
continue
if isinstance(data, EmbeddedDocument):
# Find all embedded fields that have been changed
changed = data._get_changed_fields(inspected)
changed = data._get_changed_fields()
changed_fields += ['%s%s' % (key, k) for k in changed if k]
elif (isinstance(data, (list, tuple, dict)) and
db_field_name not in changed_fields):
elif isinstance(data, (list, tuple, dict)):
if (hasattr(field, 'field') and
isinstance(field.field, ReferenceField)):
isinstance(field.field, (ReferenceField, GenericReferenceField))):
continue
elif isinstance(field, SortedListField) and field._ordering:
# if ordering is affected whole list is changed
if any(map(lambda d: field._ordering in d._changed_fields, data)):
if any(field._ordering in d._changed_fields for d in data):
changed_fields.append(db_field_name)
continue
self._nestable_types_changed_fields(
changed_fields, key, data, inspected)
changed_fields, key, data)
return changed_fields
def _delta(self):
@@ -578,7 +582,6 @@ class BaseDocument(object):
set_fields = self._get_changed_fields()
unset_data = {}
parts = []
if hasattr(self, '_changed_fields'):
set_data = {}
# Fetch each set item from its path
@@ -588,15 +591,13 @@ class BaseDocument(object):
new_path = []
for p in parts:
if isinstance(d, (ObjectId, DBRef)):
# Don't dig in the references
break
elif isinstance(d, list) and p.lstrip('-').isdigit():
if p[0] == '-':
p = str(len(d) + int(p))
try:
d = d[int(p)]
except IndexError:
d = None
elif isinstance(d, list) and p.isdigit():
# An item of a list (identified by its index) is updated
d = d[int(p)]
elif hasattr(d, 'get'):
# dict-like (dict, embedded document)
d = d.get(p)
new_path.append(p)
path = '.'.join(new_path)
@@ -608,26 +609,26 @@ class BaseDocument(object):
# Determine if any changed items were actually unset.
for path, value in set_data.items():
if value or isinstance(value, (numbers.Number, bool)):
if value or isinstance(value, (numbers.Number, bool)): # Account for 0 and True that are truthy
continue
# If we've set a value that ain't the default value don't unset it.
default = None
parts = path.split('.')
if (self._dynamic and len(parts) and parts[0] in
self._dynamic_fields):
del set_data[path]
unset_data[path] = 1
continue
elif path in self._fields:
# If we've set a value that ain't the default value don't unset it.
default = None
if path in self._fields:
default = self._fields[path].default
else: # Perform a full lookup for lists / embedded lookups
d = self
parts = path.split('.')
db_field_name = parts.pop()
for p in parts:
if isinstance(d, list) and p.lstrip('-').isdigit():
if p[0] == '-':
p = str(len(d) + int(p))
if isinstance(d, list) and p.isdigit():
d = d[int(p)]
elif (hasattr(d, '__getattribute__') and
not isinstance(d, dict)):
@@ -645,10 +646,9 @@ class BaseDocument(object):
default = None
if default is not None:
if callable(default):
default = default()
default = default() if callable(default) else default
if default != value:
if value != default:
continue
del set_data[path]
@@ -694,7 +694,7 @@ class BaseDocument(object):
fields = cls._fields
if not _auto_dereference:
fields = copy.copy(fields)
fields = copy.deepcopy(fields)
for field_name, field in fields.iteritems():
field._auto_dereference = _auto_dereference
@@ -1080,5 +1080,11 @@ class BaseDocument(object):
"""Return the display value for a choice field"""
value = getattr(self, field.name)
if field.choices and isinstance(field.choices[0], (list, tuple)):
return dict(field.choices).get(value, value)
if value is None:
return None
sep = getattr(field, 'display_sep', ' ')
values = value if field.__class__.__name__ in ('ListField', 'SortedListField') else [value]
return sep.join([
six.text_type(dict(field.choices).get(val, val))
for val in values or []])
return value

View File

@@ -55,7 +55,7 @@ class BaseField(object):
field. Generally this is deprecated in favour of the
`FIELD.validate` method
:param choices: (optional) The valid choices
:param null: (optional) Is the field value can be null. If no and there is a default value
: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
@@ -130,7 +130,6 @@ class BaseField(object):
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:
@@ -213,8 +212,10 @@ class BaseField(object):
)
)
# Choices which are types other than Documents
elif value not in choice_list:
self.error('Value must be one of %s' % six.text_type(choice_list))
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
@@ -265,13 +266,15 @@ class ComplexBaseField(BaseField):
ReferenceField = _import_class('ReferenceField')
GenericReferenceField = _import_class('GenericReferenceField')
EmbeddedDocumentListField = _import_class('EmbeddedDocumentListField')
dereference = (self._auto_dereference and
auto_dereference = instance._fields[self.name]._auto_dereference
dereference = (auto_dereference and
(self.field is None or isinstance(self.field,
(GenericReferenceField, ReferenceField))))
_dereference = _import_class('DeReference')()
self._auto_dereference = instance._fields[self.name]._auto_dereference
if instance._initialised and dereference and instance._data.get(self.name):
instance._data[self.name] = _dereference(
instance._data.get(self.name), max_depth=1, instance=instance,
@@ -292,7 +295,7 @@ class ComplexBaseField(BaseField):
value = BaseDict(value, instance, self.name)
instance._data[self.name] = value
if (self._auto_dereference and instance._initialised and
if (auto_dereference and instance._initialised and
isinstance(value, (BaseList, BaseDict)) and
not value._dereferenced):
value = _dereference(
@@ -311,11 +314,16 @@ class ComplexBaseField(BaseField):
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 = {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
@@ -500,7 +508,7 @@ class GeoJsonBaseField(BaseField):
def validate(self, value):
"""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))

View File

@@ -18,14 +18,14 @@ class DocumentMetaclass(type):
"""Metaclass for all documents."""
# TODO lower complexity of this method
def __new__(cls, name, bases, attrs):
flattened_bases = cls._get_bases(bases)
super_new = super(DocumentMetaclass, cls).__new__
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'] = []
@@ -121,7 +121,8 @@ class DocumentMetaclass(type):
# inheritance of classes where inheritance is set to False
allow_inheritance = base._meta.get('allow_inheritance')
if not allow_inheritance and not base._meta.get('abstract'):
raise ValueError('Document %s may not be subclassed' %
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
@@ -138,7 +139,7 @@ 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:
@@ -147,7 +148,7 @@ class DocumentMetaclass(type):
base._types = base._subclasses # TODO depreciate _types
(Document, EmbeddedDocument, DictField,
CachedReferenceField) = cls._import_classes()
CachedReferenceField) = mcs._import_classes()
if issubclass(new_class, Document):
new_class._collection = None
@@ -219,29 +220,26 @@ class DocumentMetaclass(type):
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')
@@ -254,9 +252,9 @@ 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:
@@ -319,7 +317,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
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
@@ -360,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
@@ -394,7 +392,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
'_auto_id_field', False)
if not new_class._meta.get('id_field'):
# After 0.10, find not existing names, instead of overwriting
id_name, id_db_name = cls.get_auto_id_names(new_class)
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)
@@ -419,7 +417,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
return new_class
@classmethod
def get_auto_id_names(cls, new_class):
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()):

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

@@ -28,7 +28,7 @@ _connections = {}
_dbs = {}
def register_connection(alias, name=None, host=None, port=None,
def register_connection(alias, db=None, name=None, host=None, port=None,
read_preference=READ_PREFERENCE,
username=None, password=None,
authentication_source=None,
@@ -39,6 +39,7 @@ def register_connection(alias, name=None, host=None, port=None,
: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 read_preference: The read preference for the collection
@@ -58,7 +59,7 @@ def register_connection(alias, name=None, host=None, port=None,
.. versionchanged:: 0.10.6 - added mongomock support
"""
conn_settings = {
'name': name or 'test',
'name': name or db or 'test',
'host': host or 'localhost',
'port': port or 27017,
'read_preference': read_preference,
@@ -103,6 +104,18 @@ def register_connection(alias, name=None, host=None, port=None,
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
@@ -146,13 +159,14 @@ def get_connection(alias=DEFAULT_CONNECTION_NAME, reconnect=False):
raise MongoEngineConnectionError(msg)
def _clean_settings(settings_dict):
irrelevant_fields = set([
'name', 'username', 'password', 'authentication_source',
'authentication_mechanism'
])
# 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
if k not in irrelevant_fields_set
}
# Retrieve a copy of the connection settings associated with the requested

View File

@@ -1,9 +1,11 @@
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
__all__ = ('switch_db', 'switch_collection', 'no_dereference',
'no_sub_classes', 'query_counter')
'no_sub_classes', 'query_counter', 'set_write_concern')
class switch_db(object):
@@ -143,66 +145,85 @@ class no_sub_classes(object):
: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,)
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
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."""
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):
@@ -210,8 +231,17 @@ class query_counter(object):
return u"%s" % self._get_count()
def _get_count(self):
"""Get the number of queries."""
ignore_query = {'ns': {'$ne': '%s.system.indexes' % self.db.name}}
count = self.db.system.profile.find(ignore_query).count() - self.counter
self.counter += 1
"""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

@@ -3,6 +3,7 @@ import six
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
@@ -99,7 +100,10 @@ class DeReference(object):
if isinstance(item, (Document, EmbeddedDocument)):
for field_name, field in item._fields.iteritems():
v = item._data.get(field_name, None)
if isinstance(v, DBRef):
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']), set()).add(v['_ref'].id)
@@ -110,6 +114,9 @@ class DeReference(object):
if isinstance(field_cls, (Document, TopLevelDocumentMetaclass)):
key = field_cls
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:
@@ -126,7 +133,12 @@ class DeReference(object):
"""
object_map = {}
for collection, dbrefs in self.reference_map.iteritems():
if hasattr(collection, 'objects'): # We have a document class for the refs
# we use getattr instead of hasattr because hasattr swallows any exception under python2
# so it could hide nasty things without raising exceptions (cfr bug #1688))
ref_document_cls_exists = (getattr(collection, 'objects', None) is not None)
if ref_document_cls_exists:
col_name = collection._get_collection_name()
refs = [dbref for dbref in dbrefs
if (col_name, dbref) not in object_map]
@@ -134,7 +146,7 @@ class DeReference(object):
for key, doc in references.iteritems():
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
@@ -230,7 +242,7 @@ class DeReference(object):
elif isinstance(v, (dict, list, tuple)) and depth <= self.max_depth:
item_name = '%s.%s' % (name, k) if name else name
data[k] = self._attach_objects(v, depth - 1, instance=instance, name=item_name)
elif hasattr(v, 'id'):
elif isinstance(v, DBRef) and hasattr(v, 'id'):
data[k] = self.object_map.get((v.collection, v.id), v)
if instance and name:

View File

@@ -12,7 +12,9 @@ from mongoengine.base import (BaseDict, BaseDocument, BaseList,
TopLevelDocumentMetaclass, get_document)
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.context_managers import switch_collection, switch_db
from mongoengine.context_managers import (set_write_concern,
switch_collection,
switch_db)
from mongoengine.errors import (InvalidDocumentError, InvalidQueryError,
SaveConditionError)
from mongoengine.python_support import IS_PYMONGO_3
@@ -39,7 +41,7 @@ class InvalidCollectionError(Exception):
pass
class EmbeddedDocument(BaseDocument):
class EmbeddedDocument(six.with_metaclass(DocumentMetaclass, BaseDocument)):
"""A :class:`~mongoengine.Document` that isn't stored in its own
collection. :class:`~mongoengine.EmbeddedDocument`\ s should be used as
fields on :class:`~mongoengine.Document`\ s through the
@@ -58,7 +60,6 @@ class EmbeddedDocument(BaseDocument):
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = DocumentMetaclass
__metaclass__ = DocumentMetaclass
# A generic embedded document doesn't have any immutable properties
# that describe it uniquely, hence it shouldn't be hashable. You can
@@ -95,7 +96,7 @@ class EmbeddedDocument(BaseDocument):
self._instance.reload(*args, **kwargs)
class Document(BaseDocument):
class Document(six.with_metaclass(TopLevelDocumentMetaclass, BaseDocument)):
"""The base class used for defining the structure and properties of
collections of documents stored in MongoDB. Inherit from this class, and
add fields as class attributes to define a document's structure.
@@ -150,7 +151,6 @@ class Document(BaseDocument):
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = TopLevelDocumentMetaclass
__metaclass__ = TopLevelDocumentMetaclass
__slots__ = ('__objects',)
@@ -172,8 +172,8 @@ class Document(BaseDocument):
"""
if self.pk is None:
return super(BaseDocument, self).__hash__()
else:
return hash(self.pk)
return hash(self.pk)
@classmethod
def _get_db(cls):
@@ -195,7 +195,10 @@ class Document(BaseDocument):
# Ensure indexes on the collection unless auto_create_index was
# set to False.
if cls._meta.get('auto_create_index', True):
# Also there is no need to ensure indexes on slave.
db = cls._get_db()
if cls._meta.get('auto_create_index', True) and\
db.client.is_primary:
cls.ensure_indexes()
return cls._collection
@@ -280,6 +283,9 @@ class Document(BaseDocument):
elif query[id_field] != self.pk:
raise InvalidQueryError('Invalid document modify query: it must modify only this document.')
# Need to add shard key to query, or you get an error
query.update(self._object_key)
updated = self._qs(**query).modify(new=True, **update)
if updated is None:
return False
@@ -320,7 +326,7 @@ class Document(BaseDocument):
:param save_condition: only perform save if matching record in db
satisfies condition(s) (e.g. version number).
Raises :class:`OperationError` if the conditions are not satisfied
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
:param signal_kwargs: (optional) kwargs dictionary to be passed to
the signal calls.
.. versionchanged:: 0.5
@@ -364,6 +370,8 @@ class Document(BaseDocument):
signals.pre_save_post_validation.send(self.__class__, document=self,
created=created, **signal_kwargs)
# it might be refreshed by the pre_save_post_validation hook, e.g., for etag generation
doc = self.to_mongo()
if self._meta.get('auto_create_index', True):
self.ensure_indexes()
@@ -423,11 +431,18 @@ class Document(BaseDocument):
Helper method, should only be used inside save().
"""
collection = self._get_collection()
with set_write_concern(collection, write_concern) as wc_collection:
if force_insert:
return wc_collection.insert_one(doc).inserted_id
# insert_one will provoke UniqueError alongside save does not
# therefore, it need to catch and call replace_one.
if '_id' in doc:
raw_object = wc_collection.find_one_and_replace(
{'_id': doc['_id']}, doc)
if raw_object:
return doc['_id']
if force_insert:
return collection.insert(doc, **write_concern)
object_id = collection.save(doc, **write_concern)
object_id = wc_collection.insert_one(doc).inserted_id
# In PyMongo 3.0, the save() call calls internally the _update() call
# but they forget to return the _id value passed back, therefore getting it back here
@@ -576,12 +591,11 @@ class Document(BaseDocument):
"""Delete the :class:`~mongoengine.Document` from the database. This
will only take effect if the document has been previously saved.
:parm signal_kwargs: (optional) kwargs dictionary to be passed to
:param signal_kwargs: (optional) kwargs dictionary to be passed to
the signal calls.
:param write_concern: Extra keyword arguments are passed down which
will be used as options for the resultant
``getLastError`` command. For example,
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
will be used as options for the resultant ``getLastError`` command.
For example, ``save(..., w: 2, fsync: True)`` will
wait until at least two servers have recorded the write and
will force an fsync on the primary server.
@@ -702,7 +716,6 @@ class Document(BaseDocument):
obj = obj[0]
else:
raise self.DoesNotExist('Document does not exist')
for field in obj._data:
if not fields or field in fields:
try:
@@ -710,7 +723,7 @@ class Document(BaseDocument):
except (KeyError, AttributeError):
try:
# If field is a special field, e.g. items is stored as _reserved_items,
# an KeyError is thrown. So try to retrieve the field from _data
# a KeyError is thrown. So try to retrieve the field from _data
setattr(self, field, self._reload(field, obj._data.get(field)))
except KeyError:
# If field is removed from the database while the object
@@ -718,7 +731,9 @@ class Document(BaseDocument):
# i.e. obj.update(unset__field=1) followed by obj.reload()
delattr(self, field)
self._changed_fields = obj._changed_fields
self._changed_fields = list(
set(self._changed_fields) - set(fields)
) if fields else obj._changed_fields
self._created = False
return self
@@ -964,8 +979,16 @@ class Document(BaseDocument):
"""
required = cls.list_indexes()
existing = [info['key']
for info in cls._get_collection().index_information().values()]
existing = []
for info in cls._get_collection().index_information().values():
if '_fts' in info['key'][0]:
index_type = info['key'][0][1]
text_index_fields = info.get('weights').keys()
existing.append(
[(key, index_type) for key in text_index_fields])
else:
existing.append(info['key'])
missing = [index for index in required if index not in existing]
extra = [index for index in existing if index not in required]
@@ -982,10 +1005,10 @@ class Document(BaseDocument):
return {'missing': missing, 'extra': extra}
class DynamicDocument(Document):
class DynamicDocument(six.with_metaclass(TopLevelDocumentMetaclass, Document)):
"""A Dynamic Document class allowing flexible, expandable and uncontrolled
schemas. As a :class:`~mongoengine.Document` subclass, acts in the same
way as an ordinary document but has expando style properties. Any data
way as an ordinary document but has expanded style properties. Any data
passed or set against the :class:`~mongoengine.DynamicDocument` that is
not a field is automatically converted into a
:class:`~mongoengine.fields.DynamicField` and data can be attributed to that
@@ -999,7 +1022,6 @@ class DynamicDocument(Document):
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = TopLevelDocumentMetaclass
__metaclass__ = TopLevelDocumentMetaclass
_dynamic = True
@@ -1010,11 +1032,12 @@ class DynamicDocument(Document):
field_name = args[0]
if field_name in self._dynamic_fields:
setattr(self, field_name, None)
self._dynamic_fields[field_name].null = False
else:
super(DynamicDocument, self).__delattr__(*args, **kwargs)
class DynamicEmbeddedDocument(EmbeddedDocument):
class DynamicEmbeddedDocument(six.with_metaclass(DocumentMetaclass, EmbeddedDocument)):
"""A Dynamic Embedded Document class allowing flexible, expandable and
uncontrolled schemas. See :class:`~mongoengine.DynamicDocument` for more
information about dynamic documents.
@@ -1023,7 +1046,6 @@ class DynamicEmbeddedDocument(EmbeddedDocument):
# The __metaclass__ attribute is removed by 2to3 when running with Python3
# my_metaclass is defined so that metaclass can be queried in Python 2 & 3
my_metaclass = DocumentMetaclass
__metaclass__ = DocumentMetaclass
_dynamic = True

View File

@@ -71,6 +71,7 @@ class ValidationError(AssertionError):
_message = None
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

View File

@@ -5,7 +5,6 @@ import re
import socket
import time
import uuid
import warnings
from operator import itemgetter
from bson import Binary, DBRef, ObjectId, SON
@@ -25,13 +24,18 @@ try:
except ImportError:
Int64 = long
from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField,
GeoJsonBaseField, ObjectIdField, get_document)
GeoJsonBaseField, LazyReference, ObjectIdField,
get_document)
from mongoengine.base.utils import LazyRegexCompiler
from mongoengine.common import _import_class
from mongoengine.connection import DEFAULT_CONNECTION_NAME, get_db
from mongoengine.document import Document, EmbeddedDocument
from mongoengine.errors import DoesNotExist, InvalidQueryError, ValidationError
from mongoengine.python_support import StringIO
from mongoengine.queryset import DO_NOTHING, QuerySet
from mongoengine.queryset import DO_NOTHING
from mongoengine.queryset.base import BaseQuerySet
try:
from PIL import Image, ImageOps
@@ -39,13 +43,20 @@ except ImportError:
Image = None
ImageOps = None
if six.PY3:
# Useless as long as 2to3 gets executed
# as it turns `long` into `int` blindly
long = int
__all__ = (
'StringField', 'URLField', 'EmailField', 'IntField', 'LongField',
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField',
'FloatField', 'DecimalField', 'BooleanField', 'DateTimeField', 'DateField',
'ComplexDateTimeField', 'EmbeddedDocumentField', 'ObjectIdField',
'GenericEmbeddedDocumentField', 'DynamicField', 'ListField',
'SortedListField', 'EmbeddedDocumentListField', 'DictField',
'MapField', 'ReferenceField', 'CachedReferenceField',
'LazyReferenceField', 'GenericLazyReferenceField',
'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy',
'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField',
'GeoPointField', 'PointField', 'LineStringField', 'PolygonField',
@@ -120,9 +131,9 @@ class URLField(StringField):
.. versionadded:: 0.3
"""
_URL_REGEX = re.compile(
_URL_REGEX = LazyRegexCompiler(
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain...
r'(?:(?:[A-Z0-9](?:[A-Z0-9-_]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}(?<!-)\.?)|' # domain...
r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
@@ -130,8 +141,7 @@ class URLField(StringField):
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
_URL_SCHEMES = ['http', 'https', 'ftp', 'ftps']
def __init__(self, verify_exists=False, url_regex=None, schemes=None, **kwargs):
self.verify_exists = verify_exists
def __init__(self, url_regex=None, schemes=None, **kwargs):
self.url_regex = url_regex or self._URL_REGEX
self.schemes = schemes or self._URL_SCHEMES
super(URLField, self).__init__(**kwargs)
@@ -154,7 +164,7 @@ class EmailField(StringField):
.. versionadded:: 0.4
"""
USER_REGEX = re.compile(
USER_REGEX = LazyRegexCompiler(
# `dot-atom` defined in RFC 5322 Section 3.2.3.
r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*\Z"
# `quoted-string` defined in RFC 5322 Section 3.2.4.
@@ -162,7 +172,7 @@ class EmailField(StringField):
re.IGNORECASE
)
UTF8_USER_REGEX = re.compile(
UTF8_USER_REGEX = LazyRegexCompiler(
six.u(
# RFC 6531 Section 3.3 extends `atext` (used by dot-atom) to
# include `UTF8-non-ascii`.
@@ -172,7 +182,7 @@ class EmailField(StringField):
), re.IGNORECASE | re.UNICODE
)
DOMAIN_REGEX = re.compile(
DOMAIN_REGEX = LazyRegexCompiler(
r'((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z',
re.IGNORECASE
)
@@ -264,14 +274,14 @@ class IntField(BaseField):
def to_python(self, value):
try:
value = int(value)
except ValueError:
except (TypeError, ValueError):
pass
return value
def validate(self, value):
try:
value = int(value)
except Exception:
except (TypeError, ValueError):
self.error('%s could not be converted to int' % value)
if self.min_value is not None and value < self.min_value:
@@ -297,7 +307,7 @@ class LongField(BaseField):
def to_python(self, value):
try:
value = long(value)
except ValueError:
except (TypeError, ValueError):
pass
return value
@@ -307,7 +317,7 @@ class LongField(BaseField):
def validate(self, value):
try:
value = long(value)
except Exception:
except (TypeError, ValueError):
self.error('%s could not be converted to long' % value)
if self.min_value is not None and value < self.min_value:
@@ -361,7 +371,8 @@ class FloatField(BaseField):
class DecimalField(BaseField):
"""Fixed-point decimal number field.
"""Fixed-point decimal number field. Stores the value as a float by default unless `force_string` is used.
If using floats, beware of Decimal to float conversion (potential precision loss)
.. versionchanged:: 0.8
.. versionadded:: 0.3
@@ -372,7 +383,9 @@ class DecimalField(BaseField):
"""
:param min_value: Validation rule for the minimum acceptable value.
:param max_value: Validation rule for the maximum acceptable value.
:param force_string: Store as a string.
:param force_string: Store the value as a string (instead of a float).
Be aware that this affects query sorting and operation like lte, gte (as string comparison is applied)
and some query operator won't work (e.g: inc, dec)
:param precision: Number of decimal places to store.
:param rounding: The rounding rule from the python decimal library:
@@ -403,7 +416,7 @@ class DecimalField(BaseField):
# Convert to string for python 2.6 before casting to Decimal
try:
value = decimal.Decimal('%s' % value)
except decimal.InvalidOperation:
except (TypeError, ValueError, decimal.InvalidOperation):
return value
return value.quantize(decimal.Decimal('.%s' % ('0' * self.precision)), rounding=self.rounding)
@@ -420,7 +433,7 @@ class DecimalField(BaseField):
value = six.text_type(value)
try:
value = decimal.Decimal(value)
except Exception as exc:
except (TypeError, ValueError, decimal.InvalidOperation) as exc:
self.error('Could not convert value to decimal: %s' % exc)
if self.min_value is not None and value < self.min_value:
@@ -459,6 +472,8 @@ class DateTimeField(BaseField):
installed you can utilise it to convert varying types of date formats into valid
python datetime objects.
Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
Note: Microseconds are rounded to the nearest millisecond.
Pre UTC microsecond support is effectively broken.
Use :class:`~mongoengine.fields.ComplexDateTimeField` if you
@@ -483,6 +498,10 @@ class DateTimeField(BaseField):
if not isinstance(value, six.string_types):
return None
value = value.strip()
if not value:
return None
# Attempt to parse a datetime:
if dateutil:
try:
@@ -518,6 +537,22 @@ class DateTimeField(BaseField):
return super(DateTimeField, self).prepare_query_value(op, self.to_mongo(value))
class DateField(DateTimeField):
def to_mongo(self, value):
value = super(DateField, self).to_mongo(value)
# drop hours, minutes, seconds
if isinstance(value, datetime.datetime):
value = datetime.datetime(value.year, value.month, value.day)
return value
def to_python(self, value):
value = super(DateField, self).to_python(value)
# convert datetime to date
if isinstance(value, datetime.datetime):
value = datetime.date(value.year, value.month, value.day)
return value
class ComplexDateTimeField(StringField):
"""
ComplexDateTimeField handles microseconds exactly instead of rounding
@@ -534,11 +569,15 @@ class ComplexDateTimeField(StringField):
The `,` as the separator can be easily modified by passing the `separator`
keyword when initializing the field.
Note: To default the field to the current datetime, use: DateTimeField(default=datetime.utcnow)
.. versionadded:: 0.5
"""
def __init__(self, separator=',', **kwargs):
self.names = ['year', 'month', 'day', 'hour', 'minute', 'second', 'microsecond']
"""
:param separator: Allows to customize the separator used for storage (default ``,``)
"""
self.separator = separator
self.format = separator.join(['%Y', '%m', '%d', '%H', '%M', '%S', '%f'])
super(ComplexDateTimeField, self).__init__(**kwargs)
@@ -565,20 +604,24 @@ class ComplexDateTimeField(StringField):
>>> ComplexDateTimeField()._convert_from_string(a)
datetime.datetime(2011, 6, 8, 20, 26, 24, 92284)
"""
values = map(int, data.split(self.separator))
values = [int(d) for d in data.split(self.separator)]
return datetime.datetime(*values)
def __get__(self, instance, owner):
if instance is None:
return self
data = super(ComplexDateTimeField, self).__get__(instance, owner)
if data is None:
return None if self.null else datetime.datetime.now()
if isinstance(data, datetime.datetime):
if isinstance(data, datetime.datetime) or data is None:
return data
return self._convert_from_string(data)
def __set__(self, instance, value):
value = self._convert_from_datetime(value) if value else value
return super(ComplexDateTimeField, self).__set__(instance, value)
super(ComplexDateTimeField, self).__set__(instance, value)
value = instance._data[self.name]
if value is not None:
instance._data[self.name] = self._convert_from_datetime(value)
def validate(self, value):
value = self.to_python(value)
@@ -607,6 +650,7 @@ class EmbeddedDocumentField(BaseField):
"""
def __init__(self, document_type, **kwargs):
# XXX ValidationError raised outside of the "validate" method.
if not (
isinstance(document_type, six.string_types) or
issubclass(document_type, EmbeddedDocument)
@@ -621,9 +665,17 @@ class EmbeddedDocumentField(BaseField):
def document_type(self):
if isinstance(self.document_type_obj, six.string_types):
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
self.document_type_obj = self.owner_document
resolved_document_type = self.owner_document
else:
self.document_type_obj = get_document(self.document_type_obj)
resolved_document_type = get_document(self.document_type_obj)
if not issubclass(resolved_document_type, EmbeddedDocument):
# Due to the late resolution of the document_type
# There is a chance that it won't be an EmbeddedDocument (#1661)
self.error('Invalid embedded document class provided to an '
'EmbeddedDocumentField')
self.document_type_obj = resolved_document_type
return self.document_type_obj
def to_python(self, value):
@@ -682,16 +734,28 @@ class GenericEmbeddedDocumentField(BaseField):
return value
def validate(self, value, clean=True):
if self.choices and isinstance(value, SON):
for choice in self.choices:
if value['_cls'] == choice._class_name:
return True
if not isinstance(value, EmbeddedDocument):
self.error('Invalid embedded document instance provided to an '
'GenericEmbeddedDocumentField')
value.validate(clean=clean)
def lookup_member(self, member_name):
if self.choices:
for choice in self.choices:
field = choice._fields.get(member_name)
if field:
return field
return None
def to_mongo(self, document, use_db_field=True, fields=None):
if document is None:
return None
data = document.to_mongo(use_db_field, fields)
if '_cls' not in data:
data['_cls'] = document._class_name
@@ -775,10 +839,20 @@ class ListField(ComplexBaseField):
kwargs.setdefault('default', lambda: [])
super(ListField, self).__init__(**kwargs)
def __get__(self, instance, owner):
if instance is None:
# Document class being used rather than a document object
return self
value = instance._data.get(self.name)
LazyReferenceField = _import_class('LazyReferenceField')
GenericLazyReferenceField = _import_class('GenericLazyReferenceField')
if isinstance(self.field, (LazyReferenceField, GenericLazyReferenceField)) and value:
instance._data[self.name] = [self.field.build_lazyref(x) for x in value]
return super(ListField, self).__get__(instance, owner)
def validate(self, value):
"""Make sure that a list of valid fields is being used."""
if (not isinstance(value, (list, tuple, QuerySet)) or
isinstance(value, six.string_types)):
if not isinstance(value, (list, tuple, BaseQuerySet)):
self.error('Only lists and tuples may be used in a list field')
super(ListField, self).validate(value)
@@ -885,12 +959,10 @@ class DictField(ComplexBaseField):
.. versionchanged:: 0.5 - Can now handle complex / varying types of data
"""
def __init__(self, basecls=None, field=None, *args, **kwargs):
def __init__(self, field=None, *args, **kwargs):
self.field = field
self._auto_dereference = False
self.basecls = basecls or BaseField
if not issubclass(self.basecls, BaseField):
self.error('DictField only accepts dict values')
kwargs.setdefault('default', lambda: {})
super(DictField, self).__init__(*args, **kwargs)
@@ -909,7 +981,7 @@ class DictField(ComplexBaseField):
super(DictField, self).validate(value)
def lookup_member(self, member_name):
return DictField(basecls=self.basecls, db_field=member_name)
return DictField(db_field=member_name)
def prepare_query_value(self, op, value):
match_operators = ['contains', 'icontains', 'startswith',
@@ -919,7 +991,7 @@ class DictField(ComplexBaseField):
if op in match_operators and isinstance(value, six.string_types):
return StringField().prepare_query_value(op, value)
if hasattr(self.field, 'field'):
if hasattr(self.field, 'field'): # Used for instance when using DictField(ListField(IntField()))
if op in ('set', 'unset') and isinstance(value, dict):
return {
k: self.field.prepare_query_value(op, v)
@@ -939,6 +1011,7 @@ class MapField(DictField):
"""
def __init__(self, field=None, *args, **kwargs):
# XXX ValidationError raised outside of the "validate" method.
if not isinstance(field, BaseField):
self.error('Argument to MapField constructor must be a valid '
'field')
@@ -949,6 +1022,15 @@ class ReferenceField(BaseField):
"""A reference to a document that will be automatically dereferenced on
access (lazily).
Note this means you will get a database I/O access everytime you access
this field. This is necessary because the field returns a :class:`~mongoengine.Document`
which precise type can depend of the value of the `_cls` field present in the
document in database.
In short, using this type of field can lead to poor performances (especially
if you access this field only to retrieve it `pk` field which is already
known before dereference). To solve this you should consider using the
:class:`~mongoengine.fields.LazyReferenceField`.
Use the `reverse_delete_rule` to handle what should happen if the document
the field is referencing is deleted. EmbeddedDocuments, DictFields and
MapFields does not support reverse_delete_rule and an `InvalidDocumentError`
@@ -967,11 +1049,13 @@ class ReferenceField(BaseField):
.. code-block:: python
class Bar(Document):
content = StringField()
foo = ReferenceField('Foo')
class Org(Document):
owner = ReferenceField('User')
Foo.register_delete_rule(Bar, 'foo', NULLIFY)
class User(Document):
org = ReferenceField('Org', reverse_delete_rule=CASCADE)
User.register_delete_rule(Org, 'owner', DENY)
.. versionchanged:: 0.5 added `reverse_delete_rule`
"""
@@ -989,6 +1073,7 @@ class ReferenceField(BaseField):
A reference to an abstract document type is always stored as a
:class:`~pymongo.dbref.DBRef`, regardless of the value of `dbref`.
"""
# XXX ValidationError raised outside of the "validate" method.
if (
not isinstance(document_type, six.string_types) and
not issubclass(document_type, Document)
@@ -1018,9 +1103,9 @@ class ReferenceField(BaseField):
# Get value from document instance if available
value = instance._data.get(self.name)
self._auto_dereference = instance._fields[self.name]._auto_dereference
auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs
if self._auto_dereference and isinstance(value, DBRef):
if auto_dereference and isinstance(value, DBRef):
if hasattr(value, 'cls'):
# Dereference using the class type specified in the reference
cls = get_document(value.cls)
@@ -1043,6 +1128,8 @@ class ReferenceField(BaseField):
if isinstance(document, Document):
# We need the id from the saved object to create the DBRef
id_ = document.pk
# XXX ValidationError raised outside of the "validate" method.
if id_ is None:
self.error('You can only reference documents once they have'
' been saved to the database')
@@ -1082,21 +1169,13 @@ class ReferenceField(BaseField):
return self.to_mongo(value)
def validate(self, value):
if not isinstance(value, (self.document_type, DBRef, ObjectId)):
self.error('A ReferenceField only accepts DBRef, ObjectId or documents')
if not isinstance(value, (self.document_type, LazyReference, DBRef, ObjectId)):
self.error('A ReferenceField only accepts DBRef, LazyReference, ObjectId or documents')
if isinstance(value, Document) and value.id is None:
self.error('You can only reference documents once they have been '
'saved to the database')
if self.document_type._meta.get('abstract') and \
not isinstance(value, self.document_type):
self.error(
'%s is not an instance of abstract reference type %s' % (
self.document_type._class_name)
)
def lookup_member(self, member_name):
return self.document_type._fields.get(member_name)
@@ -1117,6 +1196,7 @@ class CachedReferenceField(BaseField):
if fields is None:
fields = []
# XXX ValidationError raised outside of the "validate" method.
if (
not isinstance(document_type, six.string_types) and
not issubclass(document_type, Document)
@@ -1176,9 +1256,10 @@ class CachedReferenceField(BaseField):
# Get value from document instance if available
value = instance._data.get(self.name)
self._auto_dereference = instance._fields[self.name]._auto_dereference
auto_dereference = instance._fields[self.name]._auto_dereference
# Dereference DBRefs
if self._auto_dereference and isinstance(value, DBRef):
if auto_dereference and isinstance(value, DBRef):
dereferenced = self.document_type._get_db().dereference(value)
if dereferenced is None:
raise DoesNotExist('Trying to dereference unknown document %s' % value)
@@ -1191,6 +1272,7 @@ class CachedReferenceField(BaseField):
id_field_name = self.document_type._meta['id_field']
id_field = self.document_type._fields[id_field_name]
# XXX ValidationError raised outside of the "validate" method.
if isinstance(document, Document):
# We need the id from the saved object to create the DBRef
id_ = document.pk
@@ -1199,7 +1281,6 @@ class CachedReferenceField(BaseField):
' been saved to the database')
else:
self.error('Only accept a document object')
# TODO: should raise here or will fail next statement
value = SON((
('_id', id_field.to_mongo(id_)),
@@ -1217,16 +1298,20 @@ class CachedReferenceField(BaseField):
if value is None:
return None
# XXX ValidationError raised outside of the "validate" method.
if isinstance(value, Document):
if value.pk is None:
self.error('You can only reference documents once they have'
' been saved to the database')
return {'_id': value.pk}
value_dict = {'_id': value.pk}
for field in self.fields:
value_dict.update({field: value[field]})
return value_dict
raise NotImplementedError
def validate(self, value):
if not isinstance(value, self.document_type):
self.error('A CachedReferenceField only accepts documents')
@@ -1259,6 +1344,12 @@ class GenericReferenceField(BaseField):
"""A reference to *any* :class:`~mongoengine.document.Document` subclass
that will be automatically dereferenced on access (lazily).
Note this field works the same way as :class:`~mongoengine.document.ReferenceField`,
doing database I/O access the first time it is accessed (even if it's to access
it ``pk`` or ``id`` field).
To solve this you should consider using the
:class:`~mongoengine.fields.GenericLazyReferenceField`.
.. note ::
* Any documents used as a generic reference must be registered in the
document registry. Importing the model will automatically register
@@ -1281,6 +1372,8 @@ class GenericReferenceField(BaseField):
elif isinstance(choice, type) and issubclass(choice, Document):
self.choices.append(choice._class_name)
else:
# XXX ValidationError raised outside of the "validate"
# method.
self.error('Invalid choices provided: must be a list of'
'Document subclasses and/or six.string_typess')
@@ -1299,8 +1392,8 @@ class GenericReferenceField(BaseField):
value = instance._data.get(self.name)
self._auto_dereference = instance._fields[self.name]._auto_dereference
if self._auto_dereference and isinstance(value, (dict, SON)):
auto_dereference = instance._fields[self.name]._auto_dereference
if auto_dereference and isinstance(value, (dict, SON)):
dereferenced = self.dereference(value)
if dereferenced is None:
raise DoesNotExist('Trying to dereference unknown document %s' % value)
@@ -1344,6 +1437,7 @@ class GenericReferenceField(BaseField):
# We need the id from the saved object to create the DBRef
id_ = document.id
if id_ is None:
# XXX ValidationError raised outside of the "validate" method.
self.error('You can only reference documents once they have'
' been saved to the database')
else:
@@ -1381,10 +1475,10 @@ class BinaryField(BaseField):
return Binary(value)
def validate(self, value):
if not isinstance(value, (six.binary_type, six.text_type, Binary)):
if not isinstance(value, (six.binary_type, Binary)):
self.error('BinaryField only accepts instances of '
'(%s, %s, Binary)' % (
six.binary_type.__name__, six.text_type.__name__))
six.binary_type.__name__, Binary.__name__))
if self.max_bytes is not None and len(value) > self.max_bytes:
self.error('Binary value is too long')
@@ -1429,9 +1523,11 @@ class GridFSProxy(object):
def __get__(self, instance, value):
return self
def __nonzero__(self):
def __bool__(self):
return bool(self.grid_id)
__nonzero__ = __bool__ # For Py2 support
def __getstate__(self):
self_dict = self.__dict__
self_dict['_fs'] = None
@@ -1449,9 +1545,9 @@ class GridFSProxy(object):
return '<%s: %s>' % (self.__class__.__name__, self.grid_id)
def __str__(self):
name = getattr(
self.get(), 'filename', self.grid_id) if self.get() else '(no file)'
return '<%s: %s>' % (self.__class__.__name__, name)
gridout = self.get()
filename = getattr(gridout, 'filename') if gridout else '<no file>'
return '<%s: %s (%s)>' % (self.__class__.__name__, filename, self.grid_id)
def __eq__(self, other):
if isinstance(other, GridFSProxy):
@@ -1461,6 +1557,9 @@ class GridFSProxy(object):
else:
return False
def __ne__(self, other):
return not self == other
@property
def fs(self):
if not self._fs:
@@ -1768,12 +1867,9 @@ class ImageField(FileField):
"""
A Image File storage field.
@size (width, height, force):
max size to store images, if larger will be automatically resized
ex: size=(800, 600, True)
@thumbnail (width, height, force):
size to generate a thumbnail
:param size: max size to store images, provided as (width, height, force)
if larger, it will be automatically resized (ex: size=(800, 600, True))
:param thumbnail_size: size to generate a thumbnail, provided as (width, height, force)
.. versionadded:: 0.6
"""
@@ -1844,8 +1940,7 @@ class SequenceField(BaseField):
self.collection_name = collection_name or self.COLLECTION_NAME
self.db_alias = db_alias or DEFAULT_CONNECTION_NAME
self.sequence_name = sequence_name
self.value_decorator = (callable(value_decorator) and
value_decorator or self.VALUE_DECORATOR)
self.value_decorator = value_decorator if callable(value_decorator) else self.VALUE_DECORATOR
super(SequenceField, self).__init__(*args, **kwargs)
def generate(self):
@@ -1954,7 +2049,7 @@ class UUIDField(BaseField):
if not isinstance(value, six.string_types):
value = six.text_type(value)
return uuid.UUID(value)
except Exception:
except (ValueError, TypeError, AttributeError):
return original_value
return value
@@ -1976,7 +2071,7 @@ class UUIDField(BaseField):
value = str(value)
try:
uuid.UUID(value)
except Exception as exc:
except (ValueError, TypeError, AttributeError) as exc:
self.error('Could not convert to UUID: %s' % exc)
@@ -2134,3 +2229,201 @@ class MultiPolygonField(GeoJsonBaseField):
.. versionadded:: 0.9
"""
_type = 'MultiPolygon'
class LazyReferenceField(BaseField):
"""A really lazy reference to a document.
Unlike the :class:`~mongoengine.fields.ReferenceField` it will
**not** be automatically (lazily) dereferenced on access.
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
instance, allowing access to `pk` or manual dereference by using
``fetch()`` method.
.. versionadded:: 0.15
"""
def __init__(self, document_type, passthrough=False, dbref=False,
reverse_delete_rule=DO_NOTHING, **kwargs):
"""Initialises the Reference Field.
:param dbref: Store the reference as :class:`~pymongo.dbref.DBRef`
or as the :class:`~pymongo.objectid.ObjectId`.id .
:param reverse_delete_rule: Determines what to do when the referring
object is deleted
:param passthrough: When trying to access unknown fields, the
:class:`~mongoengine.base.datastructure.LazyReference` instance will
automatically call `fetch()` and try to retrive the field on the fetched
document. Note this only work getting field (not setting or deleting).
"""
# XXX ValidationError raised outside of the "validate" method.
if (
not isinstance(document_type, six.string_types) and
not issubclass(document_type, Document)
):
self.error('Argument to LazyReferenceField constructor must be a '
'document class or a string')
self.dbref = dbref
self.passthrough = passthrough
self.document_type_obj = document_type
self.reverse_delete_rule = reverse_delete_rule
super(LazyReferenceField, self).__init__(**kwargs)
@property
def document_type(self):
if isinstance(self.document_type_obj, six.string_types):
if self.document_type_obj == RECURSIVE_REFERENCE_CONSTANT:
self.document_type_obj = self.owner_document
else:
self.document_type_obj = get_document(self.document_type_obj)
return self.document_type_obj
def build_lazyref(self, value):
if isinstance(value, LazyReference):
if value.passthrough != self.passthrough:
value = LazyReference(value.document_type, value.pk, passthrough=self.passthrough)
elif value is not None:
if isinstance(value, self.document_type):
value = LazyReference(self.document_type, value.pk, passthrough=self.passthrough)
elif isinstance(value, DBRef):
value = LazyReference(self.document_type, value.id, passthrough=self.passthrough)
else:
# value is the primary key of the referenced document
value = LazyReference(self.document_type, value, passthrough=self.passthrough)
return value
def __get__(self, instance, owner):
"""Descriptor to allow lazy dereferencing."""
if instance is None:
# Document class being used rather than a document object
return self
value = self.build_lazyref(instance._data.get(self.name))
if value:
instance._data[self.name] = value
return super(LazyReferenceField, self).__get__(instance, owner)
def to_mongo(self, value):
if isinstance(value, LazyReference):
pk = value.pk
elif isinstance(value, self.document_type):
pk = value.pk
elif isinstance(value, DBRef):
pk = value.id
else:
# value is the primary key of the referenced document
pk = value
id_field_name = self.document_type._meta['id_field']
id_field = self.document_type._fields[id_field_name]
pk = id_field.to_mongo(pk)
if self.dbref:
return DBRef(self.document_type._get_collection_name(), pk)
else:
return pk
def validate(self, value):
if isinstance(value, LazyReference):
if value.collection != self.document_type._get_collection_name():
self.error('Reference must be on a `%s` document.' % self.document_type)
pk = value.pk
elif isinstance(value, self.document_type):
pk = value.pk
elif isinstance(value, DBRef):
# TODO: check collection ?
collection = self.document_type._get_collection_name()
if value.collection != collection:
self.error("DBRef on bad collection (must be on `%s`)" % collection)
pk = value.id
else:
# value is the primary key of the referenced document
id_field_name = self.document_type._meta['id_field']
id_field = getattr(self.document_type, id_field_name)
pk = value
try:
id_field.validate(pk)
except ValidationError:
self.error(
"value should be `{0}` document, LazyReference or DBRef on `{0}` "
"or `{0}`'s primary key (i.e. `{1}`)".format(
self.document_type.__name__, type(id_field).__name__))
if pk is None:
self.error('You can only reference documents once they have been '
'saved to the database')
def prepare_query_value(self, op, value):
if value is None:
return None
super(LazyReferenceField, self).prepare_query_value(op, value)
return self.to_mongo(value)
def lookup_member(self, member_name):
return self.document_type._fields.get(member_name)
class GenericLazyReferenceField(GenericReferenceField):
"""A reference to *any* :class:`~mongoengine.document.Document` subclass.
Unlike the :class:`~mongoengine.fields.GenericReferenceField` it will
**not** be automatically (lazily) dereferenced on access.
Instead, access will return a :class:`~mongoengine.base.LazyReference` class
instance, allowing access to `pk` or manual dereference by using
``fetch()`` method.
.. note ::
* Any documents used as a generic reference must be registered in the
document registry. Importing the model will automatically register
it.
* You can use the choices param to limit the acceptable Document types
.. versionadded:: 0.15
"""
def __init__(self, *args, **kwargs):
self.passthrough = kwargs.pop('passthrough', False)
super(GenericLazyReferenceField, self).__init__(*args, **kwargs)
def _validate_choices(self, value):
if isinstance(value, LazyReference):
value = value.document_type._class_name
super(GenericLazyReferenceField, self)._validate_choices(value)
def build_lazyref(self, value):
if isinstance(value, LazyReference):
if value.passthrough != self.passthrough:
value = LazyReference(value.document_type, value.pk, passthrough=self.passthrough)
elif value is not None:
if isinstance(value, (dict, SON)):
value = LazyReference(get_document(value['_cls']), value['_ref'].id, passthrough=self.passthrough)
elif isinstance(value, Document):
value = LazyReference(type(value), value.pk, passthrough=self.passthrough)
return value
def __get__(self, instance, owner):
if instance is None:
return self
value = self.build_lazyref(instance._data.get(self.name))
if value:
instance._data[self.name] = value
return super(GenericLazyReferenceField, self).__get__(instance, owner)
def validate(self, value):
if isinstance(value, LazyReference) and value.pk is None:
self.error('You can only reference documents once they have been'
' saved to the database')
return super(GenericLazyReferenceField, self).validate(value)
def to_mongo(self, document):
if document is None:
return None
if isinstance(document, LazyReference):
return SON((
('_cls', document.document_type._class_name),
('_ref', DBRef(document.document_type._get_collection_name(), document.pk))
))
else:
return super(GenericLazyReferenceField, self).to_mongo(document)

View File

@@ -6,11 +6,7 @@ import pymongo
import six
if pymongo.version_tuple[0] < 3:
IS_PYMONGO_3 = False
else:
IS_PYMONGO_3 = True
IS_PYMONGO_3 = pymongo.version_tuple[0] >= 3
# six.BytesIO resolves to StringIO.StringIO in Py2 and io.BytesIO in Py3.
StringIO = six.BytesIO
@@ -23,3 +19,10 @@ if not six.PY3:
pass
else:
StringIO = cStringIO.StringIO
if six.PY3:
from collections.abc import Hashable
else:
# raises DeprecationWarnings in Python >=3.7
from collections import Hashable

View File

@@ -2,7 +2,6 @@ from __future__ import absolute_import
import copy
import itertools
import operator
import pprint
import re
import warnings
@@ -18,7 +17,7 @@ from mongoengine import signals
from mongoengine.base import get_document
from mongoengine.common import _import_class
from mongoengine.connection import get_db
from mongoengine.context_managers import switch_db
from mongoengine.context_managers import set_write_concern, switch_db
from mongoengine.errors import (InvalidQueryError, LookUpError,
NotUniqueError, OperationError)
from mongoengine.python_support import IS_PYMONGO_3
@@ -39,8 +38,6 @@ CASCADE = 2
DENY = 3
PULL = 4
RE_TYPE = type(re.compile(''))
class BaseQuerySet(object):
"""A set of results returned from a query. Wraps a MongoDB cursor,
@@ -209,14 +206,12 @@ class BaseQuerySet(object):
queryset = self.order_by()
return False if queryset.first() is None else True
def __nonzero__(self):
"""Avoid to open all records in an if stmt in Py2."""
return self._has_data()
def __bool__(self):
"""Avoid to open all records in an if stmt in Py3."""
return self._has_data()
__nonzero__ = __bool__ # For Py2 support
# Core functions
def all(self):
@@ -269,13 +264,13 @@ class BaseQuerySet(object):
queryset = queryset.filter(*q_objs, **query)
try:
result = queryset.next()
result = six.next(queryset)
except StopIteration:
msg = ('%s matching query does not exist.'
% queryset._document._class_name)
raise queryset._document.DoesNotExist(msg)
try:
queryset.next()
six.next(queryset)
except StopIteration:
return result
@@ -350,11 +345,24 @@ class BaseQuerySet(object):
documents=docs, **signal_kwargs)
raw = [doc.to_mongo() for doc in docs]
with set_write_concern(self._collection, write_concern) as collection:
insert_func = collection.insert_many
if return_one:
raw = raw[0]
insert_func = collection.insert_one
try:
ids = self._collection.insert(raw, **write_concern)
inserted_result = insert_func(raw)
ids = [inserted_result.inserted_id] if return_one else inserted_result.inserted_ids
except pymongo.errors.DuplicateKeyError as err:
message = 'Could not save document (%s)'
raise NotUniqueError(message % six.text_type(err))
except pymongo.errors.BulkWriteError as err:
# inserting documents that already have an _id field will
# give huge performance debt or raise
message = u'Document must not have _id value before bulk write (%s)'
raise NotUniqueError(message % six.text_type(err))
except pymongo.errors.OperationFailure as err:
message = 'Could not save document (%s)'
if re.match('^E1100[01] duplicate key', six.text_type(err)):
@@ -364,18 +372,20 @@ class BaseQuerySet(object):
raise NotUniqueError(message % six.text_type(err))
raise OperationError(message % six.text_type(err))
# Apply inserted_ids to documents
for doc, doc_id in zip(docs, ids):
doc.pk = doc_id
if not load_bulk:
signals.post_bulk_insert.send(
self._document, documents=docs, loaded=False, **signal_kwargs)
return return_one and ids[0] or ids
return ids[0] if return_one else ids
documents = self.in_bulk(ids)
results = []
for obj_id in ids:
results.append(documents.get(obj_id))
results = [documents.get(obj_id) for obj_id in ids]
signals.post_bulk_insert.send(
self._document, documents=results, loaded=True, **signal_kwargs)
return return_one and results[0] or results
return results[0] if return_one else results
def count(self, with_limit_and_skip=False):
"""Count the selected elements in the query.
@@ -384,7 +394,7 @@ class BaseQuerySet(object):
:meth:`skip` that has been applied to this cursor into account when
getting the count
"""
if self._limit == 0 and with_limit_and_skip or self._none:
if self._limit == 0 and with_limit_and_skip is False or self._none:
return 0
return self._cursor.count(with_limit_and_skip=with_limit_and_skip)
@@ -486,8 +496,9 @@ class BaseQuerySet(object):
``save(..., write_concern={w: 2, fsync: True}, ...)`` will
wait until at least two servers have recorded the write and
will force an fsync on the primary server.
:param full_result: Return the full result rather than just the number
updated.
:param full_result: Return the full result dictionary rather than just the number
updated, e.g. return
``{'n': 2, 'nModified': 2, 'ok': 1.0, 'updatedExisting': True}``.
:param update: Django-style update keyword arguments
.. versionadded:: 0.2
@@ -510,12 +521,15 @@ class BaseQuerySet(object):
else:
update['$set'] = {'_cls': queryset._document._class_name}
try:
result = queryset._collection.update(query, update, multi=multi,
upsert=upsert, **write_concern)
with set_write_concern(queryset._collection, write_concern) as collection:
update_func = collection.update_one
if multi:
update_func = collection.update_many
result = update_func(query, update, upsert=upsert)
if full_result:
return result
elif result:
return result['n']
elif result.raw_result:
return result.raw_result['n']
except pymongo.errors.DuplicateKeyError as err:
raise NotUniqueError(u'Update failed (%s)' % six.text_type(err))
except pymongo.errors.OperationFailure as err:
@@ -544,10 +558,10 @@ class BaseQuerySet(object):
write_concern=write_concern,
full_result=True, **update)
if atomic_update['updatedExisting']:
if atomic_update.raw_result['updatedExisting']:
document = self.get()
else:
document = self._document.objects.with_id(atomic_update['upserted'])
document = self._document.objects.with_id(atomic_update.upserted_id)
return document
def update_one(self, upsert=False, write_concern=None, **update):
@@ -759,10 +773,11 @@ class BaseQuerySet(object):
"""Limit the number of returned documents to `n`. This may also be
achieved using array-slicing syntax (e.g. ``User.objects[:5]``).
:param n: the maximum number of objects to return
:param n: the maximum number of objects to return if n is greater than 0.
When 0 is passed, returns all the documents in the cursor
"""
queryset = self.clone()
queryset._limit = n if n != 0 else 1
queryset._limit = n
# If a cursor object has already been created, apply the limit to it.
if queryset._cursor_obj:
@@ -960,11 +975,10 @@ class BaseQuerySet(object):
# explicitly included, and then more complicated operators such as
# $slice.
def _sort_key(field_tuple):
key, value = field_tuple
if isinstance(value, (int)):
_, value = field_tuple
if isinstance(value, int):
return value # 0 for exclusion, 1 for inclusion
else:
return 2 # so that complex values appear last
return 2 # so that complex values appear last
fields = sorted(cleaned_fields, key=_sort_key)
@@ -1182,6 +1196,10 @@ class BaseQuerySet(object):
pipeline = initial_pipeline + list(pipeline)
if IS_PYMONGO_3 and self._read_preference is not None:
return self._collection.with_options(read_preference=self._read_preference) \
.aggregate(pipeline, cursor={}, **kwargs)
return self._collection.aggregate(pipeline, cursor={}, **kwargs)
# JS functionality
@@ -1457,13 +1475,13 @@ class BaseQuerySet(object):
# Iterator helpers
def next(self):
def __next__(self):
"""Wrap the result in a :class:`~mongoengine.Document` object.
"""
if self._limit == 0 or self._none:
raise StopIteration
raw_doc = self._cursor.next()
raw_doc = six.next(self._cursor)
if self._as_pymongo:
return self._get_as_pymongo(raw_doc)
@@ -1477,6 +1495,8 @@ class BaseQuerySet(object):
return doc
next = __next__ # For Python2 support
def rewind(self):
"""Rewind the cursor to its unevaluated state.
@@ -1578,6 +1598,9 @@ class BaseQuerySet(object):
if self._batch_size is not None:
self._cursor_obj.batch_size(self._batch_size)
if self._comment is not None:
self._cursor_obj.comment(self._comment)
return self._cursor_obj
def __deepcopy__(self, memo):
@@ -1722,25 +1745,33 @@ class BaseQuerySet(object):
return frequencies
def _fields_to_dbfields(self, fields):
"""Translate fields paths to its db equivalents"""
ret = []
"""Translate fields' paths to their db equivalents."""
subclasses = []
document = self._document
if document._meta['allow_inheritance']:
if self._document._meta['allow_inheritance']:
subclasses = [get_document(x)
for x in document._subclasses][1:]
for x in self._document._subclasses][1:]
db_field_paths = []
for field in fields:
field_parts = field.split('.')
try:
field = '.'.join(f.db_field for f in
document._lookup_field(field.split('.')))
ret.append(field)
field = '.'.join(
f if isinstance(f, six.string_types) else f.db_field
for f in self._document._lookup_field(field_parts)
)
db_field_paths.append(field)
except LookUpError as err:
found = False
# If a field path wasn't found on the main document, go
# through its subclasses and see if it exists on any of them.
for subdoc in subclasses:
try:
subfield = '.'.join(f.db_field for f in
subdoc._lookup_field(field.split('.')))
ret.append(subfield)
subfield = '.'.join(
f if isinstance(f, six.string_types) else f.db_field
for f in subdoc._lookup_field(field_parts)
)
db_field_paths.append(subfield)
found = True
break
except LookUpError:
@@ -1748,7 +1779,8 @@ class BaseQuerySet(object):
if not found:
raise err
return ret
return db_field_paths
def _get_order_by(self, keys):
"""Given a list of MongoEngine-style sort keys, return a list
@@ -1840,8 +1872,8 @@ class BaseQuerySet(object):
# Substitute the correct name for the field into the javascript
return '.'.join([f.db_field for f in fields])
code = re.sub(u'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code)
code = re.sub(u'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub,
code = re.sub(r'\[\s*~([A-z_][A-z_0-9.]+?)\s*\]', field_sub, code)
code = re.sub(r'\{\{\s*~([A-z_][A-z_0-9.]+?)\s*\}\}', field_path_sub,
code)
return code

View File

@@ -63,9 +63,11 @@ 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 = {field: self.value for field in self.fields}
if self.slice:

View File

@@ -36,7 +36,7 @@ class QuerySetManager(object):
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:

View File

@@ -1,3 +1,5 @@
import six
from mongoengine.errors import OperationError
from mongoengine.queryset.base import (BaseQuerySet, CASCADE, DENY, DO_NOTHING,
NULLIFY, PULL)
@@ -87,10 +89,10 @@ class QuerySet(BaseQuerySet):
yield self._result_cache[pos]
pos += 1
# Raise StopIteration if we already established there were no more
# return if we already established there were no more
# docs in the db cursor.
if not self._has_more:
raise StopIteration
return
# Otherwise, populate more of the cache and repeat.
if len(self._result_cache) <= pos:
@@ -112,8 +114,8 @@ class QuerySet(BaseQuerySet):
# Pull in ITER_CHUNK_SIZE docs from the database and store them in
# the result cache.
try:
for _ in xrange(ITER_CHUNK_SIZE):
self._result_cache.append(self.next())
for _ in six.moves.range(ITER_CHUNK_SIZE):
self._result_cache.append(six.next(self))
except StopIteration:
# Getting this exception means there are no more docs in the
# db cursor. Set _has_more to False so that we can use that
@@ -166,9 +168,9 @@ class QuerySetNoCache(BaseQuerySet):
return '.. queryset mid-iteration ..'
data = []
for _ in xrange(REPR_OUTPUT_SIZE + 1):
for _ in six.moves.range(REPR_OUTPUT_SIZE + 1):
try:
data.append(self.next())
data.append(six.next(self))
except StopIteration:
break
@@ -184,10 +186,3 @@ class QuerySetNoCache(BaseQuerySet):
queryset = self.clone()
queryset.rewind()
return queryset
class QuerySetNoDeRef(QuerySet):
"""Special no_dereference QuerySet"""
def __dereference(items, max_depth=1, instance=None, name=None):
return items

View File

@@ -101,21 +101,8 @@ def query(_doc_cls=None, **kwargs):
value = value['_id']
elif op in ('in', 'nin', 'all', 'near') and not isinstance(value, dict):
# Raise an error if the in/nin/all/near param is not iterable. We need a
# special check for BaseDocument, because - although it's iterable - using
# it as such in the context of this method is most definitely a mistake.
BaseDocument = _import_class('BaseDocument')
if isinstance(value, BaseDocument):
raise TypeError("When using the `in`, `nin`, `all`, or "
"`near`-operators you can\'t use a "
"`Document`, you must wrap your object "
"in a list (object -> [object]).")
elif not hasattr(value, '__iter__'):
raise TypeError("The `in`, `nin`, `all`, or "
"`near`-operators must be applied to an "
"iterable (e.g. a list).")
else:
value = [field.prepare_query_value(op, v) for v in value]
# 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:
@@ -160,7 +147,7 @@ def query(_doc_cls=None, **kwargs):
if op is None or key not in mongo_query:
mongo_query[key] = value
elif key in mongo_query:
if isinstance(mongo_query[key], dict):
if isinstance(mongo_query[key], dict) and isinstance(value, dict):
mongo_query[key].update(value)
# $max/minDistance needs to come last - convert to SON
value_dict = mongo_query[key]
@@ -214,30 +201,37 @@ def update(_doc_cls=None, **update):
format.
"""
mongo_update = {}
for key, value in update.items():
if key == '__raw__':
mongo_update.update(value)
continue
parts = key.split('__')
# if there is no operator, default to 'set'
if len(parts) < 3 and parts[0] not in UPDATE_OPERATORS:
parts.insert(0, 'set')
# Check for an operator and transform to mongo-style if there is
op = None
if parts[0] in UPDATE_OPERATORS:
op = parts.pop(0)
# Convert Pythonic names to Mongo equivalents
if op in ('push_all', 'pull_all'):
op = op.replace('_all', 'All')
elif op == 'dec':
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'
value = -value
elif op == 'add_to_set':
op = 'addToSet'
elif op == 'set_on_insert':
op = 'setOnInsert'
# 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:
@@ -284,7 +278,15 @@ def update(_doc_cls=None, **update):
if isinstance(field, GeoJsonBaseField):
value = field.to_mongo(value)
if op in (None, 'set', 'push', 'pull'):
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'):
@@ -296,6 +298,8 @@ def update(_doc_cls=None, **update):
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
@@ -319,11 +323,17 @@ def update(_doc_cls=None, **update):
field_classes = [c.__class__ for c in cleaned_fields]
field_classes.reverse()
ListField = _import_class('ListField')
if ListField in field_classes:
# Join all fields via dot notation to the last ListField
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(ListField)
cleaned_fields) - field_classes.index(_check_field)
key = '.'.join(parts[:last_listField])
parts = parts[last_listField:]
parts.insert(0, key)
@@ -333,10 +343,26 @@ def update(_doc_cls=None, **update):
value = {key: value}
elif op == 'addToSet' and isinstance(value, list):
value = {key: {'$each': value}}
elif op in ('push', 'pushAll'):
if parts[-1].isdigit():
key = parts[0]
position = int(parts[-1])
# $position expects an iterable. If pushing a single value,
# wrap it in a list.
if not isinstance(value, (set, tuple, list)):
value = [value]
value = {key: {'$each': value, '$position': position}}
else:
if op == 'pushAll':
op = 'push' # convert to non-deprecated keyword
if not isinstance(value, (set, tuple, list)):
value = [value]
value = {key: {'$each': value}}
else:
value = {key: value}
else:
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):
@@ -403,7 +429,6 @@ def _infer_geometry(value):
'type and coordinates keys')
elif isinstance(value, (list, set)):
# TODO: shouldn't we test value[0][0][0][0] to see if it is MultiPolygon?
# TODO: should both TypeError and IndexError be alike interpreted?
try:
value[0][0][0]
@@ -425,3 +450,22 @@ def _infer_geometry(value):
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

@@ -3,7 +3,7 @@ import copy
from mongoengine.errors import InvalidQueryError
from mongoengine.queryset import transform
__all__ = ('Q',)
__all__ = ('Q', 'QNode')
class QNodeVisitor(object):
@@ -131,6 +131,10 @@ class QCombination(QNode):
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):
@@ -151,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,11 @@
[nosetests]
verbosity=2
detailed-errors=1
tests=tests
#tests=tests
cover-package=mongoengine
[flake8]
ignore=E501,F401,F403,F405,I201
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

@@ -44,9 +44,8 @@ CLASSIFIERS = [
"Programming Language :: Python :: 2",
"Programming Language :: Python :: 2.7",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.3",
"Programming Language :: Python :: 3.4",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: Implementation :: CPython",
"Programming Language :: Python :: Implementation :: PyPy",
'Topic :: Database',
@@ -70,9 +69,9 @@ 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",
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',

View File

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

View File

@@ -1,13 +1,13 @@
import unittest
from class_methods import *
from delta import *
from dynamic import *
from indexes import *
from inheritance import *
from instance import *
from json_serialisation import *
from validation import *
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

@@ -5,6 +5,7 @@ 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", )
@@ -65,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
@@ -187,6 +188,26 @@ class ClassMethodsTest(unittest.TestCase):
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
@@ -319,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")
@@ -328,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

@@ -694,7 +694,7 @@ class DeltaTest(unittest.TestCase):
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
self.assertIn('employees', updates)
def test_delta_with_dbref_false(self):
person, organization, employee = self.circular_reference_deltas_2(Document, Document, False)
@@ -709,7 +709,7 @@ class DeltaTest(unittest.TestCase):
organization.employees.append(person)
updates, removals = organization._delta()
self.assertEqual({}, removals)
self.assertTrue('employees' in updates)
self.assertIn('employees', updates)
def test_nested_nested_fields_mark_as_changed(self):
class EmbeddedDoc(EmbeddedDocument):

View File

@@ -174,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())
@@ -189,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"""

View File

@@ -1,15 +1,14 @@
# -*- coding: utf-8 -*-
import unittest
import sys
from datetime import datetime
from nose.plugins.skip import SkipTest
from datetime import datetime
from pymongo.errors import OperationFailure
import pymongo
from mongoengine import *
from mongoengine.connection import get_db
from tests.utils import get_mongodb_version, needs_mongodb_v26
from tests.utils import get_mongodb_version, requires_mongodb_gte_26, MONGODB_32, MONGODB_3
__all__ = ("IndexesTest", )
@@ -19,6 +18,7 @@ class IndexesTest(unittest.TestCase):
def setUp(self):
self.connection = connect(db='mongoenginetest')
self.db = get_db()
self.mongodb_version = get_mongodb_version()
class Person(Document):
name = StringField()
@@ -70,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):
@@ -102,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()
@@ -117,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
@@ -226,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]
@@ -246,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]
@@ -269,7 +269,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([('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]
@@ -289,7 +289,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', '2dsphere')] in info)
self.assertIn([('location.point', '2dsphere')], info)
def test_explicit_geohaystack_index(self):
"""Ensure that geohaystack indexes work when created via meta[indexes]
@@ -311,7 +311,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', 'geoHaystack')] in info)
self.assertIn([('location.point', 'geoHaystack')], info)
def test_create_geohaystack_index(self):
"""Ensure that geohaystack indexes can be created
@@ -323,7 +323,7 @@ class IndexesTest(unittest.TestCase):
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.assertTrue([('location.point', 'geoHaystack'), ('name', 1)] in info)
self.assertIn([('location.point', 'geoHaystack'), ('name', 1)], info)
def test_dictionary_indexes(self):
"""Ensure that indexes are used when meta[indexes] contains
@@ -356,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()
@@ -491,7 +491,7 @@ class IndexesTest(unittest.TestCase):
obj = Test(a=1)
obj.save()
IS_MONGODB_3 = get_mongodb_version()[0] >= 3
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.
@@ -541,19 +541,24 @@ 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 range(0, 10):
tags = [("tag %i" % n) for n in range(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)
@@ -563,18 +568,18 @@ class IndexesTest(unittest.TestCase):
if pymongo.version != '3.0':
self.assertEqual(BlogPost.objects.hint([('tags', 1)]).count(), 10)
if MONGO_VER == MONGODB_32:
# Mongo32 throws an error if an index exists (i.e `tags` in our case)
# and you use hint on an index name that does not exist
with self.assertRaises(OperationFailure):
BlogPost.objects.hint([('ZZ', 1)]).count()
else:
self.assertEqual(BlogPost.objects.hint([('ZZ', 1)]).count(), 10)
if pymongo.version >= '2.8':
self.assertEqual(BlogPost.objects.hint('tags').count(), 10)
else:
def invalid_index():
BlogPost.objects.hint('tags').next()
self.assertRaises(TypeError, invalid_index)
self.assertEqual(BlogPost.objects.hint(TAGS_INDEX_NAME ).count(), 10)
def invalid_index_2():
return BlogPost.objects.hint(('tags', 1)).next()
self.assertRaises(Exception, invalid_index_2)
with self.assertRaises(Exception):
BlogPost.objects.hint(('tags', 1)).next()
def test_unique(self):
"""Ensure that uniqueness constraints are applied to fields.
@@ -749,7 +754,7 @@ class IndexesTest(unittest.TestCase):
except NotUniqueError:
pass
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.
"""
@@ -803,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):
@@ -850,8 +855,8 @@ class IndexesTest(unittest.TestCase):
info = MyDoc.objects._collection.index_information()
info = [value['key'] for key, value in info.iteritems()]
self.assertTrue([('provider_ids.foo', 1)] in info)
self.assertTrue([('provider_ids.bar', 1)] in info)
self.assertIn([('provider_ids.foo', 1)], info)
self.assertIn([('provider_ids.bar', 1)], info)
def test_sparse_compound_indexes(self):
@@ -867,7 +872,7 @@ class IndexesTest(unittest.TestCase):
info['provider_ids.foo_1_provider_ids.bar_1']['key'])
self.assertTrue(info['provider_ids.foo_1_provider_ids.bar_1']['sparse'])
@needs_mongodb_v26
@requires_mongodb_gte_26
def test_text_indexes(self):
class Book(Document):
title = DictField()
@@ -876,9 +881,9 @@ class IndexesTest(unittest.TestCase):
}
indexes = Book.objects._collection.index_information()
self.assertTrue("title_text" in indexes)
self.assertIn("title_text", indexes)
key = indexes["title_text"]["key"]
self.assertTrue(('_fts', 'text') in key)
self.assertIn(('_fts', 'text'), key)
def test_hashed_indexes(self):
@@ -889,8 +894,8 @@ class IndexesTest(unittest.TestCase):
}
indexes = Book.objects._collection.index_information()
self.assertTrue("ref_id_hashed" in indexes)
self.assertTrue(('ref_id', 'hashed') in indexes["ref_id_hashed"]["key"])
self.assertIn("ref_id_hashed", indexes)
self.assertIn(('ref_id', 'hashed'), indexes["ref_id_hashed"]["key"])
def test_indexes_after_database_drop(self):
"""
@@ -1013,7 +1018,7 @@ class IndexesTest(unittest.TestCase):
TestDoc.ensure_indexes()
index_info = TestDoc._get_collection().index_information()
self.assertTrue('shard_1_1__cls_1_txt_1_1' in index_info)
self.assertIn('shard_1_1__cls_1_txt_1_1', index_info)
if __name__ == '__main__':

View File

@@ -2,14 +2,11 @@
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', )
@@ -26,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.
"""
@@ -258,9 +276,10 @@ class InheritanceTest(unittest.TestCase):
name = StringField()
# can't inherit because Animal didn't explicitly allow inheritance
with self.assertRaises(ValueError):
with self.assertRaises(ValueError) as cm:
class Dog(Animal):
pass
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()
@@ -268,7 +287,7 @@ 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.
@@ -277,9 +296,10 @@ class InheritanceTest(unittest.TestCase):
name = StringField()
meta = {'allow_inheritance': True}
with self.assertRaises(ValueError):
with self.assertRaises(ValueError) as cm:
class Mammal(Animal):
meta = {'allow_inheritance': False}
self.assertEqual(str(cm.exception), 'Only direct subclasses of Document may set "allow_inheritance" to False')
def test_allow_inheritance_abstract_document(self):
"""Ensure that abstract documents can set inheritance rules and that
@@ -292,13 +312,48 @@ class InheritanceTest(unittest.TestCase):
class Animal(FinalDocument):
name = StringField()
with self.assertRaises(ValueError):
with self.assertRaises(ValueError) as cm:
class Mammal(Animal):
pass
# 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):
@@ -358,11 +413,11 @@ class InheritanceTest(unittest.TestCase):
meta = {'abstract': True,
'allow_inheritance': False}
bkk = City(continent='asia')
self.assertEqual(None, bkk.pk)
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(bkk, 'pk', 1)
setattr(city, 'pk', 1)
def test_allow_inheritance_embedded_document(self):
"""Ensure embedded documents respect inheritance."""
@@ -374,14 +429,14 @@ class InheritanceTest(unittest.TestCase):
pass
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
@@ -434,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)

View File

@@ -8,9 +8,12 @@ import weakref
from datetime import datetime
from bson import DBRef, ObjectId
from pymongo.errors import DuplicateKeyError
from tests import fixtures
from tests.fixtures import (PickleEmbedded, PickleTest, PickleSignalsTest,
PickleDynamicEmbedded, PickleDynamicTest)
from tests.utils import MongoDBTestCase
from mongoengine import *
from mongoengine.base import get_document, _document_registry
@@ -22,18 +25,17 @@ from mongoengine.queryset import NULLIFY, Q
from mongoengine.context_managers import switch_db, query_counter
from mongoengine import signals
from tests.utils import requires_mongodb_gte_26
TEST_IMAGE_PATH = os.path.join(os.path.dirname(__file__),
'../fields/mongoengine.png')
__all__ = ("InstanceTest",)
class InstanceTest(unittest.TestCase):
class InstanceTest(MongoDBTestCase):
def setUp(self):
connect(db='mongoenginetest')
self.db = get_db()
class Job(EmbeddedDocument):
name = StringField()
years = IntField()
@@ -355,7 +357,7 @@ class InstanceTest(unittest.TestCase):
user_son = User.objects._collection.find_one()
self.assertEqual(user_son['_id'], 'test')
self.assertTrue('username' not in user_son['_id'])
self.assertNotIn('username', user_son['_id'])
User.drop_collection()
@@ -368,7 +370,7 @@ class InstanceTest(unittest.TestCase):
user_son = User.objects._collection.find_one()
self.assertEqual(user_son['_id'], 'mongo')
self.assertTrue('username' not in user_son['_id'])
self.assertNotIn('username', user_son['_id'])
def test_document_not_registered(self):
class Place(Document):
@@ -474,6 +476,24 @@ class InstanceTest(unittest.TestCase):
doc.save()
doc.reload()
def test_reload_with_changed_fields(self):
"""Ensures reloading will not affect changed fields"""
class User(Document):
name = StringField()
number = IntField()
User.drop_collection()
user = User(name="Bob", number=1).save()
user.name = "John"
user.number = 2
self.assertEqual(user._get_changed_fields(), ['name', 'number'])
user.reload('number')
self.assertEqual(user._get_changed_fields(), ['name'])
user.save()
user.reload()
self.assertEqual(user.name, "John")
def test_reload_referencing(self):
"""Ensures reloading updates weakrefs correctly."""
class Embedded(EmbeddedDocument):
@@ -519,7 +539,7 @@ class InstanceTest(unittest.TestCase):
doc.save()
doc.dict_field['extra'] = 1
doc = doc.reload(10, 'list_field')
self.assertEqual(doc._get_changed_fields(), [])
self.assertEqual(doc._get_changed_fields(), ['dict_field.extra'])
self.assertEqual(len(doc.list_field), 5)
self.assertEqual(len(doc.dict_field), 3)
self.assertEqual(len(doc.embedded_field.list_field), 4)
@@ -530,21 +550,14 @@ class InstanceTest(unittest.TestCase):
pass
f = Foo()
try:
with self.assertRaises(Foo.DoesNotExist):
f.reload()
except Foo.DoesNotExist:
pass
except Exception:
self.assertFalse("Threw wrong exception")
f.save()
f.delete()
try:
with self.assertRaises(Foo.DoesNotExist):
f.reload()
except Foo.DoesNotExist:
pass
except Exception:
self.assertFalse("Threw wrong exception")
def test_reload_of_non_strict_with_special_field_name(self):
"""Ensures reloading works for documents with meta strict == False."""
@@ -581,10 +594,10 @@ class InstanceTest(unittest.TestCase):
# Length = length(assigned fields + id)
self.assertEqual(len(person), 5)
self.assertTrue('age' in person)
self.assertIn('age', person)
person.age = None
self.assertFalse('age' in person)
self.assertFalse('nationality' in person)
self.assertNotIn('age', person)
self.assertNotIn('nationality', person)
def test_embedded_document_to_mongo(self):
class Person(EmbeddedDocument):
@@ -614,8 +627,8 @@ class InstanceTest(unittest.TestCase):
class Comment(EmbeddedDocument):
content = StringField()
self.assertTrue('content' in Comment._fields)
self.assertFalse('id' in Comment._fields)
self.assertIn('content', Comment._fields)
self.assertNotIn('id', Comment._fields)
def test_embedded_document_instance(self):
"""Ensure that embedded documents can reference parent instance."""
@@ -714,12 +727,12 @@ class InstanceTest(unittest.TestCase):
t = TestDocument(status="draft", pub_date=datetime.now())
try:
with self.assertRaises(ValidationError) as cm:
t.save()
except ValidationError as e:
expect_msg = "Draft entries may not have a publication date."
self.assertTrue(expect_msg in e.message)
self.assertEqual(e.to_dict(), {'__all__': expect_msg})
expected_msg = "Draft entries may not have a publication date."
self.assertIn(expected_msg, cm.exception.message)
self.assertEqual(cm.exception.to_dict(), {'__all__': expected_msg})
t = TestDocument(status="published")
t.save(clean=False)
@@ -753,12 +766,13 @@ class InstanceTest(unittest.TestCase):
TestDocument.drop_collection()
t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25, z=15))
try:
with self.assertRaises(ValidationError) as cm:
t.save()
except ValidationError as e:
expect_msg = "Value of z != x + y"
self.assertTrue(expect_msg in e.message)
self.assertEqual(e.to_dict(), {'doc': {'__all__': expect_msg}})
expected_msg = "Value of z != x + y"
self.assertIn(expected_msg, cm.exception.message)
self.assertEqual(cm.exception.to_dict(), {'doc': {'__all__': expected_msg}})
t = TestDocument(doc=TestEmbeddedDocument(x=10, y=25)).save()
self.assertEqual(t.doc.z, 35)
@@ -826,6 +840,22 @@ class InstanceTest(unittest.TestCase):
self.assertDbEqual([dict(other_doc.to_mongo()), dict(doc.to_mongo())])
@requires_mongodb_gte_26
def test_modify_with_positional_push(self):
class BlogPost(Document):
tags = ListField(StringField())
post = BlogPost.objects.create(tags=['python'])
self.assertEqual(post.tags, ['python'])
post.modify(push__tags__0=['code', 'mongo'])
self.assertEqual(post.tags, ['code', 'mongo', 'python'])
# Assert same order of the list items is maintained in the db
self.assertEqual(
BlogPost._get_collection().find_one({'_id': post.pk})['tags'],
['code', 'mongo', 'python']
)
def test_save(self):
"""Ensure that a document may be saved in the database."""
@@ -1323,6 +1353,23 @@ class InstanceTest(unittest.TestCase):
site = Site.objects.first()
self.assertEqual(site.page.log_message, "Error: Dummy message")
def test_update_list_field(self):
"""Test update on `ListField` with $pull + $in.
"""
class Doc(Document):
foo = ListField(StringField())
Doc.drop_collection()
doc = Doc(foo=['a', 'b', 'c'])
doc.save()
# Update
doc = Doc.objects.first()
doc.update(pull__foo__in=['a', 'c'])
doc = Doc.objects.first()
self.assertEqual(doc.foo, ['b'])
def test_embedded_update_db_field(self):
"""Test update on `EmbeddedDocumentField` fields when db_field
is other than default.
@@ -1375,6 +1422,60 @@ class InstanceTest(unittest.TestCase):
self.assertEqual(person.age, 21)
self.assertEqual(person.active, False)
def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop(self):
# Refers to Issue #1685
class EmbeddedChildModel(EmbeddedDocument):
id = DictField(primary_key=True)
class ParentModel(Document):
child = EmbeddedDocumentField(
EmbeddedChildModel)
emb = EmbeddedChildModel(id={'1': [1]})
ParentModel(children=emb)._get_changed_fields()
def test__get_changed_fields_same_ids_reference_field_does_not_enters_infinite_loop(self):
class User(Document):
id = IntField(primary_key=True)
name = StringField()
class Message(Document):
id = IntField(primary_key=True)
author = ReferenceField(User)
Message.drop_collection()
# All objects share the same id, but each in a different collection
user = User(id=1, name='user-name').save()
message = Message(id=1, author=user).save()
message.author.name = 'tutu'
self.assertEqual(message._get_changed_fields(), [])
self.assertEqual(user._get_changed_fields(), ['name'])
def test__get_changed_fields_same_ids_embedded(self):
# Refers to Issue #1768
class User(EmbeddedDocument):
id = IntField()
name = StringField()
class Message(Document):
id = IntField(primary_key=True)
author = EmbeddedDocumentField(User)
Message.drop_collection()
# All objects share the same id, but each in a different collection
user = User(id=1, name='user-name')#.save()
message = Message(id=1, author=user).save()
message.author.name = 'tutu'
self.assertEqual(message._get_changed_fields(), ['author.name'])
message.save()
message_fetched = Message.objects.with_id(message.id)
self.assertEqual(message_fetched.author.name, 'tutu')
def test_query_count_when_saving(self):
"""Ensure references don't cause extra fetches when saving"""
class Organization(Document):
@@ -1408,9 +1509,9 @@ class InstanceTest(unittest.TestCase):
user = User.objects.first()
# Even if stored as ObjectId's internally mongoengine uses DBRefs
# As ObjectId's aren't automatically derefenced
self.assertTrue(isinstance(user._data['orgs'][0], DBRef))
self.assertTrue(isinstance(user.orgs[0], Organization))
self.assertTrue(isinstance(user._data['orgs'][0], Organization))
self.assertIsInstance(user._data['orgs'][0], DBRef)
self.assertIsInstance(user.orgs[0], Organization)
self.assertIsInstance(user._data['orgs'][0], Organization)
# Changing a value
with query_counter() as q:
@@ -1790,9 +1891,8 @@ class InstanceTest(unittest.TestCase):
post_obj = BlogPost.objects.first()
# Test laziness
self.assertTrue(isinstance(post_obj._data['author'],
bson.DBRef))
self.assertTrue(isinstance(post_obj.author, self.Person))
self.assertIsInstance(post_obj._data['author'], bson.DBRef)
self.assertIsInstance(post_obj.author, self.Person)
self.assertEqual(post_obj.author.name, 'Test User')
# Ensure that the dereferenced object may be changed and saved
@@ -1866,6 +1966,25 @@ class InstanceTest(unittest.TestCase):
author.delete()
self.assertEqual(BlogPost.objects.count(), 0)
def test_reverse_delete_rule_pull(self):
"""Ensure that a referenced document is also deleted with
pull.
"""
class Record(Document):
name = StringField()
children = ListField(ReferenceField('self', reverse_delete_rule=PULL))
Record.drop_collection()
parent_record = Record(name='parent').save()
child_record = Record(name='child').save()
parent_record.children.append(child_record)
parent_record.save()
child_record.delete()
self.assertEqual(Record.objects(name='parent').get().children, [])
def test_reverse_delete_rule_with_custom_id_field(self):
"""Ensure that a referenced document with custom primary key
is also deleted upon deletion.
@@ -2179,12 +2298,12 @@ class InstanceTest(unittest.TestCase):
# Make sure docs are properly identified in a list (__eq__ is used
# for the comparison).
all_user_list = list(User.objects.all())
self.assertTrue(u1 in all_user_list)
self.assertTrue(u2 in all_user_list)
self.assertTrue(u3 in all_user_list)
self.assertTrue(u4 not in all_user_list) # New object
self.assertTrue(b1 not in all_user_list) # Other object
self.assertTrue(b2 not in all_user_list) # Other object
self.assertIn(u1, all_user_list)
self.assertIn(u2, all_user_list)
self.assertIn(u3, all_user_list)
self.assertNotIn(u4, all_user_list) # New object
self.assertNotIn(b1, all_user_list) # Other object
self.assertNotIn(b2, all_user_list) # Other object
# Make sure docs can be used as keys in a dict (__hash__ is used
# for hashing the docs).
@@ -2202,10 +2321,10 @@ class InstanceTest(unittest.TestCase):
# Make sure docs are properly identified in a set (__hash__ is used
# for hashing the docs).
all_user_set = set(User.objects.all())
self.assertTrue(u1 in all_user_set)
self.assertTrue(u4 not in all_user_set)
self.assertTrue(b1 not in all_user_list)
self.assertTrue(b2 not in all_user_list)
self.assertIn(u1, all_user_set)
self.assertNotIn(u4, all_user_set)
self.assertNotIn(b1, all_user_list)
self.assertNotIn(b2, all_user_list)
# Make sure duplicate docs aren't accepted in the set
self.assertEqual(len(all_user_set), 3)
@@ -2906,7 +3025,7 @@ class InstanceTest(unittest.TestCase):
Person(name="Harry Potter").save()
person = Person.objects.first()
self.assertTrue('id' in person._data.keys())
self.assertIn('id', person._data.keys())
self.assertEqual(person._data.get('id'), person.id)
def test_complex_nesting_document_and_embedded_document(self):
@@ -2998,36 +3117,36 @@ class InstanceTest(unittest.TestCase):
dbref2 = f._data['test2']
obj2 = f.test2
self.assertTrue(isinstance(dbref2, DBRef))
self.assertTrue(isinstance(obj2, Test2))
self.assertTrue(obj2.id == dbref2.id)
self.assertTrue(obj2 == dbref2)
self.assertTrue(dbref2 == obj2)
self.assertIsInstance(dbref2, DBRef)
self.assertIsInstance(obj2, Test2)
self.assertEqual(obj2.id, dbref2.id)
self.assertEqual(obj2, dbref2)
self.assertEqual(dbref2, obj2)
dbref3 = f._data['test3']
obj3 = f.test3
self.assertTrue(isinstance(dbref3, DBRef))
self.assertTrue(isinstance(obj3, Test3))
self.assertTrue(obj3.id == dbref3.id)
self.assertTrue(obj3 == dbref3)
self.assertTrue(dbref3 == obj3)
self.assertIsInstance(dbref3, DBRef)
self.assertIsInstance(obj3, Test3)
self.assertEqual(obj3.id, dbref3.id)
self.assertEqual(obj3, dbref3)
self.assertEqual(dbref3, obj3)
self.assertTrue(obj2.id == obj3.id)
self.assertTrue(dbref2.id == dbref3.id)
self.assertFalse(dbref2 == dbref3)
self.assertFalse(dbref3 == dbref2)
self.assertTrue(dbref2 != dbref3)
self.assertTrue(dbref3 != dbref2)
self.assertEqual(obj2.id, obj3.id)
self.assertEqual(dbref2.id, dbref3.id)
self.assertNotEqual(dbref2, dbref3)
self.assertNotEqual(dbref3, dbref2)
self.assertNotEqual(dbref2, dbref3)
self.assertNotEqual(dbref3, dbref2)
self.assertFalse(obj2 == dbref3)
self.assertFalse(dbref3 == obj2)
self.assertTrue(obj2 != dbref3)
self.assertTrue(dbref3 != obj2)
self.assertNotEqual(obj2, dbref3)
self.assertNotEqual(dbref3, obj2)
self.assertNotEqual(obj2, dbref3)
self.assertNotEqual(dbref3, obj2)
self.assertFalse(obj3 == dbref2)
self.assertFalse(dbref2 == obj3)
self.assertTrue(obj3 != dbref2)
self.assertTrue(dbref2 != obj3)
self.assertNotEqual(obj3, dbref2)
self.assertNotEqual(dbref2, obj3)
self.assertNotEqual(obj3, dbref2)
self.assertNotEqual(dbref2, obj3)
def test_default_values(self):
class Person(Document):
@@ -3076,6 +3195,64 @@ class InstanceTest(unittest.TestCase):
self.assertEquals(p.id, None)
p.id = "12345" # in case it is not working: "OperationError: Shard Keys are immutable..." will be raised here
def test_from_son_created_False_without_id(self):
class MyPerson(Document):
name = StringField()
MyPerson.objects.delete()
p = MyPerson.from_json('{"name": "a_fancy_name"}', created=False)
self.assertFalse(p._created)
self.assertIsNone(p.id)
p.save()
self.assertIsNotNone(p.id)
saved_p = MyPerson.objects.get(id=p.id)
self.assertEqual(saved_p.name, 'a_fancy_name')
def test_from_son_created_False_with_id(self):
# 1854
class MyPerson(Document):
name = StringField()
MyPerson.objects.delete()
p = MyPerson.from_json('{"_id": "5b85a8b04ec5dc2da388296e", "name": "a_fancy_name"}', created=False)
self.assertFalse(p._created)
self.assertEqual(p._changed_fields, [])
self.assertEqual(p.name, 'a_fancy_name')
self.assertEqual(p.id, ObjectId('5b85a8b04ec5dc2da388296e'))
p.save()
with self.assertRaises(DoesNotExist):
# Since created=False and we gave an id in the json and _changed_fields is empty
# mongoengine assumes that the document exits with that structure already
# and calling .save() didn't save anything
MyPerson.objects.get(id=p.id)
self.assertFalse(p._created)
p.name = 'a new fancy name'
self.assertEqual(p._changed_fields, ['name'])
p.save()
saved_p = MyPerson.objects.get(id=p.id)
self.assertEqual(saved_p.name, p.name)
def test_from_son_created_True_with_an_id(self):
class MyPerson(Document):
name = StringField()
MyPerson.objects.delete()
p = MyPerson.from_json('{"_id": "5b85a8b04ec5dc2da388296e", "name": "a_fancy_name"}', created=True)
self.assertTrue(p._created)
self.assertEqual(p._changed_fields, [])
self.assertEqual(p.name, 'a_fancy_name')
self.assertEqual(p.id, ObjectId('5b85a8b04ec5dc2da388296e'))
p.save()
saved_p = MyPerson.objects.get(id=p.id)
self.assertEqual(saved_p, p)
self.assertEqual(p.name, 'a_fancy_name')
def test_null_field(self):
# 734
class User(Document):
@@ -3149,6 +3326,50 @@ class InstanceTest(unittest.TestCase):
person.update(set__height=2.0)
@requires_mongodb_gte_26
def test_push_with_position(self):
"""Ensure that push with position works properly for an instance."""
class BlogPost(Document):
slug = StringField()
tags = ListField(StringField())
blog = BlogPost()
blog.slug = "ABC"
blog.tags = ["python"]
blog.save()
blog.update(push__tags__0=["mongodb", "code"])
blog.reload()
self.assertEqual(blog.tags, ['mongodb', 'code', 'python'])
def test_push_nested_list(self):
"""Ensure that push update works in nested list"""
class BlogPost(Document):
slug = StringField()
tags = ListField()
blog = BlogPost(slug="test").save()
blog.update(push__tags=["value1", 123])
blog.reload()
self.assertEqual(blog.tags, [["value1", 123]])
def test_accessing_objects_with_indexes_error(self):
insert_result = self.db.company.insert_many([{'name': 'Foo'},
{'name': 'Foo'}]) # Force 2 doc with same name
REF_OID = insert_result.inserted_ids[0]
self.db.user.insert_one({'company': REF_OID}) # Force 2 doc with same name
class Company(Document):
name = StringField(unique=True)
class User(Document):
company = ReferenceField(Company)
# Ensure index creation exception aren't swallowed (#1688)
with self.assertRaises(DuplicateKeyError):
User.objects().select_related()
if __name__ == '__main__':
unittest.main()

View File

@@ -20,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
@@ -40,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')
@@ -58,7 +58,7 @@ class ValidatorErrorTest(unittest.TestCase):
try:
User().validate()
except ValidationError as e:
self.assertTrue("User:None" in e.message)
self.assertIn("User:None", e.message)
self.assertEqual(e.to_dict(), {
'username': 'Field is required',
'name': 'Field is required'})
@@ -68,7 +68,7 @@ class ValidatorErrorTest(unittest.TestCase):
try:
user.save()
except ValidationError as e:
self.assertTrue("User:RossC0" in e.message)
self.assertIn("User:RossC0", e.message)
self.assertEqual(e.to_dict(), {
'name': 'Field is required'})
@@ -116,7 +116,7 @@ class ValidatorErrorTest(unittest.TestCase):
try:
Doc(id="bad").validate()
except ValidationError as e:
self.assertTrue("SubDoc:None" in e.message)
self.assertIn("SubDoc:None", e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})
@@ -127,14 +127,14 @@ 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 as e:
self.assertTrue("Doc:test" in e.message)
self.assertIn("Doc:test", e.message)
self.assertEqual(e.to_dict(), {
"e": {'val': 'OK could not be converted to int'}})

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

@@ -53,8 +53,8 @@ class FileTest(MongoDBTestCase):
putfile.save()
result = PutFile.objects.first()
self.assertTrue(putfile == result)
self.assertEqual("%s" % result.the_file, "<GridFSProxy: hello>")
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
@@ -71,7 +71,7 @@ class FileTest(MongoDBTestCase):
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()
@@ -96,7 +96,7 @@ class FileTest(MongoDBTestCase):
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)
@@ -132,7 +132,7 @@ class FileTest(MongoDBTestCase):
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)
@@ -161,7 +161,7 @@ class FileTest(MongoDBTestCase):
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
@@ -169,7 +169,7 @@ class FileTest(MongoDBTestCase):
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()
@@ -231,8 +231,8 @@ class FileTest(MongoDBTestCase):
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()
@@ -291,7 +291,7 @@ class FileTest(MongoDBTestCase):
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 """

View File

@@ -298,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.
@@ -316,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):
@@ -335,9 +335,9 @@ class GeoFieldTest(unittest.TestCase):
Parent(name='Berlin').save()
info = Parent._get_collection().index_information()
self.assertFalse('location_2d' in info)
self.assertNotIn('location_2d', info)
info = Location._get_collection().index_information()
self.assertTrue('location_2d' in info)
self.assertIn('location_2d', info)
self.assertEqual(len(Parent._geo_indices()), 0)
self.assertEqual(len(Location._geo_indices()), 1)

View File

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

View File

@@ -181,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()
@@ -197,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()
@@ -215,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)

View File

@@ -3,7 +3,7 @@ import unittest
from mongoengine import *
from tests.utils import MongoDBTestCase, needs_mongodb_v3
from tests.utils import MongoDBTestCase, requires_mongodb_gte_3
__all__ = ("GeoQueriesTest",)
@@ -72,7 +72,7 @@ class GeoQueriesTest(MongoDBTestCase):
# $minDistance was added in MongoDB v2.6, but continued being buggy
# until v3.0; skip for older versions
@needs_mongodb_v3
@requires_mongodb_gte_3
def test_near_and_min_distance(self):
"""Ensure the "min_distance" operator works alongside the "near"
operator.
@@ -95,9 +95,9 @@ class GeoQueriesTest(MongoDBTestCase):
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)
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]
@@ -245,7 +245,7 @@ class GeoQueriesTest(MongoDBTestCase):
# $minDistance was added in MongoDB v2.6, but continued being buggy
# until v3.0; skip for older versions
@needs_mongodb_v3
@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.
@@ -285,9 +285,9 @@ class GeoQueriesTest(MongoDBTestCase):
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)
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
@@ -329,7 +329,7 @@ class GeoQueriesTest(MongoDBTestCase):
self._test_embedded(point_field_class=PointField)
# Needs MongoDB > 2.6.4 https://jira.mongodb.org/browse/SERVER-14039
@needs_mongodb_v3
@requires_mongodb_gte_3
def test_spherical_geospatial_operators(self):
"""Ensure that spherical geospatial queries are working."""
class Point(Document):
@@ -510,6 +510,24 @@ class GeoQueriesTest(MongoDBTestCase):
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()

View File

@@ -1,6 +1,8 @@
import unittest
from mongoengine import connect, Document, IntField
from mongoengine import connect, Document, IntField, StringField, ListField
from tests.utils import requires_mongodb_gte_26
__all__ = ("FindAndModifyTest",)
@@ -94,6 +96,37 @@ class FindAndModifyTest(unittest.TestCase):
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()

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,7 @@
import unittest
from bson.son import SON
from mongoengine import *
from mongoengine.queryset import Q, transform
@@ -28,12 +30,16 @@ class TransformTest(unittest.TestCase):
{'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()
@@ -42,14 +48,28 @@ class TransformTest(unittest.TestCase):
for k, v in (("set", "$set"), ("set_on_insert", "$setOnInsert"), ("push", "$push")):
update = transform.update(DicDoc, **{"%s__dictField__test" % k: doc})
self.assertTrue(isinstance(update[v]["dictField.test"], dict))
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.assertTrue(isinstance(update["$pull"]["dictField"]["test"], dict))
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.
@@ -68,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()
@@ -96,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()
@@ -241,6 +259,30 @@ class TransformTest(unittest.TestCase):
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__':
unittest.main()

View File

@@ -196,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)
@@ -296,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.
"""

View File

@@ -39,15 +39,15 @@ class ConnectionTest(unittest.TestCase):
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.
@@ -59,31 +59,31 @@ class ConnectionTest(unittest.TestCase):
connect('mongoenginetest', host='mongomock://localhost')
conn = get_connection()
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect('mongoenginetest2', host='mongomock://localhost', alias='testdb2')
conn = get_connection('testdb2')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect('mongoenginetest3', host='mongodb://localhost', is_mock=True, alias='testdb3')
conn = get_connection('testdb3')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect('mongoenginetest4', is_mock=True, alias='testdb4')
conn = get_connection('testdb4')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host='mongodb://localhost:27017/mongoenginetest5', is_mock=True, alias='testdb5')
conn = get_connection('testdb5')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host='mongomock://localhost:27017/mongoenginetest6', alias='testdb6')
conn = get_connection('testdb6')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host='mongomock://localhost:27017/mongoenginetest7', is_mock=True, alias='testdb7')
conn = get_connection('testdb7')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
def test_connect_with_host_list(self):
"""Ensure that the connect() method works when host is a list
@@ -97,27 +97,27 @@ class ConnectionTest(unittest.TestCase):
connect(host=['mongomock://localhost'])
conn = get_connection()
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongodb://localhost'], is_mock=True, alias='testdb2')
conn = get_connection('testdb2')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['localhost'], is_mock=True, alias='testdb3')
conn = get_connection('testdb3')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongomock://localhost:27017', 'mongomock://localhost:27018'], alias='testdb4')
conn = get_connection('testdb4')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['mongodb://localhost:27017', 'mongodb://localhost:27018'], is_mock=True, alias='testdb5')
conn = get_connection('testdb5')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
connect(host=['localhost:27017', 'localhost:27018'], is_mock=True, alias='testdb6')
conn = get_connection('testdb6')
self.assertTrue(isinstance(conn, mongomock.MongoClient))
self.assertIsInstance(conn, mongomock.MongoClient)
def test_disconnect(self):
"""Ensure that the disconnect() method works properly
@@ -163,10 +163,10 @@ class ConnectionTest(unittest.TestCase):
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({})
@@ -179,10 +179,10 @@ class ConnectionTest(unittest.TestCase):
connect("mongoenginetest", host='mongodb://localhost/')
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')
def test_connect_uri_default_db(self):
@@ -192,10 +192,10 @@ class ConnectionTest(unittest.TestCase):
connect(host='mongodb://localhost/')
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, 'test')
def test_uri_without_credentials_doesnt_override_conn_settings(self):
@@ -242,7 +242,7 @@ class ConnectionTest(unittest.TestCase):
'mongoenginetest?authSource=admin')
)
db = get_db('test2')
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'mongoenginetest')
# Clear all users
@@ -255,10 +255,10 @@ class ConnectionTest(unittest.TestCase):
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_register_connection_defaults(self):
@@ -267,7 +267,7 @@ class ConnectionTest(unittest.TestCase):
register_connection('testdb', 'mongoenginetest', host=None, port=None)
conn = get_connection('testdb')
self.assertTrue(isinstance(conn, pymongo.mongo_client.MongoClient))
self.assertIsInstance(conn, pymongo.mongo_client.MongoClient)
def test_connection_kwargs(self):
"""Ensure that connection kwargs get passed to pymongo."""
@@ -326,7 +326,7 @@ class ConnectionTest(unittest.TestCase):
if IS_PYMONGO_3:
c = connect(host='mongodb://localhost/test?replicaSet=local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
@@ -343,7 +343,7 @@ class ConnectionTest(unittest.TestCase):
self.assertEqual(c._MongoClient__options.replica_set_name,
'local-rs')
db = get_db()
self.assertTrue(isinstance(db, pymongo.database.Database))
self.assertIsInstance(db, pymongo.database.Database)
self.assertEqual(db.name, 'test')
else:
# PyMongo < v3.x raises an exception:
@@ -364,6 +364,12 @@ 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")
@@ -371,8 +377,8 @@ class ConnectionTest(unittest.TestCase):
mongo_connections = mongoengine.connection._connections
self.assertEqual(len(mongo_connections.items()), 2)
self.assertTrue('t1' in mongo_connections.keys())
self.assertTrue('t2' in mongo_connections.keys())
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')

View File

@@ -89,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.
@@ -129,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):
@@ -152,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:
@@ -185,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 range(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

@@ -1,6 +1,21 @@
import unittest
from mongoengine.base.datastructures import StrictDict, SemiStrictDict
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):
@@ -76,44 +91,5 @@ class TestStrictDict(unittest.TestCase):
assert dict(**d) == {'a': 1, 'b': 2}
class TestSemiSrictDict(TestStrictDict):
def strict_dict_class(self, *args, **kwargs):
return SemiStrictDict.create(*args, **kwargs)
def test_init_fails_on_nonexisting_attrs(self):
# disable irrelevant test
pass
def test_setattr_raises_on_nonexisting_attr(self):
# disable irrelevant test
pass
def test_setattr_getattr_nonexisting_attr_succeeds(self):
d = self.dtype()
d.x = 1
self.assertEqual(d.x, 1)
def test_init_succeeds_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
self.assertEqual((d.a, d.b, d.c, d.x), (1, 1, 1, 2))
def test_iter_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
self.assertEqual(list(d), ['a', 'b', 'c', 'x'])
def test_iteritems_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
self.assertEqual(list(d.iteritems()), [('a', 1), ('b', 1), ('c', 1), ('x', 2)])
def tets_cmp_with_strict_dicts(self):
d = self.dtype(a=1, b=1, c=1)
dd = StrictDict.create(("a", "b", "c"))(a=1, b=1, c=1)
self.assertEqual(d, dd)
def test_cmp_with_strict_dict_with_nonexisting_attrs(self):
d = self.dtype(a=1, b=1, c=1, x=2)
dd = StrictDict.create(("a", "b", "c", "x"))(a=1, b=1, c=1, x=2)
self.assertEqual(d, dd)
if __name__ == '__main__':
unittest.main()

View File

@@ -200,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)
@@ -224,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.
@@ -469,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:
@@ -485,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:
@@ -502,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()
@@ -560,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:
@@ -576,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:
@@ -593,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()
@@ -633,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:
@@ -646,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:
@@ -660,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()
@@ -715,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:
@@ -731,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:
@@ -748,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()
@@ -806,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:
@@ -822,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:
@@ -839,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()
@@ -894,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:
@@ -910,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:
@@ -927,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()
@@ -1029,7 +1029,6 @@ 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
@@ -1209,10 +1208,10 @@ class FieldTest(unittest.TestCase):
# Can't use query_counter across databases - so test the _data object
book = Book.objects.first()
self.assertFalse(isinstance(book._data['author'], User))
self.assertNotIsInstance(book._data['author'], User)
book.select_related()
self.assertTrue(isinstance(book._data['author'], User))
self.assertIsInstance(book._data['author'], User)
def test_non_ascii_pk(self):
"""

38
tests/test_utils.py Normal file
View File

@@ -0,0 +1,38 @@
import unittest
import re
from mongoengine.base.utils import LazyRegexCompiler
signal_output = []
class LazyRegexCompilerTest(unittest.TestCase):
def test_lazy_regex_compiler_verify_laziness_of_descriptor(self):
class UserEmail(object):
EMAIL_REGEX = LazyRegexCompiler('@', flags=32)
descriptor = UserEmail.__dict__['EMAIL_REGEX']
self.assertIsNone(descriptor._compiled_regex)
regex = UserEmail.EMAIL_REGEX
self.assertEqual(regex, re.compile('@', flags=32))
self.assertEqual(regex.search('user@domain.com').group(), '@')
user_email = UserEmail()
self.assertIs(user_email.EMAIL_REGEX, UserEmail.EMAIL_REGEX)
def test_lazy_regex_compiler_verify_cannot_set_descriptor_on_instance(self):
class UserEmail(object):
EMAIL_REGEX = LazyRegexCompiler('@')
user_email = UserEmail()
with self.assertRaises(AttributeError):
user_email.EMAIL_REGEX = re.compile('@')
def test_lazy_regex_compiler_verify_can_override_class_attr(self):
class UserEmail(object):
EMAIL_REGEX = LazyRegexCompiler('@')
UserEmail.EMAIL_REGEX = re.compile('cookies')
self.assertEqual(UserEmail.EMAIL_REGEX.search('Cake & cookies').group(), 'cookies')

View File

@@ -7,12 +7,19 @@ from mongoengine.connection import get_db, get_connection
from mongoengine.python_support import IS_PYMONGO_3
MONGO_TEST_DB = 'mongoenginetest'
MONGO_TEST_DB = 'mongoenginetest' # standard name for the test database
# Constant that can be used to compare the version retrieved with
# get_mongodb_version()
MONGODB_26 = (2, 6)
MONGODB_3 = (3,0)
MONGODB_32 = (3, 2)
class MongoDBTestCase(unittest.TestCase):
"""Base class for tests that need a mongodb connection
db is being dropped automatically
It ensures that the db is clean at the beginning and dropped at the end automatically
"""
@classmethod
@@ -27,40 +34,46 @@ class MongoDBTestCase(unittest.TestCase):
def get_mongodb_version():
"""Return the version tuple of the MongoDB server that the default
connection is connected to.
"""
return tuple(get_connection().server_info()['versionArray'])
"""Return the version of the connected mongoDB (first 2 digits)
def _decorated_with_ver_requirement(func, ver_tuple):
:return: tuple(int, int)
"""
version_list = get_connection().server_info()['versionArray'][:2] # e.g: (3, 2)
return tuple(version_list)
def _decorated_with_ver_requirement(func, version):
"""Return a given function decorated with the version requirement
for a particular MongoDB version tuple.
:param version: The version required (tuple(int, int))
"""
def _inner(*args, **kwargs):
mongodb_ver = get_mongodb_version()
if mongodb_ver >= ver_tuple:
MONGODB_V = get_mongodb_version()
if MONGODB_V >= version:
return func(*args, **kwargs)
raise SkipTest('Needs MongoDB v{}+'.format(
'.'.join([str(v) for v in ver_tuple])
))
raise SkipTest('Needs MongoDB v{}+'.format('.'.join(str(n) for n in version)))
_inner.__name__ = func.__name__
_inner.__doc__ = func.__doc__
return _inner
def needs_mongodb_v26(func):
def requires_mongodb_gte_26(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v2.6.
"""
return _decorated_with_ver_requirement(func, (2, 6))
return _decorated_with_ver_requirement(func, MONGODB_26)
def needs_mongodb_v3(func):
def requires_mongodb_gte_3(func):
"""Raise a SkipTest exception if we're working with MongoDB version
lower than v3.0.
"""
return _decorated_with_ver_requirement(func, (3, 0))
return _decorated_with_ver_requirement(func, MONGODB_3)
def skip_pymongo3(f):
"""Raise a SkipTest exception if we're running a test against

View File

@@ -1,13 +1,12 @@
[tox]
envlist = {py27,py35,pypy,pypy3}-{mg27,mg28,mg30}
envlist = {py27,py35,pypy,pypy3}-{mg35,mg3x}
[testenv]
commands =
python setup.py nosetests {posargs}
deps =
nose
mg27: PyMongo<2.8
mg28: PyMongo>=2.8,<2.9
mg30: PyMongo>=3.0
mg35: PyMongo==3.5
mg3x: PyMongo>=3.0,<3.7
setenv =
PYTHON_EGG_CACHE = {envdir}/python-eggs