def test_softdelete_datalayer(self): """Soft deleted items should not be returned by find methods in the Eve data layer unless show_deleted is explicitly configured in the request, the deleted field is included in the lookup, or the operation is 'raw'. """ # Soft delete item r, status = self.delete(self.item_id_url, headers=self.etag_headers) self.assert204(status) with self.app.test_request_context(): # find_one should only return item if a request w/ show_deleted == # True is passed or if the deleted field is part of the lookup req = ParsedRequest() doc = self.app.data.find_one(self.known_resource, req, _id=self.item_id) self.assertEqual(doc, None) req.show_deleted = True doc = self.app.data.find_one(self.known_resource, req, _id=self.item_id) self.assertNotEqual(doc, None) self.assertEqual(doc.get(self.deleted_field), True) req.show_deleted = False doc = self.app.data.find_one(self.known_resource, req, _id=self.item_id, _deleted=True) self.assertNotEqual(doc, None) self.assertEqual(doc.get(self.deleted_field), True) # find_one_raw should always return a document, soft deleted or not doc = self.app.data.find_one_raw(self.known_resource, _id=ObjectId(self.item_id)) self.assertNotEqual(doc, None) self.assertEqual(doc.get(self.deleted_field), True) # find should only return deleted items if a request with # show_deleted == True is passed or if the deleted field is part of # the lookup req.show_deleted = False _, undeleted_count = self.app.data.find(self.known_resource, req, None) req.show_deleted = True _, challenge = self.app.data.find(self.known_resource, req, None) self.assertEqual(undeleted_count, challenge - 1) req.show_deleted = False _, deleted_count = self.app.data.find(self.known_resource, req, {self.deleted_field: True}) self.assertEqual(deleted_count, 1) # find_list_of_ids will return deleted documents if given their id ids = self.app.data.find_list_of_ids(self.known_resource, [ObjectId(self.item_id)]) self.assertEqual(str(ids[0]["_id"]), self.item_id)
def test_softdelete_datalayer(self): """Soft deleted items should not be returned by find methods in the Eve data layer unless show_deleted is explicitly configured in the request, the deleted field is included in the lookup, or the operation is 'raw'. """ # Soft delete item r, status = self.delete(self.item_id_url, headers=self.etag_headers) self.assert204(status) with self.app.test_request_context(): # find_one should only return item if a request w/ show_deleted == # True is passed or if the deleted field is part of the lookup req = ParsedRequest() doc = self.app.data.find_one( self.known_resource, req, _id=self.item_id) self.assertEqual(doc, None) req.show_deleted = True doc = self.app.data.find_one( self.known_resource, req, _id=self.item_id) self.assertNotEqual(doc, None) self.assertEqual(doc.get(self.deleted_field), True) req.show_deleted = False doc = self.app.data.find_one( self.known_resource, req, _id=self.item_id, _deleted=True) self.assertNotEqual(doc, None) self.assertEqual(doc.get(self.deleted_field), True) # find_one_raw should always return a document, soft deleted or not doc = self.app.data.find_one_raw( self.known_resource, _id=ObjectId(self.item_id)) self.assertNotEqual(doc, None) self.assertEqual(doc.get(self.deleted_field), True) # find should only return deleted items if a request with # show_deleted == True is passed or if the deleted field is part of # the lookup req.show_deleted = False docs = self.app.data.find(self.known_resource, req, None) undeleted_count = docs.count() req.show_deleted = True docs = self.app.data.find(self.known_resource, req, None) with_deleted_count = docs.count() self.assertEqual(undeleted_count, with_deleted_count - 1) req.show_deleted = False docs = self.app.data.find( self.known_resource, req, {self.deleted_field: True}) deleted_count = docs.count() self.assertEqual(deleted_count, 1) # find_list_of_ids will return deleted documents if given their id docs = self.app.data.find_list_of_ids( self.known_resource, [ObjectId(self.item_id)]) self.assertEqual(docs.count(), 1)
def delete(resource, **lookup): """ Deletes all item of a resource (collection in MongoDB terms). Won't drop indexes. Use with caution! .. versionchanged:: 0.5 Return 204 NoContent instead of 200. .. versionchanged:: 0.4 Support for document versioning. 'on_delete_resource' raised before performing the actual delete. 'on_deleted_resource' raised after performing the delete .. versionchanged:: 0.3 Support for the lookup filter, which allows for develtion of sub-resources (only delete documents that match a given condition). .. versionchanged:: 0.0.4 Added the ``requires_auth`` decorator. .. versionadded:: 0.0.2 """ resource_def = config.DOMAIN[resource] getattr(app, "on_delete_resource")(resource) getattr(app, "on_delete_resource_%s" % resource)() default_request = ParsedRequest() if resource_def["soft_delete"]: # get_document should always fetch soft deleted documents from the db # callers must handle soft deleted documents default_request.show_deleted = True result, _ = app.data.find(resource, default_request, lookup) originals = list(result) if not originals: return all_done() # I add new callback as I want the framework to be retro-compatible getattr(app, "on_delete_resource_originals")(resource, originals, lookup) getattr(app, "on_delete_resource_originals_%s" % resource)(originals, lookup) id_field = resource_def["id_field"] if resource_def["soft_delete"]: # I need to check that I have at least some documents not soft_deleted # I skip all the soft_deleted documents originals = [x for x in originals if x.get(config.DELETED) is False] if not originals: # Nothing to be deleted return all_done() for document in originals: lookup[id_field] = document[id_field] deleteitem_internal(resource, concurrency_check=False, suppress_callbacks=True, original=document, **lookup) else: # TODO if the resource schema includes media files, these won't be # deleted by use of this global method (it should be disabled). Media # cleanup is handled at the item endpoint by the delete() method # (see above). app.data.remove(resource, lookup) # TODO: should attempt to delete version collection even if setting is # off if resource_def["versioning"] is True: app.data.remove(resource + config.VERSIONS, lookup) getattr(app, "on_deleted_resource")(resource) getattr(app, "on_deleted_resource_%s" % resource)() return all_done()
def get_data_version_relation_document(data_relation, reference, latest=False): """ Returns document at the version specified in data_relation, or at the latest version if passed `latest=True`. Returns None if data_relation cannot be satisfied. :param data_relation: the schema definition describing the data_relation. :param reference: a dictionary with a value_field and a version_field. :param latest: if we should obey the version param in reference or not. .. versionadded:: 0.4 """ value_field = data_relation['field'] version_field = app.config['VERSION'] collection = data_relation['resource'] versioned_collection = collection + config.VERSIONS resource_def = app.config['DOMAIN'][data_relation['resource']] id_field = app.config['ID_FIELD'] # Fetch document data at the referenced version query = {version_field: reference[version_field]} if value_field == id_field: # Versioned documents store the primary id in a different field query[versioned_id_field()] = reference[value_field] elif value_field not in versioned_fields(resource_def): # The relation value field is unversioned, and will not be present in # the versioned collection. Need to find id field for version query req = ParsedRequest() if resource_def['soft_delete']: req.show_deleted = True latest_version = app.data.find_one( collection, req, **{value_field: reference[value_field]}) if not latest_version: return None query[versioned_id_field()] = latest_version[id_field] else: # Field will be present in the versioned collection query[value_field] = reference[value_field] referenced_version = app.data.find_one(versioned_collection, None, **query) # support late versioning if referenced_version is None and reference[version_field] == 1: # there is a chance this document hasn't been saved # since versioning was turned on referenced_version = missing_version_field(data_relation, reference) return referenced_version # v1 is both referenced and latest if referenced_version is None: return None # The referenced document version was not found # Fetch the latest version of this document to use in version synthesis query = {id_field: referenced_version[versioned_id_field()]} req = ParsedRequest() if resource_def['soft_delete']: # Still return latest after soft delete. It is needed to synthesize # full document version. req.show_deleted = True latest_version = app.data.find_one(collection, req, **query) if latest is True: return latest_version # Syntheisze referenced version from latest and versioned data document = synthesize_versioned_document( latest_version, referenced_version, resource_def) return document
def get_data_version_relation_document(data_relation, reference, latest=False): """ Returns document at the version specified in data_relation, or at the latest version if passed `latest=True`. Returns None if data_relation cannot be satisfied. :param data_relation: the schema definition describing the data_relation. :param reference: a dictionary with a value_field and a version_field. :param latest: if we should obey the version param in reference or not. .. versionadded:: 0.4 """ value_field = data_relation["field"] version_field = app.config["VERSION"] collection = data_relation["resource"] versioned_collection = collection + config.VERSIONS resource_def = app.config["DOMAIN"][data_relation["resource"]] id_field = resource_def["id_field"] # Fetch document data at the referenced version query = {version_field: reference[version_field]} if value_field == id_field: # Versioned documents store the primary id in a different field query[versioned_id_field(resource_def)] = reference[value_field] elif value_field not in versioned_fields(resource_def): # The relation value field is unversioned, and will not be present in # the versioned collection. Need to find id field for version query req = ParsedRequest() if resource_def["soft_delete"]: req.show_deleted = True latest_version = app.data.find_one( collection, req, **{value_field: reference[value_field]}) if not latest_version: return None query[versioned_id_field(resource_def)] = latest_version[id_field] else: # Field will be present in the versioned collection query[value_field] = reference[value_field] referenced_version = app.data.find_one(versioned_collection, None, **query) # support late versioning if referenced_version is None and reference[version_field] == 1: # there is a chance this document hasn't been saved # since versioning was turned on referenced_version = missing_version_field(data_relation, reference) return referenced_version # v1 is both referenced and latest if referenced_version is None: return None # The referenced document version was not found # Fetch the latest version of this document to use in version synthesis query = {id_field: referenced_version[versioned_id_field(resource_def)]} req = ParsedRequest() if resource_def["soft_delete"]: # Still return latest after soft delete. It is needed to synthesize # full document version. req.show_deleted = True latest_version = app.data.find_one(collection, req, **query) if latest is True: return latest_version # Syntheisze referenced version from latest and versioned data document = synthesize_versioned_document(latest_version, referenced_version, resource_def) return document
def delete(resource, **lookup): """ Deletes all item of a resource (collection in MongoDB terms). Won't drop indexes. Use with caution! .. versionchanged:: 0.5 Return 204 NoContent instead of 200. .. versionchanged:: 0.4 Support for document versioning. 'on_delete_resource' raised before performing the actual delete. 'on_deleted_resource' raised after performing the delete .. versionchanged:: 0.3 Support for the lookup filter, which allows for develtion of sub-resources (only delete documents that match a given condition). .. versionchanged:: 0.0.4 Added the ``requires_auth`` decorator. .. versionadded:: 0.0.2 """ resource_def = config.DOMAIN[resource] getattr(app, "on_delete_resource")(resource) getattr(app, "on_delete_resource_%s" % resource)() default_request = ParsedRequest() if resource_def["soft_delete"]: # get_document should always fetch soft deleted documents from the db # callers must handle soft deleted documents default_request.show_deleted = True originals = list(app.data.find(resource, default_request, lookup)) if not originals: abort(404) # I add new callback as I want the framework to be retro-compatible getattr(app, "on_delete_resource_originals")(resource, originals, lookup) getattr(app, "on_delete_resource_originals_%s" % resource)(originals, lookup) id_field = resource_def["id_field"] if resource_def["soft_delete"]: # I need to check that I have at least some documents not soft_deleted # Otherwise, I should abort 404 # I skip all the soft_deleted documents originals = [x for x in originals if x.get(config.DELETED) is not True] if not originals: # Nothing to be deleted abort(404) for document in originals: lookup[id_field] = document[id_field] deleteitem_internal( resource, concurrency_check=False, suppress_callbacks=True, original=document, **lookup ) else: # TODO if the resource schema includes media files, these won't be # deleted by use of this global method (it should be disabled). Media # cleanup is handled at the item endpoint by the delete() method # (see above). app.data.remove(resource, lookup) # TODO: should attempt to delete version collection even if setting is # off if resource_def["versioning"] is True: app.data.remove(resource + config.VERSIONS, lookup) getattr(app, "on_deleted_resource")(resource) getattr(app, "on_deleted_resource_%s" % resource)() return {}, None, None, 204