Updated tutorial, added tutorial link to readme

This commit is contained in:
Harry Marr 2009-12-16 18:47:53 +00:00
parent 1529fd901d
commit c3ca3bd97c
2 changed files with 47 additions and 47 deletions

View File

@ -2,5 +2,7 @@ MongoEngine
=========== ===========
MongoEngine is an ORM-like layer on top of PyMongo. MongoEngine is an ORM-like layer on top of PyMongo.
Tutorial available at http://hmarr.com/mongoengine/
**Warning:** this software is still in development and should *not* be used **Warning:** this software is still in development and should *not* be used
in production. in production.

View File

@ -11,9 +11,9 @@ interface.
Connecting to MongoDB Connecting to MongoDB
--------------------- ---------------------
Before we start, you should make sure that you have a copy of MongoDB running Before we start, make sure that a copy of MongoDB is running in an accessible
in an accessible location --- running it locally will be easier, but if that is location --- running it locally will be easier, but if that is not an option
not an option then it may be run on a remote server. then it may be run on a remote server.
Before we can start using MongoEngine, we need to tell it how to connect to our Before we can start using MongoEngine, we need to tell it how to connect to our
instance of **mongod**. For this we use the :func:`mongoengine.connect` instance of **mongod**. For this we use the :func:`mongoengine.connect`
@ -25,8 +25,8 @@ database to use::
connect('tumblelog') connect('tumblelog')
This will connect to a mongod instance running locally on the default port. To This will connect to a mongod instance running locally on the default port. To
connect to a mongod instance running elsewhere, we may specify the host and connect to a mongod instance running elsewhere, specify the host and port
port explicitly:: explicitly::
connect('tumblelog', host='192.168.1.35', port=12345) connect('tumblelog', host='192.168.1.35', port=12345)
@ -34,20 +34,20 @@ Defining our documents
---------------------- ----------------------
MongoDB is *schemaless*, which means that no schema is enforced by the database 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. --- 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 it comes to This makes life a lot easier in many regards, especially when there is a change
migrations. However, defining schemata for our documents can help to iron out to the data model. However, defining schemata for our documents can help to
bugs involving incorrect types or missing fields, and also allow us to define iron out bugs involving incorrect types or missing fields, and also allow us to
utility methods on our documents in the same way that traditional :abbr:`ORMs define utility methods on our documents in the same way that traditional
(Object-Relational Mappers)` do. :abbr:`ORMs (Object-Relational Mappers)` do.
In our Tumblelog application we need to store several different types of 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 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 link posts to an individual. We also need to store our different types
**posts** (text, image and link) in the database. For to aid navigation of our **posts** (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 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 a specified tag. posts shown to the user may be limited to posts that have been assigned a
Finally, it would be nice if **comments** could be added to posts. We'll start specified tag. Finally, it would be nice if **comments** could be added to
with **users**, as the others are slightly more involved. posts. We'll start with **users**, as the others are slightly more involved.
Users Users
^^^^^ ^^^^^
@ -66,16 +66,15 @@ documents will be stored in a MongoDB *collection* rather than a table.
Posts, Comments and Tags Posts, Comments and Tags
^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
Well that wasn't too bad, was it? Now we'll think about how to store the rest Now we'll think about how to store the rest of the information. If we were
of the information. If we were using a relational database, we would most using a relational database, we would most likely have a table of **posts**, a
likely have a table of **posts**, a table of **comments** and a table of table of **comments** and a table of **tags**. To associate the comments with
**tags**. To associate the comments with individual posts, we would put a individual posts, we would put a column in the comments table that contained a
column in the comments table that contained a foreign key to the posts table. foreign key to the posts table. We'd also need a link table to provide the
We'd also need a link table to provide the many-to-many relationship between many-to-many relationship between posts and tags. Then we'd need to address the
posts and tags. Then we'd need to address the problem of storing the problem of storing the specialised post-types (text, image and link). There are
specialised post-types (text, image and link). There are several ways we can several ways we can achieve this, but each of them have their problems --- none
achieve this, but each of them have their problems --- none of them stand out of them stand out as particularly intuitive solutions.
as particularly intuitive solutions.
Posts Posts
""""" """""
@ -86,7 +85,7 @@ each post type will just have the fields it needs. If we later want to add
video posts, we don't have to modify the collection at all, we just *start video posts, we don't have to modify the collection at all, we just *start
using* the new fields we need to support video posts. This fits with the using* the new fields we need to support video posts. This fits with the
Object-Oriented principle of *inheritance* nicely. We can think of Object-Oriented principle of *inheritance* nicely. We can think of
:class:`Post` as an base class, and :class:`TextPost`, :class:`ImagePost` and :class:`Post` as a base class, and :class:`TextPost`, :class:`ImagePost` and
:class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports :class:`LinkPost` as subclasses of :class:`Post`. In fact, MongoEngine supports
this kind of modelling out of the box:: this kind of modelling out of the box::
@ -112,10 +111,10 @@ Tags
"""" """"
Now that we have our Post models figured out, how will we attach tags to them? Now that we have our Post models figured out, how will we attach tags to them?
MongoDB allows us to store lists of items natively, so rather than having a MongoDB allows us to store lists of items natively, so rather than having a
link table, we can just store a list of tags in each post. Also, for both link table, we can just store a list of tags in each post. So, for both
efficiency and simplicity's sake, we'll store the tags as strings directly efficiency and simplicity's sake, we'll store the tags as strings directly
within the post, rather than storing references to tags in a separate within the post, rather than storing references to tags in a separate
collection. Especially as tags are generally very short (often even shorted collection. Especially as tags are generally very short (often even shorter
than a document's id), this denormalisation won't impact very strongly on the than a document's id), this denormalisation won't impact very strongly on the
size of our database. So let's take a look that the code our modified size of our database. So let's take a look that the code our modified
:class:`Post` class:: :class:`Post` class::
@ -138,11 +137,11 @@ database, then query the database again for the comments associated with the
post. This works, but there is no real reason to be storing the comments post. This works, but there is no real reason to be storing the comments
separately from their associated posts, other than to work around the separately from their associated posts, other than to work around the
relational model. Using MongoDB we can store the comments as a list of relational model. Using MongoDB we can store the comments as a list of
*embedded documents* directly on the post document. An embedded document should *embedded documents* directly on a post document. An embedded document should
be treated no differently that a regular document; it just doesn't have its own be treated no differently that a regular document; it just doesn't have its own
collection. Using MongoEngine, we can define the structure of embedded collection in the database. Using MongoEngine, we can define the structure of
documents, along with utility methods, in exactly the same way we do with embedded documents, along with utility methods, in exactly the same way we do
regular documents:: with regular documents::
class Comment(EmbeddedDocument): class Comment(EmbeddedDocument):
content = StringField() content = StringField()
@ -158,23 +157,22 @@ We can then store a list of comment documents in our post document::
Adding data to our Tumblelog Adding data to our Tumblelog
---------------------------- ----------------------------
Now that we've defined how our documents will be structured, lets start adding 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 :class:`User` some documents to the database. Firstly, we'll need to create a :class:`User`
object:: object::
john = User(email='jdoe@example.com', first_name='John', last_name='Doe') john = User(email='jdoe@example.com', first_name='John', last_name='Doe')
john.save() john.save()
Simple, eh? Note that only fields with ``required=True`` need to be specified Note that only fields with ``required=True`` need to be specified in the
in the constructor, we could have also defined our user using attribute constructor, we could have also defined our user using attribute syntax::
syntax::
john = User(email='jdoe@example.com') john = User(email='jdoe@example.com')
john.first_name = 'John' john.first_name = 'John'
john.last_name = 'Doe' john.last_name = 'Doe'
john.save() john.save()
Now that we've got our user in the database, lets add a couple of posts:: 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 = TextPost(title='Fun with MongoEngine', author=john)
post1.content = 'Took a look at MongoEngine today, looks pretty cool.' post1.content = 'Took a look at MongoEngine today, looks pretty cool.'
@ -194,8 +192,8 @@ Accessing our data
So now we've got a couple of posts in our database, how do we display them? 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 Each document class (i.e. any class that inherits either directly or indirectly
from :class:`mongoengine.Document`) has an :attr:`objects` attribute, which is from :class:`mongoengine.Document`) has an :attr:`objects` attribute, which is
used to access the documents in the database associated with that class. So used to access the documents in the database collection associated with that
lets see how we can get our posts' titles:: class. So let's see how we can get our posts' titles::
for post in Post.objects: for post in Post.objects:
print post.title print post.title
@ -216,12 +214,12 @@ only looks for documents that were created using that subclass or one of its
subclasses. subclasses.
So how would we display all of our posts, showing only the information that So how would we display all of our posts, showing only the information that
corresponds to each post's specific type? As you might have guessed, there is a corresponds to each post's specific type? There is a better way than just using
better way than just using each of the subclasses individually. When we used each of the subclasses individually. When we used :class:`Post`'s
:class:`Post`'s :attr:`objects` attribute earlier, the objects being returned :attr:`objects` attribute earlier, the objects being returned weren't actually
weren't actually instances of :class:`Post` --- they were instances of the instances of :class:`Post` --- they were instances of the subclass of
subclass of :class:`Post` that matches the post's type. Lets look at how this :class:`Post` that matches the post's type. Let's look at how this works in
works in practice:: practice::
for post in Post.objects: for post in Post.objects:
print post.title print post.title
@ -242,8 +240,8 @@ Searching our posts by tag
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^
The :attr:`objects` attribute of a :class:`mongoengine.Document` is actually a The :attr:`objects` attribute of a :class:`mongoengine.Document` is actually a
:class:`mongoengine.QuerySet` object. This lazily queries the database only :class:`mongoengine.QuerySet` object. This lazily queries the database only
when you need the data. It may also be filtered to narrow down your query. Lets when you need the data. It may also be filtered to narrow down your query.
adjust our query so that only posts with the tag "mongodb" are returned:: Let's adjust our query so that only posts with the tag "mongodb" are returned::
for post in Post.objects(tags='mongodb'): for post in Post.objects(tags='mongodb'):
print post.title print post.title