Configurable cascading saves
Updated cascading save logic - can now add meta or pass cascade to save(). Also Cleaned up reset changed fields logic as well, so less looping Refs: #370 #349
This commit is contained in:
parent
4607b08be5
commit
e1bb453f32
@ -120,7 +120,8 @@ class Document(BaseDocument):
|
|||||||
self._collection = db[collection_name]
|
self._collection = db[collection_name]
|
||||||
return self._collection
|
return self._collection
|
||||||
|
|
||||||
def save(self, safe=True, force_insert=False, validate=True, write_options=None, _refs=None):
|
def save(self, safe=True, force_insert=False, validate=True, write_options=None,
|
||||||
|
cascade=None, _refs=None):
|
||||||
"""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.
|
||||||
@ -138,14 +139,19 @@ class Document(BaseDocument):
|
|||||||
which will be used as options for the resultant ``getLastError`` command.
|
which will be used as options for the resultant ``getLastError`` command.
|
||||||
For example, ``save(..., w=2, fsync=True)`` will wait until at least two servers
|
For example, ``save(..., w=2, fsync=True)`` will wait until at least two servers
|
||||||
have recorded the write and will force an fsync on each server being written to.
|
have recorded the write and will force an fsync on each server being written to.
|
||||||
|
:param cascade: Sets the flag for cascading saves. You can set a default by setting
|
||||||
|
"cascade" in the document __meta__
|
||||||
|
:param _refs: A list of processed references used in cascading saves
|
||||||
|
|
||||||
.. versionchanged:: 0.5
|
.. versionchanged:: 0.5
|
||||||
In existing documents it only saves changed fields using set / unset
|
In existing documents it only saves changed fields using set / unset
|
||||||
Saves are cascaded and any :class:`~pymongo.dbref.DBRef` objects
|
Saves are cascaded and any :class:`~pymongo.dbref.DBRef` objects
|
||||||
that have changes are saved as well.
|
that have changes are saved as well.
|
||||||
"""
|
.. versionchanged:: 0.6
|
||||||
from fields import ReferenceField, GenericReferenceField
|
Cascade saves are optional = defaults to True, if you want fine grain
|
||||||
|
control then you can turn off using document meta['cascade'] = False
|
||||||
|
|
||||||
|
"""
|
||||||
signals.pre_save.send(self.__class__, document=self)
|
signals.pre_save.send(self.__class__, document=self)
|
||||||
|
|
||||||
if validate:
|
if validate:
|
||||||
@ -173,16 +179,11 @@ class Document(BaseDocument):
|
|||||||
if removals:
|
if removals:
|
||||||
collection.update({'_id': object_id}, {"$unset": removals}, upsert=True, safe=safe, **write_options)
|
collection.update({'_id': object_id}, {"$unset": removals}, upsert=True, safe=safe, **write_options)
|
||||||
|
|
||||||
# Save any references / generic references
|
cascade = self._meta.get('cascade', True) if cascade is None else cascade
|
||||||
_refs = _refs or []
|
if cascade:
|
||||||
for name, cls in self._fields.items():
|
self.cascade_save(safe=safe, force_insert=force_insert,
|
||||||
if isinstance(cls, (ReferenceField, GenericReferenceField)):
|
validate=validate, write_options=write_options,
|
||||||
ref = getattr(self, name)
|
cascade=cascade, _refs=_refs)
|
||||||
if ref and str(ref) not in _refs:
|
|
||||||
_refs.append(str(ref))
|
|
||||||
ref.save(safe=safe, force_insert=force_insert,
|
|
||||||
validate=validate, write_options=write_options,
|
|
||||||
_refs=_refs)
|
|
||||||
|
|
||||||
except pymongo.errors.OperationFailure, err:
|
except pymongo.errors.OperationFailure, err:
|
||||||
message = 'Could not save document (%s)'
|
message = 'Could not save document (%s)'
|
||||||
@ -192,23 +193,26 @@ class Document(BaseDocument):
|
|||||||
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 reset_changed_fields(doc, inspected=None):
|
|
||||||
"""Loop through and reset changed fields lists"""
|
|
||||||
|
|
||||||
inspected = inspected or []
|
|
||||||
inspected.append(doc)
|
|
||||||
if hasattr(doc, '_changed_fields'):
|
|
||||||
doc._changed_fields = []
|
|
||||||
|
|
||||||
for field_name in doc._fields:
|
|
||||||
field = getattr(doc, field_name)
|
|
||||||
if field not in inspected and hasattr(field, '_changed_fields'):
|
|
||||||
reset_changed_fields(field, inspected)
|
|
||||||
|
|
||||||
reset_changed_fields(self)
|
|
||||||
self._changed_fields = []
|
self._changed_fields = []
|
||||||
signals.post_save.send(self.__class__, document=self, created=creation_mode)
|
signals.post_save.send(self.__class__, document=self, created=creation_mode)
|
||||||
|
|
||||||
|
def cascade_save(self, *args, **kwargs):
|
||||||
|
"""Recursively saves any references / generic references on an object"""
|
||||||
|
from fields import ReferenceField, GenericReferenceField
|
||||||
|
_refs = kwargs.get('_refs', []) or []
|
||||||
|
for name, cls in self._fields.items():
|
||||||
|
if not isinstance(cls, (ReferenceField, GenericReferenceField)):
|
||||||
|
continue
|
||||||
|
ref = getattr(self, name)
|
||||||
|
if not ref:
|
||||||
|
continue
|
||||||
|
ref_id = "%s,%s" % (ref.__class__.__name__, str(ref._data))
|
||||||
|
if ref and ref_id not in _refs:
|
||||||
|
_refs.append(ref_id)
|
||||||
|
kwargs["_refs"] = _refs
|
||||||
|
ref.save(**kwargs)
|
||||||
|
ref._changed_fields = []
|
||||||
|
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs):
|
||||||
"""Performs an update on the :class:`~mongoengine.Document`
|
"""Performs an update on the :class:`~mongoengine.Document`
|
||||||
A convenience wrapper to :meth:`~mongoengine.QuerySet.update`.
|
A convenience wrapper to :meth:`~mongoengine.QuerySet.update`.
|
||||||
|
@ -1228,6 +1228,35 @@ class DocumentTest(unittest.TestCase):
|
|||||||
p1.reload()
|
p1.reload()
|
||||||
self.assertEquals(p1.name, p.parent.name)
|
self.assertEquals(p1.name, p.parent.name)
|
||||||
|
|
||||||
|
def test_save_cascade_meta(self):
|
||||||
|
|
||||||
|
class Person(Document):
|
||||||
|
name = StringField()
|
||||||
|
parent = ReferenceField('self')
|
||||||
|
|
||||||
|
meta = {'cascade': False}
|
||||||
|
|
||||||
|
Person.drop_collection()
|
||||||
|
|
||||||
|
p1 = Person(name="Wilson Snr")
|
||||||
|
p1.parent = None
|
||||||
|
p1.save()
|
||||||
|
|
||||||
|
p2 = Person(name="Wilson Jr")
|
||||||
|
p2.parent = p1
|
||||||
|
p2.save()
|
||||||
|
|
||||||
|
p = Person.objects(name="Wilson Jr").get()
|
||||||
|
p.parent.name = "Daddy Wilson"
|
||||||
|
p.save()
|
||||||
|
|
||||||
|
p1.reload()
|
||||||
|
self.assertNotEquals(p1.name, p.parent.name)
|
||||||
|
|
||||||
|
p.save(cascade=True)
|
||||||
|
p1.reload()
|
||||||
|
self.assertEquals(p1.name, p.parent.name)
|
||||||
|
|
||||||
def test_save_cascades_generically(self):
|
def test_save_cascades_generically(self):
|
||||||
|
|
||||||
class Person(Document):
|
class Person(Document):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user