def comments_create(): content = request.form['content'] parent_id = request.form.get('parent_id') api = system_util.pillar_api() parent_node = Node.find(parent_id, api=api) node_asset_props = dict( project=parent_node.project, name='Comment', user=current_user.objectid, node_type='comment', properties=dict( content=content, status='published', confidence=0, rating_positive=0, rating_negative=0)) if parent_id: node_asset_props['parent'] = parent_id # Get the parent node and check if it's a comment. In which case we flag # the current comment as a reply. parent_node = Node.find(parent_id, api=api) if parent_node.node_type == 'comment': node_asset_props['properties']['is_reply'] = True node_asset = Node(node_asset_props) node_asset.create(api=api) return jsonify( asset_id=node_asset._id, content=node_asset.properties.content)
def comments_create(): content = request.form['content'] parent_id = request.form.get('parent_id') api = system_util.pillar_api() parent_node = Node.find(parent_id, api=api) node_asset_props = dict(project=parent_node.project, name='Comment', user=current_user.objectid, node_type='comment', properties=dict(content=content, status='published', confidence=0, rating_positive=0, rating_negative=0)) if parent_id: node_asset_props['parent'] = parent_id # Get the parent node and check if it's a comment. In which case we flag # the current comment as a reply. parent_node = Node.find(parent_id, api=api) if parent_node.node_type == 'comment': node_asset_props['properties']['is_reply'] = True node_asset = Node(node_asset_props) node_asset.create(api=api) return jsonify(asset_id=node_asset._id, content=node_asset.properties.content)
def jstree(node_id): """JsTree view. This return a lightweight version of the node, to be used by JsTree in the frontend. We have two possible cases: - https://pillar/<node_id>/jstree (construct the whole expanded tree starting from the node_id. Use only once) - https://pillar/<node_id>/jstree&children=1 (deliver the children of a node - use in the navigation of the tree) """ # Get node with basic embedded data api = system_util.pillar_api() node = Node.find(node_id, api=api) if request.args.get('children') != '1': return jsonify(items=jstree_build_from_node(node)) if node.node_type == 'storage': storage = StorageNode(node) # Check if we specify a path within the storage path = request.args.get('path') # Generate the storage listing listing = storage.browse(path) # Inject the current node id in the response, so that JsTree can # expose the storage_node property and use it for further queries listing['storage_node'] = node._id if 'children' in listing: for child in listing['children']: child['storage_node'] = node._id return jsonify(listing) return jsonify(jstree_build_children(node))
def view_embed(node_id): api = system_util.pillar_api() # Get node, we'll embed linked objects later. try: node = Node.find(node_id, api=api) except ResourceNotFound: return render_template('errors/404_embed.html') except ForbiddenAccess: return render_template('errors/403_embed.html') project_projection = {'project': {'url': 1, 'name': 1}} project = Project.find(node.project, project_projection, api=api) node.picture = get_file(node.picture, api=api) node.user = node.user and User.find(node.user, api=api) write_access = 'PUT' in (node.allowed_methods or set()) extra_template_args = {'project': project} return render_template( 'nodes/custom/dillo_post/view_embed.html', node_id=node._id, node=node, write_access=write_access, api=api, **extra_template_args)
def comments_for_node(node_id): """Shows the comments attached to the given node.""" api = system_util.pillar_api() node = Node.find(node_id, api=api) project = Project({'_id': node.project}) can_post_comments = project.node_type_has_method('comment', 'POST', api=api) can_comment_override = request.args.get('can_comment', 'True') == 'True' can_post_comments = can_post_comments and can_comment_override # Query for all children, i.e. comments on the node. comments = Node.all( { 'where': { 'node_type': 'comment', 'parent': node_id }, }, api=api) def enrich(some_comment): some_comment['_user'] = subquery.get_user_info(some_comment['user']) some_comment['_is_own'] = some_comment['user'] == current_user.objectid some_comment['_current_user_rating'] = None # tri-state boolean some_comment[ '_rating'] = some_comment.properties.rating_positive - some_comment.properties.rating_negative if current_user.is_authenticated: for rating in some_comment.properties.ratings or (): if rating.user != current_user.objectid: continue some_comment['_current_user_rating'] = rating.is_positive for comment in comments['_items']: # Query for all grandchildren, i.e. replies to comments on the node. comment['_replies'] = Node.all( { 'where': { 'node_type': 'comment', 'parent': comment['_id'] }, }, api=api) enrich(comment) for reply in comment['_replies']['_items']: enrich(reply) nr_of_comments = sum(1 + comment['_replies']['_meta']['total'] for comment in comments['_items']) return render_template('nodes/custom/comment/list_embed.html', node_id=node_id, comments=comments, nr_of_comments=nr_of_comments, show_comments=True, can_post_comments=can_post_comments)
def comments_index(): parent_id = request.args.get('parent_id') # Get data only if we format it api = system_util.pillar_api() if request.args.get('format'): nodes = Node.all({ 'where': '{"node_type" : "comment", "parent": "%s"}' % (parent_id), 'embedded': '{"user":1}'}, api=api) comments = [] for comment in nodes._items: # Query for first level children (comment replies) replies = Node.all({ 'where': '{"node_type" : "comment", "parent": "%s"}' % (comment._id), 'embedded': '{"user":1}'}, api=api) replies = replies._items if replies._items else None if replies: replies = [format_comment(reply, is_reply=True) for reply in replies] comments.append( format_comment(comment, is_reply=False, replies=replies)) if request.args.get('format') == 'json': return_content = jsonify(items=[c for c in comments if c is not None]) else: parent_node = Node.find(parent_id, api=api) project = Project.find(parent_node.project, api=api) has_method_POST = project.node_type_has_method('comment', 'POST', api=api) # Data will be requested via javascript return_content = render_template('nodes/custom/_comments.html', parent_id=parent_id, has_method_POST=has_method_POST) return return_content
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(parent.parent, 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'])
def load_latest(list_of_ids, node_type=None): """Loads a list of IDs in reversed order.""" if not list_of_ids: return [] # Construct query parameters outside the loop. projection = {'name': 1, 'user': 1, 'node_type': 1, 'project': 1, 'properties.url': 1, 'properties.content_type': 1, 'picture': 1} params = {'projection': projection, 'embedded': {'user': 1}} if node_type == 'post': projection['properties.content'] = 1 elif node_type == 'asset': projection['description'] = 1 list_latest = [] for node_id in reversed(list_of_ids or ()): try: node_item = Node.find(node_id, params, api=api) node_item.picture = utils.get_file(node_item.picture, api=api) list_latest.append(node_item) except ForbiddenAccess: pass except ResourceNotFound: log.warning('Project %s refers to removed node %s!', project._id, node_id) return list_latest
def add_featured_node(): """Feature a node in a project. This method belongs here, because it affects the project node itself, not the asset. """ api = system_util.pillar_api() node = Node.find(request.form['node_id'], api=api) action = project_update_nodes_list(node, list_name='featured') return jsonify(status='success', data=dict(action=action))
def add_featured_node(): """Feature a node in a project. This method belongs here, because it affects the project node itself, not the asset. """ api = system_util.pillar_api() node = Node.find(request.form['node_id'], api=api) action = project_update_nodes_list(node, project_id=node.project, list_name='featured') return jsonify(status='success', data=dict(action=action))
def projects_move_node(): # Process the move action node_id = request.form['node_id'] dest_parent_node_id = request.form['dest_parent_node_id'] api = SystemUtility.attract_api() node = Node.find(node_id, api=api) node.parent = dest_parent_node_id node.update(api=api) return jsonify(status='success', data=dict(message='node moved'))
def delete_node(): """Delete a node""" api = system_util.pillar_api() node = Node.find(request.form['node_id'], api=api) if not node.has_method('DELETE'): return abort(403) node.delete(api=api) return jsonify(status='success', data=dict(message='Node deleted'))
def comments_create(): content = request.form['content'] parent_id = request.form.get('parent_id') if not parent_id: log.warning('User %s tried to create comment without parent_id', current_user.objectid) raise wz_exceptions.UnprocessableEntity() api = system_util.pillar_api() parent_node = Node.find(parent_id, api=api) if not parent_node: log.warning( 'Unable to create comment for user %s, parent node %r not found', current_user.objectid, parent_id) raise wz_exceptions.UnprocessableEntity() log.info('Creating comment for user %s on parent node %r', current_user.objectid, parent_id) comment_props = dict(project=parent_node.project, name='Comment', user=current_user.objectid, node_type='comment', properties=dict(content=content, status='published', confidence=0, rating_positive=0, rating_negative=0)) if parent_id: comment_props['parent'] = parent_id # Get the parent node and check if it's a comment. In which case we flag # the current comment as a reply. parent_node = Node.find(parent_id, api=api) if parent_node.node_type == 'comment': comment_props['properties']['is_reply'] = True comment = Node(comment_props) comment.create(api=api) return jsonify({'node_id': comment._id}), 201
def view_node(project_url, node_id): """Entry point to view a node in the context of a project""" # Some browsers mangle URLs and URL-encode /p/{p-url}/#node-id if node_id.startswith('#'): return redirect(url_for('projects.view_node', project_url=project_url, node_id=node_id[1:]), code=301) # permanent redirect theatre_mode = 't' in request.args api = system_util.pillar_api() # First we check if it's a simple string, in which case we are looking for # a static page. Maybe we could use bson.objectid.ObjectId.is_valid(node_id) if not utils.is_valid_id(node_id): # raise wz_exceptions.NotFound('No such node') project, node = render_node_page(project_url, node_id, api) else: # Fetch the node before the project. If this user has access to the # node, we should be able to get the project URL too. try: node = Node.find(node_id, api=api) except ForbiddenAccess: return render_template('errors/403.html'), 403 except ResourceNotFound: raise wz_exceptions.NotFound('No such node') try: project = Project.find_one({'where': {"url": project_url, '_id': node.project}}, api=api) except ResourceNotFound: # In theatre mode, we don't need access to the project at all. if theatre_mode: project = None else: raise wz_exceptions.NotFound('No such project') og_picture = node.picture = utils.get_file(node.picture, api=api) if project: if not node.picture: og_picture = utils.get_file(project.picture_header, api=api) project.picture_square = utils.get_file(project.picture_square, api=api) # Append _theatre to load the proper template theatre = '_theatre' if theatre_mode else '' extension_sidebar_links = current_app.extension_sidebar_links(project) return render_template('projects/view{}.html'.format(theatre), api=api, project=project, node=node, show_node=True, show_project=False, og_picture=og_picture, extension_sidebar_links=extension_sidebar_links)
def _find_parent(parent_node_id) -> Node: return Node.find(parent_node_id, { 'projection': { '_id': 1, 'name': 1, 'node_type': 1, 'project': 1, 'parent': 1, 'properties.url': 1, } }, api=api)
def homepage(): """Homepage""" if not current_user.is_authenticated(): return render_template( 'join.html', title="join") # Get latest blog posts api = system_util.pillar_api() latest_posts = Node.all({ 'projection': {'name': 1, 'project': 1, 'user': 1, 'node_type': 1, 'picture': 1, 'properties.status': 1, 'properties.url': 1}, 'where': {'node_type': 'post', 'properties.status': 'published'}, 'embedded': {'user': 1, 'project': 1}, 'sort': '-_created', 'max_results': '3' }, api=api) # Append picture Files to last_posts for post in latest_posts._items: if post.picture: post.picture = get_file(post.picture, api=api) # Get latest assets added to any project latest_assets = Node.latest('assets', api=api) # Append picture Files to latest_assets for asset in latest_assets._items: if asset.picture: asset.picture = get_file(asset.picture, api=api) # Get latest comments to any node latest_comments = Node.latest('comments', api=api) # Parse results for replies for comment in latest_comments._items: if comment.properties.is_reply: comment.parent = Node.find(comment.parent.parent, api=api) else: comment.parent = comment.parent main_project = Project.find(app.config['MAIN_PROJECT_ID'], api=api) main_project.picture_square = get_file(main_project.picture_square, api=api) main_project.picture_header = get_file(main_project.picture_header, api=api) return render_template( 'homepage.html', main_project=main_project, latest_posts=latest_posts._items, latest_assets=latest_assets._items, latest_comments=latest_comments._items, api=api)
def comments_for_node(node_id): """Shows the comments attached to the given node. The URL can be overridden in order to define can_post_comments in a different way """ api = system_util.pillar_api() node = Node.find(node_id, api=api) project = Project({'_id': node.project}) can_post_comments = project.node_type_has_method('comment', 'POST', api=api) can_comment_override = request.args.get('can_comment', 'True') == 'True' can_post_comments = can_post_comments and can_comment_override return render_comments_for_node(node_id, can_post_comments=can_post_comments)
def comments_index(): parent_id = request.args.get('parent_id') # Get data only if we format it api = system_util.pillar_api() if request.args.get('format'): nodes = Node.all( { 'where': '{"node_type" : "comment", "parent": "%s"}' % (parent_id), 'embedded': '{"user":1}' }, api=api) comments = [] for comment in nodes._items: # Query for first level children (comment replies) replies = Node.all( { 'where': '{"node_type" : "comment", "parent": "%s"}' % (comment._id), 'embedded': '{"user":1}' }, api=api) replies = replies._items if replies._items else None if replies: replies = [ format_comment(reply, is_reply=True) for reply in replies ] comments.append( format_comment(comment, is_reply=False, replies=replies)) if request.args.get('format') == 'json': return_content = jsonify( items=[c for c in comments if c is not None]) else: parent_node = Node.find(parent_id, api=api) project = Project.find(parent_node.project, api=api) has_method_POST = project.node_type_has_method('comment', 'POST', api=api) # Data will be requested via javascript return_content = render_template('nodes/custom/_comments.html', parent_id=parent_id, has_method_POST=has_method_POST) return return_content
def commentform_for_node(node_id): """Shows only the comment for for comments attached to the given node. i.e. does not show the comments themselves, just the form to post a new comment. """ api = system_util.pillar_api() node = Node.find(node_id, api=api) project = Project({'_id': node.project}) can_post_comments = project.node_type_has_method('comment', 'POST', api=api) return render_template('nodes/custom/comment/list_embed.html', node_id=node_id, show_comments=False, can_post_comments=can_post_comments)
def move_node(): """Move a node within a project. While this affects the node.parent prop, we keep it in the scope of the project.""" node_id = request.form['node_id'] dest_parent_node_id = request.form.get('dest_parent_node_id') api = system_util.pillar_api() node = Node.find(node_id, api=api) # Get original parent id for clearing template fragment on success previous_parent_id = node.parent if dest_parent_node_id: node.parent = dest_parent_node_id elif node.parent: node.parent = None node.update(api=api) return jsonify(status='success', data=dict(message='node moved'))
def toggle_node_public(): """Give a node GET permissions for the world. Later on this can turn into a more powerful permission management function. """ api = system_util.pillar_api() node = Node.find(request.form['node_id'], api=api) if node.has_method('PUT'): if node.permissions and 'world' in node.permissions.to_dict(): node.permissions = {} message = "Node is not public anymore." else: node.permissions = dict(world=['GET']) message = "Node is now public!" node.update(api=api) return jsonify(status='success', data=dict(message=message)) else: return abort(403)
def delete(node_id): """Generic node deletion """ api = SystemUtility.attract_api() node = Node.find(node_id, api=api) name = node.name node_type = NodeType.find(node.node_type, api=api) try: node.delete(api=api) forbidden = False except ForbiddenAccess: forbidden = True if not forbidden: # print (node_type['name']) return redirect(url_for('nodes.index', node_type_name=node_type['name'])) else: return redirect(url_for('nodes.edit', node_id=node._id))
def url_for_node(node_id=None, node=None): assert isinstance(node_id, (str, type(None))) api = system_util.pillar_api() if node_id is None and node is None: raise ValueError('Either node or node_id must be given') if node is None: try: node = Node.find(node_id, api=api) except ResourceNotFound: log.warning( 'url_for_node(node_id=%r, node=None): Unable to find node.', node_id) raise wz_exceptions.NotFound('Unable to find node %r' % node_id) return finders.find_url_for_node(node)
def posts_edit(post_id): api = system_util.pillar_api() try: post = Node.find(post_id, {'embedded': '{"user": 1}'}, api=api) except ResourceNotFound: return abort(404) # Check if user is allowed to edit the post if not post.has_method('PUT'): return abort(403) project = Project.find(post.project, api=api) attach_project_pictures(project, api) node_type = project.get_node_type(post.node_type) form = get_node_form(node_type) if form.validate_on_submit(): if process_node_form(form, node_id=post_id, node_type=node_type, user=current_user.objectid): # The the post is published, add it to the list if form.status.data == 'published': project_update_nodes_list(post, project_id=project._id, list_name='blog') return redirect(url_for_node(node=post)) form.parent.data = post.parent form.name.data = post.name form.content.data = post.properties.content form.status.data = post.properties.status form.url.data = post.properties.url if post.picture: form.picture.data = post.picture # Embed picture file post.picture = get_file(post.picture, api=api) if post.properties.picture_square: form.picture_square.data = post.properties.picture_square return render_template('nodes/custom/post/edit.html', node_type=node_type, post=post, form=form, project=project, api=api)
def delete(node_id): """Generic node deletion """ api = system_util.pillar_api() node = Node.find(node_id, api=api) name = node.name node_type = NodeType.find(node.node_type, api=api) try: node.delete(api=api) forbidden = False except ForbiddenAccess: forbidden = True if not forbidden: # print (node_type['name']) return redirect( url_for('nodes.index', node_type_name=node_type['name'])) else: return redirect(url_for('nodes.edit', node_id=node._id))
def toggle_node_public(): """Give a node GET permissions for the world. Later on this can turn into a more powerful permission management function. """ api = system_util.pillar_api() node = Node.find(request.form['node_id'], api=api) if node.has_method('PUT'): if node.permissions and 'world' in node.permissions.to_dict(): node.permissions = {} message = "Node is not public anymore." else: node.permissions = dict(world=['GET']) message = "Node is now public!" node.update(api=api) # Delete cached parent template fragment delete_redis_cache_template('group_view', node.parent) return jsonify(status='success', data=dict(message=message)) else: return abort(403)
def shot_edit(): """We want to be able to edit the following properties: - notes - status - cut in - cut out - picture (optional) """ api = system_util.pillar_api() shot_id = request.form['shot_id'] shot = Node.find(shot_id, api=api) shot.properties.notes = request.form['shot_notes'] shot.properties.status = request.form['shot_status'] shot.properties.cut_in = int(request.form['shot_cut_in']) shot.properties.cut_out = int(request.form['shot_cut_out']) shot.update(api=api) return jsonify(shot.to_dict())
def move_node(): """Move a node within a project. While this affects the node.parent prop, we keep it in the scope of the project.""" node_id = request.form['node_id'] dest_parent_node_id = request.form.get('dest_parent_node_id') api = system_util.pillar_api() node = Node.find(node_id, api=api) # Get original parent id for clearing template fragment on success previous_parent_id = node.parent if dest_parent_node_id: node.parent = dest_parent_node_id elif node.parent: node.parent = None node.update(api=api) # Delete cached parent template fragment if node.parent: delete_redis_cache_template('group_view', node.parent) if previous_parent_id: delete_redis_cache_template('group_view', previous_parent_id) return jsonify(status='success', data=dict(message='node moved'))
def find_for_comment(): """Returns the URL for a comment.""" parent = node while parent.node_type == 'comment': if isinstance(parent.parent, pillarsdk.Resource): parent = parent.parent continue try: parent = Node.find(parent.parent, 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 = url_for_node(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)
def project_update_nodes_list(node_id, project_id=None, list_name='latest'): """Update the project node with the latest edited or favorited node. The list value can be 'latest' or 'featured' and it will determined where the node reference will be placed in. """ if not project_id and 'current_project_id' in session: project_id = session['current_project_id'] elif not project_id: return None api = SystemUtility.attract_api() project = Node.find(project_id, api=api) if list_name == 'latest': nodes_list = project.properties.nodes_latest else: nodes_list = project.properties.nodes_featured # Do not allow adding project to lists if node_id == project._id: return "fail" if not nodes_list: node_list_name = 'nodes_' + list_name project.properties[node_list_name] = [] nodes_list = project.properties[node_list_name] elif len(nodes_list) > 5: nodes_list.pop(0) if node_id in nodes_list: # Pop to put this back on top of the list nodes_list.remove(node_id) if list_name == 'featured': # We treat the action as a toggle and do not att the item back project.update(api=api) return "removed" nodes_list.append(node_id) project.update(api=api) return "added"
def toggle_node_project_header(): """Sets this node as the project header, or removes it if already there. """ api = system_util.pillar_api() node_id = request.form['node_id'] try: node = Node.find(node_id, {'projection': {'project': 1}}, api=api) except ResourceNotFound: log.info( 'User %s trying to toggle non-existing node %s as project header', current_user.objectid, node_id) return jsonify(_status='ERROR', message='Node not found'), 404 try: project = Project.find(node.project, api=api) except ResourceNotFound: log.info( 'User %s trying to toggle node %s as project header, but project %s not found', current_user.objectid, node_id, node.project) return jsonify(_status='ERROR', message='Project not found'), 404 # Toggle header node if project.header_node == node_id: log.debug('Un-setting header node of project %s', node.project) project.header_node = None action = 'unset' else: log.debug('Setting node %s as header of project %s', node_id, node.project) project.header_node = node_id action = 'set' # Save the project project.update(api=api) return jsonify({'_status': 'OK', 'action': action})
def view(node_id): api = system_util.pillar_api() # Get node, we'll embed linked objects later. try: node = Node.find(node_id, api=api) except ResourceNotFound: return render_template('errors/404_embed.html') except ForbiddenAccess: return render_template('errors/403_embed.html') node_type_name = node.node_type if node_type_name == 'post': # Posts shouldn't be shown using this end point, redirect to the correct one. return redirect(url_for_node(node=node)) # Set the default name of the template path based on the node name template_path = os.path.join('nodes', 'custom', node_type_name) # Set the default action for a template. By default is view and we override # it only if we are working storage nodes, where an 'index' is also possible template_action = 'view' def allow_link(): """Helper function to cross check if the user is authenticated, and it is has the 'subscriber' role. Also, we check if the node has world GET permissions, which means it's free. """ # Check if node permissions for the world exist (if node is free) if node.permissions and node.permissions.world: return 'GET' in node.permissions.world if current_user.is_authenticated(): allowed_roles = {u'subscriber', u'demo', u'admin'} return bool(allowed_roles.intersection(current_user.roles or ())) return False link_allowed = allow_link() node_type_handlers = { 'asset': _view_handler_asset, 'storage': _view_handler_storage, 'texture': _view_handler_texture, } if node_type_name in node_type_handlers: handler = node_type_handlers[node_type_name] template_path, template_action = handler(node, template_path, template_action, link_allowed) # Fetch linked resources. node.picture = get_file(node.picture, api=api) node.user = node.user and pillarsdk.User.find(node.user, api=api) node.parent = node.parent and pillarsdk.Node.find(node.parent, api=api) # Get children children_projection = {'project': 1, 'name': 1, 'picture': 1, 'parent': 1, 'node_type': 1, 'properties.order': 1, 'properties.status': 1, 'user': 1, 'properties.content_type': 1} children_where = {'parent': node._id} if node_type_name == 'group': children_where['properties.status'] = 'published' children_projection['permissions.world'] = 1 else: children_projection['properties.files'] = 1 children_projection['properties.is_tileable'] = 1 try: children = Node.all({ 'projection': children_projection, 'where': children_where, 'sort': [('properties.order', 1), ('name', 1)]}, api=api) except ForbiddenAccess: return render_template('errors/403_embed.html') children = children._items for child in children: child.picture = get_file(child.picture, api=api) if request.args.get('format') == 'json': node = node.to_dict() node['url_edit'] = url_for('nodes.edit', node_id=node['_id']), return jsonify({ 'node': node, 'children': children.to_dict(), 'parent': node.parent.to_dict() if node.parent else {} }) # Check if template exists on the filesystem template_path = '{0}/{1}_embed.html'.format(template_path, template_action) template_path_full = os.path.join(app.config['TEMPLATES_PATH'], template_path) if not os.path.exists(template_path_full): raise NotFound("Missing template '{0}'".format(template_path)) return render_template(template_path, node_id=node._id, node=node, parent=node.parent, children=children, config=app.config, api=api)
def task_edit(): """We want to be able to edit the following properties: - status - owners - description - picture (optional) """ api = SystemUtility.attract_api() task_id = request.form["task_id"] task = Node.find(task_id, api=api) task.description = request.form["task_description"] if request.form["task_revision"]: task.properties.revision = int(request.form["task_revision"]) task.properties.status = request.form["task_status"] task.properties.filepath = request.form["task_filepath"] task.properties.owners.users = request.form.getlist("task_owners_users[]") siblings = Node.all( {"where": 'parent==ObjectId("%s")' % task.parent, "embedded": '{"picture":1, "user":1}'}, api=api ) def check_conflict(task_current, task_sibling): return revsion_conflict[task_current.name](task_current, task_sibling) def task_animation(task_current, task_sibling): if task_sibling.name in ["fx_hair", "fx_smoke", "fx_grass", "lighting"]: if task_current.properties.revision > task_sibling.properties.revision: return True return False def task_lighting(task_current, task_sibling): if task_sibling.name in ["fx_hair", "fx_smoke", "fx_grass", "animation"]: if task_current.properties.revision < task_sibling.properties.revision: return True return False def task_fx_hair(task_current, task_sibling): if task_sibling.name in ["animation"]: if task_current.properties.revision < task_sibling.properties.revision: return True if task_sibling.name in ["lighting"]: if task_current.properties.revision > task_sibling.properties.revision: return True return False def task_fx_grass(task_current, task_sibling): pass def task_fx_smoke(task_current, task_sibling): pass revsion_conflict = { "animation": task_animation, "lighting": task_lighting, "fx_hair": task_fx_hair, "fx_grass": task_fx_grass, "fx_smoke": task_fx_smoke, } if task.properties.revision: for sibling in siblings._items: if sibling.properties.revision and sibling._id != task_id: if check_conflict(task, sibling) == True: task.properties.is_conflicting = True break else: task.properties.is_conflicting = False task.update(api=api) return jsonify(task.to_dict())
def view(node_id, extra_template_args: dict = None): api = system_util.pillar_api() # Get node, we'll embed linked objects later. try: node = Node.find(node_id, api=api) except ResourceNotFound: return render_template('errors/404_embed.html') except ForbiddenAccess: return render_template('errors/403_embed.html') node_type_name = node.node_type if node_type_name == 'post' and not request.args.get('embed'): # Posts shouldn't be shown at this route (unless viewed embedded, tipically # after an edit. Redirect to the correct one. return redirect(url_for_node(node=node)) # Set the default name of the template path based on the node name template_path = os.path.join('nodes', 'custom', node_type_name) # Set the default action for a template. By default is view and we override # it only if we are working storage nodes, where an 'index' is also possible template_action = 'view' def allow_link(): """Helper function to cross check if the user is authenticated, and it is has the 'subscriber' role. Also, we check if the node has world GET permissions, which means it's free. """ # Check if node permissions for the world exist (if node is free) if node.permissions and node.permissions.world: return 'GET' in node.permissions.world return current_user.has_cap('subscriber') link_allowed = allow_link() node_type_handlers = { 'asset': _view_handler_asset, 'storage': _view_handler_storage, 'texture': _view_handler_texture, 'hdri': _view_handler_hdri, } if node_type_name in node_type_handlers: handler = node_type_handlers[node_type_name] template_path, template_action = handler(node, template_path, template_action, link_allowed) # Fetch linked resources. node.picture = get_file(node.picture, api=api) node.user = node.user and pillarsdk.User.find(node.user, api=api) try: node.parent = node.parent and pillarsdk.Node.find(node.parent, api=api) except ForbiddenAccess: # This can happen when a node has world-GET, but the parent doesn't. node.parent = None # Get children children_projection = { 'project': 1, 'name': 1, 'picture': 1, 'parent': 1, 'node_type': 1, 'properties.order': 1, 'properties.status': 1, 'user': 1, 'properties.content_type': 1 } children_where = {'parent': node._id} if node_type_name in GROUP_NODES: children_where['properties.status'] = 'published' children_projection['permissions.world'] = 1 else: children_projection['properties.files'] = 1 children_projection['properties.is_tileable'] = 1 try: children = Node.all( { 'projection': children_projection, 'where': children_where, 'sort': [('properties.order', 1), ('name', 1)] }, api=api) except ForbiddenAccess: return render_template('errors/403_embed.html') children = children._items for child in children: child.picture = get_file(child.picture, api=api) # Overwrite the file length by the biggest variation, if any. if node.file and node.file_variations: node.file.length = max(var.length for var in node.file_variations) if request.args.get('format') == 'json': node = node.to_dict() node['url_edit'] = url_for('nodes.edit', node_id=node['_id']) return jsonify({ 'node': node, 'children': children.to_dict() if children else {}, 'parent': node['parent'] if 'parent' in node else {} }) if 't' in request.args: template_path = os.path.join('nodes', 'custom', 'asset') template_action = 'view_theatre' template_path = '{0}/{1}_embed.html'.format(template_path, template_action) # Full override for AMP view if request.args.get('format') == 'amp': template_path = 'nodes/view_amp.html' write_access = 'PUT' in (node.allowed_methods or set()) if extra_template_args is None: extra_template_args = {} try: return render_template(template_path, node_id=node._id, node=node, parent=node.parent, children=children, config=current_app.config, write_access=write_access, api=api, **extra_template_args) except TemplateNotFound: log.error('Template %s does not exist for node type %s', template_path, node_type_name) return render_template('nodes/error_type_not_found.html', node_id=node._id, node=node, parent=node.parent, children=children, config=current_app.config, write_access=write_access, api=api, **extra_template_args)
def edit(node_id): """Generic node editing form """ def set_properties(dyn_schema, form_schema, node_properties, form, prefix="", set_data=True): """Initialize custom properties for the form. We run this function once before validating the function with set_data=False, so that we can set any multiselect field that was originally specified empty and fill it with the current choices. """ for prop in dyn_schema: schema_prop = dyn_schema[prop] form_prop = form_schema[prop] prop_name = "{0}{1}".format(prefix, prop) if schema_prop['type'] == 'dict': set_properties( schema_prop['schema'], form_prop['schema'], node_properties[prop_name], form, "{0}__".format(prop_name)) continue if prop_name not in form: continue try: db_prop_value = node_properties[prop] except KeyError: log.debug('%s not found in form for node %s', prop_name, node_id) continue if schema_prop['type'] == 'datetime': db_prop_value = datetime.strptime(db_prop_value, app.config['RFC1123_DATE_FORMAT']) if isinstance(form[prop_name], SelectMultipleField): # If we are dealing with a multiselect field, check if # it's empty (usually because we can't query the whole # database to pick all the choices). If it's empty we # populate the choices with the actual data. if not form[prop_name].choices: form[prop_name].choices = [(d, d) for d in db_prop_value] # Choices should be a tuple with value and name # Assign data to the field if set_data: if prop_name == 'attachments': for attachment_collection in db_prop_value: for a in attachment_collection['files']: attachment_form = ProceduralFileSelectForm() attachment_form.file = a['file'] attachment_form.slug = a['slug'] attachment_form.size = 'm' form[prop_name].append_entry(attachment_form) elif prop_name == 'files': schema = schema_prop['schema']['schema'] # Extra entries are caused by min_entries=1 in the form # creation. field_list = form[prop_name] if len(db_prop_value) > 0: while len(field_list): field_list.pop_entry() for file_data in db_prop_value: file_form_class = build_file_select_form(schema) subform = file_form_class() for key, value in file_data.iteritems(): setattr(subform, key, value) field_list.append_entry(subform) # elif prop_name == 'tags': # form[prop_name].data = ', '.join(data) else: form[prop_name].data = db_prop_value else: # Default population of multiple file form list (only if # we are getting the form) if request.method == 'POST': continue if prop_name == 'attachments': if not db_prop_value: attachment_form = ProceduralFileSelectForm() attachment_form.file = 'file' attachment_form.slug = '' attachment_form.size = '' form[prop_name].append_entry(attachment_form) api = system_util.pillar_api() node = Node.find(node_id, api=api) project = Project.find(node.project, api=api) node_type = project.get_node_type(node.node_type) form = get_node_form(node_type) user_id = current_user.objectid dyn_schema = node_type['dyn_schema'].to_dict() form_schema = node_type['form_schema'].to_dict() error = "" node_properties = node.properties.to_dict() ensure_lists_exist_as_empty(node.to_dict(), node_type) set_properties(dyn_schema, form_schema, node_properties, form, set_data=False) if form.validate_on_submit(): if process_node_form(form, node_id=node_id, node_type=node_type, user=user_id): # Handle the specific case of a blog post if node_type.name == 'post': project_update_nodes_list(node, list_name='blog') else: project_update_nodes_list(node) # Emergency hardcore cache flush # cache.clear() return redirect(url_for('nodes.view', node_id=node_id, embed=1, _external=True, _scheme=app.config['SCHEME'])) else: log.debug('Error sending data to Pillar, see Pillar logs.') error = 'Server error' else: if form.errors: log.debug('Form errors: %s', form.errors) # Populate Form form.name.data = node.name form.description.data = node.description if 'picture' in form: form.picture.data = node.picture if node.parent: form.parent.data = node.parent set_properties(dyn_schema, form_schema, node_properties, form) # Get previews node.picture = get_file(node.picture, api=api) if node.picture else None # Get Parent try: parent = Node.find(node['parent'], api=api) except KeyError: parent = None except ResourceNotFound: parent = None embed_string = '' # Check if we want to embed the content via an AJAX call if request.args.get('embed'): if request.args.get('embed') == '1': # Define the prefix for the embedded template embed_string = '_embed' template = '{0}/edit{1}.html'.format(node_type['name'], embed_string) # We should more simply check if the template file actually exsists on # the filesystem level try: return render_template( template, node=node, parent=parent, form=form, errors=form.errors, error=error, api=api) except TemplateNotFound: template = 'nodes/edit{1}.html'.format(node_type['name'], embed_string) return render_template( template, node=node, parent=parent, form=form, errors=form.errors, error=error, api=api)
def get_node(node_id, user_id): api = system_util.pillar_api() node = Node.find(node_id + '/?embedded={"node_type":1}', api=api) return node.to_dict()
def edit(node_id): """Generic node editing form, displayed only if the user is allowed. """ def set_properties(dyn_schema, form_schema, node_properties, form, set_data, prefix=""): """Initialize custom properties for the form. We run this function once before validating the function with set_data=False, so that we can set any multiselect field that was originally specified empty and fill it with the current choices. """ log.debug('set_properties(..., prefix=%r, set_data=%r) called', prefix, set_data) for prop, schema_prop in dyn_schema.items(): prop_name = "{0}{1}".format(prefix, prop) if prop_name not in form: continue try: db_prop_value = node_properties[prop] except KeyError: log.debug('%s not found in form for node %s', prop_name, node_id) continue if schema_prop['type'] == 'datetime': db_prop_value = datetime.strptime( db_prop_value, current_app.config['RFC1123_DATE_FORMAT']) if isinstance(form[prop_name], SelectMultipleField): # If we are dealing with a multiselect field, check if # it's empty (usually because we can't query the whole # database to pick all the choices). If it's empty we # populate the choices with the actual data. if not form[prop_name].choices: form[prop_name].choices = [(d, d) for d in db_prop_value] # Choices should be a tuple with value and name if not set_data: continue # Assign data to the field if prop_name == 'attachments': # If attachments is an empty list, do not append data if not db_prop_value: continue attachments.attachment_form_group_set_data( db_prop_value, schema_prop, form[prop_name]) elif prop_name == 'files': subschema = schema_prop['schema']['schema'] # Extra entries are caused by min_entries=1 in the form # creation. field_list = form[prop_name] if len(db_prop_value): while len(field_list): field_list.pop_entry() for file_data in db_prop_value: file_form_class = build_file_select_form(subschema) subform = file_form_class() for key, value in file_data.items(): setattr(subform, key, value) field_list.append_entry(subform) # elif prop_name == 'tags': # form[prop_name].data = ', '.join(data) else: form[prop_name].data = db_prop_value api = system_util.pillar_api() node = Node.find(node_id, api=api) # We do not want to display the page to users who can't PUT if 'PUT' not in node.allowed_methods: raise wz_exceptions.Forbidden() project = Project.find(node.project, api=api) node_type = project.get_node_type(node.node_type) form = get_node_form(node_type) user_id = current_user.objectid dyn_schema = node_type['dyn_schema'].to_dict() form_schema = node_type['form_schema'].to_dict() error = "" node_properties = node.properties.to_dict() ensure_lists_exist_as_empty(node.to_dict(), node_type) set_properties(dyn_schema, form_schema, node_properties, form, set_data=False) if form.validate_on_submit(): if process_node_form(form, node_id=node_id, node_type=node_type, user=user_id): # Handle the specific case of a blog post if node_type.name == 'post': project_update_nodes_list(node, project_id=project._id, list_name='blog') else: try: project_update_nodes_list(node, project_id=project._id) except ForbiddenAccess: # TODO (fsiddi): Implement this as a blender-cloud-only hook log.debug( 'User %s not allowed to update latest_nodes in %s' % (user_id, project._id)) return redirect( url_for('nodes.view', node_id=node_id, embed=1, _external=True, _scheme=current_app.config['SCHEME'])) else: log.debug('Error sending data to Pillar, see Pillar logs.') error = 'Server error' else: if form.errors: log.debug('Form errors: %s', form.errors) # Populate Form form.name.data = node.name form.description.data = node.description if 'picture' in form: form.picture.data = node.picture if node.parent: form.parent.data = node.parent set_properties(dyn_schema, form_schema, node_properties, form, set_data=True) # Get previews node.picture = get_file(node.picture, api=api) if node.picture else None # Get Parent try: parent = Node.find(node['parent'], api=api) except KeyError: parent = None except ResourceNotFound: parent = None embed_string = '' # Check if we want to embed the content via an AJAX call if request.args.get('embed') == '1': # Define the prefix for the embedded template embed_string = '_embed' else: attach_project_pictures(project, api) template = 'nodes/custom/{0}/edit{1}.html'.format(node_type['name'], embed_string) # We should more simply check if the template file actually exists on the filesystem try: return render_template( template, node=node, parent=parent, form=form, errors=form.errors, error=error, api=api, project=project, ) except TemplateNotFound: template = 'nodes/edit{1}.html'.format(node_type['name'], embed_string) is_embedded_edit = True if embed_string else False return render_template( template, node=node, parent=parent, form=form, errors=form.errors, error=error, api=api, project=project, is_embedded_edit=is_embedded_edit, )
def edit(node_id): """Generic node editing form """ def set_properties(dyn_schema, form_schema, node_properties, form, prefix="", set_data=True): """Initialize custom properties for the form. We run this function once before validating the function with set_data=False, so that we can set any multiselect field that was originally specified empty and fill it with the current choices. """ for prop in dyn_schema: schema_prop = dyn_schema[prop] form_prop = form_schema[prop] prop_name = "{0}{1}".format(prefix, prop) if schema_prop['type'] == 'dict': set_properties(schema_prop['schema'], form_prop['schema'], node_properties[prop_name], form, "{0}__".format(prop_name)) continue if prop_name not in form: continue try: db_prop_value = node_properties[prop] except KeyError: log.debug('%s not found in form for node %s', prop_name, node_id) continue if schema_prop['type'] == 'datetime': db_prop_value = datetime.strptime( db_prop_value, app.config['RFC1123_DATE_FORMAT']) if isinstance(form[prop_name], SelectMultipleField): # If we are dealing with a multiselect field, check if # it's empty (usually because we can't query the whole # database to pick all the choices). If it's empty we # populate the choices with the actual data. if not form[prop_name].choices: form[prop_name].choices = [(d, d) for d in db_prop_value] # Choices should be a tuple with value and name # Assign data to the field if set_data: if prop_name == 'attachments': for attachment_collection in db_prop_value: for a in attachment_collection['files']: attachment_form = ProceduralFileSelectForm() attachment_form.file = a['file'] attachment_form.slug = a['slug'] attachment_form.size = 'm' form[prop_name].append_entry(attachment_form) elif prop_name == 'files': schema = schema_prop['schema']['schema'] # Extra entries are caused by min_entries=1 in the form # creation. field_list = form[prop_name] if len(db_prop_value) > 0: while len(field_list): field_list.pop_entry() for file_data in db_prop_value: file_form_class = build_file_select_form(schema) subform = file_form_class() for key, value in file_data.iteritems(): setattr(subform, key, value) field_list.append_entry(subform) # elif prop_name == 'tags': # form[prop_name].data = ', '.join(data) else: form[prop_name].data = db_prop_value else: # Default population of multiple file form list (only if # we are getting the form) if request.method == 'POST': continue if prop_name == 'attachments': if not db_prop_value: attachment_form = ProceduralFileSelectForm() attachment_form.file = 'file' attachment_form.slug = '' attachment_form.size = '' form[prop_name].append_entry(attachment_form) api = system_util.pillar_api() node = Node.find(node_id, api=api) project = Project.find(node.project, api=api) node_type = project.get_node_type(node.node_type) form = get_node_form(node_type) user_id = current_user.objectid dyn_schema = node_type['dyn_schema'].to_dict() form_schema = node_type['form_schema'].to_dict() error = "" node_properties = node.properties.to_dict() ensure_lists_exist_as_empty(node.to_dict(), node_type) set_properties(dyn_schema, form_schema, node_properties, form, set_data=False) if form.validate_on_submit(): if process_node_form(form, node_id=node_id, node_type=node_type, user=user_id): # Handle the specific case of a blog post if node_type.name == 'post': project_update_nodes_list(node, list_name='blog') else: project_update_nodes_list(node) # Emergency hardcore cache flush # cache.clear() return redirect( url_for('nodes.view', node_id=node_id, embed=1, _external=True, _scheme=app.config['SCHEME'])) else: log.debug('Error sending data to Pillar, see Pillar logs.') error = 'Server error' else: if form.errors: log.debug('Form errors: %s', form.errors) # Populate Form form.name.data = node.name form.description.data = node.description if 'picture' in form: form.picture.data = node.picture if node.parent: form.parent.data = node.parent set_properties(dyn_schema, form_schema, node_properties, form) # Get previews node.picture = get_file(node.picture, api=api) if node.picture else None # Get Parent try: parent = Node.find(node['parent'], api=api) except KeyError: parent = None except ResourceNotFound: parent = None embed_string = '' # Check if we want to embed the content via an AJAX call if request.args.get('embed'): if request.args.get('embed') == '1': # Define the prefix for the embedded template embed_string = '_embed' template = '{0}/edit{1}.html'.format(node_type['name'], embed_string) # We should more simply check if the template file actually exsists on # the filesystem level try: return render_template(template, node=node, parent=parent, form=form, errors=form.errors, error=error, api=api) except TemplateNotFound: template = 'nodes/edit{1}.html'.format(node_type['name'], embed_string) return render_template(template, node=node, parent=parent, form=form, errors=form.errors, error=error, api=api)
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))})
def url_for_node(node_id=None, node=None): assert isinstance(node_id, (basestring, type(None))) # assert isinstance(node, (Node, type(None))), 'wrong type for node: %r' % type(node) api = system_util.pillar_api() # Find node by its ID, or the ID by the node, depending on what was passed as parameters. if node is None: try: node = Node.find(node_id, api=api) except ResourceNotFound: log.warning('url_for_node(node_id=%r, node=None): Unable to find node.', node_id) raise ValueError('Unable to find node %r' % node_id) elif node_id is None: node_id = node['_id'] else: raise ValueError('Either node or node_id must be given') # Find the node's project, or its ID, depending on whether a project was embedded. # This is needed in two of the three finder functions. project_id = node.project if isinstance(project_id, pillarsdk.Resource): # Embedded project project = project_id project_id = project['_id'] else: project = None def project_or_error(): """Returns the project, raising a ValueError if it can't be found.""" if project is not None: return project try: return Project.find(project_id, {'projection': {'url': 1}}, api=api) except ResourceNotFound: log.warning('url_for_node(node_id=%r): Unable to find project %r', node_id, project_id) raise ValueError('Unable to find node project %r' % project_id) def find_for_comment(): """Returns the URL for a comment.""" parent = node while parent.node_type == 'comment': if isinstance(parent.parent, pillarsdk.Resource): parent = parent.parent continue try: parent = Node.find(parent.parent, 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 = url_for_node(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) def find_for_post(): """Returns the URL for a blog post.""" if str(project_id) == app.config['MAIN_PROJECT_ID']: return url_for('main_blog', url=node.properties.url) return url_for('project_blog', project_url=project_or_error().url, url=node.properties.url) # Fallback: Assets, textures, and other node types. def find_for_other(): return url_for('projects.view', project_url=project_or_error().url) + '#{}'.format(node_id) # Determine which function to use to find the correct URL. url_finders = { 'comment': find_for_comment, 'post': find_for_post, } finder = url_finders.get(node.node_type, find_for_other) return finder()
def view(project_url): """Entry point to view a project""" api = system_util.pillar_api() # Fetch the Node or 404 try: project = Project.find_one({'where': {"url": project_url}}, api=api) except ResourceNotFound: abort(404) # Set up variables for processing user_id = 'ANONYMOUS' if current_user.is_anonymous() else str(current_user.objectid) rewrite_url = None embedded_node_id = None if request.args.get('redir') and request.args.get('redir') == '1': # Handle special cases (will be mainly used for items that are part # of the blog, or attract) if g.get('embedded_node')['node_type'] == 'post': # Very special case of the post belonging to the main project, # which is read from the configuration. if project._id == app.config['MAIN_PROJECT_ID']: return redirect(url_for('main_blog', url=g.get('embedded_node')['properties']['url'])) else: return redirect(url_for('project_blog', project_url=project.url, url=g.get('embedded_node')['properties']['url'])) rewrite_url = "/p/{0}/#{1}".format(project.url, g.get('embedded_node')['_id']) embedded_node_id = g.get('embedded_node')['_id'] if request.args.get('format') == 'jstree': return jsonify(items=jstree_get_children(None, project._id)) project.picture_square = project.picture_square and get_file(project.picture_square, api=api) project.picture_header = project.picture_header and get_file(project.picture_header, api=api) embed_string = '' if request.args.get('embed'): embed_string = '_embed' list_latest = [] if project.nodes_latest: for node_id in project.nodes_latest: try: node_item = Node.find(node_id, { 'projection': '{"name":1, "user":1, "node_type":1, \ "project": 1}', 'embedded': '{"user":1}', }, api=api) list_latest.append(node_item) except ForbiddenAccess: pass project.nodes_latest = list(reversed(list_latest)) list_featured = [] if project.nodes_featured: for node_id in project.nodes_featured: try: node_item = Node.find(node_id, { 'projection': '{"name":1, "user":1, "picture":1, \ "node_type":1, "project": 1}', 'embedded': '{"user":1}', }, api=api) if node_item.picture: picture = get_file(node_item.picture, api=api) # picture = File.find(node_item.picture, api=api) node_item.picture = picture list_featured.append(node_item) except ForbiddenAccess: pass project.nodes_featured = list(reversed(list_featured)) list_blog = [] if project.nodes_blog: for node_id in project.nodes_blog: try: node_item = Node.find(node_id, { # 'projection': '{"name":1, "user":1, "node_type":1}', 'embedded': '{"user":1}', }, api=api) list_blog.append(node_item) except ForbiddenAccess: pass project.nodes_blog = list(reversed(list_blog)) return render_template("projects/view{0}.html".format(embed_string), embedded_node_id=embedded_node_id, rewrite_url=rewrite_url, user_string_id=user_id, project=project, api=api)
def jstree_build_from_node(node): api = SystemUtility.attract_api() open_nodes = [jstree_parse_node(node)] # Get the current node again (with parent data) try: parent = Node.find(node.parent, { 'projection': '{"name": 1, "parent": 1, "node_type": 1}', 'embedded': '{"node_type":1}', }, api=api) except ResourceNotFound: parent = None except ForbiddenAccess: parent = None while (parent): open_nodes.append(jstree_parse_node(parent)) # If we have a parent if parent.parent: try: parent = Node.find(parent.parent, { 'projection': '{"name":1, "parent":1, "node_type": 1}', 'embedded': '{"node_type":1}', }, api=api) except ResourceNotFound: parent = None else: parent = None open_nodes.reverse() #open_nodes.pop(0) nodes_list = [] for node in jstree_get_children(open_nodes[0]['id']): # Nodes at the root of the project node_dict = { 'id': node['id'], 'text': node['text'], 'type': node['type'], 'children': True } if len(open_nodes) > 1: # Opening and selecting the tree nodes according to the landing place if node['id'] == open_nodes[1]['id']: current_dict = node_dict current_dict['state'] = {'opened': True} current_dict['children'] = jstree_get_children(node['id']) # Iterate on open_nodes until the end for n in open_nodes[2:]: for c in current_dict['children']: if n['id'] == c['id']: current_dict = c break current_dict['state'] = {'opened': True} current_dict['children'] = jstree_get_children(n['id']) # if landing_asset_id: # current_dict['children'] = aux_product_tree_node(open_nodes[-1]) # for asset in current_dict['children']: # if int(asset['id'][1:])==landing_asset_id: # asset.update(state=dict(selected=True)) nodes_list.append(node_dict) return nodes_list
def view(node_id): #import time #start = time.time() api = SystemUtility.attract_api() # Get node with embedded picture data try: node = Node.find(node_id + '/?embedded={"picture":1, "node_type":1}', api=api) except ResourceNotFound: return abort(404) except ForbiddenAccess: return abort(403) node_type_name = node.node_type.name # JsTree functionality. # This return a lightweight version of the node, to be used by JsTree in the # frontend. We have two possible cases: # - https://pillar/<node_id>/view?format=jstree (construct the whole expanded # tree starting from the node_id. Use only once) # - https://pillar/<node_id>/view?format=jstree&children=1 (deliver the # children of a node - use in the navigation of the tree) if request.args.get('format') and request.args.get('format') == 'jstree': if request.args.get('children') == '1': if node_type_name == 'storage': storage = StorageNode(node) # Check if we specify a path within the storage path = request.args.get('path') # Generate the storage listing listing = storage.browse(path) # Inject the current node id in the response, so that JsTree can # expose the storage_node property and use it for further queries listing['storage_node'] = node._id if 'children' in listing: for child in listing['children']: child['storage_node'] = node._id return jsonify(listing) else: return jsonify(jstree_build_children(node)) else: return jsonify(items=jstree_build_from_node(node)) # Continue to process the node (for HTML, HTML embeded and JSON responses) # Set the default name of the template path based on the node name template_path = os.path.join('nodes', 'custom', node_type_name) # Set the default action for a template. By default is view and we override # it only if we are working storage nodes, where an 'index' is also possible template_action = 'view' # XXX Code to detect a node of type asset, and aggregate file data if node_type_name == 'asset': node_file = File.find(node.properties.file, api=api) node_file_children = node_file.children(api=api) # Attach the file node to the asset node setattr(node, 'file', node_file) try: asset_type = node_file.content_type.split('/')[0] except AttributeError: asset_type = None if asset_type == 'video': # Process video type and select video template sources = [] if node_file_children: for f in node_file_children._items: sources.append(dict( type=f.content_type, src=f.link)) setattr(node, 'video_sources', json.dumps(sources)) setattr(node, 'file_children', node_file_children) template_path = os.path.join(template_path, asset_type) elif asset_type == 'image': # Process image type and select image template #setattr(node, 'file_children', node_file_children) template_path = os.path.join(template_path, asset_type) else: # Treat it as normal file (zip, blend, application, etc) template_path = os.path.join(template_path, 'file') # XXX The node is of type project elif node_type_name == 'project': if node.properties.picture_square: picture_square = File.find(node.properties.picture_square, api=api) node.properties.picture_square = picture_square if node.properties.picture_header: picture_header = File.find(node.properties.picture_header, api=api) node.properties.picture_header = picture_header if node.properties.nodes_latest: list_latest = [] for node_id in node.properties.nodes_latest: try: node_item = Node.find(node_id, { 'projection': '{"name":1, "user":1, "node_type":1}', 'embedded': '{"user":1}', }, api=api) list_latest.append(node_item) except ForbiddenAccess: list_latest.append(FakeNodeAsset()) node.properties.nodes_latest = list(reversed(list_latest)) if node.properties.nodes_featured: list_featured = [] for node_id in node.properties.nodes_featured: try: node_item = Node.find(node_id, { 'projection': '{"name":1, "user":1, "picture":1, "node_type":1}', 'embedded': '{"user":1}', }, api=api) picture = File.find(node_item.picture, api=api) node_item.picture = picture list_featured.append(node_item) except ForbiddenAccess: list_featured.append(FakeNodeAsset()) node.properties.nodes_featured = list(reversed(list_featured)) elif node_type_name == 'storage': storage = StorageNode(node) path = request.args.get('path') listing = storage.browse(path) node.name = listing['name'] listing['storage_node'] = node._id # If the item has children we are working with a group if 'children' in listing: for child in listing['children']: child['storage_node'] = node._id child['name'] = child['text'] child['content_type'] = os.path.dirname(child['type']) node.children = listing['children'] template_action = 'index' else: node.status = 'published' node.length = listing['size'] node.download_link = listing['signed_url'] # Get previews if node.picture: node.picture = File.find(node.picture._id, api=api) # Get Parent try: parent = Node.find(node['parent'], api=api) except KeyError: parent = None except ResourceNotFound: parent = None # Get children try: children = Node.all({ 'where': '{"parent": "%s"}' % node._id, 'embedded': '{"picture": 1, "node_type": 1}'}, api=api) children = children._items except ForbiddenAccess: return abort(403) for child in children: if child.picture: child.picture = File.find(child.picture._id, api=api) if request.args.get('format'): if request.args.get('format') == 'json': node = node.to_dict() node['url_edit'] = url_for('nodes.edit', node_id=node['_id']), if parent: parent = parent.to_dict() return_content = jsonify({ 'node': node, 'children': children.to_dict(), 'parent': parent }) else: embed_string = '' # Check if we want to embed the content via an AJAX call if request.args.get('embed'): if request.args.get('embed') == '1': # Define the prefix for the embedded template embed_string = '_embed' # Check if template exists on the filesystem template_path = '{0}/{1}{2}.html'.format(template_path, template_action, embed_string) template_path_full = os.path.join(app.config['TEMPLATES_PATH'], template_path) if not os.path.exists(template_path_full): return "Missing template" return_content = render_template(template_path, node=node, type_names=type_names(), parent=parent, children=children, config=app.config) #print(time.time() - start) return return_content
def edit(node_id): """Generic node editing form """ def set_properties( dyn_schema, form_schema, node_properties, form, prefix="", set_data=True): """Initialize custom properties for the form. We run this function once before validating the function with set_data=False, so that we can set any multiselect field that was originally specified empty and fill it with the current choices. """ for prop in dyn_schema: if not prop in node_properties: continue schema_prop = dyn_schema[prop] form_prop = form_schema[prop] prop_name = "{0}{1}".format(prefix, prop) if schema_prop['type'] == 'dict': set_properties( schema_prop['schema'], form_prop['schema'], node_properties[prop_name], form, "{0}__".format(prop_name)) else: try: data = node_properties[prop] except KeyError: print ("{0} not found in form".format(prop_name)) if schema_prop['type'] == 'datetime': data = datetime.strptime(data, RFC1123_DATE_FORMAT) if prop_name in form: # Other field types if isinstance(form[prop_name], SelectMultipleField): # If we are dealing with a multiselect field, check if # it's empty (usually because we can't query the whole # database to pick all the choices). If it's empty we # populate the choices with the actual data. if not form[prop_name].choices: form[prop_name].choices = [(d,d) for d in data] # Choices should be a tuple with value and name # Assign data to the field if set_data: form[prop_name].data = data api = SystemUtility.attract_api() node = Node.find(node_id, api=api) # TODO: simply embed node_type node_type = NodeType.find(node.node_type, api=api) form = get_node_form(node_type) user_id = current_user.objectid dyn_schema = node_type['dyn_schema'].to_dict() form_schema = node_type['form_schema'].to_dict() error = "" node_type_name = node_type.name node_properties = node.properties.to_dict() set_properties(dyn_schema, form_schema, node_properties, form, set_data=False) if form.validate_on_submit(): if process_node_form( form, node_id=node_id, node_type=node_type, user=user_id): project_update_nodes_list(node_id) return redirect(url_for('nodes.view', node_id=node_id, embed=1)) else: error = "Server error" print ("Error sending data") else: print form.errors # Populate Form form.name.data = node.name form.description.data = node.description if 'picture' in form: form.picture.data = node.picture if node.parent: form.parent.data = node.parent set_properties(dyn_schema, form_schema, node_properties, form) # Get Parent try: parent = Node.find(node['parent'], api=api) except KeyError: parent = None except ResourceNotFound: parent = None embed_string = '' # Check if we want to embed the content via an AJAX call if request.args.get('embed'): if request.args.get('embed') == '1': # Define the prefix for the embedded template embed_string = '_embed' template = '{0}/edit{1}.html'.format(node_type['name'], embed_string) # We should more simply check if the template file actually exsists on # the filesystem level try: return render_template( template, node=node, parent=parent, form=form, errors=form.errors, error=error) except TemplateNotFound: template = 'nodes/edit{1}.html'.format(node_type['name'], embed_string) return render_template( template, node=node, parent=parent, form=form, errors=form.errors, error=error)
def comments_rate(comment_id): """Comment rating function :param comment_id: the comment id :type comment_id: str :param rating: the rating (is cast from 0 to False and from 1 to True) :type rating: int """ rating_is_positive = False if request.form['is_positive'] == 'false' else True api = SystemUtility.attract_api() comment = Node.find(comment_id, api=api) # Check if comment has been rated user_comment_rating = None if comment.properties.ratings: for rating in comment.properties.ratings: if rating['user'] == current_user.objectid: user_comment_rating = rating #r = next((r for r in comment.ratings if r['user'] == current_user.objectid), None) if user_comment_rating: # Update or remove rating if user_comment_rating['is_positive'] == rating_is_positive: # If the rating matches, remove the it comment.properties.ratings.remove(user_comment_rating) # Update global rating values if rating_is_positive: comment.properties.rating_positive -= 1 else: comment.properties.rating_negative -= 1 comment.update(api=api) return_data = dict(is_rated=False, rating_up=comment.properties.rating_positive) else: # If the rating differs from the current, update its value. In this # case we make sure we update the existing global rating values as well user_comment_rating['is_positive'] = rating_is_positive if rating_is_positive: comment.properties.rating_positive += 1 comment.properties.rating_negative -= 1 else: comment.properties.rating_negative += 1 comment.properties.rating_positive -= 1 comment.update(api=api) return_data = dict(is_positive=rating_is_positive, is_rated=True, rating_up=comment.properties.rating_positive) else: # Create rating for current user user_comment_rating = dict(user=current_user.objectid, is_positive=rating_is_positive, # Hardcoded to default (auto valid) weight=3) if not comment.properties.ratings: comment.properties.ratings = [] comment.properties.ratings.append(user_comment_rating) if rating_is_positive: comment.properties.rating_positive += 1 else: comment.properties.rating_negative += 1 comment.update(api=api) return_data = dict(is_positive=rating_is_positive, is_rated=True, rating_up=comment.properties.rating_positive) return jsonify(status='success', data=return_data)
def task_edit(): """We want to be able to edit the following properties: - status - owners - description - picture (optional) """ api = system_util.pillar_api() task_id = request.form['task_id'] task = Node.find(task_id, api=api) task.description = request.form['task_description'] if request.form['task_revision']: task.properties.revision = int(request.form['task_revision']) task.properties.status = request.form['task_status'] task.properties.filepath = request.form['task_filepath'] task.properties.owners.users = request.form.getlist('task_owners_users[]') siblings = Node.all({ 'where': 'parent==ObjectId("%s")' % task.parent, 'embedded': '{"picture":1, "user":1}'}, api=api) def check_conflict(task_current, task_sibling): return revsion_conflict[task_current.name](task_current, task_sibling) def task_animation(task_current, task_sibling): if task_sibling.name in ['fx_hair', 'fx_smoke', 'fx_grass', 'lighting']: if task_current.properties.revision > task_sibling.properties.revision: return True return False def task_lighting(task_current, task_sibling): if task_sibling.name in ['fx_hair', 'fx_smoke', 'fx_grass', 'animation']: if task_current.properties.revision < task_sibling.properties.revision: return True return False def task_fx_hair(task_current, task_sibling): if task_sibling.name in ['animation']: if task_current.properties.revision < task_sibling.properties.revision: return True if task_sibling.name in ['lighting']: if task_current.properties.revision > task_sibling.properties.revision: return True return False def task_fx_grass(task_current, task_sibling): pass def task_fx_smoke(task_current, task_sibling): pass revsion_conflict = { 'animation': task_animation, 'lighting': task_lighting, 'fx_hair': task_fx_hair, 'fx_grass': task_fx_grass, 'fx_smoke': task_fx_smoke } if task.properties.revision: for sibling in siblings._items: if sibling.properties.revision and sibling._id != task_id: if check_conflict(task, sibling) == True: task.properties.is_conflicting = True break else: task.properties.is_conflicting = False task.update(api=api) return jsonify(task.to_dict())
def url_for_node(node_id=None, node=None): assert isinstance(node_id, (basestring, type(None))) # assert isinstance(node, (Node, type(None))), 'wrong type for node: %r' % type(node) api = system_util.pillar_api() # Find node by its ID, or the ID by the node, depending on what was passed as parameters. if node is None: try: node = Node.find(node_id, api=api) except ResourceNotFound: log.warning( 'url_for_node(node_id=%r, node=None): Unable to find node.', node_id) raise ValueError('Unable to find node %r' % node_id) elif node_id is None: node_id = node['_id'] else: raise ValueError('Either node or node_id must be given') # Find the node's project, or its ID, depending on whether a project was embedded. # This is needed in two of the three finder functions. project_id = node.project if isinstance(project_id, pillarsdk.Resource): # Embedded project project = project_id project_id = project['_id'] else: project = None def project_or_error(): """Returns the project, raising a ValueError if it can't be found.""" if project is not None: return project try: return Project.find(project_id, {'projection': { 'url': 1 }}, api=api) except ResourceNotFound: log.warning('url_for_node(node_id=%r): Unable to find project %r', node_id, project_id) raise ValueError('Unable to find node project %r' % project_id) def find_for_comment(): """Returns the URL for a comment.""" parent = node while parent.node_type == 'comment': if isinstance(parent.parent, pillarsdk.Resource): parent = parent.parent continue try: parent = Node.find(parent.parent, 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 = url_for_node(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) def find_for_post(): """Returns the URL for a blog post.""" if str(project_id) == app.config['MAIN_PROJECT_ID']: return url_for('main_blog', url=node.properties.url) return url_for('project_blog', project_url=project_or_error().url, url=node.properties.url) # Fallback: Assets, textures, and other node types. def find_for_other(): return url_for( 'projects.view', project_url=project_or_error().url) + '#{}'.format(node_id) # Determine which function to use to find the correct URL. url_finders = { 'comment': find_for_comment, 'post': find_for_post, } finder = url_finders.get(node.node_type, find_for_other) return finder()