This tutorial introduces MongoEngine by means of example — we will walk through how to create a simple Tumblelog application. A Tumblelog is a type of blog where posts are not constrained to being conventional text-based posts. As well as text-based entries, users may post images, links, videos, etc. For simplicity’s sake, we’ll stick to text, image and link entries in our application. As the purpose of this tutorial is to introduce MongoEngine, we’ll focus on the data-modelling side of the application, leaving out a user interface.
Before we start, make sure that a copy of MongoDB is running in an accessible location — running it locally will be easier, but if that is not an option then it may be run on a remote server. If you haven’t installed mongoengine, simply use pip to install it like so:
$ pip install mongoengine
Before we can start using MongoEngine, we need to tell it how to connect to our
instance of mongod. For this we use the
function. If running locally the only argument we need to provide is the name
of the MongoDB database to use:
from mongoengine import * connect('tumblelog')
There are lots of options for connecting to MongoDB, for more information about them see the Connecting to MongoDB guide.
MongoDB is schemaless, which means that no schema is enforced by the database — we may add and remove fields however we want and MongoDB won’t complain. This makes life a lot easier in many regards, especially when there is a change to the data model. However, defining schemata for our documents can help to iron out bugs involving incorrect types or missing fields, and also allow us to define utility methods on our documents in the same way that traditional ORMs do.
In our Tumblelog application we need to store several different types of information. We will need to have a collection of users, so that we may link posts to an individual. We also need to store our different types of posts (eg: text, image and link) in the database. To aid navigation of our Tumblelog, posts may have tags associated with them, so that the list of posts shown to the user may be limited to posts that have been assigned a specific tag. Finally, it would be nice if comments could be added to posts. We’ll start with users, as the other document models are slightly more involved.
Just as if we were using a relational database with an ORM, we need to define
which fields a
User may have, and what types of data they might store:
class User(Document): email = StringField(required=True) first_name = StringField(max_length=50) last_name = StringField(max_length=50)
This looks similar to how the structure of a table would be defined in a regular ORM. The key difference is that this schema will never be passed on to MongoDB — this will only be enforced at the application level, making future changes easy to manage. Also, the User documents will be stored in a MongoDB collection rather than a table.
Now that we’ve defined how our documents will be structured, let’s start adding
some documents to the database. Firstly, we’ll need to create a
ross = User(firstname.lastname@example.org', first_name='Ross', last_name='Lawley').save()
We could have also defined our user using attribute syntax:
ross = User(email@example.com') ross.first_name = 'Ross' ross.last_name = 'Lawley' ross.save()
Now that we’ve got our user in the database, let’s add a couple of posts:
post1 = TextPost(title='Fun with MongoEngine', author=john) post1.content = 'Took a look at MongoEngine today, looks pretty cool.' post1.tags = ['mongodb', 'mongoengine'] post1.save() post2 = LinkPost(title='MongoEngine Documentation', author=ross) post2.link_url = 'http://docs.mongoengine.com/' post2.tags = ['mongoengine'] post2.save()
If you change a field on a object that has already been saved, then
save() again, the document will be updated.
So now we’ve got a couple of posts in our database, how do we display them?
Each document class (i.e. any class that inherits either directly or indirectly
Document) has an
objects attribute, which is
used to access the documents in the database collection associated with that
class. So let’s see how we can get our posts’ titles:
for post in Post.objects: print post.title
This will print the titles of our posts, one on each line. But What if we want
to access the type-specific data (link_url, content, etc.)? One way is simply
to use the
objects attribute of a subclass of
for post in TextPost.objects: print post.content
objects attribute only returns documents that were
TextPost. Actually, there is a more general rule here:
objects attribute of any subclass of
only looks for documents that were created using that subclass or one of its
So how would we display all of our posts, showing only the information that
corresponds to each post’s specific type? There is a better way than just using
each of the subclasses individually. When we used
objects attribute earlier, the objects being returned weren’t actually
Post — they were instances of the subclass of
Post that matches the post’s type. Let’s look at how this works in
for post in Post.objects: print post.title print '=' * len(post.title) if isinstance(post, TextPost): print post.content if isinstance(post, LinkPost): print 'Link:', post.link_url print
This would print the title of each post, followed by the content if it was a text post, and “Link: <url>” if it was a link post.
objects attribute of a
Document is actually a
QuerySet object. This lazily queries the
database only when you need the data. It may also be filtered to narrow down
your query. Let’s adjust our query so that only posts with the tag “mongodb”
for post in Post.objects(tags='mongodb'): print post.title
There are also methods available on
objects that allow different results to be returned, for example, calling
first() on the
objects attribute will return a single document,
the first matched by the query you provide. Aggregation functions may also be
num_posts = Post.objects(tags='mongodb').count() print 'Found %d posts with tag "mongodb"' % num_posts