Model Inheritance¶
We can implement model inheritance at the collection level.
Scenario¶
Let's imagine we want to implement a course system.
The course can have multiple types of content like text, video, image, file, etc. But all share some common fields like course_id, created_at, etc. And content should be listed.
We can use this functionality relationally, but it will be expensive to query because we need multiple lookups.
In this scenario, we can implement Model Inheritance provided by MongoDB-ODM. (Note: Currently, only one level of inheritance is supported.)
Define Model¶
In this section, we will use multiple models to implement model inheritance.
Overview¶
First look at all models in a single block for better understanding.
# Code omitted above
class Course(Document):
title: str
class Content(Document):
course_id: ODMObjectId
title: str = Field(max_length=255)
class ODMConfig(Document.ODMConfig):
allow_inheritance = True
indexes = [
IndexModel([("course_id", ASCENDING)]),
]
class Text(Content):
text: str
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
class Video(Content):
video_path: str = Field(max_length=512)
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
# Code omitted below
Full file preview
import os
from mongodb_odm import (
ASCENDING,
Document,
Field,
IndexModel,
ODMObjectId,
apply_indexes,
connect,
)
class Course(Document):
title: str
class Content(Document):
course_id: ODMObjectId
title: str = Field(max_length=255)
class ODMConfig(Document.ODMConfig):
allow_inheritance = True
indexes = [
IndexModel([("course_id", ASCENDING)]),
]
class Text(Content):
text: str
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
class Video(Content):
video_path: str = Field(max_length=512)
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
def configuration():
connect(os.environ.get("MONGO_URL", "mongodb://localhost:27017/testdb"))
apply_indexes()
def create_data():
course = Course(title="MongoDB-ODM Tutorial").create()
Text(course_id=course.id, title="Introduction", text="Introduction Text").create()
Video(
course_id=course.id,
title="Environment Setup",
video_path="/media/video_path.mp4",
).create()
def retrieve_content():
for content in Content.find():
print(content)
print()
def retrieve_text():
for text in Text.find():
print(text)
print()
def retrieve_video():
for video in Video.find():
print(video)
print()
def main():
configuration()
create_data()
retrieve_content()
retrieve_text()
retrieve_video()
if __name__ == "__main__":
main()
We want to keep everything simple. First, we define the course, where courses only have one field: title.
Content¶
We define the Content model. We want to inherit the content model in the Text and Video models.
In the ODMConfig class for the Content model, we define allow_inheritance = True. To make a model inheritable, we need to set this field to True.
class Content(Document):
course_id: ODMObjectId
title: str = Field(max_length=255)
class ODMConfig(Document.ODMConfig):
allow_inheritance = True
indexes = [
IndexModel([("course_id", ASCENDING)]),
]
Text¶
After defining the Content model, we will define the Text model that inherits from the Content model to have all the functionality of Content.
Set allow_inheritance = False for the Text model. Otherwise, MongoDB-ODM will throw an error.
class Text(Content):
text: str
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
Video¶
Like the Text functionality and declaration structure, the Video will be the same.
class Video(Content):
video_path: str = Field(max_length=512)
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
Insert Data¶
Let's insert some data in the Course and Content collection.
First, we create a course document.
Then we create two Content documents: one Text content and one video content.
def create_data():
course = Course(title="MongoDB-ODM Tutorial").create()
Text(course_id=course.id, title="Introduction", text="Introduction Text").create()
Video(
course_id=course.id,
title="Environment Setup",
video_path="/media/video_path.mp4",
).create()
Full file preview
import os
from mongodb_odm import (
ASCENDING,
Document,
Field,
IndexModel,
ODMObjectId,
apply_indexes,
connect,
)
class Course(Document):
title: str
class Content(Document):
course_id: ODMObjectId
title: str = Field(max_length=255)
class ODMConfig(Document.ODMConfig):
allow_inheritance = True
indexes = [
IndexModel([("course_id", ASCENDING)]),
]
class Text(Content):
text: str
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
class Video(Content):
video_path: str = Field(max_length=512)
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
def configuration():
connect(os.environ.get("MONGO_URL", "mongodb://localhost:27017/testdb"))
apply_indexes()
def create_data():
course = Course(title="MongoDB-ODM Tutorial").create()
Text(course_id=course.id, title="Introduction", text="Introduction Text").create()
Video(
course_id=course.id,
title="Environment Setup",
video_path="/media/video_path.mp4",
).create()
def retrieve_content():
for content in Content.find():
print(content)
print()
def retrieve_text():
for text in Text.find():
print(text)
print()
def retrieve_video():
for video in Video.find():
print(video)
print()
def main():
configuration()
create_data()
retrieve_content()
retrieve_text()
retrieve_video()
if __name__ == "__main__":
main()
Impact in Database¶
For model inheritance, we do not create a separate collection for each model. Instead, we create a single collection for all models, and the collection name will be according to the parent class. We add an extra field _cls with each document to distinguish between the different models.
Tip
By default, we define the _cls field as an index field. We can remove the index for this field by defining index_inheritance_field = False in the parent ODMConfig.
class Content(Document):
...
class ODMConfig(Document.ODMConfig):
index_inheritance_field = False
Retrieve Collection¶
We can use all our retrieval methods to retrieve data for the inherited model. We will add an extra filter key {"_cls": '<Model Name>'} to filter out the targeted children, or no extra filter for parents.
Filter Content¶
To get all content, we can filter data with the parent class Content.
def retrieve_content():
for content in Content.find():
print(content)
print()
Full file preview
import os
from mongodb_odm import (
ASCENDING,
Document,
Field,
IndexModel,
ODMObjectId,
apply_indexes,
connect,
)
class Course(Document):
title: str
class Content(Document):
course_id: ODMObjectId
title: str = Field(max_length=255)
class ODMConfig(Document.ODMConfig):
allow_inheritance = True
indexes = [
IndexModel([("course_id", ASCENDING)]),
]
class Text(Content):
text: str
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
class Video(Content):
video_path: str = Field(max_length=512)
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
def configuration():
connect(os.environ.get("MONGO_URL", "mongodb://localhost:27017/testdb"))
apply_indexes()
def create_data():
course = Course(title="MongoDB-ODM Tutorial").create()
Text(course_id=course.id, title="Introduction", text="Introduction Text").create()
Video(
course_id=course.id,
title="Environment Setup",
video_path="/media/video_path.mp4",
).create()
def retrieve_content():
for content in Content.find():
print(content)
print()
def retrieve_text():
for text in Text.find():
print(text)
print()
def retrieve_video():
for video in Video.find():
print(video)
print()
def main():
configuration()
create_data()
retrieve_content()
retrieve_text()
retrieve_video()
if __name__ == "__main__":
main()
After running this function, the printed object should look like this.
Text(id=ObjectId('id'), course_id=ObjectId('id'), title='Introduction', text='Introduction Text', _id=ObjectId('id'))
Video(id=ObjectId('id'), course_id=ObjectId('id'), title='Environment Setup', video_path='/media/video_path.mp4', _id=ObjectId('id'))
Retrieve Text and Video¶
We can retrieve data using the Text and Video models.
def retrieve_text():
for text in Text.find():
print(text)
print()
def retrieve_video():
for video in Video.find():
print(video)
print()
Full file preview
import os
from mongodb_odm import (
ASCENDING,
Document,
Field,
IndexModel,
ODMObjectId,
apply_indexes,
connect,
)
class Course(Document):
title: str
class Content(Document):
course_id: ODMObjectId
title: str = Field(max_length=255)
class ODMConfig(Document.ODMConfig):
allow_inheritance = True
indexes = [
IndexModel([("course_id", ASCENDING)]),
]
class Text(Content):
text: str
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
class Video(Content):
video_path: str = Field(max_length=512)
class ODMConfig(Document.ODMConfig):
allow_inheritance = False
def configuration():
connect(os.environ.get("MONGO_URL", "mongodb://localhost:27017/testdb"))
apply_indexes()
def create_data():
course = Course(title="MongoDB-ODM Tutorial").create()
Text(course_id=course.id, title="Introduction", text="Introduction Text").create()
Video(
course_id=course.id,
title="Environment Setup",
video_path="/media/video_path.mp4",
).create()
def retrieve_content():
for content in Content.find():
print(content)
print()
def retrieve_text():
for text in Text.find():
print(text)
print()
def retrieve_video():
for video in Video.find():
print(video)
print()
def main():
configuration()
create_data()
retrieve_content()
retrieve_text()
retrieve_video()
if __name__ == "__main__":
main()
After executing the retrieve_text and retrieve_video functions the output should look like this.
Text(id=ObjectId('id'), course_id=ObjectId('id'), title='Introduction', text='Introduction Text', _id=ObjectId('id'))
Video(id=ObjectId('id'), course_id=ObjectId('id'), title='Environment Setup', video_path='/media/video_path.mp4', _id=ObjectId('id'))
find_one and get with inheritance¶
Like the find method, we can retrieve the child object from the parent using the find_one and get methods. If the retrieved object appears to be one of the children, then the object will have all of the properties of that child.