Source code for mbe.node

"""
    The node module provides the node-API for MBE. All interaction with the node-collection should happen
    through the Node-class

    Note: Sphinx does not properly document this module with the decorators, comment them before running sphinx
"""

from bson.objectid import ObjectId

from mbe.authentication import init_authentication
from mbe.constants import object_id_right_admin_everything
from mbe.groups import aop_has_right, init_groups
from mbe.misc.schema_mongodb import mbe_object_id
from mbe.permission import filter_by_group



# TODO: Separate or classify all MongoDB-specific querying


__author__ = 'nibo'

node_rights = None
"""
A cache of node rights
"""

[docs]def get_node_rights(): """ This function is necessary to hand runtime data to the decorators :return: the node rights """ return node_rights
[docs]class Node(): """ The node class is used for all node management in MBE. """ database_access = None rights = None def __init__(self, _database_access, _rights=None): """ Initializes that node object and groups. :param _database_access: A database access instance """ if _database_access is None: raise Exception("Node: The database access parameter is None, it must be set for the Node class to work") init_authentication(_database_access) self.database_access = _database_access global node_rights if _rights is not None: node_rights = [object_id_right_admin_everything] + _rights else: node_rights = [object_id_right_admin_everything] init_groups(self.database_access) def _string_to_object_ids(self, _data): if isinstance(_data, list): _destination = [] for _curr_row in _data: _destination.append(self._string_to_object_ids(_curr_row)) return _destination elif isinstance(_data, dict): _destination = {} for _curr_key, _curr_value in _data.items(): _destination[_curr_key] = self._string_to_object_ids(_curr_value) return _destination elif type(_data) != ObjectId and _data[:9] == "ObjectId(": return ObjectId(_data[9:-1]) else: return _data # @aop_has_right(get_node_rights)
[docs] def save(self, _document, _user): """save(self, _document, _user) Saves an MBE node descendant to the database. :param _document: The MBE node document to save. :param _user: The current user :return: A structure detailing the save result """ # The data must have a schema id key and it must say that the collection is "node". self.database_access.verify_document(_document, "node", "Node.save") if "_id" in _document: # It is an existing node, check if user can write to it. _old_document_result = filter_by_group( self.database_access.find( {"conditions": {"_id": mbe_object_id(_document["_id"])}, "collection": "node"}), "canWrite", _user, "Node.save(existing node)") if len(_old_document_result) == 1: _old_document = _old_document_result[0] else: raise Exception("Needed exactly one(1) existing node, found " + str(len(_old_document_result)) + ". _id: " + _document["_id"]) elif "parent_id" in _document: # It is a new node, check if the parent allows user to create it filter_by_group( self.database_access.find( {"conditions": {"_id": mbe_object_id(_document["parent_id"])}, "collection": "node"}), "canWrite", _user, "Node.save(new node)") _old_document = None else: raise Exception("Node.save: Data must have a parentId or an _id.") # TODO: Specify how the result actually looks return self.database_access.save(_document, _user, _old_document) # @aop_has_right(get_node_rights)
[docs] def find(self, _conditions, _user): """find(_conditions,_user) Returns a list of nodes matching the MBE condition. Note: This method is literate, it takes ObjectId instances when comparing to ObjectId instances. But JSON doesn't handle ObjectId. So instead it supports an ObjectId(000000010000010001ee3214)-syntax and when it encounters those values, it converts them into ObjectId instances. :param _conditions: A condition :param _user: A user object :return: A list of nodes """ # Filter result by canRead groups return filter_by_group(self.database_access.find( {"conditions": self._string_to_object_ids(_conditions), "collection": "node"}), "canRead", _user, self.database_access) # @aop_has_right(get_node_rights)
[docs] def load_children(self, _parent_id, _user): """ Returns a list of child nodes whose _parent_ids match _parent_id :param _parent_id: An object with a parent_id property :param _user: A user object :return: A list of nodes """ # Filter result by canRead groups return filter_by_group( self.database_access.find( {"conditions": {"parent_id": mbe_object_id(_parent_id["parent_id"])}, "collection": "node"}), "canRead", _user, self.database_access) # @aop_has_right(get_node_rights)
[docs] def load_node(self, _id, _user): """ Returns the child with _id. :param _id: An object with a id property :param _user: A user object :return: A list of nodes """ # Filter result by canRead groups return filter_by_group(self.database_access.find( {"conditions": {"_id": mbe_object_id(_id["_id"])}, "collection": "node"}), "canRead", _user, self.database_access, _error_prefix_if_not_allowed="Node.load_node: Not permissioned to load this node. _id: " + _id["_id"])[0] # @aop_has_right(get_node_rights)
[docs] def lookup(self, _conditions, _user): """ Returns a list of value-name objects for use in drop downs. As find, handles the ObjectId(000000010000010001ee3214) string format. (no quotes) :param _conditions: An MBE condition :param _user: A user object :return: A list of lookup objects. """ # Only allow node collection, but allow not specifying schema in conditions. self.database_access.verify_condition(_conditions, "node", "Node.find") print(str(_conditions)) # Filter result by canRead groups return [{"value": _row["_id"], "text": _row["name"]} for _row in filter_by_group(self.database_access.find(self._string_to_object_ids(_conditions)), "canRead", _user, self.database_access)] # @aop_has_right(get_node_rights)
[docs] def remove(self, _id, _user): """ Remove the specified node :param _id: An object with a node _id field :param _user: A user object :return: Result of removal """ # Recurse and gather all children def _recurse_children(_parent_node): _result = [_parent_node] _child_nodes = self.database_access.find( {"conditions": {"parent_id": _parent_node["_id"]}, "collection": "node"}, _do_not_fix_object_ids=True) for _curr_child in _child_nodes: _result += _recurse_children(_curr_child) return _result # Load the current node _node = self.database_access.find({"conditions": {"_id": mbe_object_id(_id["_id"])}, "collection": "node"}, _do_not_fix_object_ids=True)[0] _all_nodes = _recurse_children(_node) # Check permissions, as the remove_documents function works with documents directly the from database, # use objectIds. _documents = filter_by_group( _all_nodes, "canWrite", _user, self.database_access, _error_prefix_if_not_allowed="Node.remove: Missing permissions to remove node or sub nodes.", _use_object_id=True) # TODO: Specify removal result structure return self.database_access.remove_documents(_documents, _user, "node", ) # @aop_has_right(get_node_rights)
[docs] def history(self, _id, _user): """ Return the change history for the specified node. :param _id: An object with a node _id field :param _user: A user object :return: """ object_id = mbe_object_id(_id["_id"]) # Filter result by canRead filter_by_group(self.database_access.find( {"conditions": {"_id": object_id}, "collection": "node"}), "canRead", _user, self.database_access, _error_prefix_if_not_allowed="Node.history: No permissions to view details of this node. _id: " + _id["_id"]) return self.database_access.find({"conditions": {"node_id": object_id}, "collection": "event"})