details: http://www.bx.psu.edu/hg/galaxy/rev/a77ec2944999 changeset: 3684:a77ec2944999 user: Nate Coraor <nate@bx.psu.edu> date: Fri Apr 23 11:30:16 2010 -0400 description: Community upload functionality diffstat: community_datatypes_conf.xml.sample | 9 + community_wsgi.ini.sample | 2 +- lib/galaxy/model/orm/__init__.py | 1 + lib/galaxy/web/base/controller.py | 2 +- lib/galaxy/webapps/community/app.py | 4 + lib/galaxy/webapps/community/config.py | 1 + lib/galaxy/webapps/community/controllers/tool_browser.py | 48 ++- lib/galaxy/webapps/community/controllers/upload.py | 63 ++++ lib/galaxy/webapps/community/datatypes/__init__.py | 145 ++++++++++ lib/galaxy/webapps/community/model/__init__.py | 124 ++----- lib/galaxy/webapps/community/model/mapping.py | 45 +- lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py | 24 +- templates/webapps/community/tool/edit_tool.mako | 73 +++++ templates/webapps/community/upload/upload.mako | 66 ++++ 14 files changed, 480 insertions(+), 127 deletions(-) diffs (832 lines): diff -r 742fa2afcad9 -r a77ec2944999 community_datatypes_conf.xml.sample --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/community_datatypes_conf.xml.sample Fri Apr 23 11:30:16 2010 -0400 @@ -0,0 +1,9 @@ +<?xml version="1.0"?> +<datatypes> + <registration> + <datatype extension="tool" type="galaxy.webapps.community.datatypes:Tool" model="galaxy.webapps.community.model:Tool"/> + </registration> + <sniffers> + <sniffer type="galaxy.webapps.community.datatypes:Tool"/> + </sniffers> +</datatypes> diff -r 742fa2afcad9 -r a77ec2944999 community_wsgi.ini.sample --- a/community_wsgi.ini.sample Fri Apr 23 11:14:26 2010 -0400 +++ b/community_wsgi.ini.sample Fri Apr 23 11:30:16 2010 -0400 @@ -22,7 +22,7 @@ #database_connection = postgres:///community_test?host=/var/run/postgresql # Where dataset files are saved -file_path = database/files +file_path = database/community_files # Temporary storage for additional datasets, this should be shared through the cluster new_file_path = database/tmp diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/model/orm/__init__.py --- a/lib/galaxy/model/orm/__init__.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/model/orm/__init__.py Fri Apr 23 11:30:16 2010 -0400 @@ -3,5 +3,6 @@ from sqlalchemy import * from sqlalchemy.orm import * +import sqlalchemy.exc from sqlalchemy.ext.orderinglist import ordering_list diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/web/base/controller.py --- a/lib/galaxy/web/base/controller.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/web/base/controller.py Fri Apr 23 11:30:16 2010 -0400 @@ -304,7 +304,7 @@ class ControllerUnavailable( Exception ): pass -class Admin(): +class Admin( object ): # Override these user_list_grid = None role_list_grid = None diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/app.py --- a/lib/galaxy/webapps/community/app.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/webapps/community/app.py Fri Apr 23 11:30:16 2010 -0400 @@ -1,5 +1,6 @@ import sys, config import galaxy.webapps.community.model +import galaxy.webapps.community.datatypes from galaxy.web import security from galaxy.tags.tag_handler import CommunityTagHandler @@ -11,6 +12,9 @@ self.config = config.Configuration( **kwargs ) self.config.check() config.configure_logging( self.config ) + # Set up datatypes registry + self.datatypes_registry = galaxy.webapps.community.datatypes.Registry( self.config.root, self.config.datatypes_config ) + galaxy.model.set_datatypes_registry( self.datatypes_registry ) # Determine the database url if self.config.database_connection: db_url = self.config.database_connection diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/config.py --- a/lib/galaxy/webapps/community/config.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/webapps/community/config.py Fri Apr 23 11:30:16 2010 -0400 @@ -61,6 +61,7 @@ self.screencasts_url = kwargs.get( 'screencasts_url', None ) self.log_events = False self.cloud_controller_instance = False + self.datatypes_config = kwargs.get( 'datatypes_config_file', 'community_datatypes_conf.xml' ) # Parse global_conf and save the parser global_conf = kwargs.get( 'global_conf', None ) global_conf_parser = ConfigParser.ConfigParser() diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/controllers/tool_browser.py --- a/lib/galaxy/webapps/community/controllers/tool_browser.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/webapps/community/controllers/tool_browser.py Fri Apr 23 11:30:16 2010 -0400 @@ -1,5 +1,4 @@ import sys, os, operator, string, shutil, re, socket, urllib, time, logging -from cgi import escape, FieldStorage from galaxy.web.base.controller import * from galaxy.webapps.community import model @@ -27,18 +26,14 @@ title = "Tools" model_class = model.Tool template='/webapps/community/tool/grid.mako' - default_sort_key = "category" + default_sort_key = "name" columns = [ NameColumn( "Name", key="name", model_class=model.Tool, + link=( lambda item: dict( operation="Edit Tool", id=item.id, webapp="community" ) ), attach_popup=False, filterable="advanced" ), - CategoryColumn( "Category", - key="category", - 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" ) ] @@ -48,7 +43,7 @@ visible=False, filterable="standard" ) ) global_actions = [ - grids.GridAction( "Upload tool", dict( controller='tool_browwser', action='upload' ) ) + grids.GridAction( "Upload tool", dict( controller='upload', action='upload', type='tool' ) ) ] operations = [ grids.GridOperation( "View versions", condition=( lambda item: not item.deleted ), allow_multiple=False ) @@ -57,7 +52,7 @@ grids.GridColumnFilter( "Deleted", args=dict( deleted=True ) ), grids.GridColumnFilter( "All", args=dict( deleted='All' ) ) ] - default_filter = dict( name="All", category="All", deleted="False" ) + default_filter = dict( name="All", deleted="False" ) num_rows_per_page = 50 preserve_state = False use_paging = True @@ -84,6 +79,10 @@ return trans.response.send_redirect( web.url_for( controller='tool_browser', action='browse_tool', **kwargs ) ) + elif operation == "edit tool": + return trans.response.send_redirect( web.url_for( controller='tool_browser', + action='edit_tool', + **kwargs ) ) # Render the list view return self.tool_list_grid( trans, **kwargs ) @web.expose @@ -96,5 +95,32 @@ message=message, status=status ) @web.expose - def upload( self, trans, **kwargs ): - pass + def edit_tool( self, trans, id=None, **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: + return trans.response.send_redirect( web.url_for( controller='tool_browser', + action='browse_tools', + message='Please select a Tool to edit (the tool ID provided was invalid)', + 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.user_description = params.description + tool.category = params.category + 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', + encoded_id = encoded_id, + tool=tool, + categories=categories, + message=message, + status=status ) diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/controllers/upload.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/webapps/community/controllers/upload.py Fri Apr 23 11:30:16 2010 -0400 @@ -0,0 +1,63 @@ +import sys, os, shutil, logging, urllib2 + +from galaxy.web.base.controller import * +from galaxy.web.framework.helpers import time_ago, iff, grids +from galaxy.model.orm import * +from galaxy.webapps.community import datatypes + +log = logging.getLogger( __name__ ) + +# States for passing messages +SUCCESS, INFO, WARNING, ERROR = "done", "info", "warning", "error" + +class UploadController( BaseController ): + + @web.expose + def upload( self, trans, **kwd ): + params = util.Params( kwd ) + message = util.restore_text( params.get( 'message', '' ) ) + status = params.get( 'status', 'done' ) + uploaded_file = None + if params.file_data == '' and params.url.strip() == '': + message = 'No files were entered on the upload form.' + status = 'error' + elif params.file_data == '': + try: + uploaded_file = urllib2.urlopen( params.url.strip() ) + except ( ValueError, urllib2.HTTPError ), e: + message = 'An error occurred trying to retrieve the URL entered on the upload form: %s' % e + status = 'error' + except urllib2.URLError, e: + message = 'An error occurred trying to retrieve the URL entered on the upload form: %s' % e.reason + status = 'error' + elif params.file_data not in ( '', None ): + uploaded_file = params.file_data.file + if params.upload_button and uploaded_file: + datatype = trans.app.datatypes_registry.get_datatype_by_extension( params.upload_type ) + if datatype is None: + message = 'An unknown filetype was selected. This should not be possble, please report the error.' + status = 'error' + else: + try: + meta = datatype.verify( uploaded_file ) + meta.user = trans.user + obj = datatype.create_model_object( meta ) + trans.sa_session.add( obj ) + trans.sa_session.flush() + try: + os.link( uploaded_file.name, obj.file_name ) + except OSError: + shutil.copy( uploaded_file.name, obj.file_name ) + message = 'Uploaded %s' % meta.message + except datatypes.DatatypeVerificationError, 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 ... ??? ... Otherwise, please choose a new ID.' + status = 'error' + uploaded_file.close() + selected_upload_type = params.get( 'type', 'tool' ) + return trans.fill_template( '/webapps/community/upload/upload.mako', message=message, + status=status, + selected_upload_type=selected_upload_type, + upload_types=trans.app.datatypes_registry.get_datatypes_for_select_list() ) diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/datatypes/__init__.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/webapps/community/datatypes/__init__.py Fri Apr 23 11:30:16 2010 -0400 @@ -0,0 +1,145 @@ +import sys, logging, tarfile +import galaxy.util +from galaxy.util.bunch import Bunch + +log = logging.getLogger( __name__ ) + +if sys.version_info[:2] == ( 2, 4 ): + from galaxy import eggs + eggs.require( 'ElementTree' ) + from elementtree import ElementTree +else: + from xml.etree import ElementTree + +class DatatypeVerificationError( Exception ): + pass + +class Registry( object ): + def __init__( self, root_dir=None, config=None ): + self.datatypes_by_extension = {} + self.sniff_order = [] + if root_dir and config: + # Parse datatypes_conf.xml + tree = galaxy.util.parse_xml( config ) + root = tree.getroot() + # Load datatypes and converters from config + log.debug( 'Loading datatypes from %s' % config ) + registration = root.find( 'registration' ) + for elem in registration.findall( 'datatype' ): + try: + extension = elem.get( 'extension', None ) + dtype = elem.get( 'type', None ) + model_object = elem.get( 'model', None ) + if extension and dtype: + fields = dtype.split( ':' ) + datatype_module = fields[0] + datatype_class = fields[1] + fields = datatype_module.split( '.' ) + module = __import__( fields.pop(0) ) + for mod in fields: + module = getattr( module, mod ) + self.datatypes_by_extension[extension] = getattr( module, datatype_class )() + log.debug( 'Loaded datatype: %s' % dtype ) + if model_object: + model_module, model_class = model_object.split( ':' ) + fields = model_module.split( '.' ) + module = __import__( fields.pop(0) ) + for mod in fields: + module = getattr( module, mod ) + self.datatypes_by_extension[extension].model_object = getattr( module, model_class ) + log.debug( 'Added model class: %s to datatype: %s' % ( model_class, dtype ) ) + except Exception, e: + log.warning( 'Error loading datatype "%s", problem: %s' % ( extension, str( e ) ) ) + # Load datatype sniffers from the config + sniff_order = [] + sniffers = root.find( 'sniffers' ) + for elem in sniffers.findall( 'sniffer' ): + dtype = elem.get( 'type', None ) + if dtype: + sniff_order.append( dtype ) + for dtype in sniff_order: + try: + fields = dtype.split( ":" ) + datatype_module = fields[0] + datatype_class = fields[1] + fields = datatype_module.split( "." ) + module = __import__( fields.pop(0) ) + for mod in fields: + module = getattr( module, mod ) + aclass = getattr( module, datatype_class )() + included = False + for atype in self.sniff_order: + if not issubclass( atype.__class__, aclass.__class__ ) and isinstance( atype, aclass.__class__ ): + included = True + break + if not included: + self.sniff_order.append( aclass ) + log.debug( 'Loaded sniffer for datatype: %s' % dtype ) + except Exception, exc: + log.warning( 'Error appending datatype %s to sniff_order, problem: %s' % ( dtype, str( exc ) ) ) + def get_datatype_by_extension( self, ext ): + return self.datatypes_by_extension.get( ext, None ) + def get_datatypes_for_select_list( self ): + rval = [] + for ext, datatype in self.datatypes_by_extension.items(): + rval.append( ( ext, datatype.select_name ) ) + return rval + def sniff( self, fname ): + for datatype in sniff_order: + try: + datatype.sniff( fname ) + return datatype.file_ext + except: + pass + +class Tool( object ): + select_name = 'Tool' + def __init__( self, model_object=None ): + self.model_object = model_object + def verify( self, file ): + msg = '' + try: + tar = tarfile.TarFile( fileobj = file ) + except tarfile.ReadError: + raise DatatypeVerificationError( 'The tool file is not a readable tar file' ) + xml_names = filter( lambda x: x.lower().endswith( '.xml' ), tar.getnames() ) + if not xml_names: + raise DatatypeVerificationError( 'The tool file does not contain an XML file' ) + for xml_name in xml_names: + try: + tree = ElementTree.parse( tar.extractfile( xml_name ) ) + root = tree.getroot() + except: + log.exception( 'fail:' ) + continue + if root.tag == 'tool': + rval = Bunch() + try: + rval.id = root.attrib['id'] + rval.name = root.attrib['name'] + rval.version = root.attrib['version'] + except KeyError, e: + raise DatatypeVerificationError( 'Tool XML file does not conform to the specification. Missing required <tool> tag attribute: %s' % e ) + rval.description = None + desc_tag = root.find( 'description' ) + if desc_tag is not None: + rval.description = desc_tag.text.strip() + rval.message = 'Tool: %s %s, Version: %s, ID: %s' % ( rval.name, rval.description or '', rval.version, rval.id ) + return rval + else: + raise DatatypeVerificationError( 'Unable to find a properly formatted tool XML file' ) + def create_model_object( self, datatype_bunch ): + if self.model_object is None: + raise Exception( 'No model object configured for %s, please check the datatype configuration file' % self.__class__.__name__ ) + if datatype_bunch is None: + # TODO: do it automatically + raise Exception( 'Unable to create %s model object without passing in data' % self.__class__.__name__ ) + o = self.model_object() + o.create_from_datatype( datatype_bunch ) + return o + def sniff( self, fname ): + try: + self.verify( open( fname, 'r' ) ) + return True + except: + return False diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/model/__init__.py --- a/lib/galaxy/webapps/community/model/__init__.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/webapps/community/model/__init__.py Fri Apr 23 11:30:16 2010 -0400 @@ -4,7 +4,7 @@ Naming: try to use class names that have a distinct plural form so that the relationship cardinalities are obvious (e.g. prefer Dataset to Data) """ -import os.path, os, errno, sys, codecs, operator, tempfile, logging +import os.path, os, errno, sys, codecs, operator, tempfile, logging, tarfile from galaxy.util.bunch import Bunch from galaxy import util from galaxy.util.hash_util import * @@ -86,93 +86,43 @@ self.prev_session_id = prev_session_id class Tool( object ): - def __init__( self, guid=None, name=None, description=None, category=None, version=None, user_id=None, external_filename=None ): + file_path = '/tmp' + 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 self.name = name or "Unnamed tool" self.description = description + self.user_description = user_description self.category = category self.version = version or "1.0.0" self.user_id = user_id self.external_filename = external_filename + def get_file_name( self ): + if not self.external_filename: + assert self.id is not None, "ID must be set before filename used (commit the object)" + dir = os.path.join( self.file_path, 'tools', *directory_hash_id( self.id ) ) + # Create directory if it does not exist + if not os.path.exists( dir ): + os.makedirs( dir ) + # Return filename inside hashed directory + filename = os.path.join( dir, "tool_%d.dat" % self.id ) + else: + filename = self.external_filename + # Make filename absolute + return os.path.abspath( filename ) + def set_file_name( self, filename ): + if not filename: + self.external_filename = None + else: + self.external_filename = filename + file_name = property( get_file_name, set_file_name ) + def create_from_datatype( self, datatype_bunch ): + self.tool_id = datatype_bunch.id + self.name = datatype_bunch.name + self.version = datatype_bunch.version + self.description = datatype_bunch.description + self.user_id = datatype_bunch.user -class Job( object ): - """ - A job represents a request to run a tool given input datasets, tool - parameters, and output datasets. - """ - states = Bunch( NEW = 'new', - UPLOAD = 'upload', - WAITING = 'waiting', - QUEUED = 'queued', - RUNNING = 'running', - OK = 'ok', - ERROR = 'error', - DELETED = 'deleted' ) - def __init__( self ): - self.session_id = None - self.tool_id = None - self.tool_version = None - self.command_line = None - self.param_filename = None - self.parameters = [] - self.input_datasets = [] - self.output_datasets = [] - self.output_library_datasets = [] - self.state = Job.states.NEW - self.info = None - self.job_runner_name = None - self.job_runner_external_id = None - def add_parameter( self, name, value ): - self.parameters.append( JobParameter( name, value ) ) - def add_input_dataset( self, name, dataset ): - self.input_datasets.append( JobToInputDatasetAssociation( name, dataset ) ) - def add_output_dataset( self, name, dataset ): - self.output_datasets.append( JobToOutputDatasetAssociation( name, dataset ) ) - def add_output_library_dataset( self, name, dataset ): - self.output_library_datasets.append( JobToOutputLibraryDatasetAssociation( name, dataset ) ) - def set_state( self, state ): - self.state = state - # For historical reasons state propogates down to datasets - for da in self.output_datasets: - da.dataset.state = state - def get_param_values( self, app ): - """ - Read encoded parameter values from the database and turn back into a - dict of tool parameter values. - """ - param_dict = dict( [ ( p.name, p.value ) for p in self.parameters ] ) - tool = app.toolbox.tools_by_id[self.tool_id] - param_dict = tool.params_from_strings( param_dict, app ) - return param_dict - def check_if_output_datasets_deleted( self ): - """ - Return true if all of the output datasets associated with this job are - in the deleted state - """ - for dataset_assoc in self.output_datasets: - dataset = dataset_assoc.dataset - # only the originator of the job can delete a dataset to cause - # cancellation of the job, no need to loop through history_associations - if not dataset.deleted: - return False - return True - def mark_deleted( self ): - """ - Mark this job as deleted, and mark any output datasets as discarded. - """ - self.state = Job.states.DELETED - self.info = "Job output deleted by user before job completed." - for dataset_assoc in self.output_datasets: - dataset = dataset_assoc.dataset - dataset.deleted = True - dataset.state = dataset.states.DISCARDED - for dataset in dataset.dataset.history_associations: - # propagate info across shared datasets - dataset.deleted = True - dataset.blurb = 'deleted' - dataset.peek = 'Job deleted' - dataset.info = 'Job output deleted by user before job completed' - class Tag ( object ): def __init__( self, id=None, type=None, parent_id=None, name=None ): self.id = id @@ -182,6 +132,12 @@ def __str__ ( self ): return "Tag(id=%s, type=%i, parent_id=%s, name=%s)" % ( self.id, self.type, self.parent_id, self.name ) +class Category( object ): + def __init__( self, id=None, name=None, description=None ): + self.id = id + self.name = name + self.description = description + class ItemTagAssociation ( object ): def __init__( self, id=None, user=None, item_id=None, tag_id=None, user_tname=None, value=None ): self.id = id @@ -198,6 +154,12 @@ class ToolAnnotationAssociation( object ): pass +class ToolCategoryAssociation( object ): + def __init__( self, id=None, tool=None, category=None ): + self.id = id + self.tool = tool + self.category = category + ## ---- Utility methods ------------------------------------------------------- def directory_hash_id( id ): @@ -207,7 +169,7 @@ if l < 4: return [ "000" ] # Pad with zeros until a multiple of three - padded = ( ( 3 - len( s ) % 3 ) * "0" ) + s + padded = ( ( ( 3 - len( s ) ) % 3 ) * "0" ) + s # Drop the last three digits -- 1000 files per directory padded = padded[:-3] # Break into chunks of three diff -r 742fa2afcad9 -r a77ec2944999 lib/galaxy/webapps/community/model/mapping.py --- a/lib/galaxy/webapps/community/model/mapping.py Fri Apr 23 11:14:26 2010 -0400 +++ b/lib/galaxy/webapps/community/model/mapping.py Fri Apr 23 11:30:16 2010 -0400 @@ -103,32 +103,28 @@ 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( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "name", TrimmedString( 255 ), index=True, unique=True ), + Column( "name", TrimmedString( 255 ), index=True ), Column( "description" , TEXT ), - Column( "category", TrimmedString( 255 ), index=True ), + Column( "user_description" , TEXT ), Column( "version", TrimmedString( 255 ) ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "external_filename" , TEXT ), Column( "deleted", Boolean, default=False ) ) -Job.table = Table( "job", metadata, +Category.table = Table( "category", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TrimmedString( 255 ), index=True, unique=True ), + Column( "description" , TEXT ) ) + +ToolCategoryAssociation.table = Table( "tool_category_association", metadata, + Column( "id", Integer, primary_key=True ), Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ), - Column( "state", String( 64 ), index=True ), - Column( "info", TrimmedString( 255 ) ), - Column( "command_line", TEXT ), - Column( "param_filename", String( 1024 ) ), - Column( "runner_name", String( 255 ) ), - Column( "stdout", TEXT ), - Column( "stderr", TEXT ), - Column( "traceback", TEXT ), - Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ), - Column( "job_runner_name", String( 255 ) ), - Column( "job_runner_external_id", String( 255 ) ) ) + Column( "category_id", Integer, ForeignKey( "category.id" ), index=True ) ) Tag.table = Table( "tag", metadata, Column( "id", Integer, primary_key=True ), @@ -193,10 +189,6 @@ assign_mapper( context, GalaxySession, GalaxySession.table, properties=dict( user=relation( User.mapper ) ) ) -assign_mapper( context, Job, Job.table, - properties=dict( galaxy_session=relation( GalaxySession ), - tool=relation( Tool ) ) ) - assign_mapper( context, Tag, Tag.table, properties=dict( children=relation(Tag, backref=backref( 'parent', remote_side=[Tag.table.c.id] ) ) ) ) @@ -207,7 +199,22 @@ properties=dict( tool=relation( Tool ), user=relation( User ) ) ) assign_mapper( context, Tool, Tool.table, - properties = dict( user=relation( User.mapper ) ) ) + properties = dict( + categories=relation( ToolCategoryAssociation ), + user=relation( User.mapper ) + ) +) + +assign_mapper( context, Category, Category.table, + properties=dict( tools=relation( ToolCategoryAssociation ) ) ) + +assign_mapper( context, ToolCategoryAssociation, ToolCategoryAssociation.table, + properties=dict( + category=relation(Category), + tool=relation(Tool) + ) +) + def guess_dialect_for_url( url ): return (url.split(':', 1))[0] diff -r 742fa2afcad9 -r a77ec2944999 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 23 11:14:26 2010 -0400 +++ b/lib/galaxy/webapps/community/model/migrate/versions/0001_initial_tables.py Fri Apr 23 11:30:16 2010 -0400 @@ -80,32 +80,28 @@ 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( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), - Column( "name", TrimmedString( 255 ), index=True, unique=True ), + Column( "name", TrimmedString( 255 ), index=True ), Column( "description" , TEXT ), - Column( "category", TrimmedString( 255 ), index=True ), + Column( "user_description" , TEXT ), Column( "version", TrimmedString( 255 ) ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), Column( "external_filename" , TEXT ), Column( "deleted", Boolean, default=False ) ) -Job_table = Table( "job", metadata, +Category_table = Table( "category", metadata, Column( "id", Integer, primary_key=True ), Column( "create_time", DateTime, default=now ), Column( "update_time", DateTime, default=now, onupdate=now ), + Column( "name", TrimmedString( 255 ), index=True, unique=True ), + Column( "description" , TEXT ) ) + +ToolCategoryAssociation_table = Table( "tool_category_association", metadata, + Column( "id", Integer, primary_key=True ), Column( "tool_id", Integer, ForeignKey( "tool.id" ), index=True ), - Column( "state", String( 64 ), index=True ), - Column( "info", TrimmedString( 255 ) ), - Column( "command_line", TEXT ), - Column( "param_filename", String( 1024 ) ), - Column( "runner_name", String( 255 ) ), - Column( "stdout", TEXT ), - Column( "stderr", TEXT ), - Column( "traceback", TEXT ), - Column( "session_id", Integer, ForeignKey( "galaxy_session.id" ), index=True, nullable=True ), - Column( "job_runner_name", String( 255 ) ), - Column( "job_runner_external_id", String( 255 ) ) ) + Column( "category_id", Integer, ForeignKey( "category.id" ), index=True ) ) Tag_table = Table( "tag", metadata, Column( "id", Integer, primary_key=True ), diff -r 742fa2afcad9 -r a77ec2944999 templates/webapps/community/tool/edit_tool.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/webapps/community/tool/edit_tool.mako Fri Apr 23 11:30:16 2010 -0400 @@ -0,0 +1,73 @@ +<%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()">Edit Tool</%def> + +<h2>Edit Tool: ${tool.name} ${tool.version} (${tool.tool_id})</h2> + +%if message: + ${render_msg( message, status )} +%endif + +<form id="tool_edit_form" name="tool_edit_form" action="${h.url_for( controller='tool_browser', action='edit_tool' )}" enctype="multipart/form-data" method="post"> +<input type="hidden" name="id" value="${encoded_id}"/> +<div class="toolForm"> + <div class="toolFormTitle">Edit Tool</div> + <div class="toolFormBody"> + <div class="form-row"> + <label>Categories:</label> + <div class="form-row-input"> + <select name="category" multiple size=5> + %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"></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"> + </div> +</div> +</form> diff -r 742fa2afcad9 -r a77ec2944999 templates/webapps/community/upload/upload.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/webapps/community/upload/upload.mako Fri Apr 23 11:30:16 2010 -0400 @@ -0,0 +1,66 @@ +<%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()">Upload</%def> + +<h2>Upload</h2> + +%if message: + ${render_msg( message, status )} +%endif + +<div class="toolForm"> + <div class="toolFormTitle">Upload</div> + <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"> + <div class="form-row"> + <label>Upload Type</label> + <div class="form-row-input"> + <select name="upload_type"> + %for type_id, type_name in upload_types: + %if type_id == selected_upload_type: + <option value="${type_id}" selected>${type_name}</option> + %else: + <option value="${type_id}">${type_name}</option> + %endif + %endfor + </select> + </div> + <div class="toolParamHelp" style="clear: both;"> + Need help creating a tool file? See help below. + </div> + <div style="clear: both"></div> + </div> + <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="upload_button" value="Upload"> + </div> + </form> + </div> +</div> +<div class="toolHelp"> + <div class="toolHelpBody"> + <p><strong>Tool Files</strong></p> + </div> +</div>