details: http://www.bx.psu.edu/hg/galaxy/rev/c3eccab29814 changeset: 3523:c3eccab29814 user: jeremy goecks <jeremy.goecks@emory.edu> date: Fri Mar 12 09:37:22 2010 -0500 description: Make visualizations sharable, publishable, taggable, and annotate-able. Plumbing code is in place, but UI code needs work; in particular, viewing a shared/published visualization is empty and annotations are available only via edit attributes. Some code tidying: refactoring, removing unused code. diffstat: lib/galaxy/model/__init__.py | 16 +- lib/galaxy/model/mapping.py | 73 +- lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py | 220 ++++++ lib/galaxy/tags/tag_handler.py | 1 + lib/galaxy/web/base/controller.py | 39 +- lib/galaxy/web/buildapp.py | 1 + lib/galaxy/web/controllers/history.py | 56 +- lib/galaxy/web/controllers/page.py | 21 +- lib/galaxy/web/controllers/tracks.py | 4 +- lib/galaxy/web/controllers/visualization.py | 322 +++++++++- lib/galaxy/web/controllers/workflow.py | 6 +- templates/base_panels.mako | 2 +- templates/display_common.mako | 7 +- templates/page/create.mako | 3 +- templates/panels.mako | 2 - templates/visualization/create.mako | 14 + templates/visualization/display.mako | 19 + templates/visualization/list.mako | 52 + templates/visualization/list_published.mako | 36 + 19 files changed, 773 insertions(+), 121 deletions(-) diffs (1317 lines): diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/model/__init__.py Fri Mar 12 09:37:22 2010 -0500 @@ -1527,6 +1527,11 @@ self.title = None self.config = None +class VisualizationUserShareAssociation( object ): + def __init__( self ): + self.visualization = None + self.user = None + class Tag ( object ): def __init__( self, id=None, type=None, parent_id=None, name=None ): self.id = id @@ -1558,16 +1563,16 @@ class PageTagAssociation ( ItemTagAssociation ): pass - -class WorkflowTagAssociation ( ItemTagAssociation ): - pass - + class WorkflowStepTagAssociation ( ItemTagAssociation ): pass class StoredWorkflowTagAssociation ( ItemTagAssociation ): pass +class VisualizationTagAssociation ( ItemTagAssociation ): + pass + class HistoryAnnotationAssociation( object ): pass @@ -1583,6 +1588,9 @@ class PageAnnotationAssociation( object ): pass +class VisualizationAnnotationAssociation( object ): + pass + class UserPreference ( object ): def __init__( self, name=None, value=None ): self.name = name diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/model/mapping.py --- a/lib/galaxy/model/mapping.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/model/mapping.py Fri Mar 12 09:37:22 2010 -0500 @@ -80,7 +80,7 @@ Column( "genome_build", TrimmedString( 40 ) ), Column( "importable", Boolean, default=False ), Column( "slug", TEXT, index=True ), - Column( "published", Boolean, index=True ) ) + Column( "published", Boolean, index=True, default=False ) ) HistoryUserShareAssociation.table = Table( "history_user_share_association", metadata, Column( "id", Integer, primary_key=True ), @@ -521,7 +521,7 @@ Column( "deleted", Boolean, default=False ), Column( "importable", Boolean, default=False ), Column( "slug", TEXT, index=True ), - Column( "published", Boolean, index=True ) + Column( "published", Boolean, index=True, default=False ) ) Workflow.table = Table( "workflow", metadata, @@ -721,7 +721,11 @@ Column( "latest_revision_id", Integer, ForeignKey( "visualization_revision.id", use_alter=True, name='visualization_latest_revision_id_fk' ), index=True ), Column( "title", TEXT ), - Column( "type", TEXT ) + Column( "type", TEXT ), + Column( "deleted", Boolean, default=False, index=True ), + Column( "importable", Boolean, default=False, index=True ), + Column( "slug", TEXT, index=True ), + Column( "published", Boolean, default=False, index=True ) ) VisualizationRevision.table = Table( "visualization_revision", metadata, @@ -733,6 +737,12 @@ Column( "config", JSONType ) ) +VisualizationUserShareAssociation.table = Table( "visualization_user_share_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) + ) + # Tagging tables. Tag.table = Table( "tag", metadata, @@ -768,16 +778,7 @@ Column( "user_tname", TrimmedString(255), index=True), Column( "value", TrimmedString(255), index=True), Column( "user_value", TrimmedString(255), index=True) ) - -WorkflowTagAssociation.table = Table( "workflow_tag_association", metadata, - Column( "id", Integer, primary_key=True ), - Column( "workflow_id", Integer, ForeignKey( "workflow.id" ), index=True ), - Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ), - Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), - Column( "user_tname", Unicode(255), index=True), - Column( "value", Unicode(255), index=True), - Column( "user_value", Unicode(255), index=True) ) - + StoredWorkflowTagAssociation.table = Table( "stored_workflow_tag_association", metadata, Column( "id", Integer, primary_key=True ), Column( "stored_workflow_id", Integer, ForeignKey( "stored_workflow.id" ), index=True ), @@ -805,6 +806,15 @@ Column( "value", Unicode(255), index=True), Column( "user_value", Unicode(255), index=True) ) +VisualizationTagAssociation.table = Table( "visualization_tag_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), + Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "user_tname", TrimmedString(255), index=True), + Column( "value", TrimmedString(255), index=True), + Column( "user_value", TrimmedString(255), index=True) ) + # Annotation tables. HistoryAnnotationAssociation.table = Table( "history_annotation_association", metadata, @@ -836,6 +846,12 @@ Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) + +VisualizationAnnotationAssociation.table = Table( "visualization_annotation_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "annotation", TEXT, index=True) ) # User tables. @@ -1271,8 +1287,7 @@ properties=dict( steps=relation( WorkflowStep, backref='workflow', order_by=asc(WorkflowStep.table.c.order_index), cascade="all, delete-orphan", - lazy=False ), - tags=relation(WorkflowTagAssociation, order_by=WorkflowTagAssociation.table.c.id, backref="workflows") + lazy=False ) ) ) assign_mapper( context, WorkflowStep, WorkflowStep.table, @@ -1359,8 +1374,20 @@ primaryjoin=( Visualization.table.c.id == VisualizationRevision.table.c.visualization_id ) ), latest_revision=relation( VisualizationRevision, post_update=True, primaryjoin=( Visualization.table.c.latest_revision_id == VisualizationRevision.table.c.id ), - lazy=False ) + lazy=False ), + tags=relation( VisualizationTagAssociation, order_by=VisualizationTagAssociation.table.c.id, backref="visualizations" ), + annotations=relation( VisualizationAnnotationAssociation, order_by=VisualizationAnnotationAssociation.table.c.id, backref="visualizations" ) ) ) + +# Set up proxy so that +# Visualization.users_shared_with_dot_users +# returns a list of User objects for users that a visualization is shared with. +Visualization.users_shared_with_dot_users = association_proxy( 'users_shared_with', 'user' ) + +assign_mapper( context, VisualizationUserShareAssociation, VisualizationUserShareAssociation.table, + properties=dict( user=relation( User, backref='visualizations_shared_by_others' ), + visualization=relation( Visualization, backref='users_shared_with' ) + ) ) assign_mapper( context, Tag, Tag.table, properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) ) @@ -1381,19 +1408,19 @@ assign_mapper( context, PageTagAssociation, PageTagAssociation.table, properties=dict( tag=relation(Tag, backref="tagged_pages"), user=relation( User ) ) ) - -assign_mapper( context, WorkflowTagAssociation, WorkflowTagAssociation.table, - properties=dict( tag=relation(Tag, backref="tagged_workflows"), user=relation( User ) ) - ) assign_mapper( context, StoredWorkflowTagAssociation, StoredWorkflowTagAssociation.table, - properties=dict( tag=relation(Tag, backref="tagged_stored_workflows"), user=relation( User ) ) + properties=dict( tag=relation(Tag, backref="tagged_workflows"), user=relation( User ) ) ) assign_mapper( context, WorkflowStepTagAssociation, WorkflowStepTagAssociation.table, properties=dict( tag=relation(Tag, backref="tagged_workflow_steps"), user=relation( User ) ) ) +assign_mapper( context, VisualizationTagAssociation, VisualizationTagAssociation.table, + properties=dict( tag=relation(Tag, backref="tagged_visualizations"), user=relation( User ) ) + ) + assign_mapper( context, HistoryAnnotationAssociation, HistoryAnnotationAssociation.table, properties=dict( history=relation( History ), user=relation( User ) ) ) @@ -1414,6 +1441,10 @@ properties=dict( page=relation( Page ), user=relation( User ) ) ) +assign_mapper( context, VisualizationAnnotationAssociation, VisualizationAnnotationAssociation.table, + properties=dict( visualization=relation( Visualization ), user=relation( User ) ) + ) + assign_mapper( context, UserPreference, UserPreference.table, properties = {} ) diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/model/migrate/versions/0043_visualization_sharing_tagging_annotating.py Fri Mar 12 09:37:22 2010 -0500 @@ -0,0 +1,220 @@ +""" +Migration script to create tables and columns for sharing visualizations. +""" + +from sqlalchemy import * +from sqlalchemy.orm import * +from migrate import * +from migrate.changeset import * + +import logging +log = logging.getLogger( __name__ ) + +metadata = MetaData( migrate_engine ) +db_session = scoped_session( sessionmaker( bind=migrate_engine, autoflush=False, autocommit=True ) ) + +# Sharing visualizations. + +VisualizationUserShareAssociation_table = Table( "visualization_user_share_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ) + ) + +# Tagging visualizations. + +VisualizationTagAssociation_table = Table( "visualization_tag_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), + Column( "tag_id", Integer, ForeignKey( "tag.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "user_tname", Unicode(255), index=True), + Column( "value", Unicode(255), index=True), + Column( "user_value", Unicode(255), index=True) ) + +# Annotating visualizations. + +VisualizationAnnotationAssociation_table = Table( "visualization_annotation_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "visualization_id", Integer, ForeignKey( "visualization.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "annotation", TEXT, index=False ) ) + +Visualiation_table = Table( "visualization", metadata, autoload=True ) + +def upgrade(): + print __doc__ + metadata.reflect() + + # Create visualization_user_share_association table. + try: + VisualizationUserShareAssociation_table.create() + except Exception, e: + print "Creating visualization_user_share_association table failed: %s" % str( e ) + log.debug( "Creating visualization_user_share_association table failed: %s" % str( e ) ) + + # Get default boolean value 'false' so that columns can be initialized. + if migrate_engine.name == 'mysql': + default_false = "0" + elif migrate_engine.name == 'sqlite': + default_false = "'false'" + elif migrate_engine.name == 'postgres': + default_false = "false" + + # Add columns & create indices for supporting sharing to visualization table. + deleted_column = Column( "deleted", Boolean, default=False, index=True ) + importable_column = Column( "importable", Boolean, default=False, index=True ) + slug_column = Column( "slug", TEXT, index=True ) + published_column = Column( "published", Boolean, index=True ) + + try: + # Add column. + deleted_column.create( Visualiation_table ) + assert deleted_column is Visualiation_table.c.deleted + + # Fill column with default value. + cmd = "UPDATE visualization SET deleted = %s" % default_false + db_session.execute( cmd ) + except Exception, e: + print "Adding deleted column to visualization table failed: %s" % str( e ) + log.debug( "Adding deleted column to visualization table failed: %s" % str( e ) ) + + try: + i = Index( "ix_visualization_deleted", Visualiation_table.c.deleted ) + i.create() + except Exception, e: + print "Adding index 'ix_visualization_deleted' failed: %s" % str( e ) + log.debug( "Adding index 'ix_visualization_deleted' failed: %s" % str( e ) ) + + try: + # Add column. + importable_column.create( Visualiation_table ) + assert importable_column is Visualiation_table.c.importable + + # Fill column with default value. + cmd = "UPDATE visualization SET importable = %s" % default_false + db_session.execute( cmd ) + except Exception, e: + print "Adding importable column to visualization table failed: %s" % str( e ) + log.debug( "Adding importable column to visualization table failed: %s" % str( e ) ) + + i = Index( "ix_visualization_importable", Visualiation_table.c.importable ) + try: + i.create() + except Exception, e: + print "Adding index 'ix_visualization_importable' failed: %s" % str( e ) + log.debug( "Adding index 'ix_visualization_importable' failed: %s" % str( e ) ) + + try: + slug_column.create( Visualiation_table ) + assert slug_column is Visualiation_table.c.slug + except Exception, e: + print "Adding slug column to visualization table failed: %s" % str( e ) + log.debug( "Adding slug column to visualization table failed: %s" % str( e ) ) + + try: + if migrate_engine.name == 'mysql': + # Have to create index manually. + cmd = "CREATE INDEX ix_visualization_slug ON visualization ( slug ( 100 ) )" + db_session.execute( cmd ) + else: + i = Index( "ix_visualization_slug", Visualiation_table.c.slug ) + i.create() + except Exception, e: + print "Adding index 'ix_visualization_slug' failed: %s" % str( e ) + log.debug( "Adding index 'ix_visualization_slug' failed: %s" % str( e ) ) + + try: + # Add column. + published_column.create( Visualiation_table ) + assert published_column is Visualiation_table.c.published + + # Fill column with default value. + cmd = "UPDATE visualization SET published = %s" % default_false + db_session.execute( cmd ) + except Exception, e: + print "Adding published column to visualization table failed: %s" % str( e ) + log.debug( "Adding published column to visualization table failed: %s" % str( e ) ) + + i = Index( "ix_visualization_published", Visualiation_table.c.published ) + try: + i.create() + except Exception, e: + print "Adding index 'ix_visualization_published' failed: %s" % str( e ) + log.debug( "Adding index 'ix_visualization_published' failed: %s" % str( e ) ) + + # Create visualization_tag_association table. + try: + VisualizationTagAssociation_table.create() + except Exception, e: + print str(e) + log.debug( "Creating visualization_tag_association table failed: %s" % str( e ) ) + + # Create visualization_annotation_association table. + try: + VisualizationAnnotationAssociation_table.create() + except Exception, e: + print str(e) + log.debug( "Creating visualization_annotation_association table failed: %s" % str( e ) ) + + # Need to create index for visualization annotation manually to deal with errors. + try: + if migrate_engine.name == 'mysql': + # Have to create index manually. + cmd = "CREATE INDEX ix_visualization_annotation_association_annotation ON visualization_annotation_association ( annotation ( 100 ) )" + db_session.execute( cmd ) + else: + i = Index( "ix_visualization_annotation_association_annotation", VisualizationAnnotationAssociation_table.c.annotation ) + i.create() + except Exception, e: + print "Adding index 'ix_visualization_annotation_association_annotation' failed: %s" % str( e ) + log.debug( "Adding index 'ix_visualization_annotation_association_annotation' failed: %s" % str( e ) ) + +def downgrade(): + metadata.reflect() + + # Drop visualization_user_share_association table. + try: + VisualizationUserShareAssociation_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping visualization_user_share_association table failed: %s" % str( e ) ) + + # Drop columns for supporting sharing from visualization table. + try: + Visualiation_table.c.deleted.drop() + except Exception, e: + print "Dropping deleted column from visualization table failed: %s" % str( e ) + log.debug( "Dropping deleted column from visualization table failed: %s" % str( e ) ) + + try: + Visualiation_table.c.importable.drop() + except Exception, e: + print "Dropping importable column from visualization table failed: %s" % str( e ) + log.debug( "Dropping importable column from visualization table failed: %s" % str( e ) ) + + try: + Visualiation_table.c.slug.drop() + except Exception, e: + print "Dropping slug column from visualization table failed: %s" % str( e ) + log.debug( "Dropping slug column from visualization table failed: %s" % str( e ) ) + + try: + Visualiation_table.c.published.drop() + except Exception, e: + print "Dropping published column from visualization table failed: %s" % str( e ) + log.debug( "Dropping published column from visualization table failed: %s" % str( e ) ) + + # Drop visualization_tag_association table. + try: + VisualizationTagAssociation_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping visualization_tag_association table failed: %s" % str( e ) ) + + # Drop visualization_annotation_association table. + try: + VisualizationAnnotationAssociation_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping visualization_annotation_association table failed: %s" % str( e ) ) \ No newline at end of file diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/tags/tag_handler.py --- a/lib/galaxy/tags/tag_handler.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/tags/tag_handler.py Fri Mar 12 09:37:22 2010 -0500 @@ -34,6 +34,7 @@ ItemTagAssocInfo( model.HistoryDatasetAssociation, model.HistoryDatasetAssociationTagAssociation, model.HistoryDatasetAssociationTagAssociation.table.c.history_dataset_association_id ) item_tag_assoc_info["Page"] = ItemTagAssocInfo( model.Page, model.PageTagAssociation, model.PageTagAssociation.table.c.page_id ) item_tag_assoc_info["StoredWorkflow"] = ItemTagAssocInfo( model.StoredWorkflow, model.StoredWorkflowTagAssociation, model.StoredWorkflowTagAssociation.table.c.stored_workflow_id ) + item_tag_assoc_info["Visualization"] = ItemTagAssocInfo( model.Visualization, model.VisualizationTagAssociation, model.VisualizationTagAssociation.table.c.visualization_id ) def get_tag_assoc_class(self, item_class): """ Returns tag association class for item class. """ diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/base/controller.py Fri Mar 12 09:37:22 2010 -0500 @@ -16,6 +16,9 @@ # States for passing messages SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" + +# RE that tests for valid slug. +VALID_SLUG_RE = re.compile( "^[a-z0-9\-]+$" ) class BaseController( object ): """ @@ -40,6 +43,8 @@ item_class = model.Page elif class_name == 'StoredWorkflow': item_class = model.StoredWorkflow + elif class_name == 'Visualization': + item_class = model.Visualization else: item_class = None return item_class @@ -76,6 +81,8 @@ annotation_assoc = annotation_assoc.filter_by( workflow_step=item ) elif item.__class__ == model.Page: annotation_assoc = annotation_assoc.filter_by( page=item ) + elif item.__class__ == model.Visualization: + annotation_assoc = annotation_assoc.filter_by( visualization=item ) return annotation_assoc.first() def add_item_annotation( self, trans, item, annotation ): @@ -153,6 +160,19 @@ truncated = False return truncated, dataset_data +class UsesVisualization( SharableItemSecurity ): + """ Mixin for controllers that use Visualization objects. """ + + def get_visualization( self, trans, id, check_ownership=True, check_accessible=False ): + """ Get a Visualization from the database by id, verifying ownership. """ + # Load workflow from database + id = trans.security.decode_id( id ) + visualization = trans.sa_session.query( model.Visualization ).get( id ) + if not visualization: + error( "Visualization not found" ) + else: + return self.security_check( trans.get_user(), stored, check_ownership, check_accessible ) + class UsesStoredWorkflow( SharableItemSecurity ): """ Mixin for controllers that use StoredWorkflow objects. """ @@ -240,6 +260,12 @@ pass @web.expose + @web.require_login( "share Galaxy items" ) + def share( self, trans, id=None, email="", **kwd ): + """ Handle sharing an item with a particular user. """ + pass + + @web.expose def display_by_username_and_slug( self, trans, username, slug ): """ Display item by username and slug. """ pass @@ -262,13 +288,18 @@ def _make_item_accessible( self, sa_session, item ): """ Makes item accessible--viewable and importable--and sets item's slug. Does not flush/commit changes, however. Item must have name, user, importable, and slug attributes. """ item.importable = True - self.set_item_slug( sa_session, item ) + self.create_item_slug( sa_session, item ) - def set_item_slug( self, sa_session, item ): - """ Set item slug. Slug is unique among user's importable items for item's class. Returns true if item's slug was set; false otherwise. """ + def create_item_slug( self, sa_session, item ): + """ Create item slug. Slug is unique among user's importable items for item's class. Returns true if item's slug was set; false otherwise. """ if item.slug is None or item.slug == "": + # Item can have either a name or a title. + if hasattr( item, 'name' ): + item_name = item.name + elif hasattr( item, 'title' ): + item_name = item.title # Replace whitespace with '-' - slug_base = re.sub( "\s+", "-", item.name.lower() ) + slug_base = re.sub( "\s+", "-", item_name.lower() ) # Remove all non-alphanumeric characters. slug_base = re.sub( "[^a-zA-Z0-9\-]", "", slug_base ) # Remove trailing '-'. diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/buildapp.py --- a/lib/galaxy/web/buildapp.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/buildapp.py Fri Mar 12 09:37:22 2010 -0500 @@ -79,6 +79,7 @@ webapp.add_route( '/u/:username/p/:slug', controller='page', action='display_by_username_and_slug' ) webapp.add_route( '/u/:username/h/:slug', controller='history', action='display_by_username_and_slug' ) webapp.add_route( '/u/:username/w/:slug', controller='workflow', action='display_by_username_and_slug' ) + webapp.add_route( '/u/:username/v/:slug', controller='visualization', action='display_by_username_and_slug' ) webapp.finalize_config() # Wrap the webapp in some useful middleware if kwargs.get( 'middleware', True ): diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/controllers/history.py --- a/lib/galaxy/web/controllers/history.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/controllers/history.py Fri Mar 12 09:37:22 2010 -0500 @@ -440,7 +440,7 @@ """ Returns history's name and link. """ history = self.get_history( trans, id, False ) - if self.set_item_slug( trans.sa_session, history ): + if self.create_item_slug( trans.sa_session, history ): trans.sa_session.flush() return_dict = { "name" : history.name, "link" : url_for( action="display_by_username_and_slug", username=history.user.username, slug=history.slug ) } return return_dict @@ -652,58 +652,6 @@ session.flush() return trans.fill_template( "/sharing_base.mako", item=history ) - - ## TODO: remove this method when history sharing has been verified to work correctly with new sharing() method. - @web.expose - @web.require_login( "share histories with other users" ) - def sharing_old( self, trans, histories=[], id=None, **kwd ): - """Performs sharing of histories among users.""" - # histories looks like: [ historyX, historyY ] - params = util.Params( kwd ) - msg = util.restore_text ( params.get( 'msg', '' ) ) - if id: - ids = util.listify( id ) - if ids: - histories = [ self.get_history( trans, history_id ) for history_id in ids ] - for history in histories: - trans.sa_session.add( history ) - if params.get( 'enable_import_via_link', False ): - self._make_item_accessible( trans.sa_session, history ) - trans.sa_session.flush() - elif params.get( 'disable_import_via_link', False ): - history.importable = False - trans.sa_session.flush() - elif params.get( 'unshare_user', False ): - user = trans.sa_session.query( trans.app.model.User ).get( trans.security.decode_id( kwd[ 'unshare_user' ] ) ) - if not user: - msg = 'History (%s) does not seem to be shared with user (%s)' % ( history.name, user.email ) - return trans.fill_template( 'history/sharing.mako', histories=histories, msg=msg, messagetype='error' ) - husas = trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ).filter_by( user=user, history=history ).all() - if husas: - for husa in husas: - trans.sa_session.delete( husa ) - trans.sa_session.flush() - histories = [] - # Get all histories that have been shared with others - husas = trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ) \ - .join( "history" ) \ - .filter( and_( trans.app.model.History.user == trans.user, - trans.app.model.History.deleted == False ) ) \ - .order_by( trans.app.model.History.table.c.name ) - for husa in husas: - history = husa.history - if history not in histories: - histories.append( history ) - # Get all histories that are importable - importables = trans.sa_session.query( trans.app.model.History ) \ - .filter_by( user=trans.user, importable=True, deleted=False ) \ - .order_by( trans.app.model.History.table.c.name ) - for importable in importables: - if importable not in histories: - histories.append( importable ) - # Sort the list of histories by history.name - histories.sort( key=operator.attrgetter( 'name') ) - return trans.fill_template( 'history/sharing.mako', histories=histories, msg=msg, messagetype='done' ) @web.expose @web.require_login( "share histories with other users" ) @@ -975,7 +923,7 @@ share.history = history share.user = send_to_user trans.sa_session.add( share ) - self.set_item_slug( trans.sa_session, history ) + self.create_item_slug( trans.sa_session, history ) trans.sa_session.flush() if history not in shared_histories: shared_histories.append( history ) diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/controllers/page.py --- a/lib/galaxy/web/controllers/page.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/controllers/page.py Fri Mar 12 09:37:22 2010 -0500 @@ -4,10 +4,6 @@ from galaxy.util.odict import odict from galaxy.util.json import from_json_string -import re - -VALID_SLUG_RE = re.compile( "^[a-z0-9\-]+$" ) - def format_bool( b ): if b: return "yes" @@ -45,8 +41,8 @@ ] operations = [ grids.DisplayByUsernameAndSlugGridOperation( "View", allow_multiple=False ), + grids.GridOperation( "Edit content", allow_multiple=False, url_args=dict( action='edit_content') ), grids.GridOperation( "Edit attributes", allow_multiple=False, url_args=dict( action='edit') ), - grids.GridOperation( "Edit content", allow_multiple=False, url_args=dict( action='edit_content') ), grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ), grids.GridOperation( "Delete", confirm="Are you sure you want to delete this page?" ), ] @@ -62,7 +58,7 @@ default_sort_key = "-update_time" default_filter = dict( title="All", username="All" ) columns = [ - grids.PublicURLColumn( "Title", key="title", model_class=model.Page, filterable="advanced"), + grids.PublicURLColumn( "Title", key="title", model_class=model.Page, filterable="advanced" ), grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_class=model.Page, model_annotation_association_class=model.PageAnnotationAssociation, filterable="advanced" ), grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced", sortable=False ), grids.CommunityTagsColumn( "Community Tags", "tags", model.Page, model.PageTagAssociation, filterable="advanced", grid_name="PageAllPublishedGrid" ), @@ -356,10 +352,10 @@ template="page/create.mako" ) @web.expose - @web.require_login( "create pages" ) + @web.require_login( "edit pages" ) def edit( self, trans, id, page_title="", page_slug="", page_annotation="" ): """ - Create a new page + Edit a page's attributes. """ encoded_id = id id = trans.security.decode_id( id ) @@ -456,6 +452,7 @@ @web.expose @web.require_login( "use Galaxy pages" ) def share( self, trans, id, email="" ): + """ Handle sharing with an individual user. """ msg = mtype = None page = trans.sa_session.query( model.Page ).get( trans.security.decode_id( id ) ) if email: @@ -468,18 +465,18 @@ msg = ( "User '%s' does not exist" % email ) elif other == trans.get_user(): mtype = "error" - msg = ( "You cannot share a workflow with yourself" ) + msg = ( "You cannot share a page with yourself" ) elif trans.sa_session.query( model.PageUserShareAssociation ) \ .filter_by( user=other, page=page ).count() > 0: mtype = "error" - msg = ( "Workflow already shared with '%s'" % email ) + msg = ( "Page already shared with '%s'" % email ) else: share = model.PageUserShareAssociation() share.page = page share.user = other session = trans.sa_session session.add( share ) - self.set_item_slug( session, page ) + self.create_item_slug( session, page ) session.flush() trans.set_message( "Page '%s' shared with user '%s'" % ( page.title, other.email ) ) return trans.response.send_redirect( url_for( controller='page', action='sharing', id=id ) ) @@ -609,7 +606,7 @@ """ Returns page's name and link. """ page = self.get_page( trans, id ) - if self.set_item_slug( trans.sa_session, page ): + if self.create_item_slug( trans.sa_session, page ): trans.sa_session.flush() return_dict = { "name" : page.title, "link" : url_for( action="display_by_username_and_slug", username=page.user.username, slug=page.slug ) } return return_dict diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/controllers/tracks.py --- a/lib/galaxy/web/controllers/tracks.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/controllers/tracks.py Fri Mar 12 09:37:22 2010 -0500 @@ -262,7 +262,9 @@ @web.json def save( self, trans, **kwargs ): session = trans.sa_session - vis_id = kwargs['vis_id'].strip('"') + vis_id = "undefined" + if 'vis_id' in kwargs: + vis_id = kwargs['vis_id'].strip('"') dbkey = kwargs['dbkey'] if vis_id == "undefined": # new vis diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/controllers/visualization.py --- a/lib/galaxy/web/controllers/visualization.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/controllers/visualization.py Fri Mar 12 09:37:22 2010 -0500 @@ -1,35 +1,325 @@ from galaxy.web.base.controller import * -from galaxy.web.framework.helpers import time_ago, grids +from galaxy.web.framework.helpers import time_ago, grids, iff from galaxy.util.sanitize_html import sanitize_html class VisualizationListGrid( grids.Grid ): # Grid definition - title = "Visualizations" + title = "Saved Visualizations" model_class = model.Visualization default_sort_key = "-update_time" + default_filter = dict( title="All", deleted="False", tags="All", sharing="All" ) columns = [ - grids.GridColumn( "Title", key="title", attach_popup=True, + grids.TextColumn( "Title", key="title", model_class=model.Visualization, attach_popup=True, link=( lambda item: dict( controller="tracks", action="browser", id=item.id ) ) ), - grids.GridColumn( "Type", key="type" ), + grids.TextColumn( "Type", key="type", model_class=model.Visualization ), + grids.IndividualTagsColumn( "Tags", "tags", model.Visualization, model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationListGrid" ), + grids.SharingStatusColumn( "Sharing", key="sharing", model_class=model.Visualization, filterable="advanced", sortable=False ), grids.GridColumn( "Created", key="create_time", format=time_ago ), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ), - ] - ## global_actions = [ - ## grids.GridAction( "Add new page", dict( action='create' ) ) - ## ] + ] + columns.append( + grids.MulticolFilterColumn( + "Search", + cols_to_filter=[ columns[0], columns[2] ], + key="free-text-search", visible=False, filterable="standard" ) + ) operations = [ - grids.GridOperation( "View", allow_multiple=False, url_args=dict( controller="tracks", action='browser' ) ), + grids.GridOperation( "Edit content", allow_multiple=False, url_args=dict( controller='tracks', action='browser' ) ), + grids.GridOperation( "Edit attributes", allow_multiple=False, url_args=dict( action='edit') ), + grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ), + grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), async_compatible=True, confirm="Are you sure you want to delete this visualization?" ), ] def apply_default_filter( self, trans, query, **kwargs ): - return query.filter_by( user=trans.user ) + return query.filter_by( user=trans.user, deleted=False ) + +class VisualizationAllPublishedGrid( grids.Grid ): + # Grid definition + use_panels = True + use_async = True + title = "Published Visualizations" + model_class = model.Visualization + default_sort_key = "-update_time" + default_filter = dict( title="All", username="All" ) + columns = [ + grids.PublicURLColumn( "Title", key="title", model_class=model.Visualization, filterable="advanced" ), + grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_class=model.Visualization, model_annotation_association_class=model.VisualizationAnnotationAssociation, filterable="advanced" ), + grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced", sortable=False ), + grids.CommunityTagsColumn( "Community Tags", "tags", model.Visualization, model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationAllPublishedGrid" ), + grids.GridColumn( "Last Updated", key="update_time", format=time_ago ) + ] + columns.append( + grids.MulticolFilterColumn( + "Search", + cols_to_filter=[ columns[0], columns[1], columns[2], columns[3] ], + key="free-text-search", visible=False, filterable="standard" ) + ) + def build_initial_query( self, session ): + # Join so that searching history.user makes sense. + return session.query( self.model_class ).join( model.User.table ) + def apply_default_filter( self, trans, query, **kwargs ): + return query.filter( self.model_class.deleted==False ).filter( self.model_class.published==True ) -class VisualizationController( BaseController ): - list_grid = VisualizationListGrid() + +class VisualizationController( BaseController, Sharable, UsesAnnotations, UsesVisualization ): + _user_list_grid = VisualizationListGrid() + _published_list_grid = VisualizationAllPublishedGrid() + @web.expose - @web.require_login() - def index( self, trans, *args, **kwargs ): - return trans.fill_template( "panels.mako", grid=self.list_grid( trans, *args, **kwargs ), active_view='visualization', main_url=url_for( action='list' ) ) + def list_published( self, trans, *args, **kwargs ): + grid = self._published_list_grid( trans, **kwargs ) + if 'async' in kwargs: + return grid + else: + # Render grid wrapped in panels + return trans.fill_template( "visualization/list_published.mako", grid=grid ) + + @web.expose + @web.require_login("use Galaxy visualizations") + def list( self, trans, *args, **kwargs ): + # Handle operation + if 'operation' in kwargs and 'id' in kwargs: + session = trans.sa_session + operation = kwargs['operation'].lower() + ids = util.listify( kwargs['id'] ) + for id in ids: + item = session.query( model.Visualization ).get( trans.security.decode_id( id ) ) + if operation == "delete": + item.deleted = True + if operation == "share or publish": + return self.sharing( trans, **kwargs ) + session.flush() + + # Build list of visualizations shared with user. + shared_by_others = trans.sa_session \ + .query( model.VisualizationUserShareAssociation ) \ + .filter_by( user=trans.get_user() ) \ + .join( model.Visualization.table ) \ + .filter( model.Visualization.deleted == False ) \ + .order_by( desc( model.Visualization.update_time ) ) \ + .all() + + return trans.fill_template( "visualization/list.mako", grid=self._user_list_grid( trans, *args, **kwargs ), shared_by_others=shared_by_others ) + + @web.expose + @web.require_login( "modify Galaxy visualizations" ) + def set_slug_async( self, trans, id, new_slug ): + """ Set item slug asynchronously. """ + visualization = self.get_visualization( trans, id ) + if visualization: + visualization.slug = new_slug + trans.sa_session.flush() + return visualization.slug + + @web.expose + @web.require_login( "share Galaxy visualizations" ) + def sharing( self, trans, id, **kwargs ): + """ Handle visualization sharing. """ + + # Get session and visualization. + session = trans.sa_session + visualization = trans.sa_session.query( model.Visualization ).get( trans.security.decode_id( id ) ) + + # Do operation on visualization. + if 'make_accessible_via_link' in kwargs: + self._make_item_accessible( trans.sa_session, visualization ) + elif 'make_accessible_and_publish' in kwargs: + self._make_item_accessible( trans.sa_session, visualization ) + visualization.published = True + elif 'publish' in kwargs: + visualization.published = True + elif 'disable_link_access' in kwargs: + visualization.importable = False + elif 'unpublish' in kwargs: + visualization.published = False + elif 'disable_link_access_and_unpublish' in kwargs: + visualization.importable = visualization.published = False + elif 'unshare_user' in kwargs: + user = session.query( model.User ).get( trans.security.decode_id( kwargs['unshare_user' ] ) ) + if not user: + error( "User not found for provided id" ) + association = session.query( model.VisualizationUserShareAssociation ) \ + .filter_by( user=user, visualization=visualization ).one() + session.delete( association ) + + session.flush() + + return trans.fill_template( "/sharing_base.mako", item=visualization ) + + @web.expose + @web.require_login( "share Galaxy visualizations" ) + def share( self, trans, id=None, email="", **kwd ): + """ Handle sharing a visualization with a particular user. """ + msg = mtype = None + visualization = trans.sa_session.query( model.Visualization ).get( trans.security.decode_id( id ) ) + if email: + other = trans.sa_session.query( model.User ) \ + .filter( and_( model.User.table.c.email==email, + model.User.table.c.deleted==False ) ) \ + .first() + if not other: + mtype = "error" + msg = ( "User '%s' does not exist" % email ) + elif other == trans.get_user(): + mtype = "error" + msg = ( "You cannot share a visualization with yourself" ) + elif trans.sa_session.query( model.VisualizationUserShareAssociation ) \ + .filter_by( user=other, visualization=visualization ).count() > 0: + mtype = "error" + msg = ( "Visualization already shared with '%s'" % email ) + else: + share = model.VisualizationUserShareAssociation() + share.visualization = visualization + share.user = other + session = trans.sa_session + session.add( share ) + self.create_item_slug( session, visualization ) + session.flush() + trans.set_message( "Visualization '%s' shared with user '%s'" % ( visualization.title, other.email ) ) + return trans.response.send_redirect( url_for( action='sharing', id=id ) ) + return trans.fill_template( "/share_base.mako", + message = msg, + messagetype = mtype, + item=visualization, + email=email ) + + + @web.expose + def display_by_username_and_slug( self, trans, username, slug ): + """ Display visualization based on a username and slug. """ + + # Get visualization. + session = trans.sa_session + user = session.query( model.User ).filter_by( username=username ).first() + visualization = trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=slug, deleted=False ).first() + if visualization is None: + raise web.httpexceptions.HTTPNotFound() + # Security check raises error if user cannot access visualization. + self.security_check( trans.get_user(), visualization, False, True) + return trans.fill_template_mako( "visualization/display.mako", item=visualization, item_data=None, content_only=True ) + + @web.expose + @web.json + @web.require_login( "get item name and link" ) + def get_name_and_link_async( self, trans, id=None ): + """ Returns visualization's name and link. """ + visualization = self.get_visualization( trans, id ) + + if self.create_item_slug( trans.sa_session, visualization ): + trans.sa_session.flush() + return_dict = { "name" : visualization.title, "link" : url_for( action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug ) } + return return_dict + + @web.expose + @web.require_login("get item content asynchronously") + def get_item_content_async( self, trans, id ): + """ Returns item content in HTML format. """ + pass + + @web.expose + @web.require_login( "create visualizations" ) + def create( self, trans, visualization_title="", visualization_slug="", visualization_annotation="" ): + """ + Create a new visualization + """ + user = trans.get_user() + visualization_title_err = visualization_slug_err = visualization_annotation_err = "" + if trans.request.method == "POST": + if not visualization_title: + visualization_title_err = "visualization name is required" + elif not visualization_slug: + visualization_slug_err = "visualization id is required" + elif not VALID_SLUG_RE.match( visualization_slug ): + visualization_slug_err = "visualization identifier must consist of only lowercase letters, numbers, and the '-' character" + elif trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=visualization_slug, deleted=False ).first(): + visualization_slug_err = "visualization id must be unique" + else: + # Create the new stored visualization + visualization = model.Visualization() + visualization.title = visualization_title + visualization.slug = visualization_slug + visualization_annotation = sanitize_html( visualization_annotation, 'utf-8', 'text/html' ) + self.add_item_annotation( trans, visualization, visualization_annotation ) + visualization.user = user + # And the first (empty) visualization revision + visualization_revision = model.VisualizationRevision() + visualization_revision.title = visualization_title + visualization_revision.visualization = visualization + visualization.latest_revision = visualization_revision + visualization_revision.content = "" + # Persist + session = trans.sa_session + session.add( visualization ) + session.flush() + # Display the management visualization + ## trans.set_message( "Visualization '%s' created" % visualization.title ) + return trans.response.send_redirect( web.url_for( action='list' ) ) + return trans.show_form( + web.FormBuilder( web.url_for(), "Create new visualization", submit_text="Submit" ) + .add_text( "visualization_title", "Visualization title", value=visualization_title, error=visualization_title_err ) + .add_text( "visualization_slug", "Visualization identifier", value=visualization_slug, error=visualization_slug_err, + help="""A unique identifier that will be used for + public links to this visualization. A default is generated + from the visualization title, but can be edited. This field + must contain only lowercase letters, numbers, and + the '-' character.""" ) + .add_text( "visualization_annotation", "Visualization annotation", value=visualization_annotation, error=visualization_annotation_err, + help="A description of the visualization; annotation is shown alongside published visualizations."), + template="visualization/create.mako" ) + + @web.expose + @web.require_login( "edit visualizations" ) + def edit( self, trans, id, visualization_title="", visualization_slug="", visualization_annotation="" ): + """ + Edit a visualization's attributes. + """ + encoded_id = id + id = trans.security.decode_id( id ) + session = trans.sa_session + visualization = session.query( model.Visualization ).get( id ) + user = trans.user + assert visualization.user == user + visualization_title_err = visualization_slug_err = visualization_annotation_err = "" + if trans.request.method == "POST": + if not visualization_title: + visualization_title_err = "Visualization name is required" + elif not visualization_slug: + visualization_slug_err = "Visualization id is required" + elif not VALID_SLUG_RE.match( visualization_slug ): + visualization_slug_err = "Visualization identifier must consist of only lowercase letters, numbers, and the '-' character" + elif visualization_slug != visualization.slug and trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=visualization_slug, deleted=False ).first(): + visualization_slug_err = "Visualization id must be unique" + elif not visualization_annotation: + visualization_annotation_err = "Visualization annotation is required" + else: + visualization.title = visualization_title + visualization.slug = visualization_slug + visualization_annotation = sanitize_html( visualization_annotation, 'utf-8', 'text/html' ) + self.add_item_annotation( trans, visualization, visualization_annotation ) + session.flush() + # Redirect to visualization list. + return trans.response.send_redirect( web.url_for( action='list' ) ) + else: + visualization_title = visualization.title + # Create slug if it's not already set. + if visualization.slug is None: + self.create_item_slug( trans.sa_session, visualization ) + visualization_slug = visualization.slug + visualization_annotation = self.get_item_annotation_str( trans.sa_session, trans.get_user(), visualization ) + if not visualization_annotation: + visualization_annotation = "" + return trans.show_form( + web.FormBuilder( web.url_for( id=encoded_id ), "Edit visualization attributes", submit_text="Submit" ) + .add_text( "visualization_title", "Visualization title", value=visualization_title, error=visualization_title_err ) + .add_text( "visualization_slug", "Visualization identifier", value=visualization_slug, error=visualization_slug_err, + help="""A unique identifier that will be used for + public links to this visualization. A default is generated + from the visualization title, but can be edited. This field + must contain only lowercase letters, numbers, and + the '-' character.""" ) + .add_text( "visualization_annotation", "Visualization annotation", value=visualization_annotation, error=visualization_annotation_err, + help="A description of the visualization; annotation is shown alongside published visualizations."), + template="visualization/create.mako" ) # @web.expose # @web.require_login() @@ -42,6 +332,6 @@ # # Build grid # grid = self.list( trans, *args, **kwargs ) # # Render grid wrapped in panels - # return trans.fill_template( "page/index.mako", grid=grid ) + # return trans.fill_template( "visualization/index.mako", grid=grid ) \ No newline at end of file diff -r 582fd1777763 -r c3eccab29814 lib/galaxy/web/controllers/workflow.py --- a/lib/galaxy/web/controllers/workflow.py Thu Mar 11 15:54:13 2010 -0500 +++ b/lib/galaxy/web/controllers/workflow.py Fri Mar 12 09:37:22 2010 -0500 @@ -123,7 +123,7 @@ # Legacy issue: all shared workflows must have slugs. slug_set = False for workflow_assoc in shared_by_others: - slug_set = self.set_item_slug( trans.sa_session, workflow_assoc.stored_workflow ) + slug_set = self.create_item_slug( trans.sa_session, workflow_assoc.stored_workflow ) if slug_set: trans.sa_session.flush() @@ -224,7 +224,7 @@ share.user = other session = trans.sa_session session.add( share ) - self.set_item_slug( session, stored ) + self.create_item_slug( session, stored ) session.flush() trans.set_message( "Workflow '%s' shared with user '%s'" % ( stored.name, other.email ) ) return trans.response.send_redirect( url_for( controller='workflow', action='sharing', id=id ) ) @@ -401,7 +401,7 @@ """ Returns workflow's name and link. """ stored = self.get_stored_workflow( trans, id ) - if self.set_item_slug( trans.sa_session, stored ): + if self.create_item_slug( trans.sa_session, stored ): trans.sa_session.flush() return_dict = { "name" : stored.name, "link" : url_for( action="display_by_username_and_slug", username=stored.user.username, slug=stored.slug ) } return return_dict diff -r 582fd1777763 -r c3eccab29814 templates/base_panels.mako --- a/templates/base_panels.mako Thu Mar 11 15:54:13 2010 -0500 +++ b/templates/base_panels.mako Fri Mar 12 09:37:22 2010 -0500 @@ -207,7 +207,7 @@ <ul> <li><a href="${h.url_for( controller='/tracks', action='index' )}">Build track browser</a></li> <li><hr style="color: inherit; background-color: gray"/></li> - <li><a href="${h.url_for( controller='/visualization', action='index' )}">Stored visualizations</a></li> + <li><a href="${h.url_for( controller='/visualization', action='list' )}">Stored visualizations</a></li> </ul> </div> </td> diff -r 582fd1777763 -r c3eccab29814 templates/display_common.mako --- a/templates/display_common.mako Thu Mar 11 15:54:13 2010 -0500 +++ b/templates/display_common.mako Fri Mar 12 09:37:22 2010 -0500 @@ -18,8 +18,11 @@ <%def name="get_item_name( item )"> <% + # Start with exceptions, end with default. if type( item ) is model.Page: return item.title + elif type( item ) is model.Visualization: + return item.title if hasattr( item, 'get_display_name'): return item.get_display_name() return item.name @@ -29,7 +32,7 @@ ## Get plural display name for a class. <%def name="get_class_plural_display_name( a_class )"> <% - ## Start with exceptions, end with default. + # Start with exceptions, end with default. if a_class is model.History: return "Histories" elif a_class is model.FormDefinitionCurrent: @@ -89,6 +92,8 @@ return "dataset" elif isinstance( item, model.Page ): return "page" + elif isinstance( item, model.Visualization ): + return "visualization" %> </%def> diff -r 582fd1777763 -r c3eccab29814 templates/page/create.mako --- a/templates/page/create.mako Thu Mar 11 15:54:13 2010 -0500 +++ b/templates/page/create.mako Fri Mar 12 09:37:22 2010 -0500 @@ -8,8 +8,7 @@ var page_slug = $("input[name=page_slug]"); page_name.keyup(function(){ page_slug.val( $(this).val().replace(/\s+/g,'-').replace(/[^a-zA-Z0-9\-]/g,'').toLowerCase() ) - }); - + }); }) </script> </%def> \ No newline at end of file diff -r 582fd1777763 -r c3eccab29814 templates/panels.mako --- a/templates/panels.mako Thu Mar 11 15:54:13 2010 -0500 +++ b/templates/panels.mako Fri Mar 12 09:37:22 2010 -0500 @@ -10,11 +10,9 @@ </%def> <%def name="center_panel()"> - <div style="overflow: auto; height: 100%;"> <div style="padding: 10px"> ${grid} </div> </div> - </%def> diff -r 582fd1777763 -r c3eccab29814 templates/visualization/create.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/visualization/create.mako Fri Mar 12 09:37:22 2010 -0500 @@ -0,0 +1,14 @@ +<%inherit file="/form.mako"/> + +<%def name="javascripts()"> +${parent.javascripts()} +<script type="text/javascript"> +$(function(){ + var visualization_name = $("input[name=visualization_title]"); + var visualization_slug = $("input[name=visualization_slug]"); + visualization_name.keyup(function(){ + visualization_slug.val( $(this).val().replace(/\s+/g,'-').replace(/[^a-zA-Z0-9\-]/g,'').toLowerCase() ) + }); +}) +</script> +</%def> \ No newline at end of file diff -r 582fd1777763 -r c3eccab29814 templates/visualization/display.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/visualization/display.mako Fri Mar 12 09:37:22 2010 -0500 @@ -0,0 +1,19 @@ +<%inherit file="/display_base.mako"/> + +<%def name="javascripts()"> + ${parent.javascripts()} + ## Need visualization JS. +</%def> + +<%def name="stylesheets()"> + ${parent.stylesheets()} + ## Need visualization CSS. +</%def> + +<%def name="render_item_links( visualization )"> + ## TODO +</%def> + +<%def name="render_item( visualization, visualization_data )"> + ## TODO +</%def> \ No newline at end of file diff -r 582fd1777763 -r c3eccab29814 templates/visualization/list.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/visualization/list.mako Fri Mar 12 09:37:22 2010 -0500 @@ -0,0 +1,52 @@ +<%inherit file="/base_panels.mako"/> + +<%def name="init()"> +<% + self.has_left_panel=False + self.has_right_panel=False + self.active_view="visualization" + self.message_box_visible=False +%> +</%def> + +<%def name="center_panel()"> + + <div style="overflow: auto; height: 100%;"> + <div class="page-container" style="padding: 10px;"> + ${grid} + + <br><br> + <h2>Visualizations shared with you by others</h2> + + %if shared_by_others: + <table class="colored" border="0" cellspacing="0" cellpadding="0" width="100%"> + <tr class="header"> + <th>Title</th> + <th>Owner</th> + <th></th> + </tr> + %for i, association in enumerate( shared_by_others ): + <% visualization = association.visualization %> + <tr> + <td> + <a class="menubutton" id="shared-${i}-popup" href="${h.url_for( action='display_by_username_and_slug', username=visualization.user.username, slug=visualization.slug)}">${visualization.title}</a> + </td> + <td>${visualization.user.username}</td> + <td> + <div popupmenu="shared-${i}-popup"> + <a class="action-button" href="${h.url_for( action='display_by_username_and_slug', username=visualization.user.username, slug=visualization.slug)}" target="_top">View</a> + </div> + </td> + </tr> + %endfor + </table> + %else: + + No visualizations have been shared with you. + + %endif + + </div> + </div> + +</%def> diff -r 582fd1777763 -r c3eccab29814 templates/visualization/list_published.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/visualization/list_published.mako Fri Mar 12 09:37:22 2010 -0500 @@ -0,0 +1,36 @@ +<%inherit file="/base_panels.mako"/> + +<%def name="init()"> +<% + self.has_left_panel=False + self.has_right_panel=False + self.active_view="page" + self.message_box_visible=False +%> +</%def> + +<%def name="title()"> + Galaxy :: Published Visualizations +</%def> + +<%def name="stylesheets()"> + ${parent.stylesheets()} + <style> + .grid td { + min-width: 100px; + } + </style> +</%def> + +<%def name="center_panel()"> + + ## <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${h.url_for( controller="page", action="list" )}"> </iframe> + + <div style="overflow: auto; height: 100%;"> + <div class="page-container" style="padding: 10px;"> + ${unicode( grid, 'utf-8' )} + </div> + </div> + + +</%def>