724 lines
29 KiB
ReStructuredText
724 lines
29 KiB
ReStructuredText
=====================
|
|
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=[<bottom left coordinates>, <upper right coordinates>])
|
|
|
|
* ``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=[ [ <x1> , <y1> ] ,
|
|
[ <x2> , <y2> ] ,
|
|
[ <x3> , <y3> ] ])
|
|
|
|
* ``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=[ [ <x>, <y> ] , <radius> ])
|
|
|
|
* ``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=[ [ <x>, <y> ] , <radius> ])
|
|
|
|
* ``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'})
|
|
|
|
.. versionadded:: 0.4
|
|
|
|
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 <https://api.mongodb.com/python/current/examples/aggregation.html#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 <https://docs.mongodb.com/manual/tutorial/update-documents/#Updating-The%24positionaloperator>`_.
|
|
|
|
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;
|
|
}
|
|
""")
|