def edit(project_url): api = system_util.pillar_api() # Fetch the Node or 404 try: project = Project.find_one({'where': {'url': project_url}}, api=api) # project = Project.find(project_url, api=api) except ResourceNotFound: abort(404) utils.attach_project_pictures(project, api) form = ProjectForm( project_id=project._id, name=project.name, url=project.url, summary=project.summary, description=project.description, is_private='GET' not in project.permissions.world, category=project.category, status=project.status, ) if form.validate_on_submit(): project = Project.find(project._id, api=api) project.name = form.name.data project.url = form.url.data project.summary = form.summary.data project.description = form.description.data project.category = form.category.data project.status = form.status.data if form.picture_square.data: project.picture_square = form.picture_square.data if form.picture_header.data: project.picture_header = form.picture_header.data # Update world permissions from is_private checkbox if form.is_private.data: project.permissions.world = [] else: project.permissions.world = ['GET'] project.update(api=api) # Reattach the pictures utils.attach_project_pictures(project, api) else: if project.picture_square: form.picture_square.data = project.picture_square._id if project.picture_header: form.picture_header.data = project.picture_header._id # List of fields from the form that should be hidden to regular users if current_user.has_role('admin'): hidden_fields = [] else: hidden_fields = ['url', 'status', 'is_private', 'category'] return render_template('projects/edit.html', form=form, hidden_fields=hidden_fields, project=project, ext_pages=find_extension_pages(), api=api)
def edit(project_url): api = system_util.pillar_api() # Fetch the Node or 404 try: project = Project.find_one({'where': {'url': project_url}}, api=api) # project = Project.find(project_url, api=api) except ResourceNotFound: abort(404) attach_project_pictures(project, api) form = ProjectForm( project_id=project._id, name=project.name, url=project.url, summary=project.summary, description=project.description, is_private=u'GET' not in project.permissions.world, category=project.category, status=project.status, ) if form.validate_on_submit(): project = Project.find(project._id, api=api) project.name = form.name.data project.url = form.url.data project.summary = form.summary.data project.description = form.description.data project.category = form.category.data project.status = form.status.data if form.picture_square.data: project.picture_square = form.picture_square.data if form.picture_header.data: project.picture_header = form.picture_header.data # Update world permissions from is_private checkbox if form.is_private.data: project.permissions.world = [] else: project.permissions.world = [u'GET'] project.update(api=api) # Reattach the pictures attach_project_pictures(project, api) else: if project.picture_square: form.picture_square.data = project.picture_square._id if project.picture_header: form.picture_header.data = project.picture_header._id # List of fields from the form that should be hidden to regular users if current_user.has_role('admin'): hidden_fields = [] else: hidden_fields = ['url', 'status', 'is_private', 'category'] return render_template('projects/edit.html', form=form, hidden_fields=hidden_fields, project=project, title="edit", api=api)
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 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 delete(): """Unapologetically deletes a project""" api = system_util.pillar_api() project_id = request.form['project_id'] project = Project.find(project_id, api=api) project.delete(api=api) return jsonify(dict(staus='success', data=dict( message='Project deleted {}'.format(project['_id']))))
def get_main_project(): api = system_util.pillar_api() try: main_project = Project.find(app.config['MAIN_PROJECT_ID'], api=api) except ResourceNotFound: raise ConfigError('MAIN_PROJECT_ID was not found. Check config.py.') except KeyError: raise ConfigError('MAIN_PROJECT_ID missing from config.py') return main_project
def _homepage_context() -> dict: """Returns homepage template context variables.""" # Get latest blog posts api = system_util.pillar_api() # Get latest comments to any node latest_comments = Node.latest('comments', api=api) # Get a list of random featured assets random_featured = get_random_featured_nodes() # Parse results for replies to_remove = [] @functools.lru_cache() 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) for idx, comment in enumerate(latest_comments._items): if comment.properties.is_reply: try: comment.attached_to = _find_parent(comment.parent.parent) except ResourceNotFound: # Remove this comment to_remove.append(idx) else: comment.attached_to = comment.parent for idx in reversed(to_remove): del latest_comments._items[idx] for comment in latest_comments._items: if not comment.attached_to: continue comment.attached_to.url = url_for_node(node=comment.attached_to) comment.url = url_for_node(node=comment) main_project = Project.find(current_app.config['MAIN_PROJECT_ID'], api=api) main_project.picture_header = get_file(main_project.picture_header, api=api) return dict(main_project=main_project, latest_comments=latest_comments._items, random_featured=random_featured)
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 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 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 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 populate_feed(feed: AtomFeed, latest_posts): """Populate the feed with the provided data.""" for post in latest_posts._items: api = system_util.pillar_api() author = post.user.full_name updated = post._updated if post._updated else post._created project_projection = {'project': {'url': 1}} project = Project.find(post.project, project_projection, api=api) url = url_for('posts.view', post_shortcode=post.properties.shortcode, community_url=project.url) content = post.properties.content[:500] feed.add(post.name, str(content), content_type='html', author=author, url=url, updated=updated, published=post._created)
def posts_create(project_id): api = system_util.pillar_api() try: project = Project.find(project_id, api=api) except ResourceNotFound: return abort(404) attach_project_pictures(project, api) blog = Node.find_one( {'where': { 'node_type': 'blog', 'project': project_id }}, api=api) node_type = project.get_node_type('post') # Check if user is allowed to create a post in the blog if not project.node_type_has_method('post', 'POST', api=api): return abort(403) form = get_node_form(node_type) if form.validate_on_submit(): # Create new post object from scratch post_props = dict(node_type='post', name=form.name.data, picture=form.picture.data, user=current_user.objectid, parent=blog._id, project=project._id, properties=dict(content=form.content.data, status=form.status.data, url=form.url.data)) if form.picture.data == '': post_props['picture'] = None post = Node(post_props) post.create(api=api) # Only if the node is set as published, push it to the list if post.properties.status == 'published': project_update_nodes_list(post, project_id=project._id, list_name='blog') return redirect(url_for_node(node=post)) form.parent.data = blog._id return render_template('nodes/custom/post/create.html', node_type=node_type, form=form, project=project, api=api)
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 create(): """Create a node. Requires a number of params: - project id - node_type - parent node (optional) """ if request.method != 'POST': return abort(403) project_id = request.form['project_id'] parent_id = request.form.get('parent_id') node_type_name = request.form['node_type_name'] api = system_util.pillar_api() # Fetch the Project or 404 try: project = Project.find(project_id, api=api) except ResourceNotFound: return abort(404) node_type = project.get_node_type(node_type_name) node_type_name = 'folder' if node_type['name'] == 'group' else \ node_type['name'] node_props = dict( name='New {}'.format(node_type_name), project=project['_id'], user=current_user.objectid, node_type=node_type['name'], properties={} ) if parent_id: node_props['parent'] = parent_id ensure_lists_exist_as_empty(node_props, node_type) node = Node(node_props) node.create(api=api) return jsonify(status='success', data=dict(asset_id=node['_id']))
def redir_job_id(job_id): """Redirects to the job view. This saves the client from performing another request to find the project URL; we do it for them. """ from flask import redirect, url_for from .sdk import Job from pillarsdk import Project # FIXME Sybren: add permission check. api = pillar_api() j = Job.find(job_id, {'projection': {'project': 1, 'status': 1}}, api=api) p = Project.find(j.project, {'projection': {'url': 1}}, api=api) target_blueprint = blueprint_for_archived[j.status == 'archived'] return redirect(url_for(f'{target_blueprint.name}.view_job', project_url=p.url, job_id=j._id))
def redirect_to_task(task_id): """Allows creation of task links without knowing the job or project ID.""" from flamenco.tasks.sdk import Task from pillarsdk import Project from flask import url_for, redirect api = pillar_api() task = Task.find(task_id, {'projection': {'project': 1}}, api=api) project = Project.find(task['project'], {'projection': { 'url': 1 }}, api=api) # FIXME Sybren: add permission check. url = url_for('flamenco.jobs.perproject.for_project_with_task', project_url=project['url'], task_id=task_id) return redirect(url, code=301)
def create(): """Create a node. Requires a number of params: - project id - node_type - parent node (optional) """ if request.method != 'POST': return abort(403) project_id = request.form['project_id'] parent_id = request.form.get('parent_id') node_type_name = request.form['node_type_name'] api = system_util.pillar_api() # Fetch the Project or 404 try: project = Project.find(project_id, api=api) except ResourceNotFound: return abort(404) node_type = project.get_node_type(node_type_name) node_type_name = 'folder' if node_type['name'] == 'group' else \ node_type['name'] node_props = dict(name='New {}'.format(node_type_name), project=project['_id'], user=current_user.objectid, node_type=node_type['name'], properties={}) if parent_id: node_props['parent'] = parent_id ensure_lists_exist_as_empty(node_props, node_type) node = Node(node_props) node.create(api=api) return jsonify(status='success', data=dict(asset_id=node['_id']))
def project_update_nodes_list(node, 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 node.properties.status and node.properties.status == 'published': if not project_id and 'current_project_id' in session: project_id = session['current_project_id'] elif not project_id: return None project_id = node.project if type(project_id) is not unicode: project_id = node.project._id api = system_util.pillar_api() project = Project.find(project_id, api=api) if list_name == 'latest': nodes_list = project.nodes_latest elif list_name == 'blog': nodes_list = project.nodes_blog else: nodes_list = project.nodes_featured if not nodes_list: node_list_name = 'nodes_' + list_name project[node_list_name] = [] nodes_list = project[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 project_update_nodes_list(node, 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 node.properties.status and node.properties.status == 'published': if not project_id and 'current_project_id' in session: project_id = session['current_project_id'] elif not project_id: return None project_id = node.project if type(project_id) is not str: project_id = node.project._id api = system_util.pillar_api() project = Project.find(project_id, api=api) if list_name == 'latest': nodes_list = project.nodes_latest elif list_name == 'blog': nodes_list = project.nodes_blog else: nodes_list = project.nodes_featured if not nodes_list: node_list_name = 'nodes_' + list_name project[node_list_name] = [] nodes_list = project[node_list_name] elif len(nodes_list) > 15: 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 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 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 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 posts_view(project_id=None, project_url=None, url=None, *, archive=False, page=1): """View individual blogpost""" if bool(project_id) == bool(project_url): raise ValueError('posts_view(): pass either project_id or project_url') if url and archive: raise ValueError('posts_view(): cannot pass both url and archive') api = system_util.pillar_api() # Fetch project (for background images and links generation) if project_id: project = Project.find(project_id, api=api) else: project = Project.find_one({'where': {'url': project_url}}, api=api) project_id = project['_id'] attach_project_pictures(project, api) blog = Node.find_one( { 'where': { 'node_type': 'blog', 'project': project_id }, }, api=api) status_query = {} if blog.has_method('PUT') else { 'properties.status': 'published' } posts = Node.all( { 'where': { 'parent': blog._id, **status_query }, 'embedded': { 'user': 1 }, 'sort': '-_created', 'max_results': 20 if archive else 5, 'page': page, }, api=api) for post in posts._items: post.picture = get_file(post.picture, api=api) post.url = url_for_node(node=post) # Use the *_main_project.html template for the main blog is_main_project = project_id == current_app.config['MAIN_PROJECT_ID'] main_project_template = '_main_project' if is_main_project else '' main_project_template = '_main_project' index_arch = 'archive' if archive else 'index' template_path = f'nodes/custom/blog/{index_arch}{main_project_template}.html', if url: template_path = f'nodes/custom/post/view{main_project_template}.html', post = Node.find_one( { 'where': { 'parent': blog._id, 'properties.url': url }, 'embedded': { 'node_type': 1, 'user': 1 }, }, api=api) if post.picture: post.picture = get_file(post.picture, api=api) post.url = url_for_node(node=post) elif posts._items: post = posts._items[0] else: post = None if post is not None: # If post is not published, check that the user is also the author of # the post. If not, return an error. if post.properties.status != "published": if not (current_user.is_authenticated and post.has_method('PUT')): abort(403) can_create_blog_posts = project.node_type_has_method('post', 'POST', api=api) # Use functools.partial so we can later pass page=X. if is_main_project: url_func = functools.partial(url_for, 'main.main_blog_archive') else: url_func = functools.partial(url_for, 'main.project_blog_archive', project_url=project.url) project.blog_archive_url = url_func() pmeta = posts._meta seen_now = pmeta['max_results'] * pmeta['page'] if pmeta['total'] > seen_now: project.blog_archive_next = url_func(page=pmeta['page'] + 1) else: project.blog_archive_next = None if pmeta['page'] > 1: project.blog_archive_prev = url_func(page=pmeta['page'] - 1) else: project.blog_archive_prev = None title = 'blog_main' if is_main_project else 'blog' pages = Node.all( { 'where': { 'project': project._id, 'node_type': 'page' }, 'projection': { 'name': 1 } }, api=api) return render_template( template_path, blog=blog, node=post, posts=posts._items, posts_meta=pmeta, more_posts_available=pmeta['total'] > pmeta['max_results'], project=project, title=title, node_type_post=project.get_node_type('post'), can_create_blog_posts=can_create_blog_posts, pages=pages._items, api=api)
def posts_view(project_id, url=None): """View individual blogpost""" api = system_util.pillar_api() # Fetch project (for backgroud images and links generation) project = Project.find(project_id, api=api) attach_project_pictures(project, api) try: blog = Node.find_one( { 'where': { 'node_type': 'blog', 'project': project_id }, }, api=api) except ResourceNotFound: abort(404) if url: try: post = Node.find_one( { 'where': '{"parent": "%s", "properties.url": "%s"}' % (blog._id, url), 'embedded': '{"node_type": 1, "user": 1}', }, api=api) if post.picture: post.picture = get_file(post.picture, api=api) except ResourceNotFound: return abort(404) # If post is not published, check that the user is also the author of # the post. If not, return 404. if post.properties.status != "published": if current_user.is_authenticated(): if not post.has_method('PUT'): abort(403) else: abort(403) return render_template('nodes/custom/post/view.html', blog=blog, node=post, project=project, title='blog', api=api) else: node_type_post = project.get_node_type('post') status_query = "" if blog.has_method( 'PUT') else ', "properties.status": "published"' posts = Node.all( { 'where': '{"parent": "%s" %s}' % (blog._id, status_query), 'embedded': '{"user": 1}', 'sort': '-_created' }, api=api) for post in posts._items: post.picture = get_file(post.picture, api=api) return render_template('nodes/custom/blog/index.html', node_type_post=node_type_post, posts=posts._items, project=project, title='blog', api=api)
def posts_view(project_id=None, project_url=None, url=None): """View individual blogpost""" if bool(project_id) == bool(project_url): raise ValueError('posts_view(): pass either project_id or project_url') api = system_util.pillar_api() # Fetch project (for backgroud images and links generation) if project_id: project = Project.find(project_id, api=api) else: project = Project.find_one({'where': {'url': project_url}}, api=api) project_id = project['_id'] attach_project_pictures(project, api) blog = Node.find_one( { 'where': { 'node_type': 'blog', 'project': project_id }, }, api=api) status_query = "" if blog.has_method( 'PUT') else ', "properties.status": "published"' posts = Node.all( { 'where': '{"parent": "%s" %s}' % (blog._id, status_query), 'embedded': '{"user": 1}', 'sort': '-_created' }, api=api) for post in posts._items: post.picture = get_file(post.picture, api=api) post['properties'][ 'content'] = pillar.web.nodes.attachments.render_attachments( post, post['properties']['content']) # Use the *_main_project.html template for the main blog main_project_template = '_main_project' if project_id == current_app.config[ 'MAIN_PROJECT_ID'] else '' if url: post = Node.find_one( { 'where': { 'parent': blog._id, 'properties.url': url }, 'embedded': { 'node_type': 1, 'user': 1 }, }, api=api) if post.picture: post.picture = get_file(post.picture, api=api) # If post is not published, check that the user is also the author of # the post. If not, return 404. if post.properties.status != "published": if not (current_user.is_authenticated and post.has_method('PUT')): abort(403) post['properties'][ 'content'] = pillar.web.nodes.attachments.render_attachments( post, post['properties']['content']) return render_template( 'nodes/custom/post/view{0}.html'.format(main_project_template), blog=blog, node=post, posts=posts._items, project=project, title='blog', api=api) else: node_type_post = project.get_node_type('post') template_path = 'nodes/custom/blog/index.html' return render_template( 'nodes/custom/blog/index{0}.html'.format(main_project_template), node_type_post=node_type_post, posts=posts._items, project=project, title='blog', 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)