PageRenderTime 122ms CodeModel.GetById 12ms app.highlight 97ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/galaxy/web/controllers/workflow.py

https://bitbucket.org/h_morita_dbcls/galaxy-central
Python | 1321 lines | 1309 code | 9 blank | 3 comment | 24 complexity | 9c8918f056f2affdf444db78e0988e28 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1from galaxy.web.base.controller import *
   2
   3import pkg_resources
   4pkg_resources.require( "simplejson" )
   5import simplejson
   6
   7from galaxy.web.framework.helpers import time_ago, iff, grids
   8from galaxy.tools.parameters import *
   9from galaxy.tools import DefaultToolState
  10from galaxy.tools.parameters.grouping import Repeat, Conditional
  11from galaxy.datatypes.data import Data
  12from galaxy.util.odict import odict
  13from galaxy.util.bunch import Bunch
  14from galaxy.util.sanitize_html import sanitize_html
  15from galaxy.util.topsort import topsort, topsort_levels, CycleError
  16from galaxy.workflow.modules import *
  17from galaxy import model
  18from galaxy.model.mapping import desc
  19from galaxy.model.orm import *
  20from galaxy.jobs.actions.post import *
  21from galaxy.item_attrs.ratings import UsesItemRatings
  22
  23import urllib2
  24
  25class StoredWorkflowListGrid( grids.Grid ):    
  26    class StepsColumn( grids.GridColumn ):
  27        def get_value(self, trans, grid, workflow):
  28            return len( workflow.latest_workflow.steps )
  29    
  30    # Grid definition
  31    use_panels = True
  32    title = "Saved Workflows"
  33    model_class = model.StoredWorkflow
  34    default_filter = { "name" : "All", "tags": "All" }
  35    default_sort_key = "-update_time"
  36    columns = [
  37        grids.TextColumn( "Name", key="name", model_class=model.StoredWorkflow, attach_popup=True, filterable="advanced" ),
  38        grids.IndividualTagsColumn( "Tags", "tags", model.StoredWorkflow, model.StoredWorkflowTagAssociation, filterable="advanced", grid_name="StoredWorkflowListGrid" ),
  39        StepsColumn( "Steps" ),
  40        grids.GridColumn( "Created", key="create_time", format=time_ago ),
  41        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
  42    ]
  43    columns.append( 
  44        grids.MulticolFilterColumn(  
  45        "Search", 
  46        cols_to_filter=[ columns[0], columns[1] ], 
  47        key="free-text-search", visible=False, filterable="standard" )
  48                )
  49    operations = [
  50        grids.GridOperation( "Edit", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
  51        grids.GridOperation( "Run", condition=( lambda item: not item.deleted ), async_compatible=False ),
  52        grids.GridOperation( "Clone", condition=( lambda item: not item.deleted ), async_compatible=False  ),
  53        grids.GridOperation( "Rename", condition=( lambda item: not item.deleted ), async_compatible=False  ),
  54        grids.GridOperation( "Sharing", condition=( lambda item: not item.deleted ), async_compatible=False ),
  55        grids.GridOperation( "Delete", condition=( lambda item: item.deleted ), async_compatible=True ),
  56    ]
  57    def apply_query_filter( self, trans, query, **kwargs ):
  58        return query.filter_by( user=trans.user, deleted=False )
  59
  60class StoredWorkflowAllPublishedGrid( grids.Grid ):
  61    title = "Published Workflows"
  62    model_class = model.StoredWorkflow
  63    default_sort_key = "-update_time"
  64    default_filter = dict( public_url="All", username="All", tags="All" )
  65    use_async = True
  66    columns = [
  67        grids.PublicURLColumn( "Name", key="name", model_class=model.StoredWorkflow, filterable="advanced" ),
  68        grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_class=model.StoredWorkflow, model_annotation_association_class=model.StoredWorkflowAnnotationAssociation, filterable="advanced" ),
  69        grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced", sortable=False ), 
  70        grids.CommunityTagsColumn( "Community Tags", "tags", model.StoredWorkflow, model.StoredWorkflowTagAssociation, filterable="advanced", grid_name="PublicWorkflowListGrid" ),
  71        grids.GridColumn( "Last Updated", key="update_time", format=time_ago )
  72    ]
  73    columns.append( 
  74        grids.MulticolFilterColumn(  
  75        "Search", 
  76        cols_to_filter=[ columns[0], columns[1], columns[2] ], 
  77        key="free-text-search", visible=False, filterable="standard" )
  78                )
  79    operations = []
  80    def build_initial_query( self, trans, **kwargs ):
  81        # Join so that searching stored_workflow.user makes sense.
  82        return trans.sa_session.query( self.model_class ).join( model.User.table )
  83    def apply_query_filter( self, trans, query, **kwargs ):
  84        # A public workflow is published, has a slug, and is not deleted.
  85        return query.filter( self.model_class.published==True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False )
  86
  87class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnnotations, UsesItemRatings ):
  88    stored_list_grid = StoredWorkflowListGrid()
  89    published_list_grid = StoredWorkflowAllPublishedGrid()
  90    
  91    @web.expose
  92    def index( self, trans ):
  93        return self.list( trans )
  94        
  95    @web.expose
  96    @web.require_login( "use Galaxy workflows" )
  97    def list_grid( self, trans, **kwargs ):
  98        """ List user's stored workflows. """
  99        status = message = None
 100        if 'operation' in kwargs:
 101            operation = kwargs['operation'].lower()
 102            if operation == "rename":
 103                return self.rename( trans, **kwargs )
 104            history_ids = util.listify( kwargs.get( 'id', [] ) )
 105            if operation == "sharing":
 106                return self.sharing( trans, id=history_ids )
 107        return self.stored_list_grid( trans, **kwargs )
 108                                   
 109    @web.expose
 110    @web.require_login( "use Galaxy workflows", use_panels=True )
 111    def list( self, trans ):
 112        """
 113        Render workflow main page (management of existing workflows)
 114        """
 115        user = trans.get_user()
 116        workflows = trans.sa_session.query( model.StoredWorkflow ) \
 117            .filter_by( user=user, deleted=False ) \
 118            .order_by( desc( model.StoredWorkflow.table.c.update_time ) ) \
 119            .all()
 120        shared_by_others = trans.sa_session \
 121            .query( model.StoredWorkflowUserShareAssociation ) \
 122            .filter_by( user=user ) \
 123            .join( 'stored_workflow' ) \
 124            .filter( model.StoredWorkflow.deleted == False ) \
 125            .order_by( desc( model.StoredWorkflow.update_time ) ) \
 126            .all()
 127            
 128        # Legacy issue: all shared workflows must have slugs.
 129        slug_set = False
 130        for workflow_assoc in shared_by_others:
 131            slug_set = self.create_item_slug( trans.sa_session, workflow_assoc.stored_workflow )
 132        if slug_set:
 133            trans.sa_session.flush()
 134
 135        return trans.fill_template( "workflow/list.mako",
 136                                    workflows = workflows,
 137                                    shared_by_others = shared_by_others )
 138    
 139    @web.expose
 140    @web.require_login( "use Galaxy workflows" )
 141    def list_for_run( self, trans ):
 142        """
 143        Render workflow list for analysis view (just allows running workflow
 144        or switching to management view)
 145        """
 146        user = trans.get_user()
 147        workflows = trans.sa_session.query( model.StoredWorkflow ) \
 148            .filter_by( user=user, deleted=False ) \
 149            .order_by( desc( model.StoredWorkflow.table.c.update_time ) ) \
 150            .all()
 151        shared_by_others = trans.sa_session \
 152            .query( model.StoredWorkflowUserShareAssociation ) \
 153            .filter_by( user=user ) \
 154            .filter( model.StoredWorkflow.deleted == False ) \
 155            .order_by( desc( model.StoredWorkflow.table.c.update_time ) ) \
 156            .all()
 157        return trans.fill_template( "workflow/list_for_run.mako",
 158                                    workflows = workflows,
 159                                    shared_by_others = shared_by_others )
 160                                    
 161    @web.expose
 162    def list_published( self, trans, **kwargs ):
 163        grid = self.published_list_grid( trans, **kwargs )
 164        if 'async' in kwargs:
 165            return grid
 166        else:
 167            # Render grid wrapped in panels
 168            return trans.fill_template( "workflow/list_published.mako", grid=grid )
 169                                    
 170    @web.expose
 171    def display_by_username_and_slug( self, trans, username, slug ):
 172        """ Display workflow based on a username and slug. """ 
 173        
 174        # Get workflow.
 175        session = trans.sa_session
 176        user = session.query( model.User ).filter_by( username=username ).first()
 177        stored_workflow = trans.sa_session.query( model.StoredWorkflow ).filter_by( user=user, slug=slug, deleted=False ).first()
 178        if stored_workflow is None:
 179           raise web.httpexceptions.HTTPNotFound()
 180        # Security check raises error if user cannot access workflow.
 181        self.security_check( trans.get_user(), stored_workflow, False, True)
 182        
 183        # Get data for workflow's steps.
 184        self.get_stored_workflow_steps( trans, stored_workflow )
 185        # Get annotations.
 186        stored_workflow.annotation = self.get_item_annotation_str( trans, stored_workflow.user, stored_workflow )
 187        for step in stored_workflow.latest_workflow.steps:
 188            step.annotation = self.get_item_annotation_str( trans, stored_workflow.user, step )
 189
 190        # Get rating data.
 191        user_item_rating = 0
 192        if trans.get_user():
 193            user_item_rating = self.get_user_item_rating( trans, trans.get_user(), stored_workflow )
 194            if user_item_rating:
 195                user_item_rating = user_item_rating.rating
 196            else:
 197                user_item_rating = 0
 198        ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans, stored_workflow )            
 199        return trans.fill_template_mako( "workflow/display.mako", item=stored_workflow, item_data=stored_workflow.latest_workflow.steps,
 200                                            user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
 201
 202    @web.expose
 203    def get_item_content_async( self, trans, id ):
 204        """ Returns item content in HTML format. """
 205        
 206        stored = self.get_stored_workflow( trans, id, False, True )
 207        if stored is None:
 208            raise web.httpexceptions.HTTPNotFound()
 209            
 210        # Get data for workflow's steps.
 211        self.get_stored_workflow_steps( trans, stored )
 212        # Get annotations.
 213        stored.annotation = self.get_item_annotation_str( trans, stored.user, stored )
 214        for step in stored.latest_workflow.steps:
 215            step.annotation = self.get_item_annotation_str( trans, stored.user, step )
 216        return trans.stream_template_mako( "/workflow/item_content.mako", item = stored, item_data = stored.latest_workflow.steps )
 217                              
 218    @web.expose
 219    @web.require_login( "use Galaxy workflows" )
 220    def share( self, trans, id, email="" ):
 221        msg = mtype = None
 222        # Load workflow from database
 223        stored = self.get_stored_workflow( trans, id )
 224        if email:
 225            other = trans.sa_session.query( model.User ) \
 226                                    .filter( and_( model.User.table.c.email==email,
 227                                                   model.User.table.c.deleted==False ) ) \
 228                                    .first()
 229            if not other:
 230                mtype = "error"
 231                msg = ( "User '%s' does not exist" % email )
 232            elif other == trans.get_user():
 233                mtype = "error"
 234                msg = ( "You cannot share a workflow with yourself" )
 235            elif trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \
 236                    .filter_by( user=other, stored_workflow=stored ).count() > 0:
 237                mtype = "error"
 238                msg = ( "Workflow already shared with '%s'" % email )
 239            else:
 240                share = model.StoredWorkflowUserShareAssociation()
 241                share.stored_workflow = stored
 242                share.user = other
 243                session = trans.sa_session
 244                session.add( share )
 245                self.create_item_slug( session, stored )
 246                session.flush()
 247                trans.set_message( "Workflow '%s' shared with user '%s'" % ( stored.name, other.email ) )
 248                return trans.response.send_redirect( url_for( controller='workflow', action='sharing', id=id ) )
 249        return trans.fill_template( "workflow/share.mako",
 250                                    message = msg,
 251                                    messagetype = mtype,
 252                                    stored=stored,
 253                                    email=email )
 254    
 255    @web.expose
 256    @web.require_login( "use Galaxy workflows" )
 257    def sharing( self, trans, id, **kwargs ):
 258        """ Handle workflow sharing. """
 259        
 260        # Get session and workflow.
 261        session = trans.sa_session
 262        stored = self.get_stored_workflow( trans, id )
 263        session.add( stored )
 264        
 265        # Do operation on workflow.
 266        if 'make_accessible_via_link' in kwargs:
 267            self._make_item_accessible( trans.sa_session, stored )
 268        elif 'make_accessible_and_publish' in kwargs:
 269            self._make_item_accessible( trans.sa_session, stored )
 270            stored.published = True
 271        elif 'publish' in kwargs:
 272            stored.published = True
 273        elif 'disable_link_access' in kwargs:
 274            stored.importable = False
 275        elif 'unpublish' in kwargs:
 276            stored.published = False
 277        elif 'disable_link_access_and_unpublish' in kwargs:
 278            stored.importable = stored.published = False
 279        elif 'unshare_user' in kwargs:
 280            user = session.query( model.User ).get( trans.security.decode_id( kwargs['unshare_user' ] ) )
 281            if not user:
 282                error( "User not found for provided id" )
 283            association = session.query( model.StoredWorkflowUserShareAssociation ) \
 284                                 .filter_by( user=user, stored_workflow=stored ).one()
 285            session.delete( association )
 286            
 287        # Legacy issue: workflows made accessible before recent updates may not have a slug. Create slug for any workflows that need them.
 288        if stored.importable and not stored.slug:
 289            self._make_item_accessible( trans.sa_session, stored )
 290            
 291        session.flush()
 292        
 293        return trans.fill_template( "/sharing_base.mako",
 294                                    item=stored )
 295
 296    @web.expose
 297    @web.require_login( "to import a workflow", use_panels=True )
 298    def imp( self, trans, id, **kwargs ):
 299        # Set referer message.
 300        referer = trans.request.referer
 301        if referer is not "":
 302            referer_message = "<a href='%s'>return to the previous page</a>" % referer
 303        else:
 304            referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
 305                    
 306        # Do import.
 307        session = trans.sa_session
 308        stored = self.get_stored_workflow( trans, id, check_ownership=False )
 309        if stored.importable == False:
 310            return trans.show_error_message( "The owner of this workflow has disabled imports via this link.<br>You can %s" % referer_message, use_panels=True )
 311        elif stored.user == trans.user:
 312            return trans.show_error_message( "You can't import this workflow because you own it.<br>You can %s" % referer_message, use_panels=True )
 313        elif stored.deleted:
 314            return trans.show_error_message( "You can't import this workflow because it has been deleted.<br>You can %s" % referer_message, use_panels=True )
 315        else:
 316            # Create imported workflow via copy.
 317            imported_stored = model.StoredWorkflow()
 318            imported_stored.name = "imported: " + stored.name
 319            imported_stored.latest_workflow = stored.latest_workflow
 320            imported_stored.user = trans.user
 321            # Save new workflow.
 322            session = trans.sa_session
 323            session.add( imported_stored )
 324            session.flush()
 325            
 326            # Redirect to load galaxy frames.
 327            return trans.show_ok_message(
 328                message="""Workflow "%s" has been imported. <br>You can <a href="%s">start using this workflow</a> or %s.""" 
 329                % ( stored.name, web.url_for( controller='workflow' ), referer_message ), use_panels=True )
 330            
 331    @web.expose
 332    @web.require_login( "use Galaxy workflows" )
 333    def edit_attributes( self, trans, id, **kwargs ):
 334        # Get workflow and do error checking.
 335        stored = self.get_stored_workflow( trans, id )
 336        if not stored:
 337            error( "You do not own this workflow or workflow ID is invalid." )
 338            
 339        # Update workflow attributes if new values submitted.
 340        if 'name' in kwargs:
 341            # Rename workflow.
 342            stored.name = kwargs[ 'name' ]
 343        if 'annotation' in kwargs:
 344            # Set workflow annotation; sanitize annotation before adding it.
 345            annotation = sanitize_html( kwargs[ 'annotation' ], 'utf-8', 'text/html' )
 346            self.add_item_annotation( trans, stored,  annotation )
 347        trans.sa_session.flush()
 348        
 349        return trans.fill_template( 'workflow/edit_attributes.mako', 
 350                                    stored=stored, 
 351                                    annotation=self.get_item_annotation_str( trans, trans.user, stored ) 
 352                                    )
 353    
 354    @web.expose
 355    @web.require_login( "use Galaxy workflows" )
 356    def rename( self, trans, id, new_name=None, **kwargs ):
 357        stored = self.get_stored_workflow( trans, id )
 358        if new_name is not None:
 359            stored.name = new_name
 360            trans.sa_session.flush()
 361            # For current workflows grid:
 362            trans.set_message ( "Workflow renamed to '%s'." % new_name )
 363            return self.list( trans )
 364            # For new workflows grid:
 365            #message = "Workflow renamed to '%s'." % new_name
 366            #return self.list_grid( trans, message=message, status='done' )
 367        else:
 368            return form( url_for( action='rename', id=trans.security.encode_id(stored.id) ), "Rename workflow", submit_text="Rename" ) \
 369                .add_text( "new_name", "Workflow Name", value=stored.name )
 370                
 371    @web.expose
 372    @web.require_login( "use Galaxy workflows" )
 373    def rename_async( self, trans, id, new_name=None, **kwargs ):
 374        stored = self.get_stored_workflow( trans, id )
 375        if new_name:
 376            stored.name = new_name
 377            trans.sa_session.flush()
 378            return stored.name
 379            
 380    @web.expose
 381    @web.require_login( "use Galaxy workflows" )
 382    def annotate_async( self, trans, id, new_annotation=None, **kwargs ):
 383        stored = self.get_stored_workflow( trans, id )
 384        if new_annotation:
 385            # Sanitize annotation before adding it.
 386            new_annotation = sanitize_html( new_annotation, 'utf-8', 'text/html' )
 387            self.add_item_annotation( trans, stored, new_annotation )
 388            trans.sa_session.flush()
 389            return new_annotation
 390            
 391    @web.expose
 392    @web.require_login( "rate items" )
 393    @web.json
 394    def rate_async( self, trans, id, rating ):
 395        """ Rate a workflow asynchronously and return updated community data. """
 396
 397        stored = self.get_stored_workflow( trans, id, check_ownership=False, check_accessible=True )
 398        if not stored:
 399            return trans.show_error_message( "The specified workflow does not exist." )
 400
 401        # Rate workflow.
 402        stored_rating = self.rate_item( trans, trans.get_user(), stored, rating )
 403
 404        return self.get_ave_item_rating_data( trans, stored )
 405            
 406    @web.expose
 407    @web.require_login( "use Galaxy workflows" )
 408    def set_accessible_async( self, trans, id=None, accessible=False ):
 409        """ Set workflow's importable attribute and slug. """
 410        stored = self.get_stored_workflow( trans, id )
 411
 412        # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
 413        importable = accessible in ['True', 'true', 't', 'T'];
 414        if stored and stored.importable != importable:
 415            if importable:
 416                self._make_item_accessible( trans.sa_session, stored )
 417            else:
 418                stored.importable = importable
 419            trans.sa_session.flush()
 420        return
 421        
 422    @web.expose
 423    @web.require_login( "modify Galaxy items" )
 424    def set_slug_async( self, trans, id, new_slug ):
 425        stored = self.get_stored_workflow( trans, id )
 426        if stored:
 427            stored.slug = new_slug
 428            trans.sa_session.flush()
 429            return stored.slug
 430            
 431    @web.expose
 432    def get_embed_html_async( self, trans, id ):
 433        """ Returns HTML for embedding a workflow in a page. """
 434
 435        # TODO: user should be able to embed any item he has access to. see display_by_username_and_slug for security code.
 436        stored = self.get_stored_workflow( trans, id )
 437        if stored:
 438            return "Embedded Workflow '%s'" % stored.name
 439        
 440    @web.expose
 441    @web.json
 442    @web.require_login( "use Galaxy workflows" )
 443    def get_name_and_link_async( self, trans, id=None ):
 444        """ Returns workflow's name and link. """
 445        stored = self.get_stored_workflow( trans, id )
 446
 447        if self.create_item_slug( trans.sa_session, stored ):
 448            trans.sa_session.flush()
 449        return_dict = { "name" : stored.name, "link" : url_for( action="display_by_username_and_slug", username=stored.user.username, slug=stored.slug ) }
 450        return return_dict
 451    
 452    @web.expose
 453    @web.require_login( "use Galaxy workflows" )
 454    def clone( self, trans, id ):
 455        stored = self.get_stored_workflow( trans, id, check_ownership=False )
 456        user = trans.get_user()
 457        if stored.user == user:
 458            owner = True
 459        else:
 460            if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \
 461                    .filter_by( user=user, stored_workflow=stored ).count() == 0:
 462                error( "Workflow is not owned by or shared with current user" )
 463            owner = False
 464        new_stored = model.StoredWorkflow()
 465        new_stored.name = "Clone of '%s'" % stored.name
 466        new_stored.latest_workflow = stored.latest_workflow
 467        if not owner:
 468            new_stored.name += " shared by '%s'" % stored.user.email
 469        new_stored.user = user
 470        # Persist
 471        session = trans.sa_session
 472        session.add( new_stored )
 473        session.flush()
 474        # Display the management page
 475        trans.set_message( 'Clone created with name "%s"' % new_stored.name )
 476        return self.list( trans )
 477    
 478    @web.expose
 479    @web.require_login( "create workflows" )
 480    def create( self, trans, workflow_name=None, workflow_annotation="" ):
 481        """
 482        Create a new stored workflow with name `workflow_name`.
 483        """
 484        user = trans.get_user()
 485        if workflow_name is not None:
 486            # Create the new stored workflow
 487            stored_workflow = model.StoredWorkflow()
 488            stored_workflow.name = workflow_name
 489            stored_workflow.user = user
 490            # And the first (empty) workflow revision
 491            workflow = model.Workflow()
 492            workflow.name = workflow_name
 493            workflow.stored_workflow = stored_workflow
 494            stored_workflow.latest_workflow = workflow
 495            # Add annotation.
 496            workflow_annotation = sanitize_html( workflow_annotation, 'utf-8', 'text/html' )
 497            self.add_item_annotation( trans, stored_workflow, workflow_annotation )
 498            # Persist
 499            session = trans.sa_session
 500            session.add( stored_workflow )
 501            session.flush()
 502            # Display the management page
 503            trans.set_message( "Workflow '%s' created" % stored_workflow.name )
 504            return self.list( trans )
 505        else:
 506            return form( url_for(), "Create New Workflow", submit_text="Create" ) \
 507                    .add_text( "workflow_name", "Workflow Name", value="Unnamed workflow" ) \
 508                    .add_text( "workflow_annotation", "Workflow Annotation", value="", help="A description of the workflow; annotation is shown alongside shared or published workflows." )
 509    
 510    @web.expose
 511    def delete( self, trans, id=None ):
 512        """
 513        Mark a workflow as deleted
 514        """        
 515        # Load workflow from database
 516        stored = self.get_stored_workflow( trans, id )
 517        # Marke as deleted and save
 518        stored.deleted = True
 519        trans.sa_session.add( stored )
 520        trans.sa_session.flush()
 521        # Display the management page
 522        trans.set_message( "Workflow '%s' deleted" % stored.name )
 523        return self.list( trans )
 524        
 525    @web.expose
 526    @web.require_login( "edit workflows" )
 527    def editor( self, trans, id=None ):
 528        """
 529        Render the main workflow editor interface. The canvas is embedded as
 530        an iframe (necessary for scrolling to work properly), which is
 531        rendered by `editor_canvas`.
 532        """
 533        if not id:
 534            error( "Invalid workflow id" )
 535        stored = self.get_stored_workflow( trans, id )
 536        return trans.fill_template( "workflow/editor.mako", stored=stored, annotation=self.get_item_annotation_str( trans, trans.user, stored ) )
 537        
 538    @web.json
 539    def editor_form_post( self, trans, type='tool', tool_id=None, annotation=None, **incoming ):
 540        """
 541        Accepts a tool state and incoming values, and generates a new tool
 542        form and some additional information, packed into a json dictionary.
 543        This is used for the form shown in the right pane when a node
 544        is selected.
 545        """
 546        
 547        trans.workflow_building_mode = True
 548        module = module_factory.from_dict( trans, {
 549            'type': type,
 550            'tool_id': tool_id,
 551            'tool_state': incoming.pop("tool_state")
 552        } )
 553        module.update_state( incoming )
 554        
 555        if type=='tool':
 556            return {
 557                'tool_state': module.get_state(),
 558                'data_inputs': module.get_data_inputs(),
 559                'data_outputs': module.get_data_outputs(),
 560                'tool_errors': module.get_errors(),
 561                'form_html': module.get_config_form(),
 562                'annotation': annotation,
 563                'post_job_actions': module.get_post_job_actions()
 564            }
 565        else:
 566            return {
 567                'tool_state': module.get_state(),
 568                'data_inputs': module.get_data_inputs(),
 569                'data_outputs': module.get_data_outputs(),
 570                'tool_errors': module.get_errors(),
 571                'form_html': module.get_config_form(),
 572                'annotation': annotation
 573            }
 574        
 575    @web.json
 576    def get_new_module_info( self, trans, type, **kwargs ):
 577        """
 578        Get the info for a new instance of a module initialized with default
 579        parameters (any keyword arguments will be passed along to the module).
 580        Result includes data inputs and outputs, html representation
 581        of the initial form, and the initial tool state (with default values).
 582        This is called asynchronously whenever a new node is added.
 583        """
 584        trans.workflow_building_mode = True
 585        module = module_factory.new( trans, type, **kwargs )
 586        return {
 587            'type': module.type,
 588            'name':  module.get_name(),
 589            'tool_id': module.get_tool_id(),
 590            'tool_state': module.get_state(),
 591            'tooltip': module.get_tooltip(),
 592            'data_inputs': module.get_data_inputs(),
 593            'data_outputs': module.get_data_outputs(),
 594            'form_html': module.get_config_form(),
 595            'annotation': ""
 596        }
 597
 598    @web.json
 599    def load_workflow( self, trans, id ):
 600        """
 601        Get the latest Workflow for the StoredWorkflow identified by `id` and
 602        encode it as a json string that can be read by the workflow editor
 603        web interface.
 604        """
 605        user = trans.get_user()
 606        id = trans.security.decode_id( id )
 607        trans.workflow_building_mode = True
 608        # Load encoded workflow from database
 609        stored = trans.sa_session.query( model.StoredWorkflow ).get( id )
 610        assert stored.user == user
 611        workflow = stored.latest_workflow
 612        # Pack workflow data into a dictionary and return
 613        data = {}
 614        data['name'] = workflow.name
 615        data['steps'] = {}
 616        data['upgrade_messages'] = {}
 617        # For each step, rebuild the form and encode the state
 618        for step in workflow.steps:
 619            # Load from database representation
 620            module = module_factory.from_workflow_step( trans, step )
 621            # Fix any missing parameters
 622            upgrade_message = module.check_and_update_state()
 623            if upgrade_message:
 624                # FIXME: Frontend should be able to handle workflow messages
 625                #        as a dictionary not just the values
 626                data['upgrade_messages'][step.order_index] = upgrade_message.values()
 627            # Get user annotation.
 628            step_annotation = self.get_item_annotation_obj ( trans, trans.user, step )
 629            annotation_str = ""
 630            if step_annotation:
 631                annotation_str = step_annotation.annotation
 632            # Pack attributes into plain dictionary
 633            step_dict = {
 634                'id': step.order_index,
 635                'type': module.type,
 636                'tool_id': module.get_tool_id(),
 637                'name': module.get_name(),
 638                'tool_state': module.get_state(),
 639                'tooltip': module.get_tooltip(),
 640                'tool_errors': module.get_errors(),
 641                'data_inputs': module.get_data_inputs(),
 642                'data_outputs': module.get_data_outputs(),
 643                'form_html': module.get_config_form(),
 644                'annotation' : annotation_str
 645            }
 646            # Connections
 647            input_connections = step.input_connections
 648            if step.type is None or step.type == 'tool':
 649                # Determine full (prefixed) names of valid input datasets
 650                data_input_names = {}
 651                def callback( input, value, prefixed_name, prefixed_label ):
 652                    if isinstance( input, DataToolParameter ):
 653                        data_input_names[ prefixed_name ] = True
 654                visit_input_values( module.tool.inputs, module.state.inputs, callback )
 655                # Filter
 656                # FIXME: this removes connection without displaying a message currently!
 657                input_connections = [ conn for conn in input_connections if conn.input_name in data_input_names ]
 658                # post_job_actions
 659                pja_dict = {}
 660                for pja in step.post_job_actions:
 661                    pja_dict[pja.action_type+pja.output_name] = dict(action_type = pja.action_type, 
 662                                            output_name = pja.output_name,
 663                                            action_arguments = pja.action_arguments)
 664                step_dict['post_job_actions'] = pja_dict
 665            # Encode input connections as dictionary
 666            input_conn_dict = {}
 667            for conn in input_connections:
 668                input_conn_dict[ conn.input_name ] = \
 669                    dict( id=conn.output_step.order_index, output_name=conn.output_name )
 670            step_dict['input_connections'] = input_conn_dict
 671            # Position
 672            step_dict['position'] = step.position
 673            # Add to return value
 674            data['steps'][step.order_index] = step_dict
 675        return data
 676
 677    @web.json
 678    def save_workflow( self, trans, id, workflow_data ):
 679        """
 680        Save the workflow described by `workflow_data` with id `id`.
 681        """
 682        # Get the stored workflow
 683        stored = self.get_stored_workflow( trans, id )
 684        # Put parameters in workflow mode
 685        trans.workflow_building_mode = True
 686        # Convert incoming workflow data from json
 687        data = simplejson.loads( workflow_data )
 688        # Create new workflow from incoming data
 689        workflow = model.Workflow()
 690        # Just keep the last name (user can rename later)
 691        workflow.name = stored.name
 692        # Assume no errors until we find a step that has some
 693        workflow.has_errors = False
 694        # Create each step
 695        steps = []
 696        # The editor will provide ids for each step that we don't need to save,
 697        # but do need to use to make connections
 698        steps_by_external_id = {}
 699        # First pass to build step objects and populate basic values
 700        for key, step_dict in data['steps'].iteritems():
 701            # Create the model class for the step
 702            step = model.WorkflowStep()
 703            steps.append( step )
 704            steps_by_external_id[ step_dict['id' ] ] = step
 705            # FIXME: Position should be handled inside module
 706            step.position = step_dict['position']
 707            module = module_factory.from_dict( trans, step_dict )
 708            module.save_to_step( step )
 709            if step.tool_errors:
 710                workflow.has_errors = True
 711            # Stick this in the step temporarily
 712            step.temp_input_connections = step_dict['input_connections']
 713            # Save step annotation.
 714            annotation = step_dict[ 'annotation' ]
 715            if annotation:
 716                annotation = sanitize_html( annotation, 'utf-8', 'text/html' )
 717                self.add_item_annotation( trans, step, annotation )
 718        # Second pass to deal with connections between steps
 719        for step in steps:
 720            # Input connections
 721            for input_name, conn_dict in step.temp_input_connections.iteritems():
 722                if conn_dict:
 723                    conn = model.WorkflowStepConnection()
 724                    conn.input_step = step
 725                    conn.input_name = input_name
 726                    conn.output_name = conn_dict['output_name']
 727                    conn.output_step = steps_by_external_id[ conn_dict['id'] ]
 728            del step.temp_input_connections
 729        # Order the steps if possible
 730        attach_ordered_steps( workflow, steps )
 731        # Connect up
 732        workflow.stored_workflow = stored
 733        stored.latest_workflow = workflow
 734        # Persist
 735        trans.sa_session.flush()
 736        # Return something informative
 737        errors = []
 738        if workflow.has_errors:
 739            errors.append( "Some steps in this workflow have validation errors" )
 740        if workflow.has_cycles:
 741            errors.append( "This workflow contains cycles" )
 742        if errors:
 743            rval = dict( message="Workflow saved, but will not be runnable due to the following errors",
 744                         errors=errors )
 745        else:
 746            rval = dict( message="Workflow saved" )
 747        rval['name'] = workflow.name
 748        return rval
 749
 750    @web.json_pretty
 751    def export_workflow( self, trans, id ):
 752        """
 753        Get the latest Workflow for the StoredWorkflow identified by `id` and
 754        encode it as a json string that can be imported back into Galaxy
 755        
 756        This has slightly different information than the above. In particular,
 757        it does not attempt to decode forms and build UIs, it just stores
 758        the raw state.
 759        """
 760        user = trans.get_user()
 761        id = trans.security.decode_id( id )
 762        trans.workflow_building_mode = True
 763        # Load encoded workflow from database
 764        stored = trans.sa_session.query( model.StoredWorkflow ).get( id )
 765        self.security_check( trans.get_user(), stored, False, True )
 766        workflow = stored.latest_workflow
 767        # Pack workflow data into a dictionary and return
 768        data = {}
 769        data['name'] = workflow.name
 770        data['steps'] = {}
 771        # For each step, rebuild the form and encode the state
 772        for step in workflow.steps:
 773            # Load from database representation
 774            module = module_factory.from_workflow_step( trans, step )
 775            # Get user annotation.
 776            step_annotation = self.get_item_annotation_obj( trans, trans.user, step )
 777            annotation_str = ""
 778            if step_annotation:
 779                annotation_str = step_annotation.annotation
 780            # Pack attributes into plain dictionary
 781            step_dict = {
 782                'id': step.order_index,
 783                'type': module.type,
 784                'tool_id': module.get_tool_id(),
 785                'name': module.get_name(),
 786                'tool_state': module.get_state( secure=False ),
 787                'tool_errors': module.get_errors(),
 788                ## 'data_inputs': module.get_data_inputs(),
 789                ## 'data_outputs': module.get_data_outputs(),
 790                'annotation' : annotation_str
 791            }
 792            # Connections
 793            input_connections = step.input_connections
 794            if step.type is None or step.type == 'tool':
 795                # Determine full (prefixed) names of valid input datasets
 796                data_input_names = {}
 797                def callback( input, value, prefixed_name, prefixed_label ):
 798                    if isinstance( input, DataToolParameter ):
 799                        data_input_names[ prefixed_name ] = True
 800                visit_input_values( module.tool.inputs, module.state.inputs, callback )
 801                # Filter
 802                # FIXME: this removes connection without displaying a message currently!
 803                input_connections = [ conn for conn in input_connections if conn.input_name in data_input_names ]
 804            # Encode input connections as dictionary
 805            input_conn_dict = {}
 806            for conn in input_connections:
 807                input_conn_dict[ conn.input_name ] = \
 808                    dict( id=conn.output_step.order_index, output_name=conn.output_name )
 809            step_dict['input_connections'] = input_conn_dict
 810            # Position
 811            step_dict['position'] = step.position
 812            # Add to return value
 813            data['steps'][step.order_index] = step_dict
 814        return data
 815
 816    @web.expose
 817    def import_workflow( self, trans, workflow_text=None, url=None ):
 818        if workflow_text is None and url is None:
 819            return form( url_for(), "Import Workflow", submit_text="Import" ) \
 820                    .add_text( "url", "URL to load workflow from", "" ) \
 821                    .add_input( "textarea", "Encoded workflow (as generated by export workflow)", "workflow_text", "" )
 822        if url:
 823            # Load workflow from external URL
 824            # NOTE: blocks the web thread. 
 825            try:
 826                workflow_data = urllib2.urlopen( url ).read()
 827            except Exception, e:
 828                return trans.show_error_message( "Failed to open URL %s<br><br>Message: %s" % ( url, str( e ) ) )
 829        else:
 830            workflow_data = workflow_text
 831        # Convert incoming workflow data from json
 832        try:
 833            data = simplejson.loads( workflow_data )
 834        except Exception, e:
 835            return trans.show_error_message( "Data at '%s' does not appear to be a Galaxy workflow<br><br>Message: %s" % ( url, str( e ) ) )
 836        # Put parameters in workflow mode
 837        trans.workflow_building_mode = True
 838        # Create new workflow from incoming data
 839        workflow = model.Workflow()
 840        # Just keep the last name (user can rename later)
 841        workflow.name = data['name']
 842        # Assume no errors until we find a step that has some
 843        workflow.has_errors = False
 844        # Create each step
 845        steps = []
 846        # The editor will provide ids for each step that we don't need to save,
 847        # but do need to use to make connections
 848        steps_by_external_id = {}
 849        # First pass to build step objects and populate basic values
 850        for key, step_dict in data['steps'].iteritems():
 851            # Create the model class for the step
 852            step = model.WorkflowStep()
 853            steps.append( step )
 854            steps_by_external_id[ step_dict['id' ] ] = step
 855            # FIXME: Position should be handled inside module
 856            step.position = step_dict['position']
 857            module = module_factory.from_dict( trans, step_dict, secure=False )
 858            module.save_to_step( step )
 859            if step.tool_errors:
 860                workflow.has_errors = True
 861            # Stick this in the step temporarily
 862            step.temp_input_connections = step_dict['input_connections']
 863            # Save step annotation.
 864            annotation = step_dict[ 'annotation' ]
 865            if annotation:
 866                annotation = sanitize_html( annotation, 'utf-8', 'text/html' )
 867                self.add_item_annotation( trans, step, annotation )
 868        # Second pass to deal with connections between steps
 869        for step in steps:
 870            # Input connections
 871            for input_name, conn_dict in step.temp_input_connections.iteritems():
 872                if conn_dict:
 873                    conn = model.WorkflowStepConnection()
 874                    conn.input_step = step
 875                    conn.input_name = input_name
 876                    conn.output_name = conn_dict['output_name']
 877                    conn.output_step = steps_by_external_id[ conn_dict['id'] ]
 878            del step.temp_input_connections
 879        # Order the steps if possible
 880        attach_ordered_steps( workflow, steps )
 881        # Connect up
 882        stored = model.StoredWorkflow()
 883        stored.name = workflow.name
 884        workflow.stored_workflow = stored
 885        stored.latest_workflow = workflow
 886        stored.user = trans.user
 887        # Persist
 888        trans.sa_session.add( stored )
 889        trans.sa_session.flush()
 890        # Return something informative
 891        errors = []
 892        if workflow.has_errors:
 893            return trans.show_warn_message( "Imported, but some steps in this workflow have validation errors" )
 894        if workflow.has_cycles:
 895            return trans.show_warn_message( "Imported, but this workflow contains cycles" )
 896        else:
 897            return trans.show_message( "Workflow '%s' imported" % workflow.name )
 898        
 899    @web.json
 900    def get_datatypes( self, trans ):
 901        ext_to_class_name = dict()
 902        classes = []
 903        for k, v in trans.app.datatypes_registry.datatypes_by_extension.iteritems():
 904            c = v.__class__
 905            ext_to_class_name[k] = c.__module__ + "." + c.__name__
 906            classes.append( c )
 907        class_to_classes = dict()
 908        def visit_bases( types, cls ):
 909            for base in cls.__bases__:
 910                if issubclass( base, Data ):
 911                    types.add( base.__module__ + "." + base.__name__ )
 912                visit_bases( types, base )
 913        for c in classes:      
 914            n =  c.__module__ + "." + c.__name__
 915            types = set( [ n ] )
 916            visit_bases( types, c )
 917            class_to_classes[ n ] = dict( ( t, True ) for t in types )
 918        return dict( ext_to_class_name=ext_to_class_name, class_to_classes=class_to_classes )
 919    
 920    @web.expose
 921    def build_from_current_history( self, trans, job_ids=None, dataset_ids=None, workflow_name=None ):
 922        user = trans.get_user()
 923        history = trans.get_history()
 924        if not user:
 925            return trans.show_error_message( "Must be logged in to create workflows" )
 926        if ( job_ids is None and dataset_ids is None ) or workflow_name is None:
 927            jobs, warnings = get_job_dict( trans )
 928            # Render
 929            return trans.fill_template(
 930                        "workflow/build_from_current_history.mako", 
 931                        jobs=jobs,
 932                        warnings=warnings,
 933                        history=history )
 934        else:
 935            # Ensure job_ids and dataset_ids are lists (possibly empty)
 936            if job_ids is None:
 937                job_ids = []
 938            elif type( job_ids ) is not list:
 939                job_ids = [ job_ids ]
 940            if dataset_ids is None:
 941                dataset_ids = []
 942            elif type( dataset_ids ) is not list:
 943                dataset_ids = [ dataset_ids ]
 944            # Convert both sets of ids to integers
 945            job_ids = [ int( id ) for id in job_ids ]
 946            dataset_ids = [ int( id ) for id in dataset_ids ]
 947            # Find each job, for security we (implicately) check that they are
 948            # associated witha job in the current history. 
 949            jobs, warnings = get_job_dict( trans )
 950            jobs_by_id = dict( ( job.id, job ) for job in jobs.keys() )
 951            steps = []
 952            steps_by_job_id = {}
 953            hid_to_output_pair = {}
 954            # Input dataset steps
 955            for hid in dataset_ids:
 956                step = model.WorkflowStep()
 957                step.type = 'data_input'
 958                hid_to_output_pair[ hid ] = ( step, 'output' )
 959                steps.append( step )
 960            # Tool steps
 961            for job_id in job_ids:
 962                assert job_id in jobs_by_id, "Attempt to create workflow with job not connected to current history"
 963                job = jobs_by_id[ job_id ]
 964                tool = trans.app.toolbox.tools_by_id[ job.tool_id ]
 965                param_values = job.get_param_values( trans.app )
 966                associations = cleanup_param_values( tool.inputs, param_values )
 967                step = model.WorkflowStep()
 968                step.type = 'tool'
 969                step.tool_id = job.tool_id
 970                step.tool_inputs = tool.params_to_strings( param_values, trans.app )
 971                # NOTE: We shouldn't need to do two passes here since only
 972                #       an earlier job can be used as an input to a later
 973                #       job.
 974                for other_hid, input_name in associations:
 975                    if other_hid in hid_to_output_pair:
 976                        other_step, other_name = hid_to_output_pair[ other_hid ]
 977                        conn = model.WorkflowStepConnection()
 978                        conn.input_step = step
 979                        conn.input_name = input_name
 980                        # Should always be connected to an earlier step
 981                        conn.output_step = other_step
 982                        conn.output_name = other_name
 983                steps.append( step )
 984                steps_by_job_id[ job_id ] = step                
 985                # Store created dataset hids
 986                for assoc in job.output_datasets:
 987                    hid_to_output_pair[ assoc.dataset.hid ] = ( step, assoc.name )
 988            # Workflow to populate
 989            workflow = model.Workflow()
 990            workflow.name = workflow_name
 991            # Order the steps if possible
 992            attach_ordered_steps( workflow, steps )
 993            # And let's try to set up some reasonable locations on the canvas
 994            # (these are pretty arbitrary values)
 995            levorder = order_workflow_steps_with_levels( steps )
 996            base_pos = 10
 997            for i, steps_at_level in enumerate( levorder ):
 998                for j, index in enumerate( steps_at_level ):
 999                    step = steps[ index ]
1000                    step.position = dict( top = ( base_pos + 120 * j ),
1001                                          left = ( base_pos + 220 * i ) )
1002            # Store it
1003            stored = model.StoredWorkflow()
1004            stored.user = user
1005            stored.name = workflow_name
1006            workflow.stored_workflow = stored
1007            stored.latest_workflow = workflow
1008            trans.sa_session.add( stored )
1009            trans.sa_session.flush()
1010            # Index page with message
1011            return trans.show_message( "Workflow '%s' created from current history." % workflow_name )
1012            ## return trans.show_ok_message( "<p>Workflow '%s' created.</p><p><a target='_top' href='%s'>Click to load in workflow editor</a></p>"
1013            ##     % ( workflow_name, web.url_for( action='editor', id=trans.security.encode_id(stored.id) ) ) )       
1014        
1015    @web.expose
1016    def run( self, trans, id, check_user=True, **kwargs ):
1017        stored = self.get_stored_workflow( trans, id, check_ownership=False )
1018        if check_user:
1019            user = trans.get_user()
1020            if stored.user != user:
1021                if trans.sa_session.query( model.StoredWorkflowUserShareAssociation ) \
1022                        .filter_by( user=user, stored_workflow=stored ).count() == 0:
1023                    error( "Workflow is not owned by or shared with current user" )
1024        # Get the latest revision
1025        workflow = stored.latest_workflow
1026        # It is possible for a workflow to have 0 steps
1027        if len( workflow.steps ) == 0:
1028            error( "Workflow cannot be run because it does not have any steps" )
1029        #workflow = Workflow.from_simple( simplejson.loads( stored.encoded_value ), trans.app )
1030        if workflow.has_cycles:
1031            error( "Workflow cannot be run because it contains cycles" )
1032        if workflow.has_errors:
1033            error( "Workflow cannot be run because of validation errors in some steps" )
1034        # Build the state for each step
1035        errors = {}
1036        has_upgrade_messages = False
1037        has_errors = False
1038        if kwargs:
1039            # If kwargs were provided, the states for each step should have
1040            # been POSTed
1041            for step in workflow.steps:
1042                step.upgrade_messages = {}
1043                # Connections by input name
1044                step.input_connections_by_name = \
1045                    dict( ( conn.input_name, conn ) for conn in step.input_connections ) 
1046                # Extract just the arguments for this step by prefix
1047                p = "%s|" % step.id
1048                l = len(p)
1049                

Large files files are truncated, but you can click here to view the full file