details: http://www.bx.psu.edu/hg/galaxy/rev/37b55fad152b changeset: 3329:37b55fad152b user: rc date: Thu Feb 04 11:01:45 2010 -0500 description: Sequencer data acquisition for LIMS - Add a data transfer script which fetches datasets from the sequencer - transfer_datasets.ini for setting up the separate user to login to Galaxy programmatically to add datasets to the data libraries. - Added a dataset transfer page which includes remote file browser to select files for transfer Removed library & folder foreign keys from the request table and added them to the sample table. Also the sample table now has a dataset_files column to store the datatsets and the their transfer status. Add an egg for pexpect which is used in the remote file browser. The request_type table has a new column datatx_info which stores all the login info for the sequencer machine diffstat: eggs.ini | 2 + lib/galaxy/model/__init__.py | 76 +- lib/galaxy/model/mapping.py | 16 +- lib/galaxy/model/migrate/versions/0019_request_library_folder.py | 2 +- lib/galaxy/model/migrate/versions/0037_samples_library.py | 124 + lib/galaxy/web/controllers/requests.py | 472 +++-- lib/galaxy/web/controllers/requests_admin.py | 766 ++++++--- lib/galaxy/web/framework/__init__.py | 4 +- scripts/galaxy_messaging/server/daemon.py | 34 + scripts/galaxy_messaging/server/data_transfer.py | 211 ++ scripts/galaxy_messaging/server/galaxydb_interface.py | 11 + templates/admin/forms/edit_form.mako | 42 +- templates/admin/forms/show_form_read_only.mako | 6 +- templates/admin/requests/create_request_type.mako | 16 + templates/admin/requests/get_data.mako | 135 + templates/admin/requests/show_request.mako | 313 ++- templates/admin/requests/view_request_type.mako | 37 +- templates/requests/show_data.mako | 86 + templates/requests/show_request.mako | 299 ++- test/base/twilltestcase.py | 25 +- test/functional/test_forms_and_requests.py | 18 +- 21 files changed, 1981 insertions(+), 714 deletions(-) diffs (truncated from 3628 to 3000 lines): diff -r 44fff02fb036 -r 37b55fad152b eggs.ini --- a/eggs.ini Thu Feb 04 10:44:36 2010 -0500 +++ b/eggs.ini Thu Feb 04 11:01:45 2010 -0500 @@ -40,6 +40,7 @@ Paste = 1.6 PasteDeploy = 1.3.3 PasteScript = 1.7.3 +pexpect = 2.3 Routes = 1.11 simplejson = 1.5 SQLAlchemy = 0.5.6 @@ -72,6 +73,7 @@ DRMAA_python = http://gridengine.sunsource.net/files/documents/7/36/DRMAA-python-0.2.tar.gz MySQL_python = http://superb-west.dl.sourceforge.net/sourceforge/mysql-python/MySQL-python-... http://downloads.mysql.com/archives/mysql-5.0/mysql-5.0.67.tar.gz pbs_python = http://ftp.sara.nl/pub/outgoing/pbs_python-2.9.4.tar.gz +pexpect = http://pexpect.sourceforge.net/pexpect-2.3.tar.gz psycopg2 = http://initd.org/pub/software/psycopg/PSYCOPG-2-0/psycopg2-2.0.6.tar.gz ftp://ftp-archives.postgresql.org/pub/source/v8.2.6/postgresql-8.2.6.tar.bz2 pycrypto = http://www.amk.ca/files/python/crypto/pycrypto-2.0.1.tar.gz pysam = http://bitbucket.org/kanwei/kanwei-pysam/get/e3c601a062fd.gz diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/model/__init__.py --- a/lib/galaxy/model/__init__.py Thu Feb 04 10:44:36 2010 -0500 +++ b/lib/galaxy/model/__init__.py Thu Feb 04 11:01:45 2010 -0500 @@ -53,6 +53,25 @@ if role not in roles: roles.append( role ) return roles + def accessible_libraries(self, trans, actions): + # get all permitted libraries for this user + all_libraries = trans.sa_session.query( trans.app.model.Library ) \ + .filter( trans.app.model.Library.table.c.deleted == False ) \ + .order_by( trans.app.model.Library.name ) + roles = self.all_roles() + actions_to_check = actions + # The libraries dictionary looks like: { library : '1,2' }, library : '3' } + # Its keys are the libraries that should be displayed for the current user and whose values are a + # string of comma-separated folder ids, of the associated folders the should NOT be displayed. + # The folders that should not be displayed may not be a complete list, but it is ultimately passed + # to the calling method to keep from re-checking the same folders when the library / folder + # select lists are rendered. + libraries = {} + for library in all_libraries: + can_show, hidden_folder_ids = trans.app.security_agent.show_library_item( self, roles, library, actions_to_check ) + if can_show: + libraries[ library ] = hidden_folder_ids + return libraries class Job( object ): """ @@ -1206,21 +1225,15 @@ self.form_definition_current = form_definition_current self.type = form_type self.layout = layout - def fields_of_grid(self, layout_grid_name): - fields_dict = {} - if not layout_grid_name: - for i, f in enumerate(self.fields): - fields_dict[i] = f - else: - layout_index = -1 - for index, lg_name in enumerate(self.layout): - if lg_name == layout_grid_name: - layout_index = index - break - for i, f in enumerate(self.fields): - if f['layout'] == str(layout_index): - fields_dict[i] = f - return fields_dict + def fields_of_grid(self, grid_index): + ''' + This method returns the list of fields belonging to the given grid. + ''' + gridfields = {} + for i, f in enumerate(self.fields): + if str(f['layout']) == str(grid_index): + gridfields[i] = f + return gridfields def get_widgets( self, user, contents=[], **kwd ): ''' Return the list of widgets that comprise a form definition, @@ -1254,6 +1267,9 @@ if field[ 'type' ] == 'TextField': field_widget.set_size( 40 ) field_widget.value = value + if field[ 'type' ] == 'NumberField': + field_widget.set_size( 40 ) + field_widget.value = value elif field[ 'type' ] == 'TextArea': field_widget.set_size( 3, 40 ) field_widget.value = value @@ -1297,14 +1313,12 @@ REJECTED = 'Rejected', COMPLETE = 'Complete') def __init__(self, name=None, desc=None, request_type=None, user=None, - form_values=None, library=None, folder=None): + form_values=None): self.name = name self.desc = desc self.type = request_type self.values = form_values self.user = user - self.library = library - self.folder = folder self.samples_list = [] def state(self): if self.events: @@ -1340,23 +1354,45 @@ self.comment = comment class RequestType( object ): - def __init__(self, name=None, desc=None, request_form=None, sample_form=None): + def __init__(self, name=None, desc=None, request_form=None, sample_form=None, + datatx_info=None): self.name = name self.desc = desc self.request_form = request_form self.sample_form = sample_form + self.datatx_info = datatx_info class Sample( object ): - def __init__(self, name=None, desc=None, request=None, form_values=None, bar_code=None): + transfer_status = Bunch( NOT_STARTED = 'Not started', + IN_PROGRESS = 'In progress', + COMPLETE = 'Complete', + ERROR = 'Error') + def __init__(self, name=None, desc=None, request=None, form_values=None, + bar_code=None, library=None, folder=None, dataset_files=None): self.name = name self.desc = desc self.request = request self.values = form_values self.bar_code = bar_code + self.library = library + self.folder = folder + self.dataset_files = dataset_files def current_state(self): if self.events: return self.events[0].state return None + def untransfered_dataset_files(self): + count = 0 + for df, status in self.dataset_files: + if status == self.transfer_status.NOT_STARTED: + count = count + 1 + return count + def inprogress_dataset_files(self): + count = 0 + for df, status in self.dataset_files: + if status == self.transfer_status.IN_PROGRESS: + count = count + 1 + return count class SampleState( object ): def __init__(self, name=None, desc=None, request_type=None): diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/model/mapping.py --- a/lib/galaxy/model/mapping.py Thu Feb 04 10:44:36 2010 -0500 +++ b/lib/galaxy/model/mapping.py Thu Feb 04 11:01:45 2010 -0500 @@ -606,6 +606,7 @@ Column( "desc", TEXT ), Column( "request_form_id", Integer, ForeignKey( "form_definition.id" ), index=True ), Column( "sample_form_id", Integer, ForeignKey( "form_definition.id" ), index=True ), + Column( "datatx_info", JSONType() ), Column( "deleted", Boolean, index=True, default=False ) ) FormValues.table = Table('form_values', metadata, @@ -624,8 +625,6 @@ Column( "form_values_id", Integer, ForeignKey( "form_values.id" ), index=True ), Column( "request_type_id", Integer, ForeignKey( "request_type.id" ), index=True ), Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ), - Column( "library_id", Integer, ForeignKey( "library.id" ), index=True ), - Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), Column( "deleted", Boolean, index=True, default=False ) ) RequestEvent.table = Table('request_event', metadata, @@ -645,6 +644,9 @@ Column( "form_values_id", Integer, ForeignKey( "form_values.id" ), index=True ), Column( "request_id", Integer, ForeignKey( "request.id" ), index=True ), Column( "bar_code", TrimmedString( 255 ), index=True ), + Column( "library_id", Integer, ForeignKey( "library.id" ), index=True ), + Column( "folder_id", Integer, ForeignKey( "library_folder.id" ), index=True ), + Column( "dataset_files", JSONType() ), Column( "deleted", Boolean, index=True, default=False ) ) SampleState.table = Table('sample_state', metadata, @@ -838,6 +840,10 @@ primaryjoin=( Sample.table.c.form_values_id == FormValues.table.c.id ) ), request=relation( Request, primaryjoin=( Sample.table.c.request_id == Request.table.c.id ) ), + folder=relation( LibraryFolder, + primaryjoin=( Sample.table.c.folder_id == LibraryFolder.table.c.id ) ), + library=relation( Library, + primaryjoin=( Sample.table.c.library_id == Library.table.c.id ) ), ) ) assign_mapper( context, FormValues, FormValues.table, @@ -856,11 +862,7 @@ backref="requests" ), samples=relation( Sample, primaryjoin=( Request.table.c.id == Sample.table.c.request_id ), - order_by=asc(Sample.table.c.update_time) ), - folder=relation( LibraryFolder, - primaryjoin=( Request.table.c.folder_id == LibraryFolder.table.c.id ) ), - library=relation( Library, - primaryjoin=( Request.table.c.library_id == Library.table.c.id ) ), + order_by=asc(Sample.table.c.id) ), events=relation( RequestEvent, backref="request", order_by=desc(RequestEvent.table.c.update_time) ) ) ) diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/model/migrate/versions/0019_request_library_folder.py --- a/lib/galaxy/model/migrate/versions/0019_request_library_folder.py Thu Feb 04 10:44:36 2010 -0500 +++ b/lib/galaxy/model/migrate/versions/0019_request_library_folder.py Thu Feb 04 11:01:45 2010 -0500 @@ -47,7 +47,7 @@ try: LibraryFolder_table = Table( "library_folder", metadata, autoload=True ) except NoSuchTableError: - Request_table = None + LibraryFolder_table = None log.debug( "Failed loading table library_folder" ) # Add 1 foreign key constraint to the library_folder table if Request_table and LibraryFolder_table: diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/model/migrate/versions/0037_samples_library.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/galaxy/model/migrate/versions/0037_samples_library.py Thu Feb 04 11:01:45 2010 -0500 @@ -0,0 +1,124 @@ +""" +This migration script removes the library_id & folder_id fields in the 'request' table and +adds the same to the 'sample' table. This also adds a 'datatx' column to request_type table +to store the sequencer login information. Finally, this adds a 'dataset_files' column to +the sample table. +""" +from sqlalchemy import * +from sqlalchemy.orm import * +from migrate import * +from migrate.changeset import * +import sys, logging +from galaxy.model.custom_types import * +from sqlalchemy.exc import * + +log = logging.getLogger( __name__ ) +log.setLevel(logging.DEBUG) +handler = logging.StreamHandler( sys.stdout ) +format = "%(name)s %(levelname)s %(asctime)s %(message)s" +formatter = logging.Formatter( format ) +handler.setFormatter( formatter ) +log.addHandler( handler ) + +metadata = MetaData( migrate_engine ) + + +def upgrade(): + print __doc__ + # Load existing tables + metadata.reflect() + # retuest_type table + try: + RequestType_table = Table( "request_type", metadata, autoload=True ) + except NoSuchTableError: + RequestType_table = None + log.debug( "Failed loading table request_type" ) + if RequestType_table: + # Add the datatx_info column in 'request_type' table + try: + col = Column( "datatx_info", JSONType() ) + col.create( RequestType_table ) + assert col is RequestType_table.c.datatx_info + except Exception, e: + log.debug( "Adding column 'datatx_info' to request_type table failed: %s" % ( str( e ) ) ) + # request table + try: + Request_table = Table( "request", metadata, autoload=True ) + except NoSuchTableError: + Request_table = None + log.debug( "Failed loading table request" ) + if Request_table: + # Delete the library_id column in 'request' table + try: + Request_table.c.library_id.drop() + except Exception, e: + log.debug( "Deleting column 'library_id' to request table failed: %s" % ( str( e ) ) ) + # Delete the folder_id column in 'request' table + try: + Request_table.c.folder_id.drop() + except Exception, e: + log.debug( "Deleting column 'folder_id' to request table failed: %s" % ( str( e ) ) ) + # sample table + try: + Sample_table = Table( "sample", metadata, autoload=True ) + except NoSuchTableError: + Sample_table = None + log.debug( "Failed loading table sample" ) + if Sample_table: + # Add the dataset_files column in 'sample' table + try: + col = Column( "dataset_files", JSONType() ) + col.create( Sample_table ) + assert col is Sample_table.c.dataset_files + except Exception, e: + log.debug( "Adding column 'dataset_files' to sample table failed: %s" % ( str( e ) ) ) + # library table + try: + Library_table = Table( "library", metadata, autoload=True ) + except NoSuchTableError: + Library_table = None + log.debug( "Failed loading table library" ) + if Library_table: + # Add the library_id column in 'sample' table + try: + col = Column( "library_id", Integer, index=True ) + col.create( Sample_table ) + assert col is Sample_table.c.library_id + except Exception, e: + log.debug( "Adding column 'library_id' to sample table failed: %s" % ( str( e ) ) ) + # add the foreign key constraint + try: + cons = ForeignKeyConstraint( [Sample_table.c.library_id], + [Library_table.c.id], + name='sample_library_id_fk' ) + # Create the constraint + cons.create() + except Exception, e: + log.debug( "Adding foreign key constraint 'sample_library_id_fk' to table 'library' failed: %s" % ( str( e ) ) ) + # library_folder table + try: + LibraryFolder_table = Table( "library_folder", metadata, autoload=True ) + except NoSuchTableError: + LibraryFolder_table = None + log.debug( "Failed loading table library_folder" ) + if LibraryFolder_table: + # Add the library_id column in 'sample' table + try: + col = Column( "folder_id", Integer, index=True ) + col.create( Sample_table ) + assert col is Sample_table.c.folder_id + except Exception, e: + log.debug( "Adding column 'folder_id' to sample table failed: %s" % ( str( e ) ) ) + # add the foreign key constraint + try: + cons = ForeignKeyConstraint( [Sample_table.c.folder_id], + [LibraryFolder_table.c.id], + name='sample_folder_id_fk' ) + # Create the constraint + cons.create() + except Exception, e: + log.debug( "Adding foreign key constraint 'sample_folder_id_fk' to table 'library_folder' failed: %s" % ( str( e ) ) ) + + +def downgrade(): + pass diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/web/controllers/requests.py --- a/lib/galaxy/web/controllers/requests.py Thu Feb 04 10:44:36 2010 -0500 +++ b/lib/galaxy/web/controllers/requests.py Thu Feb 04 11:01:45 2010 -0500 @@ -178,30 +178,6 @@ % rejected # Render the list view return self.request_grid( trans, **kwd ) - def __show_request(self, trans, **kwd): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - add_sample = params.get('add_sample', False) - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) - except: - return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - status='error', - message="Invalid request ID" ) ) - current_samples = [] - for s in request.samples: - current_samples.append([s.name, s.values.content]) - if add_sample: - current_samples.append(['Sample_%i' % (len(current_samples)+1),['' for field in request.type.sample_form.fields]]) - return trans.fill_template( '/requests/show_request.mako', - request=request, - request_details=self.request_details(trans, request.id), - current_samples = current_samples, - sample_copy=self.__copy_sample(current_samples), - details='hide', edit_mode='False', - msg=msg, messagetype=messagetype ) def __request_events(self, trans, **kwd): try: request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) @@ -239,22 +215,6 @@ request_details.append(dict(label='Date created', value=request.create_time, helptext='')) - # library associated - if request.library: - value = request.library.name - else: - value = None - request_details.append( dict( label='Data library', - value=value, - helptext='Data library where the resultant dataset will be stored' ) ) - # folder associated - if request.folder: - value = request.folder.name - else: - value = None - request_details.append( dict( label='Data library folder', - value=value, - helptext='Data library folder where the resultant dataset will be stored' ) ) # form fields for index, field in enumerate(request.type.request_form.fields): if field['required']: @@ -276,36 +236,190 @@ value=request.values.content[index], helptext=field['helptext']+' ('+req+')')) return request_details - def __update_samples(self, request, **kwd): + def __show_request(self, trans, **kwd): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + add_sample = params.get('add_sample', False) + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests', + action='list', + status='error', + message="Invalid request ID") ) + # get all data libraries accessible to this user + libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) + current_samples = [] + for i, s in enumerate(request.samples): + lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd) + current_samples.append(dict(name=s.name, + barcode=s.bar_code, + library=s.library, + folder=s.folder, + dataset_files=s.dataset_files, + field_values=s.values.content, + lib_widget=lib_widget, + folder_widget=folder_widget)) + if add_sample: + lib_widget, folder_widget = self.__library_widgets(trans, request.user, + len(current_samples)+1, + libraries, None, **kwd) + current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1), + barcode='', + library=None, + folder=None, + dataset_files=[], + field_values=['' for field in request.type.sample_form.fields], + lib_widget=lib_widget, + folder_widget=folder_widget)) + return trans.fill_template( '/requests/show_request.mako', + request=request, + request_details=self.request_details(trans, request.id), + current_samples=current_samples, + sample_copy=self.__copy_sample(current_samples), + details='hide', edit_mode=util.restore_text( params.get( 'edit_mode', 'False' ) ), + msg=msg, messagetype=messagetype ) + def __library_widgets(self, trans, user, sample_index, libraries, sample=None, **kwd): + ''' + This method creates the data library & folder selectbox for creating & + editing samples. First we get a list of all the libraries accessible to + the current user and display it in a selectbox. If the user has selected an + existing library then display all the accessible sub folders of the selected + data library. + ''' + params = util.Params( kwd ) + # data library selectbox + lib_id = params.get( "sample_%i_library_id" % sample_index, 'none' ) + selected_lib = None + if sample and lib_id == 'none': + if sample.library: + lib_id = str(sample.library.id) + selected_lib = sample.library + # create data library selectbox with refresh on change enabled + lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()] + lib_widget = SelectField( "sample_%i_library_id" % sample_index, + refresh_on_change=True, + refresh_on_change_values=lib_id_list ) + # fill up the options in the Library selectbox + # first option 'none' is the value for "Select one" option + if lib_id == 'none': + lib_widget.add_option('Select one', 'none', selected=True) + else: + lib_widget.add_option('Select one', 'none') + # all the libraries available to the selected user + for lib, hidden_folder_ids in libraries.items(): + if str(lib.id) == lib_id: + lib_widget.add_option(lib.name, lib.id, selected=True) + selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',') + else: + lib_widget.add_option(lib.name, lib.id) + lib_widget.refresh_on_change_values.append(lib.id) + # create the folder selectbox + folder_widget = SelectField( "sample_%i_folder_id" % sample_index ) + # when editing a request, either the user has already selected a subfolder or not + if sample: + if sample.folder: + current_fid = sample.folder.id + else: + # when a folder not yet associated with the request then the + # the current folder is set to the root_folder of the + # parent data library if present. + if sample.library: + current_fid = sample.library.root_folder.id + else: + current_fid = params.get( "sample_%i_folder_id" % sample_index, 'none' ) + else: + current_fid = 'none' + # first option + if lib_id == 'none': + folder_widget.add_option('Select one', 'none', selected=True) + else: + folder_widget.add_option('Select one', 'none') + if selected_lib: + # get all show-able folders for the selected library + showable_folders = trans.app.security_agent.get_showable_folders( user, user.all_roles(), + selected_lib, + [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ], + selected_hidden_folder_ids ) + for f in showable_folders: + if str(f.id) == str(current_fid): + folder_widget.add_option(f.name, f.id, selected=True) + else: + folder_widget.add_option(f.name, f.id) + return lib_widget, folder_widget + def __update_samples(self, trans, request, **kwd): ''' This method retrieves all the user entered sample information and returns an list of all the samples and their field values ''' params = util.Params( kwd ) + details = params.get( 'details', 'hide' ) + edit_mode = params.get( 'edit_mode', 'False' ) + # get all data libraries accessible to this user + libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) + current_samples = [] - for s in request.samples: - current_samples.append([s.name, s.values.content]) - index = len(request.samples) + for i, s in enumerate(request.samples): + lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd) + current_samples.append(dict(name=s.name, + barcode=s.bar_code, + library=s.library, + folder=s.folder, + field_values=s.values.content, + lib_widget=lib_widget, + folder_widget=folder_widget)) + if edit_mode == 'False': + sample_index = len(request.samples) + else: + sample_index = 0 while True: - if params.get( 'sample_%i_name' % index, '' ): - sample_index = index - sample_name = util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ) - sample_values = [] + if params.get( 'sample_%i_name' % sample_index, '' ): + # data library + try: + library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_%i_library_id' % sample_index, None ) ) ) + except: + library = None + # folder + try: + folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_%i_folder_id' % sample_index, None ) ) ) + except: + if library: + folder = library.root_folder + else: + folder = None + sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ), + barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, '' ) ), + library=library, + folder=folder) + sample_info['field_values'] = [] for field_index in range(len(request.type.sample_form.fields)): - sample_values.append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - current_samples.append([sample_name, sample_values]) - index = index + 1 + sample_info['field_values'].append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) + if edit_mode == 'False': + sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, + request.user, + sample_index, + libraries, + None, **kwd) + current_samples.append(sample_info) + else: + sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, + request.user, + sample_index, + libraries, + request.samples[sample_index], + **kwd) + current_samples[sample_index] = sample_info + sample_index = sample_index + 1 else: break - details = params.get( 'details', 'hide' ) - edit_mode = params.get( 'edit_mode', 'False' ) - return current_samples, details, edit_mode + return current_samples, details, edit_mode, libraries def __copy_sample(self, current_samples): copy_list = SelectField('copy_sample') copy_list.add_option('None', -1, selected=True) for i, s in enumerate(current_samples): - copy_list.add_option(s[0], i) - return copy_list + copy_list.add_option(s['name'], i) + return copy_list @web.expose @web.require_login( "create/submit sequencing requests" ) def show_request(self, trans, **kwd): @@ -321,7 +435,7 @@ message="Invalid request ID", **kwd) ) # get the user entered sample details - current_samples, details, edit_mode = self.__update_samples( request, **kwd ) + current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd ) if params.get('import_samples_button', False) == 'Import samples': try: file_obj = params.get('file_data', '') @@ -349,10 +463,27 @@ src_sample_index = int(params.get( 'copy_sample', -1 )) if src_sample_index == -1: # empty sample - current_samples.append(['Sample_%i' % (len(current_samples)+1),['' for field in request.type.sample_form.fields]]) + lib_widget, folder_widget = self.__library_widgets(trans, request.user, + len(current_samples), + libraries, None, **kwd) + current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1), + barcode='', + library=None, + folder=None, + field_values=['' for field in request.type.sample_form.fields], + lib_widget=lib_widget, + folder_widget=folder_widget)) else: - current_samples.append([current_samples[src_sample_index][0]+'_%i' % (len(current_samples)+1), - [val for val in current_samples[src_sample_index][1]]]) + lib_widget, folder_widget = self.__library_widgets(trans, request.user, + len(current_samples), + libraries, None, **kwd) + current_samples.append(dict(name=current_samples[src_sample_index]['name']+'_%i' % (len(current_samples)+1), + barcode='', + library_id='none', + folder_id='none', + field_values=[val for val in current_samples[src_sample_index]['field_values']], + lib_widget=lib_widget, + folder_widget=folder_widget)) return trans.fill_template( '/requests/show_request.mako', request=request, request_details=self.request_details(trans, request.id), @@ -365,13 +496,13 @@ msg = '' for index in range(len(current_samples)-len(request.samples)): sample_index = index + len(request.samples) - sample_name = current_samples[sample_index][0] + sample_name = current_samples[sample_index]['name'] if not sample_name.strip(): msg = 'Please enter the name of sample number %i' % sample_index break count = 0 for i in range(len(current_samples)): - if sample_name == current_samples[i][0]: + if sample_name == current_samples[i]['name']: count = count + 1 if count > 1: msg = "This request has <b>%i</b> samples with the name <b>%s</b>.\nSamples belonging to a request must have unique names." % (count, sample_name) @@ -388,34 +519,45 @@ if edit_mode == 'False': for index in range(len(current_samples)-len(request.samples)): sample_index = len(request.samples) - sample_name = util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ) - sample_values = [] - for field_index in range(len(request.type.sample_form.fields)): - sample_values.append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - form_values = trans.app.model.FormValues(request.type.sample_form, sample_values) + form_values = trans.app.model.FormValues(request.type.sample_form, + current_samples[sample_index]['field_values']) trans.sa_session.add( form_values ) trans.sa_session.flush() - s = trans.app.model.Sample(sample_name, '', request, form_values) + s = trans.app.model.Sample(current_samples[sample_index]['name'], '', + request, form_values, + current_samples[sample_index]['barcode'], + current_samples[sample_index]['library'], + current_samples[sample_index]['folder'], + dataset_files=[]) trans.sa_session.add( s ) trans.sa_session.flush() else: + messagetype = 'done' + msg = 'Changes made to the sample(s) are saved. ' for sample_index in range(len(current_samples)): - sample_name = current_samples[sample_index][0] - new_sample_name = util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ) - sample_values = [] - for field_index in range(len(request.type.sample_form.fields)): - sample_values.append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - sample = request.has_sample(sample_name) - if sample: - form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id ) - form_values.content = sample_values - sample.name = new_sample_name - trans.sa_session.add( sample ) - trans.sa_session.flush() + sample = request.samples[sample_index] + sample.name = current_samples[sample_index]['name'] + sample.library = current_samples[sample_index]['library'] + sample.folder = current_samples[sample_index]['folder'] + if request.submitted(): + bc_msg = self.__validate_barcode(trans, sample, current_samples[sample_index]['barcode']) + if bc_msg: + messagetype = 'error' + msg += bc_msg + else: + sample.bar_code = current_samples[sample_index]['barcode'] + trans.sa_session.add( sample ) + trans.sa_session.flush() + form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id ) + form_values.content = current_samples[sample_index]['field_values'] + trans.sa_session.add( form_values ) + trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='requests', - action='list', - operation='show_request', - id=trans.security.encode_id(request.id)) ) + action='list', + operation='show_request', + id=trans.security.encode_id(request.id), + messagetype=messagetype, + msg=msg )) elif params.get('edit_samples_button', False) == 'Edit samples': edit_mode = 'True' return trans.fill_template( '/requests/show_request.mako', @@ -423,13 +565,21 @@ request_details=self.request_details(trans, request.id), current_samples=current_samples, sample_copy=self.__copy_sample(current_samples), - details=details, + details=details, libraries=libraries, edit_mode=edit_mode) elif params.get('cancel_changes_button', False) == 'Cancel': return trans.response.send_redirect( web.url_for( controller='requests', action='list', operation='show_request', id=trans.security.encode_id(request.id)) ) + else: + return trans.fill_template( '/requests/show_request.mako', + request=request, + request_details=self.request_details(trans, request.id), + current_samples=current_samples, + sample_copy=self.__copy_sample(current_samples), + details=details, libraries=libraries, + edit_mode=edit_mode, messagetype=messagetype, msg=msg) @web.expose @@ -441,7 +591,7 @@ request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) ) current_samples, details, edit_mode = self.__update_samples( request, **kwd ) sample_index = int(params.get('sample_id', 0)) - sample_name = current_samples[sample_index][0] + sample_name = current_samples[sample_index]['name'] s = request.has_sample(sample_name) if s: trans.sa_session.delete( s ) @@ -454,23 +604,6 @@ sample_copy=self.__copy_sample(current_samples), details=details, edit_mode=edit_mode) - - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def toggle_request_details(self, trans, **kwd): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - # TODO: Fix the following - can we get a Request.id == 0??? - request = trans.sa_session.query( trans.app.model.Request ).get(int(params.get('request_id', 0))) - current_samples, details, edit_mode = self.__update_samples( request, **kwd ) - return trans.fill_template( '/requests/show_request.mako', - request=request, - request_details=self.request_details(trans, request.id), - current_samples = current_samples, - sample_copy=self.__copy_sample(current_samples), - details=details, - edit_mode=edit_mode) def __select_request_type(self, trans, rtid): requesttype_list = trans.sa_session.query( trans.app.model.RequestType )\ .order_by( trans.app.model.RequestType.name.asc() ) @@ -563,10 +696,6 @@ widget=TextField('desc', 40, util.restore_text( params.get( 'desc', '' ) )), helptext='(Optional)')) - - # libraries selectbox - libui = self.__library_ui(trans, request=None, **kwd) - widgets = widgets + libui widgets = widgets + request_type.request_form.get_widgets( trans.user, **kwd ) return trans.fill_template( '/requests/new_request.mako', select_request_type=select_request_type, @@ -574,110 +703,6 @@ widgets=widgets, msg=msg, messagetype=messagetype) - def __library_ui(self, trans, request=None, **kwd): - ''' - This method creates the data library & folder selectbox for new & - editing requests. First we get a list of all the libraries accessible to - the current user and display it in a selectbox. If the user has select an - existing library then display all the accessible sub folders of the selected - data library. - ''' - params = util.Params( kwd ) - lib_id = params.get( 'library_id', 'none' ) - selected_lib = None - # if editing a request and the user has already associated a library to - # this request, then set the selected_lib to the request.library - if request and lib_id == 'none': - if request.library: - lib_id = str(request.library.id) - selected_lib = request.library - # get all permitted libraries for this user - all_libraries = trans.sa_session.query( trans.app.model.Library ) \ - .filter( trans.app.model.Library.table.c.deleted == False ) \ - .order_by( trans.app.model.Library.name ) - current_user_roles = trans.get_current_user_roles() - actions_to_check = [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] - libraries = odict() - for library in all_libraries: - can_show, hidden_folder_ids = trans.app.security_agent.show_library_item( trans.user, current_user_roles, library, actions_to_check ) - if can_show: - libraries[ library ] = hidden_folder_ids - # create data library selectbox with refresh on change enabled - lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()] - lib_list = SelectField( 'library_id', refresh_on_change=True, refresh_on_change_values=lib_id_list ) - # fill up the options in the Library selectbox - # first option 'none' is the value for "Select one" option - if lib_id == 'none': - lib_list.add_option('Select one', 'none', selected=True) - else: - lib_list.add_option('Select one', 'none') - # add all the libraries available to the user to the library selectbox - for lib, hidden_folder_ids in libraries.items(): - if str(lib.id) == lib_id: - lib_list.add_option(lib.name, lib.id, selected=True) - selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',') - else: - lib_list.add_option(lib.name, lib.id) - lib_list.refresh_on_change_values.append(lib.id) - # new library option - if lib_id == 'new': - lib_list.add_option('Create a new data library', 'new', selected=True) - else: - lib_list.add_option('Create a new data library', 'new') - # data library widget - lib_widget = dict(label='Data library', - widget=lib_list, - helptext='Data library where the resultant dataset will be stored.') - # show the folder widget only if the user has selected a valid library above - if selected_lib: - # when editing a request, either the user has already selected a subfolder or not - if request: - if request.folder: - current_fid = request.folder.id - else: - # when a folder not yet associated with the request then the - # the current folder is set to the root_folder of the - # parent data library if present. - if request.library: - current_fid = request.library.root_folder.id - else: - current_fid = params.get( 'folder_id', 'none' ) - else: - current_fid = params.get( 'folder_id', 'none' ) - # create the folder selectbox - folder_list = SelectField( 'folder_id') - # first option - if lib_id == 'none': - folder_list.add_option('Select one', 'none', selected=True) - else: - folder_list.add_option('Select one', 'none') - # get all show-able folders for the selected library - showable_folders = trans.app.security_agent.get_showable_folders( trans.user, - current_user_roles, - selected_lib, - actions_to_check, - selected_hidden_folder_ids ) - # add all the folders to the folder selectbox - for f in showable_folders: - if str(f.id) == str(current_fid): - folder_list.add_option(f.name, f.id, selected=True) - else: - folder_list.add_option(f.name, f.id) - # folder widget - folder_widget = dict(label='Folder', - widget=folder_list, - helptext='Folder of the selected data library where the resultant dataset will be stored.') - if lib_id == 'new': - new_lib = dict(label='Create a new data library', - widget=TextField('new_library_name', 40, - util.restore_text( params.get( 'new_library_name', '' ) )), - helptext='Enter a name here to request a new data library') - return [lib_widget, new_lib] - else: - if selected_lib: - return [lib_widget, folder_widget] - else: - return [lib_widget] def __validate(self, trans, request): ''' Validates the request entered by the user @@ -747,8 +772,7 @@ trans.sa_session.flush() if not request: request = trans.app.model.Request(name, desc, request_type, - trans.user, form_values, - library=library, folder=folder) + trans.user, form_values) trans.sa_session.add( request ) trans.sa_session.flush() trans.sa_session.refresh( request ) @@ -763,8 +787,6 @@ request.type = request_type request.user = trans.user request.values = form_values - request.library = library - request.folder = folder trans.sa_session.add( request ) trans.sa_session.flush() return request @@ -844,9 +866,6 @@ widgets.append(dict(label='Description', widget=TextField('desc', 40, desc), helptext='(Optional)')) - # libraries selectbox - libui = self.__library_ui(trans, request, **kwd) - widgets = widgets + libui widgets = widgets + request.type.request_form.get_widgets( trans.user, request.values.content, **kwd ) return trans.fill_template( '/requests/edit_request.mako', select_request_type=select_request_type, @@ -966,3 +985,22 @@ events_list=events_list, sample_name=sample.name, request=sample.request) + # + # Data transfer from sequencer + # + @web.expose + @web.require_login( "create/submit sequencing requests" ) + def show_datatx_page( self, trans, **kwd ): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + try: + sample = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id( kwd['sample_id'] ) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests', + action='list', + status='error', + message="Invalid sample ID", + **kwd) ) + return trans.fill_template( '/requests/show_data.mako', + sample=sample, dataset_files=sample.dataset_files ) diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/web/controllers/requests_admin.py --- a/lib/galaxy/web/controllers/requests_admin.py Thu Feb 04 10:44:36 2010 -0500 +++ b/lib/galaxy/web/controllers/requests_admin.py Thu Feb 04 11:01:45 2010 -0500 @@ -10,6 +10,8 @@ from galaxy.web.controllers.forms import get_all_forms from sqlalchemy.sql.expression import func, and_ from sqlalchemy.sql import select +import pexpect +import ConfigParser log = logging.getLogger( __name__ ) @@ -110,9 +112,9 @@ visible=False, filterable="advanced" ), StateColumn( "State", + key='state', model_class=model.Request, event_class=model.RequestEvent, - key='state', filterable="advanced", link=( lambda item: iff( item.deleted, None, dict( operation="events", id=item.id ) ) ), ), @@ -201,6 +203,8 @@ grids.GridAction( "Create new request type", dict( controller='requests_admin', action='create_request_type' ) ) ] + + # # ---- Request Controller ------------------------------------------------------ # @@ -208,6 +212,7 @@ class RequestsAdmin( BaseController ): request_grid = RequestsGrid() requesttype_grid = RequestTypeGrid() + @web.expose @web.require_admin @@ -243,33 +248,10 @@ return self.__request_events( trans, **kwd ) elif operation == "view_type": return self.__view_request_type( trans, **kwd ) - + elif operation == "upload_datasets": + return self.__upload_datasets( trans, **kwd ) # Render the grid view return self.request_grid( trans, **kwd ) - def __show_request(self, trans, **kwd): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - add_sample = params.get('add_sample', False) - try: - request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) - except: - return trans.response.send_redirect( web.url_for( controller='requests_admin', - action='list', - status='error', - message="Invalid request ID") ) - current_samples = [] - for s in request.samples: - current_samples.append([s.name, s.values.content]) - if add_sample: - current_samples.append(['Sample_%i' % (len(current_samples)+1),['' for field in request.type.sample_form.fields]]) - return trans.fill_template( '/admin/requests/show_request.mako', - request=request, - request_details=self.request_details(trans, request.id), - current_samples = current_samples, - sample_copy=self.__copy_sample(current_samples), - details='hide', edit_mode='False', - msg=msg, messagetype=messagetype ) @web.expose @web.require_admin def edit(self, trans, **kwd): @@ -346,9 +328,6 @@ widgets.append(dict(label='Description', widget=TextField('desc', 40, desc), helptext='(Optional)')) - # libraries selectbox - libui = self.__library_ui(trans, request.user, request, **kwd) - widgets = widgets + libui widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd ) return trans.fill_template( '/admin/requests/edit_request.mako', select_request_type=select_request_type, @@ -504,6 +483,9 @@ events_list.append((event.state, time_ago(event.update_time), event.comment)) return trans.fill_template( '/admin/requests/events.mako', events_list=events_list, request=request) + + def __upload_datasets(self, trans, **kwd): + return trans.fill_template( '/admin/requests/upload_datasets.mako' ) # #---- Request Creation ---------------------------------------------------------- # @@ -609,9 +591,6 @@ widget=TextField('desc', 40, util.restore_text( params.get( 'desc', '' ) )), helptext='(Optional)')) - # libraries selectbox - libui = self.__library_ui(trans, user, **kwd) - widgets = widgets + libui widgets = widgets + request_type.request_form.get_widgets( user, **kwd ) return trans.fill_template( '/admin/requests/new_request.mako', select_request_type=select_request_type, @@ -640,119 +619,6 @@ else: select_user.add_option(user.email, user.id) return select_user - def __library_ui(self, trans, user, request=None, **kwd): - ''' - This method creates the data library & folder selectbox for new & - editing requests. First we get a list of all the libraries accessible to - the current user and display it in a selectbox. If the user has select an - existing library then display all the accessible sub folders of the selected - data library. - ''' - params = util.Params( kwd ) - lib_id = params.get( 'library_id', 'none' ) - # if editing a request and the user has already associated a library to - # this request, then set the selected_lib to the request.library - selected_lib = None - if request and lib_id == 'none': - if request.library: - lib_id = str(request.library.id) - selected_lib = request.library - # if new request no user is selected initially, none of the libraries are - # listed in the selectfield - if not user: - libraries = {} - else: - # get all permitted libraries for this user - all_libraries = trans.sa_session.query( trans.app.model.Library ) \ - .filter( trans.app.model.Library.table.c.deleted == False ) \ - .order_by( trans.app.model.Library.name ) - roles = user.all_roles() - actions_to_check = [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] - # The libraries dictionary looks like: { library : '1,2' }, library : '3' } - # Its keys are the libraries that should be displayed for the current user and whose values are a - # string of comma-separated folder ids, of the associated folders the should NOT be displayed. - # The folders that should not be displayed may not be a complete list, but it is ultimately passed - # to the calling method to keep from re-checking the same folders when the library / folder - # select lists are rendered. - libraries = {} - for library in all_libraries: - can_show, hidden_folder_ids = trans.app.security_agent.show_library_item( user, roles, library, actions_to_check ) - if can_show: - libraries[ library ] = hidden_folder_ids - # create data library selectbox with refresh on change enabled - lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()] - lib_list = SelectField( 'library_id', refresh_on_change=True, refresh_on_change_values=lib_id_list ) - # fill up the options in the Library selectbox - # first option 'none' is the value for "Select one" option - if lib_id == 'none': - lib_list.add_option('Select one', 'none', selected=True) - else: - lib_list.add_option('Select one', 'none') - # all the libraries available to the selected user - for lib, hidden_folder_ids in libraries.items(): - if str(lib.id) == lib_id: - lib_list.add_option(lib.name, lib.id, selected=True) - selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',') - else: - lib_list.add_option(lib.name, lib.id) - lib_list.refresh_on_change_values.append(lib.id) - # new data library option - if lib_id == 'new': - lib_list.add_option('Create a new data library', 'new', selected=True) - else: - lib_list.add_option('Create a new data library', 'new') - # widget - lib_widget = dict(label='Data library', - widget=lib_list, - helptext='Data library where the resultant dataset will be stored.') - # show the folder widget only if the user has selected a valid library above - if selected_lib: - # when editing a request, either the user has already selected a subfolder or not - if request: - if request.folder: - current_fid = request.folder.id - else: - # when a folder not yet associated with the request then the - # the current folder is set to the root_folder of the - # parent data library if present. - if request.library: - current_fid = request.library.root_folder.id - else: - current_fid = params.get( 'folder_id', 'none' ) - else: - current_fid = params.get( 'folder_id', 'none' ) - # create the folder selectbox - folder_list = SelectField( 'folder_id') - # first option - if lib_id == 'none': - folder_list.add_option('Select one', 'none', selected=True) - else: - folder_list.add_option('Select one', 'none') - # get all show-able folders for the selected library - showable_folders = trans.app.security_agent.get_showable_folders( user, roles, - selected_lib, - actions_to_check, - selected_hidden_folder_ids ) - for f in showable_folders: - if str(f.id) == str(current_fid): - folder_list.add_option(f.name, f.id, selected=True) - else: - folder_list.add_option(f.name, f.id) - # folder widget - folder_widget = dict(label='Folder', - widget=folder_list, - helptext='Folder of the selected data library where the resultant dataset will be stored.') - if lib_id == 'new': - new_lib = dict(label='Create a new data library', - widget=TextField('new_library_name', 40, - util.restore_text( params.get( 'new_library_name', '' ) )), - helptext='Enter a name here to request a new data library') - return [lib_widget, new_lib] - else: - if selected_lib: - return [lib_widget, folder_widget] - else: - return [lib_widget] def __validate(self, trans, request): ''' Validates the request entered by the user @@ -787,18 +653,6 @@ user = trans.sa_session.query( trans.app.model.User ).get( int( params.get( 'select_user', '' ) ) ) name = util.restore_text(params.get('name', '')) desc = util.restore_text(params.get('desc', '')) - # library - try: - library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'library_id', None ) ) ) - except: - library = None - try: - folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'folder_id', None ) ) ) - except: - if library: - folder = library.root_folder - else: - folder = None # fields values = [] for index, field in enumerate(request_type.request_form.fields): @@ -833,8 +687,7 @@ trans.sa_session.flush() if not request: request = trans.app.model.Request(name, desc, request_type, - user, form_values, - library=library, folder=folder) + user, form_values) trans.sa_session.add( request ) trans.sa_session.flush() trans.sa_session.refresh( request ) @@ -852,46 +705,195 @@ request.type = request_type request.user = user request.values = form_values - request.library = library - request.folder = folder trans.sa_session.add( request ) trans.sa_session.flush() - return request - - # #---- Request Page ---------------------------------------------------------- # - def __update_samples(self, request, **kwd): + def __show_request(self, trans, **kwd): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + add_sample = params.get('add_sample', False) + try: + request = trans.sa_session.query( trans.app.model.Request ).get( trans.security.decode_id(kwd['id']) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message="Invalid request ID") ) + # get all data libraries accessible to this user + libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) + current_samples = [] + for i, s in enumerate(request.samples): + lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd) + current_samples.append(dict(name=s.name, + barcode=s.bar_code, + library=s.library, + folder=s.folder, + dataset_files=s.dataset_files, + field_values=s.values.content, + lib_widget=lib_widget, + folder_widget=folder_widget)) + if add_sample: + lib_widget, folder_widget = self.__library_widgets(trans, request.user, + len(current_samples)+1, + libraries, None, **kwd) + current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1), + barcode='', + library=None, + folder=None, + dataset_files=[], + field_values=['' for field in request.type.sample_form.fields], + lib_widget=lib_widget, + folder_widget=folder_widget)) + return trans.fill_template( '/admin/requests/show_request.mako', + request=request, + request_details=self.request_details(trans, request.id), + current_samples=current_samples, + sample_copy=self.__copy_sample(current_samples), + details='hide', edit_mode=util.restore_text( params.get( 'edit_mode', 'False' ) ), + msg=msg, messagetype=messagetype ) + def __library_widgets(self, trans, user, sample_index, libraries, sample=None, **kwd): + ''' + This method creates the data library & folder selectbox for creating & + editing samples. First we get a list of all the libraries accessible to + the current user and display it in a selectbox. If the user has selected an + existing library then display all the accessible sub folders of the selected + data library. + ''' + params = util.Params( kwd ) + # data library selectbox + lib_id = params.get( "sample_%i_library_id" % sample_index, 'none' ) + selected_lib = None + if sample and lib_id == 'none': + if sample.library: + lib_id = str(sample.library.id) + selected_lib = sample.library + # create data library selectbox with refresh on change enabled + lib_id_list = ['new'] + [str(lib.id) for lib in libraries.keys()] + lib_widget = SelectField( "sample_%i_library_id" % sample_index, + refresh_on_change=True, + refresh_on_change_values=lib_id_list ) + # fill up the options in the Library selectbox + # first option 'none' is the value for "Select one" option + if lib_id == 'none': + lib_widget.add_option('Select one', 'none', selected=True) + else: + lib_widget.add_option('Select one', 'none') + # all the libraries available to the selected user + for lib, hidden_folder_ids in libraries.items(): + if str(lib.id) == lib_id: + lib_widget.add_option(lib.name, lib.id, selected=True) + selected_lib, selected_hidden_folder_ids = lib, hidden_folder_ids.split(',') + else: + lib_widget.add_option(lib.name, lib.id) + lib_widget.refresh_on_change_values.append(lib.id) + # create the folder selectbox + folder_widget = SelectField( "sample_%i_folder_id" % sample_index ) + # when editing a request, either the user has already selected a subfolder or not + if sample: + if sample.folder: + current_fid = sample.folder.id + else: + # when a folder not yet associated with the request then the + # the current folder is set to the root_folder of the + # parent data library if present. + if sample.library: + current_fid = sample.library.root_folder.id + else: + current_fid = params.get( "sample_%i_folder_id" % sample_index, 'none' ) + else: + current_fid = 'none' + # first option + if lib_id == 'none': + folder_widget.add_option('Select one', 'none', selected=True) + else: + folder_widget.add_option('Select one', 'none') + if selected_lib: + # get all show-able folders for the selected library + showable_folders = trans.app.security_agent.get_showable_folders( user, user.all_roles(), + selected_lib, + [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ], + selected_hidden_folder_ids ) + for f in showable_folders: + if str(f.id) == str(current_fid): + folder_widget.add_option(f.name, f.id, selected=True) + else: + folder_widget.add_option(f.name, f.id) + return lib_widget, folder_widget + def __update_samples(self, trans, request, **kwd): ''' This method retrieves all the user entered sample information and returns an list of all the samples and their field values ''' params = util.Params( kwd ) + details = params.get( 'details', 'hide' ) + edit_mode = params.get( 'edit_mode', 'False' ) + # get all data libraries accessible to this user + libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] ) + current_samples = [] - for s in request.samples: - current_samples.append([s.name, s.values.content]) - index = len(request.samples) + for i, s in enumerate(request.samples): + lib_widget, folder_widget = self.__library_widgets(trans, request.user, i, libraries, s, **kwd) + current_samples.append(dict(name=s.name, + barcode=s.bar_code, + library=s.library, + folder=s.folder, + field_values=s.values.content, + lib_widget=lib_widget, + folder_widget=folder_widget)) + if edit_mode == 'False': + sample_index = len(request.samples) + else: + sample_index = 0 while True: - if params.get( 'sample_%i_name' % index, '' ): - sample_index = index - sample_name = util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ) - sample_values = [] + if params.get( 'sample_%i_name' % sample_index, '' ): + # data library + try: + library = trans.sa_session.query( trans.app.model.Library ).get( int( params.get( 'sample_%i_library_id' % sample_index, None ) ) ) + except: + library = None + # folder + try: + folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( int( params.get( 'sample_%i_folder_id' % sample_index, None ) ) ) + except: + if library: + folder = library.root_folder + else: + folder = None + sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ), + barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, '' ) ), + library=library, + folder=folder) + sample_info['field_values'] = [] for field_index in range(len(request.type.sample_form.fields)): - sample_values.append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - current_samples.append([sample_name, sample_values]) - index = index + 1 + sample_info['field_values'].append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) + if edit_mode == 'False': + sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, + request.user, + sample_index, + libraries, + None, **kwd) + current_samples.append(sample_info) + else: + sample_info['lib_widget'], sample_info['folder_widget'] = self.__library_widgets(trans, + request.user, + sample_index, + libraries, + request.samples[sample_index], + **kwd) + current_samples[sample_index] = sample_info + sample_index = sample_index + 1 else: break - details = params.get( 'details', 'hide' ) - edit_mode = params.get( 'edit_mode', 'False' ) - return current_samples, details, edit_mode + return current_samples, details, edit_mode, libraries def __copy_sample(self, current_samples): copy_list = SelectField('copy_sample') copy_list.add_option('None', -1, selected=True) for i, s in enumerate(current_samples): - copy_list.add_option(s[0], i) + copy_list.add_option(s['name'], i) return copy_list @web.expose @web.require_login( "create/submit sequencing requests" ) @@ -908,7 +910,7 @@ message="Invalid request ID", **kwd) ) # get the user entered sample details - current_samples, details, edit_mode = self.__update_samples( request, **kwd ) + current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd ) if params.get('import_samples_button', False) == 'Import samples': try: file_obj = params.get('file_data', '') @@ -936,10 +938,27 @@ src_sample_index = int(params.get( 'copy_sample', -1 )) if src_sample_index == -1: # empty sample - current_samples.append(['Sample_%i' % (len(current_samples)+1),['' for field in request.type.sample_form.fields]]) + lib_widget, folder_widget = self.__library_widgets(trans, request.user, + len(current_samples), + libraries, None, **kwd) + current_samples.append(dict(name='Sample_%i' % (len(current_samples)+1), + barcode='', + library=None, + folder=None, + field_values=['' for field in request.type.sample_form.fields], + lib_widget=lib_widget, + folder_widget=folder_widget)) else: - current_samples.append([current_samples[src_sample_index][0]+'_%i' % (len(current_samples)+1), - [val for val in current_samples[src_sample_index][1]]]) + lib_widget, folder_widget = self.__library_widgets(trans, request.user, + len(current_samples), + libraries, None, **kwd) + current_samples.append(dict(name=current_samples[src_sample_index]['name']+'_%i' % (len(current_samples)+1), + barcode='', + library_id='none', + folder_id='none', + field_values=[val for val in current_samples[src_sample_index]['field_values']], + lib_widget=lib_widget, + folder_widget=folder_widget)) return trans.fill_template( '/admin/requests/show_request.mako', request=request, request_details=self.request_details(trans, request.id), @@ -952,13 +971,13 @@ msg = '' for index in range(len(current_samples)-len(request.samples)): sample_index = index + len(request.samples) - sample_name = current_samples[sample_index][0] + sample_name = current_samples[sample_index]['name'] if not sample_name.strip(): msg = 'Please enter the name of sample number %i' % sample_index break count = 0 for i in range(len(current_samples)): - if sample_name == current_samples[i][0]: + if sample_name == current_samples[i]['name']: count = count + 1 if count > 1: msg = "This request has <b>%i</b> samples with the name <b>%s</b>.\nSamples belonging to a request must have unique names." % (count, sample_name) @@ -975,37 +994,45 @@ if edit_mode == 'False': for index in range(len(current_samples)-len(request.samples)): sample_index = len(request.samples) - sample_name = util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ) - sample_values = [] - for field_index in range(len(request.type.sample_form.fields)): - sample_values.append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - form_values = trans.app.model.FormValues(request.type.sample_form, sample_values) + form_values = trans.app.model.FormValues(request.type.sample_form, + current_samples[sample_index]['field_values']) trans.sa_session.add( form_values ) trans.sa_session.flush() - s = trans.app.model.Sample(sample_name, '', request, form_values) + s = trans.app.model.Sample(current_samples[sample_index]['name'], '', + request, form_values, + current_samples[sample_index]['barcode'], + current_samples[sample_index]['library'], + current_samples[sample_index]['folder'], + dataset_files=[]) trans.sa_session.add( s ) trans.sa_session.flush() else: - for index in range(len(current_samples)): - sample_index = index - sample_name = current_samples[sample_index][0] - new_sample_name = util.restore_text( params.get( 'sample_%i_name' % sample_index, '' ) ) - sample_values = [] - for field_index in range(len(request.type.sample_form.fields)): - sample_values.append(util.restore_text( params.get( 'sample_%i_field_%i' % (sample_index, field_index), '' ) )) - sample = request.has_sample(sample_name) - if sample: - form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id ) - form_values.content = sample_values - trans.sa_session.add( form_values ) - trans.sa_session.flush() - sample.name = new_sample_name - trans.sa_session.add( sample ) - trans.sa_session.flush() + messagetype = 'done' + msg = 'Changes made to the sample(s) are saved. ' + for sample_index in range(len(current_samples)): + sample = request.samples[sample_index] + sample.name = current_samples[sample_index]['name'] + sample.library = current_samples[sample_index]['library'] + sample.folder = current_samples[sample_index]['folder'] + if request.submitted(): + bc_msg = self.__validate_barcode(trans, sample, current_samples[sample_index]['barcode']) + if bc_msg: + messagetype = 'error' + msg += bc_msg + else: + sample.bar_code = current_samples[sample_index]['barcode'] + trans.sa_session.add( sample ) + trans.sa_session.flush() + form_values = trans.sa_session.query( trans.app.model.FormValues ).get( sample.values.id ) + form_values.content = current_samples[sample_index]['field_values'] + trans.sa_session.add( form_values ) + trans.sa_session.flush() return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', operation='show_request', - id=trans.security.encode_id(request.id)) ) + id=trans.security.encode_id(request.id), + messagetype=messagetype, + msg=msg )) elif params.get('edit_samples_button', False) == 'Edit samples': edit_mode = 'True' return trans.fill_template( '/admin/requests/show_request.mako', @@ -1013,13 +1040,21 @@ request_details=self.request_details(trans, request.id), current_samples=current_samples, sample_copy=self.__copy_sample(current_samples), - details=details, + details=details, libraries=libraries, edit_mode=edit_mode) elif params.get('cancel_changes_button', False) == 'Cancel': return trans.response.send_redirect( web.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(request.id)) ) + else: + return trans.fill_template( '/admin/requests/show_request.mako', + request=request, + request_details=self.request_details(trans, request.id), + current_samples=current_samples, + sample_copy=self.__copy_sample(current_samples), + details=details, libraries=libraries, + edit_mode=edit_mode, messagetype=messagetype, msg=msg) @web.expose @@ -1029,9 +1064,9 @@ msg = util.restore_text( params.get( 'msg', '' ) ) messagetype = params.get( 'messagetype', 'done' ) request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) ) - current_samples, details, edit_mode = self.__update_samples( request, **kwd ) + current_samples, details, edit_mode, libraries = self.__update_samples( trans, request, **kwd ) sample_index = int(params.get('sample_id', 0)) - sample_name = current_samples[sample_index][0] + sample_name = current_samples[sample_index]['name'] s = request.has_sample(sample_name) if s: trans.sa_session.delete( s ) @@ -1044,22 +1079,6 @@ sample_copy=self.__copy_sample(current_samples), details=details, edit_mode=edit_mode) - - @web.expose - @web.require_login( "create/submit sequencing requests" ) - def toggle_request_details(self, trans, **kwd): - params = util.Params( kwd ) - msg = util.restore_text( params.get( 'msg', '' ) ) - messagetype = params.get( 'messagetype', 'done' ) - request = trans.sa_session.query( trans.app.model.Request ).get( int( params.get( 'request_id', 0 ) ) ) - current_samples, details, edit_mode = self.__update_samples( request, **kwd ) - return trans.fill_template( '/admin/requests/show_request.mako', - request=request, - request_details=self.request_details(trans, request.id), - current_samples = current_samples, - sample_copy=self.__copy_sample(current_samples), - details=details, - edit_mode=edit_mode) @web.expose @web.require_admin def request_details(self, trans, id): @@ -1085,23 +1104,6 @@ request_details.append(dict(label='Date created', value=request.create_time, helptext='')) - # library associated - if request.library: - value=request.library.name - else: - value = None - request_details.append(dict(label='Data library', - value=value, - helptext='Data library where the resultant dataset will be stored')) - # folder associated - if request.folder: - value = request.folder.name - else: - value = None - request_details.append( dict( label='Data library folder', - value=value, - helptext='Data library folder where the resultant dataset will be stored' ) ) - # form fields for index, field in enumerate(request.type.request_form.fields): if field['required']: @@ -1122,6 +1124,33 @@ value=request.values.content[index], helptext=field['helptext']+' ('+req+')')) return request_details + def __validate_barcode(self, trans, sample, barcode): + ''' + This method makes sure that the given barcode about to be assigned to + the given sample is gobally unique. That is, barcodes must be unique + across requests in Galaxy LIMS + ''' + msg = '' + for index in range(len(sample.request.samples)): + # check for empty bar code + if not barcode.strip(): + msg = 'Please fill the barcode for sample <b>%s</b>.' % samples[index].name + break + # check all the saved bar codes + all_samples = trans.sa_session.query( trans.app.model.Sample ) + for s in all_samples: + if barcode == s.bar_code: + if sample.id == s.id: + continue + else: + msg = '''The bar code <b>%s</b> of sample <b>%s</b> + belongs another sample. The sample bar codes must be + unique throughout the system''' % \ + (barcode, sample.name) + break + if msg: + break + return msg @web.expose @web.require_admin def bar_codes(self, trans, **kwd): @@ -1233,7 +1262,12 @@ for s in request.samples: if s.current_state().id != request.type.states[-1].id: complete = False - if complete: + if request.complete() and not complete: + comments = "Sample(s) " % request.type.states[-1].name + event = trans.app.model.RequestEvent(request, request.states.COMPLETE, comments) + trans.sa_session.add( event ) + trans.sa_session.flush() + elif complete: # change the request state to 'Complete' comments = "All samples of this request are in the last sample state (%s)." % request.type.states[-1].name event = trans.app.model.RequestEvent(request, request.states.COMPLETE, comments) @@ -1279,7 +1313,7 @@ event = trans.app.model.SampleEvent(sample, new_state, comments) trans.sa_session.add( event ) trans.sa_session.flush() - self.__set_request_state( trans, sample.request ) + #self.__set_request_state( trans, sample.request ) return trans.response.send_redirect( web.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)) @@ -1306,7 +1340,217 @@ return trans.fill_template( '/admin/samples/events.mako', events_list=events_list, sample=sample, widgets=widgets, title=title) + + # + # Data transfer from sequencer + # + @web.expose + @web.require_admin + def show_datatx_page( self, trans, **kwd ): + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + try: + sample = trans.sa_session.query( trans.app.model.Sample ).get( trans.security.decode_id( kwd['sample_id'] ) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message="Invalid sample ID", + **kwd) ) + return trans.fill_template( '/admin/requests/get_data.mako', + sample=sample, dataset_files=sample.dataset_files, + msg=msg, messagetype=messagetype, files=[], + folder_path=util.restore_text( params.get( 'folder_path', '' ) )) + def __get_files(self, trans, sample, folder_path): + ''' + This method retrieves the filenames to be transfer from the remote host. + ''' + datatx_info = sample.request.type.datatx_info + if not datatx_info['host'] or not datatx_info['username'] or not datatx_info['password']: + msg = "Error in sequencer login information." + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='show_datatx_page', + sample_id=trans.security.encode_id(sample.id), + messagetype='error', + msg=msg)) + def print_ticks(d): + pass + cmd = 'ssh %s@%s "ls -F %s"' % ( datatx_info['username'], + datatx_info['host'], + folder_path) + output = pexpect.run(cmd, events={'.ssword:*': datatx_info['password']+'\r\n', + pexpect.TIMEOUT:print_ticks}, + timeout=10) + if 'No such file or directory' in output: + msg = "No such folder (%s) exists on the sequencer." % folder_path + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='show_datatx_page', + sample_id=trans.security.encode_id(sample.id), + msg=msg, messagetype='error', + folder_path=folder_path )) + files = output.split()[2:] + return files + + @web.expose + @web.require_admin + def get_data(self, trans, **kwd): + try: + sample = trans.sa_session.query( trans.app.model.Sample ).get( kwd['sample_id'] ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='list', + status='error', + message="Invalid sample ID" ) ) + params = util.Params( kwd ) + msg = util.restore_text( params.get( 'msg', '' ) ) + messagetype = params.get( 'messagetype', 'done' ) + folder_path = util.restore_text( params.get( 'folder_path', '' ) ) + files_list = util.listify( params.get( 'files_list', '' ) ) + if not folder_path: + return trans.fill_template( '/admin/requests/get_data.mako', + sample=sample, files=[], + dataset_files=sample.dataset_files, + folder_path=folder_path ) + if folder_path[-1] != os.sep: + folder_path = folder_path+os.sep + if params.get( 'browse_button', False ): + # get the filenames from the remote host + files = self.__get_files(trans, sample, folder_path) + return trans.fill_template( '/admin/requests/get_data.mako', + sample=sample, files=files, + dataset_files=sample.dataset_files, + folder_path=folder_path ) + elif params.get( 'folder_up', False ): + if folder_path[-1] == os.sep: + folder_path = os.path.dirname(folder_path[:-1]) + # get the filenames from the remote host + files = self.__get_files(trans, sample, folder_path) + return trans.fill_template( '/admin/requests/get_data.mako', + sample=sample, files=files, + dataset_files=sample.dataset_files, + folder_path=folder_path ) + elif params.get( 'open_folder', False ): + if len(files_list) == 1: + folder_path = os.path.join(folder_path, files_list[0]) + # get the filenames from the remote host + files = self.__get_files(trans, sample, folder_path) + return trans.fill_template( '/admin/requests/get_data.mako', + sample=sample, files=files, + dataset_files=sample.dataset_files, + folder_path=folder_path ) + elif params.get( 'remove_dataset_button', False ): + dataset_index = int(params.get( 'dataset_index', 0 )) + del sample.dataset_files[dataset_index] + trans.sa_session.add( sample ) + trans.sa_session.flush() + return trans.fill_template( '/admin/requests/get_data.mako', + sample=sample, + dataset_files=sample.dataset_files) + elif params.get( 'start_transfer_button', False ): + for f in files_list: + sample.dataset_files.append([os.path.join(folder_path, f), + sample.transfer_status.NOT_STARTED]) + trans.sa_session.add( sample ) + trans.sa_session.flush() + return self.__start_datatx(trans, sample) + + def __setup_datatx_user(self, trans, library, folder): + ''' + This method sets up the datatx user: + - Checks if the user exists already, if not creates the user + - Checks if the user had ADD_LIBRARY permission on the target library + and the target folder, if not sets up the permissions. + ''' + # Retrieve the upload user login information from the config file + config = ConfigParser.ConfigParser() + config.read('transfer_datasets.ini') + email = config.get("data_transfer_user_login_info", "email") + password = config.get("data_transfer_user_login_info", "password") + # check if the user already exists + datatx_user = trans.sa_session.query( trans.app.model.User ) \ + .filter( trans.app.model.User.table.c.email==email ) \ + .first() + if not datatx_user: + # if not create the user + datatx_user = trans.app.model.User( email=email ) + datatx_user.set_password_cleartext( password ) + if trans.app.config.use_remote_user: + datatx_user.external = True + trans.sa_session.add( datatx_user ) + trans.sa_session.flush() + trans.app.security_agent.create_private_user_role( datatx_user ) + trans.app.security_agent.user_set_default_permissions( datatx_user, history=False, dataset=False ) + for role in datatx_user.all_roles(): + if role.name == datatx_user.email and role.description == 'Private Role for %s' % datatx_user.email: + datatx_user_private_role = role + break + def check_permission(item_actions, role): + for item_permission in item_actions: + if item_permission.action == trans.app.security_agent.permitted_actions.LIBRARY_ADD.action \ + and item_permission.role.id == role.id: + return True + return False + # check if this user has 'add' permissions on the target library & folder + # if not, set 'ADD' permission + if not check_permission(library.actions, datatx_user_private_role): + lp = trans.app.model.LibraryPermissions( trans.app.security_agent.permitted_actions.LIBRARY_ADD.action, + library, + datatx_user_private_role ) + trans.sa_session.add( lp ) + if not check_permission(folder.actions, datatx_user_private_role): + dp = trans.app.model.LibraryFolderPermissions( trans.app.security_agent.permitted_actions.LIBRARY_ADD.action, + folder, + datatx_user_private_role ) + trans.sa_session.add( dp ) + trans.sa_session.flush() + return datatx_user + + def __start_datatx(self, trans, sample): + # data transfer user + datatx_user = self.__setup_datatx_user(trans, sample.library, sample.folder) + # validate sequecer information + datatx_info = sample.request.type.datatx_info + if not datatx_info['host'] or \ + not datatx_info['username'] or \ + not datatx_info['password']: + msg = "Error in sequencer login information." + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='show_datatx_page', + sample_id=trans.security.encode_id(sample.id), + messagetype='error', + msg=msg)) + transfer_script = "scripts/galaxy_messaging/server/daemon.py" + for index, dataset in enumerate(sample.dataset_files): + dfile = dataset[0] + status = dataset[1] + if status == sample.transfer_status.NOT_STARTED: + cmd = "python %s %s %s %s %s %s %s %s %s" % ( transfer_script, + datatx_info['host'], + datatx_info['username'], + datatx_info['password'], + dfile, + sample.id, + index, + trans.security.encode_id(sample.library.id), + trans.security.encode_id(sample.folder.id)) + # set the transfer status + sample.dataset_files[index][1] = sample.transfer_status.IN_PROGRESS + trans.sa_session.add( sample ) + trans.sa_session.flush() + os.system(cmd) + # set the sample state to the last state + if sample.current_state().id != sample.request.type.states[-1].id: + event = trans.app.model.SampleEvent(sample, sample.request.type.states[-1], + 'The dataset are ready & are being transfered to Galaxy') + trans.sa_session.add( event ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='show_datatx_page', + sample_id=trans.security.encode_id(sample.id))) + + ## #### Request Type Stuff ################################################### ## @@ -1379,16 +1623,36 @@ msg=msg, messagetype=messagetype) elif params.get( 'save_request_type', False ): - st, msg = self.__save_request_type(trans, **kwd) - if not st: + rt, msg = self.__save_request_type(trans, **kwd) + if not rt: return trans.fill_template( '/admin/requests/create_request_type.mako', forms=get_all_forms( trans ), msg=msg, messagetype='error') return trans.response.send_redirect( web.url_for( controller='requests_admin', action='manage_request_types', - message='Request type <b>%s</b> has been created' % st.name, + message='Request type <b>%s</b> has been created' % rt.name, status='done') ) + elif params.get( 'save_changes', False ): + try: + rt = trans.sa_session.query( trans.app.model.RequestType ).get( int(kwd['rt_id']) ) + except: + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + msg='Invalid request type ID', + messagetype='error') ) + # data transfer info + rt.datatx_info = dict(host=util.restore_text( params.get( 'host', '' ) ), + username=util.restore_text( params.get( 'username', '' ) ), + password=util.restore_text( params.get( 'password', '' ) )) + trans.sa_session.add( rt ) + trans.sa_session.flush() + return trans.response.send_redirect( web.url_for( controller='requests_admin', + action='manage_request_types', + operation='view', + id=trans.security.encode_id(rt.id), + msg='Changes made to request type <b>%s</b> has been saved' % rt.name, + messagetype='done') ) else: rt_info, rt_states = self.__create_request_type_form(trans, **kwd) return trans.fill_template( '/admin/requests/create_request_type.mako', @@ -1448,6 +1712,10 @@ rt.desc = util.restore_text( params.get( 'desc', '' ) ) rt.request_form = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.request_form_id ) ) rt.sample_form = trans.sa_session.query( trans.app.model.FormDefinition ).get( int( params.sample_form_id ) ) + # data transfer info + rt.datatx_info = dict(host=util.restore_text( params.get( 'host', '' ) ), + username=util.restore_text( params.get( 'username', '' ) ), + password=util.restore_text( params.get( 'password', '' ) )) trans.sa_session.add( rt ) trans.sa_session.flush() # set sample states diff -r 44fff02fb036 -r 37b55fad152b lib/galaxy/web/framework/__init__.py --- a/lib/galaxy/web/framework/__init__.py Thu Feb 04 10:44:36 2010 -0500 +++ b/lib/galaxy/web/framework/__init__.py Thu Feb 04 11:01:45 2010 -0500 @@ -30,7 +30,9 @@ pkg_resources.require( "SQLAlchemy >= 0.4" ) from sqlalchemy import and_ - + +pkg_resources.require( "pexpect" ) + import logging log = logging.getLogger( __name__ ) diff -r 44fff02fb036 -r 37b55fad152b scripts/galaxy_messaging/server/daemon.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/galaxy_messaging/server/daemon.py Thu Feb 04 11:01:45 2010 -0500 @@ -0,0 +1,34 @@ +#!/usr/bin/python +""" + +Data Transfer Script Daemon + +This script is called from Galaxy LIMS once the lab admin starts the data +transfer process using the user interface. This creates a child process and +this child process starts the data_transfer.py script as a new process + +This script passes all the arguments from Galaxy LIMS to the data_transfer.py script + +Usage: + +python daemon.py <sequencer_host> + <username> + <password> + <source_file> + <sample_id> + <dataset_index> + <library_id> + <folder_id> +""" + +import sys, os +# Perform first fork. +try: + pid = os.fork( ) + if pid > 0: + sys.exit(0) # Exit first parent. +except OSError, e: + sys.stderr.write("fork #1 failed: (%d) %sn" % (e.errno, e.strerror)) + sys.exit(1) +os.execv(os.path.join( os.getcwd(), "scripts/galaxy_messaging/server/data_transfer.py"), sys.argv) + diff -r 44fff02fb036 -r 37b55fad152b scripts/galaxy_messaging/server/data_transfer.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scripts/galaxy_messaging/server/data_transfer.py Thu Feb 04 11:01:45 2010 -0500 @@ -0,0 +1,211 @@ +#!/usr/bin/python +""" + +Data Transfer Script: Sequencer to Galaxy + +This script is called from Galaxy LIMS once the lab admin starts the data +transfer process using the user interface. + +Usage: + +python data_transfer.py <sequencer_host> + <username> + <password> + <source_file> + <sample_id> + <dataset_index> + <library_id> + <folder_id> +""" + +import ConfigParser +import sys, os, time, traceback +import optparse +import urllib,urllib2, cookielib, shutil +import logging +from galaxydb_interface import GalaxyDbInterface + +assert sys.version_info[:2] >= ( 2, 4 ) +new_path = [ os.path.join( os.getcwd(), "lib" ) ] +new_path.extend( sys.path[1:] ) # remove scripts/ from the path +sys.path = new_path + +from galaxy.util.json import from_json_string, to_json_string +from galaxy import eggs +import pkg_resources +pkg_resources.require( "pexpect" ) +import pexpect + +pkg_resources.require( "simplejson" ) +import simplejson + +curr_dir = os.getcwd() +logfile = os.path.join(curr_dir, 'data_transfer.log') +logging.basicConfig(filename=logfile, level=logging.DEBUG, + format="%(asctime)s [%(levelname)s] %(message)s") + +class DataTransfer(object): + + def __init__(self, host, username, password, remote_file, sample_id, + dataset_index, library_id, folder_id): + self.host = host + self.username = username + self.password = password + self.remote_file = remote_file + self.sample_id = sample_id + self.dataset_index = dataset_index + self.library_id = library_id + self.folder_id = folder_id + try: + # Retrieve the upload user login information from the config file + config = ConfigParser.ConfigParser() + config.read('universe_wsgi.ini') + self.server_host = config.get("server:main", "host") + self.server_port = config.get("server:main", "port") + self.database_connection = config.get("app:main", "database_connection") + self.import_dir = config.get("app:main", "library_import_dir") + # create the destination directory within the import directory + self.server_dir = os.path.join( self.import_dir, 'datatx_'+str(os.getpid()) ) + os.mkdir(self.server_dir) + if not os.path.exists(self.server_dir): + raise Exception + except: + logging.error(traceback.format_exc()) + logging.error('FATAL ERROR') + if self.database_connection: + self.update_status('Error') + sys.exit(1) + + def start(self): + ''' + This method executes the file transfer from the sequencer, adds the dataset + to the data library & finally updates the data transfer status in the db + ''' + # datatx + self.transfer_file() + # add the dataset to the given library + self.add_to_library() + # update the data transfer status in the db + self.update_status('Complete') + # cleanup + self.cleanup() + sys.exit(0) + + def cleanup(self): + ''' + remove the directory created to store the dataset files temporarily + before adding the same to the data library + ''' + try: + shutil.rmtree( self.server_dir ) + except: + self.error_and_exit() + + + def error_and_exit(self): + ''' + This method is called any exception is raised. This prints the traceback + and terminates this script + ''' + logging.error(traceback.format_exc()) + logging.error('FATAL ERROR') + self.update_status('Error') + sys.exit(1) + + def transfer_file(self): + ''' + This method executes a scp process using pexpect library to transfer + the dataset file from the remote sequencer to the Galaxy server + ''' + def print_ticks(d): + pass + try: + cmd = "scp %s@%s:%s %s" % ( self.username, + self.host, + self.remote_file, + self.server_dir) + logging.debug(cmd) + output = pexpect.run(cmd, events={'.ssword:*': self.password+'\r\n', + pexpect.TIMEOUT:print_ticks}, + timeout=10) + logging.debug(output) + if not os.path.exists(os.path.join(self.server_dir, os.path.basename(self.remote_file))): + raise Exception + except: + self.error_and_exit() + + + def add_to_library(self): + ''' + This method adds the dataset file to the target data library & folder + by opening the corresponding url in Galaxy server running. + ''' + try: + logging.debug('Adding %s to library...' % os.path.basename(self.remote_file)) + # Retrieve the upload user login information from the config file + config = ConfigParser.ConfigParser() + config.read('transfer_datasets.ini') + email = config.get("data_transfer_user_login_info", "email") + password = config.get("data_transfer_user_login_info", "password") + # create url + base_url = "http://%s:%s" % (self.server_host, self.server_port) + # login + url = "%s/user/login?email=%s&password=%s" % (base_url, email, password) + cj = cookielib.CookieJar() + opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj)) + f = opener.open(url) + if f.read().find("Now logged in as "+email) == -1: + # if the user doesnt exist, create the user + url = "%s/user/create?email=%s&username=%s&password=%s&confirm=%s&create_user_button=Submit" % ( base_url, email, email, password, password ) + f = opener.open(url) + if f.read().find("Now logged in as "+email) == -1: + raise Exception + # after login, add dataset to the library + params = urllib.urlencode(dict( cntrller='library_admin', + tool_id='upload1', + tool_state='None', + library_id=self.library_id, + folder_id=self.folder_id, + upload_option='upload_directory', + file_type='auto', + server_dir=os.path.basename(self.server_dir), + dbkey='', + runtool_btn='Upload to library')) + #url = "http://localhost:8080/library_common/upload_library_dataset?cntrller=library_admin&tool_id=upload1&tool_state=None&library_id=adb5f5c93f827949&folder_id=adb5f5c93f827949&upload_option=upload_directory&file_type=auto&server_dir=003&dbkey=%3F&message=&runtool_btn=Upload+to+library" + #url = base_url+"/library_common/upload_library_dataset?library_id=adb5f5c93f827949&tool_id=upload1&file_type=auto&server_dir=datatx_22858&dbkey=%3F&upload_option=upload_directory&folder_id=529fd61ab1c6cc36&cntrller=library_admin&tool_state=None&runtool_btn=Upload+to+library" + url = base_url+"/library_common/upload_library_dataset" + logging.debug(url) + logging.debug(params) + f = opener.open(url, params) + #print f.read() + except: + self.error_and_exit() + + def update_status(self, status): + ''' + + ''' + try: + galaxy = GalaxyDbInterface(self.database_connection) + df = from_json_string(galaxy.get_sample_dataset_files(self.sample_id)) + logging.debug(df) + df[self.dataset_index][1] = status + + galaxy.set_sample_dataset_files(self.sample_id, to_json_string(df)) + logging.debug("######################\n"+str(from_json_string(galaxy.get_sample_dataset_files(self.sample_id))[self.dataset_index])) + except: + logging.error(traceback.format_exc()) + logging.error('FATAL ERROR') + sys.exit(1) + +if __name__ == '__main__': + logging.info('STARTING %i %s' % (os.getpid(), str(sys.argv))) + logging.info('daemonized %i' % os.getpid()) + # + # Start the daemon + # + dt = DataTransfer(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], + int(sys.argv[5]), int(sys.argv[6]), sys.argv[7], sys.argv[8]) + dt.start() + + diff -r 44fff02fb036 -r 37b55fad152b scripts/galaxy_messaging/server/galaxydb_interface.py --- a/scripts/galaxy_messaging/server/galaxydb_interface.py Thu Feb 04 10:44:36 2010 -0500 +++ b/scripts/galaxy_messaging/server/galaxydb_interface.py Thu Feb 04 11:01:45 2010 -0500 @@ -12,6 +12,7 @@ new_path.extend( sys.path[1:] ) # remove scripts/ from the path sys.path = new_path from galaxy import eggs +from galaxy.model.custom_types import * import pkg_resources pkg_resources.require( "psycopg2" ) import psycopg2 @@ -126,6 +127,16 @@ request_id=self.request_id, state=request_state, comment='All samples of this request have finished processing.') + + def get_sample_dataset_files(self, sample_id): + subsubquery = select(columns=[self.sample_table.c.dataset_files], + whereclause=self.sample_table.c.id==sample_id) + return subsubquery.execute().fetchall()[0][0] + + def set_sample_dataset_files(self, sample_id, value): + u = self.sample_table.update(whereclause=self.sample_table.c.id==sample_id) + u.execute(dataset_files=value) + diff -r 44fff02fb036 -r 37b55fad152b templates/admin/forms/edit_form.mako --- a/templates/admin/forms/edit_form.mako Thu Feb 04 10:44:36 2010 -0500 +++ b/templates/admin/forms/edit_form.mako Thu Feb 04 11:01:45 2010 -0500 @@ -30,6 +30,25 @@ }); </script> +<script type="text/javascript"> +$(document).ready(function(){ + //hide the all of the element with class msg_body + $(".msg_body").hide(); + //toggle the componenet with class msg_body + $(".msg_head").click(function(){ + $(this).next(".msg_body").slideToggle(450); + }); +}); +</script> +<style type="text/css"> +.msg_head { + padding: 0px 0px; + cursor: pointer; +} + +} +</style> + <%def name="render_selectbox_options( index, field_attr )"> %if field_attr[0] == 'Type': %if field_attr[1].get_selected()[0] == 'SelectField': @@ -55,11 +74,16 @@ %endif </%def> -<%def name="render_field( index, field )"> +<%def name="render_field( index, field, saved )"> + %if saved: + <h4 class="msg_head"> + <div class="form-row">${index+1}. ${field[0][1].value} (${field[2][1].get_selected()[1]})</div> + </h4> + <div class="msg_body"> + %else: + <div class="msg_body2"> + %endif <div class="repeat-group-item"> - <div class="form-row"> - <label>Field ${1+index}</label> - </div> %for field_attr in field: <div class="form-row"> <label>${field_attr[0]}</label> @@ -70,7 +94,9 @@ <div class="form-row"> <input type="submit" name="remove_button" value="Remove field ${index+1}"/> </div> - </div> + </div> + </div> + </%def> <%def name="render_layout( index, widget )"> @@ -109,7 +135,11 @@ %endif <div class="toolFormTitle">Fields (${len(form.fields)})</div> %for ctr, field in enumerate(field_details): - ${render_field( ctr, field )} + %if ctr < len(form.fields): + ${render_field( ctr, field, True )} + %else: + ${render_field( ctr, field, False )} + %endif %endfor <div class="form-row"> <input type="submit" name="add_field_button" value="Add field"/> diff -r 44fff02fb036 -r 37b55fad152b templates/admin/forms/show_form_read_only.mako --- a/templates/admin/forms/show_form_read_only.mako Thu Feb 04 10:44:36 2010 -0500 +++ b/templates/admin/forms/show_form_read_only.mako Thu Feb 04 11:01:45 2010 -0500 @@ -8,7 +8,9 @@ <%def name="render_grid( grid_index, grid_name, fields_dict )"> %if grid_name: - <div class="toolFormTitle">${grid_name}</div> + <div class="form-row"> + <h4>${grid_name}</h4> + </div> %endif <div style="clear: both"></div> <table class="grid"> @@ -83,7 +85,7 @@ ${render_grid( 0, '', form.fields_of_grid( None ) )} %else: %for grid_index, grid_name in enumerate(form.layout): - ${render_grid( grid_index, grid_name, form.fields_of_grid( grid_name ) )} + ${render_grid( grid_index, grid_name, form.fields_of_grid( grid_index ) )} %endfor %endif %else: diff -r 44fff02fb036 -r 37b55fad152b templates/admin/requests/create_request_type.mako --- a/templates/admin/requests/create_request_type.mako Thu Feb 04 10:44:36 2010 -0500 +++ b/templates/admin/requests/create_request_type.mako Thu Feb 04 11:01:45 2010 -0500 @@ -49,6 +49,22 @@ <div class="form-row"> <input type="submit" name="add_state_button" value="Add state"/> </div> + <div class="toolFormTitle">Sequencer information</div> + <div class="form-row"> + This information is only needed for transferring data from sequencer to Galaxy + </div> + <div class="form-row"> + <label>Hostname or IP Address:</label> + <input type="text" name="host" value="" size="40"/> + </div> + <div class="form-row"> + <label>Username:</label> + <input type="text" name="username" value="" size="40"/> + </div> + <div class="form-row"> + <label>Password:</label> + <input type="password" name="password" value="" size="40"/> + </div> <div class="form-row"> <div style="float: left; width: 250px; margin-right: 10px;"> <input type="hidden" name="new" value="submitted" size="40"/> diff -r 44fff02fb036 -r 37b55fad152b templates/admin/requests/get_data.mako --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/admin/requests/get_data.mako Thu Feb 04 11:01:45 2010 -0500 @@ -0,0 +1,135 @@ +<%inherit file="/base.mako"/> +<%namespace file="/message.mako" import="render_msg" /> + + +%if msg: + ${render_msg( msg, messagetype )} +%endif + +<script type="text/javascript"> +$(document).ready(function(){ + //hide the all of the element with class msg_body + $(".msg_body").hide(); + //toggle the componenet with class msg_body + $(".msg_head").click(function(){ + $(this).next(".msg_body").slideToggle(450); + }); +}); +</script> +<style type="text/css"> +.msg_head { + padding: 0px 0px; + cursor: pointer; +} + +} +</style> + + +<h2>Data transfer from Sequencer</h2> +<h3>Sample "${sample.name}" of Request "${sample.request.name}"</h3> + +<ul class="manage-table-actions"> +## %if sample.request.submitted() and sample.untransfered_dataset_files(): +## <li> +## <a class="action-button" href="${h.url_for( controller='requests_admin', action='start_datatx', id=trans.security.encode_id(sample.id) )}"> +## <span>Start data transfer</span></a> +## </li> +## %endif + %if sample.request.submitted() and sample.inprogress_dataset_files(): + <li> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}"> + <span>Refresh this page</span></a> + </li> + %endif + <li> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_request_types', operation='view', id=trans.security.encode_id(sample.request.type.id) )}"> + <span>Sequencer information</span></a> + </li> + <li> + <a class="action-button" href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( sample.library.id ) )}"> + <span>${sample.library.name} Data Library</span></a> + </li> + <li> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='show_request', id=trans.security.encode_id(sample.request.id) )}"> + <span>Browse this request</span></a> + </li> +</ul> + +<div class="toolForm"> + <form name="get_data" action="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id)}" method="post" > + %if len(dataset_files): + <div class="form-row"> + <h4>Datasets Transfered</h4> + <div class="form-row"> + <table class="grid"> + <thead> + <tr> + <th>Dataset File</th> + <th>Transfer Status</th> + ##<th>Data Library</th> + <th></th> + </tr> + <thead> + <tbody> + %for dataset_index, dataset_file in enumerate(dataset_files): + ${sample_dataset_files( dataset_index, dataset_file[0], dataset_file[1] )} + %endfor + </tbody> + </table> + </div> + </div> + %endif + <div class="form-row"> + <h4>Select files for transfer</h4> + <div style="width: 60%;"> + <div class="form-row"> + <label>Folder path on the sequencer:</label> + <input type="text" name="folder_path" value="${folder_path}" size="100"/> + <input type="submit" name="browse_button" value="List contents"/> + <input type="submit" name="open_folder" value="Open folder"/> + <input type="submit" name="folder_up" value="Up"/> + </div> + <div class="form-row"> + <select name="files_list" id="files_list" style="max-width: 98%; width: 98%; height: 150px; font-size: 100%;" multiple> + %for index, f in enumerate(files): + <option value="${f}">${f}</option> + %endfor + </select> + </div> + <div class="form-row"> + <input type="submit" name="start_transfer_button" value="Transfer"/> + </div> + </div> + </div> + </form> +</div> + +<%def name="sample_dataset_files( dataset_index, dataset_file, status )"> + <tr> + <td> +## <label class="msg_head"><a href="${h.url_for( controller='requests_admin', action='show_dataset_file', sample_id=trans.security.encode_id(sample.id), dataset_index=dataset_index )}">${dataset_file.split('/')[-1]}</a></label> + <div class="msg_head"><u>${dataset_file.split('/')[-1]}</u></div> + <div class="msg_body"> + ${dataset_file} + </div> + </td> + <td> + %if status == sample.transfer_status.IN_PROGRESS: + <i>${status}</i> + %else: + ${status} + %endif + </td> + ##<td></td> + %if status == sample.transfer_status.NOT_STARTED: + <td> + <a class="action-button" href="${h.url_for( controller='requests_admin', action='get_data', sample_id=sample.id, remove_dataset_button=True, dataset_index=dataset_index )}"> + <img src="${h.url_for('/static/images/delete_icon.png')}" /> + <span></span></a> + </td> + %else: + <td></td> + %endif + </tr> +</%def> \ No newline at end of file diff -r 44fff02fb036 -r 37b55fad152b templates/admin/requests/show_request.mako --- a/templates/admin/requests/show_request.mako Thu Feb 04 10:44:36 2010 -0500 +++ b/templates/admin/requests/show_request.mako Thu Feb 04 11:01:45 2010 -0500 @@ -5,6 +5,51 @@ ${render_msg( msg, messagetype )} %endif +<script type="text/javascript"> +$( function() { + $( "select[refresh_on_change='true']").change( function() { + var refresh = false; + var refresh_on_change_values = $( this )[0].attributes.getNamedItem( 'refresh_on_change_values' ) + if ( refresh_on_change_values ) { + refresh_on_change_values = refresh_on_change_values.value.split( ',' ); + var last_selected_value = $( this )[0].attributes.getNamedItem( 'last_selected_value' ); + for( i= 0; i < refresh_on_change_values.length; i++ ) { + if ( $( this )[0].value == refresh_on_change_values[i] || ( last_selected_value && last_selected_value.value == refresh_on_change_values[i] ) ){ + refresh = true; + break; + } + } + } + else { + refresh = true; + } + if ( refresh ){ + $( "#show_request" ).submit(); + } + }); +}); +</script> + + +<script type="text/javascript"> +$(document).ready(function(){ + //hide the all of the element with class msg_body + $(".msg_body").hide(); + //toggle the componenet with class msg_body + $(".msg_head").click(function(){ + $(this).next(".msg_body").slideToggle(450); + }); +}); +</script> +<style type="text/css"> +.msg_head { + padding: 15px 0px; + cursor: pointer; +} + +} +</style> + %if request.rejected(): ${render_msg( "Reason for rejection: "+request.last_comment(), "warning" )} %endif @@ -27,37 +72,144 @@ <span>Reject request</span></a> </li> %endif - %if request.submitted() and request.samples: - <li> - <a class="action-button" href="${h.url_for( controller='requests_admin', action='bar_codes', request_id=request.id)}"> - <span>Bar codes</span></a> - </li> - %endif +## %if request.submitted() and request.samples: +## <li> +## <a class="action-button" href="${h.url_for( controller='requests_admin', action='bar_codes', request_id=request.id)}"> +## <span>Bar codes</span></a> +## </li> +## %endif <li> <a class="action-button" href="${h.url_for( controller='requests_admin', action='list', operation='events', id=trans.security.encode_id(request.id) )}"> <span>History</span></a> </li> + </ul> +<%def name="show_basic_info_form( sample_index, sample, info )"> + <td> + <input type="text" name=sample_${sample_index}_name value="${info['name']}" size="10"/> + <div class="toolParamHelp" style="clear: both;"> + <i>${' (required)' }</i> + </div> + </td> + %if sample: + %if sample.request.unsubmitted(): + <td></td> + %else: + <td><input type="text" name=sample_${sample_index}_barcode value="${info['barcode']}" size="10"/></td> + %endif + %else: + <td></td> + %endif + %if sample: + %if sample.request.unsubmitted(): + <td>Unsubmitted</td> + %else: + <td><a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a></td> + %endif + %else: + <td></td> + %endif + <td>${info['lib_widget'].get_html()}</td> + <td>${info['folder_widget'].get_html()}</td> + %if request.submitted() or request.complete(): + %if sample: + <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a></td> + %else: + <td><a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">Add</a></td> + %endif + %endif +</%def> -<%def name="render_sample_form( index, sample_name, sample_values, grid_index, fields_dict )"> - %if grid_index == 0: - <td> - <input type="text" name=sample_${index}_name value="${sample_name}" size="10"/> - <div class="toolParamHelp" style="clear: both;"> - <i>${' (required)' }</i> - </div> - </td> - <td> - </td> - <td> - </td> - %else: - <td> - ${sample_name} - </td> - %endif +## This function displays the "Basic Information" grid +<%def name="render_basic_info_grid()"> + <h4>Sample Information</h4> + <table class="grid"> + <thead> + <tr> + <th>Name</th> + <th>Barcode</th> + <th>State</th> + <th>Data Library</th> + <th>Folder</th> + %if request.submitted() or request.complete(): + <th>Dataset(s)</th> + %endif + <th></th> + </tr> + <thead> + <tbody> + <% + trans.sa_session.refresh( request ) + %> + %for sample_index, info in enumerate(current_samples): + <% + if sample_index in range(len(request.samples)): + sample = request.samples[sample_index] + else: + sample = None + %> + %if edit_mode == 'True': + <tr> + ${show_basic_info_form( sample_index, sample, info )} + </tr> + %else: + <tr> + %if sample_index in range(len(request.samples)): + <td>${info['name']}</td> + <td>${info['barcode']}</td> + <td> + %if sample: + %if sample.request.unsubmitted(): + Unsubmitted + %else: + <a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a> + %endif + %endif + </td> + %if info['library']: + <td><a href="${h.url_for( controller='library_common', action='browse_library', cntrller='library', id=trans.security.encode_id( info['library'].id ) )}">${info['library'].name}</a></td> + %else: + <td></td> + %endif + %if info['folder']: + <td>${info['folder'].name}</td> + %else: + <td></td> + %endif + %if request.submitted() or request.complete(): + <td> + <a href="${h.url_for( controller='requests_admin', action='show_datatx_page', sample_id=trans.security.encode_id(sample.id) )}">${len(sample.dataset_files)}</a> + </td> + %endif + + + %else: + ${show_basic_info_form( sample_index, sample, info )} + %endif + %if request.unsubmitted() or request.rejected(): + <td> + %if sample: + %if sample.request.unsubmitted(): + <a class="action-button" href="${h.url_for( controller='requests_admin', action='delete_sample', request_id=request.id, sample_id=sample_index )}"> + <img src="${h.url_for('/static/images/delete_icon.png')}" /> + <span></span></a> + %endif + %endif + </td> + %endif + </tr> + %endif + %endfor + </tbody> + </table> +</%def> + +<%def name="render_sample_form( index, sample_name, sample_values, fields_dict )"> + <td> + ${sample_name} + </td> %for field_index, field in fields_dict.items(): <td> %if field['type'] == 'TextField': @@ -75,7 +227,7 @@ %elif field['type'] == 'CheckboxField': <input type="checkbox" name="sample_${index}_field_${field_index}" value="Yes"/> %elif field['type'] == 'NumberField': - <input type=int name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/> + <input type="int" name="sample_${index}_field_${field_index}" value="${sample_values[field_index]}" size="7"/> %endif <div class="toolParamHelp" style="clear: both;"> <i>${'('+field['required']+')' }</i> @@ -84,24 +236,14 @@ %endfor </%def> -<%def name="render_sample( index, sample, grid_index, fields_dict )"> +<%def name="render_sample( index, sample_name, sample_values, fields_dict )"> <td> - ${sample.name} + ${sample_name} </td> - %if grid_index == 0: - <td>${sample.bar_code}</td> - <td> - %if sample.request.new(): - Unsubmitted - %else: - <a href="${h.url_for( controller='requests_admin', action='show_events', sample_id=sample.id)}">${sample.current_state().name}</a> - %endif - </td> - %endif %for field_index, field in fields_dict.items(): <td> - %if sample.values.content[field_index]: - ${sample.values.content[field_index]} + %if sample_values[field_index]: + ${sample_values[field_index]} %else: <i>None</i> %endif @@ -111,22 +253,16 @@ <div class="toolForm"> <div class="form-row"> - %if details == "show": - <a href="${h.url_for( controller='requests_admin', action='toggle_request_details', request_id=request.id, details="hide" )}">Hide request details</a> - </div> + <div class="msg_list"> + <h4 class="msg_head">Request Information</h4> + <div class="msg_body"> %for index, rd in enumerate(request_details): <div class="form-row"> <label>${rd['label']}</label> %if not rd['value']: <i>None</i> %else: - %if rd['label'] == 'Data library': - %if rd['value']: - <a href="${h.url_for( controller='library_common', action='browse_library', cntrller='requests_admin', id=trans.security.encode_id( request.library.id ) )}">${rd['value']}</a> - %else: - <i>None</i> - %endif - %elif rd['label'] == 'State': + %if rd['label'] == 'State': <a href="${h.url_for( controller='requests_admin', action='list', operation='events', id=trans.security.encode_id(request.id) )}">${rd['value']}</a> %else: ${rd['value']} @@ -144,25 +280,27 @@ </ul> </div> </div> - %else: - <a href="${h.url_for( controller='requests_admin', action='toggle_request_details', request_id=request.id, details="show" )}">Show request details</a> - %endif + </div> + </div> </div> + <%def name="render_grid( grid_index, grid_name, fields_dict )"> + <div class="msg_list"> %if grid_name: - <div class="toolFormTitle">${grid_name}</div> + <h4 class="msg_head">${grid_name}</h4> + %else: + <h4>Grid ${grid_index}</h4> %endif - <div style="clear: both"></div> + %if edit_mode == 'False' or len(current_samples) <= len(request.samples): + <div class="msg_body"> + %else: + <div class="msg_body2"> + %endif <table class="grid"> <thead> <tr> - <th>No.</th> - <th>Sample Name</th> - %if grid_index == 0: - <th>Barcode</th> - <th>State</th> - %endif + <th>Name</th> %for index, field in fields_dict.items(): <th> ${field['label']} @@ -181,50 +319,36 @@ %for sample_index, sample in enumerate(current_samples): %if edit_mode == 'True': <tr> - <td>${sample_index+1}</td> - ${render_sample_form( sample_index, sample[0], sample[1], grid_index, fields_dict)} - <td> - %if request.unsubmitted() and grid_index == 0: - <a class="action-button" href="${h.url_for( controller='requests_admin', action='delete_sample', request_id=request.id, sample_id=sample_index)}"> - <img src="${h.url_for('/static/images/delete_icon.png')}" /> - <span></span></a> - %endif - </td> + ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)} </tr> %else: <tr> - <td>${sample_index+1}</td> %if sample_index in range(len(request.samples)): - ${render_sample( sample_index, request.samples[sample_index], grid_index, fields_dict )} + ${render_sample( sample_index, sample['name'], sample['field_values'], fields_dict )} %else: - ${render_sample_form( sample_index, sample[0], sample[1], grid_index, fields_dict)} + ${render_sample_form( sample_index, sample['name'], sample['field_values'], fields_dict)} %endif - <td> - %if grid_index == 0: - <a class="action-button" href="${h.url_for( controller='requests_admin', action='delete_sample', request_id=request.id, sample_id=sample_index)}"> - <img src="${h.url_for('/static/images/delete_icon.png')}" /> - <span></span></a> - %endif - </td> </tr> %endif %endfor </tbody> </table> + </div> + </div> </%def> <div class="toolForm"> ##<div class="toolFormTitle">Samples (${len(request.samples)})</div> <form id="show_request" name="show_request" action="${h.url_for( controller='requests_admin', action='show_request', edit_mode=edit_mode )}" enctype="multipart/form-data" method="post" > <div class="form-row"> - %if current_samples: - %if not request.type.sample_form.layout: - ${render_grid( 0, "", request.type.sample_form.fields_of_grid( None ) )} - %else: - %for grid_index, grid_name in enumerate(request.type.sample_form.layout): - ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_name ) )} - %endfor - %endif + %if current_samples: + ## first render the basic info grid + ${render_basic_info_grid()} + ## then render the other grid(s) + <% trans.sa_session.refresh( request.type.sample_form ) %> + %for grid_index, grid_name in enumerate(request.type.sample_form.layout): + ${render_grid( grid_index, grid_name, request.type.sample_form.fields_of_grid( grid_index ) )} + %endfor %else: <label>There are no samples.</label> %endif @@ -233,6 +357,7 @@ <table class="grid"> <tbody> <tr> + %if not request.complete(): <div class="form-row"> <td> %if current_samples and not request.complete(): @@ -254,6 +379,7 @@ </td> %endif </div> + %endif </tr> </tbody> </table> @@ -264,15 +390,18 @@ <input type="hidden" name="refresh" value="true" size="40"/> </div> <div style="clear: both"></div> - </div> - <div class="form-row"> - %if edit_mode == 'True': + </div> + %if edit_mode == 'True': + <div class="form-row"> <input type="submit" name="save_samples_button" value="Save"/> <input type="submit" name="cancel_changes_button" value="Cancel"/> - %elif request.unsubmitted(): + </div> + %elif request.unsubmitted(): + <div class="form-row"> <input type="submit" name="save_samples_button" value="Save"/> - %endif - </div> + </div> + %endif + %endif <input type="hidden" name="request_id" value="${request.id}" /> </form> diff -r 44fff02fb036 -r 37b55fad152b templates/admin/requests/view_request_type.mako --- a/templates/admin/requests/view_request_type.mako Thu Feb 04 10:44:36 2010 -0500 +++ b/templates/admin/requests/view_request_type.mako Thu Feb 04 11:01:45 2010 -0500 @@ -6,9 +6,11 @@ ${render_msg( msg, messagetype )} %endif +<h2>Request Type "${request_type.name}"</h2> + <div class="toolForm"> <div class="toolFormTitle">Request type information</div> - <form name="library"> + <form name="view_request_type" action="${h.url_for( controller='requests_admin', action='create_request_type', rt_id=request_type.id)}" method="post" > <div class="form-row"> <label>Name</label> ${request_type.name} @@ -32,12 +34,31 @@ ${request_type.sample_form.name} </div> <div class="toolFormTitle">Possible sample states</div> - %for element_count, state in enumerate(states_list): - <div class="form-row"> - <label>${1+element_count}. ${state.name}</label> - ${state.desc} - </div> - <div style="clear: both"></div> - %endfor + %for element_count, state in enumerate(states_list): + <div class="form-row"> + <label>${1+element_count}. ${state.name}</label> + ${state.desc} + </div> + <div style="clear: both"></div> + %endfor + <div class="toolFormTitle">Sequencer information</div> + <div class="form-row"> + This information is only needed for transferring data from sequencer to Galaxy + </div> + <div class="form-row"> + <label>Hostname or IP Address:</label> + <input type="text" name="host" value="${request_type.datatx_info['host']}" size="40"/> + </div>