Node breadcrumbs
Breadcrumbs are served as JSON at `/nodes/{node ID}/breadcrumbs`, with the top-level parent listed first and the node itself listed last: {breadcrumbs: [ ... {_id: "parentID", name: "The Parent Node", node_type: "group", url: "/p/project/parentID"}, {_id: "deadbeefbeefbeefbeeffeee", name: "The Node Itself", node_type: "asset", url: "/p/project/nodeID", _self: true}, ]} When a parent node is missing, it has a breadcrumb like this: {_id: "deadbeefbeefbeefbeeffeee", _exists': false, name': '-unknown-'} Of course this will be the first in the breadcrumbs list, as we won't be able to determine the parent of a deleted/non-existing node. Breadcrumbs are rendered with Vue.js in Blender Cloud (not in Pillar); see projects/view.pug.
This commit is contained in:
@@ -604,5 +604,94 @@ def url_for_node(node_id=None, node=None):
|
||||
return finders.find_url_for_node(node)
|
||||
|
||||
|
||||
@blueprint.route("/<node_id>/breadcrumbs")
|
||||
def breadcrumbs(node_id: str):
|
||||
"""Return breadcrumbs for the given node, as JSON.
|
||||
|
||||
Note that a missing parent is still returned in the breadcrumbs,
|
||||
but with `{_exists: false, name: '-unknown-'}`.
|
||||
|
||||
The breadcrumbs start with the top-level parent, and end with the node
|
||||
itself (marked by {_self: true}). Returns JSON like this:
|
||||
|
||||
{breadcrumbs: [
|
||||
...,
|
||||
{_id: "parentID",
|
||||
name: "The Parent Node",
|
||||
node_type: "group",
|
||||
url: "/p/project/parentID"},
|
||||
{_id: "deadbeefbeefbeefbeeffeee",
|
||||
_self: true,
|
||||
name: "The Node Itself",
|
||||
node_type: "asset",
|
||||
url: "/p/project/nodeID"},
|
||||
]}
|
||||
|
||||
When a parent node is missing, it has a breadcrumb like this:
|
||||
|
||||
{_id: "deadbeefbeefbeefbeeffeee",
|
||||
_exists': false,
|
||||
name': '-unknown-'}
|
||||
"""
|
||||
|
||||
api = system_util.pillar_api()
|
||||
is_self = True
|
||||
|
||||
def make_crumb(some_node: None) -> dict:
|
||||
"""Construct a breadcrumb for this node."""
|
||||
nonlocal is_self
|
||||
|
||||
crumb = {
|
||||
'_id': some_node._id,
|
||||
'name': some_node.name,
|
||||
'node_type': some_node.node_type,
|
||||
'url': finders.find_url_for_node(some_node),
|
||||
}
|
||||
if is_self:
|
||||
crumb['_self'] = True
|
||||
is_self = False
|
||||
return crumb
|
||||
|
||||
def make_missing_crumb(some_node_id: None) -> dict:
|
||||
"""Construct 'missing parent' breadcrumb."""
|
||||
|
||||
return {
|
||||
'_id': some_node_id,
|
||||
'_exists': False,
|
||||
'name': '-unknown-',
|
||||
}
|
||||
|
||||
# The first node MUST exist.
|
||||
try:
|
||||
node = Node.find(node_id, api=api)
|
||||
except ResourceNotFound:
|
||||
log.warning('breadcrumbs(node_id=%r): Unable to find node', node_id)
|
||||
raise wz_exceptions.NotFound(f'Unable to find node {node_id}')
|
||||
except ForbiddenAccess:
|
||||
log.warning('breadcrumbs(node_id=%r): access denied to current user', node_id)
|
||||
raise wz_exceptions.Forbidden(f'No access to node {node_id}')
|
||||
|
||||
crumbs = []
|
||||
while True:
|
||||
crumbs.append(make_crumb(node))
|
||||
|
||||
child_id = node._id
|
||||
node_id = node.parent
|
||||
if not node_id:
|
||||
break
|
||||
|
||||
# If a subsequent node doesn't exist any more, include that in the breadcrumbs.
|
||||
# Forbidden nodes are handled as if they don't exist.
|
||||
try:
|
||||
node = Node.find(node_id, api=api)
|
||||
except (ResourceNotFound, ForbiddenAccess):
|
||||
log.warning('breadcrumbs: Unable to find node %r but it is marked as parent of %r',
|
||||
node_id, child_id)
|
||||
crumbs.append(make_missing_crumb(node_id))
|
||||
break
|
||||
|
||||
return jsonify({'breadcrumbs': list(reversed(crumbs))})
|
||||
|
||||
|
||||
# Import of custom modules (using the same nodes decorator)
|
||||
from .custom import groups, storage, posts
|
||||
|
Reference in New Issue
Block a user