def __setattr__(self, name, value): # Handle dynamic data only if an initialised dynamic document if self._dynamic and not self._dynamic_lock: field = None if not hasattr(self, name) and not name.startswith('_'): field = BaseDynamicField(db_field=name) field.name = name self._dynamic_fields[name] = field if not name.startswith('_'): value = self.__expand_dynamic_values(name, value) # Handle marking data as changed if name in self._dynamic_fields: self._data[name] = value if hasattr(self, '_changed_fields'): self._mark_as_changed(name) # Handle None values for required fields if value is None and name in getattr(self, '_fields', {}): self._data[name] = value if hasattr(self, '_changed_fields'): self._mark_as_changed(name) return if not self._created and name in self._meta.get('shard_key', tuple()): from queryset import OperationError raise OperationError("Shard Keys are immutable. Tried to update %s" % name) super(BaseDocument, self).__setattr__(name, value)
def save(self, safe=True, force_insert=False, validate=True): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be 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 :param validate: validates the document; set to ``False`` for skiping """ if validate: self.validate() doc = self.to_mongo() try: 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: message = 'Could not save document (%s)' if u'duplicate key' in unicode(err): message = u'Tried to save duplicate unique keys (%s)' raise OperationError(message % unicode(err))
def to_dbref(self): """Returns an instance of :class:`~bson.dbref.DBRef` useful in `__raw__` queries.""" if not self.pk: msg = "Only saved documents can have a valid dbref" raise OperationError(msg) return DBRef(self.__class__._get_collection_name(), self.pk)
def update(self, **kwargs): """Performs an update on the :class:`~mongoengine.Document` A convenience wrapper to :meth:`~mongoengine.QuerySet.update`. Raises :class:`OperationError` if called on an object that has not yet been saved. """ if not self.pk: raise OperationError('attempt to update a document not yet saved') return self.__class__.objects(pk=self.pk).update_one(**kwargs)
def delete(self, safe=False): """Delete the :class:`~mongoengine.Document` from the database. This 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'] object_id = self._fields[id_field].to_mongo(self[id_field]) try: self.__class__.objects(**{id_field: object_id}).delete(safe=safe) except pymongo.errors.OperationFailure, err: message = u'Could not delete document (%s)' % err.message raise OperationError(message)
def delete(self, safe=False): """Delete the :class:`~mongoengine.Document` from the database. This will only take effect if the document has been previously saved. :param safe: check if the operation succeeded before returning """ signals.pre_delete.send(self.__class__, document=self) try: self.__class__.objects(pk=self.pk).delete(safe=safe) except pymongo.errors.OperationFailure, err: message = u'Could not delete document (%s)' % err.message raise OperationError(message)
def update(self, **kwargs): """Performs an update on the :class:`~mongoengine.Document` A convenience wrapper to :meth:`~mongoengine.QuerySet.update`. Raises :class:`OperationError` if called on an object that has not yet been saved. """ if not self.pk: raise OperationError('attempt to update a document not yet saved') # Need to add shard key to query, or you get an error select_dict = {'pk': self.pk} shard_key = self.__class__._meta.get('shard_key', tuple()) for k in shard_key: select_dict[k] = getattr(self, k) return self.__class__.objects(**select_dict).update_one(**kwargs)
def reload(self, max_depth=1): """Reloads all attributes from the database. .. versionadded:: 0.1.2 .. versionchanged:: 0.6 Now chainable """ id_field = self._meta['id_field'] obj = self.__class__.objects(**{ id_field: self[id_field] }).limit(1).select_related(max_depth=max_depth) if obj: obj = obj[0] else: msg = "Reloaded document has been deleted" raise OperationError(msg) for field in self._fields: setattr(self, field, self._reload(field, obj[field])) if self._dynamic: for name in self._dynamic_fields.keys(): setattr(self, name, self._reload(name, obj._data[name])) self._changed_fields = obj._changed_fields return obj
def save(self, safe=True, force_insert=False, validate=True, write_options=None, cascade=None, cascade_kwargs=None, _refs=None): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be 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 :param validate: validates the document; set to ``False`` to skip. :param write_options: Extra keyword arguments are passed down to :meth:`~pymongo.collection.Collection.save` OR :meth:`~pymongo.collection.Collection.insert` which will be used as options for the resultant ``getLastError`` command. For example, ``save(..., write_options={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. :param cascade: Sets the flag for cascading saves. You can set a default by setting "cascade" in the document __meta__ :param cascade_kwargs: optional kwargs dictionary to be passed throw to cascading saves :param _refs: A list of processed references used in cascading saves .. versionchanged:: 0.5 In existing documents it only saves changed fields using set / unset Saves are cascaded and any :class:`~bson.dbref.DBRef` objects that have changes are saved as well. .. versionchanged:: 0.6 Cascade saves are optional = defaults to True, if you want fine grain control then you can turn off using document meta['cascade'] = False Also you can pass different kwargs to the cascade save using cascade_kwargs which overwrites the existing kwargs with custom values """ signals.pre_save.send(self.__class__, document=self) if validate: self.validate() if not write_options: write_options = {} doc = self.to_mongo() created = force_insert or '_id' not in doc try: collection = self.__class__.objects._collection if created: if force_insert: object_id = collection.insert(doc, safe=safe, **write_options) else: object_id = collection.save(doc, safe=safe, **write_options) else: object_id = doc['_id'] updates, removals = self._delta() # Need to add shard key to query, or you get an error select_dict = {'_id': object_id} shard_key = self.__class__._meta.get('shard_key', tuple()) for k in shard_key: actual_key = self._db_field_map.get(k, k) select_dict[actual_key] = doc[actual_key] upsert = self._created if updates: collection.update(select_dict, {"$set": updates}, upsert=upsert, safe=safe, **write_options) if removals: collection.update(select_dict, {"$unset": removals}, upsert=upsert, safe=safe, **write_options) cascade = self._meta.get('cascade', True) if cascade is None else cascade if cascade: kwargs = { "safe": safe, "force_insert": force_insert, "validate": validate, "write_options": write_options, "cascade": cascade } if cascade_kwargs: # Allow granular control over cascades kwargs.update(cascade_kwargs) kwargs['_refs'] = _refs #self._changed_fields = [] self.cascade_save(**kwargs) except pymongo.errors.OperationFailure, err: message = 'Could not save document (%s)' if u'duplicate key' in unicode(err): message = u'Tried to save duplicate unique keys (%s)' raise OperationError(message % unicode(err))
def save(self, safe=True, force_insert=False, validate=True, write_options=None, _refs=None): """Save the :class:`~mongoengine.Document` to the database. If the document already exists, it will be updated, otherwise it will be 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 :param validate: validates the document; set to ``False`` to skip. :param write_options: Extra keyword arguments are passed down to :meth:`~pymongo.collection.Collection.save` OR :meth:`~pymongo.collection.Collection.insert` 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 have recorded the write and will force an fsync on each server being written to. .. versionchanged:: 0.5 In existing documents it only saves changed fields using set / unset Saves are cascaded and any :class:`~pymongo.dbref.DBRef` objects that have changes are saved as well. """ from fields import ReferenceField, GenericReferenceField signals.pre_save.send(self.__class__, document=self) if validate: self.validate() if not write_options: write_options = {} doc = self.to_mongo() created = '_id' in doc creation_mode = force_insert or not created try: collection = self.__class__.objects._collection if creation_mode: if force_insert: object_id = collection.insert(doc, safe=safe, **write_options) else: object_id = collection.save(doc, safe=safe, **write_options) else: object_id = doc['_id'] updates, removals = self._delta() if updates: collection.update({'_id': object_id}, {"$set": updates}, upsert=False, safe=safe, **write_options) if removals: collection.update({'_id': object_id}, {"$unset": removals}, upsert=False, safe=safe, **write_options) # Save any references / generic references _refs = _refs or [] for name, cls in self._fields.items(): if isinstance(cls, (ReferenceField, GenericReferenceField)): ref = getattr(self, name) 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: message = 'Could not save document (%s)' if u'duplicate key' in unicode(err): message = u'Tried to save duplicate unique keys (%s)' raise OperationError(message % unicode(err))