diff --git a/mongoengine/base/document.py b/mongoengine/base/document.py index 7a1de50e3..c0735eac1 100644 --- a/mongoengine/base/document.py +++ b/mongoengine/base/document.py @@ -1150,20 +1150,21 @@ def _lookup_field(cls, parts): if hasattr(getattr(field, "field", None), "lookup_member"): new_field = field.field.lookup_member(field_name) - # If the parent field is a DynamicField or if it's part of - # a DynamicDocument, mark current field as a DynamicField + # If the parent field is a DynamicField, mark current field as a DynamicField # with db_name equal to the field name. - elif cls._dynamic and ( - isinstance(field, DynamicField) - or getattr(getattr(field, "document_type", None), "_dynamic", None) - ): + elif isinstance(field, DynamicField): new_field = DynamicField(db_field=field_name) - # Else, try to use the parent field's lookup_member method + # Try to use the parent field's lookup_member method # to find the subfield. elif hasattr(field, "lookup_member"): new_field = field.lookup_member(field_name) + # Else, if the current field it's part of a DynamicDocument, mark current field + # as a DynamicField with db_name equal to the field name. + elif cls._dynamic and getattr(getattr(field, "document_type", None), "_dynamic", None): + new_field = DynamicField(db_field=field_name) + # Raise a LookUpError if all the other conditions failed. else: raise LookUpError( diff --git a/mongoengine/fields.py b/mongoengine/fields.py index a2ccc7aea..923ed97cb 100644 --- a/mongoengine/fields.py +++ b/mongoengine/fields.py @@ -38,6 +38,7 @@ from mongoengine.errors import ( DoesNotExist, InvalidQueryError, + LookUpError, ValidationError, ) from mongoengine.queryset import DO_NOTHING @@ -794,9 +795,12 @@ def validate(self, value, clean=True): def lookup_member(self, member_name): doc_and_subclasses = [self.document_type] + self.document_type.__subclasses__() for doc_type in doc_and_subclasses: - field = doc_type._fields.get(member_name) - if field: - return field + try: + field = doc_type._lookup_field(member_name) + if field: + return field[0] + except LookUpError: + return None def prepare_query_value(self, op, value): if value is not None and not isinstance(value, self.document_type): @@ -854,9 +858,13 @@ def lookup_member(self, member_name): for document_choice in document_choices: doc_and_subclasses = [document_choice] + document_choice.__subclasses__() for doc_type in doc_and_subclasses: - field = doc_type._fields.get(member_name) - if field: - return field + try: + field = doc_type._lookup_field(member_name) + if field: + return field[0] + except LookUpError: + # Try with the next doc_type + pass def to_mongo(self, document, use_db_field=True, fields=None): if document is None: diff --git a/tests/document/test_dynamic.py b/tests/document/test_dynamic.py index 170b2ea3d..f5a2631d5 100644 --- a/tests/document/test_dynamic.py +++ b/tests/document/test_dynamic.py @@ -223,6 +223,60 @@ class Doc(DynamicDocument): with pytest.raises(ValidationError): doc.validate() + def test_complex_embedded_document_query(self): + """Ensure embedded dynamic documents can be queried""" + + class EmbeddedDoc1(DynamicEmbeddedDocument): + email = StringField() + + class EmbeddedDoc2(DynamicEmbeddedDocument): + name = StringField() + + class Doc(DynamicDocument): + embedded = EmbeddedDocumentField(EmbeddedDoc1) + generic_embedded = GenericEmbeddedDocumentField(choices=(EmbeddedDoc1, EmbeddedDoc2)) + + Doc.drop_collection() + + doc1 = Doc(content='doc1') + doc1.embedded = EmbeddedDoc1(email='doc1@te.st') + doc1.embedded.dynamic_field = 'dynamic value 1' + doc1.generic_embedded = EmbeddedDoc1(email='doc1@te.st') + doc1.generic_embedded.another_dynamic_field = 'generic dynamic value 1' + doc1.save() + + doc2 = Doc(content='doc2') + doc2.embedded = EmbeddedDoc1(email='doc2@te.st') + doc2.embedded.dynamic_field = 'dynamic value 2' + doc2.generic_embedded = EmbeddedDoc2(name='doc2 name') + doc2.generic_embedded.another_dynamic_field = 'generic dynamic value 2' + doc2.save() + + assert Doc.objects(embedded__dynamic_field='dynamic value 1').first().id == doc1.id + assert Doc.objects(embedded__dynamic_field='dynamic value 2').first().id == doc2.id + + assert Doc.objects(generic_embedded__another_dynamic_field='generic dynamic value 1').first().id == doc1.id + assert Doc.objects(generic_embedded__another_dynamic_field='generic dynamic value 2').first().id == doc2.id + + def test_complex_embedded_document_with_aliased_field_query(self): + class EmbeddedDoc(DynamicEmbeddedDocument): + first_name = StringField(db_field='firstName') + + class Doc(DynamicDocument): + embedded_doc = EmbeddedDocumentField(EmbeddedDoc, db_field='embeddedDoc') + + Doc.drop_collection() + + doc1 = Doc(content='doc1') + doc1.embedded_doc = EmbeddedDoc(first_name='Alice') + doc1.save() + + doc2 = Doc(content='doc1') + doc2.embedded_doc = EmbeddedDoc(first_name='John') + doc2.save() + + assert Doc.objects(embedded_doc__first_name='John').first().id == doc2.id + def test_inheritance(self): """Ensure that dynamic document plays nice with inheritance"""