1========
2Tutorial
3========
4
5This tutorial introduces **MongoEngine** by means of example --- we will walk
6through how to create a simple **Tumblelog** application. A tumblelog is a
7blog that supports mixed media content, including text, images, links, video,
8audio, etc. For simplicity's sake, we'll stick to text, image, and link
9entries. As the purpose of this tutorial is to introduce MongoEngine, we'll
10focus on the data-modelling side of the application, leaving out a user
11interface.
12
13Getting started
14===============
15
16Before we start, make sure that a copy of MongoDB is running in an accessible
17location --- running it locally will be easier, but if that is not an option
18then it may be run on a remote server. If you haven't installed MongoEngine,
19simply use pip to install it like so::
20
21    $ python -m pip install mongoengine
22
23Before we can start using MongoEngine, we need to tell it how to connect to our
24instance of :program:`mongod`. For this we use the :func:`~mongoengine.connect`
25function. If running locally, the only argument we need to provide is the name
26of the MongoDB database to use::
27
28    from mongoengine import *
29
30    connect('tumblelog')
31
32There are lots of options for connecting to MongoDB, for more information about
33them see the :ref:`guide-connecting` guide.
34
35Defining our documents
36======================
37
38MongoDB is *schemaless*, which means that no schema is enforced by the database
39--- we may add and remove fields however we want and MongoDB won't complain.
40This makes life a lot easier in many regards, especially when there is a change
41to the data model. However, defining schemas for our documents can help to iron
42out bugs involving incorrect types or missing fields, and also allow us to
43define utility methods on our documents in the same way that traditional
44:abbr:`ORMs (Object-Relational Mappers)` do.
45
46In our Tumblelog application we need to store several different types of
47information. We will need to have a collection of **users**, so that we may
48link posts to an individual. We also need to store our different types of
49**posts** (eg: text, image and link) in the database. To aid navigation of our
50Tumblelog, posts may have **tags** associated with them, so that the list of
51posts shown to the user may be limited to posts that have been assigned a
52specific tag. Finally, it would be nice if **comments** could be added to
53posts. We'll start with **users**, as the other document models are slightly
54more involved.
55
56Users
57-----
58
59Just as if we were using a relational database with an ORM, we need to define
60which fields a :class:`User` may have, and what types of data they might store::
61
62    class User(Document):
63        email = StringField(required=True)
64        first_name = StringField(max_length=50)
65        last_name = StringField(max_length=50)
66
67This looks similar to how the structure of a table would be defined in a
68regular ORM. The key difference is that this schema will never be passed on to
69MongoDB --- this will only be enforced at the application level, making future
70changes easy to manage. Also, the User documents will be stored in a
71MongoDB *collection* rather than a table.
72
73Posts, Comments and Tags
74------------------------
75
76Now we'll think about how to store the rest of the information. If we were
77using a relational database, we would most likely have a table of **posts**, a
78table of **comments** and a table of **tags**.  To associate the comments with
79individual posts, we would put a column in the comments table that contained a
80foreign key to the posts table. We'd also need a link table to provide the
81many-to-many relationship between posts and tags. Then we'd need to address the
82problem of storing the specialised post-types (text, image and link). There are
83several ways we can achieve this, but each of them have their problems --- none
84of them stand out as particularly intuitive solutions.
85
86Posts
87^^^^^
88
89Happily MongoDB *isn't* a relational database, so we're not going to do it that
90way. As it turns out, we can use MongoDB's schemaless nature to provide us with
91a much nicer solution. We will store all of the posts in *one collection* and
92each post type will only store the fields it needs. If we later want to add
93video posts, we don't have to modify the collection at all, we just *start
94using* the new fields we need to support video posts. This fits with the
95Object-Oriented principle of *inheritance* nicely. We can think of
96:class:`Post` as a base class, and :class:`TextPost`, :class:`ImagePost` and
97:class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports
98this kind of modeling out of the box --- all you need do is turn on inheritance
99by setting :attr:`allow_inheritance` to True in the :attr:`meta`::
100
101    class Post(Document):
102        title = StringField(max_length=120, required=True)
103        author = ReferenceField(User)
104
105        meta = {'allow_inheritance': True}
106
107    class TextPost(Post):
108        content = StringField()
109
110    class ImagePost(Post):
111        image_path = StringField()
112
113    class LinkPost(Post):
114        link_url = StringField()
115
116We are storing a reference to the author of the posts using a
117:class:`~mongoengine.fields.ReferenceField` object. These are similar to foreign key
118fields in traditional ORMs, and are automatically translated into references
119when they are saved, and dereferenced when they are loaded.
120
121Tags
122^^^^
123
124Now that we have our Post models figured out, how will we attach tags to them?
125MongoDB allows us to store lists of items natively, so rather than having a
126link table, we can just store a list of tags in each post. So, for both
127efficiency and simplicity's sake, we'll store the tags as strings directly
128within the post, rather than storing references to tags in a separate
129collection. Especially as tags are generally very short (often even shorter
130than a document's id), this denormalization won't impact the size of the
131database very strongly. Let's take a look at the code of our modified
132:class:`Post` class::
133
134    class Post(Document):
135        title = StringField(max_length=120, required=True)
136        author = ReferenceField(User)
137        tags = ListField(StringField(max_length=30))
138
139The :class:`~mongoengine.fields.ListField` object that is used to define a Post's tags
140takes a field object as its first argument --- this means that you can have
141lists of any type of field (including lists).
142
143.. note:: We don't need to modify the specialized post types as they all
144    inherit from :class:`Post`.
145
146Comments
147^^^^^^^^
148
149A comment is typically associated with *one* post. In a relational database, to
150display a post with its comments, we would have to retrieve the post from the
151database and then query the database again for the comments associated with the
152post. This works, but there is no real reason to be storing the comments
153separately from their associated posts, other than to work around the
154relational model. Using MongoDB we can store the comments as a list of
155*embedded documents* directly on a post document. An embedded document should
156be treated no differently than a regular document; it just doesn't have its own
157collection in the database. Using MongoEngine, we can define the structure of
158embedded documents, along with utility methods, in exactly the same way we do
159with regular documents::
160
161    class Comment(EmbeddedDocument):
162        content = StringField()
163        name = StringField(max_length=120)
164
165We can then store a list of comment documents in our post document::
166
167    class Post(Document):
168        title = StringField(max_length=120, required=True)
169        author = ReferenceField(User)
170        tags = ListField(StringField(max_length=30))
171        comments = ListField(EmbeddedDocumentField(Comment))
172
173Handling deletions of references
174^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
175
176The :class:`~mongoengine.fields.ReferenceField` object takes a keyword
177`reverse_delete_rule` for handling deletion rules if the reference is deleted.
178To delete all the posts if a user is deleted set the rule::
179
180    class Post(Document):
181        title = StringField(max_length=120, required=True)
182        author = ReferenceField(User, reverse_delete_rule=CASCADE)
183        tags = ListField(StringField(max_length=30))
184        comments = ListField(EmbeddedDocumentField(Comment))
185
186See :class:`~mongoengine.fields.ReferenceField` for more information.
187
188.. note::
189    MapFields and DictFields currently don't support automatic handling of
190    deleted references
191
192
193Adding data to our Tumblelog
194============================
195Now that we've defined how our documents will be structured, let's start adding
196some documents to the database. Firstly, we'll need to create a :class:`User`
197object::
198
199    ross = User(email='ross@example.com', first_name='Ross', last_name='Lawley').save()
200
201.. note::
202    We could have also defined our user using attribute syntax::
203
204        ross = User(email='ross@example.com')
205        ross.first_name = 'Ross'
206        ross.last_name = 'Lawley'
207        ross.save()
208
209Assign another user to a variable called ``john``, just like we did above with
210``ross``.
211
212Now that we've got our users in the database, let's add a couple of posts::
213
214    post1 = TextPost(title='Fun with MongoEngine', author=john)
215    post1.content = 'Took a look at MongoEngine today, looks pretty cool.'
216    post1.tags = ['mongodb', 'mongoengine']
217    post1.save()
218
219    post2 = LinkPost(title='MongoEngine Documentation', author=ross)
220    post2.link_url = 'http://docs.mongoengine.com/'
221    post2.tags = ['mongoengine']
222    post2.save()
223
224.. note:: If you change a field on an object that has already been saved and
225    then call :meth:`save` again, the document will be updated.
226
227Accessing our data
228==================
229
230So now we've got a couple of posts in our database, how do we display them?
231Each document class (i.e. any class that inherits either directly or indirectly
232from :class:`~mongoengine.Document`) has an :attr:`objects` attribute, which is
233used to access the documents in the database collection associated with that
234class. So let's see how we can get our posts' titles::
235
236    for post in Post.objects:
237        print(post.title)
238
239Retrieving type-specific information
240------------------------------------
241
242This will print the titles of our posts, one on each line. But what if we want
243to access the type-specific data (link_url, content, etc.)? One way is simply
244to use the :attr:`objects` attribute of a subclass of :class:`Post`::
245
246    for post in TextPost.objects:
247        print(post.content)
248
249Using TextPost's :attr:`objects` attribute only returns documents that were
250created using :class:`TextPost`. Actually, there is a more general rule here:
251the :attr:`objects` attribute of any subclass of :class:`~mongoengine.Document`
252only looks for documents that were created using that subclass or one of its
253subclasses.
254
255So how would we display all of our posts, showing only the information that
256corresponds to each post's specific type? There is a better way than just using
257each of the subclasses individually. When we used :class:`Post`'s
258:attr:`objects` attribute earlier, the objects being returned weren't actually
259instances of :class:`Post` --- they were instances of the subclass of
260:class:`Post` that matches the post's type. Let's look at how this works in
261practice::
262
263    for post in Post.objects:
264        print(post.title)
265        print('=' * len(post.title))
266
267        if isinstance(post, TextPost):
268            print(post.content)
269
270        if isinstance(post, LinkPost):
271            print('Link: {}'.format(post.link_url))
272
273This would print the title of each post, followed by the content if it was a
274text post, and "Link: <url>" if it was a link post.
275
276Searching our posts by tag
277--------------------------
278
279The :attr:`objects` attribute of a :class:`~mongoengine.Document` is actually a
280:class:`~mongoengine.queryset.QuerySet` object. This lazily queries the
281database only when you need the data. It may also be filtered to narrow down
282your query.  Let's adjust our query so that only posts with the tag "mongodb"
283are returned::
284
285    for post in Post.objects(tags='mongodb'):
286        print(post.title)
287
288There are also methods available on :class:`~mongoengine.queryset.QuerySet`
289objects that allow different results to be returned, for example, calling
290:meth:`first` on the :attr:`objects` attribute will return a single document,
291the first matched by the query you provide. Aggregation functions may also be
292used on :class:`~mongoengine.queryset.QuerySet` objects::
293
294    num_posts = Post.objects(tags='mongodb').count()
295    print('Found {} posts with tag "mongodb"'.format(num_posts))
296
297Learning more about MongoEngine
298-------------------------------
299
300If you got this far you've made a great start, so well done! The next step on
301your MongoEngine journey is the `full user guide <guide/index.html>`_, where
302you can learn in-depth about how to use MongoEngine and MongoDB.
303