[hg] galaxy 3424: Annotations for published items: (1) added ann...
details: http://www.bx.psu.edu/hg/galaxy/rev/9d9f0dfeeebf changeset: 3424:9d9f0dfeeebf user: jeremy goecks <jeremy.goecks@emory.edu> date: Mon Feb 22 13:56:51 2010 -0500 description: Annotations for published items: (1) added annotations to pages; (2) added annotation column to all published item grids; (3) refactored annotation code out of base controller and into its own mixin. diffstat: lib/galaxy/model/__init__.py | 3 + lib/galaxy/model/mapping.py | 13 ++++- lib/galaxy/model/migrate/versions/0040_page_annotations.py | 42 ++++++++++++++ lib/galaxy/web/base/controller.py | 8 ++- lib/galaxy/web/controllers/dataset.py | 2 +- lib/galaxy/web/controllers/history.py | 3 +- lib/galaxy/web/controllers/page.py | 24 +++++-- lib/galaxy/web/controllers/root.py | 2 +- lib/galaxy/web/controllers/workflow.py | 3 +- lib/galaxy/web/framework/helpers/grids.py | 39 ++++++++++-- 10 files changed, 118 insertions(+), 21 deletions(-) diffs (351 lines): diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/model/__init__.py Mon Feb 22 13:56:51 2010 -0500 @@ -1567,6 +1567,9 @@ class WorkflowStepAnnotationAssociation( object ): pass +class PageAnnotationAssociation( object ): + pass + class UserPreference ( object ): def __init__( self, name=None, value=None ): self.name = name diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/model/mapping.py --- a/lib/galaxy/model/mapping.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/model/mapping.py Mon Feb 22 13:56:51 2010 -0500 @@ -814,6 +814,12 @@ Column( "workflow_step_id", Integer, ForeignKey( "workflow_step.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "annotation", TEXT, index=True) ) + +PageAnnotationAssociation.table = Table( "page_annotation_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "annotation", TEXT, index=True) ) # User tables. @@ -1304,7 +1310,8 @@ latest_revision=relation( PageRevision, post_update=True, primaryjoin=( Page.table.c.latest_revision_id == PageRevision.table.c.id ), lazy=False ), - tags=relation(PageTagAssociation, order_by=PageTagAssociation.table.c.id, backref="pages") + tags=relation(PageTagAssociation, order_by=PageTagAssociation.table.c.id, backref="pages"), + annotations=relation( PageAnnotationAssociation, order_by=PageAnnotationAssociation.table.c.id, backref="pages" ) ) ) # Set up proxy so that @@ -1377,6 +1384,10 @@ properties=dict( workflow_step=relation( WorkflowStep ), user=relation( User ) ) ) +assign_mapper( context, PageAnnotationAssociation, PageAnnotationAssociation.table, + properties=dict( page=relation( Page ), user=relation( User ) ) + ) + assign_mapper( context, UserPreference, UserPreference.table, properties = {} ) diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/model/migrate/versions/0040_page_annotations.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/model/migrate/versions/0040_page_annotations.py Mon Feb 22 13:56:51 2010 -0500 @@ -0,0 +1,42 @@ +""" +Migration script to (a) create tables for annotating pages. +""" + +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 ) ) + +PageAnnotationAssociation_table = Table( "page_annotation_association", metadata, + Column( "id", Integer, primary_key=True ), + Column( "page_id", Integer, ForeignKey( "page.id" ), index=True ), + Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), + Column( "annotation", TEXT, index=True) ) + +def upgrade(): + print __doc__ + metadata.reflect() + + # Create history_annotation_association table. + try: + PageAnnotationAssociation_table.create() + except Exception, e: + print str(e) + log.debug( "Creating page_annotation_association table failed: %s" % str( e ) ) + +def downgrade(): + metadata.reflect() + + # Drop page_annotation_association table. + try: + PageAnnotationAssociation_table.drop() + except Exception, e: + print str(e) + log.debug( "Dropping page_annotation_association table failed: %s" % str( e ) ) + \ No newline at end of file diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/base/controller.py Mon Feb 22 13:56:51 2010 -0500 @@ -41,6 +41,10 @@ item_class = None return item_class +Root = BaseController + +class UsesAnnotations: + """ Mixin for getting and setting item annotations. """ def get_item_annotation_str( self, db_session, user, item ): """ Returns a user's annotation string for an item. """ annotation_obj = self.get_item_annotation_obj( db_session, user, item ) @@ -67,6 +71,8 @@ annotation_assoc = annotation_assoc.filter_by( stored_workflow=item ) elif item.__class__ == model.WorkflowStep: annotation_assoc = annotation_assoc.filter_by( workflow_step=item ) + elif item.__class__ == model.Page: + annotation_assoc = annotation_assoc.filter_by( page=item ) return annotation_assoc.first() def add_item_annotation( self, trans, item, annotation ): @@ -89,8 +95,6 @@ # Set annotation. annotation_assoc.annotation = annotation return True - -Root = BaseController class SharableItemSecurity: """ Mixin for handling security for sharable items. """ diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/controllers/dataset.py --- a/lib/galaxy/web/controllers/dataset.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/controllers/dataset.py Mon Feb 22 13:56:51 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, UsesHistoryDatasetAssociation ): +class DatasetInterface( BaseController, UsesAnnotations, UsesHistoryDatasetAssociation ): stored_list_grid = HistoryDatasetAssociationListGrid() diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/controllers/history.py --- a/lib/galaxy/web/controllers/history.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/controllers/history.py Mon Feb 22 13:56:51 2010 -0500 @@ -128,6 +128,7 @@ use_async = True columns = [ NameURLColumn( "Name", key="name", model_class=model.History, filterable="advanced" ), + grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_class=model.History, model_annotation_association_class=model.HistoryAnnotationAssociation, filterable="advanced" ), grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced", sortable=False ), grids.CommunityTagsColumn( "Community Tags", "tags", model.History, model.HistoryTagAssociation, filterable="advanced", grid_name="PublicHistoryListGrid" ), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ) @@ -146,7 +147,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, UsesHistory ): +class HistoryController( BaseController, Sharable, UsesAnnotations, UsesHistory ): @web.expose def index( self, trans ): return "" diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/controllers/page.py --- a/lib/galaxy/web/controllers/page.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/controllers/page.py Mon Feb 22 13:56:51 2010 -0500 @@ -45,7 +45,7 @@ ] operations = [ grids.DisplayByUsernameAndSlugGridOperation( "View", allow_multiple=False ), - grids.GridOperation( "Edit name/id", allow_multiple=False, url_args=dict( action='edit') ), + 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" ), @@ -63,6 +63,7 @@ default_filter = dict( title="All", username="All" ) columns = [ 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" ), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ) @@ -70,7 +71,7 @@ columns.append( grids.MulticolFilterColumn( "Search", - cols_to_filter=[ columns[0], columns[1], columns[2] ], + 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 ): @@ -285,7 +286,7 @@ # Default behavior: _BaseHTMLProcessor.unknown_endtag( self, tag ) -class PageController( BaseController, Sharable, UsesHistory, UsesStoredWorkflow, UsesHistoryDatasetAssociation ): +class PageController( BaseController, Sharable, UsesAnnotations, UsesHistory, UsesStoredWorkflow, UsesHistoryDatasetAssociation ): _page_list = PageListGrid() _all_published_list = PageAllPublishedGrid() @@ -385,7 +386,7 @@ @web.expose @web.require_login( "create pages" ) - def edit( self, trans, id, page_title="", page_slug="" ): + def edit( self, trans, id, page_title="", page_slug="", page_annotation="" ): """ Create a new page """ @@ -395,7 +396,7 @@ page = session.query( model.Page ).get( id ) user = trans.user assert page.user == user - page_title_err = page_slug_err = "" + page_title_err = page_slug_err = page_annotation_err = "" if trans.request.method == "POST": if not page_title: page_title_err = "Page name is required" @@ -403,17 +404,24 @@ page_slug_err = "Page id is required" elif not VALID_SLUG_RE.match( page_slug ): page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character" - elif page_slug == page.slug or trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first(): + elif page_slug != page.slug and trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first(): page_slug_err = "Page id must be unique" + elif not page_annotation: + page_annotation_err = "Page annotation is required" else: page.title = page_title page.slug = page_slug + page_annotation = sanitize_html( page_annotation, 'utf-8', 'text/html' ) + self.add_item_annotation( trans, page, page_annotation ) session.flush() # Redirect to page list. return trans.response.send_redirect( web.url_for( action='list' ) ) else: page_title = page.title page_slug = page.slug + page_annotation = self.get_item_annotation_str( trans.sa_session, trans.get_user(), page ) + if not page_annotation: + page_annotation = "" return trans.show_form( web.FormBuilder( web.url_for( id=encoded_id ), "Edit page attributes", submit_text="Submit" ) .add_text( "page_title", "Page title", value=page_title, error=page_title_err ) @@ -422,7 +430,9 @@ public links to this page. A default is generated from the page title, but can be edited. This field must contain only lowercase letters, numbers, and - the '-' character.""" ), + the '-' character.""" ) + .add_text( "page_annotation", "Page annotation", value=page_annotation, error=page_annotation_err, + help="A description of or notes about the page. Annotation is shown alongside published pages."), template="page/create.mako" ) @web.expose diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/controllers/root.py --- a/lib/galaxy/web/controllers/root.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/controllers/root.py Mon Feb 22 13:56:51 2010 -0500 @@ -10,7 +10,7 @@ log = logging.getLogger( __name__ ) -class RootController( BaseController, UsesHistory ): +class RootController( BaseController, UsesHistory, UsesAnnotations ): @web.expose def default(self, trans, target1=None, target2=None, **kwd): diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/controllers/workflow.py --- a/lib/galaxy/web/controllers/workflow.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/controllers/workflow.py Mon Feb 22 13:56:51 2010 -0500 @@ -60,6 +60,7 @@ use_async = True columns = [ grids.PublicURLColumn( "Name", key="name", model_class=model.StoredWorkflow, filterable="advanced" ), + grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_class=model.StoredWorkflow, model_annotation_association_class=model.StoredWorkflowAnnotationAssociation, filterable="advanced" ), grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced", sortable=False ), grids.CommunityTagsColumn( "Community Tags", "tags", model.StoredWorkflow, model.StoredWorkflowTagAssociation, filterable="advanced", grid_name="PublicWorkflowListGrid" ), grids.GridColumn( "Last Updated", key="update_time", format=time_ago ) @@ -78,7 +79,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, UsesStoredWorkflow ): +class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnnotations ): stored_list_grid = StoredWorkflowListGrid() published_list_grid = StoredWorkflowAllPublishedGrid() diff -r 8d77e3acf653 -r 9d9f0dfeeebf lib/galaxy/web/framework/helpers/grids.py --- a/lib/galaxy/web/framework/helpers/grids.py Mon Feb 22 09:21:42 2010 -0500 +++ b/lib/galaxy/web/framework/helpers/grids.py Mon Feb 22 13:56:51 2010 -0500 @@ -1,6 +1,7 @@ from galaxy.model import * from galaxy.model.orm import * +from galaxy.web.base import controller from galaxy.web.framework.helpers import iff from galaxy.tags.tag_handler import TagHandler from galaxy.web import url_for @@ -341,22 +342,46 @@ elif column_filter: query = query.filter( self.get_filter( user, column_filter ) ) return query + def get_filter( self, user, column_filter ): - """ Returns a SQLAlchemy criterion derived from column_filter. """ - model_class_key_field = getattr( self.model_class, self.key ) - + """ Returns a SQLAlchemy criterion derived from column_filter. """ if isinstance( column_filter, basestring ): - return func.lower( model_class_key_field ).like( "%" + column_filter.lower() + "%" ) + return self.get_single_filter( user, column_filter ) elif isinstance( column_filter, list ): clause_list = [] for filter in column_filter: - clause_list.append( func.lower( model_class_key_field ).like( "%" + filter.lower() + "%" ) ) + clause_list.append( self.get_single_filter( user, filter ) ) return and_( *clause_list ) - + + def get_single_filter( self, user, a_filter ): + """ Returns a SQLAlchemy criterion derived for a single filter. Single filter is the most basic filter--usually a string--and cannot be a list. """ + model_class_key_field = getattr( self.model_class, self.key ) + return func.lower( model_class_key_field ).like( "%" + a_filter.lower() + "%" ) + +class OwnerAnnotationColumn( TextColumn, controller.UsesAnnotations ): + """ Column that displays and filters item owner's annotations. """ + def __init__( self, col_name, key, model_class, model_annotation_association_class, filterable ): + GridColumn.__init__( self, col_name, key=key, model_class=model_class, filterable=filterable ) + self.sortable = False + self.model_annotation_association_class = model_annotation_association_class + + def get_value( self, trans, grid, item ): + """ Returns item annotation. """ + annotation = self.get_item_annotation_str( trans.sa_session, item.user, item ) + return iff( annotation, annotation, "" ) + + def get_single_filter( self, user, a_filter ): + """ Filter by annotation and annotation owner. """ + return self.model_class.annotations.any( + and_( func.lower( self.model_annotation_association_class.annotation ).like( "%" + a_filter.lower() + "%" ), + # TODO: not sure why, to filter by owner's annotations, we have to do this rather than + # 'self.model_class.user==self.model_annotation_association_class.user' + self.model_annotation_association_class.table.c.user_id==self.model_class.table.c.user_id ) ) + class CommunityTagsColumn( TextColumn ): """ Column that supports community tags. """ def __init__( self, col_name, key, model_class, model_tag_association_class, filterable, grid_name=None ): - GridColumn.__init__(self, col_name, key=key, model_class=model_class, filterable=filterable) + GridColumn.__init__( self, col_name, key=key, model_class=model_class, filterable=filterable ) self.model_tag_association_class = model_tag_association_class # Tags cannot be sorted. self.sortable = False
participants (1)
-
Greg Von Kuster