mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
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.
This commit is contained in:
@@ -1567,6 +1567,9 @@ class StoredWorkflowAnnotationAssociation( object ):
|
||||
class WorkflowStepAnnotationAssociation( object ):
|
||||
pass
|
||||
|
||||
class PageAnnotationAssociation( object ):
|
||||
pass
|
||||
|
||||
class UserPreference ( object ):
|
||||
def __init__( self, name=None, value=None ):
|
||||
self.name = name
|
||||
|
||||
@@ -814,6 +814,12 @@ WorkflowStepAnnotationAssociation.table = Table( "workflow_step_annotation_assoc
|
||||
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 @@ assign_mapper( context, Page, Page.table,
|
||||
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 @@ assign_mapper( context, WorkflowStepAnnotationAssociation, WorkflowStepAnnotatio
|
||||
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 = {}
|
||||
)
|
||||
|
||||
@@ -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 ) )
|
||||
|
||||
@@ -41,6 +41,10 @@ class BaseController( object ):
|
||||
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 @@ class BaseController( object ):
|
||||
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 @@ class BaseController( object ):
|
||||
# Set annotation.
|
||||
annotation_assoc.annotation = annotation
|
||||
return True
|
||||
|
||||
Root = BaseController
|
||||
|
||||
class SharableItemSecurity:
|
||||
""" Mixin for handling security for sharable items. """
|
||||
|
||||
@@ -110,7 +110,7 @@ class HistoryDatasetAssociationListGrid( grids.Grid ):
|
||||
# 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()
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ class HistoryAllPublishedGrid( grids.Grid ):
|
||||
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 @@ class HistoryAllPublishedGrid( grids.Grid ):
|
||||
# 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 ""
|
||||
|
||||
@@ -45,7 +45,7 @@ class PageListGrid( grids.Grid ):
|
||||
]
|
||||
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 @@ class PageAllPublishedGrid( grids.Grid ):
|
||||
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 @@ class PageAllPublishedGrid( grids.Grid ):
|
||||
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 @@ class _PageContentProcessor( _BaseHTMLProcessor ):
|
||||
# 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 @@ class PageController( BaseController, Sharable, UsesHistory, UsesStoredWorkflow,
|
||||
|
||||
@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 @@ class PageController( BaseController, Sharable, UsesHistory, UsesStoredWorkflow,
|
||||
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 @@ class PageController( BaseController, Sharable, UsesHistory, UsesStoredWorkflow,
|
||||
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 @@ class PageController( BaseController, Sharable, UsesHistory, UsesStoredWorkflow,
|
||||
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
|
||||
|
||||
@@ -10,7 +10,7 @@ from galaxy.model.orm import *
|
||||
|
||||
log = logging.getLogger( __name__ )
|
||||
|
||||
class RootController( BaseController, UsesHistory ):
|
||||
class RootController( BaseController, UsesHistory, UsesAnnotations ):
|
||||
|
||||
@web.expose
|
||||
def default(self, trans, target1=None, target2=None, **kwd):
|
||||
|
||||
@@ -60,6 +60,7 @@ class StoredWorkflowAllPublishedGrid( grids.Grid ):
|
||||
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 @@ class StoredWorkflowAllPublishedGrid( grids.Grid ):
|
||||
# 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()
|
||||
|
||||
|
||||
@@ -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 @@ class TextColumn( GridColumn ):
|
||||
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 )
|
||||
|
||||
def get_filter( self, user, column_filter ):
|
||||
""" 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
|
||||
|
||||
Reference in New Issue
Block a user