From 6906f5a0481f5dfb40a4fff397ef017a2a03fe3e Mon Sep 17 00:00:00 2001 From: James Taylor Date: Mon, 14 Jul 2008 01:56:50 +0000 Subject: [PATCH] Support for sharing workflows (as well as renaming). A shared workflow is read only for the recipient, however they can always run the latest version of the workflow. They can also 'clone' the workflow to create their own editable copy of the current version. --- lib/galaxy/model/__init__.py | 5 ++ lib/galaxy/model/mapping.py | 12 ++++ lib/galaxy/web/base/controller.py | 2 +- lib/galaxy/web/controllers/workflow.py | 94 ++++++++++++++++++++++++-- templates/workflow/index.mako | 47 +++++++++++-- templates/workflow/rename.mako | 36 ++++++++++ templates/workflow/share.mako | 36 ++++++++++ 7 files changed, 219 insertions(+), 13 deletions(-) create mode 100644 templates/workflow/rename.mako create mode 100644 templates/workflow/share.mako diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py index 8d71f18a678..15737ce77b4 100644 --- a/lib/galaxy/model/__init__.py +++ b/lib/galaxy/model/__init__.py @@ -538,6 +538,11 @@ class WorkflowStepConnection( object ): self.input_step_id = None self.input_name = None +class StoredWorkflowUserShareAssociation( object ): + def __init__( self ): + self.stored_workflow = None + self.user = None + ## ---- Utility methods ------------------------------------------------------- def directory_hash_id( id ): diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py index 27d8d4342f6..dd15ec11338 100644 --- a/lib/galaxy/model/mapping.py +++ b/lib/galaxy/model/mapping.py @@ -223,6 +223,12 @@ WorkflowStepConnection.table = Table( "workflow_step_connection", metadata, Column( "input_name", String) ) +StoredWorkflowUserShareAssociation.table = Table( "stored_workflow_user_share_connection", metadata, + Column( "id", Integer, primary_key=True ), + Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) + ) + # With the tables defined we can define the mappers and setup the # relationships between the model objects. @@ -327,6 +333,12 @@ assign_mapper( context, StoredWorkflow, StoredWorkflow.table, primaryjoin=( StoredWorkflow.table.c.latest_workflow_id == Workflow.table.c.id ) ) ) ) +assign_mapper( context, StoredWorkflowUserShareAssociation, StoredWorkflowUserShareAssociation.table, + properties=dict( user=relation( User, backref='workflows_shared_by_others' ), + stored_workflow=relation( StoredWorkflow ) + ) ) + + def db_next_hid( self ): """ Override __next_hid to generate from the database in a concurrency diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py index fac5e8e8d44..30b77890310 100644 --- a/lib/galaxy/web/base/controller.py +++ b/lib/galaxy/web/base/controller.py @@ -6,7 +6,7 @@ import os, time, logging # Pieces of Galaxy to make global in every controller from galaxy import config, tools, web, model, util -from galaxy.web import error +from galaxy.web import error, url_for from Cheetah.Template import Template diff --git a/lib/galaxy/web/controllers/workflow.py b/lib/galaxy/web/controllers/workflow.py index b885285ce71..8713bd37dbd 100644 --- a/lib/galaxy/web/controllers/workflow.py +++ b/lib/galaxy/web/controllers/workflow.py @@ -22,7 +22,82 @@ class WorkflowController( BaseController ): """ user = trans.get_user() workflows = trans.sa_session.query( model.StoredWorkflow ).filter_by( user=user, deleted=False ).all() - return trans.fill_template( "workflow/index.mako", workflows = workflows ) + shared_by_others = trans.sa_session \ + .query( model.StoredWorkflowUserShareAssociation ) \ + .filter_by( user=user ) \ + .filter( model.StoredWorkflow.c.deleted == False ) \ + .all() + return trans.fill_template( "workflow/index.mako", + workflows = workflows, + shared_by_others = shared_by_others ) + + @web.expose + @web.require_login( "use Galaxy workflows" ) + def share( self, trans, id, email="" ): + msg = mtype = None + # Load workflow from database + stored = get_stored_workflow( trans, id ) + if email: + other = model.User.get_by( email=email ) + if not other: + mtype = "error" + msg = ( "User '%s' does not exist" % email ) + elif trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \ + .filter_by( user=other, stored_workflow=stored ).count() > 0: + mtype = "error" + msg = ( "Workflow already shared with '%s'" % email ) + else: + + share = model.StoredWorkflowUserShareAssociation() + share.stored_workflow = stored + share.user = other + session = trans.sa_session + session.save( share ) + session.flush() + ## trans.template_context['message'] = "Workflow '%s' shared with user '%s'" % ( stored.name, other.email ) + ## return trans.send_redirect( url_for( controller='workflow', action='index' ) ) + return trans.show_message( "Workflow '%s' shared with user '%s'" % ( stored.name, other.email ) ) + return trans.fill_template( "workflow/share.mako", + message = msg, + messagetype = mtype, + stored=stored, + email=email ) + + @web.expose + @web.require_login( "use Galaxy workflows" ) + def rename( self, trans, id, new_name=None ): + stored = get_stored_workflow( trans, id ) + if new_name is not None: + stored.name = new_name + trans.sa_session.flush() + return trans.response.send_redirect( url_for( controller='workflow', action='index' ) ) + else: + return trans.fill_template( "workflow/rename.mako", stored=stored ) + + @web.expose + @web.require_login( "use Galaxy workflows" ) + def clone( self, trans, id ): + stored = get_stored_workflow( trans, id, check_ownership=False ) + user = trans.get_user() + if stored.user == user: + owner = True + else: + if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \ + .filter_by( user=user, stored_workflow=stored ).count() == 0: + error( "Workflow is not owned by or shared with current user" ) + owner = False + new_stored = model.StoredWorkflow() + new_stored.name = "Clone of '%s'" % stored.name + new_stored.latest_workflow = stored.latest_workflow + if not owner: + new_stored.name += "shared by '%s'" % stored.user.email + new_stored.user = user + # Persist + session = trans.sa_session + session.save( new_stored ) + session.flush() + # Display the management page + return trans.response.send_redirect( url_for( controller='workflow', action='index' ) ) @web.expose @web.require_login( "create workflows" ) @@ -47,7 +122,7 @@ class WorkflowController( BaseController ): session.save( stored_workflow ) session.flush() # Display the management page - return self.index( trans ) + return trans.response.send_redirect( url_for( controller='workflow', action='index' ) ) @web.expose def delete( self, trans, id=None ): @@ -60,7 +135,7 @@ class WorkflowController( BaseController ): stored.deleted = True stored.flush() # Display the management page - return self.index( trans ) + return trans.response.send_redirect( url_for( controller='workflow', action='index' ) ) @web.expose @web.require_login( "edit workflows" ) @@ -348,7 +423,12 @@ class WorkflowController( BaseController ): @web.expose def run( self, trans, id, **kwargs ): - stored = get_stored_workflow( trans, id ) + stored = get_stored_workflow( trans, id, check_ownership=False ) + user = trans.get_user() + if stored.user != user: + if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \ + .filter_by( user=user, stored_workflow=stored ).count() == 0: + error( "Workflow is not owned by or shared with current user" ) # Get the latest revision workflow = stored.latest_workflow # It is possible for a workflow to have 0 steps @@ -392,7 +472,7 @@ class WorkflowController( BaseController ): step.input_connections_by_name = dict( ( conn.input_name, conn ) for conn in step.input_connections ) if not errors: # Run each step, connecting outputs to inputs - outputs = {} + outputs = odict() for step in workflow.steps: if step.type == 'tool' or step.type is None: tool = trans.app.toolbox.tools_by_id[ step.tool_id ] @@ -453,7 +533,7 @@ class WorkflowController( BaseController ): ## ---- Utility methods ------------------------------------------------------- -def get_stored_workflow( trans, id ): +def get_stored_workflow( trans, id, check_ownership=True ): """ Get a StoredWorkflow from the database by id, verifying ownership. """ @@ -466,7 +546,7 @@ def get_stored_workflow( trans, id ): user = trans.get_user() if not user: error( "Must be logged in to use workflows" ) - if not( stored.user == user ): + if check_ownership and not( stored.user == user ): error( "Workflow is not owned by current user" ) # Looks good return stored diff --git a/templates/workflow/index.mako b/templates/workflow/index.mako index 71fe19bbe7f..686e80343c2 100644 --- a/templates/workflow/index.mako +++ b/templates/workflow/index.mako @@ -47,26 +47,63 @@ -

Stored workflows

+

Your workflows

%if workflows: - + + + + ## + + %for workflow in workflows: - + + ## %endfor
Name# of StepsLast UpdatedActions
Name# of StepsLast UpdatedActions
${workflow.name}${len(workflow.latest_workflow.steps)} - ${str(workflow.update_time)[:19]}${len(workflow.latest_workflow.steps)}${str(workflow.update_time)[:19]} run | edit + | rename + | share | delete +
%else: - You have no stored workflows. + You have no workflows. + + %endif + +

Workflows shared with you by others

+ + %if shared_by_others: + + + + + + + + %for association in shared_by_others: + <% workflow = association.stored_workflow %> + + + + + + + %endfor +
NameOwner# of StepsActions
${workflow.name}${workflow.user.email}${len(workflow.latest_workflow.steps)} + run + clone +
+ %else: + + No workflows have been shared with you. %endif diff --git a/templates/workflow/rename.mako b/templates/workflow/rename.mako new file mode 100644 index 00000000000..62c9d709e5d --- /dev/null +++ b/templates/workflow/rename.mako @@ -0,0 +1,36 @@ +<%inherit file="/base.mako"/> + +%if message: +<% +if messagetype is UNDEFINED: + mt = "done" +else: + mt = messagetype +%> +

+

+ ${message} +
+

+%endif + +

+
Rename workflow '${stored.name}'
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ \ No newline at end of file diff --git a/templates/workflow/share.mako b/templates/workflow/share.mako new file mode 100644 index 00000000000..3b7b4848c5a --- /dev/null +++ b/templates/workflow/share.mako @@ -0,0 +1,36 @@ +<%inherit file="/base.mako"/> + +%if message: +<% +if messagetype is UNDEFINED: + mt = "done" +else: + mt = messagetype +%> +

+

+ ${message} +
+

+%endif + +

+
Share workflow '${stored.name}'
+
+
+
+ +
+ +
+
+
+
+ +
+
+
+
+ \ No newline at end of file