details: http://www.bx.psu.edu/hg/galaxy/rev/3ac0dfcdf44e changeset: 3728:3ac0dfcdf44e user: jeremy goecks <jeremy.goecks@emory.edu> date: Fri Apr 30 18:26:49 2010 -0400 description: merge diffstat: lib/galaxy/web/framework/__init__.py | 4 +- lib/galaxy/webapps/community/controllers/admin.py | 385 +++++++-- lib/galaxy/webapps/community/controllers/common.py | 105 ++- lib/galaxy/webapps/community/controllers/tool.py | 293 +++++-- lib/galaxy/webapps/community/controllers/upload.py | 68 +- lib/galaxy/webapps/community/model/__init__.py | 7 +- lib/galaxy/webapps/community/model/mapping.py | 14 +- lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py | 3 +- lib/galaxy/webapps/community/security/__init__.py | 6 + templates/webapps/community/admin/index.mako | 43 +- templates/webapps/community/category/edit_category.mako | 44 + templates/webapps/community/category/rename_category.mako | 44 - templates/webapps/community/index.mako | 64 +- templates/webapps/community/tool/browse_tool.mako | 37 - templates/webapps/community/tool/edit_tool.mako | 3 +- templates/webapps/community/tool/view_tool.mako | 58 +- templates/webapps/community/upload/upload.mako | 3 + tools/samtools/sam_to_bam.py | 2 +- tools/sr_mapping/bowtie_wrapper.py | 4 +- tools/sr_mapping/bwa_wrapper_code.py | 9 +- 20 files changed, 883 insertions(+), 313 deletions(-) diffs (1884 lines): diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/web/framework/__init__.py Fri Apr 30 18:26:49 2010 -0400 @@ -66,7 +66,7 @@ decorator.exposed = True return decorator -def require_login( verb="perform this action", use_panels=False ): +def require_login( verb="perform this action", use_panels=False, webapp='galaxy' ): def argcatcher( func ): def decorator( self, trans, *args, **kwargs ): if trans.get_user(): @@ -74,7 +74,7 @@ else: return trans.show_error_message( 'You must be <a target="_top" href="%s">logged in</a> to %s</div>.' - % ( url_for( controller='user', action='login' ), verb ), use_panels=use_panels ) + % ( url_for( controller='user', action='login', webapp=webapp ), verb ), use_panels=use_panels ) return decorator return argcatcher diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/controllers/admin.py --- a/lib/galaxy/webapps/community/controllers/admin.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/admin.py Fri Apr 30 18:26:49 2010 -0400 @@ -2,7 +2,7 @@ from galaxy.webapps.community import model from galaxy.model.orm import * from galaxy.web.framework.helpers import time_ago, iff, grids -from common import get_categories, get_category +from common import get_categories, get_category, get_tools, get_event, get_tool, get_versions import logging log = logging.getLogger( __name__ ) @@ -18,13 +18,6 @@ if user.username: return user.username return 'not set' - class StatusColumn( grids.GridColumn ): - def get_value( self, trans, grid, user ): - if user.purged: - return "purged" - elif user.deleted: - return "deleted" - return "" class GroupsColumn( grids.GridColumn ): def get_value( self, trans, grid, user ): if user.groups: @@ -45,6 +38,16 @@ if user.galaxy_sessions: return self.format( user.galaxy_sessions[ 0 ].update_time ) return 'never' + class StatusColumn( grids.GridColumn ): + def get_value( self, trans, grid, user ): + if user.purged: + return "purged" + elif user.deleted: + return "deleted" + return "" + class ToolsColumn( grids.TextColumn ): + def get_value( self, trans, grid, user ): + return '<a href="browse_tools_by_user?operation=browse&id=%s">%s</a>' % ( trans.security.encode_id( user.id ), str( len( user.tools ) ) ) # Grid definition webapp = "community" @@ -69,6 +72,10 @@ ExternalColumn( "External", attach_popup=False ), LastLoginColumn( "Last Login", format=time_ago ), StatusColumn( "Status", attach_popup=False ), + ToolsColumn( "Uploaded Tools", + model_class=model.User, + attach_popup=False, + filterable="advanced" ), # Columns that are valid for filtering but are not visible. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) ] @@ -200,7 +207,7 @@ return None def build_initial_query( self, session ): return session.query( self.model_class ) - def apply_default_filter( self, trans, query, **kwargs ): + def apply_default_filter( self, trans, query, **kwd ): return query.filter( model.Role.type != model.Role.types.PRIVATE ) class GroupListGrid( grids.Grid ): @@ -280,34 +287,31 @@ def build_initial_query( self, session ): return session.query( self.model_class ) -class CategoryListGrid( grids.Grid ): +class ManageCategoryListGrid( grids.Grid ): class NameColumn( grids.TextColumn ): def get_value( self, trans, grid, category ): return category.name class DescriptionColumn( grids.TextColumn ): def get_value( self, trans, grid, category ): return category.description - class StatusColumn( grids.GridColumn ): - def get_value( self, trans, grid, category ): - if category.deleted: - return "deleted" - return "" # Grid definition webapp = "community" - title = "Categories" + title = "Manage Categories" model_class = model.Category template='/webapps/community/category/grid.mako' default_sort_key = "name" columns = [ NameColumn( "Name", key="name", - link=( lambda item: dict( operation="Edit category", id=item.id, webapp="community" ) ), + link=( lambda item: dict( operation="Edit", id=item.id, webapp="community" ) ), model_class=model.Category, - attach_popup=True, + attach_popup=False, filterable="advanced" ), - DescriptionColumn( "Description", attach_popup=False ), - StatusColumn( "Status", attach_popup=False ), + DescriptionColumn( "Description", + model_class=model.Category, + attach_popup=False, + filterable="advanced" ), # Columns that are valid for filtering but are not visible. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) ] @@ -318,13 +322,9 @@ filterable="standard" ) ) global_actions = [ grids.GridAction( "Add new category", - dict( controller='admin', action='categories', operation='create', webapp="community" ) ) + dict( controller='admin', action='manage_categories', operation='create', webapp="community" ) ) ] - operations = [ grids.GridOperation( "Rename", - condition=( lambda item: not item.deleted ), - allow_multiple=False, - url_args=dict( webapp="community", action="rename_category" ) ), - grids.GridOperation( "Delete", + operations = [ grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), allow_multiple=True, url_args=dict( webapp="community", action="mark_category_deleted" ) ), @@ -349,16 +349,77 @@ def build_initial_query( self, session ): return session.query( self.model_class ) +class ToolsByCategoryListGrid( grids.Grid ): + class NameColumn( grids.TextColumn ): + def get_value( self, trans, grid, category ): + return category.name + 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: + return len( category.tools ) + return 0 + + # Grid definition + webapp = "community" + title = "Tools by Category" + model_class = model.Category + template='/webapps/community/category/grid.mako' + default_sort_key = "name" + columns = [ + NameColumn( "Name", + key="name", + link=( lambda item: dict( operation="Browse Category", id=item.id, webapp="community" ) ), + model_class=model.Category, + attach_popup=True, + filterable="advanced" ), + DescriptionColumn( "Description", + model_class=model.Category, + attach_popup=False, + filterable="advanced" ), + ToolsColumn( "Tools", + model_class=model.Category, + attach_popup=False, + filterable="advanced" ), + # Columns that are valid for filtering but are not visible. + grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) + ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1], columns[2] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + standard_filters = [ + grids.GridColumnFilter( "Active", args=dict( deleted=False ) ), + grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), + grids.GridColumnFilter( "All", args=dict( deleted='All' ) ) + ] + num_rows_per_page = 50 + preserve_state = False + use_paging = True + def get_current_item( self, trans ): + return None + def build_initial_query( self, session ): + return session.query( self.model_class ) + class ToolListGrid( grids.Grid ): class NameColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): return tool.name + class VersionColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.version + class DescriptionColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.description class CategoryColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): if tool.categories: rval = '' for tca in tool.categories: - rval = '%s%s<br/>' % ( rval, tca.category.name ) + rval += '<a href="browse_category?id=%s">%s</a><br/>\n' % ( trans.security.encode_id( tca.category.id ), tca.category.name ) return rval return 'not set' class StateColumn( grids.GridColumn ): @@ -389,7 +450,7 @@ return accepted_filters class UserColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): - return tool.user.email + return '<a href="browse_tools_by_user?operation=browse&id=%s">%s</a>' % ( trans.security.encode_id( tool.user.id ), tool.user.username ) # Grid definition title = "Tools" model_class = model.Tool @@ -402,36 +463,39 @@ link=( lambda item: dict( operation="View Tool", id=item.id, cntrller='admin', webapp="community" ) ), attach_popup=True, filterable="advanced" ), + VersionColumn( "Version", + model_class=model.Tool, + attach_popup=False, + filterable="advanced" ), + DescriptionColumn( "Description", + model_class=model.Tool, + attach_popup=False, + filterable="advanced" ), CategoryColumn( "Category", - key="category", model_class=model.Category, attach_popup=False, filterable="advanced" ), - StateColumn( "State", - key="state", + StateColumn( "Status", model_class=model.Event, - attach_popup=False, - filterable="advanced" ), + attach_popup=False ), + UserColumn( "Uploaded By", + key="username", + model_class=model.User, + attach_popup=False, + filterable="advanced" ), # Columns that are valid for filtering but are not visible. - grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) + grids.DeletedColumn( "Deleted", model_class=model.Tool, key="deleted", visible=False, filterable="advanced" ) ] columns.append( grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0], columns[1] ], key="free-text-search", visible=False, filterable="standard" ) ) - global_actions = [ - grids.GridAction( "Upload tool", dict( controller='upload', action='upload', type='tool' ) ) - ] operations = [ grids.GridOperation( "Edit information", condition=( lambda item: not item.deleted ), allow_multiple=False, - url_args=dict( controller="common", action="edit_tool", cntrller="admin", webapp="community" ) ), - grids.GridOperation( "Manage categories", - condition=( lambda item: not item.deleted ), - allow_multiple=False, - url_args=dict( controller="common", action="manage_categories", cntrller="admin", webapp="community" ) ) + url_args=dict( controller="common", action="edit_tool", cntrller="admin", webapp="community" ) ) ] standard_filters = [ grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), @@ -443,57 +507,128 @@ use_paging = True def build_initial_query( self, session ): return session.query( self.model_class ) - def apply_default_filter( self, trans, query, **kwargs ): + def apply_default_filter( self, trans, query, **kwd ): + tool_id = kwd.get( 'tool_id', False ) + if tool_id: + if str( tool_id ).lower() in [ '', 'none' ]: + # Return an empty query since the current user cannot view any + # tools (possibly due to state not being approved, etc). + return query.filter( model.Tool.id == None ) + tool_id = util.listify( tool_id ) + query = query.filter( or_( *map( lambda id: self.model_class.id == id, tool_id ) ) ) return query.filter( self.model_class.deleted==False ) -class AdminCommunity( BaseController, Admin ): +class AdminController( BaseController, Admin ): user_list_grid = UserListGrid() role_list_grid = RoleListGrid() group_list_grid = GroupListGrid() - category_list_grid = CategoryListGrid() + manage_category_list_grid = ManageCategoryListGrid() + tools_by_category_list_grid = ToolsByCategoryListGrid() tool_list_grid = ToolListGrid() @web.expose @web.require_admin - def browse_tools( self, trans, **kwargs ): - if 'operation' in kwargs: - operation = kwargs['operation'].lower() + def browse_tools( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "edit tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='edit_tool', + cntrller='admin', + **kwd ) ) + elif operation == "view tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='view_tool', + cntrller='admin', + **kwd ) ) + # Render the list view + return self.tool_list_grid( trans, **kwd ) + @web.expose + @web.require_admin + def browse_tools_by_category( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "browse category": + return self.browse_category( trans, id=kwd['id'] ) + # Render the list view + return self.tools_by_category_list_grid( trans, **kwd ) + @web.expose + @web.require_admin + def browse_tools_by_user( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() if operation == "browse": - return trans.response.send_redirect( web.url_for( controller='tool', - action='browse_tools', + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_tools_by_user', cntrller='admin', - **kwargs ) ) + **kwd ) ) + elif operation == "browse category": + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_category', + cntrller='admin', + **kwd ) ) + elif operation == "view tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='view_tool', + cntrller='admin', + **kwd ) ) elif operation == "edit tool": return trans.response.send_redirect( web.url_for( controller='common', action='edit_tool', cntrller='admin', - **kwargs ) ) - elif operation == "view tool": - return trans.response.send_redirect( web.url_for( controller='common', - action='view_tool', - cntrller='admin', - **kwargs ) ) + **kwd ) ) + elif operation == "download tool": + return trans.response.send_redirect( web.url_for( controller='tool', + action='download_tool', + **kwd ) ) # Render the list view - return self.tool_list_grid( trans, **kwargs ) + return self.tool_list_grid( trans, **kwd ) @web.expose @web.require_admin - def categories( self, trans, **kwargs ): - if 'operation' in kwargs: - operation = kwargs['operation'].lower() + def manage_categories( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() if operation == "create": - return self.create_category( trans, **kwargs ) + return self.create_category( trans, **kwd ) if operation == "delete": - return self.mark_category_deleted( trans, **kwargs ) + return self.mark_category_deleted( trans, **kwd ) if operation == "undelete": - return self.undelete_category( trans, **kwargs ) + return self.undelete_category( trans, **kwd ) if operation == "purge": - return self.purge_category( trans, **kwargs ) - if operation == "rename": - return self.rename_category( trans, **kwargs ) + return self.purge_category( trans, **kwd ) + if operation == "edit": + return self.edit_category( trans, **kwd ) # Render the list view - return self.category_list_grid( trans, **kwargs ) - + return self.manage_category_list_grid( trans, **kwd ) + @web.expose + @web.require_admin + def browse_tools_by_state( self, trans, state=None, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + if state is None: + id = params.get( 'id', None ) + if not id: + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_tools', + message='Select a status', + status='error' ) ) + event = get_event( trans, id ) + state = event.state + tool_id = get_tools_by_state( trans, state ) + if not tool_id: + tool_id = 'None' + return trans.response.send_redirect( web.url_for( controller='admin', + action='browse_tools', + tool_id=tool_id ) ) + @web.expose + @web.require_admin + def browse_category( self, trans, **kwd ): + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_category', + cntrller='admin', + **kwd ) ) @web.expose @web.require_admin def create_category( self, trans, **kwd ): @@ -515,7 +650,7 @@ message = "Category '%s' has been created" % category.name trans.sa_session.flush() trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='done' ) ) @@ -530,42 +665,76 @@ status=status ) @web.expose @web.require_admin - def rename_category( self, trans, **kwd ): + def set_tool_state( self, trans, state, **kwd ): + params = util.Params( kwd ) + webapp = params.get( 'webapp', 'galaxy' ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + redirect = params.get( 'no_redirect', True ) + id = params.get( 'id', None ) + if not id: + message = "No tool id received for setting status" + status = 'error' + else: + tool = get_tool( trans, id ) + if state == trans.app.model.Tool.states.APPROVED: + # If we're approving a tool, all previous versions must be set to archived + for version in get_versions( trans, tool ): + if version != tool and version.is_approved(): + self.set_tool_state( trans, trans.app.model.Tool.states.ARCHIVED, id=trans.app.security.encode_id( version.id ), redirect='False' ) + event = trans.model.Event( state ) + # Flush so we an get an id + trans.sa_session.add( event ) + trans.sa_session.flush() + tea = trans.model.ToolEventAssociation( tool, event ) + trans.sa_session.add( tea ) + trans.sa_session.flush() + message = "State of tool '%s' is now %s" % ( tool.name, state ) + if redirect: + trans.response.send_redirect( web.url_for( controller='admin', + action='browse_tools', + webapp=webapp, + message=message, + status=status ) ) + @web.expose + @web.require_admin + def edit_category( self, trans, **kwd ): params = util.Params( kwd ) webapp = params.get( 'webapp', 'galaxy' ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) id = params.get( 'id', None ) if not id: - message = "No category ids received for renaming" + message = "No category ids received for editing" trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=message, status='error' ) ) category = get_category( trans, id ) - if params.get( 'rename_category_button', False ): + if params.get( 'edit_category_button', False ): old_name = category.name new_name = util.restore_text( params.name ) new_description = util.restore_text( params.description ) - if not new_name: - message = 'Enter a valid name' - status = 'error' - elif trans.sa_session.query( trans.app.model.Category ).filter( trans.app.model.Category.table.c.name==new_name ).first(): - message = 'A category with that name already exists' - status = 'error' + if old_name != new_name: + if not new_name: + message = 'Enter a valid name' + status = 'error' + elif trans.sa_session.query( trans.app.model.Category ).filter( trans.app.model.Category.table.c.name==new_name ).first(): + message = 'A category with that name already exists' + status = 'error' else: category.name = new_name category.description = new_description trans.sa_session.add( category ) trans.sa_session.flush() - message = "Category '%s' has been renamed to '%s'" % ( old_name, new_name ) + message = "The information has been saved for category '%s'" % ( new_name ) return trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='done' ) ) - return trans.fill_template( '/webapps/community/category/rename_category.mako', + return trans.fill_template( '/webapps/community/category/edit_category.mako', category=category, webapp=webapp, message=message, @@ -579,7 +748,7 @@ if not id: message = "No category ids received for deleting" trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=message, status='error' ) ) @@ -592,7 +761,7 @@ trans.sa_session.flush() message += " %s " % category.name trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='done' ) ) @@ -605,7 +774,7 @@ if not id: message = "No category ids received for undeleting" trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=message, status='error' ) ) @@ -617,7 +786,7 @@ if not category.deleted: message = "Category '%s' has not been deleted, so it cannot be undeleted." % category.name trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='error' ) ) @@ -628,7 +797,7 @@ undeleted_categories += " %s" % category.name message = "Undeleted %d categories: %s" % ( count, undeleted_categories ) trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='done' ) ) @@ -644,7 +813,7 @@ if not id: message = "No category ids received for purging" trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='error' ) ) @@ -655,7 +824,7 @@ if not category.deleted: message = "Category '%s' has not been deleted, so it cannot be purged." % category.name trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='error' ) ) @@ -665,7 +834,41 @@ trans.sa_session.flush() message += " %s " % category.name trans.response.send_redirect( web.url_for( controller='admin', - action='categories', + action='manage_categories', webapp=webapp, message=util.sanitize_text( message ), status='done' ) ) + +## ---- Utility methods ------------------------------------------------------- + +def get_tools_by_state( trans, state ): + tool_id = [] + if state == trans.model.Tool.states.NEW: + for tool in get_tools( trans ): + if tool.is_new(): + tool_id.append( tool.id ) + elif state == trans.model.Tool.states.ERROR: + for tool in get_tools( trans ): + if tool.is_error(): + tool_id.append( tool.id ) + elif state == trans.model.Tool.states.DELETED: + for tool in get_tools( trans ): + if tool.is_deleted(): + tool_id.append( tool.id ) + elif state == trans.model.Tool.states.WAITING: + for tool in get_tools( trans ): + if tool.is_waiting(): + tool_id.append( tool.id ) + elif state == trans.model.Tool.states.APPROVED: + for tool in get_tools( trans ): + if tool.is_approved(): + tool_id.append( tool.id ) + elif state == trans.model.Tool.states.REJECTED: + for tool in get_tools( trans ): + if tool.is_rejected(): + tool_id.append( tool.id ) + elif state == trans.model.Tool.states.ARCHIVED: + for tool in get_tools( trans ): + if tool.is_archived(): + tool_id.append( tool.id ) + return tool_id diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/controllers/common.py --- a/lib/galaxy/webapps/community/controllers/common.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/common.py Fri Apr 30 18:26:49 2010 -0400 @@ -8,7 +8,7 @@ import logging log = logging.getLogger( __name__ ) -class CommunityCommon( BaseController ): +class CommonController( BaseController ): @web.expose def edit_tool( self, trans, cntrller, **kwd ): params = util.Params( kwd ) @@ -81,9 +81,11 @@ tool = get_tool( trans, id ) categories = [ tca.category for tca in tool.categories ] tool_file_contents = tarfile.open( tool.file_name, 'r' ).getnames() + versions = get_versions( trans, tool ) return trans.fill_template( '/webapps/community/tool/view_tool.mako', tool=tool, tool_file_contents=tool_file_contents, + versions=versions, categories=categories, cntrller=cntrller, message=message, @@ -100,22 +102,78 @@ message='Select a tool to to upload a new version', status='error' ) ) tool = get_tool( trans, id ) - if params.save_button and ( params.file_data != '' or params.url != '' ): - # TODO: call the upload method in the upload controller. - message = 'Uploading new version not implemented' - status = 'error' + return trans.response.send_redirect( web.url_for( controller='upload', + action='upload', + message=message, + status=status, + replace_id=id ) ) + @web.expose + def browse_category( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + id = params.get( 'id', None ) + if not id: + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_categories', + message='Select a category', + status='error' ) ) + category = get_category( trans, id ) + # If request came from the tool controller, then we need to filter by the state of the + # tool in addition to the category. + if cntrller == 'tool': + tool_id = get_approved_tools( trans, category=category ) + else: + # If request came from the admin controller, we don't filter on tool state. + tool_id = [ tca.tool.id for tca in category.tools ] + if not tool_id: + tool_id = 'None' return trans.response.send_redirect( web.url_for( controller=cntrller, action='browse_tools', - message='Not yet implemented, sorry...', - status='error' ) ) + tool_id=tool_id ) ) + @web.expose + def browse_tools_by_user( self, trans, cntrller, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + id = params.get( 'id', None ) + if not id: + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_tools', + message='Select a user', + status='error' ) ) + user = get_user( trans, id ) + # If request came from the tool controller, then we need to filter by the state of the + # tool if the user is not viewing his own tools + if cntrller == 'tool': + tool_id = get_approved_tools( trans, user=user ) + else: + # If request came from the admin controller, we don't filter on tool state. + tool_id = [ tool.id for tool in user.tools ] + if not tool_id: + tool_id = 'None' + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_tools_by_user', + tool_id=tool_id ) ) ## ---- Utility methods ------------------------------------------------------- +def get_versions( trans, tool ): + versions = [tool] + this_tool = tool + while tool.newer_version: + versions.insert( 0, tool.newer_version ) + tool = tool.newer_version + tool = this_tool + while tool.older_version: + versions.append( tool.older_version[0] ) + tool = tool.older_version[0] + return versions def get_categories( trans ): """Get all categories from the database""" return trans.sa_session.query( trans.model.Category ) \ .filter( trans.model.Category.table.c.deleted==False ) \ - .order_by( trans.model.Category.table.c.name ) + .order_by( trans.model.Category.table.c.name ).all() def get_unassociated_categories( trans, obj ): """Get all categories from the database that are not associated with obj""" # TODO: we currently assume we are setting a tool category, so this method may need @@ -142,4 +200,33 @@ obj.categories.append( trans.model.ToolCategoryAssociation( obj, category ) ) def get_tool( trans, id ): return trans.sa_session.query( trans.model.Tool ).get( trans.app.security.decode_id( id ) ) - +def get_tools( trans ): + return trans.sa_session.query( trans.model.Tool ).order_by( trans.model.Tool.name ) +def get_approved_tools( trans, category=None, user=None ): + tool_id = [] + if category: + # Return only the approved tools in the category + for tca in category.tools: + tool = tca.tool + if tool.is_approved(): + tool_id.append( tool.id ) + elif user: + if trans.user == user: + # If the current user is browsing his own tools, then don't filter on state + tool_id = [ tool.id for tool in user.tools ] + else: + # The current user is viewing all tools uploaded by another user, so show only + # approved tools + for tool in user.active_tools: + if tool.is_approved(): + tool_id.append( tool.id ) + else: + # Return all approved tools + for tool in get_tools( trans ): + if tool.is_approved(): + tool_id.append( tool.id ) + return tool_id +def get_event( trans, id ): + return trans.sa_session.query( trans.model.Event ).get( trans.security.decode_id( id ) ) +def get_user( trans, id ): + return trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( id ) ) diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/controllers/tool.py --- a/lib/galaxy/webapps/community/controllers/tool.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/tool.py Fri Apr 30 18:26:49 2010 -0400 @@ -15,38 +15,111 @@ class NameColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): return tool.name + class VersionColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.version + class DescriptionColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.description class CategoryColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): if tool.categories: rval = '' for tca in tool.categories: - rval += '%s<br/>\n' % tca.category.name + rval += '<a href="browse_category?id=%s">%s</a><br/>\n' % ( trans.security.encode_id( tca.category.id ), tca.category.name ) return rval return 'not set' - def filter( self, trans, user, query, column_filter ): - # Category.name conflicts with Tool.name, so we have to make our own filter - def get_single_filter( filter ): - return func.lower( model.Category.name ).like( "%" + filter.lower() + "%" ) - if column_filter == 'All': - pass - elif isinstance( column_filter, list ): - clause_list = [] - for filter in column_filter: - clause_list.append( get_single_filter( filter ) ) - query = query.filter( or_( *clause_list ) ) - else: - query = query.filter( get_single_filter( column_filter ) ) - return query - def get_link( self, trans, grid, tool, filter_params ): + class UserColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return '<a href="browse_tools_by_user?operation=browse&id=%s">%s</a>' % ( trans.security.encode_id( tool.user.id ), tool.user.username ) + # Grid definition + title = "Tools" + model_class = model.Tool + template='/webapps/community/tool/grid.mako' + default_sort_key = "name" + columns = [ + NameColumn( "Name", + key="name", + model_class=model.Tool, + link=( lambda item: dict( operation="View Tool", id=item.id, cntrller='tool', webapp="community" ) ), + attach_popup=True, + filterable="advanced" ), + VersionColumn( "Version", + model_class=model.Tool, + attach_popup=False, + filterable="advanced" ), + DescriptionColumn( "Description", + model_class=model.Tool, + attach_popup=False, + filterable="advanced" ), + CategoryColumn( "Categories", + model_class=model.Category, + attach_popup=False, + filterable="advanced" ), + UserColumn( "Uploaded By", + key="username", + model_class=model.User, + attach_popup=False, + filterable="advanced" ), + # Columns that are valid for filtering but are not visible. + grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) + ] + columns.append( grids.MulticolFilterColumn( "Search", + cols_to_filter=[ columns[0], columns[1] ], + key="free-text-search", + visible=False, + filterable="standard" ) ) + operations = [ + grids.GridOperation( "Download tool", + condition=( lambda item: not item.deleted ), + allow_multiple=False, + url_args=dict( controller="tool", action="download_tool", cntrller="tool", webapp="community" ) ) + ] + standard_filters = [ + grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), + grids.GridColumnFilter( "All", args=dict( deleted='All' ) ) + ] + default_filter = dict( name="All", deleted="False", username="All" ) + num_rows_per_page = 50 + preserve_state = False + use_paging = True + def build_initial_query( self, session ): + return session.query( self.model_class ) + def apply_default_filter( self, trans, query, **kwd ): + def filter_query( query, tool_id ): + if str( tool_id ).lower() in [ '', 'none' ]: + # Return an empty query since the current user cannot view any + # tools (possibly due to state not being approved, etc). + return query.filter( model.Tool.id == None ) + tool_id = util.listify( tool_id ) + query = query.filter( or_( *map( lambda id: self.model_class.id == id, tool_id ) ) ) + return query.filter( self.model_class.deleted==False ) + tool_id = kwd.get( 'tool_id', False ) + if not tool_id: + # Display only approved tools + tool_id = get_approved_tools( trans ) + if not tool_id: + tool_id = 'None' + return filter_query( query, tool_id ) + +class ToolsByUserListGrid( grids.Grid ): + class NameColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.name + class VersionColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.version + class DescriptionColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): + return tool.description + class CategoryColumn( grids.TextColumn ): + def get_value( self, trans, grid, tool ): if tool.categories: - filter_params['f-category'] = [] + rval = '' for tca in tool.categories: - filter_params['f-category'].append( tca.category.name ) - if len( filter_params['f-category'] ) == 1: - filter_params['f-category'] = filter_params['f-category'][0] - filter_params['advanced-search'] = 'True' - return filter_params - return None + rval += '<a href="browse_category?id=%s">%s</a><br/>\n' % ( trans.security.encode_id( tca.category.id ), tca.category.name ) + return rval + return 'not set' class StateColumn( grids.GridColumn ): def get_value( self, trans, grid, tool ): state = tool.state() @@ -75,13 +148,9 @@ return accepted_filters class UserColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): - return tool.user.username - def get_link( self, trans, grid, tool, filter_params ): - filter_params['f-username'] = tool.user.username - filter_params['advanced-search'] = 'True' - return filter_params + return '<a href="browse_tools_by_user?operation=browse&id=%s">%s</a>' % ( trans.security.encode_id( tool.user.id ), tool.user.username ) # Grid definition - title = "Tools" + title = "Tools By User" model_class = model.Tool template='/webapps/community/tool/grid.mako' default_sort_key = "name" @@ -92,21 +161,26 @@ link=( lambda item: dict( operation="View Tool", id=item.id, cntrller='tool', webapp="community" ) ), attach_popup=True, filterable="advanced" ), - CategoryColumn( "Categories", - key="category", + VersionColumn( "Version", model_class=model.Tool, attach_popup=False, filterable="advanced" ), + DescriptionColumn( "Description", + model_class=model.Tool, + attach_popup=False, + filterable="advanced" ), + CategoryColumn( "Categories", + model_class=model.Category, + attach_popup=False, + filterable="advanced" ), + StateColumn( "Status", + model_class=model.Event, + attach_popup=False ), UserColumn( "Uploaded By", key="username", model_class=model.User, attach_popup=False, filterable="advanced" ), - StateColumn( "State", - key="state", - model_class=model.Event, - attach_popup=False, - filterable="advanced" ), # Columns that are valid for filtering but are not visible. grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) ] @@ -130,18 +204,44 @@ preserve_state = False use_paging = True def build_initial_query( self, session ): - return session.query( self.model_class ).outerjoin( model.ToolCategoryAssociation ).outerjoin( model.Category ) - def apply_default_filter( self, trans, query, **kwargs ): - return query.filter( self.model_class.deleted==False ) + return session.query( self.model_class ) + def apply_default_filter( self, trans, query, **kwd ): + def filter_query( query, tool_id ): + if str( tool_id ).lower() in [ '', 'none' ]: + # Return an empty query since the current user cannot view any + # tools (possibly due to state not being approved, etc). + return query.filter( model.Tool.id == None ) + tool_id = util.listify( tool_id ) + query = query.filter( or_( *map( lambda id: self.model_class.id == id, tool_id ) ) ) + return query.filter( self.model_class.deleted==False ) + tool_id = kwd.get( 'tool_id', False ) + if not tool_id: + # Display only approved tools + tool_id = get_approved_tools( trans ) + if not tool_id: + tool_id = 'None' + return filter_query( query, tool_id ) -class ToolCategoryListGrid( grids.Grid ): +class CategoryListGrid( grids.Grid ): class NameColumn( grids.TextColumn ): def get_value( self, trans, grid, category ): return category.name 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: + tool = tca.tool + if tool.is_approved(): + viewable_tools += 1 + return viewable_tools + return 0 + # Grid definition + webapp = "community" title = "Tool Categories" model_class = model.Category template='/webapps/community/category/grid.mako' @@ -157,7 +257,13 @@ key="description", model_class=model.Category, attach_popup=False, - filterable="advanced" ) + filterable="advanced" ), + ToolsColumn( "Tools", + model_class=model.Tool, + attach_popup=False, + filterable="advanced" ), + # Columns that are valid for filtering but are not visible. + grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ) ] columns.append( grids.MulticolFilterColumn( "Search", cols_to_filter=[ columns[0], columns[1] ], @@ -165,21 +271,23 @@ visible=False, filterable="standard" ) ) standard_filters = [ + grids.GridColumnFilter( "Active", args=dict( deleted=False ) ), + grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), grids.GridColumnFilter( "All", args=dict( deleted='All' ) ) ] - default_filter = dict( name="All", deleted="False" ) num_rows_per_page = 50 preserve_state = False use_paging = True def build_initial_query( self, session ): return session.query( self.model_class ) - def apply_default_filter( self, trans, query, **kwargs ): + def apply_default_filter( self, trans, query, **kwd ): return query.filter( self.model_class.deleted==False ) -class ToolBrowserController( BaseController ): - - tool_category_list_grid = ToolCategoryListGrid() +class ToolController( BaseController ): + tool_list_grid = ToolListGrid() + tools_by_user_list_grid = ToolsByUserListGrid() + category_list_grid = CategoryListGrid() @web.expose def index( self, trans, **kwd ): @@ -188,53 +296,84 @@ status = params.get( 'status', 'done' ) return trans.fill_template( '/webapps/community/index.mako', message=message, status=status ) @web.expose - def browse_tool_categories( self, trans, **kwargs ): - if 'operation' in kwargs: - operation = kwargs['operation'].lower() + def browse_categories( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() if operation == "browse category": - category_id = int( trans.app.security.decode_id( kwargs['id'] ) ) - category = trans.sa_session.query( model.Category ).get( category_id ) - del kwargs['id'] - del kwargs['operation'] - kwargs['f-category'] = category.name - return trans.response.send_redirect( web.url_for( controller='tool', - action='browse_tools', - **kwargs ) ) - return self.tool_category_list_grid( trans, **kwargs ) + return self.browse_category( trans, id=kwd['id'] ) + elif operation == "view tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='view_category', + cntrller='tool', + **kwd ) ) + elif operation == "edit tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='edit_category', + cntrller='tool', + **kwd ) ) + # Render the list view + return self.category_list_grid( trans, **kwd ) @web.expose - def browse_tools( self, trans, **kwargs ): - if 'operation' in kwargs: - operation = kwargs['operation'].lower() - if operation == "browse": - return trans.response.send_redirect( web.url_for( controller='tool', - action='browse_tool', + def browse_category( self, trans, **kwd ): + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_category', + cntrller='tool', + **kwd ) ) + @web.expose + def browse_tools( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "browse category": + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_category', cntrller='tool', - **kwargs ) ) + **kwd ) ) elif operation == "view tool": return trans.response.send_redirect( web.url_for( controller='common', action='view_tool', cntrller='tool', - **kwargs ) ) + **kwd ) ) elif operation == "edit tool": return trans.response.send_redirect( web.url_for( controller='common', action='edit_tool', cntrller='tool', - **kwargs ) ) + **kwd ) ) elif operation == "download tool": return trans.response.send_redirect( web.url_for( controller='tool', action='download_tool', - **kwargs ) ) + **kwd ) ) # Render the list view - return self.tool_list_grid( trans, **kwargs ) + return self.tool_list_grid( trans, **kwd ) @web.expose - def browse_tool( self, trans, **kwd ): - params = util.Params( kwd ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - return trans.fill_template( '/webapps/community/tool/browse_tool.mako', - tools=tools, - message=message, - status=status ) + def browse_tools_by_user( self, trans, **kwd ): + if 'operation' in kwd: + operation = kwd['operation'].lower() + if operation == "browse": + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_tools_by_user', + cntrller='tool', + **kwd ) ) + elif operation == "browse category": + return trans.response.send_redirect( web.url_for( controller='common', + action='browse_category', + cntrller='tool', + **kwd ) ) + elif operation == "view tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='view_tool', + cntrller='tool', + **kwd ) ) + elif operation == "edit tool": + return trans.response.send_redirect( web.url_for( controller='common', + action='edit_tool', + cntrller='tool', + **kwd ) ) + elif operation == "download tool": + return trans.response.send_redirect( web.url_for( controller='tool', + action='download_tool', + **kwd ) ) + # Render the list view + return self.tools_by_user_list_grid( trans, **kwd ) @web.expose def download_tool( self, trans, **kwd ): params = util.Params( kwd ) diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/controllers/upload.py --- a/lib/galaxy/webapps/community/controllers/upload.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/upload.py Fri Apr 30 18:26:49 2010 -0400 @@ -3,23 +3,36 @@ from galaxy.web.framework.helpers import time_ago, iff, grids from galaxy.model.orm import * from galaxy.webapps.community import datatypes -from common import get_categories, get_category +from common import get_categories, get_category, get_versions log = logging.getLogger( __name__ ) # States for passing messages SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" +class UploadError( Exception ): + pass + class UploadController( BaseController ): @web.expose + @web.require_login( 'upload', use_panels=True, webapp='community' ) def upload( self, trans, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) category_ids = util.listify( params.get( 'category_id', '' ) ) + replace_id = params.get( 'replace_id', None ) + replace_version = None uploaded_file = None - if params.file_data == '' and params.url.strip() == '': + categories = get_categories( trans ) + if not get_categories( trans ): + return trans.response.send_redirect( web.url_for( controller='tool', + action='browse_tools', + cntrller='tool', + message='No categories have been configured in this instance of the Galaxy Community. An administrator needs to create some via the Administrator control panel before anything can be uploaded', + status='error' ) ) + elif params.file_data == '' and params.url.strip() == '': message = 'No files were entered on the upload form.' status = 'error' elif params.file_data == '': @@ -47,6 +60,29 @@ obj = datatype.create_model_object( meta ) trans.sa_session.add( obj ) if isinstance( obj, trans.app.model.Tool ): + existing = trans.sa_session.query( trans.app.model.Tool ).filter_by( tool_id = meta.id ).all() + if existing and replace_id is None: + raise UploadError( 'A tool with the same ID already exists. If you are trying to update this tool to a new version, please use the upload form on the "Edit Tool" page. Otherwise, please choose a new ID.' ) + elif existing: + replace_version = trans.sa_session.query( trans.app.model.Tool ).get( int( trans.app.security.decode_id( replace_id ) ) ) + if replace_version.newer_version: + # If the user has picked an old version, switch to the newest version + replace_version = get_versions( trans, replace_version )[0] + if trans.user != replace_version.user: + raise UploadError( 'You are not the owner of this tool and may not upload new versions of it.' ) + if replace_version.tool_id != meta.id: + raise UploadError( 'The new tool id (%s) does not match the old tool id (%s). Please check the tool XML file' % ( meta.id, replace_version.tool_id ) ) + for old_version in get_versions( trans, replace_version ): + if old_version.version == meta.version: + raise UploadError( 'The new version (%s) matches an old version. Please check your version in the tool XML file' % meta.version ) + if old_version.is_new(): + raise UploadError( 'There is an existing version of this tool which is unsubmitted. Please either <a href="%s">submit or delete it</a> before uploading a new version.' % url_for( controller='common', + action='view_tool', + cntrller='tool', + id=trans.app.security.encode_id( old_version.id ) ) ) + if old_version.is_waiting(): + raise UploadError( 'There is an existing version of this tool which is waiting for administrative approval. Please contact an administrator for help.' ) + # Defer setting the id since the newer version id doesn't exist until the new Tool object is flushed if category_ids: for category_id in category_ids: category = trans.app.model.Category.get( trans.security.decode_id( category_id ) ) @@ -58,6 +94,9 @@ tea = trans.app.model.ToolEventAssociation( obj, event ) trans.sa_session.add_all( ( event, tea ) ) trans.sa_session.flush() + if replace_version and replace_id: + replace_version.newer_version_id = obj.id + trans.sa_session.flush() try: os.link( uploaded_file.name, obj.file_name ) except OSError: @@ -69,13 +108,29 @@ id=trans.app.security.encode_id( obj.id ), message='Uploaded %s' % meta.message, status='done' ) ) - except datatypes.DatatypeVerificationError, e: + except ( datatypes.DatatypeVerificationError, UploadError ), e: message = str( e ) status = 'error' - except sqlalchemy.exc.IntegrityError: - message = 'A tool with the same ID already exists. If you are trying to update this tool to a new version, please use the upload form on the "Edit Tool" page. Otherwise, please choose a new ID.' - status = 'error' uploaded_file.close() + elif replace_id is not None: + replace_version = trans.sa_session.query( trans.app.model.Tool ).get( int( trans.app.security.decode_id( replace_id ) ) ) + old_version = None + for old_version in get_versions( trans, replace_version ): + if old_version.is_new(): + message = 'There is an existing version of this tool which is unsubmitted. Please either submit or delete it before uploading a new version.' + break + if old_version.is_waiting(): + message = 'There is an existing version of this tool which is waiting for administrative approval. Please contact an administrator for help.' + break + else: + old_version = None + if old_version is not None: + return trans.response.send_redirect( web.url_for( controller='common', + action='view_tool', + cntrller='tool', + id=trans.app.security.encode_id( old_version.id ), + message=message, + status='error' ) ) selected_upload_type = params.get( 'type', 'tool' ) selected_categories = [ trans.security.decode_id( id ) for id in category_ids ] return trans.fill_template( '/webapps/community/upload/upload.mako', @@ -83,5 +138,6 @@ status=status, selected_upload_type=selected_upload_type, upload_types=trans.app.datatypes_registry.get_datatypes_for_select_list(), + replace_id=replace_id, selected_categories=selected_categories, categories=get_categories( trans ) ) diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/model/__init__.py --- a/lib/galaxy/webapps/community/model/__init__.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/model/__init__.py Fri Apr 30 18:26:49 2010 -0400 @@ -92,7 +92,8 @@ DELETED = 'deleted', WAITING = 'waiting for approval', APPROVED = 'approved', - REJECTED = 'rejected' ) + REJECTED = 'rejected', + ARCHIVED = 'archived' ) def __init__( self, guid=None, tool_id=None, name=None, description=None, user_description=None, category=None, version=None, user_id=None, external_filename=None ): self.guid = guid self.tool_id = tool_id @@ -153,10 +154,14 @@ return self.state() == self.states.ERROR def is_deleted( self ): return self.state() == self.states.DELETED + def is_waiting( self ): + return self.state() == self.states.WAITING def is_approved( self ): return self.state() == self.states.APPROVED def is_rejected( self ): return self.state() == self.states.REJECTED + def is_archived( self ): + return self.state() == self.states.ARCHIVED @property def extension( self ): # if instantiated via a query, this unmapped property won't exist diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/model/mapping.py --- a/lib/galaxy/webapps/community/model/mapping.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/model/mapping.py Fri Apr 30 18:26:49 2010 -0400 @@ -103,9 +103,10 @@ Tool.table = Table( "tool", metadata, Column( "id", Integer, primary_key=True ), Column( "guid", TrimmedString( 255 ), index=True, unique=True ), - Column( "tool_id", TrimmedString( 255 ), index=True, unique=True ), + Column( "tool_id", TrimmedString( 255 ), index=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "newer_version_id", Integer, ForeignKey( "tool.id" ), nullable=True ), Column( "name", TrimmedString( 255 ), index=True ), Column( "description" , TEXT ), Column( "user_description" , TEXT ), @@ -164,8 +165,8 @@ # With the tables defined we can define the mappers and setup the # relationships between the model objects. assign_mapper( context, User, User.table, - properties=dict( tools=relation( Tool, order_by=desc( Tool.table.c.update_time ) ), - active_tools=relation( Tool, primaryjoin=( ( Tool.table.c.user_id == User.table.c.id ) & ( not_( Tool.table.c.deleted ) ) ), order_by=desc( Tool.table.c.update_time ) ), + properties=dict( tools=relation( Tool, primaryjoin=( Tool.table.c.user_id == User.table.c.id ), order_by=( Tool.table.c.name ) ), + active_tools=relation( Tool, primaryjoin=( ( Tool.table.c.user_id == User.table.c.id ) & ( not_( Tool.table.c.deleted ) ) ), order_by=( Tool.table.c.name ) ), galaxy_sessions=relation( GalaxySession, order_by=desc( GalaxySession.table.c.update_time ) ) ) ) assign_mapper( context, Group, Group.table, @@ -215,7 +216,11 @@ properties = dict( categories=relation( ToolCategoryAssociation ), events=relation( ToolEventAssociation ), - user=relation( User.mapper ) + user=relation( User.mapper ), + older_version=relation( + Tool, + primaryjoin=( Tool.table.c.newer_version_id == Tool.table.c.id ), + backref=backref( "newer_version", primaryjoin=( Tool.table.c.newer_version_id == Tool.table.c.id ), remote_side=[Tool.table.c.id] ) ) ) ) assign_mapper( context, Event, Event.table, @@ -238,7 +243,6 @@ ) ) - def guess_dialect_for_url( url ): return (url.split(':', 1))[0] diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py --- a/lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py Fri Apr 30 18:26:49 2010 -0400 @@ -80,9 +80,10 @@ Tool_table = Table( "tool", metadata, Column( "id", Integer, primary_key=True ), Column( "guid", TrimmedString( 255 ), index=True, unique=True ), - Column( "tool_id", TrimmedString( 255 ), index=True, unique=True ), + Column( "tool_id", TrimmedString( 255 ), index=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "newer_version_id", Integer, ForeignKey( "tool.id" ), nullable=True ), Column( "name", TrimmedString( 255 ), index=True ), Column( "description" , TEXT ), Column( "user_description" , TEXT ), diff -r 8e00344d941a -r 3ac0dfcdf44e lib/galaxy/webapps/community/security/__init__.py --- a/lib/galaxy/webapps/community/security/__init__.py Fri Apr 30 18:26:06 2010 -0400 +++ b/lib/galaxy/webapps/community/security/__init__.py Fri Apr 30 18:26:49 2010 -0400 @@ -167,6 +167,12 @@ # We currently assume the current user can edit the item if they are the owner (i.e., they # uploaded the item), and the item is in a NEW state. return user and user==item.user and item.is_new() + def can_upload_new_version( self, user, item, versions ): + state_ok = True + for version in versions: + if version.is_new() or version.is_approved(): + state_ok = False + return user and user==item.user and state_ok def get_permitted_actions( filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/admin/index.mako --- a/templates/webapps/community/admin/index.mako Fri Apr 30 18:26:06 2010 -0400 +++ b/templates/webapps/community/admin/index.mako Fri Apr 30 18:26:49 2010 -0400 @@ -54,7 +54,6 @@ </style> </%def> - <%def name="init()"> <% self.has_left_panel=True @@ -71,32 +70,34 @@ <div class="toolMenu"> <div class="toolSectionList"> <div class="toolSectionTitle"> + <span>Tools</span> + </div> + <div class="toolSectionBody"> + <div class="toolSectionBg"> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='browse_tools_by_state', state=trans.model.Tool.states.WAITING, webapp='community' )}">Tools awaiting approval</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='browse_tools_by_category' )}">Browse by category</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='browse_tools', webapp='community' )}">Browse all tools</a></div> + </div> + </div> + <div class="toolSectionPad"></div> + <div class="toolSectionTitle"> + <span>Categories</span> + </div> + <div class="toolSectionBody"> + <div class="toolSectionBg"> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='manage_categories', webapp='community' )}">Manage categories</a></div> + </div> + </div> + <div class="toolSectionTitle"> <span>Security</span> </div> <div class="toolSectionBody"> <div class="toolSectionBg"> - <div class="toolTitle"><a href="${h.url_for( controller='admin', action='users', webapp='community' )}" target="galaxy_main">Manage users</a></div> - <div class="toolTitle"><a href="${h.url_for( controller='admin', action='groups', webapp='community' )}" target="galaxy_main">Manage groups</a></div> - <div class="toolTitle"><a href="${h.url_for( controller='admin', action='roles', webapp='community' )}" target="galaxy_main">Manage roles</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='users', webapp='community' )}">Manage users</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='groups', webapp='community' )}">Manage groups</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='admin', action='roles', webapp='community' )}">Manage roles</a></div> </div> </div> - <div class="toolSectionTitle"> - <span>Tools</span> - </div> - <div class="toolSectionBody"> - <div class="toolSectionBg"> - <div class="toolTitle"><a href="${h.url_for( controller='admin', action='browse_tools', webapp='community' )}" target="galaxy_main">Manage tools</a></div> - </div> - </div> - <div class="toolSectionPad"></div> - <div class="toolSectionTitle"> - <span>Community</span> - </div> - <div class="toolSectionBody"> - <div class="toolSectionBg"> - <div class="toolTitle"><a href="${h.url_for( controller='admin', action='categories', webapp='community' )}" target="galaxy_main">Manage categories</a></div> - </div> - </div> </div> </div> </div> diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/category/edit_category.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/webapps/community/category/edit_category.mako Fri Apr 30 18:26:49 2010 -0400 @@ -0,0 +1,44 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Change category name and description</div> + <div class="toolFormBody"> + <form name="library" action="${h.url_for( controller='admin', action='edit_category' )}" method="post" > + <div class="form-row"> + <input name="webapp" type="hidden" value="${webapp}" size=40"/> + <label>Name:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + <input type="text" name="name" value="${category.name}" size="40"/> + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <label>Description:</label> + <div style="float: left; width: 250px; margin-right: 10px;"> + <input name="description" type="textfield" value="${category.description}" size=40"/> + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <div style="float: left; width: 250px; margin-right: 10px;"> + <input type="hidden" name="rename" value="submitted"/> + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <div style="float: left; width: 250px; margin-right: 10px;"> + <input type="hidden" name="id" value="${trans.security.encode_id( category.id )}"/> + </div> + <div style="clear: both"></div> + </div> + <div class="form-row"> + <input type="submit" name="edit_category_button" value="Save"/> + </div> + </form> + </div> +</div> diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/category/rename_category.mako --- a/templates/webapps/community/category/rename_category.mako Fri Apr 30 18:26:06 2010 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -<%inherit file="/base.mako"/> -<%namespace file="/message.mako" import="render_msg" /> - -%if message: - ${render_msg( message, status )} -%endif - -<div class="toolForm"> - <div class="toolFormTitle">Change category name and description</div> - <div class="toolFormBody"> - <form name="library" action="${h.url_for( controller='admin', action='rename_category' )}" method="post" > - <div class="form-row"> - <input name="webapp" type="hidden" value="${webapp}" size=40"/> - <label>Name:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input type="text" name="name" value="${category.name}" size="40"/> - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>Description:</label> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input name="description" type="textfield" value="${category.description}" size=40"/> - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input type="hidden" name="rename" value="submitted"/> - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <div style="float: left; width: 250px; margin-right: 10px;"> - <input type="hidden" name="id" value="${trans.security.encode_id( category.id )}"/> - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <input type="submit" name="rename_category_button" value="Save"/> - </div> - </form> - </div> -</div> diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/index.mako --- a/templates/webapps/community/index.mako Fri Apr 30 18:26:06 2010 -0400 +++ b/templates/webapps/community/index.mako Fri Apr 30 18:26:49 2010 -0400 @@ -1,6 +1,57 @@ <%inherit file="/webapps/community/base_panels.mako"/> <%namespace file="/message.mako" import="render_msg" /> +<%def name="stylesheets()"> + ${parent.stylesheets()} + ## TODO: Clean up these styles and move into panel_layout.css (they are + ## used here and in the editor). + <style type="text/css"> + #left { + background: #C1C9E5 url(${h.url_for('/static/style/menu_bg.png')}) top repeat-x; + } + div.toolMenu { + margin: 5px; + margin-left: 10px; + margin-right: 10px; + } + div.toolSectionPad { + margin: 0; + padding: 0; + height: 5px; + font-size: 0px; + } + div.toolSectionDetailsInner { + margin-left: 5px; + margin-right: 5px; + } + div.toolSectionTitle { + padding-bottom: 0px; + font-weight: bold; + } + div.toolMenuGroupHeader { + font-weight: bold; + padding-top: 0.5em; + padding-bottom: 0.5em; + color: #333; + font-style: italic; + border-bottom: dotted #333 1px; + margin-bottom: 0.5em; + } + div.toolTitle { + padding-top: 5px; + padding-bottom: 5px; + margin-left: 16px; + margin-right: 10px; + display: list-item; + list-style: square outside; + } + a:link, a:visited, a:active + { + color: #303030; + } + </style> +</%def> + <%def name="init()"> <% self.has_left_panel=True @@ -29,17 +80,20 @@ </div> <div class="toolSectionBody"> <div class="toolSectionBg"> - <div class="toolTitle"><a href="${h.url_for( controller='tool', action='browse_tool_categories' )}" target="galaxy_main">Browse tools by category</a></div> - <div class="toolTitle"><a href="${h.url_for( controller='tool', action='browse_tools' )}" target="galaxy_main">Browse all tools</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_categories' )}">Browse by category</a></div> + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools' )}">Browse all tools</a></div> + %if trans.user: + <div class="toolTitle"><a target="galaxy_main" href="${h.url_for( controller='tool', action='browse_tools_by_user', operation='browse', id=trans.security.encode_id( trans.user.id ) )}">Browse your tools</a></div> + %endif </div> </div> <div class="toolSectionBody"> <div class="toolSectionBg"> <div class="toolTitle"> %if trans.user: - <a href="${h.url_for( controller='upload', action='upload', type='tool' )}" target="galaxy_main">Upload a tool</a> + <a target="galaxy_main" href="${h.url_for( controller='upload', action='upload', type='tool' )}">Upload a tool</a> %else: - Login to upload + <a target="galaxy_main" href="${h.url_for( controller='/user', action='login', webapp='community' )}">Login to upload</a> %endif </div> </div> @@ -54,7 +108,7 @@ if trans.app.config.require_login and not trans.user: center_url = h.url_for( controller='user', action='login', message=message, status=status ) else: - center_url = h.url_for( controller='tool', action='browse_tool_categories', message=message, status=status ) + center_url = h.url_for( controller='tool', action='browse_categories', message=message, status=status ) %> <iframe name="galaxy_main" id="galaxy_main" frameborder="0" style="position: absolute; width: 100%; height: 100%;" src="${center_url}"> </iframe> </%def> diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/tool/browse_tool.mako --- a/templates/webapps/community/tool/browse_tool.mako Fri Apr 30 18:26:06 2010 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -<%namespace file="/message.mako" import="render_msg" /> - -<%! - def inherit(context): - if context.get('use_panels'): - return '/webapps/community/base_panels.mako' - else: - return '/base.mako' -%> -<%inherit file="${inherit(context)}"/> - -<%def name="title()">Browse Tool</%def> - -<h2>Galaxy Tool</h2> - -%if message: - ${render_msg( message, status )} -%endif - -%if not tools: - There are no tools -%else: - <table class="grid"> - <thead> - <tr> - <th>Name</th> - <th>Description</th> - </tr> - </thead> - <tbody> - <tr class="formRow id="toolRow"> - <td><a href="${h.url_for( controller='tool', action='browse', id=trans.security.encode_id( tool.id ) )}">${tool.name}</a></td> - <td>${tool.description}</td> - </tr> - </tbody> - </table> -%endif diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/tool/edit_tool.mako --- a/templates/webapps/community/tool/edit_tool.mako Fri Apr 30 18:26:06 2010 -0400 +++ b/templates/webapps/community/tool/edit_tool.mako Fri Apr 30 18:26:49 2010 -0400 @@ -58,7 +58,6 @@ <a id="tool-${tool.id}-popup" class="popup-arrow" style="display: none;">▼</a> <div popupmenu="tool-${tool.id}-popup"> <a class="action-button" href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">View information</a> - <a class="action-button" href="${h.url_for( controller='common', action='manage_categories', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Manage categories</a> <a class="action-button" href="${h.url_for( controller='common', action='upload_new_tool_version', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Upload a new version</a> <a class="action-button" href="${h.url_for( controller='tool', action='download_tool', id=trans.app.security.encode_id( tool.id ) )}">Download tool</a> </div> @@ -123,7 +122,7 @@ <div class="toolParamHelp" style="clear: both;"> Tools must be approved before they are made available to others in the community. After you have submitted your tool to be published, you will no longer be able to modify it, so make sure the information above is - correct and has been saved before submitting for approval. + correct and and save any changes before submitting for approval. </div> </div> </form> diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/tool/view_tool.mako --- a/templates/webapps/community/tool/view_tool.mako Fri Apr 30 18:26:06 2010 -0400 +++ b/templates/webapps/community/tool/view_tool.mako Fri Apr 30 18:26:49 2010 -0400 @@ -6,6 +6,12 @@ if cntrller in [ 'tool' ]: can_edit = trans.app.security_agent.can_edit_item( trans.user, tool ) + can_upload_new_version = trans.app.security_agent.can_upload_new_version( trans.user, tool, versions ) + + visible_versions = [] + for version in versions: + if version.is_approved() or version.is_archived() or version.user == trans.user: + visible_versions.append( version ) %> <%! @@ -49,6 +55,30 @@ <h2>View Tool: ${tool.name} <em>${tool.description}</em></h2> +%if tool.is_approved(): + <b><i>This is the latest approved version of this tool</i></b> +%elif tool.is_deleted(): + <font color="red"><b><i>This is a deleted version of this tool</i></b></font> +%elif tool.is_archived(): + <font color="red"><b><i>This is an archived version of this tool</i></b></font> +%elif tool.is_new(): + <font color="red"><b><i>This is an unsubmitted version of this tool</i></b></font> +%elif tool.is_waiting(): + <font color="red"><b><i>This version of this tool is awaiting administrative approval</i></b></font> +%elif tool.is_rejected(): + <font color="red"><b><i>This version of this tool has been rejected by an administrator</i></b></font> +%endif +<p/> + +%if cntrller=='admin' and tool.is_waiting(): + <p> + <ul class="manage-table-actions"> + <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.APPROVED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}"><span>Approve</span></a></li> + <li><a class="action-button" href="${h.url_for( controller='admin', action='set_tool_state', state=trans.model.Tool.states.REJECTED, id=trans.security.encode_id( tool.id ), cntrller=cntrller )}"><span>Reject</span></a></li> + </ul> + </p> +%endif + %if message: ${render_msg( message, status )} %endif @@ -59,7 +89,8 @@ <div popupmenu="tool-${tool.id}-popup"> %if cntrller=='admin' or can_edit: <a class="action-button" href="${h.url_for( controller='common', action='edit_tool', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Edit information</a> - <a class="action-button" href="${h.url_for( controller='common', action='manage_categories', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Manage categories</a> + %endif + %if cntrller=='admin' or can_upload_new_version: <a class="action-button" href="${h.url_for( controller='common', action='upload_new_tool_version', id=trans.app.security.encode_id( tool.id ), cntrller=cntrller )}">Upload a new version</a> %endif <a class="action-button" href="${h.url_for( controller='tool', action='download_tool', id=trans.app.security.encode_id( tool.id ) )}">Download tool</a> @@ -93,11 +124,30 @@ </div> <div class="form-row"> <label>Categories:</label> - %for category in categories: - ${category.name} - %endfor + %if categories: + %for category in categories: + ${category.name} + %endfor + %else: + none set + %endif <div style="clear: both"></div> </div> + %if len( visible_versions ) > 1: + <div class="form-row"> + <label>All Versions:</label> + <ul> + %for version in visible_versions: + %if version == tool: + <li><strong>${version.version} (this version)</strong></li> + %else: + <li><a href="${h.url_for( controller='common', action='view_tool', id=trans.app.security.encode_id( version.id ), cntrller=cntrller )}">${version.version}</a></li> + %endif + %endfor + </ul> + <div style="clear: both"></div> + </div> + %endif </div> </div> diff -r 8e00344d941a -r 3ac0dfcdf44e templates/webapps/community/upload/upload.mako --- a/templates/webapps/community/upload/upload.mako Fri Apr 30 18:26:06 2010 -0400 +++ b/templates/webapps/community/upload/upload.mako Fri Apr 30 18:26:49 2010 -0400 @@ -22,6 +22,9 @@ <div class="toolFormBody"> ## TODO: nginx <form id="upload_form" name="upload_form" action="${h.url_for( controller='upload', action='upload' )}" enctype="multipart/form-data" method="post"> + %if replace_id is not None: + <input type='hidden' name="replace_id" value="${replace_id}"/> + %endif <div class="form-row"> <label>Upload Type</label> <div class="form-row-input"> diff -r 8e00344d941a -r 3ac0dfcdf44e tools/samtools/sam_to_bam.py --- a/tools/samtools/sam_to_bam.py Fri Apr 30 18:26:06 2010 -0400 +++ b/tools/samtools/sam_to_bam.py Fri Apr 30 18:26:49 2010 -0400 @@ -94,7 +94,7 @@ tmp_stderr.close() if returncode != 0: raise Exception, stderr - if len( open( fai_index_file_path ).read().strip() ) == 0: + if os.path.getsize( fai_index_file_path ) == 0: raise Exception, 'Index file empty, there may be an error with your reference file or settings.' except Exception, e: #clean up temp files diff -r 8e00344d941a -r 3ac0dfcdf44e tools/sr_mapping/bowtie_wrapper.py --- a/tools/sr_mapping/bowtie_wrapper.py Fri Apr 30 18:26:06 2010 -0400 +++ b/tools/sr_mapping/bowtie_wrapper.py Fri Apr 30 18:26:49 2010 -0400 @@ -387,8 +387,8 @@ if returncode != 0: raise Exception, stderr # check that there are results in the output file - if len( open( options.output, 'rb' ).read().strip() ) == 0: - raise Exception, 'The output file is empty, there may be an error with your input file or settings.' + if os.path.getsize( options.output ) == 0: + raise Exception, 'The output file is empty, there may be an error with your input file or settings.' + '\nextra: ' + str(extra) except Exception, e: stop_err( 'Error aligning sequence. ' + str( e ) ) finally: diff -r 8e00344d941a -r 3ac0dfcdf44e tools/sr_mapping/bwa_wrapper_code.py --- a/tools/sr_mapping/bwa_wrapper_code.py Fri Apr 30 18:26:06 2010 -0400 +++ b/tools/sr_mapping/bwa_wrapper_code.py Fri Apr 30 18:26:49 2010 -0400 @@ -2,11 +2,10 @@ def exec_before_job(app, inp_data, out_data, param_dict, tool): try: - refFile = param_dict['solidOrSolexa']['solidRefGenomeSource']['indices'].value + refFile = param_dict[ 'genomeSource' ][ 'indices' ].value except: try: - refFile = param_dict['solidOrSolexa']['solidRefGenomeSource']['ownFile'].dbkey + refFile = param_dict[ 'genomeSource' ][ 'ownFile' ].dbkey except: - out_data['output'].set_dbkey('?') - return - out_data['output'].set_dbkey(os.path.split(refFile)[1].split('.')[0]) + refFile = '?' + out_data[ 'output' ].set_dbkey( os.path.split( refFile )[1].split( '.' )[0] )