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 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 save_film_settings(project: pillarsdk.Project): # Ensure that the project is setup for Cloud (see @attract_project_view for example) form = FilmProjectForm() if not form.validate_on_submit(): log.debug('Form submission failed') # Return list of validation errors updated_extension_props = {} for field_name in form.data: # Skip csrf_token field if field_name == 'csrf_token': continue form_field = getattr(form, field_name) # TODO(fsiddi) if form_field type is FileSelectField, convert it to ObjectId # Currently this raises TypeError: Object of type 'ObjectId' is not JSON serializable if form_field.data == '': form_field.data = None updated_extension_props[field_name] = form_field.data # Update extension props and save project extension_props = project['extension_props'][EXTENSION_NAME] # Project is a Resource, so we update properties iteratively for k, v in updated_extension_props.items(): extension_props[k] = v project.update(api=system_util.pillar_api()) return '', 204
def emails(): """Main email settings. """ if current_user.has_role('protected'): return abort(404) # TODO: make this 403, handle template properly api = system_util.pillar_api() user = User.find(current_user.objectid, api=api) # Force creation of settings for the user (safely remove this code once # implemented on account creation level, and after adding settings to all # existing users) if not user.settings: user.settings = dict(email_communications=1) user.update(api=api) if user.settings.email_communications is None: user.settings.email_communications = 1 user.update(api=api) # Generate form form = forms.UserSettingsEmailsForm( email_communications=user.settings.email_communications) if form.validate_on_submit(): try: user.settings.email_communications = form.email_communications.data user.update(api=api) flash("Profile updated", 'success') except sdk_exceptions.ResourceInvalid as e: message = json.loads(e.content) flash(message) return render_template('users/settings/emails.html', form=form, title='emails')
def comments_rate(comment_id, operation): """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 """ if operation not in {'revoke', 'upvote', 'downvote'}: raise wz_exceptions.BadRequest('Invalid operation') api = system_util.pillar_api() # PATCH the node and return the result. comment = Node({'_id': comment_id}) result = comment.patch({'op': operation}, api=api) assert result['_status'] == 'OK' return jsonify({ 'status': 'success', 'data': { 'op': operation, 'rating_positive': result.properties.rating_positive, 'rating_negative': result.properties.rating_negative, } })
def index(): """Get notifications for the current user. Optional url args: - limit: limits the number of notifications """ limit = request.args.get('limit', 25) api = system_util.pillar_api() user_notifications = Notification.all( { 'where': { 'user': str(current_user.objectid) }, 'sort': '-_created', 'max_results': str(limit), 'parse': '1' }, api=api) # TODO: properly investigate and handle missing actors items = [ notification_parse(n) for n in user_notifications['_items'] if notification_parse(n) ] return jsonify(items=items)
def homepage(): if current_user.is_anonymous: return redirect(url_for('cloud.welcome')) return render_template( 'homepage.html', api=system_util.pillar_api(), **_homepage_context(), )
def project_browse(project: pillarsdk.Project): """Project view displaying all top-level nodes. We render a regular project view, but we introduce an additional template variable: browse. By doing that we prevent the regular project view from loading and fetch via AJAX a "group" node-like view instead (see project_browse_view_nodes). """ return render_template( 'projects/view.html', api=system_util.pillar_api(), project=project, node=None, show_project=True, browse=True, og_picture=None, navigation_links=project_navigation_links(project, system_util.pillar_api()), extension_sidebar_links=current_app.extension_sidebar_links(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 action_read_toggle(notification_id): api = system_util.pillar_api() notification = Notification.find(notification_id, api=api) if notification.user == current_user.objectid: notification.is_read = not notification.is_read notification.update(api=api) return jsonify(status='success', data=dict(message="Notification {0} is_read {1}".format( notification_id, notification.is_read), is_read=notification.is_read)) else: return abort(403)
def render_page(): feed = AtomFeed('Blender Cloud - Latest updates', feed_url=ensure_schema(request.url), url=ensure_schema(request.url_root)) # Get latest blog posts api = system_util.pillar_api() latest_posts = Node.all( { 'where': { 'node_type': 'post', 'properties.status': 'published' }, 'embedded': { 'user': 1 }, 'sort': '-_created', 'max_results': '15' }, api=api) newest = None # Populate the feed for post in latest_posts._items: author = post.user.fullname or post.user.username updated = post._updated if post._updated else post._created url = ensure_schema( urllib.parse.urljoin(request.host_url, url_for_node(node=post))) content = post.properties.content[:500] content = '<p>{0}... <a href="{1}">Read more</a></p>'.format( content, url) if newest is None: newest = updated else: newest = max(newest, updated) feed.add(post.name, str(content), content_type='html', author=author, url=url, updated=updated, published=post._created) resp = feed.get_response() if newest is not None: resp.headers['Last-Modified'] = newest.strftime( current_app.config['RFC1123_DATE_FORMAT']) return resp
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 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 comment_edit(comment_id): """Allows a user to edit their comment.""" from pillar.web import jinja api = system_util.pillar_api() comment = Node({'_id': comment_id}) result = comment.patch({'op': 'edit', 'content': request.form['content']}, api=api) assert result['_status'] == 'OK' return jsonify({ 'status': 'success', 'data': { 'content': result.properties.content or '', 'content_html': jinja.do_markdowned(result.properties, 'content'), }})
def action_subscription_toggle(notification_id): """Given a notification id, get the ActivitySubscription and update it by toggling the notifications status for the web key. """ api = system_util.pillar_api() # Get the notification notification = notification_parse( Notification.find(notification_id, {'parse': '1'}, api=api)) # Get the subscription and modify it subscription = ActivitySubscription.find(notification['subscription'], api=api) subscription.notifications['web'] = not subscription.notifications['web'] subscription.update(api=api) return jsonify(status='success', data=dict(message="You have been {}subscribed".format( '' if subscription.notifications['web'] else 'un')))
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 get_projects(category): """Utility to get projects based on category. Should be moved on the API and improved with more extensive filtering capabilities. """ api = system_util.pillar_api() projects = Project.all( { 'where': { 'category': category, 'is_private': False }, 'sort': '-_created', }, api=api) for project in projects._items: attach_project_pictures(project, api) return projects
def action_read_all(): """Mark all notifications as read""" api = system_util.pillar_api() notifications = Notification.all( { 'where': '{"user": "******"}' % current_user.objectid, 'sort': '-_created' }, api=api) for notification in notifications._items: notification = Notification.find(notification._id, api=api) notification.is_read = True notification.update(api=api) return jsonify(status='success', data=dict(message="All notifications mark as read"))
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 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 test_edit_with_explicit_owner(self): """Test editing a node as the owner of the node.""" import pillarsdk from pillar.web.utils import system_util other_user_id = self.create_user(user_id='cafef005972666988bef6500', groups=[self.dillo_user_main_grp]) self.create_valid_auth_token(other_user_id, 'other_token') test_node = copy.deepcopy(self.test_node) node_doc = self._test_user(test_node, auth_token='other_token') # Is PUT allowed? self.assertIn('PUT', node_doc['allowed_methods']) with self.app.test_request_context(): api = system_util.pillar_api(token='other_token') node = pillarsdk.Node.find(node_doc['_id'], api=api) node.properties.content = 'Some content here' node.properties.status = 'published' node.update(api=api)
def render_comments_for_node(node_id: str, *, can_post_comments: bool): """Render the list of comments for a node.""" api = system_util.pillar_api() # 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 render_page(): feed = AtomFeed('Blender Cloud - Latest updates', feed_url=request.url, url=request.url_root) # Get latest blog posts api = system_util.pillar_api() latest_posts = Node.all( { 'where': { 'node_type': 'post', 'properties.status': 'published' }, 'embedded': { 'user': 1 }, 'sort': '-_created', 'max_results': '15' }, api=api) # Populate the feed for post in latest_posts._items: author = post.user.fullname updated = post._updated if post._updated else post._created url = url_for_node(node=post) content = post.properties.content[:500] content = '<p>{0}... <a href="{1}">Read more</a></p>'.format( content, url) feed.add(post.name, str(content), content_type='html', author=author, url=url, updated=updated, published=post._created) return feed.get_response()
def render_page(): projects = get_projects('workshop') return render_template('projects_index_collection.html', title='workshops', projects=projects._items, api=system_util.pillar_api())
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 process_node_form(form, node_id=None, node_type=None, user=None): """Generic function used to process new nodes, as well as edits """ if not user: log.warning( 'process_node_form(node_id=%s) called while user not logged in', node_id) return False api = system_util.pillar_api() node_schema = node_type['dyn_schema'].to_dict() form_schema = node_type['form_schema'].to_dict() if node_id: # Update existing node node = pillarsdk.Node.find(node_id, api=api) node.name = form.name.data node.description = form.description.data if 'picture' in form: node.picture = form.picture.data if node.picture == 'None' or node.picture == '': node.picture = None if 'parent' in form: if form.parent.data != "": node.parent = form.parent.data for prop_name, schema_prop, form_prop in iter_node_properties( node_type): data = form[prop_name].data if schema_prop['type'] == 'dict': data = attachments.attachment_form_parse_post_data(data) elif schema_prop['type'] == 'integer': if not data: data = None else: data = int(form[prop_name].data) elif schema_prop['type'] == 'float': if not data: data = None else: data = float(form[prop_name].data) elif schema_prop['type'] == 'datetime': data = datetime.strftime( data, current_app.config['RFC1123_DATE_FORMAT']) elif schema_prop['type'] == 'list': if prop_name == 'files': # Only keep those items that actually refer to a file. data = [ file_item for file_item in data if file_item.get('file') ] else: log.warning('Ignoring property %s of type %s', prop_name, schema_prop['type']) # elif pr == 'tags': # data = [tag.strip() for tag in data.split(',')] elif schema_prop['type'] == 'objectid': if data == '': # Set empty object to None so it gets removed by the # SDK before node.update() data = None else: if prop_name in form: data = form[prop_name].data path = prop_name.split('__') assert len(path) == 1 if len(path) > 1: recursive_prop = recursive(path, node.properties.to_dict(), data) node.properties = recursive_prop else: node.properties[prop_name] = data ok = node.update(api=api) if not ok: log.warning('Unable to update node: %s', node.error) # if form.picture.data: # image_data = request.files[form.picture.name].read() # post = node.replace_picture(image_data, api=api) return ok else: # Create a new node node = pillarsdk.Node() prop = {} files = {} prop['name'] = form.name.data prop['description'] = form.description.data prop['user'] = user if 'picture' in form: prop['picture'] = form.picture.data if prop['picture'] == 'None' or prop['picture'] == '': prop['picture'] = None if 'parent' in form: prop['parent'] = form.parent.data prop['properties'] = {} def get_data(node_schema, form_schema, prefix=""): for pr in node_schema: schema_prop = node_schema[pr] form_prop = form_schema.get(pr, {}) if pr == 'items': continue if 'visible' in form_prop and not form_prop['visible']: continue prop_name = "{0}{1}".format(prefix, pr) if schema_prop['type'] == 'dict': get_data(schema_prop['schema'], form_prop['schema'], "{0}__".format(prop_name)) continue data = form[prop_name].data if schema_prop['type'] == 'media': tmpfile = '/tmp/binary_data' data.save(tmpfile) binfile = open(tmpfile, 'rb') files[pr] = binfile continue if schema_prop['type'] == 'integer': if data == '': data = 0 if schema_prop['type'] == 'list': if data == '': data = [] if schema_prop['type'] == 'datetime': data = datetime.strftime(data, app.config['RFC1123_DATE_FORMAT']) if schema_prop['type'] == 'objectid': if data == '': data = None path = prop_name.split('__') if len(path) > 1: prop['properties'] = recursive(path, prop['properties'], data) else: prop['properties'][prop_name] = data get_data(node_schema, form_schema) prop['node_type'] = form.node_type_id.data post = node.post(prop, api=api) return post
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 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 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)