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?

Asked By: Alex Jolig

||

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.

Answered By: Alex Jolig

Just use 'abstract': True and remove 'allow_inheritance': True.
For more details see this.

Answered By: Vahid Panahi

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.

Answered By: PeterM
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.