Subclassing document in separate collections in Mongoengine
Question:
I have a collection in Mongoengine with long list of fields which contains some documents:
class Component(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
Now I want another collection similar to the first collection with some extra stuffs. So I used allow_inheritance
in the first collection for subclassing:
class Component(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
meta = {'allow_inheritance': True}
class ChildComponent(Component):
extra_stuff = StringField()
But as the documents mentions, this way all the data will save in the same collection.
So I tried adding abstract
to meta in the parent document, so I can have two separate collections.
class Component(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
meta = {'allow_inheritance': True, 'abstract': True}
But now when I try to get the documents from Component
using Component.objects().all()
I get this error:
AttributeError: type object ‘Component’ has no attribute ‘objects’
Should I create a base document and abstract my two collections from it or there’s simpler way to solve this issue?
UPDATE
Even if I create a base class and inherit both classes from it:
class BaseComponent(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
meta = {'allow_inheritance': True, 'abstract': True}
class Component(BaseComponent):
pass
class ChildComponent(BaseComponent):
extra_stuff = StringField()
When I try Component.objects.all()
, the result will be and empty list.
UPDATE 2
If I add a component
with .save()
I can get that one with Component.objects.all()
. But what about the rest of the documents which are already saved in that collection? How can I restore them?
Answers:
Since I got no answer, I’ve found a work around for it. When using abstract
in meta, the child collections get an extra field called _cls
with the collection name as value. So I used pymongo to update existing documents with a _cls
field and value equals to the collection name like this:
db["component"].update_many({}, {"$set": {"_cls": "Component"}}, upsert=False, array_filters=None)
After doing this, now I can get the documents using mongoengine Component.objects().all()
query.
Just use 'abstract': True
and remove 'allow_inheritance': True
.
For more details see this.
I had very same issue, the Vahid Panahi answer is ok, i’ve tried it. Full example here with optional collection names:
from mongoengine import *
connect('test')
class BaseTest(Document):
_id = ObjectIdField()
name = StringField()
meta = {
'abstract': True,
'allow_inheritance': True
}
class Test(BaseTest):
pass
# meta = {
# 'collection': 'Test',
# }
class TestExtended(BaseTest):
text = StringField()
# meta = {
# 'collection': 'TestExtended'
# }
model1 = Test()
model1.name = 'foo'
model1.save()
model2 = TestExtended()
model2.name = 'bar'
model2.text = 'foo bar story'
model2.save()
for model in Test.objects.all():
print(f"Test: {model.name}")
for model in TestExtended.objects.all():
print(f"TestExtended: {model.name}")
Will print:
Test: foo
TestExtended: bar
And in fact models are stored in different collections.
I have a collection in Mongoengine with long list of fields which contains some documents:
class Component(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
Now I want another collection similar to the first collection with some extra stuffs. So I used allow_inheritance
in the first collection for subclassing:
class Component(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
meta = {'allow_inheritance': True}
class ChildComponent(Component):
extra_stuff = StringField()
But as the documents mentions, this way all the data will save in the same collection.
So I tried adding abstract
to meta in the parent document, so I can have two separate collections.
class Component(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
meta = {'allow_inheritance': True, 'abstract': True}
But now when I try to get the documents from Component
using Component.objects().all()
I get this error:
AttributeError: type object ‘Component’ has no attribute ‘objects’
Should I create a base document and abstract my two collections from it or there’s simpler way to solve this issue?
UPDATE
Even if I create a base class and inherit both classes from it:
class BaseComponent(Document):
name = StringField(unique=True)
title = StringField(required=True)
description = StringField(default='')
# And so on...
meta = {'allow_inheritance': True, 'abstract': True}
class Component(BaseComponent):
pass
class ChildComponent(BaseComponent):
extra_stuff = StringField()
When I try Component.objects.all()
, the result will be and empty list.
UPDATE 2
If I add a component
with .save()
I can get that one with Component.objects.all()
. But what about the rest of the documents which are already saved in that collection? How can I restore them?
Since I got no answer, I’ve found a work around for it. When using abstract
in meta, the child collections get an extra field called _cls
with the collection name as value. So I used pymongo to update existing documents with a _cls
field and value equals to the collection name like this:
db["component"].update_many({}, {"$set": {"_cls": "Component"}}, upsert=False, array_filters=None)
After doing this, now I can get the documents using mongoengine Component.objects().all()
query.
Just use 'abstract': True
and remove 'allow_inheritance': True
.
For more details see this.
I had very same issue, the Vahid Panahi answer is ok, i’ve tried it. Full example here with optional collection names:
from mongoengine import *
connect('test')
class BaseTest(Document):
_id = ObjectIdField()
name = StringField()
meta = {
'abstract': True,
'allow_inheritance': True
}
class Test(BaseTest):
pass
# meta = {
# 'collection': 'Test',
# }
class TestExtended(BaseTest):
text = StringField()
# meta = {
# 'collection': 'TestExtended'
# }
model1 = Test()
model1.name = 'foo'
model1.save()
model2 = TestExtended()
model2.name = 'bar'
model2.text = 'foo bar story'
model2.save()
for model in Test.objects.all():
print(f"Test: {model.name}")
for model in TestExtended.objects.all():
print(f"TestExtended: {model.name}")
Will print:
Test: foo
TestExtended: bar
And in fact models are stored in different collections.