===================== Querying the database ===================== :class:`~mongoengine.Document` classes have an :attr:`objects` attribute, which is used for accessing the objects in the database associated with the class. The :attr:`objects` attribute is actually a :class:`~mongoengine.queryset.QuerySetManager`, which creates and returns a new :class:`~mongoengine.queryset.QuerySet` object on access. The :class:`~mongoengine.queryset.QuerySet` object may be iterated over to fetch documents from the database:: # Prints out the names of all the users in the database for user in User.objects: print user.name .. note:: As of MongoEngine 0.8 the querysets utilise a local cache. So iterating it multiple times will only cause a single query. If this is not the desired behaviour you can call :class:`~mongoengine.QuerySet.no_cache` (version **0.8.3+**) to return a non-caching queryset. Filtering queries ================= The query may be filtered by calling the :class:`~mongoengine.queryset.QuerySet` object with field lookup keyword arguments. The keys in the keyword arguments correspond to fields on the :class:`~mongoengine.Document` you are querying:: # This will return a QuerySet that will only iterate over users whose # 'country' field is set to 'uk' uk_users = User.objects(country='uk') Fields on embedded documents may also be referred to using field lookup syntax by using a double-underscore in place of the dot in object attribute access syntax:: # This will return a QuerySet that will only iterate over pages that have # been written by a user whose 'country' field is set to 'uk' uk_pages = Page.objects(author__country='uk') .. note:: (version **0.9.1+**) if your field name is like mongodb operator name (for example type, lte, lt...) and you want to place it at the end of lookup keyword mongoengine automatically prepend $ to it. To avoid this use __ at the end of your lookup keyword. For example if your field name is ``type`` and you want to query by this field you must use ``.objects(user__type__="admin")`` instead of ``.objects(user__type="admin")`` Query operators =============== Operators other than equality may also be used in queries --- just attach the operator name to a key with a double-underscore:: # Only find users whose age is 18 or less young_users = Users.objects(age__lte=18) Available operators are as follows: * ``ne`` -- not equal to * ``lt`` -- less than * ``lte`` -- less than or equal to * ``gt`` -- greater than * ``gte`` -- greater than or equal to * ``not`` -- negate a standard check, may be used before other operators (e.g. ``Q(age__not__mod=(5, 0))``) * ``in`` -- value is in list (a list of values should be provided) * ``nin`` -- value is not in list (a list of values should be provided) * ``mod`` -- ``value % x == y``, where ``x`` and ``y`` are two provided values * ``all`` -- every item in list of values provided is in array * ``size`` -- the size of the array is * ``exists`` -- value for field exists String queries -------------- The following operators are available as shortcuts to querying with regular expressions: * ``exact`` -- string field exactly matches value * ``iexact`` -- string field exactly matches value (case insensitive) * ``contains`` -- string field contains value * ``icontains`` -- string field contains value (case insensitive) * ``startswith`` -- string field starts with value * ``istartswith`` -- string field starts with value (case insensitive) * ``endswith`` -- string field ends with value * ``iendswith`` -- string field ends with value (case insensitive) * ``wholeword`` -- string field contains whole word * ``iwholeword`` -- string field contains whole word (case insensitive) * ``regex`` -- string field match by regex * ``iregex`` -- string field match by regex (case insensitive) * ``match`` -- performs an $elemMatch so you can match an entire document within an array Geo queries ----------- There are a few special operators for performing geographical queries. The following were added in MongoEngine 0.8 for :class:`~mongoengine.fields.PointField`, :class:`~mongoengine.fields.LineStringField` and :class:`~mongoengine.fields.PolygonField`: * ``geo_within`` -- check if a geometry is within a polygon. For ease of use it accepts either a geojson geometry or just the polygon coordinates eg:: loc.objects(point__geo_within=[[[40, 5], [40, 6], [41, 6], [40, 5]]]) loc.objects(point__geo_within={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [40, 5]]]}) * ``geo_within_box`` -- simplified geo_within searching with a box eg:: loc.objects(point__geo_within_box=[(-125.0, 35.0), (-100.0, 40.0)]) loc.objects(point__geo_within_box=[, ]) * ``geo_within_polygon`` -- simplified geo_within searching within a simple polygon eg:: loc.objects(point__geo_within_polygon=[[40, 5], [40, 6], [41, 6], [40, 5]]) loc.objects(point__geo_within_polygon=[ [ , ] , [ , ] , [ , ] ]) * ``geo_within_center`` -- simplified geo_within the flat circle radius of a point eg:: loc.objects(point__geo_within_center=[(-125.0, 35.0), 1]) loc.objects(point__geo_within_center=[ [ , ] , ]) * ``geo_within_sphere`` -- simplified geo_within the spherical circle radius of a point eg:: loc.objects(point__geo_within_sphere=[(-125.0, 35.0), 1]) loc.objects(point__geo_within_sphere=[ [ , ] , ]) * ``geo_intersects`` -- selects all locations that intersect with a geometry eg:: # Inferred from provided points lists: loc.objects(poly__geo_intersects=[40, 6]) loc.objects(poly__geo_intersects=[[40, 5], [40, 6]]) loc.objects(poly__geo_intersects=[[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]) # With geoJson style objects loc.objects(poly__geo_intersects={"type": "Point", "coordinates": [40, 6]}) loc.objects(poly__geo_intersects={"type": "LineString", "coordinates": [[40, 5], [40, 6]]}) loc.objects(poly__geo_intersects={"type": "Polygon", "coordinates": [[[40, 5], [40, 6], [41, 6], [41, 5], [40, 5]]]}) * ``near`` -- find all the locations near a given point:: loc.objects(point__near=[40, 5]) loc.objects(point__near={"type": "Point", "coordinates": [40, 5]}) You can also set the maximum and/or the minimum distance in meters as well:: loc.objects(point__near=[40, 5], point__max_distance=1000) loc.objects(point__near=[40, 5], point__min_distance=100) The older 2D indexes are still supported with the :class:`~mongoengine.fields.GeoPointField`: * ``within_distance`` -- provide a list containing a point and a maximum distance (e.g. [(41.342, -87.653), 5]) * ``within_spherical_distance`` -- same as above but using the spherical geo model (e.g. [(41.342, -87.653), 5/earth_radius]) * ``near`` -- order the documents by how close they are to a given point * ``near_sphere`` -- Same as above but using the spherical geo model * ``within_box`` -- filter documents to those within a given bounding box (e.g. [(35.0, -125.0), (40.0, -100.0)]) * ``within_polygon`` -- filter documents to those within a given polygon (e.g. [(41.91,-87.69), (41.92,-87.68), (41.91,-87.65), (41.89,-87.65)]). .. note:: Requires Mongo Server 2.0 * ``max_distance`` -- can be added to your location queries to set a maximum distance. * ``min_distance`` -- can be added to your location queries to set a minimum distance. Querying lists -------------- On most fields, this syntax will look up documents where the field specified matches the given value exactly, but when the field refers to a :class:`~mongoengine.fields.ListField`, a single item may be provided, in which case lists that contain that item will be matched:: class Page(Document): tags = ListField(StringField()) # This will match all pages that have the word 'coding' as an item in the # 'tags' list Page.objects(tags='coding') It is possible to query by position in a list by using a numerical value as a query operator. So if you wanted to find all pages whose first tag was ``db``, you could use the following query:: Page.objects(tags__0='db') If you only want to fetch part of a list eg: you want to paginate a list, then the `slice` operator is required:: # comments - skip 5, limit 10 Page.objects.fields(slice__comments=[5, 10]) For updating documents, if you don't know the position in a list, you can use the $ positional operator :: Post.objects(comments__by="joe").update(**{'inc__comments__$__votes': 1}) However, this doesn't map well to the syntax so you can also use a capital S instead :: Post.objects(comments__by="joe").update(inc__comments__S__votes=1) .. note:: Due to :program:`Mongo`, currently the $ operator only applies to the first matched item in the query. Raw queries ----------- It is possible to provide a raw :mod:`PyMongo` query as a query parameter, which will be integrated directly into the query. This is done using the ``__raw__`` keyword argument:: Page.objects(__raw__={'tags': 'coding'}) Similarly, a raw update can be provided to the :meth:`~mongoengine.queryset.QuerySet.update` method:: Page.objects(tags='coding').update(__raw__={'$set': {'tags': 'coding'}}) And the two can also be combined:: Page.objects(__raw__={'tags': 'coding'}).update(__raw__={'$set': {'tags': 'coding'}}) Update with Aggregation Pipeline -------------------------------- It is possible to provide a raw :mod:`PyMongo` aggregation update parameter, which will be integrated directly into the update. This is done by using ``__raw__`` keyword argument to the update method and provide the pipeline as a list `Update with Aggregation Pipeline `_ :: # 'tags' field is set to 'coding is fun' Page.objects(tags='coding').update(__raw__=[ {"$set": {"tags": {"$concat": ["$tags", "is fun"]}}} ], ) .. versionadded:: 0.23.2 Sorting/Ordering results ======================== It is possible to order the results by 1 or more keys using :meth:`~mongoengine.queryset.QuerySet.order_by`. The order may be specified by prepending each of the keys by "+" or "-". Ascending order is assumed if there's no prefix.:: # Order by ascending date blogs = BlogPost.objects().order_by('date') # equivalent to .order_by('+date') # Order by ascending date first, then descending title blogs = BlogPost.objects().order_by('+date', '-title') Limiting and skipping results ============================= Just as with traditional ORMs, you may limit the number of results returned or skip a number or results in you query. :meth:`~mongoengine.queryset.QuerySet.limit` and :meth:`~mongoengine.queryset.QuerySet.skip` methods are available on :class:`~mongoengine.queryset.QuerySet` objects, but the `array-slicing` syntax is preferred for achieving this:: # Only the first 5 people users = User.objects[:5] # All except for the first 5 people users = User.objects[5:] # 5 users, starting from the 11th user found users = User.objects[10:15] You may also index the query to retrieve a single result. If an item at that index does not exists, an :class:`IndexError` will be raised. A shortcut for retrieving the first result and returning :attr:`None` if no result exists is provided (:meth:`~mongoengine.queryset.QuerySet.first`):: >>> # Make sure there are no users >>> User.drop_collection() >>> User.objects[0] IndexError: list index out of range >>> User.objects.first() == None True >>> User(name='Test User').save() >>> User.objects[0] == User.objects.first() True Retrieving unique results ------------------------- To retrieve a result that should be unique in the collection, use :meth:`~mongoengine.queryset.QuerySet.get`. This will raise :class:`~mongoengine.queryset.DoesNotExist` if no document matches the query, and :class:`~mongoengine.queryset.MultipleObjectsReturned` if more than one document matched the query. These exceptions are merged into your document definitions eg: `MyDoc.DoesNotExist` A variation of this method, get_or_create() existed, but it was unsafe. It could not be made safe, because there are no transactions in mongoDB. Other approaches should be investigated, to ensure you don't accidentally duplicate data when using something similar to this method. Therefore it was deprecated in 0.8 and removed in 0.10. Default Document queries ======================== By default, the objects :attr:`~Document.objects` attribute on a document returns a :class:`~mongoengine.queryset.QuerySet` that doesn't filter the collection -- it returns all objects. This may be changed by defining a method on a document that modifies a queryset. The method should accept two arguments -- :attr:`doc_cls` and :attr:`queryset`. The first argument is the :class:`~mongoengine.Document` class that the method is defined on (in this sense, the method is more like a :func:`classmethod` than a regular method), and the second argument is the initial queryset. The method needs to be decorated with :func:`~mongoengine.queryset.queryset_manager` in order for it to be recognised. :: class BlogPost(Document): title = StringField() date = DateTimeField() @queryset_manager def objects(doc_cls, queryset): # This may actually also be done by defining a default ordering for # the document, but this illustrates the use of manager methods return queryset.order_by('-date') You don't need to call your method :attr:`objects` -- you may define as many custom manager methods as you like:: class BlogPost(Document): title = StringField() published = BooleanField() @queryset_manager def live_posts(doc_cls, queryset): return queryset.filter(published=True) BlogPost(title='test1', published=False).save() BlogPost(title='test2', published=True).save() assert len(BlogPost.objects) == 2 assert len(BlogPost.live_posts()) == 1 Custom QuerySets ================ Should you want to add custom methods for interacting with or filtering documents, extending the :class:`~mongoengine.queryset.QuerySet` class may be the way to go. To use a custom :class:`~mongoengine.queryset.QuerySet` class on a document, set ``queryset_class`` to the custom class in a :class:`~mongoengine.Document`'s ``meta`` dictionary:: class AwesomerQuerySet(QuerySet): def get_awesome(self): return self.filter(awesome=True) class Page(Document): meta = {'queryset_class': AwesomerQuerySet} # To call: Page.objects.get_awesome() .. versionadded:: 0.4 Aggregation =========== MongoDB provides some aggregation methods out of the box, but there are not as many as you typically get with an RDBMS. MongoEngine provides a wrapper around the built-in methods and provides some of its own, which are implemented as Javascript code that is executed on the database server. Counting results ---------------- Just as with limiting and skipping results, there is a method on a :class:`~mongoengine.queryset.QuerySet` object -- :meth:`~mongoengine.queryset.QuerySet.count`:: num_users = User.objects.count() You could technically use ``len(User.objects)`` to get the same result, but it would be significantly slower than :meth:`~mongoengine.queryset.QuerySet.count`. When you execute a server-side count query, you let MongoDB do the heavy lifting and you receive a single integer over the wire. Meanwhile, ``len()`` retrieves all the results, places them in a local cache, and finally counts them. If we compare the performance of the two operations, ``len()`` is much slower than :meth:`~mongoengine.queryset.QuerySet.count`. Further aggregation ------------------- You may sum over the values of a specific field on documents using :meth:`~mongoengine.queryset.QuerySet.sum`:: yearly_expense = Employee.objects.sum('salary') .. note:: If the field isn't present on a document, that document will be ignored from the sum. To get the average (mean) of a field on a collection of documents, use :meth:`~mongoengine.queryset.QuerySet.average`:: mean_age = User.objects.average('age') As MongoDB provides native lists, MongoEngine provides a helper method to get a dictionary of the frequencies of items in lists across an entire collection -- :meth:`~mongoengine.queryset.QuerySet.item_frequencies`. An example of its use would be generating "tag-clouds":: class Article(Document): tag = ListField(StringField()) # After adding some tagged articles... tag_freqs = Article.objects.item_frequencies('tag', normalize=True) from operator import itemgetter top_tags = sorted(tag_freqs.items(), key=itemgetter(1), reverse=True)[:10] MongoDB aggregation API ----------------------- If you need to run aggregation pipelines, MongoEngine provides an entry point to `Pymongo's aggregation framework `_ through :meth:`~mongoengine.queryset.QuerySet.aggregate`. Check out Pymongo's documentation for the syntax and pipeline. An example of its use would be:: class Person(Document): name = StringField() Person(name='John').save() Person(name='Bob').save() pipeline = [ {"$sort" : {"name" : -1}}, {"$project": {"_id": 0, "name": {"$toUpper": "$name"}}} ] data = Person.objects().aggregate(pipeline) assert data == [{'name': 'BOB'}, {'name': 'JOHN'}] Query efficiency and performance ================================ There are a couple of methods to improve efficiency when querying, reducing the information returned by the query or efficient dereferencing . Retrieving a subset of fields ----------------------------- Sometimes a subset of fields on a :class:`~mongoengine.Document` is required, and for efficiency only these should be retrieved from the database. This issue is especially important for MongoDB, as fields may often be extremely large (e.g. a :class:`~mongoengine.fields.ListField` of :class:`~mongoengine.EmbeddedDocument`\ s, which represent the comments on a blog post. To select only a subset of fields, use :meth:`~mongoengine.queryset.QuerySet.only`, specifying the fields you want to retrieve as its arguments. Note that if fields that are not downloaded are accessed, their default value (or :attr:`None` if no default value is provided) will be given:: >>> class Film(Document): ... title = StringField() ... year = IntField() ... rating = IntField(default=3) ... >>> Film(title='The Shawshank Redemption', year=1994, rating=5).save() >>> f = Film.objects.only('title').first() >>> f.title 'The Shawshank Redemption' >>> f.year # None >>> f.rating # default value 3 .. note:: The :meth:`~mongoengine.queryset.QuerySet.exclude` is the opposite of :meth:`~mongoengine.queryset.QuerySet.only` if you want to exclude a field. If you later need the missing fields, just call :meth:`~mongoengine.Document.reload` on your document. Getting related data -------------------- When iterating the results of :class:`~mongoengine.fields.ListField` or :class:`~mongoengine.fields.DictField` we automatically dereference any :class:`~pymongo.dbref.DBRef` objects as efficiently as possible, reducing the number the queries to mongo. There are times when that efficiency is not enough, documents that have :class:`~mongoengine.fields.ReferenceField` objects or :class:`~mongoengine.fields.GenericReferenceField` objects at the top level are expensive as the number of queries to MongoDB can quickly rise. To limit the number of queries use :func:`~mongoengine.queryset.QuerySet.select_related` which converts the QuerySet to a list and dereferences as efficiently as possible. By default :func:`~mongoengine.queryset.QuerySet.select_related` only dereferences any references to the depth of 1 level. If you have more complicated documents and want to dereference more of the object at once then increasing the :attr:`max_depth` will dereference more levels of the document. Turning off dereferencing ------------------------- Sometimes for performance reasons you don't want to automatically dereference data. To turn off dereferencing of the results of a query use :func:`~mongoengine.queryset.QuerySet.no_dereference` on the queryset like so:: post = Post.objects.no_dereference().first() assert(isinstance(post.author, DBRef)) You can also turn off all dereferencing for a fixed period by using the :class:`~mongoengine.context_managers.no_dereference` context manager:: with no_dereference(Post) as Post: post = Post.objects.first() assert(isinstance(post.author, DBRef)) # Outside the context manager dereferencing occurs. assert(isinstance(post.author, User)) Advanced queries ================ Sometimes calling a :class:`~mongoengine.queryset.QuerySet` object with keyword arguments can't fully express the query you want to use -- for example if you need to combine a number of constraints using *and* and *or*. This is made possible in MongoEngine through the :class:`~mongoengine.queryset.Q` class. A :class:`~mongoengine.queryset.Q` object represents part of a query, and can be initialised using the same keyword-argument syntax you use to query documents. To build a complex query, you may combine :class:`~mongoengine.queryset.Q` objects using the ``&`` (and) and ``|`` (or) operators. To use a :class:`~mongoengine.queryset.Q` object, pass it in as the first positional argument to :attr:`Document.objects` when you filter it by calling it with keyword arguments:: from mongoengine.queryset.visitor import Q # Get published posts Post.objects(Q(published=True) | Q(publish_date__lte=datetime.now())) # Get top posts Post.objects((Q(featured=True) & Q(hits__gte=1000)) | Q(hits__gte=5000)) .. warning:: You have to use bitwise operators. You cannot use ``or``, ``and`` to combine queries as ``Q(a=a) or Q(b=b)`` is not the same as ``Q(a=a) | Q(b=b)``. As ``Q(a=a)`` equates to true ``Q(a=a) or Q(b=b)`` is the same as ``Q(a=a)``. .. _guide-atomic-updates: Atomic updates ============== Documents may be updated atomically by using the :meth:`~mongoengine.queryset.QuerySet.update_one`, :meth:`~mongoengine.queryset.QuerySet.update` and :meth:`~mongoengine.queryset.QuerySet.modify` methods on a :class:`~mongoengine.queryset.QuerySet` or :meth:`~mongoengine.Document.modify` and :meth:`~mongoengine.Document.save` (with :attr:`save_condition` argument) on a :class:`~mongoengine.Document`. There are several different "modifiers" that you may use with these methods: * ``set`` -- set a particular value * ``set_on_insert`` -- set only if this is new document `need to add upsert=True`_ * ``unset`` -- delete a particular value (since MongoDB v1.3) * ``max`` -- update only if value is bigger * ``min`` -- update only if value is smaller * ``inc`` -- increment a value by a given amount * ``dec`` -- decrement a value by a given amount * ``push`` -- append a value to a list * ``push_all`` -- append several values to a list * ``pop`` -- remove the first or last element of a list `depending on the value`_ * ``pull`` -- remove a value from a list * ``pull_all`` -- remove several values from a list * ``add_to_set`` -- add value to a list only if its not in the list already * ``rename`` -- rename the key name .. _depending on the value: http://docs.mongodb.org/manual/reference/operator/update/pop/ The syntax for atomic updates is similar to the querying syntax, but the modifier comes before the field, not after it:: >>> post = BlogPost(title='Test', page_views=0, tags=['database']) >>> post.save() >>> BlogPost.objects(id=post.id).update_one(inc__page_views=1) >>> post.reload() # the document has been changed, so we need to reload it >>> post.page_views 1 >>> BlogPost.objects(id=post.id).update_one(set__title='Example Post') >>> post.reload() >>> post.title 'Example Post' >>> BlogPost.objects(id=post.id).update_one(push__tags='nosql') >>> post.reload() >>> post.tags ['database', 'nosql'] .. note:: If no modifier operator is specified the default will be ``$set``. So the following sentences are identical:: >>> BlogPost.objects(id=post.id).update(title='Example Post') >>> BlogPost.objects(id=post.id).update(set__title='Example Post') .. note:: In version 0.5 the :meth:`~mongoengine.Document.save` runs atomic updates on changed documents by tracking changes to that document. The positional operator allows you to update list items without knowing the index position, therefore making the update a single atomic operation. As we cannot use the `$` syntax in keyword arguments it has been mapped to `S`:: >>> post = BlogPost(title='Test', page_views=0, tags=['database', 'mongo']) >>> post.save() >>> BlogPost.objects(id=post.id, tags='mongo').update(set__tags__S='mongodb') >>> post.reload() >>> post.tags ['database', 'mongodb'] From MongoDB version 2.6, push operator supports $position value which allows to push values with index:: >>> post = BlogPost(title="Test", tags=["mongo"]) >>> post.save() >>> post.update(push__tags__0=["database", "code"]) >>> post.reload() >>> post.tags ['database', 'code', 'mongo'] .. note:: Currently only top level lists are handled, future versions of mongodb / pymongo plan to support nested positional operators. See `The $ positional operator `_. Server-side javascript execution ================================ Javascript functions may be written and sent to the server for execution. The result of this is the return value of the Javascript function. This functionality is accessed through the :meth:`~mongoengine.queryset.QuerySet.exec_js` method on :meth:`~mongoengine.queryset.QuerySet` objects. Pass in a string containing a Javascript function as the first argument. The remaining positional arguments are names of fields that will be passed into you Javascript function as its arguments. This allows functions to be written that may be executed on any field in a collection (e.g. the :meth:`~mongoengine.queryset.QuerySet.sum` method, which accepts the name of the field to sum over as its argument). Note that field names passed in in this manner are automatically translated to the names used on the database (set using the :attr:`name` keyword argument to a field constructor). Keyword arguments to :meth:`~mongoengine.queryset.QuerySet.exec_js` are combined into an object called :attr:`options`, which is available in the Javascript function. This may be used for defining specific parameters for your function. Some variables are made available in the scope of the Javascript function: * ``collection`` -- the name of the collection that corresponds to the :class:`~mongoengine.Document` class that is being used; this should be used to get the :class:`Collection` object from :attr:`db` in Javascript code * ``query`` -- the query that has been generated by the :class:`~mongoengine.queryset.QuerySet` object; this may be passed into the :meth:`find` method on a :class:`Collection` object in the Javascript function * ``options`` -- an object containing the keyword arguments passed into :meth:`~mongoengine.queryset.QuerySet.exec_js` The following example demonstrates the intended usage of :meth:`~mongoengine.queryset.QuerySet.exec_js` by defining a function that sums over a field on a document (this functionality is already available through :meth:`~mongoengine.queryset.QuerySet.sum` but is shown here for sake of example):: def sum_field(document, field_name, include_negatives=True): code = """ function(sumField) { var total = 0.0; db[collection].find(query).forEach(function(doc) { var val = doc[sumField]; if (val >= 0.0 || options.includeNegatives) { total += val; } }); return total; } """ options = {'includeNegatives': include_negatives} return document.objects.exec_js(code, field_name, **options) As fields in MongoEngine may use different names in the database (set using the :attr:`db_field` keyword argument to a :class:`Field` constructor), a mechanism exists for replacing MongoEngine field names with the database field names in Javascript code. When accessing a field on a collection object, use square-bracket notation, and prefix the MongoEngine field name with a tilde. The field name that follows the tilde will be translated to the name used in the database. Note that when referring to fields on embedded documents, the name of the :class:`~mongoengine.fields.EmbeddedDocumentField`, followed by a dot, should be used before the name of the field on the embedded document. The following example shows how the substitutions are made:: class Comment(EmbeddedDocument): content = StringField(db_field='body') class BlogPost(Document): title = StringField(db_field='doctitle') comments = ListField(EmbeddedDocumentField(Comment), name='cs') # Returns a list of dictionaries. Each dictionary contains a value named # "document", which corresponds to the "title" field on a BlogPost, and # "comment", which corresponds to an individual comment. The substitutions # made are shown in the comments. BlogPost.objects.exec_js(""" function() { var comments = []; db[collection].find(query).forEach(function(doc) { // doc[~comments] -> doc["cs"] var docComments = doc[~comments]; for (var i = 0; i < docComments.length; i++) { // doc[~comments][i] -> doc["cs"][i] var comment = doc[~comments][i]; comments.push({ // doc[~title] -> doc["doctitle"] 'document': doc[~title], // comment[~comments.content] -> comment["body"] 'comment': comment[~comments.content] }); } }); return comments; } """)