diff --git a/pillar/application/modules/nodes/__init__.py b/pillar/application/modules/nodes/__init__.py index 66554ec5..de44475e 100644 --- a/pillar/application/modules/nodes/__init__.py +++ b/pillar/application/modules/nodes/__init__.py @@ -393,6 +393,10 @@ def after_deleting_node(item): def setup_app(app, url_prefix): + + from . import patch + patch.setup_app(app, url_prefix=url_prefix) + app.on_fetched_item_nodes += before_returning_node app.on_fetched_resource_nodes += before_returning_nodes diff --git a/pillar/application/modules/nodes/custom/__init__.py b/pillar/application/modules/nodes/custom/__init__.py new file mode 100644 index 00000000..d2f28595 --- /dev/null +++ b/pillar/application/modules/nodes/custom/__init__.py @@ -0,0 +1,20 @@ +import logging + +log = logging.getLogger(__name__) +patch_handlers = {} # mapping from node type to callable. + + +def register_patch_handler(node_type): + """Decorator, registers the decorated function as patch handler for the given node type.""" + + def wrapper(func): + if node_type in patch_handlers: + raise ValueError('Node type %r already handled by %r' % + (node_type, patch_handlers[node_type])) + + log.debug('Registering %s as PATCH handler for node type %r', + func, node_type) + patch_handlers[node_type] = func + return func + + return wrapper diff --git a/pillar/application/modules/nodes/patch.py b/pillar/application/modules/nodes/patch.py new file mode 100644 index 00000000..b94dd055 --- /dev/null +++ b/pillar/application/modules/nodes/patch.py @@ -0,0 +1,51 @@ +"""Generic node patching support. + +Depends on node_type-specific patch handlers in submodules. +""" + +import logging + +from flask import Blueprint, request +import werkzeug.exceptions as wz_exceptions + +from application.utils import str2id +from application.utils import authorization, mongo, authentication + +from . import custom + +log = logging.getLogger(__name__) +blueprint = Blueprint('nodes.patch', __name__) + + +@blueprint.route('/', methods=['PATCH']) +@authorization.require_login() +def patch_node(node_id): + # Parse the request + node_id = str2id(node_id) + patch = request.get_json() + + # Find the node type. + node = mongo.find_one_or_404('nodes', node_id, + projection={'node_type': 1}) + try: + node_type = node['node_type'] + except KeyError: + msg = 'Node %s has no node_type property' % node_id + log.warning(msg) + raise wz_exceptions.InternalServerError(msg) + log.debug('User %s wants to PATCH %s node %s', + authentication.current_user_id(), node_type, node_id) + + # Find the PATCH handler for the node type. + try: + patch_handler = custom.patch_handlers[node_type] + except KeyError: + log.info('No patch handler for node type %r', node_type) + raise wz_exceptions.MethodNotAllowed('PATCH on node type %r not allowed' % node_type) + + # Let the PATCH handler do its thing. + return patch_handler(node_id, patch) + + +def setup_app(app, url_prefix): + app.register_blueprint(blueprint, url_prefix=url_prefix) diff --git a/pillar/settings.py b/pillar/settings.py index 5465cc0f..8c02dec8 100644 --- a/pillar/settings.py +++ b/pillar/settings.py @@ -7,7 +7,7 @@ RESOURCE_METHODS = ['GET', 'POST', 'DELETE'] # Enable reads (GET), edits (PATCH), replacements (PUT) and deletes of # individual items (defaults to read-only item access). -ITEM_METHODS = ['GET', 'PUT', 'DELETE', 'PATCH'] +ITEM_METHODS = ['GET', 'PUT', 'DELETE'] PAGINATION_LIMIT = 250 PAGINATION_DEFAULT = 250