Updated docs, added force_insert to save()

This commit is contained in:
Harry Marr 2010-01-11 04:12:51 +00:00
parent ec927bdd63
commit afd416c84e
6 changed files with 94 additions and 8 deletions

View File

@ -27,6 +27,8 @@ Querying
.. autoclass:: mongoengine.queryset.QuerySet .. autoclass:: mongoengine.queryset.QuerySet
:members: :members:
.. automethod:: mongoengine.queryset.QuerySet.__call__
.. autofunction:: mongoengine.queryset.queryset_manager .. autofunction:: mongoengine.queryset.queryset_manager
Fields Fields

View File

@ -141,6 +141,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
simple_class = True simple_class = True
id_field = None id_field = None
base_indexes = []
# Subclassed documents inherit collection from superclass # Subclassed documents inherit collection from superclass
for base in bases: for base in bases:
@ -156,6 +157,7 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
collection = base._meta['collection'] collection = base._meta['collection']
id_field = id_field or base._meta.get('id_field') id_field = id_field or base._meta.get('id_field')
base_indexes += base._meta.get('indexes', [])
meta = { meta = {
'collection': collection, 'collection': collection,
@ -169,6 +171,8 @@ class TopLevelDocumentMetaclass(DocumentMetaclass):
# Apply document-defined meta options # Apply document-defined meta options
meta.update(attrs.get('meta', {})) meta.update(attrs.get('meta', {}))
meta['indexes'] += base_indexes
# Only simple classes - direct subclasses of Document - may set # Only simple classes - direct subclasses of Document - may set
# allow_inheritance to False # allow_inheritance to False

View File

@ -56,31 +56,51 @@ class Document(BaseDocument):
__metaclass__ = TopLevelDocumentMetaclass __metaclass__ = TopLevelDocumentMetaclass
def save(self, safe=True): def save(self, safe=True, force_insert=False):
"""Save the :class:`~mongoengine.Document` to the database. If the """Save the :class:`~mongoengine.Document` to the database. If the
document already exists, it will be updated, otherwise it will be document already exists, it will be updated, otherwise it will be
created. created.
If ``safe=True`` and the operation is unsuccessful, an
:class:`~mongoengine.OperationError` will be raised.
:param safe: check if the operation succeeded before returning
:param force_insert: only try to create a new document, don't allow
updates of existing documents
""" """
self.validate() self.validate()
doc = self.to_mongo() doc = self.to_mongo()
try: try:
object_id = self.__class__.objects._collection.save(doc, safe=safe) collection = self.__class__.objects._collection
if force_insert:
object_id = collection.insert(doc, safe=safe)
else:
object_id = collection.save(doc, safe=safe)
except pymongo.errors.OperationFailure, err: except pymongo.errors.OperationFailure, err:
raise OperationError('Tried to save duplicate unique keys (%s)' message = 'Could not save document (%s)'
% str(err)) if 'duplicate key' in str(err):
message = 'Tried to save duplicate unique keys (%s)'
raise OperationError(message % str(err))
id_field = self._meta['id_field'] id_field = self._meta['id_field']
self[id_field] = self._fields[id_field].to_python(object_id) self[id_field] = self._fields[id_field].to_python(object_id)
def delete(self): def delete(self, safe=False):
"""Delete the :class:`~mongoengine.Document` from the database. This """Delete the :class:`~mongoengine.Document` from the database. This
will only take effect if the document has been previously saved. will only take effect if the document has been previously saved.
:param safe: check if the operation succeeded before returning
""" """
id_field = self._meta['id_field'] id_field = self._meta['id_field']
object_id = self._fields[id_field].to_mongo(self[id_field]) object_id = self._fields[id_field].to_mongo(self[id_field])
self.__class__.objects(**{id_field: object_id}).delete() try:
self.__class__.objects(**{id_field: object_id}).delete(safe=safe)
except pymongo.errors.OperationFailure, err:
raise OperationError('Could not delete document (%s)' % str(err))
def reload(self): def reload(self):
"""Reloads all attributes from the database. """Reloads all attributes from the database.
.. versionadded:: 0.1.2
""" """
id_field = self._meta['id_field'] id_field = self._meta['id_field']
obj = self.__class__.objects(**{id_field: self[id_field]}).first() obj = self.__class__.objects(**{id_field: self[id_field]}).first()

View File

@ -82,6 +82,8 @@ class FloatField(BaseField):
class BooleanField(BaseField): class BooleanField(BaseField):
"""A boolean field type. """A boolean field type.
.. versionadded:: 0.1.2
""" """
def to_python(self, value): def to_python(self, value):

View File

@ -121,6 +121,10 @@ class QuerySet(object):
def ensure_index(self, key_or_list): def ensure_index(self, key_or_list):
"""Ensure that the given indexes are in place. """Ensure that the given indexes are in place.
:param key_or_list: a single index key or a list of index keys (to
construct a multi-field index); keys may be prefixed with a **+**
or a **-** to determine the index ordering
""" """
if isinstance(key_or_list, basestring): if isinstance(key_or_list, basestring):
key_or_list = [key_or_list] key_or_list = [key_or_list]
@ -146,6 +150,9 @@ class QuerySet(object):
def __call__(self, *q_objs, **query): def __call__(self, *q_objs, **query):
"""Filter the selected documents by calling the """Filter the selected documents by calling the
:class:`~mongoengine.QuerySet` with a query. :class:`~mongoengine.QuerySet` with a query.
:param q_objs: :class:`~mongoengine.Q` objects to be used in the query
:param query: Django-style query keyword arguments
""" """
for q in q_objs: for q in q_objs:
self._where_clauses.append(q.as_js(self._document)) self._where_clauses.append(q.as_js(self._document))
@ -269,6 +276,8 @@ class QuerySet(object):
def with_id(self, object_id): def with_id(self, object_id):
"""Retrieve the object matching the id provided. """Retrieve the object matching the id provided.
:param object_id: the value for the id of the document to look up
""" """
id_field = self._document._meta['id_field'] id_field = self._document._meta['id_field']
object_id = self._document._fields[id_field].to_mongo(object_id) object_id = self._document._fields[id_field].to_mongo(object_id)
@ -294,6 +303,8 @@ class QuerySet(object):
def limit(self, n): def limit(self, n):
"""Limit the number of returned documents to `n`. This may also be """Limit the number of returned documents to `n`. This may also be
achieved using array-slicing syntax (e.g. ``User.objects[:5]``). achieved using array-slicing syntax (e.g. ``User.objects[:5]``).
:param n: the maximum number of objects to return
""" """
self._cursor.limit(n) self._cursor.limit(n)
# Return self to allow chaining # Return self to allow chaining
@ -302,6 +313,8 @@ class QuerySet(object):
def skip(self, n): def skip(self, n):
"""Skip `n` documents before returning the results. This may also be """Skip `n` documents before returning the results. This may also be
achieved using array-slicing syntax (e.g. ``User.objects[5:]``). achieved using array-slicing syntax (e.g. ``User.objects[5:]``).
:param n: the number of objects to skip before returning results
""" """
self._cursor.skip(n) self._cursor.skip(n)
return self return self
@ -322,6 +335,9 @@ class QuerySet(object):
"""Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The """Order the :class:`~mongoengine.queryset.QuerySet` by the keys. The
order may be specified by prepending each of the keys by a + or a -. order may be specified by prepending each of the keys by a + or a -.
Ascending order is assumed. Ascending order is assumed.
:param keys: fields to order the query results by; keys may be
prefixed with **+** or **-** to determine the ordering direction
""" """
key_list = [] key_list = []
for key in keys: for key in keys:
@ -338,6 +354,8 @@ class QuerySet(object):
def explain(self, format=False): def explain(self, format=False):
"""Return an explain plan record for the """Return an explain plan record for the
:class:`~mongoengine.queryset.QuerySet`\ 's cursor. :class:`~mongoengine.queryset.QuerySet`\ 's cursor.
:param format: format the plan before returning it
""" """
plan = self._cursor.explain() plan = self._cursor.explain()
@ -346,10 +364,12 @@ class QuerySet(object):
plan = pprint.pformat(plan) plan = pprint.pformat(plan)
return plan return plan
def delete(self): def delete(self, safe=False):
"""Delete the documents matched by the query. """Delete the documents matched by the query.
:param safe: check if the operation succeeded before returning
""" """
self._collection.remove(self._query) self._collection.remove(self._query, safe=safe)
@classmethod @classmethod
def _transform_update(cls, _doc_cls=None, **update): def _transform_update(cls, _doc_cls=None, **update):
@ -402,6 +422,11 @@ class QuerySet(object):
def update(self, safe_update=True, **update): def update(self, safe_update=True, **update):
"""Perform an atomic update on the fields matched by the query. """Perform an atomic update on the fields matched by the query.
:param safe: check if the operation succeeded before returning
:param update: Django-style update keyword arguments
.. versionadded:: 0.2
""" """
if pymongo.version < '1.1.1': if pymongo.version < '1.1.1':
raise OperationError('update() method requires PyMongo 1.1.1+') raise OperationError('update() method requires PyMongo 1.1.1+')
@ -417,6 +442,11 @@ class QuerySet(object):
def update_one(self, safe_update=True, **update): def update_one(self, safe_update=True, **update):
"""Perform an atomic update on first field matched by the query. """Perform an atomic update on first field matched by the query.
:param safe: check if the operation succeeded before returning
:param update: Django-style update keyword arguments
.. versionadded:: 0.2
""" """
update = QuerySet._transform_update(self._document, **update) update = QuerySet._transform_update(self._document, **update)
try: try:
@ -442,6 +472,12 @@ class QuerySet(object):
collection in use; ``query``, which is an object representing the collection in use; ``query``, which is an object representing the
current query; and ``options``, which is an object containing any current query; and ``options``, which is an object containing any
options specified as keyword arguments. options specified as keyword arguments.
:param code: a string of Javascript code to execute
:param fields: fields that you will be using in your function, which
will be passed in to your function as arguments
:param options: options that you want available to the function
(accessed in Javascript through the ``options`` object)
""" """
fields = [QuerySet._translate_field_name(self._document, f) fields = [QuerySet._translate_field_name(self._document, f)
for f in fields] for f in fields]
@ -458,6 +494,9 @@ class QuerySet(object):
def sum(self, field): def sum(self, field):
"""Sum over the values of the specified field. """Sum over the values of the specified field.
:param field: the field to sum over; use dot-notation to refer to
embedded document fields
""" """
sum_func = """ sum_func = """
function(sumField) { function(sumField) {
@ -472,6 +511,9 @@ class QuerySet(object):
def average(self, field): def average(self, field):
"""Average over the values of the specified field. """Average over the values of the specified field.
:param field: the field to average over; use dot-notation to refer to
embedded document fields
""" """
average_func = """ average_func = """
function(averageField) { function(averageField) {
@ -492,6 +534,9 @@ class QuerySet(object):
"""Returns a dictionary of all items present in a list field across """Returns a dictionary of all items present in a list field across
the whole queried set of documents, and their corresponding frequency. the whole queried set of documents, and their corresponding frequency.
This is useful for generating tag clouds, or searching documents. This is useful for generating tag clouds, or searching documents.
:param list_field: the list field to use
:param normalize: normalize the results so they add to 1.0
""" """
freq_func = """ freq_func = """
function(listField) { function(listField) {

View File

@ -245,6 +245,19 @@ class DocumentTest(unittest.TestCase):
self.assertTrue([('_types', 1), ('category', 1), ('addDate', -1)] self.assertTrue([('_types', 1), ('category', 1), ('addDate', -1)]
in info.values()) in info.values())
self.assertTrue([('_types', 1), ('addDate', -1)] in info.values()) self.assertTrue([('_types', 1), ('addDate', -1)] in info.values())
class ExtendedBlogPost(BlogPost):
title = StringField()
meta = {'indexes': ['title']}
BlogPost.drop_collection()
list(ExtendedBlogPost.objects)
info = ExtendedBlogPost.objects._collection.index_information()
self.assertTrue([('_types', 1), ('category', 1), ('addDate', -1)]
in info.values())
self.assertTrue([('_types', 1), ('addDate', -1)] in info.values())
self.assertTrue([('_types', 1), ('title', 1)] in info.values())
BlogPost.drop_collection() BlogPost.drop_collection()