# HG changeset patch -- Bitbucket.org # Project galaxy-dist # URL http://bitbucket.org/galaxy/galaxy-dist/overview # User Greg Von Kuster <greg@bx.psu.edu> # Date 1280346733 14400 # Node ID 34ee69d9ba931b99a935b4ba7b38288160d68d31 # Parent 00672de098618096b9a11f7ad4983eb1c4faa32a Fixes and code cleanup for the Galaxy tool shed. Fixes include improved mappers and more optimal methods for defining the latest version of a tool and the latest version of tools by state. --- a/lib/galaxy/webapps/community/controllers/common.py +++ b/lib/galaxy/webapps/community/controllers/common.py @@ -96,7 +96,7 @@ class ToolListGrid( grids.Grid ): ToolCategoryColumn( "Category", key="category", model_class=model.Category, - visible=False ), + visible=False ) ] columns.append( grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0], columns[1], columns[2] ], @@ -123,6 +123,14 @@ class CategoryListGrid( grids.Grid ): class DescriptionColumn( grids.TextColumn ): def get_value( self, trans, grid, category ): return category.description + class ToolsColumn( grids.TextColumn ): + def get_value( self, trans, grid, category ): + if category.tools: + viewable_tools = 0 + for tca in category.tools: + viewable_tools += 1 + return viewable_tools + return 0 # Grid definition webapp = "community" @@ -148,13 +156,17 @@ class CategoryListGrid( grids.Grid ): grids.DeletedColumn( "Deleted", key="deleted", visible=False, - filterable="advanced" ) + filterable="advanced" ), + ToolsColumn( "Tools", + model_class=model.Tool, + attach_popup=False ) ] columns.append( grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0], columns[1] ], key="free-text-search", visible=False, filterable="standard" ) ) + # Override these global_actions = [] operations = [] @@ -240,7 +252,7 @@ class CommonController( BaseController ) out_categories.append( ( category.id, category.name ) ) if tool.is_rejected: # Include the comments regarding the reason for rejection - reason_for_rejection = get_most_recent_event( tool ).comment + reason_for_rejection = tool.latest_event.comment else: reason_for_rejection = '' can_approve_or_reject = trans.app.security_agent.can_approve_or_reject( trans.user, trans.user_is_admin(), cntrller, tool ) @@ -294,7 +306,7 @@ class CommonController( BaseController ) tool_file_contents = tarfile.open( tool.file_name, 'r' ).getnames() if tool.is_rejected: # Include the comments regarding the reason for rejection - reason_for_rejection = get_most_recent_event( tool ).comment + reason_for_rejection = tool.latest_event.comment else: reason_for_rejection = '' return trans.fill_template( '/webapps/community/tool/view_tool.mako', @@ -429,15 +441,15 @@ class CommonController( BaseController ) def get_versions( item ): """Get all versions of item""" - versions = [item] + versions = [ item ] this_item = item while item.newer_version: versions.insert( 0, item.newer_version ) item = item.newer_version item = this_item while item.older_version: - versions.append( item.older_version[0] ) - item = item.older_version[0] + versions.append( item.older_version[ 0 ] ) + item = item.older_version[ 0 ] return versions def get_categories( trans ): """Get all categories from the database""" @@ -450,22 +462,21 @@ def get_category( trans, id ): def get_tool( trans, id ): """Get a tool from the database""" return trans.sa_session.query( trans.model.Tool ).get( trans.app.security.decode_id( id ) ) -def get_tools( trans ): +def get_latest_versions_of_tools( trans ): """Get only the latest version of each tool from the database""" return trans.sa_session.query( trans.model.Tool ) \ .filter( trans.model.Tool.newer_version_id == None ) \ .order_by( trans.model.Tool.name ) +def get_approved_tools( trans ): + """Get the tools from the database whose state is APPROVED""" + approved_tools = [] + for tool in get_latest_versions_of_tools( trans ): + if tool.state == trans.model.Tool.states.APPROVED: + approved_tools.append( tool ) + return approved_tools def get_event( trans, id ): """Get an event from the databse""" return trans.sa_session.query( trans.model.Event ).get( trans.security.decode_id( id ) ) -def get_most_recent_event( item ): - """Get the most recent event for item""" - if item.events: - # Sort the events in ascending order by update_time - events = model.sort_by_attr( [ item_event_assoc.event for item_event_assoc in item.events ], 'update_time' ) - # Get the last event that occurred - return events[-1] - return None def get_user( trans, id ): """Get a user from the database""" return trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( id ) ) --- a/templates/webapps/community/index.mako +++ b/templates/webapps/community/index.mako @@ -81,7 +81,7 @@ <div class="toolSectionBody"><div class="toolSectionBg"><div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_categories', webapp='community' )}">Browse by category</a></div> - <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools', webapp='community' )}">Browse all tools</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools', operation='approved_tools', webapp='community' )}">Browse all tools</a></div> %if trans.user: <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools', operation='my_tools', webapp='community' )}">Browse your tools</a></div> %endif --- a/lib/galaxy/webapps/community/model/__init__.py +++ b/lib/galaxy/webapps/community/model/__init__.py @@ -10,7 +10,6 @@ from galaxy import util from galaxy.util.hash_util import * from galaxy.web.form_builder import * log = logging.getLogger( __name__ ) -from sqlalchemy.orm import object_session class User( object ): def __init__( self, email=None, password=None ): @@ -137,22 +136,17 @@ class Tool( object ): self.suite = datatype_bunch.suite @property def state( self ): + latest_event = self.latest_event + if latest_event: + return latest_event.state + return None + @property + def latest_event( self ): if self.events: - # Sort the events in ascending order by update_time - events = sort_by_attr( [ tca.event for tca in self.events ], 'update_time' ) - # Get the last event that occurred - return events[-1].state + events = [ tea.event for tea in self.events ] + # Get the last event that occurred ( events mapper is sorted descending ) + return events[0] return None - def last_comment( self ): - if self.events: - # Sort the events in ascending order by update_time - events = sort_by_attr( [ tca.event for tca in self.events ], 'update_time' ) - # Get the last event that occurred - if events[-1].comment: - return events[-1].comment - else: - return '' - return 'No comment' # Tool states @property def is_new( self ): --- a/lib/galaxy/webapps/community/model/mapping.py +++ b/lib/galaxy/webapps/community/model/mapping.py @@ -14,8 +14,6 @@ from galaxy.model.orm.ext.assignmapper i from galaxy.model.custom_types import * from galaxy.util.bunch import Bunch from galaxy.webapps.community.security import CommunityRBACAgent -from sqlalchemy.orm.collections import attribute_mapped_collection -from sqlalchemy.ext.associationproxy import association_proxy metadata = MetaData() context = Session = scoped_session( sessionmaker( autoflush=False, autocommit=True ) ) @@ -216,7 +214,12 @@ assign_mapper( context, ToolAnnotationAs assign_mapper( context, Tool, Tool.table, properties = dict( categories=relation( ToolCategoryAssociation ), - events=relation( ToolEventAssociation ), + events=relation( ToolEventAssociation, secondary=Event.table, + primaryjoin=( Tool.table.c.id==ToolEventAssociation.table.c.tool_id ), + secondaryjoin=( ToolEventAssociation.table.c.event_id==Event.table.c.id ), + order_by=desc( Event.table.c.update_time ), + viewonly=True, + uselist=True ), user=relation( User.mapper ), older_version=relation( Tool, --- a/lib/galaxy/webapps/community/controllers/tool.py +++ b/lib/galaxy/webapps/community/controllers/tool.py @@ -8,45 +8,55 @@ from common import * log = logging.getLogger( __name__ ) +class StateColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + state = tool.state + if state == trans.model.Tool.states.APPROVED: + state_color = 'ok' + elif state == trans.model.Tool.states.REJECTED: + state_color = 'error' + elif state == trans.model.Tool.states.ARCHIVED: + state_color = 'upload' + else: + state_color = state + return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state ) +class ToolStateColumn( grids.StateColumn ): + def filter( self, trans, user, query, column_filter ): + """Modify query to filter self.model_class by state.""" + if column_filter == "All": + pass + elif column_filter in [ v for k, v in self.model_class.states.items() ]: + # Get all of the latest Events associated with the current version of each tool + latest_event_id_for_current_versions_of_tools = [ tool.latest_event.id for tool in get_latest_versions_of_tools( trans ) ] + # Filter query by the latest state for the current version of each tool + return query.filter( and_( model.Event.table.c.state == column_filter, + model.Event.table.c.id.in_( latest_event_id_for_current_versions_of_tools ) ) ) + return query + class ApprovedToolListGrid( ToolListGrid ): - def apply_query_filter( self, trans, query, **kwargs ): - return query.filter( model.Event.table.c.state == 'approved' ) - -class MyToolsListGrid( ToolListGrid ): - class StateColumn( grids.TextColumn ): - def get_value( self, trans, grid, tool ): - state = tool.state - if state == 'approved': - state_color = 'ok' - elif state == 'rejected': - state_color = 'error' - elif state == 'archived': - state_color = 'upload' - else: - state_color = state - return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state ) - class ToolStateColumn( grids.StateColumn ): - def filter( self, trans, user, query, column_filter ): - """Modify query to filter self.model_class by state.""" - if column_filter == "All": - pass - elif column_filter in [ v for k, v in self.model_class.states.items() ]: - # Get all of the latest ToolEventAssociation ids - tea_ids = [ tea_id_tup[0] for tea_id_tup in trans.sa_session.query( func.max( model.ToolEventAssociation.table.c.id ) ) \ - .group_by( model.ToolEventAssociation.table.c.tool_id ) ] - # Get all of the Event ids associated with the latest ToolEventAssociation ids - event_ids = [ event_id_tup[0] for event_id_tup in trans.sa_session.query( model.ToolEventAssociation.table.c.event_id ) \ - .filter( model.ToolEventAssociation.table.c.id.in_( tea_ids ) ) ] - # Filter our query by state and event ids - return query.filter( and_( model.Event.table.c.state == column_filter, - model.Event.table.c.id.in_( event_ids ) ) ) - return query - columns = [ col for col in ToolListGrid.columns ] columns.append( StateColumn( "Status", model_class=model.Tool, link=( lambda item: dict( operation="tools_by_state", id=item.id, webapp="community" ) ), + visible=False, + attach_popup=False ) + ) + columns.append( + ToolStateColumn( "State", + key="state", + model_class=model.Tool, + visible=False, + filterable="advanced" ) + ) + +class MyToolsListGrid( ApprovedToolListGrid ): + columns = [ col for col in ToolListGrid.columns ] + columns.append( + StateColumn( "Status", + model_class=model.Tool, + link=( lambda item: dict( operation="tools_by_state", id=item.id, webapp="community" ) ), + visible=True, attach_popup=False ) ) columns.append( @@ -58,6 +68,10 @@ class MyToolsListGrid( ToolListGrid ): ) class ToolCategoryListGrid( CategoryListGrid ): + """ + Replaces the tools column in the Category grid with a similar column, + but displaying the number of APPROVED tools in the category. + """ class ToolsColumn( grids.TextColumn ): def get_value( self, trans, grid, category ): if category.tools: @@ -69,7 +83,10 @@ class ToolCategoryListGrid( CategoryList return viewable_tools return 0 - columns = [ col for col in CategoryListGrid.columns ] + columns = [] + for col in CategoryListGrid.columns: + if not isinstance( col, CategoryListGrid.ToolsColumn ): + columns.append( col ) columns.append( ToolsColumn( "Tools", model_class=model.Tool, @@ -146,6 +163,14 @@ class ToolController( BaseController ): del kwd[ k ] kwd[ 'f-email' ] = trans.user.email return self.my_tools_list_grid( trans, **kwd ) + elif operation == "approved_tools": + # Eliminate the current filters if any exist. + for k, v in kwd.items(): + if k.startswith( 'f-' ): + del kwd[ k ] + # Make sure only the latest version of a tool whose state is APPROVED are displayed. + kwd[ 'f-state' ] = trans.model.Tool.states.APPROVED + return self.tool_list_grid( trans, **kwd ) elif operation == "tools_by_category": # Eliminate the current filters if any exist. for k, v in kwd.items(): @@ -154,6 +179,8 @@ class ToolController( BaseController ): category_id = kwd.get( 'id', None ) category = get_category( trans, category_id ) kwd[ 'f-category' ] = category.name + # Make sure only the latest version of a tool whose state is APPROVED are displayed. + kwd[ 'f-state' ] = trans.model.Tool.states.APPROVED # Render the list view return self.tool_list_grid( trans, **kwd ) @web.expose --- a/lib/galaxy/webapps/community/datatypes/__init__.py +++ b/lib/galaxy/webapps/community/datatypes/__init__.py @@ -69,7 +69,7 @@ class Tool( object ): raise DatatypeVerificationError( 'The archive is not a readable tar file.' ) if not xml_files: # Make sure we're not uploading a tool suite - if filter( lambda x: x.lower() == 'suite_config.xml', tar.getnames() ): + if filter( lambda x: x.lower().find( 'suite_config.xml' ) >= 0, tar.getnames() ): raise DatatypeVerificationError( 'The archive includes a suite_config.xml file, so set the upload type to "Tool Suite".' ) xml_files = filter( lambda x: x.lower().endswith( '.xml' ), tar.getnames() ) if not xml_files: --- a/lib/galaxy/webapps/community/controllers/admin.py +++ b/lib/galaxy/webapps/community/controllers/admin.py @@ -2,7 +2,7 @@ from galaxy.web.base.controller import * from galaxy.webapps.community import model from galaxy.model.orm import * from galaxy.web.framework.helpers import time_ago, iff, grids -from common import ToolListGrid, CategoryListGrid, get_category, get_tools, get_event, get_tool, get_versions +from common import ToolListGrid, CategoryListGrid, get_category, get_event, get_tool, get_versions import logging log = logging.getLogger( __name__ ) @@ -791,38 +791,3 @@ class AdminController( BaseController, A action='manage_categories', message=util.sanitize_text( message ), status='done' ) ) - -## ---- Utility methods ------------------------------------------------------- - -def get_tools_by_state( trans, state ): - # TODO: write this as a query using eagerload - will be much faster. - ids = [] - if state == trans.model.Tool.states.NEW: - for tool in get_tools( trans ): - if tool.is_new: - ids.append( tool.id ) - elif state == trans.model.Tool.states.ERROR: - for tool in get_tools( trans ): - if tool.is_error: - ids.append( tool.id ) - elif state == trans.model.Tool.states.DELETED: - for tool in get_tools( trans ): - if tool.is_deleted: - ids.append( tool.id ) - elif state == trans.model.Tool.states.WAITING: - for tool in get_tools( trans ): - if tool.is_waiting: - ids.append( tool.id ) - elif state == trans.model.Tool.states.APPROVED: - for tool in get_tools( trans ): - if tool.is_approved: - ids.append( tool.id ) - elif state == trans.model.Tool.states.REJECTED: - for tool in get_tools( trans ): - if tool.is_rejected: - ids.append( tool.id ) - elif state == trans.model.Tool.states.ARCHIVED: - for tool in get_tools( trans ): - if tool.is_archived: - ids.append( tool.id ) - return ids