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)
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)
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)
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)
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
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
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)
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')
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)
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
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
def edit(project_url): api = system_util.pillar_api() # Fetch the Node or 404 try: project = Project.find_one({'where': {'url': project_url}}, api=api) # project = Project.find(project_url, api=api) except ResourceNotFound: abort(404) utils.attach_project_pictures(project, api) form = ProjectForm( project_id=project._id, name=project.name, url=project.url, summary=project.summary, description=project.description, is_private='GET' not in project.permissions.world, category=project.category, status=project.status, ) if form.validate_on_submit(): project = Project.find(project._id, api=api) project.name = form.name.data project.url = form.url.data project.summary = form.summary.data project.description = form.description.data project.category = form.category.data project.status = form.status.data if form.picture_square.data: project.picture_square = form.picture_square.data if form.picture_header.data: project.picture_header = form.picture_header.data # Update world permissions from is_private checkbox if form.is_private.data: project.permissions.world = [] else: project.permissions.world = ['GET'] project.update(api=api) # Reattach the pictures utils.attach_project_pictures(project, api) else: if project.picture_square: form.picture_square.data = project.picture_square._id if project.picture_header: form.picture_header.data = project.picture_header._id # List of fields from the form that should be hidden to regular users if current_user.has_role('admin'): hidden_fields = [] else: hidden_fields = ['url', 'status', 'is_private', 'category'] return render_template('projects/edit.html', form=form, hidden_fields=hidden_fields, project=project, ext_pages=find_extension_pages(), api=api)
def 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, }})
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 }
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'])
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))
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
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)
def view(project_url): """Entry point to view a project""" if request.args.get('format') == 'jstree': log.warning('projects.view(%r) endpoint called with format=jstree, ' 'redirecting to proper endpoint. URL is %s; referrer is %s', project_url, request.url, request.referrer) return redirect(url_for('projects.jstree', project_url=project_url)) api = system_util.pillar_api() project = find_project_or_404(project_url, embedded={'header_node': 1}, api=api) # Load the header video file, if there is any. header_video_file = None header_video_node = None if project.header_node and project.header_node.node_type == 'asset' and \ project.header_node.properties.content_type == 'video': header_video_node = project.header_node header_video_file = utils.get_file(project.header_node.properties.file) header_video_node.picture = utils.get_file(header_video_node.picture) return render_project(project, api, extra_context={'header_video_file': header_video_file, 'header_video_node': header_video_node})
def 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
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)
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
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
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, )
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
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
def view_embed(node_id): api = system_util.pillar_api() # Get node, we'll embed linked objects later. try: node = Node.find(node_id, api=api) except ResourceNotFound: return render_template('errors/404_embed.html') except ForbiddenAccess: return render_template('errors/403_embed.html') project_projection = {'project': {'url': 1, 'name': 1}} project = Project.find(node.project, project_projection, api=api) node.picture = get_file(node.picture, api=api) node.user = node.user and User.find(node.user, api=api) write_access = 'PUT' in (node.allowed_methods or set()) extra_template_args = {'project': project} return render_template( 'nodes/custom/dillo_post/view_embed.html', node_id=node._id, node=node, write_access=write_access, api=api, **extra_template_args)
def 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)
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)
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)
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'))
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
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']))
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)
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
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)
def redir_job_id(job_id): """Redirects to the job view. This saves the client from performing another request to find the project URL; we do it for them. """ from flask import redirect, url_for from .sdk import Job from pillarsdk import Project # FIXME Sybren: add permission check. api = pillar_api() j = Job.find(job_id, {'projection': {'project': 1, 'status': 1}}, api=api) p = Project.find(j.project, {'projection': {'url': 1}}, api=api) target_blueprint = blueprint_for_archived[j.status == 'archived'] return redirect(url_for(f'{target_blueprint.name}.view_job', project_url=p.url, job_id=j._id))
def 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
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), )
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, ''), )
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)