diff --git a/docs/changelog.rst b/docs/changelog.rst index ff2dd38c1..65d560442 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -24,6 +24,7 @@ Development - BugFix - Calling .clear on a ListField wasn't being marked as changed (and flushed to db upon .save()) #2858 - Improve error message in case a document assigned to a ReferenceField wasn't saved yet #1955 - BugFix - Take `where()` into account when using `.modify()`, as in MyDocument.objects().where("this[field] >= this[otherfield]").modify(field='new') #2044 +- fix: exception on null embedded scalar values Changes in 0.29.0 ================= diff --git a/mongoengine/queryset/base.py b/mongoengine/queryset/base.py index 2db97ddb7..973912d16 100644 --- a/mongoengine/queryset/base.py +++ b/mongoengine/queryset/base.py @@ -2036,6 +2036,8 @@ def lookup(obj, name): chunks = name.split("__") for chunk in chunks: obj = getattr(obj, chunk) + if obj is None: + break return obj data = [lookup(doc, n) for n in self._scalar] diff --git a/tests/queryset/test_queryset.py b/tests/queryset/test_queryset.py index 8386249f2..eed4d660d 100644 --- a/tests/queryset/test_queryset.py +++ b/tests/queryset/test_queryset.py @@ -21,6 +21,7 @@ from mongoengine.queryset import ( DoesNotExist, MultipleObjectsReturned, + Q, QuerySet, QuerySetManager, queryset_manager, @@ -4674,6 +4675,35 @@ class Person(Document): ("Gabriel Falcao", 23, "New York"), ] + def test_scalar_embedded_null_parents(self): + """Test a multi-scalar query on embedded fields raises an exception when the parent field is null.""" + + class EmbeddedModelA(EmbeddedDocument): + designator = StringField() + + class Container(Document): + source = EmbeddedDocumentField(EmbeddedModelA) + target = EmbeddedDocumentField(EmbeddedModelA, null=True) + + # Create one with both values + Container( + source=EmbeddedModelA(designator="value1"), + target=EmbeddedModelA(designator="value2"), + ).save() + + # Create one with a null target, but the source value will match the query + Container( + source=EmbeddedModelA(designator="value1"), + target=None, + ).save() + + queryset = Container.objects.filter( + Q(source__designator="value1") | Q(target__designator="value2") + ).values_list("source__designator", "target__designator") + # This should not raise an AttributeError on NoneType for the second Container's target__designator + values = list(queryset) + assert values == [("value1", "value2"), ("value1", None)] + def test_scalar_decimal(self): from decimal import Decimal