Compare commits
	
		
			72 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 2121387aa2 | ||
|  | 72c4444a60 | ||
|  | 2d8d2e7e6f | ||
|  | 49bff5d544 | ||
|  | bf30aba005 | ||
|  | 727778b730 | ||
|  | b081ffce50 | ||
|  | e46779f87b | ||
|  | dabe8c1bb7 | ||
|  | 4042f88bd8 | ||
|  | a0947d0c54 | ||
|  | aa68322641 | ||
|  | 7cc1d23bc7 | ||
|  | 0bd2103a8c | ||
|  | 7d8916b6e9 | ||
|  | ffdfe99d37 | ||
|  | 38fdf26405 | ||
|  | 9e80da705a | ||
|  | 9b04391f82 | ||
|  | 8f6c0796e3 | ||
|  | 326fcf4398 | ||
|  | fdda27abd1 | ||
|  | da7d64667e | ||
|  | d19c6a1573 | ||
|  | 5cd23039a0 | ||
|  | 19b18d3d0a | ||
|  | 101947da8b | ||
|  | d3c3c23630 | ||
|  | abc14316ea | ||
|  | b66621f9c6 | ||
|  | aa5510531d | ||
|  | 12b846586c | ||
|  | b705f5b743 | ||
|  | 18a5fba42b | ||
|  | b5a3b6f86a | ||
|  | 00f2eda576 | ||
|  | c70d252dc3 | ||
|  | 2f088ce29e | ||
|  | ff408c604b | ||
|  | 6621c318db | ||
|  | 22a8ad2fde | ||
|  | 7674dc9b34 | ||
|  | 9e0ca51c2f | ||
|  | 961629d156 | ||
|  | 2cbebf9c99 | ||
|  | 08a4deca17 | ||
|  | ce9ea7baad | ||
|  | b35efb9f72 | ||
|  | c45dfacb41 | ||
|  | 91152a7977 | ||
|  | 0ce081323f | ||
|  | 79486e3393 | ||
|  | 60758dd76b | ||
|  | e74f659015 | ||
|  | c1c09fa6b4 | ||
|  | 47c7cb9327 | ||
|  | 4d6256e1a1 | ||
|  | 13180d92e3 | ||
|  | ea25972257 | ||
|  | b6168898ec | ||
|  | da33cb54fe | ||
|  | 35d0458228 | ||
|  | e6c0280b40 | ||
|  | 9ab856e186 | ||
|  | aa4996ef28 | ||
|  | 2f4e2bde6b | ||
|  | e90f6a2fa3 | ||
|  | be8f1b9fdd | ||
|  | ba99190f53 | ||
|  | 70088704e2 | ||
|  | 1eae97731f | ||
|  | 71c3c632d7 | 
							
								
								
									
										14
									
								
								.travis.yml
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								.travis.yml
									
									
									
									
									
								
							| @@ -21,7 +21,7 @@ python: | ||||
| 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 | ||||
| @@ -31,19 +31,19 @@ matrix: | ||||
|   - python: 2.7 | ||||
|     env: MONGODB=2.4 PYMONGO=2.7 | ||||
|   - python: 2.7 | ||||
|     env: MONGODB=2.4 PYMONGO=3.0 | ||||
|     env: MONGODB=2.4 PYMONGO=3.5 | ||||
|   - python: 2.7 | ||||
|     env: MONGODB=3.0 PYMONGO=3.0 | ||||
|     env: MONGODB=3.0 PYMONGO=3.x | ||||
|   - python: 3.5 | ||||
|     env: MONGODB=2.4 PYMONGO=2.7 | ||||
|   - python: 3.5 | ||||
|     env: MONGODB=2.4 PYMONGO=3.0 | ||||
|     env: MONGODB=2.4 PYMONGO=3.5 | ||||
|   - python: 3.5 | ||||
|     env: MONGODB=3.0 PYMONGO=3.0 | ||||
|     env: MONGODB=3.0 PYMONGO=3.x | ||||
|   - python: 3.6 | ||||
|     env: MONGODB=2.4 PYMONGO=3.0 | ||||
|     env: MONGODB=2.4 PYMONGO=3.5 | ||||
|   - python: 3.6 | ||||
|     env: MONGODB=3.0 PYMONGO=3.0 | ||||
|     env: MONGODB=3.0 PYMONGO=3.x | ||||
|  | ||||
| before_install: | ||||
| - bash .install_mongodb_on_travis.sh | ||||
|   | ||||
							
								
								
									
										3
									
								
								AUTHORS
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								AUTHORS
									
									
									
									
									
								
							| @@ -244,4 +244,5 @@ that much better: | ||||
|  * Stanislav Kaledin (https://github.com/sallyruthstruik) | ||||
|  * Dmitry Yantsen (https://github.com/mrTable) | ||||
|  * Renjianxin (https://github.com/Davidrjx) | ||||
|  * Erdenezul Batmunkh (https://github.com/erdenezul) | ||||
|  * Erdenezul Batmunkh (https://github.com/erdenezul) | ||||
|  * Andy Yankovsky (https://github.com/werat) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -2,9 +2,18 @@ | ||||
| Changelog | ||||
| ========= | ||||
|  | ||||
| Development | ||||
| =========== | ||||
| - (Fill this out as you fix issues and develop your features). | ||||
| dev | ||||
| === | ||||
| -  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 | ||||
|  | ||||
| Changes in 0.15.0 | ||||
| ================= | ||||
| - Add LazyReferenceField and GenericLazyReferenceField to address #1230 | ||||
|  | ||||
| Changes in 0.14.1 | ||||
| ================= | ||||
|   | ||||
| @@ -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() | ||||
| @@ -618,7 +620,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} | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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:: | ||||
|   | ||||
| @@ -23,7 +23,7 @@ __all__ = (list(document.__all__) + list(fields.__all__) + | ||||
|            list(signals.__all__) + list(errors.__all__)) | ||||
|  | ||||
|  | ||||
| VERSION = (0, 14, 3) | ||||
| VERSION = (0, 15, 0) | ||||
|  | ||||
|  | ||||
| def get_version(): | ||||
|   | ||||
| @@ -15,7 +15,7 @@ __all__ = ( | ||||
|     'UPDATE_OPERATORS', '_document_registry', 'get_document', | ||||
|  | ||||
|     # datastructures | ||||
|     'BaseDict', 'BaseList', 'EmbeddedDocumentList', | ||||
|     'BaseDict', 'BaseList', 'EmbeddedDocumentList', 'LazyReference', | ||||
|  | ||||
|     # document | ||||
|     'BaseDocument', | ||||
|   | ||||
| @@ -1,12 +1,13 @@ | ||||
| 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): | ||||
| @@ -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 | ||||
| @@ -445,3 +447,42 @@ class StrictDict(object): | ||||
|  | ||||
|             cls._classes[allowed_keys] = SpecificStrictDict | ||||
|         return cls._classes[allowed_keys] | ||||
|  | ||||
|  | ||||
| class LazyReference(DBRef): | ||||
|     __slots__ = ('_cached_doc', 'passthrough', 'document_type') | ||||
|  | ||||
|     def fetch(self, force=False): | ||||
|         if not self._cached_doc or force: | ||||
|             self._cached_doc = self.document_type.objects.get(pk=self.pk) | ||||
|             if not self._cached_doc: | ||||
|                 raise DoesNotExist('Trying to dereference unknown document %s' % (self)) | ||||
|         return self._cached_doc | ||||
|  | ||||
|     @property | ||||
|     def pk(self): | ||||
|         return self.id | ||||
|  | ||||
|     def __init__(self, document_type, pk, cached_doc=None, passthrough=False): | ||||
|         self.document_type = document_type | ||||
|         self._cached_doc = cached_doc | ||||
|         self.passthrough = passthrough | ||||
|         super(LazyReference, self).__init__(self.document_type._get_collection_name(), pk) | ||||
|  | ||||
|     def __getitem__(self, name): | ||||
|         if not self.passthrough: | ||||
|             raise KeyError() | ||||
|         document = self.fetch() | ||||
|         return document[name] | ||||
|  | ||||
|     def __getattr__(self, name): | ||||
|         if not object.__getattribute__(self, 'passthrough'): | ||||
|             raise AttributeError() | ||||
|         document = self.fetch() | ||||
|         try: | ||||
|             return document[name] | ||||
|         except KeyError: | ||||
|             raise AttributeError() | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return "<LazyReference(%s, %r)>" % (self.document_type, self.pk) | ||||
|   | ||||
| @@ -13,6 +13,7 @@ from mongoengine import signals | ||||
| from mongoengine.base.common import get_document | ||||
| from mongoengine.base.datastructures import (BaseDict, BaseList, | ||||
|                                              EmbeddedDocumentList, | ||||
|                                              LazyReference, | ||||
|                                              StrictDict) | ||||
| from mongoengine.base.fields import ComplexBaseField | ||||
| from mongoengine.common import _import_class | ||||
| @@ -488,7 +489,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 | ||||
|  | ||||
|   | ||||
| @@ -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, | ||||
|   | ||||
| @@ -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: | ||||
|   | ||||
| @@ -280,6 +280,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 | ||||
|   | ||||
| @@ -26,7 +26,9 @@ except ImportError: | ||||
|     Int64 = long | ||||
|  | ||||
| from mongoengine.base import (BaseDocument, BaseField, ComplexBaseField, | ||||
|                               GeoJsonBaseField, ObjectIdField, get_document) | ||||
|                               GeoJsonBaseField, LazyReference, ObjectIdField, | ||||
|                               get_document) | ||||
| 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 | ||||
| @@ -46,6 +48,7 @@ __all__ = ( | ||||
|     'GenericEmbeddedDocumentField', 'DynamicField', 'ListField', | ||||
|     'SortedListField', 'EmbeddedDocumentListField', 'DictField', | ||||
|     'MapField', 'ReferenceField', 'CachedReferenceField', | ||||
|     'LazyReferenceField', 'GenericLazyReferenceField', | ||||
|     'GenericReferenceField', 'BinaryField', 'GridFSError', 'GridFSProxy', | ||||
|     'FileField', 'ImageGridFsProxy', 'ImproperlyConfigured', 'ImageField', | ||||
|     'GeoPointField', 'PointField', 'LineStringField', 'PolygonField', | ||||
| @@ -611,6 +614,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) | ||||
| @@ -686,16 +690,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 | ||||
| @@ -779,6 +795,17 @@ 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 | ||||
| @@ -893,8 +920,11 @@ class DictField(ComplexBaseField): | ||||
|         self.field = field | ||||
|         self._auto_dereference = False | ||||
|         self.basecls = basecls or BaseField | ||||
|  | ||||
|         # XXX ValidationError raised outside of the "validate" method. | ||||
|         if not issubclass(self.basecls, BaseField): | ||||
|             self.error('DictField only accepts dict values') | ||||
|  | ||||
|         kwargs.setdefault('default', lambda: {}) | ||||
|         super(DictField, self).__init__(*args, **kwargs) | ||||
|  | ||||
| @@ -943,6 +973,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') | ||||
| @@ -953,6 +984,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` | ||||
| @@ -993,6 +1033,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) | ||||
| @@ -1047,6 +1088,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') | ||||
| @@ -1086,19 +1129,21 @@ 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): | ||||
|         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) | ||||
|                     self.document_type._class_name | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
|     def lookup_member(self, member_name): | ||||
| @@ -1121,6 +1166,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) | ||||
| @@ -1195,6 +1241,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 | ||||
| @@ -1203,7 +1250,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_)), | ||||
| @@ -1221,16 +1267,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') | ||||
|  | ||||
| @@ -1263,6 +1313,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 | ||||
| @@ -1285,6 +1341,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') | ||||
|  | ||||
| @@ -1348,6 +1406,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: | ||||
| @@ -2141,3 +2200,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) | ||||
|   | ||||
| @@ -486,8 +486,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 | ||||
|   | ||||
| @@ -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: | ||||
| @@ -284,9 +271,15 @@ def update(_doc_cls=None, **update): | ||||
|             if isinstance(field, GeoJsonBaseField): | ||||
|                 value = field.to_mongo(value) | ||||
|  | ||||
|             if op == 'push' and isinstance(value, (list, tuple, set)): | ||||
|             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', 'pull'): | ||||
|             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'): | ||||
| @@ -335,7 +328,7 @@ def update(_doc_cls=None, **update): | ||||
|                 value = {key: value} | ||||
|         elif op == 'addToSet' and isinstance(value, list): | ||||
|             value = {key: {'$each': value}} | ||||
|         elif op == 'push': | ||||
|         elif op in ('push', 'pushAll'): | ||||
|             if parts[-1].isdigit(): | ||||
|                 key = parts[0] | ||||
|                 position = int(parts[-1]) | ||||
| @@ -344,10 +337,14 @@ def update(_doc_cls=None, **update): | ||||
|                 if not isinstance(value, (set, tuple, list)): | ||||
|                     value = [value] | ||||
|                 value = {key: {'$each': value, '$position': position}} | ||||
|             elif isinstance(value, list): | ||||
|                 value = {key: {'$each': value}} | ||||
|             else: | ||||
|                 value = {key: value} | ||||
|                 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 | ||||
| @@ -439,3 +436,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] | ||||
|   | ||||
| @@ -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 | ||||
| exclude=build,dist,docs,venv,venv3,.tox,.eggs,tests | ||||
| max-complexity=47 | ||||
| application-import-names=mongoengine,tests | ||||
|   | ||||
| @@ -1341,6 +1341,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. | ||||
| @@ -1884,6 +1901,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. | ||||
| @@ -3183,6 +3219,17 @@ class InstanceTest(unittest.TestCase): | ||||
|         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]]) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
|   | ||||
| @@ -26,7 +26,7 @@ except ImportError: | ||||
| from mongoengine import * | ||||
| from mongoengine.connection import get_db | ||||
| from mongoengine.base import (BaseDict, BaseField, EmbeddedDocumentList, | ||||
|                               _document_registry) | ||||
|                               _document_registry, LazyReference) | ||||
|  | ||||
| from tests.utils import MongoDBTestCase | ||||
|  | ||||
| @@ -931,7 +931,9 @@ class FieldTest(MongoDBTestCase): | ||||
|             comments = ListField(EmbeddedDocumentField(Comment)) | ||||
|             tags = ListField(StringField()) | ||||
|             authors = ListField(ReferenceField(User)) | ||||
|             authors_as_lazy = ListField(LazyReferenceField(User)) | ||||
|             generic = ListField(GenericReferenceField()) | ||||
|             generic_as_lazy = ListField(GenericLazyReferenceField()) | ||||
|  | ||||
|         User.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
| @@ -969,6 +971,15 @@ class FieldTest(MongoDBTestCase): | ||||
|         post.authors = [user] | ||||
|         post.validate() | ||||
|  | ||||
|         post.authors_as_lazy = [Comment()] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.authors_as_lazy = [User()] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.authors_as_lazy = [user] | ||||
|         post.validate() | ||||
|  | ||||
|         post.generic = [1, 2] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
| @@ -981,6 +992,18 @@ class FieldTest(MongoDBTestCase): | ||||
|         post.generic = [user] | ||||
|         post.validate() | ||||
|  | ||||
|         post.generic_as_lazy = [1, 2] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.generic_as_lazy = [User(), Comment()] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.generic_as_lazy = [Comment()] | ||||
|         self.assertRaises(ValidationError, post.validate) | ||||
|  | ||||
|         post.generic_as_lazy = [user] | ||||
|         post.validate() | ||||
|  | ||||
|     def test_sorted_list_sorting(self): | ||||
|         """Ensure that a sorted list field properly sorts values. | ||||
|         """ | ||||
| @@ -4356,6 +4379,51 @@ class CachedReferenceFieldTest(MongoDBTestCase): | ||||
|         self.assertEqual(SocialData.objects(person__group=g2).count(), 1) | ||||
|         self.assertEqual(SocialData.objects(person__group=g2).first(), s2) | ||||
|  | ||||
|     def test_cached_reference_field_push_with_fields(self): | ||||
|         class Product(Document): | ||||
|             name = StringField() | ||||
|  | ||||
|         Product.drop_collection() | ||||
|  | ||||
|         class Basket(Document): | ||||
|             products = ListField(CachedReferenceField(Product, fields=['name'])) | ||||
|  | ||||
|         Basket.drop_collection() | ||||
|         product1 = Product(name='abc').save() | ||||
|         product2 = Product(name='def').save() | ||||
|         basket = Basket(products=[product1]).save() | ||||
|         self.assertEqual( | ||||
|             Basket.objects._collection.find_one(), | ||||
|             { | ||||
|                 '_id': basket.pk, | ||||
|                 'products': [ | ||||
|                     { | ||||
|                         '_id': product1.pk, | ||||
|                         'name': product1.name | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         ) | ||||
|         # push to list | ||||
|         basket.update(push__products=product2) | ||||
|         basket.reload() | ||||
|         self.assertEqual( | ||||
|             Basket.objects._collection.find_one(), | ||||
|             { | ||||
|                 '_id': basket.pk, | ||||
|                 'products': [ | ||||
|                     { | ||||
|                         '_id': product1.pk, | ||||
|                         'name': product1.name | ||||
|                     }, | ||||
|                     { | ||||
|                         '_id': product2.pk, | ||||
|                         'name': product2.name | ||||
|                     } | ||||
|                 ] | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|     def test_cached_reference_field_update_all(self): | ||||
|         class Person(Document): | ||||
|             TYPES = ( | ||||
| @@ -4598,5 +4666,522 @@ class CachedReferenceFieldTest(MongoDBTestCase): | ||||
|         self.assertTrue(isinstance(ocorrence.animal, Animal)) | ||||
|  | ||||
|  | ||||
| class LazyReferenceFieldTest(MongoDBTestCase): | ||||
|     def test_lazy_reference_config(self): | ||||
|         # Make sure ReferenceField only accepts a document class or a string | ||||
|         # with a document class name. | ||||
|         self.assertRaises(ValidationError, LazyReferenceField, EmbeddedDocument) | ||||
|  | ||||
|     def test_lazy_reference_simple(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = LazyReferenceField(Animal) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         Ocurrence(person="test", animal=animal).save() | ||||
|         p = Ocurrence.objects.get() | ||||
|         self.assertIsInstance(p.animal, LazyReference) | ||||
|         fetched_animal = p.animal.fetch() | ||||
|         self.assertEqual(fetched_animal, animal) | ||||
|         # `fetch` keep cache on referenced document by default... | ||||
|         animal.tag = "not so heavy" | ||||
|         animal.save() | ||||
|         double_fetch = p.animal.fetch() | ||||
|         self.assertIs(fetched_animal, double_fetch) | ||||
|         self.assertEqual(double_fetch.tag, "heavy") | ||||
|         # ...unless specified otherwise | ||||
|         fetch_force = p.animal.fetch(force=True) | ||||
|         self.assertIsNot(fetch_force, fetched_animal) | ||||
|         self.assertEqual(fetch_force.tag, "not so heavy") | ||||
|  | ||||
|     def test_lazy_reference_fetch_invalid_ref(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = LazyReferenceField(Animal) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         Ocurrence(person="test", animal=animal).save() | ||||
|         animal.delete() | ||||
|         p = Ocurrence.objects.get() | ||||
|         self.assertIsInstance(p.animal, LazyReference) | ||||
|         with self.assertRaises(DoesNotExist): | ||||
|             p.animal.fetch() | ||||
|  | ||||
|     def test_lazy_reference_set(self): | ||||
|         class Animal(Document): | ||||
|             meta = {'allow_inheritance': True} | ||||
|  | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = LazyReferenceField(Animal) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         class SubAnimal(Animal): | ||||
|             nick = StringField() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         sub_animal = SubAnimal(nick='doggo', name='dog').save() | ||||
|         for ref in ( | ||||
|                 animal, | ||||
|                 animal.pk, | ||||
|                 DBRef(animal._get_collection_name(), animal.pk), | ||||
|                 LazyReference(Animal, animal.pk), | ||||
|  | ||||
|                 sub_animal, | ||||
|                 sub_animal.pk, | ||||
|                 DBRef(sub_animal._get_collection_name(), sub_animal.pk), | ||||
|                 LazyReference(SubAnimal, sub_animal.pk), | ||||
|                 ): | ||||
|             p = Ocurrence(person="test", animal=ref).save() | ||||
|             p.reload() | ||||
|             self.assertIsInstance(p.animal, LazyReference) | ||||
|             p.animal.fetch() | ||||
|  | ||||
|     def test_lazy_reference_bad_set(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = LazyReferenceField(Animal) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         class BadDoc(Document): | ||||
|             pass | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         baddoc = BadDoc().save() | ||||
|         for bad in ( | ||||
|                 42, | ||||
|                 'foo', | ||||
|                 baddoc, | ||||
|                 DBRef(baddoc._get_collection_name(), animal.pk), | ||||
|                 LazyReference(BadDoc, animal.pk) | ||||
|                 ): | ||||
|             with self.assertRaises(ValidationError): | ||||
|                 p = Ocurrence(person="test", animal=bad).save() | ||||
|  | ||||
|     def test_lazy_reference_query_conversion(self): | ||||
|         """Ensure that LazyReferenceFields can be queried using objects and values | ||||
|         of the type of the primary key of the referenced object. | ||||
|         """ | ||||
|         class Member(Document): | ||||
|             user_num = IntField(primary_key=True) | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|             author = LazyReferenceField(Member, dbref=False) | ||||
|  | ||||
|         Member.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         m1 = Member(user_num=1) | ||||
|         m1.save() | ||||
|         m2 = Member(user_num=2) | ||||
|         m2.save() | ||||
|  | ||||
|         post1 = BlogPost(title='post 1', author=m1) | ||||
|         post1.save() | ||||
|  | ||||
|         post2 = BlogPost(title='post 2', author=m2) | ||||
|         post2.save() | ||||
|  | ||||
|         post = BlogPost.objects(author=m1).first() | ||||
|         self.assertEqual(post.id, post1.id) | ||||
|  | ||||
|         post = BlogPost.objects(author=m2).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|         # Same thing by passing a LazyReference instance | ||||
|         post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|     def test_lazy_reference_query_conversion_dbref(self): | ||||
|         """Ensure that LazyReferenceFields can be queried using objects and values | ||||
|         of the type of the primary key of the referenced object. | ||||
|         """ | ||||
|         class Member(Document): | ||||
|             user_num = IntField(primary_key=True) | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|             author = LazyReferenceField(Member, dbref=True) | ||||
|  | ||||
|         Member.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         m1 = Member(user_num=1) | ||||
|         m1.save() | ||||
|         m2 = Member(user_num=2) | ||||
|         m2.save() | ||||
|  | ||||
|         post1 = BlogPost(title='post 1', author=m1) | ||||
|         post1.save() | ||||
|  | ||||
|         post2 = BlogPost(title='post 2', author=m2) | ||||
|         post2.save() | ||||
|  | ||||
|         post = BlogPost.objects(author=m1).first() | ||||
|         self.assertEqual(post.id, post1.id) | ||||
|  | ||||
|         post = BlogPost.objects(author=m2).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|         # Same thing by passing a LazyReference instance | ||||
|         post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|     def test_lazy_reference_passthrough(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             animal = LazyReferenceField(Animal, passthrough=False) | ||||
|             animal_passthrough = LazyReferenceField(Animal, passthrough=True) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         Ocurrence(animal=animal, animal_passthrough=animal).save() | ||||
|         p = Ocurrence.objects.get() | ||||
|         self.assertIsInstance(p.animal, LazyReference) | ||||
|         with self.assertRaises(KeyError): | ||||
|             p.animal['name'] | ||||
|         with self.assertRaises(AttributeError): | ||||
|             p.animal.name | ||||
|         self.assertEqual(p.animal.pk, animal.pk) | ||||
|  | ||||
|         self.assertEqual(p.animal_passthrough.name, "Leopard") | ||||
|         self.assertEqual(p.animal_passthrough['name'], "Leopard") | ||||
|  | ||||
|         # Should not be able to access referenced document's methods | ||||
|         with self.assertRaises(AttributeError): | ||||
|             p.animal.save | ||||
|         with self.assertRaises(KeyError): | ||||
|             p.animal['save'] | ||||
|  | ||||
|     def test_lazy_reference_not_set(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = LazyReferenceField(Animal) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         Ocurrence(person='foo').save() | ||||
|         p = Ocurrence.objects.get() | ||||
|         self.assertIs(p.animal, None) | ||||
|  | ||||
|     def test_lazy_reference_equality(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         animalref = LazyReference(Animal, animal.pk) | ||||
|         self.assertEqual(animal, animalref) | ||||
|         self.assertEqual(animalref, animal) | ||||
|  | ||||
|         other_animalref = LazyReference(Animal, ObjectId("54495ad94c934721ede76f90")) | ||||
|         self.assertNotEqual(animal, other_animalref) | ||||
|         self.assertNotEqual(other_animalref, animal) | ||||
|  | ||||
|     def test_lazy_reference_embedded(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class EmbeddedOcurrence(EmbeddedDocument): | ||||
|             in_list = ListField(LazyReferenceField(Animal)) | ||||
|             direct = LazyReferenceField(Animal) | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             in_list = ListField(LazyReferenceField(Animal)) | ||||
|             in_embedded = EmbeddedDocumentField(EmbeddedOcurrence) | ||||
|             direct = LazyReferenceField(Animal) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal1 = Animal('doggo').save() | ||||
|         animal2 = Animal('cheeta').save() | ||||
|  | ||||
|         def check_fields_type(occ): | ||||
|             self.assertIsInstance(occ.direct, LazyReference) | ||||
|             for elem in occ.in_list: | ||||
|                 self.assertIsInstance(elem, LazyReference) | ||||
|             self.assertIsInstance(occ.in_embedded.direct, LazyReference) | ||||
|             for elem in occ.in_embedded.in_list: | ||||
|                 self.assertIsInstance(elem, LazyReference) | ||||
|  | ||||
|         occ = Ocurrence( | ||||
|             in_list=[animal1, animal2], | ||||
|             in_embedded={'in_list': [animal1, animal2], 'direct': animal1}, | ||||
|             direct=animal1 | ||||
|         ).save() | ||||
|         check_fields_type(occ) | ||||
|         occ.reload() | ||||
|         check_fields_type(occ) | ||||
|         occ.direct = animal1.id | ||||
|         occ.in_list = [animal1.id, animal2.id] | ||||
|         occ.in_embedded.direct = animal1.id | ||||
|         occ.in_embedded.in_list = [animal1.id, animal2.id] | ||||
|         check_fields_type(occ) | ||||
|  | ||||
|  | ||||
| class GenericLazyReferenceFieldTest(MongoDBTestCase): | ||||
|     def test_generic_lazy_reference_simple(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = GenericLazyReferenceField() | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         Ocurrence(person="test", animal=animal).save() | ||||
|         p = Ocurrence.objects.get() | ||||
|         self.assertIsInstance(p.animal, LazyReference) | ||||
|         fetched_animal = p.animal.fetch() | ||||
|         self.assertEqual(fetched_animal, animal) | ||||
|         # `fetch` keep cache on referenced document by default... | ||||
|         animal.tag = "not so heavy" | ||||
|         animal.save() | ||||
|         double_fetch = p.animal.fetch() | ||||
|         self.assertIs(fetched_animal, double_fetch) | ||||
|         self.assertEqual(double_fetch.tag, "heavy") | ||||
|         # ...unless specified otherwise | ||||
|         fetch_force = p.animal.fetch(force=True) | ||||
|         self.assertIsNot(fetch_force, fetched_animal) | ||||
|         self.assertEqual(fetch_force.tag, "not so heavy") | ||||
|  | ||||
|     def test_generic_lazy_reference_choices(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|  | ||||
|         class Vegetal(Document): | ||||
|             name = StringField() | ||||
|  | ||||
|         class Mineral(Document): | ||||
|             name = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             living_thing = GenericLazyReferenceField(choices=[Animal, Vegetal]) | ||||
|             thing = GenericLazyReferenceField() | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Vegetal.drop_collection() | ||||
|         Mineral.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal = Animal(name="Leopard").save() | ||||
|         vegetal = Vegetal(name="Oak").save() | ||||
|         mineral = Mineral(name="Granite").save() | ||||
|  | ||||
|         occ_animal = Ocurrence(living_thing=animal, thing=animal).save() | ||||
|         occ_vegetal = Ocurrence(living_thing=vegetal, thing=vegetal).save() | ||||
|         with self.assertRaises(ValidationError): | ||||
|             Ocurrence(living_thing=mineral).save() | ||||
|  | ||||
|         occ = Ocurrence.objects.get(living_thing=animal) | ||||
|         self.assertEqual(occ, occ_animal) | ||||
|         self.assertIsInstance(occ.thing, LazyReference) | ||||
|         self.assertIsInstance(occ.living_thing, LazyReference) | ||||
|  | ||||
|         occ.thing = vegetal | ||||
|         occ.living_thing = vegetal | ||||
|         occ.save() | ||||
|  | ||||
|         occ.thing = mineral | ||||
|         occ.living_thing = mineral | ||||
|         with self.assertRaises(ValidationError): | ||||
|             occ.save() | ||||
|  | ||||
|     def test_generic_lazy_reference_set(self): | ||||
|         class Animal(Document): | ||||
|             meta = {'allow_inheritance': True} | ||||
|  | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = GenericLazyReferenceField() | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         class SubAnimal(Animal): | ||||
|             nick = StringField() | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         sub_animal = SubAnimal(nick='doggo', name='dog').save() | ||||
|         for ref in ( | ||||
|                 animal, | ||||
|                 LazyReference(Animal, animal.pk), | ||||
|                 {'_cls': 'Animal', '_ref': DBRef(animal._get_collection_name(), animal.pk)}, | ||||
|  | ||||
|                 sub_animal, | ||||
|                 LazyReference(SubAnimal, sub_animal.pk), | ||||
|                 {'_cls': 'SubAnimal', '_ref': DBRef(sub_animal._get_collection_name(), sub_animal.pk)}, | ||||
|                 ): | ||||
|             p = Ocurrence(person="test", animal=ref).save() | ||||
|             p.reload() | ||||
|             self.assertIsInstance(p.animal, (LazyReference, Document)) | ||||
|             p.animal.fetch() | ||||
|  | ||||
|     def test_generic_lazy_reference_bad_set(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = GenericLazyReferenceField(choices=['Animal']) | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         class BadDoc(Document): | ||||
|             pass | ||||
|  | ||||
|         animal = Animal(name="Leopard", tag="heavy").save() | ||||
|         baddoc = BadDoc().save() | ||||
|         for bad in ( | ||||
|                 42, | ||||
|                 'foo', | ||||
|                 baddoc, | ||||
|                 LazyReference(BadDoc, animal.pk) | ||||
|                 ): | ||||
|             with self.assertRaises(ValidationError): | ||||
|                 p = Ocurrence(person="test", animal=bad).save() | ||||
|  | ||||
|     def test_generic_lazy_reference_query_conversion(self): | ||||
|         class Member(Document): | ||||
|             user_num = IntField(primary_key=True) | ||||
|  | ||||
|         class BlogPost(Document): | ||||
|             title = StringField() | ||||
|             author = GenericLazyReferenceField() | ||||
|  | ||||
|         Member.drop_collection() | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         m1 = Member(user_num=1) | ||||
|         m1.save() | ||||
|         m2 = Member(user_num=2) | ||||
|         m2.save() | ||||
|  | ||||
|         post1 = BlogPost(title='post 1', author=m1) | ||||
|         post1.save() | ||||
|  | ||||
|         post2 = BlogPost(title='post 2', author=m2) | ||||
|         post2.save() | ||||
|  | ||||
|         post = BlogPost.objects(author=m1).first() | ||||
|         self.assertEqual(post.id, post1.id) | ||||
|  | ||||
|         post = BlogPost.objects(author=m2).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|         # Same thing by passing a LazyReference instance | ||||
|         post = BlogPost.objects(author=LazyReference(Member, m2.pk)).first() | ||||
|         self.assertEqual(post.id, post2.id) | ||||
|  | ||||
|     def test_generic_lazy_reference_not_set(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             person = StringField() | ||||
|             animal = GenericLazyReferenceField() | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         Ocurrence(person='foo').save() | ||||
|         p = Ocurrence.objects.get() | ||||
|         self.assertIs(p.animal, None) | ||||
|  | ||||
|     def test_generic_lazy_reference_embedded(self): | ||||
|         class Animal(Document): | ||||
|             name = StringField() | ||||
|             tag = StringField() | ||||
|  | ||||
|         class EmbeddedOcurrence(EmbeddedDocument): | ||||
|             in_list = ListField(GenericLazyReferenceField()) | ||||
|             direct = GenericLazyReferenceField() | ||||
|  | ||||
|         class Ocurrence(Document): | ||||
|             in_list = ListField(GenericLazyReferenceField()) | ||||
|             in_embedded = EmbeddedDocumentField(EmbeddedOcurrence) | ||||
|             direct = GenericLazyReferenceField() | ||||
|  | ||||
|         Animal.drop_collection() | ||||
|         Ocurrence.drop_collection() | ||||
|  | ||||
|         animal1 = Animal('doggo').save() | ||||
|         animal2 = Animal('cheeta').save() | ||||
|  | ||||
|         def check_fields_type(occ): | ||||
|             self.assertIsInstance(occ.direct, LazyReference) | ||||
|             for elem in occ.in_list: | ||||
|                 self.assertIsInstance(elem, LazyReference) | ||||
|             self.assertIsInstance(occ.in_embedded.direct, LazyReference) | ||||
|             for elem in occ.in_embedded.in_list: | ||||
|                 self.assertIsInstance(elem, LazyReference) | ||||
|  | ||||
|         occ = Ocurrence( | ||||
|             in_list=[animal1, animal2], | ||||
|             in_embedded={'in_list': [animal1, animal2], 'direct': animal1}, | ||||
|             direct=animal1 | ||||
|         ).save() | ||||
|         check_fields_type(occ) | ||||
|         occ.reload() | ||||
|         check_fields_type(occ) | ||||
|         animal1_ref = {'_cls': 'Animal', '_ref': DBRef(animal1._get_collection_name(), animal1.pk)} | ||||
|         animal2_ref = {'_cls': 'Animal', '_ref': DBRef(animal2._get_collection_name(), animal2.pk)} | ||||
|         occ.direct = animal1_ref | ||||
|         occ.in_list = [animal1_ref, animal2_ref] | ||||
|         occ.in_embedded.direct = animal1_ref | ||||
|         occ.in_embedded.in_list = [animal1_ref, animal2_ref] | ||||
|         check_fields_type(occ) | ||||
|  | ||||
|  | ||||
| if __name__ == '__main__': | ||||
|     unittest.main() | ||||
|   | ||||
| @@ -1929,6 +1929,21 @@ class QuerySetTest(unittest.TestCase): | ||||
|         post.reload() | ||||
|         self.assertEqual(post.tags, ['scala', 'mongodb', 'python', 'java']) | ||||
|  | ||||
|     def test_update_push_list_of_list(self): | ||||
|         """Ensure that the 'push' update operation works in the list of list | ||||
|         """ | ||||
|         class BlogPost(Document): | ||||
|             slug = StringField() | ||||
|             tags = ListField() | ||||
|  | ||||
|         BlogPost.drop_collection() | ||||
|  | ||||
|         post = BlogPost(slug="test").save() | ||||
|  | ||||
|         BlogPost.objects.filter(slug="test").update(push__tags=["value1", 123]) | ||||
|         post.reload() | ||||
|         self.assertEqual(post.tags, [["value1", 123]]) | ||||
|  | ||||
|     def test_update_push_and_pull_add_to_set(self): | ||||
|         """Ensure that the 'pull' update operation works correctly. | ||||
|         """ | ||||
| @@ -2071,6 +2086,23 @@ class QuerySetTest(unittest.TestCase): | ||||
|             Site.objects(id=s.id).update_one( | ||||
|                 pull_all__collaborators__helpful__user=['Ross']) | ||||
|  | ||||
|     def test_pull_in_genericembedded_field(self): | ||||
|  | ||||
|         class Foo(EmbeddedDocument): | ||||
|             name = StringField() | ||||
|  | ||||
|         class Bar(Document): | ||||
|             foos = ListField(GenericEmbeddedDocumentField( | ||||
|                 choices=[Foo, ])) | ||||
|  | ||||
|         Bar.drop_collection() | ||||
|  | ||||
|         foo = Foo(name="bar") | ||||
|         bar = Bar(foos=[foo]).save() | ||||
|         Bar.objects(id=bar.id).update(pull__foos=foo) | ||||
|         bar.reload() | ||||
|         self.assertEqual(len(bar.foos), 0) | ||||
|  | ||||
|     def test_update_one_pop_generic_reference(self): | ||||
|  | ||||
|         class BlogTag(Document): | ||||
| @@ -2164,6 +2196,24 @@ class QuerySetTest(unittest.TestCase): | ||||
|         self.assertEqual(message.authors[1].name, "Ross") | ||||
|         self.assertEqual(message.authors[2].name, "Adam") | ||||
|  | ||||
|     def test_set_generic_embedded_documents(self): | ||||
|  | ||||
|         class Bar(EmbeddedDocument): | ||||
|             name = StringField() | ||||
|  | ||||
|         class User(Document): | ||||
|             username = StringField() | ||||
|             bar = GenericEmbeddedDocumentField(choices=[Bar,]) | ||||
|  | ||||
|         User.drop_collection() | ||||
|  | ||||
|         User(username='abc').save() | ||||
|         User.objects(username='abc').update( | ||||
|             set__bar=Bar(name='test'), upsert=True) | ||||
|  | ||||
|         user = User.objects(username='abc').first() | ||||
|         self.assertEqual(user.bar.name, "test") | ||||
|  | ||||
|     def test_reload_embedded_docs_instance(self): | ||||
|  | ||||
|         class SubDoc(EmbeddedDocument): | ||||
| @@ -4790,6 +4840,30 @@ class QuerySetTest(unittest.TestCase): | ||||
|         for obj in C.objects.no_sub_classes(): | ||||
|             self.assertEqual(obj.__class__, C) | ||||
|  | ||||
|     def test_query_generic_embedded_document(self): | ||||
|         """Ensure that querying sub field on generic_embedded_field works | ||||
|         """ | ||||
|         class A(EmbeddedDocument): | ||||
|             a_name = StringField() | ||||
|  | ||||
|         class B(EmbeddedDocument): | ||||
|             b_name = StringField() | ||||
|  | ||||
|         class Doc(Document): | ||||
|             document = GenericEmbeddedDocumentField(choices=(A, B)) | ||||
|  | ||||
|         Doc.drop_collection() | ||||
|         Doc(document=A(a_name='A doc')).save() | ||||
|         Doc(document=B(b_name='B doc')).save() | ||||
|  | ||||
|         # Using raw in filter working fine | ||||
|         self.assertEqual(Doc.objects( | ||||
|             __raw__={'document.a_name': 'A doc'}).count(), 1) | ||||
|         self.assertEqual(Doc.objects( | ||||
|             __raw__={'document.b_name': 'B doc'}).count(), 1) | ||||
|         self.assertEqual(Doc.objects(document__a_name='A doc').count(), 1) | ||||
|         self.assertEqual(Doc.objects(document__b_name='B doc').count(), 1) | ||||
|  | ||||
|     def test_query_reference_to_custom_pk_doc(self): | ||||
|  | ||||
|         class A(Document): | ||||
|   | ||||
| @@ -28,12 +28,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() | ||||
|  | ||||
| @@ -50,6 +54,20 @@ class TransformTest(unittest.TestCase): | ||||
|  | ||||
|         update = transform.update(DicDoc, pull__dictField__test=doc) | ||||
|         self.assertTrue(isinstance(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. | ||||
|   | ||||
							
								
								
									
										5
									
								
								tox.ini
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								tox.ini
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| [tox] | ||||
| envlist = {py27,py35,pypy,pypy3}-{mg27,mg28,mg30} | ||||
| envlist = {py27,py35,pypy,pypy3}-{mg27,mg28,mg35,mg3x} | ||||
|  | ||||
| [testenv] | ||||
| commands = | ||||
| @@ -8,6 +8,7 @@ deps = | ||||
|     nose | ||||
|     mg27: PyMongo<2.8 | ||||
|     mg28: PyMongo>=2.8,<2.9 | ||||
|     mg30: PyMongo>=3.0 | ||||
|     mg35: PyMongo==3.5 | ||||
|     mg3x: PyMongo>=3.0 | ||||
| setenv = | ||||
|     PYTHON_EGG_CACHE = {envdir}/python-eggs | ||||
|   | ||||
		Reference in New Issue
	
	Block a user