813 lines
30 KiB
ReStructuredText
813 lines
30 KiB
ReStructuredText
==================
|
||
Defining documents
|
||
==================
|
||
In MongoDB, a **document** is roughly equivalent to a **row** in an RDBMS. When
|
||
working with relational databases, rows are stored in **tables**, which have a
|
||
strict **schema** that the rows follow. MongoDB stores documents in
|
||
**collections** rather than tables --- the principal difference is that no schema
|
||
is enforced at a database level.
|
||
|
||
Defining a document's schema
|
||
============================
|
||
MongoEngine allows you to define schemata for documents as this helps to reduce
|
||
coding errors, and allows for utility methods to be defined on fields which may
|
||
be present.
|
||
|
||
To define a schema for a document, create a class that inherits from
|
||
:class:`~mongoengine.Document`. Fields are specified by adding **field
|
||
objects** as class attributes to the document class::
|
||
|
||
from mongoengine import *
|
||
import datetime
|
||
|
||
class Page(Document):
|
||
title = StringField(max_length=200, required=True)
|
||
date_modified = DateTimeField(default=datetime.datetime.utcnow)
|
||
|
||
As BSON (the binary format for storing data in mongodb) is order dependent,
|
||
documents are serialized based on their field order.
|
||
|
||
Dynamic document schemas
|
||
========================
|
||
One of the benefits of MongoDB is dynamic schemas for a collection, whilst data
|
||
should be planned and organised (after all explicit is better than implicit!)
|
||
there are scenarios where having dynamic / expando style documents is desirable.
|
||
|
||
:class:`~mongoengine.DynamicDocument` documents work in the same way as
|
||
:class:`~mongoengine.Document` but any data / attributes set to them will also
|
||
be saved ::
|
||
|
||
from mongoengine import *
|
||
|
||
class Page(DynamicDocument):
|
||
title = StringField(max_length=200, required=True)
|
||
|
||
# Create a new page and add tags
|
||
>>> page = Page(title='Using MongoEngine')
|
||
>>> page.tags = ['mongodb', 'mongoengine']
|
||
>>> page.save()
|
||
|
||
>>> Page.objects(tags='mongoengine').count()
|
||
>>> 1
|
||
|
||
.. note::
|
||
|
||
There is one caveat on Dynamic Documents: fields cannot start with `_`
|
||
|
||
Dynamic fields are stored in creation order *after* any declared fields.
|
||
|
||
Fields
|
||
======
|
||
By default, fields are not required. To make a field mandatory, set the
|
||
:attr:`required` keyword argument of a field to ``True``. Fields also may have
|
||
validation constraints available (such as :attr:`max_length` in the example
|
||
above). Fields may also take default values, which will be used if a value is
|
||
not provided. Default values may optionally be a callable, which will be called
|
||
to retrieve the value (such as in the above example). The field types available
|
||
are as follows:
|
||
|
||
* :class:`~mongoengine.fields.BinaryField`
|
||
* :class:`~mongoengine.fields.BooleanField`
|
||
* :class:`~mongoengine.fields.ComplexDateTimeField`
|
||
* :class:`~mongoengine.fields.DateTimeField`
|
||
* :class:`~mongoengine.fields.DecimalField`
|
||
* :class:`~mongoengine.fields.DictField`
|
||
* :class:`~mongoengine.fields.DynamicField`
|
||
* :class:`~mongoengine.fields.EmailField`
|
||
* :class:`~mongoengine.fields.EmbeddedDocumentField`
|
||
* :class:`~mongoengine.fields.EmbeddedDocumentListField`
|
||
* :class:`~mongoengine.fields.EnumField`
|
||
* :class:`~mongoengine.fields.FileField`
|
||
* :class:`~mongoengine.fields.FloatField`
|
||
* :class:`~mongoengine.fields.GenericEmbeddedDocumentField`
|
||
* :class:`~mongoengine.fields.GenericReferenceField`
|
||
* :class:`~mongoengine.fields.GenericLazyReferenceField`
|
||
* :class:`~mongoengine.fields.GeoPointField`
|
||
* :class:`~mongoengine.fields.ImageField`
|
||
* :class:`~mongoengine.fields.IntField`
|
||
* :class:`~mongoengine.fields.ListField`
|
||
* :class:`~mongoengine.fields.LongField`
|
||
* :class:`~mongoengine.fields.MapField`
|
||
* :class:`~mongoengine.fields.ObjectIdField`
|
||
* :class:`~mongoengine.fields.ReferenceField`
|
||
* :class:`~mongoengine.fields.LazyReferenceField`
|
||
* :class:`~mongoengine.fields.SequenceField`
|
||
* :class:`~mongoengine.fields.SortedListField`
|
||
* :class:`~mongoengine.fields.StringField`
|
||
* :class:`~mongoengine.fields.URLField`
|
||
* :class:`~mongoengine.fields.UUIDField`
|
||
* :class:`~mongoengine.fields.PointField`
|
||
* :class:`~mongoengine.fields.LineStringField`
|
||
* :class:`~mongoengine.fields.PolygonField`
|
||
* :class:`~mongoengine.fields.MultiPointField`
|
||
* :class:`~mongoengine.fields.MultiLineStringField`
|
||
* :class:`~mongoengine.fields.MultiPolygonField`
|
||
|
||
Field arguments
|
||
---------------
|
||
Each field type can be customized by keyword arguments. The following keyword
|
||
arguments can be set on all fields:
|
||
|
||
:attr:`db_field` (Default: None)
|
||
The MongoDB field name.
|
||
|
||
:attr:`required` (Default: False)
|
||
If set to True and the field is not set on the document instance, a
|
||
:class:`~mongoengine.ValidationError` will be raised when the document is
|
||
validated.
|
||
|
||
:attr:`default` (Default: None)
|
||
A value to use when no value is set for this field.
|
||
|
||
The definition of default parameters follow `the general rules on Python
|
||
<http://docs.python.org/reference/compound_stmts.html#function-definitions>`__,
|
||
which means that some care should be taken when dealing with default mutable objects
|
||
(like in :class:`~mongoengine.fields.ListField` or :class:`~mongoengine.fields.DictField`)::
|
||
|
||
class ExampleFirst(Document):
|
||
# Default an empty list
|
||
values = ListField(IntField(), default=list)
|
||
|
||
class ExampleSecond(Document):
|
||
# Default a set of values
|
||
values = ListField(IntField(), default=lambda: [1,2,3])
|
||
|
||
class ExampleDangerous(Document):
|
||
# This can make an .append call to add values to the default (and all the following objects),
|
||
# instead to just an object
|
||
values = ListField(IntField(), default=[1,2,3])
|
||
|
||
.. note:: Unsetting a field with a default value will revert back to the default.
|
||
|
||
:attr:`unique` (Default: False)
|
||
When True, no documents in the collection will have the same value for this
|
||
field.
|
||
|
||
:attr:`unique_with` (Default: None)
|
||
A field name (or list of field names) that when taken together with this
|
||
field, will not have two documents in the collection with the same value.
|
||
|
||
:attr:`primary_key` (Default: False)
|
||
When True, use this field as a primary key for the collection. `DictField`
|
||
and `EmbeddedDocuments` both support being the primary key for a document.
|
||
|
||
.. note:: If set, this field is also accessible through the `pk` field.
|
||
|
||
:attr:`choices` (Default: None)
|
||
An iterable (e.g. list, tuple or set) of choices to which the value of this
|
||
field should be limited.
|
||
|
||
Can either be nested tuples of value (stored in mongo) and a
|
||
human readable key ::
|
||
|
||
SIZE = (('S', 'Small'),
|
||
('M', 'Medium'),
|
||
('L', 'Large'),
|
||
('XL', 'Extra Large'),
|
||
('XXL', 'Extra Extra Large'))
|
||
|
||
|
||
class Shirt(Document):
|
||
size = StringField(max_length=3, choices=SIZE)
|
||
|
||
Or a flat iterable just containing values ::
|
||
|
||
SIZE = ('S', 'M', 'L', 'XL', 'XXL')
|
||
|
||
class Shirt(Document):
|
||
size = StringField(max_length=3, choices=SIZE)
|
||
|
||
:attr:`validation` (Optional)
|
||
A callable to validate the value of the field.
|
||
The callable takes the value as parameter and should raise a ValidationError
|
||
if validation fails
|
||
|
||
e.g ::
|
||
|
||
def _not_empty(val):
|
||
if not val:
|
||
raise ValidationError('value can not be empty')
|
||
|
||
class Person(Document):
|
||
name = StringField(validation=_not_empty)
|
||
|
||
|
||
:attr:`**kwargs` (Optional)
|
||
You can supply additional metadata as arbitrary additional keyword
|
||
arguments. You can not override existing attributes, however. Common
|
||
choices include `help_text` and `verbose_name`, commonly used by form and
|
||
widget libraries.
|
||
|
||
|
||
List fields
|
||
-----------
|
||
MongoDB allows storing lists of items. To add a list of items to a
|
||
:class:`~mongoengine.Document`, use the :class:`~mongoengine.fields.ListField` field
|
||
type. :class:`~mongoengine.fields.ListField` takes another field object as its first
|
||
argument, which specifies which type elements may be stored within the list::
|
||
|
||
class Page(Document):
|
||
tags = ListField(StringField(max_length=50))
|
||
|
||
Embedded documents
|
||
------------------
|
||
MongoDB has the ability to embed documents within other documents. Schemata may
|
||
be defined for these embedded documents, just as they may be for regular
|
||
documents. To create an embedded document, just define a document as usual, but
|
||
inherit from :class:`~mongoengine.EmbeddedDocument` rather than
|
||
:class:`~mongoengine.Document`::
|
||
|
||
class Comment(EmbeddedDocument):
|
||
content = StringField()
|
||
|
||
To embed the document within another document, use the
|
||
:class:`~mongoengine.fields.EmbeddedDocumentField` field type, providing the embedded
|
||
document class as the first argument::
|
||
|
||
class Page(Document):
|
||
comments = ListField(EmbeddedDocumentField(Comment))
|
||
|
||
comment1 = Comment(content='Good work!')
|
||
comment2 = Comment(content='Nice article!')
|
||
page = Page(comments=[comment1, comment2])
|
||
|
||
Dictionary Fields
|
||
-----------------
|
||
Often, an embedded document may be used instead of a dictionary – generally
|
||
embedded documents are recommended as dictionaries don’t support validation
|
||
or custom field types. However, sometimes you will not know the structure of what you want to
|
||
store; in this situation a :class:`~mongoengine.fields.DictField` is appropriate::
|
||
|
||
class SurveyResponse(Document):
|
||
date = DateTimeField()
|
||
user = ReferenceField(User)
|
||
answers = DictField()
|
||
|
||
survey_response = SurveyResponse(date=datetime.utcnow(), user=request.user)
|
||
response_form = ResponseForm(request.POST)
|
||
survey_response.answers = response_form.cleaned_data()
|
||
survey_response.save()
|
||
|
||
Dictionaries can store complex data, other dictionaries, lists, references to
|
||
other objects, so are the most flexible field type available.
|
||
|
||
Reference fields
|
||
----------------
|
||
References may be stored to other documents in the database using the
|
||
:class:`~mongoengine.fields.ReferenceField`. Pass in another document class as the
|
||
first argument to the constructor, then simply assign document objects to the
|
||
field::
|
||
|
||
class User(Document):
|
||
name = StringField()
|
||
|
||
class Page(Document):
|
||
content = StringField()
|
||
author = ReferenceField(User)
|
||
|
||
john = User(name="John Smith")
|
||
john.save()
|
||
|
||
post = Page(content="Test Page")
|
||
post.author = john
|
||
post.save()
|
||
|
||
The :class:`User` object is automatically turned into a reference behind the
|
||
scenes, and dereferenced when the :class:`Page` object is retrieved.
|
||
|
||
To add a :class:`~mongoengine.fields.ReferenceField` that references the document
|
||
being defined, use the string ``'self'`` in place of the document class as the
|
||
argument to :class:`~mongoengine.fields.ReferenceField`'s constructor. To reference a
|
||
document that has not yet been defined, use the name of the undefined document
|
||
as the constructor's argument::
|
||
|
||
class Employee(Document):
|
||
name = StringField()
|
||
boss = ReferenceField('self')
|
||
profile_page = ReferenceField('ProfilePage')
|
||
|
||
class ProfilePage(Document):
|
||
content = StringField()
|
||
|
||
|
||
.. _many-to-many-with-listfields:
|
||
|
||
Many to Many with ListFields
|
||
'''''''''''''''''''''''''''
|
||
|
||
If you are implementing a many to many relationship via a list of references,
|
||
then the references are stored as DBRefs and to query you need to pass an
|
||
instance of the object to the query::
|
||
|
||
class User(Document):
|
||
name = StringField()
|
||
|
||
class Page(Document):
|
||
content = StringField()
|
||
authors = ListField(ReferenceField(User))
|
||
|
||
bob = User(name="Bob Jones").save()
|
||
john = User(name="John Smith").save()
|
||
|
||
Page(content="Test Page", authors=[bob, john]).save()
|
||
Page(content="Another Page", authors=[john]).save()
|
||
|
||
# Find all pages Bob authored
|
||
Page.objects(authors__in=[bob])
|
||
|
||
# Find all pages that both Bob and John have authored
|
||
Page.objects(authors__all=[bob, john])
|
||
|
||
# Remove Bob from the authors for a page.
|
||
Page.objects(id='...').update_one(pull__authors=bob)
|
||
|
||
# Add John to the authors for a page.
|
||
Page.objects(id='...').update_one(push__authors=john)
|
||
|
||
|
||
Dealing with deletion of referred documents
|
||
'''''''''''''''''''''''''''''''''''''''''''
|
||
By default, MongoDB doesn't check the integrity of your data, so deleting
|
||
documents that other documents still hold references to will lead to consistency
|
||
issues. Mongoengine's :class:`ReferenceField` adds some functionality to
|
||
safeguard against these kinds of database integrity problems, providing each
|
||
reference with a delete rule specification. A delete rule is specified by
|
||
supplying the :attr:`reverse_delete_rule` attributes on the
|
||
:class:`ReferenceField` definition, like this::
|
||
|
||
class ProfilePage(Document):
|
||
employee = ReferenceField('Employee', reverse_delete_rule=mongoengine.CASCADE)
|
||
|
||
The declaration in this example means that when an :class:`Employee` object is
|
||
removed, the :class:`ProfilePage` that references that employee is removed as
|
||
well. If a whole batch of employees is removed, all profile pages that are
|
||
linked are removed as well.
|
||
|
||
Its value can take any of the following constants:
|
||
|
||
:const:`mongoengine.DO_NOTHING`
|
||
This is the default and won't do anything. Deletes are fast, but may cause
|
||
database inconsistency or dangling references.
|
||
:const:`mongoengine.DENY`
|
||
Deletion is denied if there still exist references to the object being
|
||
deleted.
|
||
:const:`mongoengine.NULLIFY`
|
||
Any object's fields still referring to the object being deleted are set to None
|
||
(using MongoDB's "unset" operation), effectively nullifying the relationship.
|
||
:const:`mongoengine.CASCADE`
|
||
Any object containing fields that are referring to the object being deleted
|
||
are deleted first.
|
||
:const:`mongoengine.PULL`
|
||
Removes the reference to the object (using MongoDB's "pull" operation)
|
||
from any object's fields of
|
||
:class:`~mongoengine.fields.ListField` (:class:`~mongoengine.fields.ReferenceField`).
|
||
|
||
|
||
.. warning::
|
||
A safety note on setting up these delete rules! Since the delete rules are
|
||
not recorded on the database level by MongoDB itself, but instead at runtime,
|
||
in-memory, by the MongoEngine module, it is of the upmost importance
|
||
that the module that declares the relationship is loaded **BEFORE** the
|
||
delete is invoked.
|
||
|
||
If, for example, the :class:`Employee` object lives in the
|
||
:mod:`payroll` app, and the :class:`ProfilePage` in the :mod:`people`
|
||
app, it is extremely important that the :mod:`people` app is loaded
|
||
before any employee is removed, because otherwise, MongoEngine could
|
||
never know this relationship exists.
|
||
|
||
In Django, be sure to put all apps that have such delete rule declarations in
|
||
their :file:`models.py` in the :const:`INSTALLED_APPS` tuple.
|
||
|
||
Generic reference fields
|
||
''''''''''''''''''''''''
|
||
A second kind of reference field also exists,
|
||
:class:`~mongoengine.fields.GenericReferenceField`. This allows you to reference any
|
||
kind of :class:`~mongoengine.Document`, and hence doesn't take a
|
||
:class:`~mongoengine.Document` subclass as a constructor argument::
|
||
|
||
class Link(Document):
|
||
url = StringField()
|
||
|
||
class Post(Document):
|
||
title = StringField()
|
||
|
||
class Bookmark(Document):
|
||
bookmark_object = GenericReferenceField()
|
||
|
||
link = Link(url='http://hmarr.com/mongoengine/')
|
||
link.save()
|
||
|
||
post = Post(title='Using MongoEngine')
|
||
post.save()
|
||
|
||
Bookmark(bookmark_object=link).save()
|
||
Bookmark(bookmark_object=post).save()
|
||
|
||
.. note::
|
||
|
||
Using :class:`~mongoengine.fields.GenericReferenceField`\ s is slightly less
|
||
efficient than the standard :class:`~mongoengine.fields.ReferenceField`\ s, so if
|
||
you will only be referencing one document type, prefer the standard
|
||
:class:`~mongoengine.fields.ReferenceField`.
|
||
|
||
Uniqueness constraints
|
||
----------------------
|
||
MongoEngine allows you to specify that a field should be unique across a
|
||
collection by providing ``unique=True`` to a :class:`~mongoengine.fields.Field`\ 's
|
||
constructor. If you try to save a document that has the same value for a unique
|
||
field as a document that is already in the database, a
|
||
:class:`~mongoengine.NotUniqueError` will be raised. You may also specify
|
||
multi-field uniqueness constraints by using :attr:`unique_with`, which may be
|
||
either a single field name, or a list or tuple of field names::
|
||
|
||
class User(Document):
|
||
username = StringField(unique=True)
|
||
first_name = StringField()
|
||
last_name = StringField(unique_with='first_name')
|
||
|
||
|
||
Document collections
|
||
====================
|
||
Document classes that inherit **directly** from :class:`~mongoengine.Document`
|
||
will have their own **collection** in the database. The name of the collection
|
||
is by default the name of the class converted to snake_case (e.g if your Document class
|
||
is named `CompanyUser`, the corresponding collection would be `company_user`). If you need
|
||
to change the name of the collection (e.g. to use MongoEngine with an existing database),
|
||
then create a class dictionary attribute called :attr:`meta` on your document, and
|
||
set :attr:`collection` to the name of the collection that you want your
|
||
document class to use::
|
||
|
||
class Page(Document):
|
||
title = StringField(max_length=200, required=True)
|
||
meta = {'collection': 'cmsPage'}
|
||
|
||
Capped collections
|
||
------------------
|
||
A :class:`~mongoengine.Document` may use a **Capped Collection** by specifying
|
||
:attr:`max_documents` and :attr:`max_size` in the :attr:`meta` dictionary.
|
||
:attr:`max_documents` is the maximum number of documents that is allowed to be
|
||
stored in the collection, and :attr:`max_size` is the maximum size of the
|
||
collection in bytes. :attr:`max_size` is rounded up to the next multiple of 256
|
||
by MongoDB internally and mongoengine before. Use also a multiple of 256 to
|
||
avoid confusions. If :attr:`max_size` is not specified and
|
||
:attr:`max_documents` is, :attr:`max_size` defaults to 10485760 bytes (10MB).
|
||
The following example shows a :class:`Log` document that will be limited to
|
||
1000 entries and 2MB of disk space::
|
||
|
||
class Log(Document):
|
||
ip_address = StringField()
|
||
meta = {'max_documents': 1000, 'max_size': 2000000}
|
||
|
||
.. defining-indexes_
|
||
|
||
Indexes
|
||
=======
|
||
|
||
You can specify indexes on collections to make querying faster. This is done
|
||
by creating a list of index specifications called :attr:`indexes` in the
|
||
:attr:`~mongoengine.Document.meta` dictionary, where an index specification may
|
||
either be a single field name, a tuple containing multiple field names, or a
|
||
dictionary containing a full index definition.
|
||
|
||
A direction may be specified on fields by prefixing the field name with a
|
||
**+** (for ascending) or a **-** sign (for descending). Note that direction
|
||
only matters on multi-field indexes. Text indexes may be specified by prefixing
|
||
the field name with a **$**. Hashed indexes may be specified by prefixing
|
||
the field name with a **#**::
|
||
|
||
class Page(Document):
|
||
category = IntField()
|
||
title = StringField()
|
||
rating = StringField()
|
||
created = DateTimeField()
|
||
meta = {
|
||
'indexes': [
|
||
'title',
|
||
'$title', # text index
|
||
'#title', # hashed index
|
||
('title', '-rating'),
|
||
('category', '_cls'),
|
||
{
|
||
'fields': ['created'],
|
||
'expireAfterSeconds': 3600
|
||
}
|
||
]
|
||
}
|
||
|
||
If a dictionary is passed then additional options become available. Valid options include,
|
||
but are not limited to:
|
||
|
||
|
||
:attr:`fields` (Default: None)
|
||
The fields to index. Specified in the same format as described above.
|
||
|
||
:attr:`cls` (Default: True)
|
||
If you have polymorphic models that inherit and have
|
||
:attr:`allow_inheritance` turned on, you can configure whether the index
|
||
should have the :attr:`_cls` field added automatically to the start of the
|
||
index.
|
||
|
||
:attr:`sparse` (Default: False)
|
||
Whether the index should be sparse.
|
||
|
||
:attr:`unique` (Default: False)
|
||
Whether the index should be unique.
|
||
|
||
:attr:`expireAfterSeconds` (Optional)
|
||
Allows you to automatically expire data from a collection by setting the
|
||
time in seconds to expire the a field.
|
||
|
||
:attr:`name` (Optional)
|
||
Allows you to specify a name for the index
|
||
|
||
:attr:`collation` (Optional)
|
||
Allows to create case insensitive indexes (MongoDB v3.4+ only)
|
||
|
||
.. note::
|
||
|
||
Additional options are forwarded as **kwargs to pymongo's create_index method.
|
||
Inheritance adds extra fields indices see: :ref:`document-inheritance`.
|
||
|
||
Global index default options
|
||
----------------------------
|
||
|
||
There are a few top level defaults for all indexes that can be set::
|
||
|
||
class Page(Document):
|
||
title = StringField()
|
||
rating = StringField()
|
||
meta = {
|
||
'index_opts': {},
|
||
'index_background': True,
|
||
'index_cls': False,
|
||
'auto_create_index': True,
|
||
}
|
||
|
||
|
||
:attr:`index_opts` (Optional)
|
||
Set any default index options - see the `full options list <https://docs.mongodb.com/manual/reference/method/db.collection.createIndex/#db.collection.createIndex>`_
|
||
|
||
:attr:`index_background` (Optional)
|
||
Set the default value for if an index should be indexed in the background
|
||
|
||
:attr:`index_cls` (Optional)
|
||
A way to turn off a specific index for _cls.
|
||
|
||
:attr:`auto_create_index` (Optional)
|
||
When this is True (default), MongoEngine will ensure that the correct
|
||
indexes exist in MongoDB each time a command is run. This can be disabled
|
||
in systems where indexes are managed separately. Disabling this will improve
|
||
performance.
|
||
|
||
|
||
Compound Indexes and Indexing sub documents
|
||
-------------------------------------------
|
||
|
||
Compound indexes can be created by adding the Embedded field or dictionary
|
||
field name to the index definition.
|
||
|
||
Sometimes its more efficient to index parts of Embedded / dictionary fields,
|
||
in this case use 'dot' notation to identify the value to index eg: `rank.title`
|
||
|
||
.. _geospatial-indexes:
|
||
|
||
Geospatial indexes
|
||
------------------
|
||
|
||
The best geo index for mongodb is the new "2dsphere", which has an improved
|
||
spherical model and provides better performance and more options when querying.
|
||
The following fields will explicitly add a "2dsphere" index:
|
||
|
||
- :class:`~mongoengine.fields.PointField`
|
||
- :class:`~mongoengine.fields.LineStringField`
|
||
- :class:`~mongoengine.fields.PolygonField`
|
||
- :class:`~mongoengine.fields.MultiPointField`
|
||
- :class:`~mongoengine.fields.MultiLineStringField`
|
||
- :class:`~mongoengine.fields.MultiPolygonField`
|
||
|
||
As "2dsphere" indexes can be part of a compound index, you may not want the
|
||
automatic index but would prefer a compound index. In this example we turn off
|
||
auto indexing and explicitly declare a compound index on ``location`` and ``datetime``::
|
||
|
||
class Log(Document):
|
||
location = PointField(auto_index=False)
|
||
datetime = DateTimeField()
|
||
|
||
meta = {
|
||
'indexes': [[("location", "2dsphere"), ("datetime", 1)]]
|
||
}
|
||
|
||
|
||
Pre MongoDB 2.4 Geo
|
||
'''''''''''''''''''
|
||
|
||
.. note:: For MongoDB < 2.4 this is still current, however the new 2dsphere
|
||
index is a big improvement over the previous 2D model - so upgrading is
|
||
advised.
|
||
|
||
Geospatial indexes will be automatically created for all
|
||
:class:`~mongoengine.fields.GeoPointField`\ s
|
||
|
||
It is also possible to explicitly define geospatial indexes. This is
|
||
useful if you need to define a geospatial index on a subfield of a
|
||
:class:`~mongoengine.fields.DictField` or a custom field that contains a
|
||
point. To create a geospatial index you must prefix the field with the
|
||
***** sign. ::
|
||
|
||
class Place(Document):
|
||
location = DictField()
|
||
meta = {
|
||
'indexes': [
|
||
'*location.point',
|
||
],
|
||
}
|
||
|
||
Time To Live indexes
|
||
--------------------
|
||
|
||
A special index type that allows you to automatically expire data from a
|
||
collection after a given period. See the official
|
||
`ttl <http://docs.mongodb.org/manual/tutorial/expire-data/#expire-data-from-collections-by-setting-ttl>`_
|
||
documentation for more information. A common usecase might be session data::
|
||
|
||
class Session(Document):
|
||
created = DateTimeField(default=datetime.utcnow)
|
||
meta = {
|
||
'indexes': [
|
||
{'fields': ['created'], 'expireAfterSeconds': 3600}
|
||
]
|
||
}
|
||
|
||
.. warning:: TTL indexes happen on the MongoDB server and not in the application
|
||
code, therefore no signals will be fired on document deletion.
|
||
If you need signals to be fired on deletion, then you must handle the
|
||
deletion of Documents in your application code.
|
||
|
||
Comparing Indexes
|
||
-----------------
|
||
|
||
Use :func:`mongoengine.Document.compare_indexes` to compare actual indexes in
|
||
the database to those that your document definitions define. This is useful
|
||
for maintenance purposes and ensuring you have the correct indexes for your
|
||
schema.
|
||
|
||
Ordering
|
||
========
|
||
A default ordering can be specified for your
|
||
:class:`~mongoengine.queryset.QuerySet` using the :attr:`ordering` attribute of
|
||
:attr:`~mongoengine.Document.meta`. Ordering will be applied when the
|
||
:class:`~mongoengine.queryset.QuerySet` is created, and can be overridden by
|
||
subsequent calls to :meth:`~mongoengine.queryset.QuerySet.order_by`. ::
|
||
|
||
from datetime import datetime
|
||
|
||
class BlogPost(Document):
|
||
title = StringField()
|
||
published_date = DateTimeField()
|
||
|
||
meta = {
|
||
'ordering': ['-published_date']
|
||
}
|
||
|
||
blog_post_1 = BlogPost(title="Blog Post #1")
|
||
blog_post_1.published_date = datetime(2010, 1, 5, 0, 0 ,0)
|
||
|
||
blog_post_2 = BlogPost(title="Blog Post #2")
|
||
blog_post_2.published_date = datetime(2010, 1, 6, 0, 0 ,0)
|
||
|
||
blog_post_3 = BlogPost(title="Blog Post #3")
|
||
blog_post_3.published_date = datetime(2010, 1, 7, 0, 0 ,0)
|
||
|
||
blog_post_1.save()
|
||
blog_post_2.save()
|
||
blog_post_3.save()
|
||
|
||
# get the "first" BlogPost using default ordering
|
||
# from BlogPost.meta.ordering
|
||
latest_post = BlogPost.objects.first()
|
||
assert latest_post.title == "Blog Post #3"
|
||
|
||
# override default ordering, order BlogPosts by "published_date"
|
||
first_post = BlogPost.objects.order_by("+published_date").first()
|
||
assert first_post.title == "Blog Post #1"
|
||
|
||
Shard keys
|
||
==========
|
||
|
||
If your collection is sharded by multiple keys, then you can improve shard
|
||
routing (and thus the performance of your application) by specifying the shard
|
||
key, using the :attr:`shard_key` attribute of
|
||
:attr:`~mongoengine.Document.meta`. The shard key should be defined as a tuple.
|
||
|
||
This ensures that the full shard key is sent with the query when calling
|
||
methods such as :meth:`~mongoengine.document.Document.save`,
|
||
:meth:`~mongoengine.document.Document.update`,
|
||
:meth:`~mongoengine.document.Document.modify`, or
|
||
:meth:`~mongoengine.document.Document.delete` on an existing
|
||
:class:`~mongoengine.Document` instance::
|
||
|
||
class LogEntry(Document):
|
||
machine = StringField()
|
||
app = StringField()
|
||
timestamp = DateTimeField()
|
||
data = StringField()
|
||
|
||
meta = {
|
||
'shard_key': ('machine', 'timestamp'),
|
||
'indexes': ('machine', 'timestamp'),
|
||
}
|
||
|
||
.. _document-inheritance:
|
||
|
||
Document inheritance
|
||
====================
|
||
|
||
To create a specialised type of a :class:`~mongoengine.Document` you have
|
||
defined, you may subclass it and add any extra fields or methods you may need.
|
||
As this new class is not a direct subclass of
|
||
:class:`~mongoengine.Document`, it will not be stored in its own collection; it
|
||
will use the same collection as its superclass uses. This allows for more
|
||
convenient and efficient retrieval of related documents -- all you need do is
|
||
set :attr:`allow_inheritance` to True in the :attr:`meta` data for a
|
||
document.::
|
||
|
||
# Stored in a collection named 'page'
|
||
class Page(Document):
|
||
title = StringField(max_length=200, required=True)
|
||
|
||
meta = {'allow_inheritance': True}
|
||
|
||
# Also stored in the collection named 'page'
|
||
class DatedPage(Page):
|
||
date = DateTimeField()
|
||
|
||
.. note:: From 0.8 onwards :attr:`allow_inheritance` defaults
|
||
to False, meaning you must set it to True to use inheritance.
|
||
|
||
Setting :attr:`allow_inheritance` to True should also be used in
|
||
:class:`~mongoengine.EmbeddedDocument` class in case you need to subclass it
|
||
|
||
When it comes to querying using :attr:`.objects()`, querying `Page.objects()` will query
|
||
both `Page` and `DatedPage` whereas querying `DatedPage` will only query the `DatedPage` documents.
|
||
Behind the scenes, MongoEngine deals with inheritance by adding a :attr:`_cls` attribute that contains
|
||
the class name in every documents. When a document is loaded, MongoEngine checks
|
||
it's :attr:`_cls` attribute and use that class to construct the instance.::
|
||
|
||
Page(title='a funky title').save()
|
||
DatedPage(title='another title', date=datetime.utcnow()).save()
|
||
|
||
print(Page.objects().count()) # 2
|
||
print(DatedPage.objects().count()) # 1
|
||
|
||
# print documents in their native form
|
||
# we remove 'id' to avoid polluting the output with unnecessary detail
|
||
qs = Page.objects.exclude('id').as_pymongo()
|
||
print(list(qs))
|
||
# [
|
||
# {'_cls': u 'Page', 'title': 'a funky title'},
|
||
# {'_cls': u 'Page.DatedPage', 'title': u 'another title', 'date': datetime.datetime(2019, 12, 13, 20, 16, 59, 993000)}
|
||
# ]
|
||
|
||
Working with existing data
|
||
--------------------------
|
||
As MongoEngine no longer defaults to needing :attr:`_cls`, you can quickly and
|
||
easily get working with existing data. Just define the document to match
|
||
the expected schema in your database ::
|
||
|
||
# Will work with data in an existing collection named 'cmsPage'
|
||
class Page(Document):
|
||
title = StringField(max_length=200, required=True)
|
||
meta = {
|
||
'collection': 'cmsPage'
|
||
}
|
||
|
||
If you have wildly varying schemas then using a
|
||
:class:`~mongoengine.DynamicDocument` might be more appropriate, instead of
|
||
defining all possible field types.
|
||
|
||
If you use :class:`~mongoengine.Document` and the database contains data that
|
||
isn't defined then that data will be stored in the `document._data` dictionary.
|
||
|
||
Abstract classes
|
||
================
|
||
|
||
If you want to add some extra functionality to a group of Document classes but
|
||
you don't need or want the overhead of inheritance you can use the
|
||
:attr:`abstract` attribute of :attr:`~mongoengine.Document.meta`.
|
||
This won't turn on :ref:`document-inheritance` but will allow you to keep your
|
||
code DRY::
|
||
|
||
class BaseDocument(Document):
|
||
meta = {
|
||
'abstract': True,
|
||
}
|
||
def check_permissions(self):
|
||
...
|
||
|
||
class User(BaseDocument):
|
||
...
|
||
|
||
Now the User class will have access to the inherited `check_permissions` method
|
||
and won't store any of the extra `_cls` information.
|