details: http://www.bx.psu.edu/hg/galaxy/rev/3009820dfc51 changeset: 3702:3009820dfc51 user: Greg Von Kuster <greg@bx.psu.edu> date: Tue Apr 27 11:31:23 2010 -0400 description: Fix managing tool categories and separate the different forms on the edit tool page. diffstat: lib/galaxy/webapps/community/controllers/admin.py | 22 +- lib/galaxy/webapps/community/controllers/common.py | 150 ++++++------- lib/galaxy/webapps/community/controllers/tool.py | 43 ++- lib/galaxy/webapps/community/security/__init__.py | 17 +- templates/webapps/community/category/add_to_category.mako | 24 -- templates/webapps/community/tool/edit_tool.mako | 45 +--- templates/webapps/community/tool/manage_categories.mako | 63 +++++ 7 files changed, 188 insertions(+), 176 deletions(-) diffs (576 lines): diff -r b633d836ce07 -r 3009820dfc51 lib/galaxy/webapps/community/controllers/admin.py --- a/lib/galaxy/webapps/community/controllers/admin.py Tue Apr 27 10:59:46 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/admin.py Tue Apr 27 11:31:23 2010 -0400 @@ -6,6 +6,9 @@ import logging log = logging.getLogger( __name__ ) +# States for passing messages +SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" + class UserListGrid( grids.Grid ): class EmailColumn( grids.TextColumn ): def get_value( self, trans, grid, user ): @@ -346,9 +349,6 @@ def build_initial_query( self, session ): return session.query( self.model_class ) -# States for passing messages -SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" - class ToolListGrid( grids.Grid ): class NameColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): @@ -399,7 +399,7 @@ NameColumn( "Name", key="name", model_class=model.Tool, - link=( lambda item: dict( operation="Edit Tool", id=item.id, webapp="community" ) ), + link=( lambda item: dict( operation="View Tool", id=item.id, webapp="community" ) ), attach_popup=True, filterable="advanced" ), CategoryColumn( "Category", @@ -424,15 +424,14 @@ grids.GridAction( "Upload tool", dict( controller='upload', action='upload', type='tool' ) ) ] operations = [ - grids.GridOperation( "Add to category", + grids.GridOperation( "Edit information", condition=( lambda item: not item.deleted ), allow_multiple=False, - url_args=dict( controller="common", action="add_category", cntrller="admin", webapp="community" ) ), - grids.GridOperation( "Remove from category", + 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="remove_category", cntrller="admin", webapp="community" ) ), - grids.GridOperation( "View versions", condition=( lambda item: not item.deleted ), allow_multiple=False ) + url_args=dict( controller="common", action="manage_categories", cntrller="admin", webapp="community" ) ) ] standard_filters = [ grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), @@ -469,6 +468,11 @@ 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 ) ) # Render the list view return self.tool_list_grid( trans, **kwargs ) @web.expose diff -r b633d836ce07 -r 3009820dfc51 lib/galaxy/webapps/community/controllers/common.py --- a/lib/galaxy/webapps/community/controllers/common.py Tue Apr 27 10:59:46 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/common.py Tue Apr 27 11:31:23 2010 -0400 @@ -9,123 +9,106 @@ class CommunityCommon( BaseController ): @web.expose - def edit_tool( self, trans, cntrller, id=None, **kwd ): + def edit_tool( self, trans, cntrller, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) - # Get the tool - tool = None - if id is not None: - encoded_id = id - id = trans.app.security.decode_id( id ) - tool = trans.sa_session.query( trans.model.Tool ).get( id ) - if tool is None: + id = params.get( 'id', None ) + if not id: return trans.response.send_redirect( web.url_for( controller=cntrller, action='browse_tools', - message='Please select a Tool to edit (the tool ID provided was invalid)', + message='Select a tool to edit', status='error' ) ) - 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' - elif params.save_button: + tool = get_tool( trans, id ) + if params.get( 'edit_tool_button', False ): tool.user_description = util.restore_text( params.description ) - categories = [] - set_categories( trans, tool, util.listify( params.category_id ) ) trans.sa_session.add( tool ) trans.sa_session.flush() - return trans.response.send_redirect( web.url_for( controller=cntrller, - action='browse_tools', - message="Saved categories and description for tool '%s'" % tool.name, + return trans.response.send_redirect( web.url_for( controller='common', + action='edit_tool', + cntrller=cntrller, + id=id, + message="The information was updated", status='done' ) ) - categories = trans.sa_session.query( trans.model.Category ).order_by( trans.model.Category.table.c.name ).all() return trans.fill_template( '/webapps/community/tool/edit_tool.mako', cntrller=cntrller, - encoded_id = encoded_id, + id=id, tool=tool, - categories=categories, message=message, status=status ) @web.expose - def view_tool( self, trans, cntrller, id=None, **kwd ): + def view_tool( self, trans, cntrller, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) - # Get the tool - tool = None - if id is not None: - id = trans.app.security.decode_id( id ) - tool = trans.sa_session.query( trans.model.Tool ).get( id ) - if tool is None: + id = params.get( 'id', None ) + if not id: return trans.response.send_redirect( web.url_for( controller=cntrller, action='browse_tools', - message='Please select a Tool to edit (the tool ID provided was invalid)', + message='Select a tool to view', status='error' ) ) + tool = get_tool( trans, id ) return trans.fill_template( '/webapps/community/tool/view_tool.mako', tool=tool, message=message, status=status ) @web.expose - def add_category( self, trans, cntrller, **kwd ): - # TODO: we currently assume we are setting a tool category, so this method may need - # tweaking if / when we decide to set history or workflow categories + def manage_categories( self, trans, cntrller, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) - use_panels = util.string_as_bool( params.get( 'use_panels', False ) ) id = params.get( 'id', None ) - # TODO: redirect if no id - tool = trans.sa_session.query( trans.model.Tool ).get( trans.security.decode_id( id ) ) - if params.get( 'add_category_button', False ): - category_ids = util.listify( params.get( 'category_id', '' ) ) - # TODO: redirect if no category_id - message = "The tool '%s' has been added to the categories: " % ( tool.name ) - for category_id in category_ids: - category = trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( category_id ) ) - tca = trans.app.model.ToolCategoryAssociation( tool, category ) - trans.sa_session.add( tca ) - trans.sa_session.flush() - message += " %s " % category.name - trans.response.send_redirect( web.url_for( controller=cntrller, - action='browse_tools', - use_panels=use_panels, + if not id: + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_tools', + message='Select a tool to manage categories', + status='error' ) ) + tool = get_tool( trans, id ) + if params.get( 'manage_categories_button', False ): + in_categories = [ trans.sa_session.query( trans.app.model.Category ).get( x ) for x in util.listify( params.in_categories ) ] + trans.app.security_agent.set_entity_category_associations( tools=[ tool ], categories=in_categories ) + trans.sa_session.refresh( tool ) + message = "Tool '%s' has been updated with %d associated categories" % ( tool.name, len( in_categories ) ) + trans.response.send_redirect( web.url_for( controller='common', + action='manage_categories', cntrller=cntrller, + id=id, message=util.sanitize_text( message ), - status=status ) ) - category_select_list = SelectField( 'category_id', multiple=True ) - for category in get_unassociated_categories( trans, tool ): - category_select_list.add_option( category.name, trans.security.encode_id( category.id ) ) - return trans.fill_template( '/webapps/community/category/add_to_category.mako', + status=status ) ) + in_categories = [] + out_categories = [] + for category in get_categories( trans ): + if category in [ x.category for x in tool.categories ]: + in_categories.append( ( category.id, category.name ) ) + else: + out_categories.append( ( category.id, category.name ) ) + return trans.fill_template( '/webapps/community/tool/manage_categories.mako', + tool=tool, + in_categories=in_categories, + out_categories=out_categories, cntrller=cntrller, - id=id, - category_select_list=category_select_list, - use_panels=use_panels ) + message=message, + status=status ) @web.expose - def remove_category( self, trans, cntrller, **kwd ): - # TODO: we currently assume we are setting a tool category, so this method may need - # tweaking if / when we decide to set history or workflow categories + def upload_new_tool_version( self, trans, cntrller, **kwd ): params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) - use_panels = util.string_as_bool( params.get( 'use_panels', False ) ) id = params.get( 'id', None ) - # TODO: redirect if no id - tool = trans.sa_session.query( trans.model.Tool ).get( trans.security.decode_id( id ) ) - category_id = params.get( 'category_id', None ) - category = trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( category_id ) ) - # TODO: redirect if no category_id - for tca in tool.categories: - if tca.category == category: - trans.sa_session.delete( tca ) - trans.sa_session.flush() - break - message = "The tool '%s' has been removed from the category '%s'" % ( tool.name, category.name ) - trans.response.send_redirect( web.url_for( controller=cntrller, - action='browse_tools', - use_panels=use_panels, - cntrller=cntrller, - message=util.sanitize_text( message ), - status=status ) ) + if not id: + return trans.response.send_redirect( web.url_for( controller=cntrller, + action='browse_tools', + 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=cntrller, + action='browse_tools', + message='Not yet implemented, sorry...', + status='error' ) ) ## ---- Utility methods ------------------------------------------------------- @@ -147,13 +130,7 @@ categories.append( category ) return categories def get_category( trans, id ): - """Get a Category from the database by id.""" - # Load user from database - id = trans.security.decode_id( id ) - category = trans.sa_session.query( trans.model.Category ).get( id ) - if not category: - return trans.show_error_message( "Category not found for id (%s)" % str( id ) ) - return category + return trans.sa_session.query( trans.model.Category ).get( trans.security.decode_id( id ) ) def set_categories( trans, obj, category_ids, delete_existing_assocs=True ): if delete_existing_assocs: for assoc in obj.categories: @@ -164,3 +141,6 @@ # tweaking if / when we decide to set history or workflow categories category = trans.sa_session.query( trans.model.Category ).get( category_id ) 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 ) ) + diff -r b633d836ce07 -r 3009820dfc51 lib/galaxy/webapps/community/controllers/tool.py --- a/lib/galaxy/webapps/community/controllers/tool.py Tue Apr 27 10:59:46 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/tool.py Tue Apr 27 11:31:23 2010 -0400 @@ -18,7 +18,10 @@ class CategoryColumn( grids.TextColumn ): def get_value( self, trans, grid, tool ): if tool.categories: - return tool.categories + rval = '' + for tca in tool.categories: + rval = '%s%s<br/>' % ( rval, tca.category.name ) + return rval return 'not set' class StateColumn( grids.GridColumn ): def get_value( self, trans, grid, tool ): @@ -58,15 +61,19 @@ NameColumn( "Name", key="name", model_class=model.Tool, - link=( lambda item: dict( operation="Edit Tool", id=item.id, webapp="community" ) ), + link=( lambda item: dict( operation="View Tool", id=item.id, webapp="community" ) ), attach_popup=True, filterable="advanced" ), CategoryColumn( "Category", - key="category", - model_class=model.Category, - link=( lambda item: dict( operation="View Tool", id=item.id, webapp="community" ) ), - attach_popup=False, - filterable="advanced" ), + key="category", + model_class=model.Category, + 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" ) ] @@ -79,15 +86,18 @@ grids.GridAction( "Upload tool", dict( controller='upload', action='upload', type='tool' ) ) ] operations = [ - grids.GridOperation( "Add to category", + grids.GridOperation( "Edit information", condition=( lambda item: not item.deleted ), allow_multiple=False, - url_args=dict( controller="common", action="add_category", webapp="community" ) ), - grids.GridOperation( "Remove from category", + url_args=dict( controller="common", action="edit_tool", cntrller="tool", webapp="community" ) ), + grids.GridOperation( "Manage categories", condition=( lambda item: not item.deleted ), allow_multiple=False, - url_args=dict( controller="common", action="remove_category", webapp="community" ) ), - grids.GridOperation( "View versions", condition=( lambda item: not item.deleted ), allow_multiple=False ) + url_args=dict( controller="common", action="manage_categories", cntrller="tool", webapp="community" ) ), + grids.GridOperation( "Upload a new version", + condition=( lambda item: not item.deleted ), + allow_multiple=False, + url_args=dict( controller="common", action="upload_new_tool_version", cntrller="tool", webapp="community" ) ), ] standard_filters = [ grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), @@ -117,17 +127,18 @@ if 'operation' in kwargs: operation = kwargs['operation'].lower() if operation == "browse": - return trans.response.send_redirect( web.url_for( controller='tool_browser', + return trans.response.send_redirect( web.url_for( controller='tool', action='browse_tool', **kwargs ) ) elif operation == "view tool": - return trans.response.send_redirect( web.url_for( controller='tool_browser', + return trans.response.send_redirect( web.url_for( controller='common', action='view_tool', + cntrller='tool', **kwargs ) ) elif operation == "edit tool": return trans.response.send_redirect( web.url_for( controller='common', action='edit_tool', - cntrller='tool_browser', + cntrller='tool', **kwargs ) ) # Render the list view return self.tool_list_grid( trans, **kwargs ) @@ -150,7 +161,7 @@ id = trans.app.security.decode_id( id ) tool = trans.sa_session.query( trans.model.Tool ).get( id ) if tool is None: - return trans.response.send_redirect( web.url_for( controller='tool_browser', + return trans.response.send_redirect( web.url_for( controller='tool', action='browse_tools', message='Please select a Tool to edit (the tool ID provided was invalid)', status='error' ) ) diff -r b633d836ce07 -r 3009820dfc51 lib/galaxy/webapps/community/security/__init__.py --- a/lib/galaxy/webapps/community/security/__init__.py Tue Apr 27 10:59:46 2010 -0400 +++ b/lib/galaxy/webapps/community/security/__init__.py Tue Apr 27 11:31:23 2010 -0400 @@ -76,6 +76,8 @@ elif 'role' in kwd: if 'group' in kwd: return self.associate_group_role( kwd['group'], kwd['role'] ) + elif 'tool' in kwd: + return self.associate_tool_category( kwd['tool'], kwd['category'] ) raise 'No valid method of associating provided components: %s' % kwd def associate_group_role( self, group, role ): assoc = self.model.GroupRoleAssociation( group, role ) @@ -92,6 +94,11 @@ self.sa_session.add( assoc ) self.sa_session.flush() return assoc + def associate_tool_category( self, tool, category ): + assoc = self.model.ToolCategoryAssociation( tool, category ) + self.sa_session.add( assoc ) + self.sa_session.flush() + return assoc def create_private_user_role( self, user ): # Create private role role = self.model.Role( name=user.email, description='Private Role for ' + user.email, type=self.model.Role.types.PRIVATE ) @@ -147,7 +154,15 @@ self.associate_components( user=user, role=role ) for group in groups: self.associate_components( user=user, group=group ) - + def set_entity_category_associations( self, tools=[], categories=[], delete_existing_assocs=True ): + for tool in tools: + if delete_existing_assocs: + for a in tool.categories: + self.sa_session.delete( a ) + self.sa_session.flush() + self.sa_session.refresh( tool ) + for category in categories: + self.associate_components( tool=tool, category=category ) def get_permitted_actions( filter=None ): '''Utility method to return a subset of RBACAgent's permitted actions''' if filter is None: diff -r b633d836ce07 -r 3009820dfc51 templates/webapps/community/category/add_to_category.mako --- a/templates/webapps/community/category/add_to_category.mako Tue Apr 27 10:59:46 2010 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +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">Select a category</div> - <div class="toolFormBody"> - <form id="select_category" name="select_category" action="${h.url_for( controller='common', action='add_category', cntrller=cntrller, id=id, use_panels=use_panels )}" method="post" > - <div class="form-row"> - <label>Category:</label> - ${category_select_list.get_html()} - </div> - <div class="toolParamHelp" style="clear: both;"> - Multi-select list - hold the appropriate key while clicking to select multiple columns - </div> - <div class="form-row"> - <input type="submit" name="add_category_button" value="Add tool to categories"/> - </div> - </form> - </div> -</div> diff -r b633d836ce07 -r 3009820dfc51 templates/webapps/community/tool/edit_tool.mako --- a/templates/webapps/community/tool/edit_tool.mako Tue Apr 27 10:59:46 2010 -0400 +++ b/templates/webapps/community/tool/edit_tool.mako Tue Apr 27 11:31:23 2010 -0400 @@ -11,62 +11,25 @@ <%def name="title()">Edit Tool</%def> -<h2>Edit Tool: ${tool.name} ${tool.version} (${tool.tool_id})</h2> +<h2>Edit Tool</h2> %if message: ${render_msg( message, status )} %endif -<form id="tool_edit_form" name="tool_edit_form" action="${h.url_for( controller='common', action='edit_tool' )}" enctype="multipart/form-data" method="post"> +<form id="edit_tool" name="edit_tool" action="${h.url_for( controller='common', action='edit_tool' )}" method="post"> <input type="hidden" name="id" value="${trans.app.security.encode_id( tool.id )}"/> <input type="hidden" name="cntrller" value="${cntrller}"/> <div class="toolForm"> - <div class="toolFormTitle">Edit Tool</div> + <div class="toolFormTitle">${tool.name} Version: ${tool.version}</div> <div class="toolFormBody"> <div class="form-row"> - <label>Categories:</label> - <div class="form-row-input"> - <select name="category_id" multiple size=5 style="min-width: 250px;"> - %for category in categories: - %if category.id in [ tool_category.id for tool_category in tool.categories ]: - <option value="${category.id}" selected>${category.name}</option> - %else: - <option value="${category.id}">${category.name}</option> - %endif - %endfor - </select> - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> <label>Description:</label> <div class="form-row-input"><textarea name="description" rows="5" cols="35">${tool.user_description}</textarea></div> <div style="clear: both"></div> </div> <div class="form-row"> - <input type="submit" class="primary-button" name="save_button" value="Save"> - </div> - </div> - </div> - <p/> - <div class="toolForm"> - <div class="toolFormTitle">Upload new version</div> - <div class="toolFormBody"> - <div class="form-row"> - <label>File:</label> - <div class="form-row-input"><input type="file" name="file_data"/></div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <label>URL:</label> - <div class="form-row-input"><input type="text" name="url" style="width: 100%;"/></div> - <div class="toolParamHelp" style="clear: both;"> - Instead of uploading directly from your computer, you may instruct Galaxy to download the file from a Web or FTP address. - </div> - <div style="clear: both"></div> - </div> - <div class="form-row"> - <input type="submit" class="primary-button" name="save_button" value="Save"> + <input type="submit" class="primary-button" name="edit_tool_button" value="Save"> </div> </div> </form> diff -r b633d836ce07 -r 3009820dfc51 templates/webapps/community/tool/manage_categories.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/webapps/community/tool/manage_categories.mako Tue Apr 27 11:31:23 2010 -0400 @@ -0,0 +1,63 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + +<%def name="javascripts()"> + ${parent.javascripts()} + <script type="text/javascript"> + $(function(){ + $("input:text:first").focus(); + }) + </script> +</%def> + +<%def name="render_select( name, options )"> + <select name="${name}" id="${name}" style="min-width: 250px; height: 150px;" multiple> + %for option in options: + <option value="${option[0]}">${option[1]}</option> + %endfor + </select> +</%def> + +<script type="text/javascript"> +$().ready(function() { + $('#categories_add_button').click(function() { + return !$('#out_categories option:selected').remove().appendTo('#in_categories'); + }); + $('#categories_remove_button').click(function() { + return !$('#in_categories option:selected').remove().appendTo('#out_categories'); + }); + $('form#associate_tool_category').submit(function() { + $('#in_categories option').each(function(i) { + $(this).attr("selected", "selected"); + }); + }); +}); +</script> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Tool '${tool.name}'</div> + <div class="toolFormBody"> + <form name="associate_tool_category" id="associate_tool_category" action="${h.url_for( controller='common', action='manage_categories', id=trans.security.encode_id( tool.id ) )}" method="post" > + <input name="cntrller" type="hidden" value="${cntrller}" size=40"/> + <div class="form-row"> + <div style="float: left; margin-right: 10px;"> + <label>Categories associated with '${tool.name}'</label> + ${render_select( "in_categories", in_categories )}<br/> + <input type="submit" id="categories_remove_button" value=">>"/> + </div> + <div> + <label>Categories not associated with '${tool.name}'</label> + ${render_select( "out_categories", out_categories )}<br/> + <input type="submit" id="categories_add_button" value="<<"/> + </div> + </div> + <div class="form-row"> + <input type="submit" name="manage_categories_button" value="Save"/> + </div> + </form> + </div> +</div>