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