Esempio n. 1
0
def for_project(project, job_id=None, task_id=None):
    api = pillar_api()

    is_archive = request.blueprint == perproject_archive_blueprint.name

    # See if we need to redirect between archive and non-archive view.
    if job_id:
        from .sdk import Job
        job = Job.find(job_id, api=api)
        job_is_archived = job.status == 'archived'

        if job_is_archived != is_archive:
            target_blueprint = blueprint_for_archived[not is_archive]
            target_endpoint = request.endpoint.split('.')[-1]
            new_url = url_for(f'{target_blueprint.name}.{target_endpoint}', **request.view_args)
            return redirect(new_url, code=307)

    navigation_links = project_navigation_links(project, pillar_api())
    extension_sidebar_links = current_app.extension_sidebar_links(project)

    return render_template('flamenco/jobs/list_for_project.html',
                           stats={'nr_of_jobs': '∞', 'nr_of_tasks': '∞'},
                           open_job_id=job_id,
                           open_task_id=task_id,
                           project=project,
                           is_archive=is_archive,
                           page_context='archive' if is_archive else 'job',
                           navigation_links=navigation_links,
                           extension_sidebar_links=extension_sidebar_links)
Esempio n. 2
0
def index():
    api = pillar_api()

    # FIXME Sybren: add permission check.
    # TODO: add projections.
    projects = current_flamenco.flamenco_projects()

    for project in projects['_items']:
        attach_project_pictures(project, api)

    projs_with_summaries = [
        (proj, current_flamenco.job_manager.job_status_summary(proj['_id']))
        for proj in projects['_items']
    ]

    last_project = session.get('flamenco_last_project')
    if last_project:
        project = Project(last_project)
        navigation_links = project_navigation_links(project, pillar_api())
        extension_sidebar_links = current_app.extension_sidebar_links(project)
    else:
        project = None
        navigation_links = []
        extension_sidebar_links = []

    return render_template('flamenco/index.html',
                           projs_with_summaries=projs_with_summaries,
                           project=project,
                           navigation_links=navigation_links,
                           extension_sidebar_links=extension_sidebar_links)
Esempio n. 3
0
def index():
    api = pillar_api()

    # FIXME Sybren: add permission check.
    # TODO: add projections.
    projects = current_flamenco.flamenco_projects()

    for project in projects['_items']:
        attach_project_pictures(project, api)

    projs_with_summaries = [
        (proj, current_flamenco.job_manager.job_status_summary(proj['_id']))
        for proj in projects['_items']
    ]

    last_project = session.get('flamenco_last_project')
    if last_project:
        project = Project(last_project)
        navigation_links = project_navigation_links(project, pillar_api())
        extension_sidebar_links = current_app.extension_sidebar_links(project)
    else:
        project = None
        navigation_links = []
        extension_sidebar_links = []

    return render_template('flamenco/index.html',
                           projs_with_summaries=projs_with_summaries,
                           project=project,
                           navigation_links=navigation_links,
                           extension_sidebar_links=extension_sidebar_links)
Esempio n. 4
0
def for_project(project, job_id=None, task_id=None):
    api = pillar_api()

    is_archive = request.blueprint == perproject_archive_blueprint.name

    # See if we need to redirect between archive and non-archive view.
    if job_id:
        from .sdk import Job
        job = Job.find(job_id, api=api)
        job_is_archived = job.status == 'archived'

        if job_is_archived != is_archive:
            target_blueprint = blueprint_for_archived[not is_archive]
            target_endpoint = request.endpoint.split('.')[-1]
            new_url = url_for(f'{target_blueprint.name}.{target_endpoint}',
                              **request.view_args)
            return redirect(new_url, code=307)

    navigation_links = project_navigation_links(project, pillar_api())
    extension_sidebar_links = current_app.extension_sidebar_links(project)

    return render_template('flamenco/jobs/list_for_project.html',
                           stats={
                               'nr_of_jobs': '∞',
                               'nr_of_tasks': '∞'
                           },
                           open_job_id=job_id,
                           open_task_id=task_id,
                           project=project,
                           is_archive=is_archive,
                           page_context='archive' if is_archive else 'job',
                           navigation_links=navigation_links,
                           extension_sidebar_links=extension_sidebar_links)
Esempio n. 5
0
def index():
    api = system_util.pillar_api()

    # Get all projects, except the home project.
    projects_user = Project.all({
        'where': {'user': current_user.objectid,
                  'category': {'$ne': 'home'}},
        'sort': '-_created'
    }, api=api)

    projects_shared = Project.all({
        'where': {'user': {'$ne': current_user.objectid},
                  'permissions.groups.group': {'$in': current_user.groups},
                  'is_private': True},
        'sort': '-_created',
        'embedded': {'user': 1},
    }, api=api)

    # Attach project images
    for project in projects_user['_items']:
        utils.attach_project_pictures(project, api)

    for project in projects_shared['_items']:
        utils.attach_project_pictures(project, api)

    return render_template(
        'projects/index_dashboard.html',
        gravatar=utils.gravatar(current_user.email, size=128),
        projects_user=projects_user['_items'],
        projects_shared=projects_shared['_items'],
        api=api)
Esempio n. 6
0
    def edit_manager(self, manager_id, **fields):
        """Edits a manager.

        :type manager_id: str
        :type fields: dict
        :rtype: pillarsdk.Node
        """

        api = pillar_api()
        manager = pillarsdk.Node.find(manager_id, api=api)

        manager._etag = fields.pop('_etag')
        manager.name = fields.pop('name')
        manager.description = fields.pop('description')
        manager.properties.status = fields.pop('status')
        manager.properties.manager_type = fields.pop('manager_type',
                                                     '').strip() or None

        users = fields.pop('users', None)
        manager.properties.assigned_to = {'users': users or []}

        self._log.info('Saving manager %s', manager.to_dict())

        if fields:
            self._log.warning(
                'edit_manager(%r, ...) called with unknown fields %r; ignoring them.',
                manager_id, fields)

        manager.update(api=api)
        return manager
Esempio n. 7
0
    def sdk_file(self, slug: str, node_properties: dict) -> pillarsdk.File:
        """Return the file document for the attachment with this slug."""

        from pillar.web import system_util

        attachments = node_properties.get('attachments', {})
        attachment = attachments.get(slug)
        if not attachment:
            raise self.NoSuchSlug(slug)

        object_id = attachment.get('oid')
        if not object_id:
            raise self.NoSuchFile(object_id)

        # In theory attachments can also point to other collections.
        # There is no support for that yet, though.
        collection = attachment.get('collection', 'files')
        if collection != 'files':
            log.warning(
                'Attachment %r points to ObjectID %s in unsupported collection %r',
                slug, object_id, collection)
            raise self.NotSupported(f'unsupported collection {collection!r}')

        api = system_util.pillar_api()
        sdk_file = pillarsdk.File.find(object_id, api=api)
        return sdk_file
Esempio n. 8
0
        def wrapper(project_url, *args, **kwargs):
            if isinstance(project_url, pillarsdk.Resource):
                # This is already a resource, so this call probably is from one
                # view to another. Assume the caller knows what he's doing and
                # just pass everything along.
                return wrapped(project_url, *args, **kwargs)

            api = pillar_api()

            project = pillarsdk.Project.find_by_url(
                project_url,
                {'projection': projections},
                api=api)

            is_flamenco = current_flamenco.is_flamenco_project(project)
            if not is_flamenco:
                return error_project_not_setup_for_flamenco(project)

            session['flamenco_last_project'] = project.to_dict()

            project_id = bson.ObjectId(project['_id'])
            auth = current_flamenco.auth
            if not auth.current_user_may(action, project_id):
                if current_user.is_anonymous:
                    raise wz_exceptions.Forbidden('Login required for this URL')
                log.info('Denying user %s access %s to Flamenco on project %s',
                         flask_login.current_user, action, project_id)
                return error_project_not_available()

            if extension_props:
                pprops = project.extension_props.flamenco
                return wrapped(project, pprops, *args, **kwargs)
            return wrapped(project, *args, **kwargs)
Esempio n. 9
0
def profile_dillo():
    """Override Pillar profile page."""
    api = system_util.pillar_api()
    user = User.find(current_user.objectid, api=api)

    form = UserProfileForm(username=user.username,
                           full_name=user.full_name,
                           email=user.email)

    if form.validate_on_submit():
        user.username = form.username.data
        user.full_name = form.full_name.data
        user.email = form.email.data
        try:
            user.update(api=api)
            # TODO(fsiddi) fix missing flash, maybe it's in the template
            flash("Profile updated", 'success')
        except sdk_exceptions.ResourceInvalid as e:
            # TODO(fsiddi) fix the missing flash and attach the error to the form
            r = json.loads(e.content)
            log.warning("Error while updating user profile for User %s" %
                        current_user.objectid)

    return render_template('users/settings/profile.html',
                           form=form,
                           title='profile')
Esempio n. 10
0
def profile():
    """Profile view and edit page. This is a temporary implementation.
    """
    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)

    form = forms.UserProfileForm(username=user.username)

    if form.validate_on_submit():
        try:
            response = user.set_username(form.username.data, api=api)
            log.info('updated username of %s: %s', current_user, response)
            flash("Profile updated", 'success')
        except sdk_exceptions.ResourceInvalid as ex:
            log.warning('unable to set username %s to %r: %s', current_user,
                        form.username.data, ex)
            message = json.loads(ex.content)
            flash(message)

    blender_id_endpoint = current_app.config['BLENDER_ID_ENDPOINT']
    blender_profile_url = urllib.parse.urljoin(blender_id_endpoint,
                                               'settings/profile')

    return render_template('users/settings/profile.html',
                           form=form,
                           title='profile',
                           blender_profile_url=blender_profile_url)
Esempio n. 11
0
    def tasks_for_job(self, job_id, status=None, *,
                      page=1, max_results=250,
                      extra_where: dict = None):
        from .sdk import Task

        api = pillar_api()

        where = {'job': str(job_id)}
        if extra_where:
            where.update(extra_where)

        payload = {
            'where': where,
            'sort': [
                ('priority', -1),
                ('_id', 1),
            ],
            'max_results': max_results,
            'page': page,
        }
        if status:
            payload['where']['status'] = status

        tasks = Task.all(payload, api=api)
        self._log.debug(
            'task_for_job: where=%s  -> %i tasks in total, fetched page %i (%i per page)',
            payload['where'], tasks['_meta']['total'], page, max_results)
        return tasks
Esempio n. 12
0
    def create_manager(self, project, manager_type=None, parent=None):
        """Creates a new manager, owned by the current user.

        :rtype: pillarsdk.Node
        """

        from pillar.web.jinja import format_undertitle

        api = pillar_api()
        node_type = project.get_node_type(node_type_manager['name'])
        if not node_type:
            raise ValueError('Project %s not set up for Flamenco' %
                             project._id)

        node_props = dict(
            name='New manager',
            project=project['_id'],
            user=flask_login.current_user.objectid,
            node_type=node_type['name'],
            properties={
                'status': node_type['dyn_schema']['status']['default'],
            },
        )

        if manager_type:
            node_props['name'] = format_undertitle(manager_type)
            node_props['properties']['manager_type'] = manager_type
        if parent:
            node_props['parent'] = parent

        manager = pillarsdk.Node(node_props)
        manager.create(api=api)
        return manager
Esempio n. 13
0
def edit(project_url):
    api = system_util.pillar_api()
    # Fetch the Node or 404
    try:
        project = Project.find_one({'where': {'url': project_url}}, api=api)
        # project = Project.find(project_url, api=api)
    except ResourceNotFound:
        abort(404)
    utils.attach_project_pictures(project, api)
    form = ProjectForm(
        project_id=project._id,
        name=project.name,
        url=project.url,
        summary=project.summary,
        description=project.description,
        is_private='GET' not in project.permissions.world,
        category=project.category,
        status=project.status,
    )

    if form.validate_on_submit():
        project = Project.find(project._id, api=api)
        project.name = form.name.data
        project.url = form.url.data
        project.summary = form.summary.data
        project.description = form.description.data
        project.category = form.category.data
        project.status = form.status.data
        if form.picture_square.data:
            project.picture_square = form.picture_square.data
        if form.picture_header.data:
            project.picture_header = form.picture_header.data

        # Update world permissions from is_private checkbox
        if form.is_private.data:
            project.permissions.world = []
        else:
            project.permissions.world = ['GET']

        project.update(api=api)
        # Reattach the pictures
        utils.attach_project_pictures(project, api)
    else:
        if project.picture_square:
            form.picture_square.data = project.picture_square._id
        if project.picture_header:
            form.picture_header.data = project.picture_header._id

    # List of fields from the form that should be hidden to regular users
    if current_user.has_role('admin'):
        hidden_fields = []
    else:
        hidden_fields = ['url', 'status', 'is_private', 'category']

    return render_template('projects/edit.html',
                           form=form,
                           hidden_fields=hidden_fields,
                           project=project,
                           ext_pages=find_extension_pages(),
                           api=api)
Esempio n. 14
0
def post_rate(post_id, operation):
    """Comment rating function

    :param post_id: the post aid
    :type post_id: str
    :param operation: the rating 'revoke', 'upvote', 'downvote'
    :type operation: string

    """

    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': post_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,
        }})
Esempio n. 15
0
def get_user_info(user_id):
    """Returns email, username and full name of the user.

    Only returns the public fields, so the return value is the same
    for authenticated & non-authenticated users, which is why we're
    allowed to cache it globally.

    Returns an empty dict when the user cannot be found.
    """

    if user_id is None:
        return {}

    try:
        user = pillarsdk.User.find(user_id, api=pillar_api())
    except pillarsdk.exceptions.ResourceNotFound:
        return {}

    if not user:
        return {}

    # TODO: put those fields into a config var or module-level global.
    return {
        'email': user.email,
        'full_name': user.full_name,
        'username': user.username
    }
Esempio n. 16
0
def find_for_comment(project, node):
    """Returns the URL for a comment."""

    api = system_util.pillar_api()

    parent = node
    while parent.node_type == 'comment':
        if isinstance(parent.parent, pillarsdk.Resource):
            parent = parent.parent
            continue

        try:
            parent = Node.find(parent.parent, api=api)
        except ResourceNotFound:
            log.warning(
                'url_for_node(node_id=%r): Unable to find parent node %r',
                node['_id'], parent.parent)
            raise ValueError('Unable to find parent node %r' % parent.parent)

    # Find the redirection URL for the parent node.
    parent_url = find_url_for_node(parent)
    if '#' in parent_url:
        # We can't attach yet another fragment, so just don't link to
        # the comment for now.
        return parent_url
    return parent_url + '#{}'.format(node['_id'])
Esempio n. 17
0
def index(community_url):
    api = system_util.pillar_api()

    project = Project.find_first({'where': {'url': community_url}}, api=api)

    if project is None:
        return abort(404)

    attach_project_pictures(project, api)

    # Fetch all activities for the main project
    activities = Activity.all({
        'where': {
            'project': project['_id'],
        },
        '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:
            # If the node was delete, we get ValueError exception.
            # By setting act.link to '', it does not get displayed in the list.
            act.link = ''

    return render_template(
        'dillo/index.html',
        col_right={'activities': activities},
        project=project,
        submit_menu=project_submit_menu(project))
Esempio n. 18
0
    def activities_for_node(self, node_id, max_results=20, page=1):
        """Returns a page of activities for the given job, manager or task.

        Activities that are either on this node or have this node as context
        are returned.

        :returns: {'_items': [task, task, ...], '_meta': {Eve metadata}}
        """

        api = pillar_api()
        activities = pillarsdk.Activity.all(
            {
                'where': {
                    '$or': [
                        {
                            'object_type': 'node',
                            'object': node_id
                        },
                        {
                            'context_object_type': 'node',
                            'context_object': node_id
                        },
                    ],
                },
                'sort': [('_created', -1)],
                'max_results': max_results,
                'page': page,
            },
            api=api)

        # Fetch more info for each activity.
        for act in activities['_items']:
            act.actor_user = pillar.web.subquery.get_user_info(act.actor_user)

        return activities
Esempio n. 19
0
    def tasks_for_job(self,
                      job_id,
                      status=None,
                      *,
                      page=1,
                      max_results=250,
                      extra_where: dict = None):
        from .sdk import Task

        api = pillar_api()

        where = {'job': str(job_id)}
        if extra_where:
            where.update(extra_where)

        payload = {
            'where': where,
            'sort': [
                ('priority', -1),
                ('_id', 1),
            ],
            'max_results': max_results,
            'page': page,
        }
        if status:
            payload['where']['status'] = status

        tasks = Task.all(payload, api=api)
        self._log.debug(
            'task_for_job: where=%s  -> %i tasks in total, fetched page %i (%i per page)',
            payload['where'], tasks['_meta']['total'], page, max_results)
        return tasks
Esempio n. 20
0
def archive(project, job_id):
    """Redirects to the actual job archive.

    This is done via a redirect, so that templates that offer a download link do not
    need to know the eventual storage backend link. This makes them render faster,
    and only requires communication with the storage backend when needed.
    """

    from pillar.api.file_storage_backends import default_storage_backend

    from . import ARCHIVE_JOB_STATES
    from .sdk import Job

    api = pillar_api()
    job = Job.find(job_id, api=api)

    if job['status'] not in ARCHIVE_JOB_STATES:
        raise wz_exceptions.PreconditionFailed('Job is not archived')

    archive_blob_name = job.archive_blob_name
    if not archive_blob_name:
        raise wz_exceptions.NotFound('Job has no archive')

    bucket = default_storage_backend(project._id)
    blob = bucket.get_blob(archive_blob_name)
    archive_url = blob.get_url(is_public=False)

    return redirect(archive_url, code=303)
Esempio n. 21
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})
Esempio n. 22
0
    def flamenco_projects(self, *, projection: dict = None):
        """Returns projects set up for Flamenco.

        If the current user is authenticated, limits to that user's projects.

        :returns: {'_items': [proj, proj, ...], '_meta': Eve metadata}
        """

        import pillarsdk
        from pillar.web.system_util import pillar_api

        api = pillar_api()

        # Find projects that are set up for Flamenco.
        params: typing.MutableMapping[str, typing.Any] = {'where': {
            'extension_props.flamenco': {'$exists': 1},
        }}
        if projection:
            params['projection'] = projection

        if current_user.is_authenticated:
            # Limit to projects current user is member of (rather than has access to).
            params['where']['permissions.groups.group'] = {'$in': current_user.groups}

        projects = pillarsdk.Project.all(params, api=api)
        return projects
Esempio n. 23
0
def view_embed(organization_id: str):
    if not request.is_xhr:
        return index(organization_id)

    api = pillar_api()

    organization: Organization = Organization.find(organization_id, api=api)

    om = current_app.org_manager
    organization_oid = str2id(organization_id)

    members = om.org_members(organization.members)
    for member in members:
        member['avatar'] = gravatar(member.get('email'))
        member['_id'] = str(member['_id'])

    admin_user = User.find(organization.admin_uid, api=api)

    # Make sure it's never None
    organization.unknown_members = organization.unknown_members or []

    can_super_edit = current_user.has_cap('admin')
    can_edit = can_super_edit or om.user_is_admin(organization_oid)

    csrf = flask_wtf.csrf.generate_csrf()

    return render_template('organizations/view_embed.html',
                           organization=organization,
                           admin_user=admin_user,
                           members=members,
                           can_edit=can_edit,
                           can_super_edit=can_super_edit,
                           seats_used=len(members) +
                           len(organization.unknown_members),
                           csrf=csrf)
Esempio n. 24
0
    def job_status_summary(self, project_id):
        """Returns number of shots per shot status for the given project.

        :rtype: ProjectSummary
        """
        from .sdk import Job

        api = pillar_api()

        # TODO: turn this into an aggregation call to do the counting on
        # MongoDB.
        try:
            jobs = Job.all({
                'where': {
                    'project': project_id,
                },
                'projection': {
                    'status': 1,
                },
                'order': [
                    ('status', 1),
                ],
            }, api=api)
        except pillarsdk.ResourceNotFound:
            return ProjectSummary()

        # FIXME: this breaks when we hit the pagination limit.
        summary = ProjectSummary()
        for job in jobs['_items']:
            summary.count(job['status'])

        return summary
Esempio n. 25
0
    def activities_for_node(self, node_id, max_results=20, page=1):

        api = pillar_api()
        activities = pillarsdk.Activity.all(
            {
                'where': {
                    '$or': [
                        {
                            'object_type': 'node',
                            'object': node_id
                        },
                        {
                            'context_object_type': 'node',
                            'context_object': node_id
                        },
                    ],
                },
                'sort': [('_created', -1)],
                'max_results': max_results,
                'page': page,
            },
            api=api)

        # Fetch more info for each activity.
        for act in activities['_items']:
            act.actor_user = pillar.web.subquery.get_user_info(act.actor_user)

        return activities
Esempio n. 26
0
def view_job(project, flamenco_props, job_id):
    if not request.is_xhr:
        return for_project(project, job_id=job_id)

    # Job list is public, job details are not.
    if not current_user.has_cap('flamenco-view'):
        raise wz_exceptions.Forbidden()

    from .sdk import Job
    from ..managers.sdk import Manager

    api = pillar_api()
    job = Job.find(job_id, api=api)

    try:
        manager = Manager.find(job.manager, api=api)
    except pillarsdk.ForbiddenAccess:
        # It's very possible that the user doesn't have access to this Manager.
        manager = None
    except pillarsdk.ResourceNotFound:
        log.warning('Flamenco job %s has a non-existant manager %s', job_id,
                    job.manager)
        manager = None

    from . import (CANCELABLE_JOB_STATES, REQUEABLE_JOB_STATES,
                   RECREATABLE_JOB_STATES, ARCHIVE_JOB_STATES,
                   ARCHIVEABLE_JOB_STATES, FAILED_TASKS_REQUEABLE_JOB_STATES)

    auth = current_flamenco.auth
    write_access = auth.current_user_may(auth.Actions.USE,
                                         bson.ObjectId(project['_id']))

    status = job['status']
    is_archived = status in ARCHIVE_JOB_STATES
    archive_available = is_archived and job.archive_blob_name

    # Sort job settings so we can iterate over them in a deterministic way.
    job_settings = collections.OrderedDict(
        (key, job.settings[key])
        for key in sorted(job.settings.to_dict().keys()))

    return render_template(
        'flamenco/jobs/view_job_embed.html',
        job=job,
        manager=manager,
        project=project,
        flamenco_props=flamenco_props.to_dict(),
        flamenco_context=request.args.get('context'),
        can_cancel_job=write_access and status in CANCELABLE_JOB_STATES,
        can_requeue_job=write_access and status in REQUEABLE_JOB_STATES,
        can_recreate_job=write_access and status in RECREATABLE_JOB_STATES,
        can_archive_job=write_access and status in ARCHIVEABLE_JOB_STATES,
        # TODO(Sybren): check that there are actually failed tasks before setting to True:
        can_requeue_failed_tasks=write_access
        and status in FAILED_TASKS_REQUEABLE_JOB_STATES,
        is_archived=is_archived,
        write_access=write_access,
        archive_available=archive_available,
        job_settings=job_settings,
    )
Esempio n. 27
0
    def flamenco_projects(self, *, projection: dict = None):
        """Returns projects set up for Flamenco.

        If the current user is authenticated, limits to that user's projects.

        :returns: {'_items': [proj, proj, ...], '_meta': Eve metadata}
        """

        import pillarsdk
        from pillar.web.system_util import pillar_api

        api = pillar_api()

        # Find projects that are set up for Flamenco.
        params: typing.MutableMapping[str, typing.Any] = {
            'where': {
                'extension_props.flamenco': {
                    '$exists': 1
                },
            }
        }
        if projection:
            params['projection'] = projection

        if current_user.is_authenticated:
            # Limit to projects current user is member of (rather than has access to).
            params['where']['permissions.groups.group'] = {
                '$in': current_user.groups
            }

        projects = pillarsdk.Project.all(params, api=api)
        return projects
Esempio n. 28
0
    def job_status_summary(self, project_id):
        """Returns number of shots per shot status for the given project.

        :rtype: ProjectSummary
        """
        from .sdk import Job

        api = pillar_api()

        # TODO: turn this into an aggregation call to do the counting on
        # MongoDB.
        try:
            jobs = Job.all(
                {
                    'where': {
                        'project': project_id,
                    },
                    'projection': {
                        'status': 1,
                    },
                    'order': [
                        ('status', 1),
                    ],
                },
                api=api)
        except pillarsdk.ResourceNotFound:
            return ProjectSummary()

        # FIXME: this breaks when we hit the pagination limit.
        summary = ProjectSummary()
        for job in jobs['_items']:
            summary.count(job['status'])

        return summary
Esempio n. 29
0
def view_embed(node_id):
    api = system_util.pillar_api()

    # Get node, we'll embed linked objects later.
    try:
        node = Node.find(node_id, api=api)
    except ResourceNotFound:
        return render_template('errors/404_embed.html')
    except ForbiddenAccess:
        return render_template('errors/403_embed.html')

    project_projection = {'project': {'url': 1, 'name': 1}}
    project = Project.find(node.project, project_projection, api=api)

    node.picture = get_file(node.picture, api=api)
    node.user = node.user and User.find(node.user, api=api)

    write_access = 'PUT' in (node.allowed_methods or set())

    extra_template_args = {'project': project}

    return render_template(
        'nodes/custom/dillo_post/view_embed.html',
        node_id=node._id,
        node=node,
        write_access=write_access,
        api=api,
        **extra_template_args)
Esempio n. 30
0
    def web_set_job_status(self, job_id, new_status):
        """Web-level call to updates the job status."""
        from .sdk import Job

        api = pillar_api()
        job = Job({'_id': job_id})
        job.patch({'op': 'set-job-status', 'status': new_status}, api=api)
Esempio n. 31
0
def archive(project, job_id):
    """Redirects to the actual job archive.

    This is done via a redirect, so that templates that offer a download link do not
    need to know the eventual storage backend link. This makes them render faster,
    and only requires communication with the storage backend when needed.
    """

    from pillar.api.file_storage_backends import default_storage_backend

    from . import ARCHIVE_JOB_STATES
    from .sdk import Job

    api = pillar_api()
    job = Job.find(job_id, api=api)

    if job['status'] not in ARCHIVE_JOB_STATES:
        raise wz_exceptions.PreconditionFailed('Job is not archived')

    archive_blob_name = job.archive_blob_name
    if not archive_blob_name:
        raise wz_exceptions.NotFound('Job has no archive')

    bucket = default_storage_backend(project._id)
    blob = bucket.get_blob(archive_blob_name)
    archive_url = blob.get_url(is_public=False)

    return redirect(archive_url, code=303)
Esempio n. 32
0
    def web_set_task_status(self, task_id, new_status):
        """Web-level call to updates the task status."""
        from .sdk import Task

        api = pillar_api()
        task = Task({'_id': task_id})
        task.patch({'op': 'set-task-status', 'status': new_status}, api=api)
Esempio n. 33
0
        def wrapper(project_url, *args, **kwargs):
            if isinstance(project_url, pillarsdk.Resource):
                # This is already a resource, so this call probably is from one
                # view to another. Assume the caller knows what he's doing and
                # just pass everything along.
                return wrapped(project_url, *args, **kwargs)

            api = pillar_api()

            project = pillarsdk.Project.find_by_url(
                project_url, {'projection': projections}, api=api)

            is_flamenco = current_flamenco.is_flamenco_project(project)
            if not is_flamenco:
                return error_project_not_setup_for_flamenco()

            session['flamenco_last_project'] = project.to_dict()

            project_id = bson.ObjectId(project['_id'])
            auth = current_flamenco.auth
            if not auth.current_user_may(action, project_id):
                log.info('Denying user %s access %s to Flamenco on project %s',
                         flask_login.current_user, action, project_id)
                return error_project_not_available()

            if extension_props:
                pprops = project.extension_props.flamenco
                return wrapped(project, pprops, *args, **kwargs)
            return wrapped(project, *args, **kwargs)
Esempio n. 34
0
def view_task(project, flamenco_props, task_id):
    if not request.is_xhr:
        return for_project(project, task_id=task_id)

    # Task list is public, task details are not.
    if not flask_login.current_user.has_role(*ROLES_REQUIRED_TO_VIEW_ITEMS):
        raise wz_exceptions.Forbidden()

    api = pillar_api()
    task = pillarsdk.Node.find(task_id, api=api)
    node_type = project.get_node_type(node_type_task['name'])

    # Fetch project users so that we can assign them tasks
    if 'PUT' in task.allowed_methods:
        users = project.get_users(api=api)
        project.users = users['_items']
    else:
        task.properties.assigned_to.users = [
            pillar.web.subquery.get_user_info(uid)
            for uid in task.properties.assigned_to.users
        ]

    return render_template('flamenco/tasks/view_task_embed.html',
                           task=task,
                           project=project,
                           task_node_type=node_type,
                           flamenco_props=flamenco_props.to_dict(),
                           flamenco_context=request.args.get('context'))
Esempio n. 35
0
    def edit_job(self, job_id, **fields):
        """Edits a job.

        :type job_id: str
        :type fields: dict
        :rtype: pillarsdk.Node
        """

        api = pillar_api()
        job = pillarsdk.Node.find(job_id, api=api)

        job._etag = fields.pop('_etag')
        job.name = fields.pop('name')
        job.description = fields.pop('description')
        job.properties.status = fields.pop('status')
        job.properties.job_type = fields.pop('job_type', '').strip() or None

        users = fields.pop('users', None)
        job.properties.assigned_to = {'users': users or []}

        self._log.info('Saving job %s', job.to_dict())

        if fields:
            self._log.warning(
                'edit_job(%r, ...) called with unknown fields %r; ignoring them.',
                job_id, fields)

        job.update(api=api)
        return job
Esempio n. 36
0
def create(community_url: str, post_type: str):
    api = system_util.pillar_api()

    project = Project.find_first({'where': {'url': community_url}}, api=api)
    if project is None:
        return abort(404)

    log.info('Creating post for user {}'.format(current_user.objectid))

    dillo_post_node_type = project.get_node_type('dillo_post')
    dillo_post_tags_default = dillo_post_node_type['dyn_schema']['tags']['schema']['default']

    post_props = dict(
        project=project['_id'],
        name='Awesome Post Title',
        user=current_user.objectid,
        node_type='dillo_post',
        properties=dict(
            tags=[dillo_post_tags_default, ],
            post_type=post_type)
    )

    post = Node(post_props)
    post.create(api=api)
    embed = request.args.get('embed')
    return redirect(url_for(
        'nodes.edit', node_id=post._id, embed=embed, _external=True,
        _scheme=current_app.config['SCHEME']))
Esempio n. 37
0
def view_task(project, flamenco_props, task_id):
    from flamenco.tasks.sdk import Task

    api = pillar_api()

    if not request.is_xhr:
        # Render page that'll perform the XHR.
        from flamenco.jobs import routes as job_routes

        task = Task.find(task_id, {'projection': {'job': 1}}, api=api)
        return job_routes.for_project(project,
                                      job_id=task['job'],
                                      task_id=task_id)

    # Task list is public, task details are not.
    if not current_user.has_cap('flamenco-view'):
        raise wz_exceptions.Forbidden()

    task = Task.find(task_id, api=api)

    from . import REQUEABLE_TASK_STATES
    project_id = bson.ObjectId(project['_id'])

    write_access = current_flamenco.auth.current_user_may(
        Actions.USE, project_id)
    can_requeue_task = write_access and task['status'] in REQUEABLE_TASK_STATES

    return render_template('flamenco/tasks/view_task_embed.html',
                           task=task,
                           project=project,
                           flamenco_props=flamenco_props.to_dict(),
                           flamenco_context=request.args.get('context'),
                           can_view_log=write_access,
                           can_requeue_task=can_requeue_task)
Esempio n. 38
0
    def web_set_task_status(self, task_id, new_status):
        """Web-level call to updates the task status."""
        from .sdk import Task

        api = pillar_api()
        task = Task({'_id': task_id})
        task.patch({'op': 'set-task-status',
                    'status': new_status}, api=api)
Esempio n. 39
0
    def web_set_job_status(self, job_id, new_status):
        """Web-level call to updates the job status."""
        from .sdk import Job

        api = pillar_api()
        job = Job({'_id': job_id})
        job.patch({'op': 'set-job-status',
                   'status': new_status}, api=api)
Esempio n. 40
0
    def tasks_for_project(self, project_id):
        """Returns the tasks for the given project.

        :returns: {'_items': [task, task, ...], '_meta': {Eve metadata}}
        """
        from .sdk import Task

        api = pillar_api()
        try:
            tasks = Task.all({
                'where': {
                    'project': project_id,
                }}, api=api)
        except ResourceNotFound:
            return {'_items': [], '_meta': {'total': 0}}

        return tasks
Esempio n. 41
0
def project_settings(project: pillarsdk.Project, **template_args: dict):
    """Renders the project settings page for Flamenco projects."""

    from pillar.api.utils import str2id
    from pillar.web.system_util import pillar_api
    from .managers.sdk import Manager

    if not current_flamenco.auth.current_user_is_flamenco_user():
        raise wz_exceptions.Forbidden()

    # Based on the project state, we can render a different template.
    if not current_flamenco.is_flamenco_project(project):
        return render_template('flamenco/project_settings/offer_setup.html',
                               project=project, **template_args)

    project_id = str2id(project['_id'])
    flauth = current_flamenco.auth
    may_use = flauth.current_user_may(flauth.Actions.USE, project_id)

    # Use the API for querying for Managers, because it implements access control.
    api = pillar_api()
    managers = Manager.all(api=api)
    linked_managers = Manager.all({
        'where': {
            'projects': project['_id'],
        },
    }, api=api)

    try:
        first_manager = managers['_items'][0]
    except (KeyError, IndexError):
        first_manager = None
    try:
        first_linked_manager = linked_managers['_items'][0]
    except (KeyError, IndexError):
        first_linked_manager = None

    return render_template('flamenco/project_settings/settings.html',
                           project=project,
                           managers=managers,
                           first_manager=first_manager,
                           linked_managers=linked_managers,
                           first_linked_manager=first_linked_manager,
                           may_use_flamenco=may_use,
                           **template_args)
Esempio n. 42
0
def redir_job_id(job_id):
    """Redirects to the job view.

    This saves the client from performing another request to find the project URL;
    we do it for them.
    """

    from flask import redirect, url_for
    from .sdk import Job
    from pillarsdk import Project

    # FIXME Sybren: add permission check.

    api = pillar_api()
    j = Job.find(job_id, {'projection': {'project': 1, 'status': 1}}, api=api)
    p = Project.find(j.project, {'projection': {'url': 1}}, api=api)

    target_blueprint = blueprint_for_archived[j.status == 'archived']
    return redirect(url_for(f'{target_blueprint.name}.view_job',
                            project_url=p.url, job_id=j._id))
Esempio n. 43
0
    def jobs_for_project(self, project_id, *, archived=False):
        """Returns the jobs for the given project.

        :returns: {'_items': [job, job, ...], '_meta': {Eve metadata}}
        """
        from .sdk import Job

        # Eve doesn't support '$eq' :(
        status_q = 'archived' if archived else {'$ne': 'archived'}
        where = {'project': project_id,
                 'status': status_q}

        api = pillar_api()
        try:
            j = Job.all({
                'where': where,
                'sort': [('_updated', -1), ('_created', -1)],
            }, api=api)
        except pillarsdk.ResourceNotFound:
            return {'_items': [], '_meta': {'total': 0}}
        return j
Esempio n. 44
0
def edit_rna_overrides(project, job_id):
    """Allows editing RNA overrides of this job."""
    from .sdk import Job

    api = pillar_api()
    job = Job.find(job_id, api=api)

    if job['job_type'] not in blender_render.job_types():
        raise wz_exceptions.PreconditionFailed('Job is not a Blender job')

    override_lines = job.settings.rna_overrides or []

    return render_template(
        'flamenco/jobs/edit_rna_overrides.html',
        job=job,
        job_id=job_id,
        project=project,
        override_lines=override_lines,
        override_text='\n'.join(override_lines),
        already_has_overrides=job.settings.rna_overrides is not None,
        close_url=url_for('flamenco.jobs.perproject.view_job',
                          project_url=project.url, job_id=job._id),
    )
Esempio n. 45
0
def view_job(project, flamenco_props, job_id):
    if not request.is_xhr:
        return for_project(project, job_id=job_id)

    # Job list is public, job details are not.
    if not current_user.has_cap('flamenco-view'):
        raise wz_exceptions.Forbidden()

    from .sdk import Job
    from ..managers.sdk import Manager

    api = pillar_api()
    job = Job.find(job_id, api=api)

    try:
        manager = Manager.find(job.manager, api=api)
    except pillarsdk.ForbiddenAccess:
        # It's very possible that the user doesn't have access to this Manager.
        manager = None
    except pillarsdk.ResourceNotFound:
        log.warning('Flamenco job %s has a non-existant manager %s', job_id, job.manager)
        manager = None

    users_coll = current_app.db('users')
    user = users_coll.find_one(bson.ObjectId(job.user), projection={'username': 1, 'full_name': 1})
    if user:
        username = user.get('username', '')
        full_name = user.get('full_name', '')
        user_name = f'{full_name} (@{username})'.strip() or '-unknown-'
    else:
        user_name = '-unknown-'

    from . import (CANCELABLE_JOB_STATES, REQUEABLE_JOB_STATES, RECREATABLE_JOB_STATES,
                   ARCHIVE_JOB_STATES, ARCHIVEABLE_JOB_STATES, FAILED_TASKS_REQUEABLE_JOB_STATES)

    auth = current_flamenco.auth
    write_access = auth.current_user_may(auth.Actions.USE, bson.ObjectId(project['_id']))

    status = job['status']
    is_archived = status in ARCHIVE_JOB_STATES
    archive_available = is_archived and job.archive_blob_name

    # Sort job settings so we can iterate over them in a deterministic way.
    job_settings = collections.OrderedDict((key, job.settings[key])
                                           for key in sorted(job.settings.to_dict().keys()))

    change_prio_states = RECREATABLE_JOB_STATES | REQUEABLE_JOB_STATES | CANCELABLE_JOB_STATES

    return render_template(
        'flamenco/jobs/view_job_embed.html',
        job=job,
        user_name=user_name,
        manager=manager.to_dict(),
        project=project,
        flamenco_props=flamenco_props.to_dict(),
        flamenco_context=request.args.get('context'),
        can_cancel_job=write_access and status in CANCELABLE_JOB_STATES,
        can_requeue_job=write_access and status in REQUEABLE_JOB_STATES,
        can_recreate_job=write_access and status in RECREATABLE_JOB_STATES,
        can_archive_job=write_access and status in ARCHIVEABLE_JOB_STATES,
        # TODO(Sybren): check that there are actually failed tasks before setting to True:
        can_requeue_failed_tasks=write_access and status in FAILED_TASKS_REQUEABLE_JOB_STATES,
        can_change_prio=write_access and status in change_prio_states,
        can_edit_rna_overrides=write_access and job['job_type'] in blender_render.job_types(),
        is_archived=is_archived,
        write_access=write_access,
        archive_available=archive_available,
        job_settings=job_settings,
        job_status_help=HELP_FOR_STATUS.get(status, ''),
    )
Esempio n. 46
0
def for_project_with_task(project, task_id):
    from flamenco.tasks.sdk import Task

    api = pillar_api()
    task = Task.find(task_id, {'projection': {'job': 1}}, api=api)
    return for_project(project, job_id=task['job'], task_id=task_id)