164 lines
5.0 KiB
Python
164 lines
5.0 KiB
Python
"""Node-URL-finding microframework."""
|
|
|
|
import logging
|
|
|
|
import bson
|
|
from flask import url_for
|
|
import werkzeug.exceptions as wz_exceptions
|
|
|
|
import pillarsdk
|
|
from pillarsdk import Node
|
|
from pillarsdk.exceptions import ResourceNotFound
|
|
|
|
from pillar import current_app
|
|
from pillar.web.utils import caching
|
|
from pillar.web import system_util
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
node_url_finders = {} # mapping from node type to callable.
|
|
|
|
|
|
def register_node_finder(node_type):
|
|
"""Decorator, registers the decorated function as node finder for the given node type."""
|
|
|
|
def wrapper(func):
|
|
if node_type in node_url_finders:
|
|
raise ValueError('Node type %r already handled by %r' %
|
|
(node_type, node_url_finders[node_type]))
|
|
|
|
log.debug('Registering %s node finder for node type %r',
|
|
func, node_type)
|
|
node_url_finders[node_type] = func
|
|
return func
|
|
|
|
return wrapper
|
|
|
|
|
|
@register_node_finder('comment')
|
|
def find_for_comment(project, node):
|
|
"""Returns the URL for a comment."""
|
|
|
|
api = system_util.pillar_api()
|
|
|
|
parent = node
|
|
while parent.node_type == 'comment':
|
|
if isinstance(parent.parent, pillarsdk.Resource):
|
|
parent = parent.parent
|
|
continue
|
|
|
|
try:
|
|
parent = Node.find_one({'where': {
|
|
'_id': parent.parent,
|
|
'_deleted': {'$ne': True}
|
|
}}, api=api)
|
|
except ResourceNotFound:
|
|
log.warning(
|
|
'url_for_node(node_id=%r): Unable to find parent node %r',
|
|
node['_id'], parent.parent)
|
|
raise ValueError('Unable to find parent node %r' % parent.parent)
|
|
|
|
# Find the redirection URL for the parent node.
|
|
parent_url = find_url_for_node(parent)
|
|
if '#' in parent_url:
|
|
# We can't attach yet another fragment, so just don't link to
|
|
# the comment for now.
|
|
return parent_url
|
|
return parent_url + '#{}'.format(node['_id'])
|
|
|
|
|
|
@register_node_finder('blog')
|
|
def find_for_blog(project, _):
|
|
"""Returns the URL for a blog."""
|
|
|
|
project_id = project['_id']
|
|
if str(project_id) == current_app.config['MAIN_PROJECT_ID']:
|
|
return url_for('main.main_blog')
|
|
|
|
the_project = project_url(project_id, project=project)
|
|
return url_for('main.project_blog', project_url=the_project.url)
|
|
|
|
|
|
@register_node_finder('post')
|
|
def find_for_post(project, node):
|
|
"""Returns the URL for a blog post."""
|
|
|
|
if not node.properties:
|
|
raise ValueError(f'Node must have properties.url, but is {node}')
|
|
|
|
project_id = project['_id']
|
|
if str(project_id) == current_app.config['MAIN_PROJECT_ID']:
|
|
return url_for('main.main_blog',
|
|
url=node.properties.url)
|
|
|
|
the_project = project_url(project_id, project=project)
|
|
return url_for('main.project_blog',
|
|
project_url=the_project.url,
|
|
url=node.properties.url)
|
|
|
|
|
|
@register_node_finder('page')
|
|
def find_for_page(project, node):
|
|
"""Returns the URL for a page."""
|
|
|
|
project_id = project['_id']
|
|
|
|
the_project = project_url(project_id, project=project)
|
|
return url_for('projects.view_node', project_url=the_project.url, node_id=node.properties.url)
|
|
|
|
|
|
def find_for_other(project, node):
|
|
"""Fallback: Assets, textures, and other node types.
|
|
|
|
Hard-coded fallback, so doesn't need @register_node_finder() decoration.
|
|
"""
|
|
|
|
if not project:
|
|
raise ValueError(f'project={project}')
|
|
|
|
the_project = project_url(project['_id'], project=project)
|
|
|
|
return url_for('projects.view_node',
|
|
project_url=the_project.url,
|
|
node_id=node['_id'])
|
|
|
|
|
|
@caching.cache_for_request()
|
|
def project_url(project_id: str, project: pillarsdk.Project=None) -> pillarsdk.Project:
|
|
"""Returns the project, raising a ValueError if it can't be found.
|
|
|
|
Uses a direct MongoDB query to allow calls by any user. Only returns
|
|
a partial project with the _id and url properties set.
|
|
"""
|
|
|
|
if project is not None:
|
|
return project
|
|
|
|
proj_coll = current_app.db('projects')
|
|
proj = proj_coll.find_one({'_id': bson.ObjectId(project_id)},
|
|
{'url': 1})
|
|
|
|
if proj is None:
|
|
log.error('project_url(%s): project does not exist, cannot find its URL', project_id)
|
|
raise wz_exceptions.NotFound()
|
|
|
|
return pillarsdk.Project(proj)
|
|
|
|
|
|
# Cache the actual URL based on the node ID, for the duration of the request.
|
|
@caching.cache_for_request()
|
|
def find_url_for_node(node):
|
|
# Find the node's project, or its ID, depending on whether a project
|
|
# was embedded. This is needed some finder functions.
|
|
if isinstance(node.project, pillarsdk.Resource):
|
|
# Embedded project
|
|
project = node.project
|
|
else:
|
|
project = project_url(node.project, None)
|
|
if not project:
|
|
raise ValueError(f'Project {node.project} not found')
|
|
|
|
# Determine which function to use to find the correct URL.
|
|
finder = node_url_finders.get(node.node_type, find_for_other)
|
|
return finder(project, node)
|