PageRenderTime 91ms CodeModel.GetById 16ms app.highlight 65ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/galaxy/web/controllers/requests_common.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 1051 lines | 1020 code | 5 blank | 26 comment | 59 complexity | fd55c095c7a089f72b1fd178d0f0b56f MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1from galaxy.web.base.controller import *
  2from galaxy.web.framework.helpers import time_ago, iff, grids
  3from galaxy.model.orm import *
  4from galaxy import model, util
  5from galaxy.util.odict import odict
  6from galaxy.web.form_builder import *
  7from galaxy.security.validate_user_input import validate_email
  8import logging, os, csv
  9
 10log = logging.getLogger( __name__ )
 11
 12class RequestsGrid( grids.Grid ):
 13    # Custom column types
 14    class NameColumn( grids.TextColumn ):
 15        def get_value( self, trans, grid, request ):
 16            return request.name
 17    class DescriptionColumn( grids.TextColumn ):
 18        def get_value(self, trans, grid, request):
 19            return request.desc
 20    class SamplesColumn( grids.GridColumn ):
 21        def get_value(self, trans, grid, request):
 22            return str( len( request.samples ) )
 23    class TypeColumn( grids.TextColumn ):
 24        def get_value( self, trans, grid, request ):
 25            return request.type.name
 26    class StateColumn( grids.StateColumn ):
 27        def get_value(self, trans, grid, request ):
 28            state = request.state
 29            if state == request.states.REJECTED:
 30                state_color = 'error'
 31            elif state == request.states.NEW:
 32                state_color = 'new'
 33            elif state == request.states.SUBMITTED:
 34                state_color = 'running'
 35            elif state == request.states.COMPLETE:
 36                state_color = 'ok'
 37            else:
 38                state_color = state
 39            return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state )
 40        def filter( self, trans, user, query, column_filter ):
 41            """ Modify query to filter request by state. """
 42            if column_filter == "All":
 43                return query
 44            if column_filter:
 45                return query.join( model.RequestEvent.table ) \
 46                            .filter( self.model_class.table.c.id == model.RequestEvent.table.c.request_id ) \
 47                            .filter( model.RequestEvent.table.c.state == column_filter ) \
 48                            .filter( model.RequestEvent.table.c.id.in_( select( columns=[ func.max( model.RequestEvent.table.c.id ) ],
 49                                                                                from_obj=model.RequestEvent.table,
 50                                                                                group_by=model.RequestEvent.table.c.request_id ) ) )
 51        
 52    # Grid definition
 53    title = "Sequencing Requests"
 54    template = "requests/grid.mako"
 55    model_class = model.Request
 56    default_sort_key = "-update_time"
 57    num_rows_per_page = 50
 58    use_paging = True
 59    default_filter = dict( state="All", deleted="False" )
 60    columns = [
 61        NameColumn( "Name", 
 62                    key="name", 
 63                    link=( lambda item: dict( operation="view_request", id=item.id ) ),
 64                    attach_popup=True, 
 65                    filterable="advanced" ),
 66        DescriptionColumn( "Description",
 67                           key='desc',
 68                           filterable="advanced" ),
 69        SamplesColumn( "Samples", 
 70                       link=( lambda item: iff( item.deleted, None, dict( operation="edit_samples", id=item.id ) ) ) ),
 71        TypeColumn( "Type",
 72                    link=( lambda item: iff( item.deleted, None, dict( operation="view_type", id=item.type.id ) ) ) ),
 73        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
 74        grids.DeletedColumn( "Deleted", 
 75                             key="deleted", 
 76                             visible=False, 
 77                             filterable="advanced" ),
 78        StateColumn( "State", 
 79                     key='state',
 80                     filterable="advanced",
 81                     link=( lambda item: iff( item.deleted, None, dict( operation="view_request_history", id=item.id ) ) )
 82                   )
 83    ]
 84    columns.append( grids.MulticolFilterColumn( "Search", 
 85                                                cols_to_filter=[ columns[0], columns[1] ], 
 86                                                key="free-text-search",
 87                                                visible=False,
 88                                                filterable="standard" ) )
 89    operations = [
 90        grids.GridOperation( "Submit",
 91                             allow_multiple=False,
 92                             condition=( lambda item: not item.deleted and item.is_unsubmitted and item.samples ),
 93                             confirm="Samples cannot be added to this request after it is submitted. Click OK to submit."  )
 94        ]
 95
 96class RequestsCommon( BaseUIController, UsesFormDefinitions ):
 97    @web.json
 98    def sample_state_updates( self, trans, ids=None, states=None ):
 99        # Avoid caching
100        trans.response.headers['Pragma'] = 'no-cache'
101        trans.response.headers['Expires'] = '0'
102        # Create new HTML for any that have changed
103        rval = {}
104        if ids is not None and states is not None:
105            ids = map( int, ids.split( "," ) )
106            states = states.split( "," )
107            for id, state in zip( ids, states ):
108                sample = trans.sa_session.query( self.app.model.Sample ).get( id )
109                if sample.state.name != state:
110                    rval[ id ] = { "state": sample.state.name,
111                                   "html_state": unicode( trans.fill_template( "requests/common/sample_state.mako",
112                                                                               sample=sample),
113                                                                               'utf-8' ) }
114        return rval
115    @web.json
116    def sample_datasets_updates( self, trans, ids=None, datasets=None ):
117        # Avoid caching
118        trans.response.headers['Pragma'] = 'no-cache'
119        trans.response.headers['Expires'] = '0'
120        # Create new HTML for any that have changed
121        rval = {}
122        if ids is not None and datasets is not None:
123            ids = map( int, ids.split( "," ) )
124            number_of_datasets_list = map(int, datasets.split( "," ) )
125            for id, number_of_datasets in zip( ids, number_of_datasets_list ):
126                sample = trans.sa_session.query( self.app.model.Sample ).get( id )
127                if len(sample.datasets) != number_of_datasets:
128                    rval[ id ] = { "datasets": len( sample.datasets ),
129                                   "html_datasets": unicode( trans.fill_template( "requests/common/sample_datasets.mako",
130                                                                                  sample=sample),
131                                                                                  'utf-8' ) }
132        return rval
133    @web.json
134    def dataset_transfer_status_updates( self, trans, ids=None, transfer_status_list=None ):
135        # Avoid caching
136        trans.response.headers['Pragma'] = 'no-cache'
137        trans.response.headers['Expires'] = '0'
138        # Create new HTML for any that have changed
139        rval = {}
140        if ids is not None and transfer_status_list is not None:
141            ids = ids.split( "," )
142            transfer_status_list = transfer_status_list.split( "," )
143            for id, transfer_status in zip( ids, transfer_status_list ):
144                sample_dataset = trans.sa_session.query( self.app.model.SampleDataset ).get( trans.security.decode_id( id ) )
145                if sample_dataset.status != transfer_status:
146                    rval[ id ] = { "status": sample_dataset.status,
147                                   "html_status": unicode( trans.fill_template( "requests/common/sample_dataset_transfer_status.mako",
148                                                                                sample_dataset=sample_dataset),
149                                                                                'utf-8' ) }
150        return rval
151    @web.expose
152    @web.require_login( "create sequencing requests" )
153    def create_request( self, trans, cntrller, **kwd ):
154        params = util.Params( kwd )
155        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
156        message = util.restore_text( params.get( 'message', '' ) )
157        status = params.get( 'status', 'done' )
158        request_type_id = params.get( 'request_type_id', 'none' )
159        if request_type_id != 'none':
160            request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) )
161        else:
162            request_type = None
163        # user_id will not be 'none' if an admin user is submitting this request on behalf of another user
164        # and they selected that user's id from the user_id SelectField.
165        user_id_encoded = True
166        user_id = params.get( 'user_id', 'none' )
167        if user_id != 'none':
168            try:
169                user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
170            except TypeError, e:
171                # We must have an email address rather than an encoded user id
172                # This is because the galaxy.base.js creates a search+select box 
173                # when there are more than 20 items in a SelectField.
174                user = trans.sa_session.query( trans.model.User ) \
175                                       .filter( trans.model.User.table.c.email==util.restore_text( user_id ) ) \
176                                       .first()
177                user_id_encoded = False
178                
179        elif not is_admin:
180            user = trans.user
181        else:
182            user = None
183        if params.get( 'create_request_button', False ) or params.get( 'add_sample_button', False ):
184            name = util.restore_text( params.get( 'name', '' ) )
185            if is_admin and user_id == 'none':
186                message = 'Select the user on behalf of whom you are submitting this request.'
187                status = 'error'
188            elif user is None:
189                message = 'Invalid user ID (%s)' % str(user_id)
190                status = 'error'
191            # when creating a request from the user perspective, check if the 
192            # user has access permission to this request_type 
193            elif cntrller == 'requests' and not trans.app.security_agent.can_access_request_type( user.all_roles(), request_type ):
194                message = '%s does not have access permission to the "%s" request type.' % ( user.email, request_type.name )
195                status = 'error'
196            elif not name:
197                message = 'Enter the name of the request.'
198                status = 'error'
199            else:
200                request = self.__save_request( trans, cntrller, **kwd )
201                message = 'The sequencing request has been created.'
202                if params.get( 'create_request_button', False ):
203                    return trans.response.send_redirect( web.url_for( controller=cntrller,
204                                                                      action='browse_requests',
205                                                                      message=message ,
206                                                                      status='done' ) )
207                elif params.get( 'add_sample_button', False ):
208                    request_id = trans.security.encode_id( request.id )
209                    return self.add_sample( trans, cntrller, request_id, **kwd )
210        request_type_select_field = self.__build_request_type_id_select_field( trans, selected_value=request_type_id )
211        # Widgets to be rendered on the request form
212        widgets = []
213        if request_type is not None or status == 'error':
214            # Either the user selected a request_type or an error exists on the form.
215            widgets.append( dict( label='Name of the Experiment', 
216                                  widget=TextField( 'name', 40, util.restore_text( params.get( 'name', ''  ) ) ), 
217                                  helptext='(Required)') )
218            widgets.append( dict( label='Description', 
219                                  widget=TextField( 'desc', 40, util.restore_text( params.get( 'desc', ''  ) )), 
220                                  helptext='(Optional)') )
221            if request_type is not None:
222                widgets += request_type.request_form.get_widgets( user, **kwd )
223        # In case there is an error on the form, make sure to populate widget fields with anything the user
224        # may have already entered.
225        widgets = self.populate_widgets_from_kwd( trans, widgets, **kwd )
226        if request_type is not None or status == 'error':
227            # Either the user selected a request_type or an error exists on the form.
228            if is_admin:
229                if not user_id_encoded and user:
230                    selected_user_id = trans.security.encode_id( user.id )
231                else:
232                    selected_user_id = user_id
233                user_widget = dict( label='Select user',
234                                    widget=self.__build_user_id_select_field( trans, selected_value=selected_user_id ),
235                                    helptext='Submit the request on behalf of the selected user (Required)')
236                widgets = [ user_widget ] + widgets
237        return trans.fill_template( '/requests/common/create_request.mako',
238                                    cntrller=cntrller,
239                                    request_type_select_field=request_type_select_field,
240                                    request_type_select_field_selected=request_type_id,                               
241                                    widgets=widgets,
242                                    message=message,
243                                    status=status )
244    @web.expose
245    @web.require_login( "view request" )
246    def view_request( self, trans, cntrller, **kwd ):
247        params = util.Params( kwd )
248        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
249        message = util.restore_text( params.get( 'message', ''  ) )
250        status = params.get( 'status', 'done' )
251        request_id = params.get( 'id', None )
252        try:
253            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
254        except:
255            return invalid_id_redirect( trans, cntrller, request_id )
256        sample_state_id = params.get( 'sample_state_id', None )
257        # Build a list of sample widgets (based on the attributes of each sample) for display.
258        displayable_sample_widgets = self.__get_sample_widgets( trans, request, request.samples, **kwd )
259        request_widgets = self.__get_request_widgets( trans, request.id )
260        return trans.fill_template( '/requests/common/view_request.mako',
261                                    cntrller=cntrller, 
262                                    request=request,
263                                    request_widgets=request_widgets,
264                                    displayable_sample_widgets=displayable_sample_widgets,
265                                    status=status,
266                                    message=message )
267    @web.expose
268    @web.require_login( "edit sequencing requests" )
269    def edit_basic_request_info( self, trans, cntrller, **kwd ):
270        params = util.Params( kwd )
271        message = util.restore_text( params.get( 'message', ''  ) )
272        status = params.get( 'status', 'done' )
273        request_id = params.get( 'id', None )
274        try:
275            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
276        except:
277            return invalid_id_redirect( trans, cntrller, request_id )
278        name = util.restore_text( params.get( 'name', '' ) )
279        desc = util.restore_text( params.get( 'desc', ''  ) )
280        if params.get( 'edit_basic_request_info_button', False ):
281            if not name:
282                status = 'error'
283                message = 'Enter the name of the request'
284            else:
285                request = self.__save_request( trans, cntrller, request=request, **kwd )
286                message = 'The changes made to request (%s) have been saved.' % request.name
287        # Widgets to be rendered on the request form
288        widgets = []
289        widgets.append( dict( label='Name', 
290                              widget=TextField( 'name', 40, request.name ), 
291                              helptext='(Required)' ) )
292        widgets.append( dict( label='Description', 
293                              widget=TextField( 'desc', 40, request.desc ), 
294                              helptext='(Optional)' ) )
295        widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd )
296        # In case there is an error on the form, make sure to populate widget fields with anything the user
297        # may have already entered.
298        widgets = self.populate_widgets_from_kwd( trans, widgets, **kwd )
299        return trans.fill_template( 'requests/common/edit_basic_request_info.mako',
300                                    cntrller=cntrller,
301                                    request_type=request.type,
302                                    request=request,
303                                    widgets=widgets,
304                                    message=message,
305                                    status=status )
306    def __save_request( self, trans, cntrller, request=None, **kwd ):
307        """
308        Saves changes to an existing request, or creates a new 
309        request if received request is None.
310        """
311        params = util.Params( kwd )
312        request_type_id = params.get( 'request_type_id', None )
313        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
314        if request is None:
315            # We're creating a new request, so we need the associated request_type
316            request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) )
317            if is_admin:
318                # The admin user is creating a request on behalf of another user
319                user_id = params.get( 'user_id', '' )
320                user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
321            else:
322                user = trans.user
323        else:
324            # We're saving changes to an existing request
325            user = request.user
326            request_type = request.type
327        name = util.restore_text( params.get( 'name', '' ) )
328        desc = util.restore_text( params.get( 'desc', '' ) )
329        notification = dict( email=[ user.email ], sample_states=[ request_type.final_sample_state.id ], body='', subject='' )
330        values = self.get_form_values( trans, user, request_type.request_form, **kwd )
331        if request is None:
332            form_values = trans.model.FormValues( request_type.request_form, values )
333            trans.sa_session.add( form_values )
334            # We're creating a new request
335            request = trans.model.Request( name, desc, request_type, user, form_values, notification )
336            trans.sa_session.add( request )
337            trans.sa_session.flush()
338            trans.sa_session.refresh( request )
339            # Create an event with state 'New' for this new request
340            comment = "Sequencing request created by %s" % trans.user.email
341            if request.user != trans.user:
342                comment += " on behalf of %s." % request.user.email
343            event = trans.model.RequestEvent( request, request.states.NEW, comment )
344            trans.sa_session.add( event )
345            trans.sa_session.flush()
346        else:
347            # We're saving changes to an existing request
348            request.name = name
349            request.desc = desc
350            request.type = request_type
351            request.user = user
352            request.notification = notification
353            request.values.content = values
354            trans.sa_session.add( request )
355            trans.sa_session.add( request.values )
356            trans.sa_session.flush()
357        return request
358    @web.expose
359    @web.require_login( "submit sequencing requests" )
360    def submit_request( self, trans, cntrller, **kwd ):
361        params = util.Params( kwd )
362        request_id = params.get( 'id', None )
363        message = util.restore_text( params.get( 'message', '' ) )
364        status = util.restore_text( params.get( 'status', 'done' ) )
365        try:
366            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
367        except:
368            return invalid_id_redirect( trans, cntrller, request_id )
369        ok = True
370        if not request.samples:
371            message = 'Add at least 1 sample to this request before submitting.'
372            ok = False
373        if ok:
374            message = self.__validate_request( trans, cntrller, request )
375        if message or not ok:
376            return trans.response.send_redirect( web.url_for( controller='requests_common',
377                                                              action='edit_basic_request_info',
378                                                              cntrller=cntrller,
379                                                              id = request_id,
380                                                              status='error',
381                                                              message=message ) )
382        # Change the request state to 'Submitted'
383        comment = "Sequencing request submitted by %s" % trans.user.email
384        if request.user != trans.user:
385            comment += " on behalf of %s." % request.user.email
386        event = trans.model.RequestEvent( request, request.states.SUBMITTED, comment )
387        trans.sa_session.add( event )
388        # Change the state of each of the samples of this request
389        # request.type.states is the list of SampleState objects configured
390        # by the admin for this RequestType.
391        trans.sa_session.add( event )
392        trans.sa_session.flush()
393        # Samples will not have an associated SampleState until the request is submitted, at which
394        # time all samples of the request will be set to the first SampleState configured for the
395        # request's RequestType configured by the admin.
396        initial_sample_state_after_request_submitted = request.type.states[0]
397        for sample in request.samples:
398            event_comment = 'Sequencing request submitted and sample state set to %s.' % request.type.states[0].name
399            event = trans.model.SampleEvent( sample,
400                                             initial_sample_state_after_request_submitted,
401                                             event_comment )
402            trans.sa_session.add( event )
403        trans.sa_session.add( request )
404        trans.sa_session.flush()
405        request.send_email_notification( trans, initial_sample_state_after_request_submitted )
406        message = 'The sequencing request has been submitted.'
407        # show the request page after submitting the request 
408        return trans.response.send_redirect( web.url_for( controller='requests_common',
409                                                          action='view_request',
410                                                          cntrller=cntrller,
411                                                          id=request_id,
412                                                          status=status,
413                                                          message=message ) )
414    @web.expose
415    @web.require_login( "edit samples" )
416    def edit_samples( self, trans, cntrller, **kwd ):
417        params = util.Params( kwd )
418        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
419        message = util.restore_text( params.get( 'message', ''  ) )
420        status = params.get( 'status', 'done' )
421        request_id = params.get( 'id', None )
422        try:
423            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
424        except:
425            return invalid_id_redirect( trans, cntrller, request_id )
426        if params.get( 'cancel_changes_button', False ):
427            return trans.response.send_redirect( web.url_for( controller='requests_common',
428                                                                          action='edit_samples',
429                                                                          cntrller=cntrller,
430                                                                          id=request_id ) )
431        libraries = trans.app.security_agent.get_accessible_libraries( trans, request.user )
432        # Build a list of sample widgets (based on the attributes of each sample) for display.
433        displayable_sample_widgets = self.__get_sample_widgets( trans, request, request.samples, **kwd )
434        encoded_selected_sample_ids = self.__get_encoded_selected_sample_ids( trans, request, **kwd )
435        sample_operation = params.get( 'sample_operation', 'none' )
436        def handle_error( **kwd ):
437            kwd[ 'status' ] = 'error'
438            return trans.response.send_redirect( web.url_for( controller='requests_common',
439                                                              action='edit_samples',
440                                                              cntrller=cntrller,
441                                                              **kwd ) )
442        if not encoded_selected_sample_ids and sample_operation != 'none':
443            # Probably occurred due to refresh_on_change...is there a better approach?
444            kwd[ 'sample_operation' ] = 'none'
445            message = 'Select at least one sample before selecting an operation.'
446            kwd[ 'message' ] = message
447            handle_error( **kwd )
448        if params.get( 'save_samples_button', False ):
449            if encoded_selected_sample_ids:
450                # We need the list of displayable_sample_widgets to include the same number
451                # of objects that that request.samples has so that we can enumerate over each
452                # list without problems.  We have to be careful here since the user may have
453                # used the multi-select check boxes when editing sample widgets, but didn't
454                # select all of them.  We'll first get the set of samples corresponding to the
455                # checked sample ids.
456                samples = []
457                selected_samples = []
458                for encoded_sample_id in encoded_selected_sample_ids:
459                    sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( encoded_sample_id ) )
460                    selected_samples.append( sample )
461                # Now build the list of samples, inserting None for samples that have not been checked.
462                for sample in request.samples:
463                    if sample in selected_samples:
464                        samples.append( sample )
465                    else:
466                        samples.append( None )
467                # The __save_samples method requires sample_widgets, not sample objects, so we'll get what we
468                # need by calling __get_sample_widgets().  However, we need to take care here because __get_sample_widgets()
469                # is used to populate the sample widget dicts from kwd, and the method assumes that a None object in the 
470                # received list of samples should be populated from the db.  Since we're just re-using the method here to
471                # change our list of samples into a list of sample widgets, we'll need to make sure to keep track of our
472                # None objects.
473                sample_widgets = [ obj for obj in samples ]
474                sample_widgets = self.__get_sample_widgets( trans, request, sample_widgets, **kwd )
475                # Replace each sample widget dict with a None object if necessary
476                for index, obj in enumerate( samples ):
477                    if obj is None:
478                        sample_widgets[ index ] = None
479            else:
480                sample_widgets = displayable_sample_widgets
481            return self.__save_samples( trans, cntrller, request, sample_widgets, saving_new_samples=False, **kwd )
482        request_widgets = self.__get_request_widgets( trans, request.id )
483        sample_copy_select_field = self.__build_copy_sample_select_field( trans, displayable_sample_widgets )
484        libraries_select_field, folders_select_field = self.__build_library_and_folder_select_fields( trans,
485                                                                                                      request.user,
486                                                                                                      'sample_operation',
487                                                                                                      libraries,
488                                                                                                      None,
489                                                                                                      **kwd )
490        sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, sample_operation )
491        sample_state_id = params.get( 'sample_state_id', None )
492        sample_state_id_select_field = self.__build_sample_state_id_select_field( trans, request, sample_state_id )
493        return trans.fill_template( '/requests/common/edit_samples.mako',
494                                    cntrller=cntrller, 
495                                    request=request,
496                                    encoded_selected_sample_ids=encoded_selected_sample_ids,
497                                    request_widgets=request_widgets,
498                                    displayable_sample_widgets=displayable_sample_widgets,
499                                    sample_copy_select_field=sample_copy_select_field, 
500                                    libraries=libraries,
501                                    sample_operation_select_field=sample_operation_select_field,
502                                    libraries_select_field=libraries_select_field,
503                                    folders_select_field=folders_select_field,
504                                    sample_state_id_select_field=sample_state_id_select_field,
505                                    status=status,
506                                    message=message )
507    @web.expose
508    def update_sample_state(self, trans, cntrller, sample_ids, new_state, comment=None ):
509        for sample_id in sample_ids:
510            try:
511                sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
512            except:
513                if cntrller == 'api':
514                    trans.response.status = 400
515                    return "Invalid sample id ( %s ) specified, unable to decode." % str( sample_id )
516                else:
517                    return invalid_id_redirect( trans, cntrller, sample_id, 'sample' )
518            if comment is None:
519                comment = 'Sample state set to %s' % str( new_state )
520            event = trans.model.SampleEvent( sample, new_state, comment )
521            trans.sa_session.add( event )
522            trans.sa_session.flush()
523        if cntrller == 'api':
524            return 200, 'Done'        
525    @web.expose
526    @web.require_login( "delete sequencing requests" )
527    def delete_request( self, trans, cntrller, **kwd ):
528        params = util.Params( kwd )
529        id_list = util.listify( kwd.get( 'id', '' ) )
530        message = util.restore_text( params.get( 'message', '' ) )
531        status = util.restore_text( params.get( 'status', 'done' ) )
532        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
533        num_deleted = 0
534        not_deleted = []
535        for id in id_list:
536            ok_for_now = True
537            try:
538                # This block will handle bots that do not send valid request ids.
539                request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) )
540            except:
541                ok_for_now = False
542            if ok_for_now:
543                # We will only allow the request to be deleted by a non-admin user if not request.submitted
544                if is_admin or not request.is_submitted:
545                    request.deleted = True
546                    trans.sa_session.add( request )
547                    # Delete all the samples belonging to this request
548                    for s in request.samples:
549                        s.deleted = True
550                        trans.sa_session.add( s )
551                    comment = "Sequencing request marked deleted by %s." % trans.user.email
552                    # There is no DELETED state for a request, so keep the current request state
553                    event = trans.model.RequestEvent( request, request.state, comment )
554                    trans.sa_session.add( event )
555                    trans.sa_session.flush()
556                    num_deleted += 1
557                else:
558                    not_deleted.append( request )
559        message += '%i requests have been deleted.' % num_deleted
560        if not_deleted:
561            message += '  Contact the administrator to delete the following submitted requests: '
562            for request in not_deleted:
563                message += '%s, ' % request.name
564            message = message.rstrip( ', ' )
565        return trans.response.send_redirect( web.url_for( controller=cntrller,
566                                                          action='browse_requests',
567                                                          status=status,
568                                                          message=message ) )
569    @web.expose
570    @web.require_login( "undelete sequencing requests" )
571    def undelete_request( self, trans, cntrller, **kwd ):
572        params = util.Params( kwd )
573        id_list = util.listify( kwd.get( 'id', '' ) )
574        message = util.restore_text( params.get( 'message', '' ) )
575        status = util.restore_text( params.get( 'status', 'done' ) )
576        num_undeleted = 0
577        for id in id_list:
578            ok_for_now = True
579            try:
580                # This block will handle bots that do not send valid request ids.
581                request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) )
582            except:
583                ok_for_now = False
584            if ok_for_now:
585                request.deleted = False
586                trans.sa_session.add( request )
587                # Undelete all the samples belonging to this request
588                for s in request.samples:
589                    s.deleted = False
590                    trans.sa_session.add( s )
591                comment = "Sequencing request marked undeleted by %s." % trans.user.email
592                event = trans.model.RequestEvent( request, request.state, comment )
593                trans.sa_session.add( event )
594                trans.sa_session.flush()
595                num_undeleted += 1
596        message += '%i requests have been undeleted.' % num_undeleted
597        return trans.response.send_redirect( web.url_for( controller=cntrller,
598                                                          action='browse_requests',
599                                                          status=status,
600                                                          message=message ) )
601    @web.expose
602    @web.require_login( "sequencing request history" )
603    def view_request_history( self, trans, cntrller, **kwd ):
604        params = util.Params( kwd )
605        request_id = params.get( 'id', None )
606        try:
607            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
608        except:
609            return invalid_id_redirect( trans, cntrller, request_id )
610        return trans.fill_template( '/requests/common/view_request_history.mako', 
611                                    cntrller=cntrller,
612                                    request=request )
613    @web.expose
614    @web.require_login( "edit email notification settings" )
615    def edit_email_settings( self, trans, cntrller, **kwd ):
616        """
617        Allow for changing the email notification settings where email is sent to a list of users
618        whenever the request state changes to one selected for notification.
619        """
620        params = util.Params( kwd )
621        message = util.restore_text( params.get( 'message', ''  ) )
622        status = params.get( 'status', 'done' )
623        request_id = params.get( 'id', None )
624        try:
625            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
626        except:
627            return invalid_id_redirect( trans, cntrller, request_id )
628        email_address = CheckboxField.is_checked( params.get( 'email_address', '' ) )
629        additional_email_addresses = params.get( 'additional_email_addresses', '' )
630        # Get the list of checked sample state CheckBoxFields
631        checked_sample_states = []
632        for index, sample_state in enumerate( request.type.states ):
633            if CheckboxField.is_checked( params.get( 'sample_state_%i' % sample_state.id, '' ) ):
634                checked_sample_states.append( sample_state.id )
635        if additional_email_addresses:
636            additional_email_addresses = additional_email_addresses.split( '\r\n' )
637        if email_address or additional_email_addresses:
638            # The user added 1 or more email addresses
639            email_addresses = []
640            if email_address:
641                email_addresses.append( request.user.email )
642            for email_address in additional_email_addresses:
643                email_addresses.append( util.restore_text( email_address ) )
644            # Make sure email addresses are valid
645            err_msg = ''
646            for email_address in email_addresses:
647                err_msg += validate_email( trans, email_address, check_dup=False )
648            if err_msg:
649                status = 'error'
650                message += err_msg
651            else:
652                request.notification = dict( email=email_addresses,
653                                             sample_states=checked_sample_states, 
654                                             body='',
655                                             subject='' )
656        else:
657            # The user may have eliminated email addresses that were previously set
658            request.notification = None
659            if checked_sample_states:
660                message = 'All sample states have been unchecked since no email addresses have been selected or entered.  '
661        trans.sa_session.add( request )
662        trans.sa_session.flush()
663        trans.sa_session.refresh( request )
664        message += 'The changes made to the email notification settings have been saved.'
665        return trans.response.send_redirect( web.url_for( controller='requests_common',
666                                                          action='edit_basic_request_info',
667                                                          cntrller=cntrller,
668                                                          id=request_id,
669                                                          message=message ,
670                                                          status=status ) )
671    @web.expose
672    @web.require_login( "update sequencing request state" )
673    def update_request_state( self, trans, cntrller, **kwd ):
674        params = util.Params( kwd )
675        message = params.get( 'message', '' )
676        status = params.get( 'status', 'done' )
677        request_id = params.get( 'request_id', None )
678        try:
679            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
680        except:
681            return invalid_id_redirect( trans, cntrller, request_id )
682        # Make sure all the samples of the current request have the same state
683        common_state = request.samples_have_common_state
684        if not common_state:
685            # If the current request state is complete and one of its samples moved from
686            # the final sample state, then move the request state to In-progress
687            if request.is_complete:
688                message = "At least 1 sample state moved from the final sample state, so now the request's state is (%s)" % request.states.SUBMITTED
689                event = trans.model.RequestEvent( request, request.states.SUBMITTED, message )
690                trans.sa_session.add( event )
691                trans.sa_session.flush()
692            if cntrller == 'api':
693                return 200, message
694        else:
695            final_state = False
696            request_type_state = request.type.final_sample_state
697            if common_state.id == request_type_state.id:
698                # since all the samples are in the final state, change the request state to 'Complete'
699                comment = "All samples of this sequencing request are in the final sample state (%s). " % request_type_state.name
700                state = request.states.COMPLETE
701                final_state = True
702            else:
703                comment = "All samples of this sequencing request are in the (%s) sample state. " % common_state.name
704                state = request.states.SUBMITTED
705            event = trans.model.RequestEvent( request, state, comment )
706            trans.sa_session.add( event )
707            trans.sa_session.flush()
708            # See if an email notification is configured to be sent when the samples are in this state.
709            retval = request.send_email_notification( trans, common_state, final_state )
710            if retval:
711                message = comment + retval
712            else:
713                message = comment
714            if cntrller == 'api':
715                return 200, message
716        return trans.response.send_redirect( web.url_for( controller='requests_common',
717                                                          action='edit_samples',
718                                                          cntrller=cntrller,
719                                                          id=request_id,
720                                                          status=status,
721                                                          message=message ) )
722    @web.expose
723    @web.require_login( "find samples" )
724    def find_samples( self, trans, cntrller, **kwd ):
725        params = util.Params( kwd )
726        message = util.restore_text( params.get( 'message', ''  ) )
727        status = params.get( 'status', 'done' )
728        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
729        samples_list = []
730        results = ''
731        if params.get( 'find_samples_button', False ):
732            search_string = kwd.get( 'search_box', ''  )
733            search_type = params.get( 'search_type', ''  )
734            request_states = util.listify( params.get( 'request_states', '' ) )
735            samples = []
736            if search_type == 'bar_code':
737                samples = trans.sa_session.query( trans.model.Sample ) \
738                                          .filter( and_( trans.model.Sample.table.c.deleted==False,
739                                                         func.lower( trans.model.Sample.table.c.bar_code ).like( "%" + search_string.lower() + "%" ) ) ) \
740                                          .order_by( trans.model.Sample.table.c.create_time.desc() )
741            elif search_type == 'sample name':
742                samples = trans.sa_session.query( trans.model.Sample ) \
743                                          .filter( and_( trans.model.Sample.table.c.deleted==False,
744                                                         func.lower( trans.model.Sample.table.c.name ).like( "%" + search_string.lower() + "%" ) ) ) \
745                                          .order_by( trans.model.Sample.table.c.create_time.desc() )
746            elif search_type == 'dataset':
747                samples = trans.sa_session.query( trans.model.Sample ) \
748                                          .filter( and_( trans.model.Sample.table.c.deleted==False,
749                                                         trans.model.SampleDataset.table.c.sample_id==trans.model.Sample.table.c.id,
750                                                         func.lower( trans.model.SampleDataset.table.c.name ).like( "%" + search_string.lower() + "%" ) ) ) \
751                                          .order_by( trans.model.Sample.table.c.create_time.desc() )
752            elif search_type == 'form value':
753                samples = []
754                if search_string.find('=') != -1:
755                    field_label, field_value = search_string.split('=')
756                    all_samples = trans.sa_session.query( trans.model.Sample ) \
757                                              .filter( trans.model.Sample.table.c.deleted==False ) \
758                                              .order_by( trans.model.Sample.table.c.create_time.desc() )
759                    for sample in all_samples:
760                        # find the field in the sample form with the given label
761                        for field in sample.request.type.sample_form.fields:
762                            if field_label == field['label']:
763                                # check if the value is equal to the value in the search string
764                                if sample.values.content[ field['name'] ] == field_value:
765                                    samples.append( sample )
766            if is_admin:
767                for s in samples:
768                    if not s.request.deleted and s.request.state in request_states:
769                        samples_list.append( s )
770            else:
771                for s in samples:
772                    if s.request.user.id == trans.user.id and s.request.state in request_states and not s.request.deleted:
773                        samples_list.append( s )
774            results = 'There are %i samples matching the search parameters.' % len( samples_list )
775        # Build the request_states SelectField
776        selected_value = kwd.get( 'request_states', trans.model.Request.states.SUBMITTED )
777        states = [ v for k, v in trans.model.Request.states.items() ]
778        request_states = build_select_field( trans,
779                                             states,
780                                             'self',
781                                             'request_states',
782                                             selected_value=selected_value,
783                                             refresh_on_change=False,
784                                             multiple=True,
785                                             display='checkboxes' )
786        # Build the search_type SelectField
787        selected_value = kwd.get( 'search_type', 'sample name' )
788        types = [ 'sample name', 'bar_code', 'dataset', 'form value' ]
789        search_type = build_select_field( trans, types, 'self', 'search_type', selected_value=selected_value, refresh_on_change=False )
790        # Build the search_box TextField
791        search_box = TextField( 'search_box', 50, kwd.get('search_box', '' ) )
792        return trans.fill_template( '/requests/common/find_samples.mako', 
793                                    cntrller=cntrller,
794                                    request_states=request_states,
795                                    samples=samples_list,
796                                    search_type=search_type,
797                                    results=results,
798                                    search_box=search_box )
799    @web.expose
800    @web.require_login( "sample events" )
801    def view_sample_history( self, trans, cntrller, **kwd ):
802        params = util.Params( kwd )
803        status = params.get( 'status', 'done' )
804        message = util.restore_text( params.get( 'message', '' ) )
805        sample_id = params.get( 'sample_id', None )
806        try:
807            sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
808        except:
809            return invalid_id_redirect( trans, cntrller, sample_id, 'sample' )
810        return trans.fill_template( '/requests/common/view_sample_history.mako', 
811                                    cntrller=cntrller,
812                                    sample=sample )
813    @web.expose
814    @web.require_login( "add samples" )
815    def add_samples( self, trans, cntrller, **kwd ):
816        params = util.Params( kwd )
817        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
818        message = util.restore_text( params.get( 'message', ''  ) )
819        status = params.get( 'status', 'done' )
820        request_id = params.get( 'id', None )
821        try:
822            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
823        except:
824            return invalid_id_redirect( trans, cntrller, request_id )
825        libraries = trans.app.security_agent.get_accessible_libraries( trans, request.user )
826        # Build a list of sample widgets (based on the attributes of each sample) for display.
827        displayable_sample_widgets = self.__get_sample_widgets( trans, request, request.samples, **kwd )
828        if params.get( 'import_samples_button', False ):
829            # Import sample field values from a csv file
830            # TODO: should this be a mapper?
831            workflows = [ w.latest_workflow for w in trans.user.stored_workflows if not w.deleted ]
832            return self.__import_samples( trans, cntrller, request, displayable_sample_widgets, libraries, workflows, **kwd )
833        elif params.get( 'add_sample_button', False ):
834            return self.add_sample( trans, cntrller, request_id, **kwd )
835        elif params.get( 'save_samples_button', False ):
836            return self.__save_samples( trans, cntrller, request, dis

Large files files are truncated, but you can click here to view the full file