def view_embed(node_id): """Extend the default view_embed Pillar function.""" 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) # Embed downloadable file, if available if 'download' in node.properties: node.properties.download = get_file(node.properties.download, 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 view(project_url): """Entry point to view a project""" if request.args.get('format') == 'jstree': log.warning('projects.view(%r) endpoint called with format=jstree, ' 'redirecting to proper endpoint. URL is %s; referrer is %s', project_url, request.url, request.referrer) return redirect(url_for('projects.jstree', project_url=project_url)) api = system_util.pillar_api() project = find_project_or_404(project_url, embedded={'header_node': 1}, api=api) # Load the header video file, if there is any. header_video_file = None header_video_node = None if project.header_node and project.header_node.node_type == 'asset' and \ project.header_node.properties.content_type == 'video': header_video_node = project.header_node header_video_file = utils.get_file(project.header_node.properties.file) header_video_node.picture = utils.get_file(header_video_node.picture) return render_project(project, api, extra_context={'header_video_file': header_video_file, 'header_video_node': header_video_node})
def search(project_url): """Search into a project""" api = system_util.pillar_api() project = find_project_or_404(project_url, api=api) project.picture_square = utils.get_file(project.picture_square, api=api) project.picture_header = utils.get_file(project.picture_header, api=api) return render_template('nodes/search.html', project=project, og_picture=project.picture_header)
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 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 render_page(): api = system_util.pillar_api() projects = Project.all( { 'where': { 'category': 'film', 'is_private': False }, 'sort': '-_created', }, api=api) for project in projects._items: # Attach poster file (ensure the extension_props.cloud.poster # attributes exists) try: # If the attribute exists, but is None, continue if not project['extension_props'][EXTENSION_NAME]['poster']: continue # Fetch the file and embed it in the document project.extension_props.cloud.poster = get_file( project.extension_props.cloud.poster, api=api) # Add convenience attribute that specifies the presence of the # poster file project.has_poster = True # If there was a key error because one of the nested attributes is # missing, except KeyError: continue return render_template('films.html', title='films', projects=projects._items, api=system_util.pillar_api())
def shared_image_nodes(home_project_id, api): """Returns a list of pillarsdk.Node objects.""" parent_group = Node.find_first({ 'where': {'project': home_project_id, 'node_type': 'group', 'parent': None, 'name': IMAGE_SHARING_GROUP_NODE_NAME}, 'projection': {'_id': 1}}, api=api) if not parent_group: log.debug('No image sharing parent node found.') return [] nodes = Node.all({ 'where': {'project': home_project_id, 'node_type': 'asset', 'properties.content_type': 'image', 'parent': parent_group['_id']}, 'sort': '-_created', 'projection': { '_created': 1, 'name': 1, 'picture': 1, 'short_code': 1, }}, api=api) nodes = nodes._items or [] for node in nodes: node.picture = utils.get_file(node.picture) return nodes
def _view_handler_texture(node, template_path, template_action, link_allowed): for f in node.properties.files: f.file = get_file(f.file) # Remove the link to the file if it's not allowed. if f.file and not link_allowed: f.file.link = None return template_path, template_action
def _view_handler_hdri(node, template_path, template_action, link_allowed): if not link_allowed: node.properties.files = None else: for f in node.properties.files: f.file = get_file(f.file) return template_path, template_action
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 download_file(post_id: str, file_id: str): """Download a file and update download counters for the Post. The the download counters are dynamically created/updated with the following names: - properties.downloads_total - properties.downloads_latest (gets reset once the file is updated) These properties must exist in POST_ADDITIONAL_PROPERTIES. """ f = get_file(file_id, api=system_util.pillar_api()) # Update download count (done via the API to reduce overhead) update_download_count(post_id) return redirect(f.link)
def project_browse_view_nodes(project: pillarsdk.Project): """Display top-level nodes for a Project. This view is always meant to be served embedded, as part of project_browse. """ api = system_util.pillar_api() # Get top level nodes projection = { 'project': 1, 'name': 1, 'picture': 1, 'node_type': 1, 'properties.order': 1, 'properties.status': 1, 'user': 1, 'properties.content_type': 1, 'permissions.world': 1 } where = { 'project': project['_id'], 'parent': { '$exists': False }, 'properties.status': 'published', '_deleted': { '$ne': True }, 'node_type': { '$in': ['group', 'asset'] }, } try: nodes = Node.all( { 'projection': projection, 'where': where, 'sort': [('properties.order', 1), ('name', 1)] }, api=api) except pillarsdk.exceptions.ForbiddenAccess: return render_template('errors/403_embed.html') nodes = nodes._items for child in nodes: child.picture = get_file(child.picture, api=api) return render_template('projects/browse_embed.html', nodes=nodes)
def view(community_url, post_shortcode, slug=None): api = system_util.pillar_api() project = Project.find_by_url(community_url, api=api) attach_project_pictures(project, api) post = Node.find_one({ 'where': { 'project': project['_id'], 'properties.shortcode': post_shortcode}, 'embedded': {'user': 1}}, api=api) if post.picture: post.picture = get_file(post.picture, api=api) return render_template( 'dillo/index.html', project=project, submit_menu=project_submit_menu(project), col_right={'post': post})
def posts_list(): """Generate an embeddable list of posts. Aggregated view of all posts for the public communities. If a user has a set of favourite communites in its settings, use those instead. """ api = system_util.pillar_api() # Are we displaying posts for a specific community? is_community_listing = 'community_id' in request.args # Build base url for API request url = '/api/posts/?' # Iterate over query strings for qs, qv in request.args.items(): # Append to API url only query string with a value if qv: url += f'{qs}={qv}&' posts_request = api.http_call(url, method='GET') posts = posts_request['data'] # Attach pictures for post in posts: if post.get('picture'): post['picture'] = get_file(post['picture'], api=api) # Check if we are at the last page is_last_page = False if len(posts) < current_app.config['PAGINATION_DEFAULT_POSTS']: is_last_page = True return render_template( 'dillo/posts_list.html', posts=posts, metadata=posts_request['metadata'], facets=posts_request['facets'], filters=posts_request['filters'], is_last_page=is_last_page, is_community_listing=is_community_listing, )
def _view_handler_asset(node, template_path, template_action, link_allowed): # Attach the file document to the asset node node_file = get_file(node.properties.file) node.file = node_file # Remove the link to the file if it's not allowed. if node_file and not link_allowed: node.file.link = None if node_file and node_file.content_type is not None: asset_type = node_file.content_type.split('/')[0] else: asset_type = None if asset_type == 'video': # Process video type and select video template if link_allowed: sources = [] if node_file and node_file.variations: for f in node_file.variations: sources.append({'type': f.content_type, 'src': f.link}) # Build a link that triggers download with proper filename # TODO: move this to Pillar if f.backend == 'cdnsun': f.link = "{0}&name={1}.{2}".format( f.link, node.name, f.format) node.video_sources = sources node.file_variations = node_file.variations else: node.video_sources = None node.file_variations = None elif asset_type != 'image': # Treat it as normal file (zip, blend, application, etc) asset_type = 'file' template_path = os.path.join(template_path, asset_type) return template_path, template_action
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, 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 get_random_featured_nodes() -> typing.List[dict]: """Returns a list of project/node combinations for featured nodes. A random subset of 3 featured nodes from all public projects is returned. Assumes that the user actually has access to the public projects' nodes. The dict is a node, with a 'project' key that contains a projected project. """ proj_coll = current_app.db('projects') featured_nodes = proj_coll.aggregate([ { '$match': { 'is_private': False } }, { '$project': { 'nodes_featured': True, 'url': True, 'name': True, 'summary': True, 'picture_square': True } }, { '$unwind': { 'path': '$nodes_featured' } }, { '$sample': { 'size': 6 } }, { '$lookup': { 'from': 'nodes', 'localField': 'nodes_featured', 'foreignField': '_id', 'as': 'node' } }, { '$unwind': { 'path': '$node' } }, { '$match': { 'node._deleted': { '$ne': True } } }, { '$project': { 'url': True, 'name': True, 'summary': True, 'picture_square': True, 'node._id': True, 'node.name': True, 'node.permissions': True, 'node.picture': True, 'node.properties.content_type': True, 'node.properties.duration_seconds': True, 'node.properties.url': True, 'node._created': True, } }, ]) featured_node_documents = [] api = system_util.pillar_api() for node_info in featured_nodes: # Turn the project-with-node doc into a node-with-project doc. node_document = node_info.pop('node') node_document['project'] = node_info node_document['_id'] = str(node_document['_id']) node = Node(node_document) node.picture = get_file(node.picture, api=api) node.project.picture_square = get_file(node.project.picture_square, api=api) featured_node_documents.append(node) return featured_node_documents
def project_landing(project_url): """Override Pillar project_view endpoint completely. The first part of the function is identical to the one in Pillar, but the second part (starting with 'Load custom project properties') extends the behaviour to support film project landing pages. """ template_name = None if request.args.get('format') == 'jstree': log.warning( 'projects.view(%r) endpoint called with format=jstree, ' 'redirecting to proper endpoint. URL is %s; referrer is %s', project_url, request.url, request.referrer) return redirect(url_for('projects.jstree', project_url=project_url)) api = system_util.pillar_api() project = find_project_or_404(project_url, embedded={'header_node': 1}, api=api) # Load the header video file, if there is any. header_video_file = None header_video_node = None if project.header_node and project.header_node.node_type == 'asset' and \ project.header_node.properties.content_type == 'video': header_video_node = project.header_node header_video_file = get_file(project.header_node.properties.file) header_video_node.picture = get_file(header_video_node.picture) extra_context = { 'header_video_file': header_video_file, 'header_video_node': header_video_node } # Load custom project properties. If the project has a 'cloud' extension prop, # render it using the projects/landing.html template and try to attach a # number of additional attributes (pages, images, etc.). if 'extension_props' in project and EXTENSION_NAME in project[ 'extension_props']: extension_props = project['extension_props'][EXTENSION_NAME] extension_props['logo'] = get_file(extension_props['logo']) pages = Node.all( { 'where': { 'project': project._id, 'node_type': 'page', '_deleted': { '$ne': True } }, 'projection': { 'name': 1 } }, api=api) extra_context.update({'pages': pages._items}) template_name = 'projects/landing.html' return render_project(project, api, extra_context=extra_context, template_name=template_name)
def users_view(username): """View public user page. Use direct db call to retrieve the user and then use the api to query the paginated list of all published dillo_posts from the user. """ users_coll = current_app.db('users') user = users_coll.find_one({'username': username}, projection={ 'username': 1, 'full_name': 1, 'email': 1, 'extension_props_public': 1, '_updated': 1, '_created': 1, }) if user is None: return abort(404) api = system_util.pillar_api() nodes_coll = current_app.db('nodes') pipeline = [{ '$match': { 'user': ObjectId(user['_id']), 'node_type': 'dillo_post', 'properties.status': 'published', '_deleted': False } }, { '$lookup': { 'from': 'projects', 'localField': 'project', 'foreignField': '_id', 'as': 'project' } }, { '$project': { 'name': 1, 'properties': 1, 'user': 1, 'picture': 1, '_created': 1, 'project': { '$arrayElemAt': ['$project', 0] } } }, { '$sort': { '_created': -1 } }] posts = list(nodes_coll.aggregate(pipeline=pipeline)) for post in posts: if post.get('picture'): post['picture'] = get_file(post['picture'], api=api) main_project_url = current_app.config['DEFAULT_COMMUNITY'] project = Project.find_by_url(main_project_url, api=api) attach_project_pictures(project, api) # Fetch all comments activity for the user activities = Activity.all( { 'where': { 'actor_user': str(user['_id']), 'node_type': 'comment' }, 'sort': [('_created', -1)], 'max_results': 15, }, api=api) # Fetch more info for each activity. for act in activities['_items']: act.actor_user = subquery.get_user_info(act.actor_user) try: act.link = url_for_node(node_id=act.object) except ValueError: # TODO: properly handle the case when the activity object has been deleted continue return render_template('dillo/user.html', col_right={'activities': activities}, user=user, posts=posts, project=project)
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=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 render_project(project, api, extra_context=None, template_name=None): project.picture_square = utils.get_file(project.picture_square, api=api) project.picture_header = utils.get_file(project.picture_header, api=api) 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 project.nodes_featured = load_latest(project.nodes_featured, node_type='asset') project.nodes_blog = load_latest(project.nodes_blog, node_type='post') # Merge featured assets and blog posts into one activity stream def sort_key(item): return item._created activities = itertools.chain(project.nodes_featured, project.nodes_blog) activity_stream = sorted(activities, key=sort_key, reverse=True) if extra_context is None: extra_context = {} if project.category == 'home' and not current_app.config['RENDER_HOME_AS_REGULAR_PROJECT']: template_name = template_name or 'projects/home_index.html' return render_template( template_name, gravatar=utils.gravatar(current_user.email, size=128), project=project, api=system_util.pillar_api(), **extra_context) if template_name is None: if request.args.get('embed'): embed_string = '_embed' else: embed_string = '' template_name = "projects/view{0}.html".format(embed_string) extension_sidebar_links = current_app.extension_sidebar_links(project) return render_template(template_name, api=api, project=project, node=None, show_node=False, show_project=True, og_picture=project.picture_header, activity_stream=activity_stream, extension_sidebar_links=extension_sidebar_links, **extra_context)