Moved node management to modules/node.py
No semantic changes, all should work identically as before.
This commit is contained in:
parent
681754eade
commit
32ad39aeb1
@ -159,11 +159,7 @@ else:
|
|||||||
|
|
||||||
from utils.authentication import validate_token
|
from utils.authentication import validate_token
|
||||||
from utils.authorization import check_permissions
|
from utils.authorization import check_permissions
|
||||||
from utils.gcs import update_file_name
|
|
||||||
from utils.activities import activity_subscribe
|
|
||||||
from utils.activities import activity_object_add
|
|
||||||
from utils.activities import notification_parse
|
from utils.activities import notification_parse
|
||||||
from modules import file_storage
|
|
||||||
from modules.projects import before_inserting_projects
|
from modules.projects import before_inserting_projects
|
||||||
from modules.projects import after_inserting_projects
|
from modules.projects import after_inserting_projects
|
||||||
|
|
||||||
@ -183,146 +179,6 @@ def before_returning_resource_permissions(response):
|
|||||||
check_permissions(item, 'GET', append_allowed_methods=True)
|
check_permissions(item, 'GET', append_allowed_methods=True)
|
||||||
|
|
||||||
|
|
||||||
def before_replacing_node(item, original):
|
|
||||||
check_permissions(original, 'PUT')
|
|
||||||
update_file_name(item)
|
|
||||||
|
|
||||||
|
|
||||||
def after_replacing_node(item, original):
|
|
||||||
"""Push an update to the Algolia index when a node item is updated. If the
|
|
||||||
project is private, prevent public indexing.
|
|
||||||
"""
|
|
||||||
projects_collection = app.data.driver.db['projects']
|
|
||||||
project = projects_collection.find_one({'_id': item['project']},
|
|
||||||
{'is_private': 1})
|
|
||||||
if 'is_private' in project and project['is_private']:
|
|
||||||
# Skip index updating and return
|
|
||||||
return
|
|
||||||
|
|
||||||
from algoliasearch.client import AlgoliaException
|
|
||||||
from utils.algolia import algolia_index_node_save
|
|
||||||
|
|
||||||
try:
|
|
||||||
algolia_index_node_save(item)
|
|
||||||
except AlgoliaException as ex:
|
|
||||||
log.warning('Unable to push node info to Algolia for node %s; %s',
|
|
||||||
item.get('_id'), ex)
|
|
||||||
|
|
||||||
|
|
||||||
def before_inserting_nodes(items):
|
|
||||||
"""Before inserting a node in the collection we check if the user is allowed
|
|
||||||
and we append the project id to it.
|
|
||||||
"""
|
|
||||||
nodes_collection = app.data.driver.db['nodes']
|
|
||||||
|
|
||||||
def find_parent_project(node):
|
|
||||||
"""Recursive function that finds the ultimate parent of a node."""
|
|
||||||
if node and 'parent' in node:
|
|
||||||
parent = nodes_collection.find_one({'_id': node['parent']})
|
|
||||||
return find_parent_project(parent)
|
|
||||||
if node:
|
|
||||||
return node
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
for item in items:
|
|
||||||
check_permissions(item, 'POST')
|
|
||||||
if 'parent' in item and 'project' not in item:
|
|
||||||
parent = nodes_collection.find_one({'_id': item['parent']})
|
|
||||||
project = find_parent_project(parent)
|
|
||||||
if project:
|
|
||||||
item['project'] = project['_id']
|
|
||||||
|
|
||||||
|
|
||||||
def after_inserting_nodes(items):
|
|
||||||
for item in items:
|
|
||||||
# Skip subscriptions for first level items (since the context is not a
|
|
||||||
# node, but a project).
|
|
||||||
# TODO: support should be added for mixed context
|
|
||||||
if 'parent' not in item:
|
|
||||||
return
|
|
||||||
context_object_id = item['parent']
|
|
||||||
if item['node_type'] == 'comment':
|
|
||||||
nodes_collection = app.data.driver.db['nodes']
|
|
||||||
parent = nodes_collection.find_one({'_id': item['parent']})
|
|
||||||
# Always subscribe to the parent node
|
|
||||||
activity_subscribe(item['user'], 'node', item['parent'])
|
|
||||||
if parent['node_type'] == 'comment':
|
|
||||||
# If the parent is a comment, we provide its own parent as
|
|
||||||
# context. We do this in order to point the user to an asset
|
|
||||||
# or group when viewing the notification.
|
|
||||||
verb = 'replied'
|
|
||||||
context_object_id = parent['parent']
|
|
||||||
# Subscribe to the parent of the parent comment (post or group)
|
|
||||||
activity_subscribe(item['user'], 'node', parent['parent'])
|
|
||||||
else:
|
|
||||||
activity_subscribe(item['user'], 'node', item['_id'])
|
|
||||||
verb = 'commented'
|
|
||||||
else:
|
|
||||||
verb = 'posted'
|
|
||||||
activity_subscribe(item['user'], 'node', item['_id'])
|
|
||||||
|
|
||||||
activity_object_add(
|
|
||||||
item['user'],
|
|
||||||
verb,
|
|
||||||
'node',
|
|
||||||
item['_id'],
|
|
||||||
'node',
|
|
||||||
context_object_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def item_parse_attachments(response):
|
|
||||||
"""Before returning a response, check if the 'attachments' property is
|
|
||||||
defined. If yes, load the file (for the moment only images) in the required
|
|
||||||
variation, get the link and build a Markdown representation. Search in the
|
|
||||||
'field' specified in the attachment and replace the 'slug' tag with the
|
|
||||||
generated link.
|
|
||||||
"""
|
|
||||||
if 'properties' in response and 'attachments' in response['properties']:
|
|
||||||
files_collection = app.data.driver.db['files']
|
|
||||||
for field in response['properties']['attachments']:
|
|
||||||
for attachment in response['properties']['attachments']:
|
|
||||||
# Make a list from the property path
|
|
||||||
field_name_path = attachment['field'].split('.')
|
|
||||||
# This currently allow to access only properties inside of
|
|
||||||
# the properties property
|
|
||||||
if len(field_name_path) > 1:
|
|
||||||
field_content = response[field_name_path[0]][field_name_path[1]]
|
|
||||||
# This is for the "normal" first level property
|
|
||||||
else:
|
|
||||||
field_content = response[field_name_path[0]]
|
|
||||||
for af in attachment['files']:
|
|
||||||
slug = af['slug']
|
|
||||||
slug_tag = "[{0}]".format(slug)
|
|
||||||
f = files_collection.find_one({'_id': ObjectId(af['file'])})
|
|
||||||
if f is None:
|
|
||||||
af['file'] = None
|
|
||||||
continue
|
|
||||||
size = f['size'] if 'size' in f else 'l'
|
|
||||||
# Get the correct variation from the file
|
|
||||||
thumbnail = next((item for item in f['variations'] if
|
|
||||||
item['size'] == size), None)
|
|
||||||
l = file_storage.generate_link(f['backend'], thumbnail['file_path'],
|
|
||||||
str(f['project']))
|
|
||||||
# Build Markdown img string
|
|
||||||
l = ''.format(slug, l, f['name'])
|
|
||||||
# Parse the content of the file and replace the attachment
|
|
||||||
# tag with the actual image link
|
|
||||||
field_content = field_content.replace(slug_tag, l)
|
|
||||||
# Apply the parsed value back to the property. See above for
|
|
||||||
# clarifications on how this is done.
|
|
||||||
if len(field_name_path) > 1:
|
|
||||||
response[field_name_path[0]][field_name_path[1]] = field_content
|
|
||||||
else:
|
|
||||||
response[field_name_path[0]] = field_content
|
|
||||||
|
|
||||||
|
|
||||||
def resource_parse_attachments(response):
|
|
||||||
for item in response['_items']:
|
|
||||||
item_parse_attachments(item)
|
|
||||||
|
|
||||||
|
|
||||||
def project_node_type_has_method(response):
|
def project_node_type_has_method(response):
|
||||||
"""Check for a specific request arg, and check generate the allowed_methods
|
"""Check for a specific request arg, and check generate the allowed_methods
|
||||||
list for the required node_type.
|
list for the required node_type.
|
||||||
@ -354,33 +210,26 @@ def before_returning_resource_notifications(response):
|
|||||||
notification_parse(item)
|
notification_parse(item)
|
||||||
|
|
||||||
|
|
||||||
app.on_fetched_item_nodes += before_returning_item_permissions
|
|
||||||
app.on_fetched_item_nodes += item_parse_attachments
|
|
||||||
app.on_fetched_resource_nodes += before_returning_resource_permissions
|
|
||||||
app.on_fetched_resource_nodes += resource_parse_attachments
|
|
||||||
app.on_fetched_item_node_types += before_returning_item_permissions
|
|
||||||
app.on_fetched_item_notifications += before_returning_item_notifications
|
app.on_fetched_item_notifications += before_returning_item_notifications
|
||||||
app.on_fetched_resource_notifications += before_returning_resource_notifications
|
app.on_fetched_resource_notifications += before_returning_resource_notifications
|
||||||
app.on_fetched_resource_node_types += before_returning_resource_permissions
|
|
||||||
app.on_replace_nodes += before_replacing_node
|
|
||||||
app.on_replaced_nodes += after_replacing_node
|
|
||||||
app.on_insert_nodes += before_inserting_nodes
|
|
||||||
app.on_inserted_nodes += after_inserting_nodes
|
|
||||||
app.on_fetched_item_projects += before_returning_item_permissions
|
app.on_fetched_item_projects += before_returning_item_permissions
|
||||||
app.on_fetched_item_projects += project_node_type_has_method
|
app.on_fetched_item_projects += project_node_type_has_method
|
||||||
app.on_fetched_resource_projects += before_returning_resource_permissions
|
app.on_fetched_resource_projects += before_returning_resource_permissions
|
||||||
|
|
||||||
file_storage.setup_app(app, url_prefix='/storage')
|
|
||||||
|
|
||||||
# The encoding module (receive notification and report progress)
|
# The encoding module (receive notification and report progress)
|
||||||
from modules.encoding import encoding
|
from modules.encoding import encoding
|
||||||
from modules.blender_id import blender_id
|
from modules.blender_id import blender_id
|
||||||
from modules import projects
|
from modules import projects
|
||||||
from modules import local_auth
|
from modules import local_auth
|
||||||
|
from modules import file_storage
|
||||||
from modules import users
|
from modules import users
|
||||||
|
from modules import nodes
|
||||||
|
|
||||||
app.register_blueprint(encoding, url_prefix='/encoding')
|
app.register_blueprint(encoding, url_prefix='/encoding')
|
||||||
app.register_blueprint(blender_id, url_prefix='/blender_id')
|
app.register_blueprint(blender_id, url_prefix='/blender_id')
|
||||||
projects.setup_app(app, url_prefix='/p')
|
projects.setup_app(app, url_prefix='/p')
|
||||||
local_auth.setup_app(app, url_prefix='/auth')
|
local_auth.setup_app(app, url_prefix='/auth')
|
||||||
|
file_storage.setup_app(app, url_prefix='/storage')
|
||||||
users.setup_app(app)
|
users.setup_app(app)
|
||||||
|
nodes.setup_app(app)
|
||||||
|
168
pillar/application/modules/nodes.py
Normal file
168
pillar/application/modules/nodes.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from bson import ObjectId
|
||||||
|
from flask import current_app
|
||||||
|
|
||||||
|
from application.modules import file_storage
|
||||||
|
from application.utils.authorization import check_permissions
|
||||||
|
from application.utils.gcs import update_file_name
|
||||||
|
from application.utils.activities import activity_subscribe, activity_object_add
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def item_parse_attachments(response):
|
||||||
|
"""Before returning a response, check if the 'attachments' property is
|
||||||
|
defined. If yes, load the file (for the moment only images) in the required
|
||||||
|
variation, get the link and build a Markdown representation. Search in the
|
||||||
|
'field' specified in the attachment and replace the 'slug' tag with the
|
||||||
|
generated link.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if 'properties' not in response or 'attachments' not in response['properties']:
|
||||||
|
return
|
||||||
|
|
||||||
|
files_collection = current_app.data.driver.db['files']
|
||||||
|
for attachment in response['properties']['attachments']:
|
||||||
|
# Make a list from the property path
|
||||||
|
field_name_path = attachment['field'].split('.')
|
||||||
|
# This currently allow to access only properties inside of
|
||||||
|
# the properties property
|
||||||
|
if len(field_name_path) > 1:
|
||||||
|
field_content = response[field_name_path[0]][field_name_path[1]]
|
||||||
|
# This is for the "normal" first level property
|
||||||
|
else:
|
||||||
|
field_content = response[field_name_path[0]]
|
||||||
|
for af in attachment['files']:
|
||||||
|
slug = af['slug']
|
||||||
|
slug_tag = "[{0}]".format(slug)
|
||||||
|
f = files_collection.find_one({'_id': ObjectId(af['file'])})
|
||||||
|
if f is None:
|
||||||
|
af['file'] = None
|
||||||
|
continue
|
||||||
|
size = f['size'] if 'size' in f else 'l'
|
||||||
|
# Get the correct variation from the file
|
||||||
|
thumbnail = next((item for item in f['variations'] if
|
||||||
|
item['size'] == size), None)
|
||||||
|
l = file_storage.generate_link(f['backend'], thumbnail['file_path'],
|
||||||
|
str(f['project']))
|
||||||
|
# Build Markdown img string
|
||||||
|
l = ''.format(slug, l, f['name'])
|
||||||
|
# Parse the content of the file and replace the attachment
|
||||||
|
# tag with the actual image link
|
||||||
|
field_content = field_content.replace(slug_tag, l)
|
||||||
|
|
||||||
|
# Apply the parsed value back to the property. See above for
|
||||||
|
# clarifications on how this is done.
|
||||||
|
if len(field_name_path) > 1:
|
||||||
|
response[field_name_path[0]][field_name_path[1]] = field_content
|
||||||
|
else:
|
||||||
|
response[field_name_path[0]] = field_content
|
||||||
|
|
||||||
|
|
||||||
|
def resource_parse_attachments(response):
|
||||||
|
for item in response['_items']:
|
||||||
|
item_parse_attachments(item)
|
||||||
|
|
||||||
|
def before_replacing_node(item, original):
|
||||||
|
check_permissions(original, 'PUT')
|
||||||
|
update_file_name(item)
|
||||||
|
|
||||||
|
|
||||||
|
def after_replacing_node(item, original):
|
||||||
|
"""Push an update to the Algolia index when a node item is updated. If the
|
||||||
|
project is private, prevent public indexing.
|
||||||
|
"""
|
||||||
|
projects_collection = current_app.data.driver.db['projects']
|
||||||
|
project = projects_collection.find_one({'_id': item['project']},
|
||||||
|
{'is_private': 1})
|
||||||
|
if 'is_private' in project and project['is_private']:
|
||||||
|
# Skip index updating and return
|
||||||
|
return
|
||||||
|
|
||||||
|
from algoliasearch.client import AlgoliaException
|
||||||
|
from application.utils.algolia import algolia_index_node_save
|
||||||
|
|
||||||
|
try:
|
||||||
|
algolia_index_node_save(item)
|
||||||
|
except AlgoliaException as ex:
|
||||||
|
log.warning('Unable to push node info to Algolia for node %s; %s',
|
||||||
|
item.get('_id'), ex)
|
||||||
|
|
||||||
|
|
||||||
|
def before_inserting_nodes(items):
|
||||||
|
"""Before inserting a node in the collection we check if the user is allowed
|
||||||
|
and we append the project id to it.
|
||||||
|
"""
|
||||||
|
nodes_collection = current_app.data.driver.db['nodes']
|
||||||
|
|
||||||
|
def find_parent_project(node):
|
||||||
|
"""Recursive function that finds the ultimate parent of a node."""
|
||||||
|
if node and 'parent' in node:
|
||||||
|
parent = nodes_collection.find_one({'_id': node['parent']})
|
||||||
|
return find_parent_project(parent)
|
||||||
|
if node:
|
||||||
|
return node
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
check_permissions(item, 'POST')
|
||||||
|
if 'parent' in item and 'project' not in item:
|
||||||
|
parent = nodes_collection.find_one({'_id': item['parent']})
|
||||||
|
project = find_parent_project(parent)
|
||||||
|
if project:
|
||||||
|
item['project'] = project['_id']
|
||||||
|
|
||||||
|
|
||||||
|
def after_inserting_nodes(items):
|
||||||
|
for item in items:
|
||||||
|
# Skip subscriptions for first level items (since the context is not a
|
||||||
|
# node, but a project).
|
||||||
|
# TODO: support should be added for mixed context
|
||||||
|
if 'parent' not in item:
|
||||||
|
return
|
||||||
|
context_object_id = item['parent']
|
||||||
|
if item['node_type'] == 'comment':
|
||||||
|
nodes_collection = current_app.data.driver.db['nodes']
|
||||||
|
parent = nodes_collection.find_one({'_id': item['parent']})
|
||||||
|
# Always subscribe to the parent node
|
||||||
|
activity_subscribe(item['user'], 'node', item['parent'])
|
||||||
|
if parent['node_type'] == 'comment':
|
||||||
|
# If the parent is a comment, we provide its own parent as
|
||||||
|
# context. We do this in order to point the user to an asset
|
||||||
|
# or group when viewing the notification.
|
||||||
|
verb = 'replied'
|
||||||
|
context_object_id = parent['parent']
|
||||||
|
# Subscribe to the parent of the parent comment (post or group)
|
||||||
|
activity_subscribe(item['user'], 'node', parent['parent'])
|
||||||
|
else:
|
||||||
|
activity_subscribe(item['user'], 'node', item['_id'])
|
||||||
|
verb = 'commented'
|
||||||
|
else:
|
||||||
|
verb = 'posted'
|
||||||
|
activity_subscribe(item['user'], 'node', item['_id'])
|
||||||
|
|
||||||
|
activity_object_add(
|
||||||
|
item['user'],
|
||||||
|
verb,
|
||||||
|
'node',
|
||||||
|
item['_id'],
|
||||||
|
'node',
|
||||||
|
context_object_id
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_app(app):
|
||||||
|
from application import before_returning_item_permissions, before_returning_resource_permissions
|
||||||
|
|
||||||
|
# Permission hooks
|
||||||
|
app.on_fetched_item_nodes += before_returning_item_permissions
|
||||||
|
app.on_fetched_resource_nodes += before_returning_resource_permissions
|
||||||
|
|
||||||
|
app.on_fetched_item_nodes += item_parse_attachments
|
||||||
|
app.on_fetched_resource_nodes += resource_parse_attachments
|
||||||
|
app.on_replace_nodes += before_replacing_node
|
||||||
|
app.on_replaced_nodes += after_replacing_node
|
||||||
|
app.on_insert_nodes += before_inserting_nodes
|
||||||
|
app.on_inserted_nodes += after_inserting_nodes
|
Loading…
x
Reference in New Issue
Block a user