# HG changeset patch -- Bitbucket.org # Project galaxy-dist # URL http://bitbucket.org/galaxy/galaxy-dist/overview # User Greg Von Kuster <greg@bx.psu.edu> # Date 1289503442 18000 # Node ID dfc848840870fa9936a69e62322446fcb940fb40 # Parent ca23ea683d26df004e666ede82950c712e3ac637 Various sample tracking bug fixes: samples no longer require bar codes, change all references to 'barcode' to be 'bar_code' since that is what the model uses, and mixing the 2 is not maintainable, enhance sample popup menu options, fix broken logic that handles bulk sample library and folderr changes. --- a/lib/galaxy/web/controllers/requests_common.py +++ b/lib/galaxy/web/controllers/requests_common.py @@ -725,7 +725,7 @@ class RequestsCommon( BaseController, Us search_type = params.get( 'search_type', '' ) request_states = util.listify( params.get( 'request_states', '' ) ) samples = [] - if search_type == 'barcode': + if search_type == 'bar_code': samples = trans.sa_session.query( trans.model.Sample ) \ .filter( and_( trans.model.Sample.table.c.deleted==False, func.lower( trans.model.Sample.table.c.bar_code ).like( "%" + search_string.lower() + "%" ) ) ) \ @@ -763,7 +763,7 @@ class RequestsCommon( BaseController, Us display='checkboxes' ) # Build the search_type SelectField selected_value = kwd.get( 'search_type', 'sample name' ) - types = [ 'sample name', 'barcode', 'dataset' ] + types = [ 'sample name', 'bar_code', 'dataset' ] search_type = build_select_field( trans, types, 'self', 'search_type', selected_value=selected_value, refresh_on_change=False ) # Build the search_box TextField search_box = TextField( 'search_box', 50, kwd.get('search_box', '' ) ) @@ -846,7 +846,7 @@ class RequestsCommon( BaseController, Us # Append the new sample to the current list of samples for the request displayable_sample_widgets.append( dict( id=None, name=name, - barcode='', + bar_code='', library=None, library_id=library_id, folder=None, @@ -999,7 +999,7 @@ class RequestsCommon( BaseController, Us **kwd ) displayable_sample_widgets.append( dict( id=None, name=row[0], - barcode='', + bar_code='', library=None, folder=None, library_select_field=library_select_field, @@ -1029,7 +1029,10 @@ class RequestsCommon( BaseController, Us editing_samples=False ) def __save_samples( self, trans, cntrller, request, samples, **kwd ): # Here we handle saving all new samples added by the user as well as saving - # changes to any subset of the request's samples. + # changes to any subset of the request's samples. A sample will not have an + # associated SampleState until the request is submitted, at which time the + # sample is automatically associated with the first SampleState configured by + # the admin for the request's RequestType. params = util.Params( kwd ) message = util.restore_text( params.get( 'message', '' ) ) status = params.get( 'status', 'done' ) @@ -1065,7 +1068,7 @@ class RequestsCommon( BaseController, Us # Send the encoded sample_ids to update_sample_state. # TODO: make changes necessary to just send the samples... encoded_selected_sample_ids = self.__get_encoded_selected_sample_ids( trans, request, **kwd ) - # Make sure all samples have a unique barcode if the state is changing + # Make sure all samples have a unique bar_code if the state is changing for sample_index in range( len( samples ) ): current_sample = samples[ sample_index ] if current_sample is None: @@ -1073,13 +1076,15 @@ class RequestsCommon( BaseController, Us # on which to perform the action. continue request_sample = request.samples[ sample_index ] - bc_message = self.__validate_barcode( trans, request_sample, current_sample[ 'barcode' ] ) - if bc_message: - #status = 'error' - message += bc_message - kwd[ 'message' ] = message - del kwd[ 'save_samples_button' ] - handle_error( **kwd ) + bar_code = current_sample[ 'bar_code' ] + if bar_code: + # If the sample has a new bar_code, make sure it is unique. + bc_message = self.__validate_bar_code( trans, request_sample, bar_code ) + if bc_message: + message += bc_message + kwd[ 'message' ] = message + del kwd[ 'save_samples_button' ] + handle_error( **kwd ) self.update_sample_state( trans, cntrller, encoded_selected_sample_ids, new_state, comment=sample_event_comment ) return trans.response.send_redirect( web.url_for( controller='requests_common', cntrller=cntrller, @@ -1088,18 +1093,22 @@ class RequestsCommon( BaseController, Us elif sample_operation == 'Select data library and folder': # TODO: fix the code so that the sample_operation_select_field does not use # sample_0_library_id as it's name. it should use something like sample_operation_library_id - # and sample_operation-folder_id because the name sample_0_library_id should belong to the + # and sample_operation_folder_id because the name sample_0_library_id should belong to the # first sample since all other form field values are named like this. The library and folder # are skewed to be named +1 resulting in the forced use of id_index everywhere... library_id = params.get( 'sample_0_library_id', 'none' ) folder_id = params.get( 'sample_0_folder_id', 'none' ) library, folder = self.__get_library_and_folder( trans, library_id, folder_id ) + for sample_index in range( len( samples ) ): + current_sample = samples[ sample_index ] + current_sample[ 'library' ] = library + current_sample[ 'folder' ] = folder self.__update_samples( trans, cntrller, request, samples, **kwd ) # Samples will not have an associated SampleState until the request is submitted, at which # time all samples of the request will be set to the first SampleState configured for the # request's RequestType defined by the admin. if request.is_submitted: - # See if all the samples' barcodes are in the same state, and if so send email if configured to. + # See if all the samples' bar_codes are in the same state, and if so send email if configured to. common_state = request.samples_have_common_state if common_state and common_state.id == request.type.states[1].id: comment = "All samples of this request are in the (%s) sample state. " % common_state.name @@ -1136,10 +1145,12 @@ class RequestsCommon( BaseController, Us status=status, message=message ) ) def __update_samples( self, trans, cntrller, request, sample_widgets, **kwd ): - # Determine if the values in kwd require updating the request's samples. The list of - # sample_widgets must have the same number of objects as request.samples, but some of - # the objects can be None. Those that are not None correspond to samples selected by - # the user for performing an action on multiple samples simultaneously. + # The list of sample_widgets must have the same number of objects as request.samples, + # but some of the objects can be None. Those that are not None correspond to samples + # selected by the user for performing an action on multiple samples simultaneously. + # The items in the sample_widgets list have already been populated with any changed + # param values (changed implies the value in kwd is different from the attribute value + # in the database) in kwd before this method is reached. def handle_error( **kwd ): kwd[ 'status' ] = 'error' return trans.response.send_redirect( web.url_for( controller='requests_common', @@ -1147,12 +1158,6 @@ class RequestsCommon( BaseController, Us cntrller=cntrller, **kwd ) ) params = util.Params( kwd ) - sample_operation = params.get( 'sample_operation', 'none' ) - if sample_operation != 'none': - # These values will be in kwd if the user checked 1 or more checkboxes for performing this action - # on a set of samples. - library_id = params.get( 'sample_0_library_id', 'none' ) - folder_id = params.get( 'sample_0_folder_id', 'none' ) for index, sample_widget in enumerate( sample_widgets ): if sample_widget is not None: # sample_widget will be None if the user checked sample check boxes and selected an action @@ -1161,21 +1166,16 @@ class RequestsCommon( BaseController, Us # Get the sample's form values to see if they have changed. form_values = trans.sa_session.query( trans.model.FormValues ).get( sample.values.id ) if sample.name != sample_widget[ 'name' ] or \ - sample.bar_code != sample_widget[ 'barcode' ] or \ + sample.bar_code != sample_widget[ 'bar_code' ] or \ sample.library != sample_widget[ 'library' ] or \ sample.folder != sample_widget[ 'folder' ] or \ form_values.content != sample_widget[ 'field_values' ]: # Information about this sample has been changed. sample.name = sample_widget[ 'name' ] - barcode = sample_widget[ 'barcode' ] - # The bar_code field requires special handling because after a request is submitted, the - # state of a sample cannot be changed without a bar_code associated with the sample. Bar - # codes can only be added to a sample after the request is submitted. Also, a samples will - # not have an associated SampleState until the request is submitted, at which time the sample - # is automatically associated with the first SamplesState configured by the admin for the - # request's RequestType. - if barcode: - bc_message = self.__validate_barcode( trans, sample, bar_code ) + bar_code = sample_widget[ 'bar_code' ] + # If the sample has a new bar_code, make sure it is unique. + if bar_code: + bc_message = self.__validate_bar_code( trans, sample, bar_code ) if bc_message: kwd[ 'message' ] = bc_message del kwd[ 'save_samples_button' ] @@ -1191,7 +1191,7 @@ class RequestsCommon( BaseController, Us 'Bar code associated with the sample' ) trans.sa_session.add( event ) trans.sa_session.flush() - sample.bar_code = barcode + sample.bar_code = bar_code sample.library = sample_widget[ 'library' ] sample.folder = sample_widget[ 'folder' ] form_values.content = sample_widget[ 'field_values' ] @@ -1281,7 +1281,7 @@ class RequestsCommon( BaseController, Us # Update the sample attributes from kwd sample_id = None name = util.restore_text( params.get( 'sample_%i_name' % index, sample.name ) ) - bar_code = util.restore_text( params.get( 'sample_%i_barcode' % index, sample.bar_code ) ) + bar_code = util.restore_text( params.get( 'sample_%i_bar_code' % index, sample.bar_code ) ) library_id = util.restore_text( params.get( 'sample_%i_library_id' % id_index, '' ) ) if not library_id and sample.library: library_id = trans.security.encode_id( sample.library.id ) @@ -1303,7 +1303,7 @@ class RequestsCommon( BaseController, Us **kwd ) sample_widgets.append( dict( id=sample_id, name=name, - barcode=bar_code, + bar_code=bar_code, library=library, folder=folder, field_values=field_values, @@ -1317,7 +1317,7 @@ class RequestsCommon( BaseController, Us if not name: break id_index = index + 1 - bar_code = util.restore_text( params.get( 'sample_%i_barcode' % index, '' ) ) + bar_code = util.restore_text( params.get( 'sample_%i_bar_code' % index, '' ) ) library_id = util.restore_text( params.get( 'sample_%i_library_id' % id_index, '' ) ) folder_id = util.restore_text( params.get( 'sample_%i_folder_id' % id_index, '' ) ) library, folder = self.__get_library_and_folder( trans, library_id, folder_id ) @@ -1334,7 +1334,7 @@ class RequestsCommon( BaseController, Us **kwd ) sample_widgets.append( dict( id=None, name=name, - barcode=bar_code, + bar_code=bar_code, library=library, folder=folder, field_values=field_values, @@ -1492,31 +1492,24 @@ class RequestsCommon( BaseController, Us action='edit_samples', cntrller=cntrller, **kwd ) ) - def __validate_barcode( self, trans, sample, barcode ): + def __validate_bar_code( self, trans, sample, bar_code ): """ - Makes sure that the barcode about to be assigned to a sample is globally unique. - That is, barcodes must be unique across requests in Galaxy sample tracking. + Make sure that the bar_code about to be assigned to a sample is globally unique. + That is, bar_codes must be unique across requests in Galaxy sample tracking. + Bar codes are not required, but if used, they can only be added to a sample after + the request is submitted. """ message = '' unique = True for index in range( len( sample.request.samples ) ): - # Check for empty bar code - if not barcode.strip(): - if sample.state.id == sample.request.type.states[0].id: - # The user has not yet filled in the barcode value, but the sample is - # 'new', so all is well. - break - else: - message = "Fill in the barcode for sample (%s) before changing it's state." % sample.name - break # TODO: Add a unique constraint to sample.bar_code table column # Make sure bar code is unique - for sample_with_barcode in trans.sa_session.query( trans.model.Sample ) \ - .filter( trans.model.Sample.table.c.bar_code == barcode ): - if sample_with_barcode and sample_with_barcode.id != sample.id: + for sample_with_bar_code in trans.sa_session.query( trans.model.Sample ) \ + .filter( trans.model.Sample.table.c.bar_code == bar_code ): + if sample_with_bar_code and sample_with_bar_code.id != sample.id: message = '''The bar code (%s) associated with the sample (%s) belongs to another sample. Bar codes must be unique across all samples, so use a different bar code - for this sample.''' % ( barcode, sample.name ) + for this sample.''' % ( bar_code, sample.name ) unique = False break if not unique: --- a/templates/requests/common/common.mako +++ b/templates/requests/common/common.mako @@ -122,7 +122,7 @@ is_rejected = request.is_rejected is_submitted = sample.request.is_submitted is_unsubmitted = sample.request.is_unsubmitted - can_delete_samples = not is_complete + can_delete_samples = editing_samples and request.samples and ( ( is_admin and not is_complete ) or is_unsubmitted ) display_checkboxes = editing_samples and ( is_complete or is_rejected or is_submitted ) display_bar_code = request.samples and ( is_complete or is_rejected or is_submitted ) display_datasets = request.samples and ( is_complete or is_submitted ) @@ -153,10 +153,10 @@ %if display_bar_code: <td valign="top"> %if is_admin: - <input type="text" name="sample_${sample_widget_index}_barcode" value="${sample_widget['barcode']}" size="10"/> + <input type="text" name="sample_${sample_widget_index}_bar_code" value="${sample_widget['bar_code']}" size="10"/> %else: - ${sample_widget['barcode']} - <input type="hidden" name="sample_${sample_widget_index}_barcode" value="${sample_widget['barcode']}"/> + ${sample_widget['bar_code']} + <input type="hidden" name="sample_${sample_widget_index}_bar_code" value="${sample_widget['bar_code']}"/> %endif </td> %endif @@ -234,7 +234,7 @@ is_submitted = request.is_submitted is_unsubmitted = request.is_unsubmitted can_add_samples = request.is_unsubmitted - can_delete_samples = editing_samples and request.samples and not is_complete + can_delete_samples = editing_samples and request.samples and ( ( is_admin and not is_complete ) or is_unsubmitted ) can_edit_samples = request.samples and ( is_admin or not is_complete ) can_select_datasets = is_admin and displayable_sample_widgets and ( is_submitted or is_complete ) can_transfer_datasets = is_admin and request.samples and not request.is_rejected @@ -280,14 +280,14 @@ <tbody><% trans.sa_session.refresh( request ) %> ## displayable_sample_widgets is a dictionary whose keys are: - ## id, name, barcode, library, folder, field_values, library_select_field, folder_select_field + ## id, name, bar_code, library, folder, field_values, library_select_field, folder_select_field ## A displayable_sample_widget will have an id == None if the widget's associated sample has not ## yet been saved (i.e., the use clicked the "Add sample" button but has not yet clicked the ## "Save" button. %for sample_widget_index, sample_widget in enumerate( displayable_sample_widgets ): <% sample_widget_name = sample_widget[ 'name' ] - sample_widget_barcode = sample_widget[ 'barcode' ] + sample_widget_bar_code = sample_widget[ 'bar_code' ] sample_widget_library = sample_widget[ 'library' ] if sample_widget_library: if cntrller == 'requests': @@ -309,7 +309,12 @@ <td> %if sample.state and ( can_select_datasets or can_transfer_datasets ): ## A sample will have a state only after the request has been submitted. - <% encoded_id = trans.security.encode_id( sample.id ) %> + <% + encoded_id = trans.security.encode_id( sample.id ) + transferred_dataset_files = sample.transferred_dataset_files + if not transferred_dataset_files: + transferred_dataset_files = [] + %><div style="float: left; margin-left: 2px;" class="menubutton split popup" id="sample-${sample.id}-popup"> ${sample.name} </div> @@ -317,8 +322,10 @@ %if can_select_datasets: <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='select_datasets_to_transfer', request_id=trans.security.encode_id( request.id ), sample_id=trans.security.encode_id( sample.id ) )}">Select datasets to transfer</a></li> %endif - %if sample.untransferred_dataset_files: - <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=trans.security.encode_id( sample.id ) )}">Transfer datasets</a></li> + %if sample.datasets and len( sample.datasets ) > len( transferred_dataset_files ): + <li><a class="action-button" href="${h.url_for( controller='requests_admin', action='manage_datasets', sample_id=trans.security.encode_id( sample.id ) )}">Manage selected datasets</a></li> + %elif sample.datasets and len(sample.datasets ) == len( transferred_dataset_files ): + <li><a class="action-button" href="${h.url_for( controller='requests_common', action='view_sample_datasets', cntrller=cntrller, sample_id=trans.security.encode_id( sample.id ), transfer_status=trans.model.SampleDataset.transfer_status.COMPLETE )}">View transferred datasets</a></li> %endif </div> %else: @@ -326,7 +333,7 @@ %endif </td> %if display_bar_code: - <td>${sample_widget_barcode}</td> + <td>${sample_widget_bar_code}</td> %endif %if is_unsubmitted: <td>Unsubmitted</td> @@ -361,9 +368,8 @@ <td> %if is_admin: <% - if sample.transferred_dataset_files: - transferred_dataset_files = sample.transferred_dataset_files - else: + transferred_dataset_files = sample.transferred_dataset_files + if not transferred_dataset_files: transferred_dataset_files = [] %> %if not sample.datasets: --- a/test/base/twilltestcase.py +++ b/test/base/twilltestcase.py @@ -1575,7 +1575,7 @@ class TwillTestCase( unittest.TestCase ) for index, field_value in enumerate( bar_codes ): sample_field_name = "sample_%i_name" % index sample_field_value = samples[ index ].name.replace( ' ', '+' ) - field_name = "sample_%i_barcode" % index + field_name = "sample_%i_bar_code" % index url += "&%s=%s" % ( field_name, field_value ) url += "&%s=%s" % ( sample_field_name, sample_field_value ) url += "&save_samples_button=Save"