I have a database model:
class Wallet(EmbeddedDocument):
is_active = BooleanField(default=True)
balance = IntField(default=0, min_value=0)
class User(Document):
email = EmailField(required=True, unique=True)
mobile = StringField(required=True, unique=True, min_length=10, max_length=10)
first_name = StringField(required=True)
last_name = StringField()
default_address_id = ObjectIdField()
wallet = EmbeddedDocumentField(Wallet, default=Wallet())
I want to query all the Users who have a wallet balance of more than 10,000. This is the query I wrote
User.objects.filter(wallet__balance__gt=0)
However it returns an empty list, whereas, the query
User.objects.filter(wallet__balance=11500) returns multiple objects.
[<User: User object>, <User: User object>, <User: User object>, <User: User object>]
This does not work for less than queries as well.
Is there something which I am doing wrong?
First off, there's something _insanely dangerous_ in your example that you might not realize.
wallet = EmbeddedDocumentField(Wallet, default=Wallet())
This does not do what you think it does. Very specifically, every new instance of User constructed will _share a single Wallet instance_.
from mongoengine import Document, EmbeddedDocument, StringField, EmbeddedDocumentField, IntField
class Foo(EmbeddedDocument):
baz = StringField()
amount = IntField(default=0)
class Bar(Document):
foo = EmbeddedDocumentField(Foo, default=Foo())
a = Bar()
a.foo.baz = "Hi."
b = Bar()
print(b.foo.baz)
# Hi.
a.foo.amount = 27
print(b.foo.amount)
# 27
This is somewhat separate from the overall issue, of course, but a common pitfall for users new to Python. This also happens if you instantiate something as a default argument to a function, take the following example:
def appendto(value, l=[]):
l.append(value)
return l
print(appendto(27))
# [27]
print(appendto(42))
# [27, 42]
MongoEngine allows you to define a callback to populate the default. Since classes are themselves callable (like functions) and return a value (an instance), just drop the () in your default definition and a new instance will be freshly created for each new instance of the containing document class.
I'm testing on 0.10.5 and it seems to work, however:
a.save()
Bar.objects(foo__amount=27) # [<Bar: Bar object>]
Bar.objects(foo__amount__gt=42) # []
Bar.objects(foo__amount__lt=42) # [<Bar: Bar object>]
Bar.objects(foo__amount__gt=0) # [<Bar: Bar object>]
Updating to the latest (0.10.6) also seems to work.
@amcgregor Thanks for the detailed explanation.
Yes it is working, I probably was making an incorrect query. I even wrote a pymongo implementation to handle this :(
Thanks for the heads-up on the default value , I am changing that in the models :)
Most helpful comment
First off, there's something _insanely dangerous_ in your example that you might not realize.
This does not do what you think it does. Very specifically, every new instance of User constructed will _share a single Wallet instance_.
This is somewhat separate from the overall issue, of course, but a common pitfall for users new to Python. This also happens if you instantiate something as a default argument to a function, take the following example:
MongoEngine allows you to define a callback to populate the default. Since classes are themselves callable (like functions) and return a value (an instance), just drop the
()in your default definition and a new instance will be freshly created for each new instance of the containing document class.I'm testing on
0.10.5and it seems to work, however:Updating to the latest (
0.10.6) also seems to work.