Beispiel #1
0
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)
Beispiel #2
0
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})
Beispiel #3
0
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)
Beispiel #4
0
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)
Beispiel #5
0
    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
Beispiel #6
0
    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())
Beispiel #7
0
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
Beispiel #8
0
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
Beispiel #9
0
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
Beispiel #10
0
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)
Beispiel #11
0
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)
Beispiel #12
0
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)
Beispiel #13
0
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})
Beispiel #14
0
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,
    )
Beispiel #15
0
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
Beispiel #16
0
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)
Beispiel #17
0
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,
        )
Beispiel #18
0
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
Beispiel #19
0
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)
Beispiel #20
0
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)
Beispiel #21
0
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)
Beispiel #22
0
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)
Beispiel #23
0
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)