Skillversitas

Defining Documents

A MongoEngine document is a Python class: each declared field becomes a document key, each instance becomes a MongoDB document, and the class itself defines validation, defaults, and which collection it lives in. This chapter is the core pattern of the library - defining a document, instantiating it, and understanding exactly what gets sent to the database when you call save().

The Basic Document

from mongoengine import Document, StringField, IntField, DateTimeField
import datetime

class Article(Document):
    title = StringField(required=True, max_length=300)
    body = StringField()
    views = IntField(default=0)
    published_at = DateTimeField()

    meta = {
        "collection": "articles",
        "indexes": ["title", "published_at"],
    }

    def __str__(self):
        return f"Article: {self.title}"

Four things happen by convention. First, the collection name defaults to the lowercased class name - "Article" becomes "articles" - and meta["collection"] overrides it. Second, fields with no value are simply absent from the stored document (MongoDB stores only what is set). Third, required=True and the other constraints are checked on save(). Fourth, every document automatically gets an _id field, an ObjectId by default, exposed as article.id.

Instantiating and Saving

article = Article(title="First post", body="Hello world")
print(article.id)          # an ObjectId is assigned at instantiation
print(article.views)       # 0 - the default was applied

article.save()             # validates, then upserts into the collection

# a modified document saves the same way
article.views = 1
article.save()             # updates the existing document (same _id)

Calling save() on a document with an existing _id performs an update, not an insert - the _id is what makes a document "the same row". Passing insert=True forces an insert (and fails if the _id already exists), which is how you catch accidental double-creation.

What MongoDB Actually Stores

article.save()
raw = Article.objects.with_id(article.id).first().to_mongo()
print(raw)   # {'_id': ObjectId(...), 'title': 'First post',
             #  'body': 'Hello world', 'views': 0}

to_mongo() is the audit view: it shows the exact BSON the driver sends, which is how you verify that a field's type, name, and presence match what the rest of the system (other services, MongoDB queries, exports) expects. Two habits follow: keep field names in stable snake_case, and use to_mongo() in tests to pin the wire format of your models.

The Useful Meta Options

OptionEffect
collectiontarget collection name (default: lowercase class name)
dbnamed connection to use (see the connecting chapter)
indexesindex specs created by ensure_indexes()
allow_inheritanceenables subclass documents (inheritance chapter)
orderingdefault queryset ordering
strictreject undeclared fields on save

strict is the production-relevant one: with strict = True, assigning a key the class does not declare raises instead of silently storing an unmodeled field - a cheap guarantee that the Python model and the stored documents stay in sync.

Summary

Define a model as a Document subclass with typed fields and a meta block; instantiate with keyword arguments; and call save(), which validates and upserts by _id. Use to_mongo() to see exactly what the driver stores, set strict = True in production so the model is the schema, and remember that the _id is the identity everything else in the tutorial - updates, references, lookups - hangs on.