details: http://www.bx.psu.edu/hg/galaxy/rev/fb48e02e1610 changeset: 3387:fb48e02e1610 user: rerla@localhost date: Fri Feb 12 17:10:52 2010 -0500 description: merge diffstat: lib/galaxy/model/mapping.py | 15 + lib/galaxy/web/base/controller.py | 141 ++++++++++- lib/galaxy/web/controllers/dataset.py | 64 ++-- lib/galaxy/web/controllers/history.py | 37 +- lib/galaxy/web/controllers/page.py | 147 +++++++++-- lib/galaxy/web/controllers/requests_admin.py | 46 ++- lib/galaxy/web/controllers/root.py | 13 +- lib/galaxy/web/controllers/workflow.py | 96 +++---- scripts/galaxy_messaging/server/data_transfer.py | 4 +- static/images/fugue/pencil.png | 0 static/images/fugue/sticky-note-text.png | 0 static/images/fugue/tag-label.png | 0 static/images/fugue/toggle-expand.png | 0 static/images/fugue/toggle.png | 0 static/images/pencil.png | 0 static/images/sticky-note-text.png | 0 static/images/tag-label.png | 0 static/june_2007_style/base.css.tmpl | 12 +- static/june_2007_style/blue/base.css | 6 +- static/june_2007_style/blue/embed_item.css | 4 + static/june_2007_style/embed_item.css.tmpl | 24 ++ templates/dataset/display.mako | 3 +- templates/dataset/embed.mako | 13 + templates/dataset/item_content.mako | 3 + templates/display_base.mako | 28 +- templates/display_common.mako | 24 ++ templates/embed_base.mako | 38 +++ templates/history/embed.mako | 15 + templates/history/item_content.mako | 3 + templates/page/display.mako | 58 ++++- templates/page/editor.mako | 265 +++++++++++++++++++--- templates/page/wymiframe.mako | 2 +- templates/sharing_base.mako | 2 +- templates/workflow/display.mako | 1 - templates/workflow/embed.mako | 15 + templates/workflow/item_content.mako | 3 + 36 files changed, 832 insertions(+), 250 deletions(-) diffs (1789 lines): diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/model/mapping.py --- a/lib/galaxy/model/mapping.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/model/mapping.py Fri Feb 12 17:10:52 2010 -0500 @@ -980,6 +980,11 @@ tags=relation( HistoryTagAssociation, order_by=HistoryTagAssociation.table.c.id, backref="histories" ), annotations=relation( HistoryAnnotationAssociation, order_by=HistoryAnnotationAssociation.table.c.id, backref="histories" ) ) ) + +# Set up proxy so that +# History.users_shared_with_dot_users +# returns a list of User objects. +History.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) assign_mapper( context, HistoryUserShareAssociation, HistoryUserShareAssociation.table, properties=dict( user=relation( User, backref='histories_shared_by_others' ), @@ -1271,6 +1276,11 @@ tags=relation( StoredWorkflowTagAssociation, order_by=StoredWorkflowTagAssociation.table.c.id, backref="stored_workflows" ), annotations=relation( StoredWorkflowAnnotationAssociation, order_by=StoredWorkflowAnnotationAssociation.table.c.id, backref="stored_workflows" ) ) ) + +# Set up proxy so that +# StoredWorkflow.users_shared_with_dot_users +# returns a list of User objects. +StoredWorkflow.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) assign_mapper( context, StoredWorkflowUserShareAssociation, StoredWorkflowUserShareAssociation.table, properties=dict( user=relation( User, backref='workflows_shared_by_others' ), @@ -1296,6 +1306,11 @@ tags=relation(PageTagAssociation, order_by=PageTagAssociation.table.c.id, backref="pages") ) ) +# Set up proxy so that +# Page.users_shared_with_dot_users +# returns a list of User objects. +Page.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) + assign_mapper( context, PageUserShareAssociation, PageUserShareAssociation.table, properties=dict( user=relation( User, backref='pages_shared_by_others' ), page=relation( Page, backref='users_shared_with' ) diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/base/controller.py Fri Feb 12 17:10:52 2010 -0500 @@ -8,7 +8,7 @@ from galaxy import config, tools, web, model, util from galaxy.web import error, form, url_for from galaxy.model.orm import * -from galaxy.web.framework.helpers import grids +from galaxy.workflow.modules import * from Cheetah.Template import Template @@ -27,22 +27,6 @@ """Returns the application toolbox""" return self.app.toolbox - def get_history( self, trans, id, check_ownership=True ): - """Get a History from the database by id, verifying ownership.""" - # Load history from database - id = trans.security.decode_id( id ) - history = trans.sa_session.query( model.History ).get( id ) - if not history: - err+msg( "History not found" ) - if check_ownership: - # Verify ownership - user = trans.get_user() - if not user: - error( "Must be logged in to manage histories" ) - if history.user != user: - error( "History is not owned by current user" ) - return history - def get_class( self, class_name ): """ Returns the class object that a string denotes. Without this method, we'd have to do eval(<class_name>). """ if class_name == 'History': @@ -107,9 +91,124 @@ return True Root = BaseController + +class SharableItemSecurity: + """ Mixin for handling security for sharable items. """ + + def security_check( self, user, item, check_ownership=False, check_accessible=False ): + """ Security checks for an item: checks if (a) user owns item or (b) item is accessible to user. """ + if check_ownership: + # Verify ownership. + if not user: + error( "Must be logged in to manage Galaxy items" ) + if item.user != user: + error( "%s is not owned by current user" % item.__class__.__name__ ) + if check_accessible: + # Verify accessible. + if ( item.user != user ) and ( not item.importable ) and ( user not in item.users_shared_with_dot_users ): + raise "hi" + error( "%s is not accessible by current user" % item.__class__.__name__ ) + return item + +class UsesHistoryDatasetAssociation: + """ Mixin for controllers that use HistoryDatasetAssociation objects. """ + + def get_dataset( self, trans, dataset_id, check_accessible=True ): + """ Get an HDA object by id. """ + # DEPRECATION: We still support unencoded ids for backward compatibility + try: + dataset_id = int( dataset_id ) + except ValueError: + dataset_id = trans.security.decode_id( dataset_id ) + data = trans.sa_session.query( model.HistoryDatasetAssociation ).get( dataset_id ) + if not data: + raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid dataset id: %s." % str( dataset_id ) ) + if check_accessible: + current_user_roles = trans.get_current_user_roles() + if trans.app.security_agent.can_access_dataset( current_user_roles, data.dataset ): + if data.state == trans.model.Dataset.states.UPLOAD: + return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." ) + else: + error( "You are not allowed to access this dataset" ) + return data + + def get_data( self, dataset, preview=True ): + """ Gets a dataset's data. """ + # Get data from file, truncating if necessary. + truncated = False + dataset_data = None + if os.path.exists( dataset.file_name ): + max_peek_size = 1000000 # 1 MB + if preview and os.stat( dataset.file_name ).st_size > max_peek_size: + dataset_data = open( dataset.file_name ).read(max_peek_size) + truncated = True + else: + dataset_data = open( dataset.file_name ).read(max_peek_size) + truncated = False + return truncated, dataset_data + +class UsesStoredWorkflow( SharableItemSecurity ): + """ Mixin for controllers that use StoredWorkflow objects. """ + + def get_stored_workflow( self, trans, id, check_ownership=True, check_accessible=False ): + """ Get a StoredWorkflow from the database by id, verifying ownership. """ + # Load workflow from database + id = trans.security.decode_id( id ) + stored = trans.sa_session.query( model.StoredWorkflow ).get( id ) + if not stored: + error( "Workflow not found" ) + else: + return self.security_check( trans.get_user(), stored, check_ownership, check_accessible ) + + def get_stored_workflow_steps( self, trans, stored_workflow ): + """ Restores states for a stored workflow's steps. """ + for step in stored_workflow.latest_workflow.steps: + if step.type == 'tool' or step.type is None: + # Restore the tool state for the step + module = module_factory.from_workflow_step( trans, step ) + # Any connected input needs to have value DummyDataset (these + # are not persisted so we need to do it every time) + module.add_dummy_datasets( connections=step.input_connections ) + # Store state with the step + step.module = module + step.state = module.state + # Error dict + if step.tool_errors: + errors[step.id] = step.tool_errors + else: + ## Non-tool specific stuff? + step.module = module_factory.from_workflow_step( trans, step ) + step.state = step.module.get_runtime_state() + # Connections by input name + step.input_connections_by_name = dict( ( conn.input_name, conn ) for conn in step.input_connections ) + +class UsesHistory( SharableItemSecurity ): + """ Mixin for controllers that use History objects. """ + + def get_history( self, trans, id, check_ownership=True, check_accessible=False ): + """Get a History from the database by id, verifying ownership.""" + # Load history from database + id = trans.security.decode_id( id ) + history = trans.sa_session.query( model.History ).get( id ) + if not history: + err+msg( "History not found" ) + else: + return self.security_check( trans.get_user(), history, check_ownership, check_accessible ) + + def get_history_datasets( self, trans, history, show_deleted=False ): + """ Returns history's datasets. """ + query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ + .filter( model.HistoryDatasetAssociation.history == history ) \ + .options( eagerload( "children" ) ) \ + .join( "dataset" ).filter( model.Dataset.purged == False ) \ + .options( eagerload_all( "dataset.actions" ) ) \ + .order_by( model.HistoryDatasetAssociation.hid ) + if not show_deleted: + query = query.filter( model.HistoryDatasetAssociation.deleted == False ) + return query.all() class Sharable: - """ Mixin for a controller that manages and item that can be shared. """ + """ Mixin for a controller that manages an item that can be shared. """ # Implemented methods. @web.expose @@ -146,6 +245,12 @@ """ Returns item's name and link. """ pass + @web.expose + @web.require_login("get item content asynchronously") + def get_item_content_async( self, trans, id ): + """ Returns item content in HTML format. """ + pass + # Helper methods. def _make_item_accessible( self, sa_session, item ): diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/controllers/dataset.py --- a/lib/galaxy/web/controllers/dataset.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/controllers/dataset.py Fri Feb 12 17:10:52 2010 -0500 @@ -110,7 +110,7 @@ # a user relation. return query.select_from( model.HistoryDatasetAssociation.table.join( model.History.table ) ).filter( model.History.user == trans.user ) -class DatasetInterface( BaseController ): +class DatasetInterface( BaseController, UsesHistoryDatasetAssociation ): stored_list_grid = HistoryDatasetAssociationListGrid() @@ -293,9 +293,18 @@ @web.require_login( "use Galaxy datasets" ) def get_name_and_link_async( self, trans, id=None ): """ Returns dataset's name and link. """ - dataset = self.get_data( trans, id ) + dataset = self.get_dataset( trans, id ) return_dict = { "name" : dataset.name, "link" : url_for( action="display_by_username_and_slug", username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) ) } return return_dict + + @web.expose + def get_embed_html_async( self, trans, id ): + """ Returns HTML for embedding a dataset in a page. """ + + # TODO: user should be able to embed any item he has access to. see display_by_username_and_slug for security code. + dataset = self.get_dataset( trans, id ) + if dataset: + return "Embedded Dataset '%s'" % dataset.name @web.expose @web.require_login( "use Galaxy datasets" ) @@ -306,20 +315,26 @@ @web.expose def display_by_username_and_slug( self, trans, username, slug, preview=True ): """ Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id. """ - data = self.get_data( trans, slug ) - if data: - # Get data from file, truncating if necessary. - if os.path.exists( data.file_name ): - max_peek_size = 1000000 # 1 MB - if preview and os.stat( data.file_name ).st_size > max_peek_size: - dataset_data = open( data.file_name ).read(max_peek_size) - truncated = True - else: - dataset_data = open( data.file_name ).read(max_peek_size) - truncated = False - return trans.fill_template_mako( "dataset/display.mako", item=data, item_data=dataset_data, truncated=truncated ) + dataset = self.get_dataset( trans, slug ) + if dataset: + truncated, dataset_data = self.get_data( dataset, preview ) + return trans.fill_template_mako( "/dataset/display.mako", item=dataset, item_data=dataset_data, truncated=truncated ) else: raise web.httpexceptions.HTTPNotFound() + + @web.expose + @web.require_login("get item content asynchronously") + def get_item_content_async( self, trans, id ): + """ Returns item content in HTML format. """ + + dataset = self.get_dataset( trans, id ) + if dataset is None: + raise web.httpexceptions.HTTPNotFound() + truncated, dataset_data = self.get_data( dataset, preview=True ) + # Get annotation. + annotation = self.get_item_annotation_str( trans.sa_session, trans.get_user(), dataset ) + return trans.stream_template_mako( "/dataset/item_content.mako", item=dataset, item_data=dataset_data, truncated=truncated ) + @web.expose def display_at( self, trans, dataset_id, filename=None, **kwd ): @@ -513,23 +528,4 @@ new_history_name = new_history_name, done_msg = done_msg, error_msg = error_msg, - refresh_frames = refresh_frames ) - - def get_data( self, trans, dataset_id, check_user_can_access=True ): - """ Get an HDA from the database by id, verifying that user can access dataset.""" - # DEPRECATION: We still support unencoded ids for backward compatibility - try: - dataset_id = int( dataset_id ) - except ValueError: - dataset_id = trans.security.decode_id( dataset_id ) - data = trans.sa_session.query( model.HistoryDatasetAssociation ).get( dataset_id ) - if not data: - raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid dataset id: %s." % str( dataset_id ) ) - if check_user_can_access: - current_user_roles = trans.get_current_user_roles() - if trans.app.security_agent.can_access_dataset( current_user_roles, data.dataset ): - if data.state == trans.model.Dataset.states.UPLOAD: - return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." ) - else: - error( "You are not allowed to access this dataset" ) - return data \ No newline at end of file + refresh_frames = refresh_frames ) \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/controllers/history.py --- a/lib/galaxy/web/controllers/history.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/controllers/history.py Fri Feb 12 17:10:52 2010 -0500 @@ -146,7 +146,7 @@ # A public history is published, has a slug, and is not deleted. return query.filter( self.model_class.published == True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False ) -class HistoryController( BaseController, Sharable ): +class HistoryController( BaseController, Sharable, UsesHistory ): @web.expose def index( self, trans ): return "" @@ -406,6 +406,21 @@ history.slug = new_slug trans.sa_session.flush() return history.slug + + @web.expose + @web.require_login("get item content asynchronously") + def get_item_content_async( self, trans, id ): + """ Returns item content in HTML format. """ + + history = self.get_history( trans, id, False, True ) + if history is None: + raise web.httpexceptions.HTTPNotFound() + + # Get datasets. + datasets = self.get_history_datasets( trans, history ) + # Get annotation. + annotation = self.get_item_annotation_str( trans.sa_session, trans.get_user(), history ) + return trans.stream_template_mako( "/history/item_content.mako", item = history, item_data = datasets ) @web.expose def name_autocomplete_data( self, trans, q=None, limit=None, timestamp=None ): @@ -489,17 +504,11 @@ if not trans.user_is_admin and not history_to_view.importable: error( "Either you are not allowed to view this history or the owner of this history has not made it accessible." ) # View history. - query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ - .filter( model.HistoryDatasetAssociation.history == history_to_view ) \ - .options( eagerload( "children" ) ) \ - .join( "dataset" ).filter( model.Dataset.purged == False ) \ - .options( eagerload_all( "dataset.actions" ) ) - # Do not show deleted datasets. - query = query.filter( model.HistoryDatasetAssociation.deleted == False ) + datasets = self.get_history_datasets( trans, history_to_view ) user_owns_history = ( trans.get_user() == history_to_view.user ) return trans.stream_template_mako( "history/view.mako", history = history_to_view, - datasets = query.all(), + datasets = datasets, user_owns_history = user_owns_history, show_deleted = False ) @@ -521,15 +530,9 @@ raise web.httpexceptions.HTTPNotFound() # Get datasets. - query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ - .filter( model.HistoryDatasetAssociation.history == history ) \ - .options( eagerload( "children" ) ) \ - .join( "dataset" ).filter( model.Dataset.purged == False ) \ - .options( eagerload_all( "dataset.actions" ) ) - # Do not show deleted datasets. - query = query.filter( model.HistoryDatasetAssociation.deleted == False ) + datasets = self.get_history_datasets( trans, history ) return trans.stream_template_mako( "history/display.mako", - item = history, item_data = query.all() ) + item = history, item_data = datasets ) @web.expose @web.require_login( "share Galaxy histories" ) diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/controllers/page.py --- a/lib/galaxy/web/controllers/page.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/controllers/page.py Fri Feb 12 17:10:52 2010 -0500 @@ -1,6 +1,6 @@ from galaxy.web.base.controller import * from galaxy.web.framework.helpers import time_ago, grids -from galaxy.util.sanitize_html import sanitize_html +from galaxy.util.sanitize_html import sanitize_html, _BaseHTMLProcessor from galaxy.util.odict import odict from galaxy.util.json import from_json_string @@ -222,7 +222,70 @@ key="free-text-search", visible=False, filterable="standard" ) ) -class PageController( BaseController, Sharable ): +class _PageContentProcessor( _BaseHTMLProcessor ): + """ Processes page content to produce HTML that is suitable for display. For now, processor renders embedded objects. """ + + def __init__( self, trans, encoding, type, render_embed_html_fn ): + _BaseHTMLProcessor.__init__( self, encoding, type) + self.trans = trans + self.ignore_content = False + self.num_open_tags_for_ignore = 0 + self.render_embed_html_fn = render_embed_html_fn + + def unknown_starttag( self, tag, attrs ): + """ Called for each start tag; attrs is a list of (attr, value) tuples. """ + + # If ignoring content, just increment tag count and ignore. + if self.ignore_content: + self.num_open_tags_for_ignore += 1 + return + + # Not ignoring tag; look for embedded content. + embedded_item = False + for attribute in attrs: + if ( attribute[0] == "class" ) and ( "embedded-item" in attribute[1].split(" ") ): + embedded_item = True + break + # For embedded content, set ignore flag to ignore current content and add new content for embedded item. + if embedded_item: + # Set processing attributes to ignore content. + self.ignore_content = True + self.num_open_tags_for_ignore = 1 + + # Insert content for embedded element. + for attribute in attrs: + name = attribute[0] + if name == "id": + # ID has form '<class_name>-<encoded_item_id>' + item_class, item_id = attribute[1].split("-") + embed_html = self.render_embed_html_fn( self.trans, item_class, item_id ) + self.pieces.append( embed_html ) + return + + # Default behavior: not ignoring and no embedded content. + _BaseHTMLProcessor.unknown_starttag( self, tag, attrs ) + + def handle_data( self, text ): + """ Called for each block of plain text. """ + if self.ignore_content: + return + _BaseHTMLProcessor.handle_data( self, text ) + + def unknown_endtag( self, tag ): + """ Called for each end tag. """ + + # If ignoring content, see if current tag is the end of content to ignore. + if self.ignore_content: + self.num_open_tags_for_ignore -= 1 + if self.num_open_tags_for_ignore == 0: + # Done ignoring content. + self.ignore_content = False + return + + # Default behavior: + _BaseHTMLProcessor.unknown_endtag( self, tag ) + +class PageController( BaseController, Sharable, UsesHistory, UsesStoredWorkflow, UsesHistoryDatasetAssociation ): _page_list = PageListGrid() _all_published_list = PageAllPublishedGrid() @@ -520,9 +583,13 @@ # User not logged in, so only way to view page is if it's importable. page = page_query_base.filter_by( importable=True ).first() if page is None: - raise web.httpexceptions.HTTPNotFound() - - return trans.fill_template_mako( "page/display.mako", item=page) + raise web.httpexceptions.HTTPNotFound() + + # Process page content. + processor = _PageContentProcessor( trans, 'utf-8', 'text/html', self._get_embed_html ) + processor.feed( page.latest_revision.content ) + page_content = processor.output() + return trans.fill_template_mako( "page/display.mako", item=page, item_data=page_content, content_only=True ) @web.expose @web.require_login( "use Galaxy pages" ) @@ -548,6 +615,15 @@ page.slug = new_slug trans.sa_session.flush() return page.slug + + @web.expose + def get_embed_html_async( self, trans, id ): + """ Returns HTML for embedding a workflow in a page. """ + + # TODO: user should be able to embed any item he has access to. see display_by_username_and_slug for security code. + page = self.get_page( trans, id ) + if page: + return "Embedded Page '%s'" % page.title @web.expose @web.json @@ -593,43 +669,50 @@ @web.require_login("get annotation table for history") def get_history_annotation_table( self, trans, id ): """ Returns HTML for an annotation table for a history. """ - - # TODO: users should be able to annotate a history if they own it, it is importable, or it is shared with them. This only - # returns a history if a user owns it. - history = self.get_history( trans, id, True ) + history = self.get_history( trans, id, False, True ) if history: - # TODO: Query taken from root/history; it should be moved either into history or trans object - # so that it can reused. - query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ - .filter( model.HistoryDatasetAssociation.history == history ) \ - .options( eagerload( "children" ) ) \ - .join( "dataset" ).filter( model.Dataset.purged == False ) \ - .options( eagerload_all( "dataset.actions" ) ) \ - .order_by( model.HistoryDatasetAssociation.hid ) - # For now, do not show deleted datasets. - show_deleted = False - if not show_deleted: - query = query.filter( model.HistoryDatasetAssociation.deleted == False ) - return trans.fill_template( "page/history_annotation_table.mako", history=history, datasets=query.all(), show_deleted=False ) + datasets = self.get_history_datasets( trans, history ) + return trans.fill_template( "page/history_annotation_table.mako", history=history, datasets=datasets, show_deleted=False ) @web.expose def get_editor_iframe( self, trans ): """ Returns the document for the page editor's iframe. """ return trans.fill_template( "page/wymiframe.mako" ) - def get_page( self, trans, id, check_ownership=True ): + def get_page( self, trans, id, check_ownership=True, check_accessible=False ): """Get a page from the database by id, verifying ownership.""" # Load history from database id = trans.security.decode_id( id ) page = trans.sa_session.query( model.Page ).get( id ) if not page: - err+msg( "History not found" ) - if check_ownership: - # Verify ownership - user = trans.get_user() - if not user: - error( "Must be logged in to work with Pages" ) - if page.user != user: - error( "History is not owned by current user" ) - return page \ No newline at end of file + err+msg( "Page not found" ) + else: + return self.security_check( trans.get_user(), page, check_ownership, check_accessible ) + + def _get_embed_html( self, trans, item_class, item_id ): + """ Returns HTML for embedding an item in a page. """ + item_class = self.get_class( item_class ) + if item_class == model.History: + history = self.get_history( trans, item_id, False, True ) + if history: + datasets = self.get_history_datasets( trans, history ) + annotation = self.get_item_annotation_str( trans.sa_session, history.user, history ) + return trans.fill_template( "history/embed.mako", item=history, item_data=datasets, annotation=annotation ) + elif item_class == model.HistoryDatasetAssociation: + dataset = self.get_dataset( trans, item_id ) + if dataset: + data = self.get_data( dataset ) + annotation = self.get_item_annotation_str( trans.sa_session, dataset.history.user, dataset ) + return trans.fill_template( "dataset/embed.mako", item=dataset, item_data=data, annotation=annotation ) + elif item_class == model.StoredWorkflow: + workflow = self.get_stored_workflow( trans, item_id, False, True ) + if workflow: + self.get_stored_workflow_steps( trans, workflow ) + annotation = self.get_item_annotation_str( trans.sa_session, workflow.user, workflow ) + return trans.fill_template( "workflow/embed.mako", item=workflow, item_data=workflow.latest_workflow.steps, annotation=annotation ) + elif item_class == model.Page: + pass + + + \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/controllers/requests_admin.py --- a/lib/galaxy/web/controllers/requests_admin.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/controllers/requests_admin.py Fri Feb 12 17:10:52 2010 -0500 @@ -11,7 +11,7 @@ from sqlalchemy.sql.expression import func, and_ from sqlalchemy.sql import select import pexpect -import ConfigParser +import ConfigParser, threading, time log = logging.getLogger( __name__ ) @@ -204,6 +204,18 @@ action='create_request_type' ) ) ] +class DataTransferThread(threading.Thread): + def __init__(self, **kwargs): + threading.Thread.__init__(self, name=kwargs['name']) + self.dataset_index = kwargs['dataset_index'] + self.cmd = kwargs['cmd'] + def run(self): + try: + retcode = subprocess.call(self.cmd) + except Exception, e: + error_msg = "Data transfer failed. " + str(e) + "<br/>" + log.debug(error_msg) + # # ---- Request Controller ------------------------------------------------------ @@ -1511,7 +1523,7 @@ trans.sa_session.add( dp ) trans.sa_session.flush() return datatx_user - + def __start_datatx(self, trans, sample): # data transfer user datatx_user = self.__setup_datatx_user(trans, sample.library, sample.folder) @@ -1542,20 +1554,28 @@ str(index), trans.security.encode_id(sample.library.id), trans.security.encode_id(sample.folder.id) ) - # set the transfer status +# # set the transfer status sample.dataset_files[index][1] = sample.transfer_status.IN_PROGRESS trans.sa_session.add( sample ) trans.sa_session.flush() - try: - retcode = subprocess.call(cmd) - except Exception, e: - error_msg = dfile.split('/')[-1] + ": Data transfer failed. " + str(e) + "<br/>" - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='show_datatx_page', - sample_id=trans.security.encode_id(sample.id), - folder_path=os.path.dirname(dfile), - messagetype='error', - msg=error_msg)) + dtt = DataTransferThread(name='thread_'+str(index), + dataset_index=index, + cmd=cmd) + dtt.start() +# # set the transfer status +# sample.dataset_files[index][1] = sample.transfer_status.IN_PROGRESS +# trans.sa_session.add( sample ) +# trans.sa_session.flush() +# try: +# retcode = subprocess.call(cmd) +# except Exception, e: +# error_msg = dfile.split('/')[-1] + ": Data transfer failed. " + str(e) + "<br/>" +# return trans.response.send_redirect( web.url_for( controller='requests_admin', +# action='show_datatx_page', +# sample_id=trans.security.encode_id(sample.id), +# folder_path=os.path.dirname(dfile), +# messagetype='error', +# msg=error_msg)) # set the sample state to the last state if sample.current_state().id != sample.request.type.states[-1].id: event = trans.app.model.SampleEvent(sample, sample.request.type.states[-1], diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/controllers/root.py --- a/lib/galaxy/web/controllers/root.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/controllers/root.py Fri Feb 12 17:10:52 2010 -0500 @@ -10,7 +10,7 @@ log = logging.getLogger( __name__ ) -class RootController( BaseController ): +class RootController( BaseController, UsesHistory ): @web.expose def default(self, trans, target1=None, target2=None, **kwd): @@ -69,18 +69,11 @@ return trans.fill_template_mako( "root/history_as_xml.mako", history=history, show_deleted=util.string_as_bool( show_deleted ) ) else: show_deleted = util.string_as_bool( show_deleted ) - query = trans.sa_session.query( model.HistoryDatasetAssociation ) \ - .filter( model.HistoryDatasetAssociation.history == history ) \ - .options( eagerload( "children" ) ) \ - .join( "dataset" ).filter( model.Dataset.purged == False ) \ - .options( eagerload_all( "dataset.actions" ) ) \ - .order_by( model.HistoryDatasetAssociation.hid ) - if not show_deleted: - query = query.filter( model.HistoryDatasetAssociation.deleted == False ) + datasets = self.get_history_datasets( trans, history, show_deleted ) return trans.stream_template_mako( "root/history.mako", history = history, annotation = self.get_item_annotation_str( trans.sa_session, trans.get_user(), history ), - datasets = query.all(), + datasets = datasets, hda_id = hda_id, show_deleted = show_deleted ) diff -r 8d9c8cf65dee -r fb48e02e1610 lib/galaxy/web/controllers/workflow.py --- a/lib/galaxy/web/controllers/workflow.py Fri Feb 12 17:10:05 2010 -0500 +++ b/lib/galaxy/web/controllers/workflow.py Fri Feb 12 17:10:52 2010 -0500 @@ -78,7 +78,7 @@ # A public workflow is published, has a slug, and is not deleted. return query.filter( self.model_class.published==True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False ) -class WorkflowController( BaseController, Sharable ): +class WorkflowController( BaseController, Sharable, UsesStoredWorkflow ): stored_list_grid = StoredWorkflowListGrid() published_list_grid = StoredWorkflowAllPublishedGrid() @@ -93,7 +93,6 @@ status = message = None if 'operation' in kwargs: operation = kwargs['operation'].lower() - print operation if operation == "rename": return self.rename( trans, **kwargs ) history_ids = util.listify( kwargs.get( 'id', [] ) ) @@ -165,7 +164,6 @@ @web.expose def display_by_username_and_slug( self, trans, username, slug ): """ Display workflow based on a username and slug. """ - session = trans.sa_session # Get workflow. session = trans.sa_session @@ -181,34 +179,31 @@ raise web.httpexceptions.HTTPNotFound() # Get data for workflow's steps. - for step in stored_workflow.latest_workflow.steps: - if step.type == 'tool' or step.type is None: - # Restore the tool state for the step - module = module_factory.from_workflow_step( trans, step ) - # Any connected input needs to have value DummyDataset (these - # are not persisted so we need to do it every time) - module.add_dummy_datasets( connections=step.input_connections ) - # Store state with the step - step.module = module - step.state = module.state - # Error dict - if step.tool_errors: - errors[step.id] = step.tool_errors - else: - ## Non-tool specific stuff? - step.module = module_factory.from_workflow_step( trans, step ) - step.state = step.module.get_runtime_state() - # Connections by input name - step.input_connections_by_name = dict( ( conn.input_name, conn ) for conn in step.input_connections ) + self.get_stored_workflow_steps( trans, stored_workflow ) return trans.fill_template_mako( "workflow/display.mako", item=stored_workflow, item_data=stored_workflow.latest_workflow.steps ) + + @web.expose + @web.require_login("get item content asynchronously") + def get_item_content_async( self, trans, id ): + """ Returns item content in HTML format. """ + + stored = self.get_stored_workflow( trans, id, False, True ) + if stored is None: + raise web.httpexceptions.HTTPNotFound() + + # Get data for workflow's steps. + self.get_stored_workflow_steps( trans, stored ) + # Get annotation. + annotation = self.get_item_annotation_str( trans.sa_session, trans.get_user(), stored ) + return trans.stream_template_mako( "/workflow/item_content.mako", item = stored, item_data = stored.latest_workflow.steps ) @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 ) + stored = self.get_stored_workflow( trans, id ) if email: other = trans.sa_session.query( model.User ) \ .filter( and_( model.User.table.c.email==email, @@ -247,7 +242,7 @@ # Get session and workflow. session = trans.sa_session - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) session.add( stored ) # Do operation on workflow. @@ -285,7 +280,7 @@ @web.require_login( "use Galaxy workflows" ) def imp( self, trans, id, **kwargs ): session = trans.sa_session - stored = get_stored_workflow( trans, id, check_ownership=False ) + stored = self.get_stored_workflow( trans, id, check_ownership=False ) if stored.importable == False: error( "The owner of this workflow has disabled imports via this link" ) elif stored.user == trans.user: @@ -309,7 +304,7 @@ @web.require_login( "use Galaxy workflows" ) def edit_attributes( self, trans, id, **kwargs ): # Get workflow and do error checking. - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) if not stored: error( "You do not own this workflow or workflow ID is invalid." ) @@ -331,7 +326,7 @@ @web.expose @web.require_login( "use Galaxy workflows" ) def rename( self, trans, id, new_name=None, **kwargs ): - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) if new_name is not None: stored.name = new_name trans.sa_session.flush() @@ -348,7 +343,7 @@ @web.expose @web.require_login( "use Galaxy workflows" ) def rename_async( self, trans, id, new_name=None, **kwargs ): - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) if new_name: stored.name = new_name trans.sa_session.flush() @@ -357,7 +352,7 @@ @web.expose @web.require_login( "use Galaxy workflows" ) def annotate_async( self, trans, id, new_annotation=None, **kwargs ): - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) if new_annotation: # Sanitize annotation before adding it. new_annotation = sanitize_html( new_annotation, 'utf-8', 'text/html' ) @@ -369,7 +364,7 @@ @web.require_login( "use Galaxy workflows" ) def set_accessible_async( self, trans, id=None, accessible=False ): """ Set workflow's importable attribute and slug. """ - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed. importable = accessible in ['True', 'true', 't', 'T']; @@ -385,18 +380,27 @@ @web.expose @web.require_login( "modify Galaxy items" ) def set_slug_async( self, trans, id, new_slug ): - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) if stored: stored.slug = new_slug trans.sa_session.flush() return stored.slug + + @web.expose + def get_embed_html_async( self, trans, id ): + """ Returns HTML for embedding a workflow in a page. """ + + # TODO: user should be able to embed any item he has access to. see display_by_username_and_slug for security code. + stored = self.get_stored_workflow( trans, id ) + if stored: + return "Embedded Workflow '%s'" % stored.name @web.expose @web.json @web.require_login( "use Galaxy workflows" ) def get_name_and_link_async( self, trans, id=None ): """ Returns workflow's name and link. """ - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) if self.set_item_slug( trans.sa_session, stored ): trans.sa_session.flush() @@ -406,7 +410,7 @@ @web.expose @web.require_login( "use Galaxy workflows" ) def clone( self, trans, id ): - stored = get_stored_workflow( trans, id, check_ownership=False ) + stored = self.get_stored_workflow( trans, id, check_ownership=False ) user = trans.get_user() if stored.user == user: owner = True @@ -463,7 +467,7 @@ Mark a workflow as deleted """ # Load workflow from database - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) # Marke as deleted and save stored.deleted = True trans.sa_session.add( stored ) @@ -482,7 +486,7 @@ """ if not id: error( "Invalid workflow id" ) - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) return trans.fill_template( "workflow/editor.mako", stored=stored, annotation=self.get_item_annotation_str( trans.sa_session, trans.get_user(), stored ) ) @web.json @@ -611,7 +615,7 @@ Save the workflow described by `workflow_data` with id `id`. """ # Get the stored workflow - stored = get_stored_workflow( trans, id ) + stored = self.get_stored_workflow( trans, id ) # Put parameters in workflow mode trans.workflow_building_mode = True # Convert incoming workflow data from json @@ -795,7 +799,7 @@ @web.expose def run( self, trans, id, check_user=True, **kwargs ): - stored = get_stored_workflow( trans, id, check_ownership=False ) + stored = self.get_stored_workflow( trans, id, check_ownership=False ) if check_user: user = trans.get_user() if stored.user != user: @@ -956,24 +960,6 @@ ids_in_menu=ids_in_menu ) ## ---- Utility methods ------------------------------------------------------- - -def get_stored_workflow( trans, id, check_ownership=True ): - """ - Get a StoredWorkflow from the database by id, verifying ownership. - """ - # Load workflow from database - id = trans.security.decode_id( id ) - stored = trans.sa_session.query( model.StoredWorkflow ).get( id ) - if not stored: - error( "Workflow not found" ) - # Verify ownership - user = trans.get_user() - if not user: - error( "Must be logged in to use workflows" ) - if check_ownership and not( stored.user == user ): - error( "Workflow is not owned by current user" ) - # Looks good - return stored def attach_ordered_steps( workflow, steps ): ordered_steps = order_workflow_steps( steps ) diff -r 8d9c8cf65dee -r fb48e02e1610 scripts/galaxy_messaging/server/data_transfer.py --- a/scripts/galaxy_messaging/server/data_transfer.py Fri Feb 12 17:10:05 2010 -0500 +++ b/scripts/galaxy_messaging/server/data_transfer.py Fri Feb 12 17:10:52 2010 -0500 @@ -38,9 +38,7 @@ pkg_resources.require( "simplejson" ) import simplejson -curr_dir = os.getcwd() -logfile = os.path.join(os.getcwd(), 'data_transfer.log') -logging.basicConfig(filename=logfile, level=logging.DEBUG, +logging.basicConfig(filename=sys.stderr, level=logging.DEBUG, format="%(asctime)s [%(levelname)s] %(message)s") class DataTransferException(Exception): diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/fugue/pencil.png Binary file static/images/fugue/pencil.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/fugue/sticky-note-text.png Binary file static/images/fugue/sticky-note-text.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/fugue/tag-label.png Binary file static/images/fugue/tag-label.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/fugue/toggle-expand.png Binary file static/images/fugue/toggle-expand.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/fugue/toggle.png Binary file static/images/fugue/toggle.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/pencil.png Binary file static/images/pencil.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/sticky-note-text.png Binary file static/images/sticky-note-text.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/images/tag-label.png Binary file static/images/tag-label.png has changed diff -r 8d9c8cf65dee -r fb48e02e1610 static/june_2007_style/base.css.tmpl --- a/static/june_2007_style/base.css.tmpl Fri Feb 12 17:10:05 2010 -0500 +++ b/static/june_2007_style/base.css.tmpl Fri Feb 12 17:10:52 2010 -0500 @@ -703,7 +703,7 @@ } .icon-button.tag { - background-image: url(/static/images/tag-label.png); + background-image: url(/static/images/fugue/tag-label.png); } .icon-button.tags { @@ -713,9 +713,17 @@ .icon-button.tag--plus { background-image: url(/static/images/fugue/tag--plus.png); } + +.icon-button.toggle-expand { + background-image:url(/static/images/fugue/toggle-expand.png); +} + +.icon-button.toggle-contract { + background-image:url(/static/images/fugue/toggle.png); +} .icon-button.annotate { - background-image:url(/static/images/sticky-note-text.png); + background-image:url(/static/images/fugue/sticky-note-text.png); background-repeat:no-repeat; background-position:center; padding: 0; diff -r 8d9c8cf65dee -r fb48e02e1610 static/june_2007_style/blue/base.css --- a/static/june_2007_style/blue/base.css Fri Feb 12 17:10:05 2010 -0500 +++ b/static/june_2007_style/blue/base.css Fri Feb 12 17:10:52 2010 -0500 @@ -117,10 +117,12 @@ .icon-button.delete:hover{background:url(history-buttons.png) no-repeat 0px -78px;} .icon-button.edit{background:url(history-buttons.png) no-repeat 0px -104px;} .icon-button.edit:hover{background:url(history-buttons.png) no-repeat 0px -130px;} -.icon-button.tag{background-image:url(/static/images/tag-label.png);} +.icon-button.tag{background-image:url(/static/images/fugue/tag-label.png);} .icon-button.tags{background-image:url(/static/images/fugue/tags.png);} .icon-button.tag--plus{background-image:url(/static/images/fugue/tag--plus.png);} -.icon-button.annotate{background-image:url(/static/images/sticky-note-text.png);background-repeat:no-repeat;background-position:center;padding:0;} +.icon-button.toggle-expand{background-image:url(/static/images/fugue/toggle-expand.png);} +.icon-button.toggle-contract{background-image:url(/static/images/fugue/toggle.png);} +.icon-button.annotate{background-image:url(/static/images/fugue/sticky-note-text.png);background-repeat:no-repeat;background-position:center;padding:0;} .tipsy{padding:5px;font-size:10px;filter:alpha(opacity=80);background-repeat:no-repeat;background-image:url(../images/tipsy.gif);} .tipsy-inner{padding:5px 8px 4px 8px;background-color:black;color:white;max-width:200px;text-align:center;} .tipsy-north{background-position:top center;} diff -r 8d9c8cf65dee -r fb48e02e1610 static/june_2007_style/blue/embed_item.css --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/june_2007_style/blue/embed_item.css Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,4 @@ +.embedded-item{background-color: #BBBBBB; margin-left: auto; margin-right: auto; width: 90%; max-height: 25em; overflow: auto; padding: 0.5em;-moz-border-radius: .5em;-webkit-border-radius: .5em;border-radius: .5em;} +.embedded-item.placeholder{} +.embedded-item .title{font-weight: bold;font-size:120%;vertical-align:top;text-align:center;} +.embedded-item.placeholder .content{padding: 1em 1em;font-style:italic;text-align:center;} \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 static/june_2007_style/embed_item.css.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/static/june_2007_style/embed_item.css.tmpl Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,24 @@ +.embedded-item { + background-color: #BBBBBB; + margin-left: auto; + margin-right: auto; + width: 90%; + max-height: 25em; + overflow: auto; + padding: 0.5em; + -moz-border-radius: .5em; + -webkit-border-radius: .5em; + border-radius: .5em; +} +.embedded-item.placeholder {} +.embedded-item .title { + font-weight: bold; + font-size:120%; + vertical-align:top; + text-align:center; +} +.embedded-item.placeholder .content{ + padding: 1em 1em; + font-style:italic; + text-align:center; + } \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 templates/dataset/display.mako --- a/templates/dataset/display.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/dataset/display.mako Fri Feb 12 17:10:52 2010 -0500 @@ -27,7 +27,6 @@ </%def> <%def name="render_item( data, data_to_render )"> - <hr/> %if truncated: <div class="warningmessagelarge"> This dataset is large and only the first megabyte is shown below. | @@ -82,7 +81,7 @@ ## Tags. <p> - <h4>Tags</strong></h4> + <h4>Tags</h4> <p> ## Community tags. <div> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/dataset/embed.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/dataset/embed.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,13 @@ +<%inherit file="/embed_base.mako"/> +<%! + from galaxy.web.framework.helpers import iff +%> + +<%def name="content( dataset, data )"> + %if annotation: + <div class='annotation'>${annotation}</div> + %endif + <ul> + <li>Format : ${dataset.extension} + </ul> +</%def> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/dataset/item_content.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/dataset/item_content.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,3 @@ +<%namespace file="/dataset/display.mako" import="*" /> + +${render_item( item, item_data )} \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 templates/display_base.mako --- a/templates/display_base.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/display_base.mako Fri Feb 12 17:10:52 2010 -0500 @@ -12,6 +12,10 @@ <%namespace file="/tagging_common.mako" import="render_individual_tagging_element, render_community_tagging_element" /> <%namespace file="/display_common.mako" import="*" /> +## +## Functions used by base.mako and base_panels.mako to display content. +## + <%def name="title()"> Galaxy | ${iff( item.published, "Published ", iff( item.importable , "Accessible ", iff( item.users_shared_with, "Shared ", "Private " ) ) ) + get_class_display_name( item.__class__ )} | ${get_item_name( item ) | h} </%def> @@ -48,7 +52,7 @@ <%def name="stylesheets()"> ${parent.stylesheets()} - ${h.css( "autocomplete_tagging" )} + ${h.css( "autocomplete_tagging", "embed_item" )} <style type="text/css"> .page-body { @@ -70,24 +74,28 @@ </%def> <%def name="render_item_links( item )"> - Item Links + ## Override. </%def> <%def name="render_item( item, item_data=None )"> - Item + ## Override. </%def> -## -## When page has no panels, center panel is body. -## +## For base.mako <%def name="body()"> - ${self.center_panel()} + ${self.render_content()} </%def> +## For base_panels.mako +<%def name="center_panel()"> + ${self.render_content()} +</%def> + + ## -## Page content. Pages that inherit this page should override render_item_links() and render_item() +## Render page content. Pages that inherit this page should override render_item_links() and render_item() ## -<%def name="center_panel()"> +<%def name="render_content()"> ## Get URL to other published items owned by user that owns this item. <% @@ -165,7 +173,7 @@ <a href="${href_to_user_items}">Published ${item_plural.lower()} by ${item.user.username | h}</a> ## Tags. - <h4>Tags</strong></h4> + <h4>Tags</h4> <p> ## Community tags. <div> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/display_common.mako --- a/templates/display_common.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/display_common.mako Fri Feb 12 17:10:52 2010 -0500 @@ -83,11 +83,35 @@ return "history" elif isinstance( item, model.StoredWorkflow ): return "workflow" + elif isinstance( item, model.HistoryDatasetAssociation ): + return "dataset" elif isinstance( item, model.Page ): return "page" %> </%def> +## Returns item user/owner. +<%def name="get_item_user( item )"> + <% + # Exceptions first, default last. + if isinstance( item, model.HistoryDatasetAssociation ): + return item.history.user + else: + return item.user + %> +</%def> + +## Returns item slug. +<%def name="get_item_slug( item )"> + <% + # Exceptions first, default last. + if isinstance( item, model.HistoryDatasetAssociation ): + return trans.security.encode_id( item.id ) + else: + return item.slug + %> +</%def> + ## Return a link to view a history. <%def name="get_history_link( history, qualify=False )"> %if history.slug and history.user.username: diff -r 8d9c8cf65dee -r fb48e02e1610 templates/embed_base.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/embed_base.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,38 @@ +## +## Base file for generating HTML for embedded objects. +## +## parameters: item, item_data +## +<%namespace file="/display_common.mako" import="*" /> + +## HTML structure. +<div class='embedded-item'> + <div class='title'> + ${self.title( item )} + </div> + <hr/> + <div class='summary-content'> + ${self.content( item, item_data )} + </div> + <div class='item-content'> + </div> +</div> + +<%def name="title( item )"> + Galaxy ${get_class_display_name( item.__class__ )} | ${get_item_name( item )} + <% + item_controller = "/%s" % get_controller_name( item ) + item_user = get_item_user( item ) + item_slug = get_item_slug( item ) + display_href = h.url_for( controller=item_controller, action='display_by_username_and_slug', username=item_user.username, slug=item_slug ) + %> + <a class="display_in_embed icon-button toggle-expand" item_id="${trans.security.encode_id( item.id )}" item_class="$item.__class__.__name__" href="${display_href}"></a> + <a class="toggle-contract icon-button" href="${display_href}"></a> + + ## Use a hidden var to store the ajax URL for getting an item's content. + <input type="hidden" name="ajax-item-content-url" value="${h.url_for( controller=item_controller, action='get_item_content_async', id=trans.security.encode_id( item.id ) )}"/> +</%def> + +## Methods to override to generate content. +<%def name="content( item, item_data )"> +</%def> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/history/embed.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/history/embed.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,15 @@ +<%inherit file="/embed_base.mako"/> +<%! + from galaxy.web.framework.helpers import iff +%> + +<%def name="content( history, datasets )"> + %if annotation: + <div class='annotation'>${annotation}</div> + %endif + <ul> + <% num_datasets = len ( datasets ) %> + <li>${num_datasets} dataset${iff( num_datasets != 1, "s", "" )} + <li>Operations: ... + </ul> +</%def> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/history/item_content.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/history/item_content.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,3 @@ +<%namespace file="/history/display.mako" import="*" /> + +${render_item( item, item_data )} \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 templates/page/display.mako --- a/templates/page/display.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/page/display.mako Fri Feb 12 17:10:52 2010 -0500 @@ -25,6 +25,54 @@ }); }); + + // Setup embedded content: + // (a) toggles for showing/hiding embedded content; + // (b) ... + $('.embedded-item').each( function() + { + // Setup toggle expand. + var container = $(this); + var toggle_expand = $(this).find('.toggle-expand'); + toggle_expand.click( function() + { + var ajax_url = container.find("input[type=hidden]").val(); + // Only get item content if it's not already there. + var item_content = $.trim(container.find(".item-content").text()); + if (item_content == "") + $.ajax({ + type: "GET", + url: ajax_url, + error: function() { alert("Getting item content failed."); }, + success: function( item_content ) { + container.find(".summary-content").hide("fast"); + container.find(".item-content").html(item_content).show("fast"); + container.find(".toggle-expand").hide(); + container.find(".toggle-contract").show(); + } + }); + else + { + container.find(".summary-content").hide("fast"); + container.find(".item-content").show("fast"); + container.find(".toggle-expand").hide(); + container.find(".toggle-contract").show(); + } + return false; + }); + // Setup toggle contract. + var toggle_contract = $(this).find('.toggle-contract'); + toggle_contract.click( function() + { + container.find(".item-content").hide("fast"); + container.find(".summary-content").show("fast"); + container.find(".toggle-contract").hide(); + container.find(".toggle-expand").show(); + return false; + }); + + + }); }); // Functionized so AJAX'd datasets can call them function initShowHide() { @@ -148,15 +196,15 @@ <%def name="stylesheets()"> ${parent.stylesheets()} ${h.css( "base", "history", "autocomplete_tagging" )} -</%def> - -<%def name="get_item_name( page )"> - <% return page.title %> + <style type="text/css"> + .toggle-contract { display: none; } + .item-content { overflow: auto; } + </style> </%def> <%def name="render_item_links( page )"> </%def> <%def name="render_item( page, page_data=None )"> - ${page.latest_revision.content.decode( "utf-8" )} + ${page_data} </%def> \ No newline at end of file diff -r 8d9c8cf65dee -r fb48e02e1610 templates/page/editor.mako --- a/templates/page/editor.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/page/editor.mako Fri Feb 12 17:10:52 2010 -0500 @@ -29,10 +29,25 @@ // Useful Galaxy stuff. var Galaxy = { - DIALOG_HISTORY_LINK : "history_link", - DIALOG_DATASET_LINK : "dataset_link", - DIALOG_WORKFLOW_LINK : "workflow_link", - DIALOG_PAGE_LINK : "page_link", + // Item types. + ITEM_HISTORY : "item_history", + ITEM_DATASET : "item_dataset", + ITEM_WORKFLOW : "item_workflow", + ITEM_PAGE : "item_page", + + // Link dialogs. + DIALOG_HISTORY_LINK : "link_history", + DIALOG_DATASET_LINK : "link_dataset", + DIALOG_WORKFLOW_LINK : "link_workflow", + DIALOG_PAGE_LINK : "link_page", + + // Embed dialogs. + DIALOG_EMBED_HISTORY : "embed_history", + DIALOG_EMBED_DATASET : "embed_dataset", + DIALOG_EMBED_WORKFLOW : "embed_workflow", + DIALOG_EMBED_PAGE : "embed_page", + + // Annotation dialogs. DIALOG_HISTORY_ANNOTATE : "history_annotate", }; @@ -55,6 +70,55 @@ }; + // Based on the dialog type, return a dictionary of information about an item + function get_item_info( dialog_type ) + { + var + item_singular, + item_plural, + item_controller; + switch( dialog_type ) { + case( Galaxy.ITEM_HISTORY ): + item_singular = "History"; + item_plural = "Histories"; + item_controller = "history"; + item_class = "History"; + break; + case( Galaxy.ITEM_DATASET ): + item_singular = "Dataset"; + item_plural = "Datasets"; + item_controller = "dataset"; + item_class = "HistoryDatasetAssociation"; + break; + case( Galaxy.ITEM_WORKFLOW ): + item_singular = "Workflow"; + item_plural = "Workflows"; + item_controller = "workflow"; + item_class = "StoredWorkflow"; + break; + case( Galaxy.ITEM_PAGE ): + item_singular = "Page"; + item_plural = "Pages"; + item_controller = "page"; + item_class = "Page"; + break; + } + + // Build ajax URL that lists items for selection. + var item_list_action = "list_" + item_plural.toLowerCase() + "_for_selection"; + var url_template = "${h.url_for( action='LIST_ACTION' )}"; + var ajax_url = url_template.replace( "LIST_ACTION", item_list_action ); + + // Set up and return dict. + return { + singular : item_singular, + plural : item_plural, + controller : item_controller, + iclass : item_class, + list_ajax_url : ajax_url + }; + }; + ## Completely replace WYM's dialog handling WYMeditor.editor.prototype.dialog = function( dialogType, dialogFeatures, bodyHtml ) { @@ -206,50 +270,35 @@ if ( dialogType == Galaxy.DIALOG_HISTORY_LINK || dialogType == Galaxy.DIALOG_DATASET_LINK || dialogType == Galaxy.DIALOG_WORKFLOW_LINK || dialogType == Galaxy.DIALOG_PAGE_LINK ) { // Based on item type, set useful vars. - var - item_singular, - item_plural, - item_controller, - item_list_action; - switch( dialogType ) { - case( Galaxy.DIALOG_HISTORY_LINK ): - item_singular = "History"; - item_plural = "Histories"; - item_controller = "history" + var item_info; + switch(dialogType) + { + case(Galaxy.DIALOG_HISTORY_LINK): + item_info = get_item_info(Galaxy.ITEM_HISTORY); break; - case( Galaxy.DIALOG_DATASET_LINK ): - item_singular = "Dataset"; - item_plural = "Datasets"; - item_controller = "dataset" + case(Galaxy.DIALOG_DATASET_LINK): + item_info = get_item_info(Galaxy.ITEM_DATASET); break; - case( Galaxy.DIALOG_WORKFLOW_LINK ): - item_singular = "Workflow"; - item_plural = "Workflows"; - item_controller = "workflow" + case(Galaxy.DIALOG_WORKFLOW_LINK): + item_info = get_item_info(Galaxy.ITEM_WORKFLOW); break; - case( Galaxy.DIALOG_PAGE_LINK ): - item_singular = "Page"; - item_plural = "Pages"; - item_controller = "page" - break; + case(Galaxy.DIALOG_PAGE_LINK): + item_info = get_item_info(Galaxy.ITEM_PAGE); + break; } - item_list_action = "list_" + item_plural.toLowerCase() + "_for_selection"; - // Show grid that enables user to select items. - var url_template = "${h.url_for( action='LIST_ACTION' )}"; - var ajax_url = url_template.replace( "LIST_ACTION", item_list_action ); $.ajax( { - url: ajax_url, + url: item_info.list_ajax_url, data: {}, - error: function() { alert( "Failed to list " + item_plural.toLowerCase() + " for selection"); }, + error: function() { alert( "Failed to list " + item_info.plural.toLowerCase() + " for selection"); }, success: function(table_html) { show_modal( - "Insert Link to " + item_singular, + "Insert Link to " + item_info.singular, table_html + "<div><input id='make-importable' type='checkbox' checked/>" + - "Make the selected " + item_plural.toLowerCase() + " accessible so that they can viewed by everyone.</div>" + "Make the selected " + item_info.plural.toLowerCase() + " accessible so that they can viewed by everyone.</div>" , { "Insert": function() @@ -264,23 +313,23 @@ $('input[name=id]:checked').each(function() { var item_id = $(this).val(); - // Make history importable? + // Make item importable? if (make_importable) { url_template = "${h.url_for( controller='ITEM_CONTROLLER', action='set_accessible_async' )}"; - ajax_url = url_template.replace( "ITEM_CONTROLLER", item_controller); + ajax_url = url_template.replace( "ITEM_CONTROLLER", item_info.controller); $.ajax({ type: "POST", url: ajax_url, data: { id: item_id, accessible: 'True' }, - error: function() { alert("Making " + item_plural.toLowerCase() + " accessible failed"); } + error: function() { alert("Making " + item_info.plural.toLowerCase() + " accessible failed"); } }); } // Insert link(s) to item(s). This is done by getting item info and then manipulating wym. url_template = "${h.url_for( controller='ITEM_CONTROLLER', action='get_name_and_link_async' )}?id=" + item_id; - ajax_url = url_template.replace( "ITEM_CONTROLLER", item_controller); - $.getJSON( ajax_url, function( item_info ) { + ajax_url = url_template.replace( "ITEM_CONTROLLER", item_info.controller); + $.getJSON( ajax_url, function( returned_item_info ) { // Get link text. wym._exec(WYMeditor.CREATE_LINK, sStamp); var link_text = $("a[href=" + sStamp + "]", wym._doc.body).text(); @@ -293,12 +342,12 @@ ) { // User selected no text; create link from scratch and use default text. - wym.insert("<a href='" + item_info.link + "'> '" + item_singular + " " + item_info.name + "'</a>"); + wym.insert("<a href='" + returned_item_info.link + "'>" + item_info.singular + " '" + returned_item_info.name + "'</a>"); } else { // Link created from selected text; add href and title. - $("a[href=" + sStamp + "]", wym._doc.body).attr(WYMeditor.HREF, item_info.link).attr(WYMeditor.TITLE, item_singular + item_id); + $("a[href=" + sStamp + "]", wym._doc.body).attr(WYMeditor.HREF, returned_item_info.link).attr(WYMeditor.TITLE, item_info.singular + item_id); } }); }); @@ -314,6 +363,72 @@ } }); } + // EMBED GALAXY OBJECT DIALOGS + if ( dialogType == Galaxy.DIALOG_EMBED_HISTORY || dialogType == Galaxy.DIALOG_EMBED_DATASET || dialogType == Galaxy.DIALOG_EMBED_WORKFLOW || dialogType == Galaxy.DIALOG_EMBED_PAGE ) { + // Based on item type, set useful vars. + var item_info; + switch(dialogType) + { + case(Galaxy.DIALOG_EMBED_HISTORY): + item_info = get_item_info(Galaxy.ITEM_HISTORY); + break; + case(Galaxy.DIALOG_EMBED_DATASET): + item_info = get_item_info(Galaxy.ITEM_DATASET); + break; + case(Galaxy.DIALOG_EMBED_WORKFLOW): + item_info = get_item_info(Galaxy.ITEM_WORKFLOW); + break; + case(Galaxy.DIALOG_EMBED_PAGE): + item_info = get_item_info(Galaxy.ITEM_PAGE); + break; + } + + $.ajax( + { + url: item_info.list_ajax_url, + data: {}, + error: function() { alert( "Failed to list " + item_info.plural.toLowerCase() + " for selection"); }, + success: function(list_html) + { + show_modal( + "Embed " + item_info.plural, + list_html, + { + "Embed": function() + { + // Embed a Galaxy item. + var item_ids = new Array(); + $('input[name=id]:checked').each(function() { + // Get item ID and name. + var item_id = $(this).val(); + // Use ':first' because there are many labels in table; the first one is the item name. + var item_name = $("label[for='" + item_id + "']:first").text(); + + // Embedded item HTML; item class is embedded in div container classes; this is necessary because the editor strips + // all non-standard attributes when it returns its content (e.g. it will not return an element attribute of the form + // item_class='History'). + var item_embed_html = + "<p><div id='" + item_info.iclass + "-" + item_id + "' class='embedded-item placeholder'> \ + <div class='title'> Embedded Galaxy " + item_info.singular + " '" + item_name + "'</div> \ + <div class='content'>[Do not edit this block; Galaxy will fill it in with the annotated " + \ + item_info.singular.toLowerCase() + " when it is displayed.]</div> \ + </div></p><p>"; + + // Insert embedded representation into document. + wym.insert(item_embed_html); + }); + hide_modal(); + }, + "Cancel": function() + { + hide_modal(); + } + } + ); + } + }); + } + // ANNOTATE HISTORY DIALOG if ( dialogType == Galaxy.DIALOG_ANNOTATE_HISTORY ) { $.ajax( @@ -418,6 +533,9 @@ var editor = $.wymeditors(0); var save = function ( callback ) { show_modal( "Saving page", "progress" ); + + /* + Not used right now. // Gather annotations. var annotations = new Array(); @@ -433,6 +551,8 @@ annotations[ annotations.length ] = annotation; }); + */ + // Do save. $.ajax( { url: "${h.url_for( action='save' )}", @@ -440,7 +560,8 @@ data: { id: "${trans.security.encode_id(page.id)}", content: editor.xhtml(), - annotations: JSON.stringify(annotations), + annotations: JSON.stringify(new Object()), + ## annotations: JSON.stringify(annotations), "_": "true" }, success: function() { @@ -500,13 +621,69 @@ }); // Initialize galaxy elements. //init_galaxy_elts(editor); + + // + // Create 'Insert Link to Galaxy Object' menu. + // + + // Add menu button. + var insert_link_menu_button = $("<div><a id='insert-galaxy-link' class='panel-header-button popup' href='#'>${_('Insert Link to Galaxy Object')}</a></div>").addClass('galaxy-page-editor-button'); + $(".wym_area_top").append(insert_link_menu_button); + + // Add menu options. + make_popupmenu( insert_link_menu_button, { + "Insert History Link": function() { + editor.dialog(Galaxy.DIALOG_HISTORY_LINK); + }, + "Insert Dataset Link": function() { + editor.dialog(Galaxy.DIALOG_DATASET_LINK); + }, + "Insert Workflow Link": function() { + editor.dialog(Galaxy.DIALOG_WORKFLOW_LINK); + }, + "Insert Page Link": function() { + editor.dialog(Galaxy.DIALOG_PAGE_LINK); + } + }); + + // + // Create 'Embed Galaxy Object' menu. + // + + // Add menu button. + var embed_object_button = $("<div><a id='embed-galaxy-object' class='panel-header-button popup' href='#'>${_('Embed Galaxy Object')}</a></div>").addClass('galaxy-page-editor-button'); + $(".wym_area_top").append(embed_object_button); + + // Add menu options. + make_popupmenu( embed_object_button, { + "Embed History": function() { + editor.dialog(Galaxy.DIALOG_EMBED_HISTORY); + }, + "Embed Dataset": function() { + editor.dialog(Galaxy.DIALOG_EMBED_DATASET); + }, + "Embed Workflow": function() { + editor.dialog(Galaxy.DIALOG_EMBED_WORKFLOW); + }, + ##"Embed Page": function() { + ## editor.dialog(Galaxy.DIALOG_EMBED_PAGE); + ##} + }); }); </script> </%def> <%def name="stylesheets()"> ${parent.stylesheets()} - ${h.css( "base", "history", "autocomplete_tagging" )} + ${h.css( "base", "autocomplete_tagging", "embed_item" )} + <style type='text/css'> + .galaxy-page-editor-button + { + position: relative; + float: left; + padding: 0.2em; + } + </style> </%def> <%def name="center_panel()"> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/page/wymiframe.mako --- a/templates/page/wymiframe.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/page/wymiframe.mako Fri Feb 12 17:10:52 2010 -0500 @@ -21,7 +21,7 @@ <title>WYMeditor iframe</title> <meta http-equiv="X-UA-Compatible" content="IE=EmulateIE7" /> <link rel="stylesheet" type="text/css" media="screen" href="/static/wymeditor/iframe/galaxy/wymiframe.css" /> - ${h.css("base", "history", "autocomplete_tagging")} + ${h.css("base", "autocomplete_tagging", "embed_item")} </head> <body class="wym_iframe text-content"></body> </html> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/sharing_base.mako --- a/templates/sharing_base.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/sharing_base.mako Fri Feb 12 17:10:52 2010 -0500 @@ -127,7 +127,7 @@ ${"/".join( url_parts[:-1] )}/<span id='item-identifier'>${url_parts[-1]}</span> </span> - <a href="#" id="edit-identifier"><img src="${h.url_for('/static/images/pencil.png')}"/></a> + <a href="#" id="edit-identifier"><img src="${h.url_for('/static/images/fugue/pencil.png')}"/></a> </blockquote> %if item.published: diff -r 8d9c8cf65dee -r fb48e02e1610 templates/workflow/display.mako --- a/templates/workflow/display.mako Fri Feb 12 17:10:05 2010 -0500 +++ b/templates/workflow/display.mako Fri Feb 12 17:10:52 2010 -0500 @@ -58,7 +58,6 @@ other_values = {} value = other_values[ param.name ] = param.get_initial_value( t, other_values ) %> - <% print param.__class__ %> ${param.get_html_field( t, value, other_values ).get_html( str(step.id) + "|" + prefix )} %endif %else: diff -r 8d9c8cf65dee -r fb48e02e1610 templates/workflow/embed.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/workflow/embed.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,15 @@ +<%inherit file="/embed_base.mako"/> +<%! + from galaxy.web.framework.helpers import iff +%> + +<%def name="content( workflow, steps )"> + %if annotation: + <div class='annotation'>${annotation}</div> + %endif + <ul> + <% num_steps = len ( steps ) %> + <li>${num_steps} step${iff( num_steps != 1, "s", "" )} + <li>Operations: ... + </ul> +</%def> diff -r 8d9c8cf65dee -r fb48e02e1610 templates/workflow/item_content.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/workflow/item_content.mako Fri Feb 12 17:10:52 2010 -0500 @@ -0,0 +1,3 @@ +<%namespace file="/workflow/display.mako" import="*" /> + +${render_item( item, item_data )} \ No newline at end of file