from time import time
from osm_common.dbbase import deep_update_rfc7396
from osm_nbi.validation import validate_input, ValidationError, is_valid_uuid
+from yaml import safe_load, YAMLError
__author__ = "Alfonso Tierno <alfonso.tiernosepulveda@telefonica.com>"
super(Exception, self).__init__(message)
+def deep_get(target_dict, key_list):
+ """
+ Get a value from target_dict entering in the nested keys. If keys does not exist, it returns None
+ Example target_dict={a: {b: 5}}; key_list=[a,b] returns 5; both key_list=[a,b,c] and key_list=[f,h] return None
+ :param target_dict: dictionary to be read
+ :param key_list: list of keys to read from target_dict
+ :return: The wanted value if exist, None otherwise
+ """
+ for key in key_list:
+ if not isinstance(target_dict, dict) or key not in target_dict:
+ return None
+ target_dict = target_dict[key]
+ return target_dict
+
+
def get_iterable(input_var):
"""
Returns an iterable, in case input_var is None it just returns an empty tuple
def _get_project_filter(session):
"""
Generates a filter dictionary for querying database, so that only allowed items for this project can be
- addressed. Only propietary or public can be used. Allowed projects are at _admin.project_read/write. If it is
+ addressed. Only proprietary or public can be used. Allowed projects are at _admin.project_read/write. If it is
not present or contains ANY mean public.
:param session: contains:
project_id: project list this session has rights to access. Can be empty, one or several
final_content["_admin"]["modified"] = now
return None
- def _send_msg(self, action, content):
- if self.topic_msg:
+ def _send_msg(self, action, content, not_send_msg=None):
+ if self.topic_msg and not_send_msg is not False:
content.pop("_admin", None)
- self.msg.write(self.topic_msg, action, content)
+ if isinstance(not_send_msg, list):
+ not_send_msg.append((self.topic_msg, action, content))
+ else:
+ self.msg.write(self.topic_msg, action, content)
def check_conflict_on_del(self, session, _id, db_content):
"""
pass
@staticmethod
- def _update_input_with_kwargs(desc, kwargs):
+ def _update_input_with_kwargs(desc, kwargs, yaml_format=False):
"""
Update descriptor with the kwargs. It contains dot separated keys
:param desc: dictionary to be updated
:param kwargs: plain dictionary to be used for updating.
+ :param yaml_format: get kwargs values as yaml format.
:return: None, 'desc' is modified. It raises EngineException.
"""
if not kwargs:
else:
raise EngineException(
"Invalid query string '{}'. Descriptor is not a list nor dict at '{}'".format(k, kitem))
- update_content[kitem_old] = v
+ update_content[kitem_old] = v if not yaml_format else safe_load(v)
except KeyError:
raise EngineException(
"Invalid query string '{}'. Descriptor does not contain '{}'".format(k, kitem_old))
except IndexError:
raise EngineException(
"Invalid query string '{}'. Index '{}' out of range".format(k, kitem_old))
+ except YAMLError:
+ raise EngineException("Invalid query string '{}' yaml format".format(k))
def show(self, session, _id):
"""
filter_q.update(self._get_project_filter(session))
return self.db.del_list(self.topic, filter_q)
- def delete_extra(self, session, _id, db_content):
+ def delete_extra(self, session, _id, db_content, not_send_msg=None):
"""
Delete other things apart from database entry of a item _id.
e.g.: other associated elements at database and other file system storage
:param _id: server internal id
:param db_content: The database content of the _id. It is already deleted when reached this method, but the
content is needed in same cases
+ :param not_send_msg: To not send message (False) or store content (list) instead
:return: None if ok or raises EngineException with the problem
"""
pass
- def delete(self, session, _id, dry_run=False):
+ def delete(self, session, _id, dry_run=False, not_send_msg=None):
"""
Delete item by its internal _id
:param session: contains "username", "admin", "force", "public", "project_id", "set_project"
:param _id: server internal id
:param dry_run: make checking but do not delete
+ :param not_send_msg: To not send message (False) or store content (list) instead
:return: operation id (None if there is not operation), raise exception if error or not found, conflict, ...
"""
# To allow addressing projects and users by name AS WELL AS by _id
- filter_q = {BaseTopic.id_field(self.topic, _id): _id}
+ if not self.multiproject:
+ filter_q = {}
+ else:
+ filter_q = self._get_project_filter(session)
+ filter_q[self.id_field(self.topic, _id)] = _id
item_content = self.db.get_one(self.topic, filter_q)
- # TODO add admin to filter, validate rights
- # data = self.get_item(topic, _id)
self.check_conflict_on_del(session, _id, item_content)
if dry_run:
return None
- if self.multiproject:
- filter_q.update(self._get_project_filter(session))
if self.multiproject and session["project_id"]:
- # remove reference from project_read. If not last delete
- # if this topic is not part of session["project_id"] no midification at database is done and an exception
- # is raised
- self.db.set_one(self.topic, filter_q, update_dict=None,
- pull={"_admin.projects_read": {"$in": session["project_id"]}})
- # try to delete if there is not any more reference from projects. Ignore if it is not deleted
- filter_q = {'_id': _id, '_admin.projects_read': [[], ["ANY"]]}
- v = self.db.del_one(self.topic, filter_q, fail_on_empty=False)
- if not v or not v["deleted"]:
+ # remove reference from project_read if there are more projects referencing it. If it last one,
+ # do not remove reference, but delete
+ other_projects_referencing = next((p for p in item_content["_admin"]["projects_read"]
+ if p not in session["project_id"]), None)
+
+ # check if there are projects referencing it (apart from ANY, that means, public)....
+ if other_projects_referencing:
+ # remove references but not delete
+ update_dict_pull = {"_admin.projects_read.{}".format(p): None for p in session["project_id"]}
+ update_dict_pull.update({"_admin.projects_write.{}".format(p): None for p in session["project_id"]})
+ self.db.set_one(self.topic, filter_q, update_dict=None, pull=update_dict_pull)
return None
- else:
- self.db.del_one(self.topic, filter_q)
- self.delete_extra(session, _id, item_content)
- self._send_msg("deleted", {"_id": _id})
+ else:
+ can_write = next((p for p in item_content["_admin"]["projects_write"] if p == "ANY" or
+ p in session["project_id"]), None)
+ if not can_write:
+ raise EngineException("You have not write permission to delete it",
+ http_code=HTTPStatus.UNAUTHORIZED)
+
+ # delete
+ self.db.del_one(self.topic, filter_q)
+ self.delete_extra(session, _id, item_content, not_send_msg=not_send_msg)
+ self._send_msg("deleted", {"_id": _id}, not_send_msg=not_send_msg)
return None
def edit(self, session, _id, indata=None, kwargs=None, content=None):