root/galaxy-central/lib/galaxy/web/controllers/requests_common.py

リビジョン 2, 83.7 KB (コミッタ: hatakeyama, 14 年 前)

import galaxy-central

行番号 
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.web.form_builder import *
6import logging, os, csv
7
8log = logging.getLogger( __name__ )
9
10class RequestsGrid( grids.Grid ):
11    # Custom column types
12    class NameColumn( grids.TextColumn ):
13        def get_value( self, trans, grid, request ):
14            return request.name
15    class DescriptionColumn( grids.TextColumn ):
16        def get_value(self, trans, grid, request):
17            return request.desc
18    class SamplesColumn( grids.GridColumn ):
19        def get_value(self, trans, grid, request):
20            return str( len( request.samples ) )
21    class TypeColumn( grids.TextColumn ):
22        def get_value( self, trans, grid, request ):
23            return request.type.name
24    class StateColumn( grids.StateColumn ):
25        def get_value(self, trans, grid, request ):
26            state = request.state
27            if state == request.states.REJECTED:
28                state_color = 'error'
29            elif state == request.states.NEW:
30                state_color = 'new'
31            elif state == request.states.SUBMITTED:
32                state_color = 'running'
33            elif state == request.states.COMPLETE:
34                state_color = 'ok'
35            else:
36                state_color = state
37            return '<div class="count-box state-color-%s">%s</div>' % ( state_color, state )
38        def filter( self, trans, user, query, column_filter ):
39            """ Modify query to filter request by state. """
40            if column_filter == "All":
41                return query
42            if column_filter:
43                return query.join( model.RequestEvent.table ) \
44                            .filter( self.model_class.table.c.id == model.RequestEvent.table.c.request_id ) \
45                            .filter( model.RequestEvent.table.c.state == column_filter ) \
46                            .filter( model.RequestEvent.table.c.id.in_( select( columns=[ func.max( model.RequestEvent.table.c.id ) ],
47                                                                                from_obj=model.RequestEvent.table,
48                                                                                group_by=model.RequestEvent.table.c.request_id ) ) )
49        def get_accepted_filters( self ):
50            """ Returns a list of accepted filters for this column. """
51            # TODO: is this method necessary?
52            accepted_filter_labels_and_vals = [ model.Request.states.get( state ) for state in model.Request.states ]
53            accepted_filter_labels_and_vals.append( "All" )
54            accepted_filters = []
55            for val in accepted_filter_labels_and_vals:
56                label = val.lower()
57                args = { self.key: val }
58                accepted_filters.append( grids.GridColumnFilter( label, args ) )
59            return accepted_filters
60       
61    # Grid definition
62    title = "Sequencing Requests"
63    template = "requests/grid.mako"
64    model_class = model.Request
65    default_sort_key = "-update_time"
66    num_rows_per_page = 50
67    preserve_state = True
68    use_paging = True
69    default_filter = dict( state="All", deleted="False" )
70    columns = [
71        NameColumn( "Name",
72                    key="name",
73                    link=( lambda item: iff( item.deleted, None, dict( operation="manage_request", id=item.id ) ) ),
74                    attach_popup=True,
75                    filterable="advanced" ),
76        DescriptionColumn( "Description",
77                           key='desc',
78                           filterable="advanced" ),
79        SamplesColumn( "Samples",
80                       link=( lambda item: iff( item.deleted, None, dict( operation="manage_request", id=item.id ) ) ) ),
81        TypeColumn( "Sequencer",
82                    link=( lambda item: iff( item.deleted, None, dict( operation="view_type", id=item.type.id ) ) ) ),
83        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
84        grids.DeletedColumn( "Deleted",
85                             key="deleted",
86                             visible=False,
87                             filterable="advanced" ),
88        StateColumn( "State",
89                     key='state',
90                     filterable="advanced",
91                     link=( lambda item: iff( item.deleted, None, dict( operation="request_events", id=item.id ) ) )
92                   )
93    ]
94    columns.append( grids.MulticolFilterColumn( "Search",
95                                                cols_to_filter=[ columns[0], columns[1] ],
96                                                key="free-text-search",
97                                                visible=False,
98                                                filterable="standard" ) )
99    operations = [
100        grids.GridOperation( "Submit",
101                             allow_multiple=False,
102                             condition=( lambda item: not item.deleted and item.is_unsubmitted and item.samples ),
103                             confirm="Samples cannot be added to this request after it is submitted. Click OK to submit."  )
104        ]
105
106class RequestsCommon( BaseController, UsesFormDefinitionWidgets ):
107    @web.json
108    def sample_state_updates( self, trans, ids=None, states=None ):
109        # Avoid caching
110        trans.response.headers['Pragma'] = 'no-cache'
111        trans.response.headers['Expires'] = '0'
112        # Create new HTML for any that have changed
113        rval = {}
114        if ids is not None and states is not None:
115            ids = map( int, ids.split( "," ) )
116            states = states.split( "," )
117            for id, state in zip( ids, states ):
118                sample = trans.sa_session.query( self.app.model.Sample ).get( id )
119                if sample.state.name != state:
120                    rval[ id ] = { "state": sample.state.name,
121                                   "datasets": len( sample.datasets ),
122                                   "html_state": unicode( trans.fill_template( "requests/common/sample_state.mako",
123                                                                               sample=sample),
124                                                                               'utf-8' ),
125                                   "html_datasets": unicode( trans.fill_template( "requests/common/sample_datasets.mako",
126                                                                                  sample=sample ),
127                                                                                  'utf-8' ) }
128        return rval
129    @web.expose
130    @web.require_login( "create sequencing requests" )
131    def create_request( self, trans, cntrller, **kwd ):
132        params = util.Params( kwd )
133        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
134        message = util.restore_text( params.get( 'message', '' ) )
135        status = params.get( 'status', 'done' )
136        request_type_id = params.get( 'request_type_id', 'none' )
137        if request_type_id != 'none':
138            request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) )
139        else:
140            request_type = None
141        # user_id will not be 'none' if an admin user is submitting this request on behalf of another user
142        # and they selected that user's id from the user_id SelectField.
143        user_id_encoded = True
144        user_id = params.get( 'user_id', 'none' )
145        if user_id != 'none':
146            try:
147                user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
148            except TypeError, e:
149                # We must have an email address rather than an encoded user id
150                # This is because the galaxy.base.js creates a search+select box
151                # when there are more than 20 items in a selectfield
152                user = trans.sa_session.query( trans.model.User ) \
153                                       .filter( trans.model.User.table.c.email==util.restore_text( user_id ) ) \
154                                       .first()
155                user_id_encoded = False
156               
157        elif not is_admin:
158            user = trans.user
159        else:
160            user = None
161        if params.get( 'create_request_button', False ) or params.get( 'add_sample_button', False ):
162            name = util.restore_text( params.get( 'name', '' ) )
163            if is_admin and user_id == 'none':
164                message = 'Select the user on behalf of whom you are submitting this request.'
165                status = 'error'
166            elif user is None:
167                message = 'Invalid user ID (%s)' % str(user_id)
168                status = 'error'
169            elif not name:
170                message = 'Enter the name of the request.'
171                status = 'error'
172            else:
173                request = self.__save_request( trans, cntrller, **kwd )
174                message = 'The request has been created.'
175                if params.get( 'create_request_button', False ):
176                    return trans.response.send_redirect( web.url_for( controller=cntrller,
177                                                                      action='browse_requests',
178                                                                      message=message ,
179                                                                      status='done' ) )
180                elif params.get( 'add_sample_button', False ):
181                    return self.__add_sample( trans, cntrller, request, **kwd )
182        request_type_select_field = self.__build_request_type_id_select_field( trans, selected_value=request_type_id )
183        # Widgets to be rendered on the request form
184        widgets = []
185        if request_type is not None or status == 'error':
186            # Either the user selected a request_type or an error exists on the form.
187            widgets.append( dict( label='Name of the Experiment',
188                                  widget=TextField( 'name', 40, util.restore_text( params.get( 'name', ''  ) ) ),
189                                  helptext='(Required)') )
190            widgets.append( dict( label='Description',
191                                  widget=TextField( 'desc', 40, util.restore_text( params.get( 'desc', ''  ) )),
192                                  helptext='(Optional)') )
193            if request_type is not None:
194                widgets += request_type.request_form.get_widgets( user, **kwd )
195        # In case there is an error on the form, make sure to populate widget fields with anything the user
196        # may have already entered.
197        self.populate_widgets_from_kwd( trans, widgets, **kwd )
198        if request_type is not None or status == 'error':
199            # Either the user selected a request_type or an error exists on the form.
200            if is_admin:
201                if not user_id_encoded:
202                    selected_user_id = trans.security.encode_id( user.id )
203                else:
204                    selected_user_id = user_id
205                user_widget = dict( label='Select user',
206                                    widget=self.__build_user_id_select_field( trans, selected_value=selected_user_id ),
207                                    helptext='Submit the request on behalf of the selected user (Required)')
208                widgets = [ user_widget ] + widgets
209        return trans.fill_template( '/requests/common/create_request.mako',
210                                    cntrller=cntrller,
211                                    request_type_select_field=request_type_select_field,
212                                    request_type_select_field_selected=request_type_id,                               
213                                    widgets=widgets,
214                                    message=message,
215                                    status=status )
216    @web.expose
217    @web.require_login( "edit sequencing requests" )
218    def edit_basic_request_info( self, trans, cntrller, **kwd ):
219        params = util.Params( kwd )
220        message = util.restore_text( params.get( 'message', ''  ) )
221        status = params.get( 'status', 'done' )
222        request_id = params.get( 'id', None )
223        try:
224            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
225        except:
226            return invalid_id_redirect( trans, cntrller, request_id )
227        name = util.restore_text( params.get( 'name', '' ) )
228        desc = util.restore_text( params.get( 'desc', ''  ) )
229        if params.get( 'edit_basic_request_info_button', False ) or params.get( 'edit_samples_button', False ):
230            if not name:
231                status = 'error'
232                message = 'Enter the name of the request'
233            else:
234                request = self.__save_request( trans, cntrller, request=request, **kwd )
235                message = 'The changes made to request (%s) have been saved.' % request.name
236        # Widgets to be rendered on the request form
237        widgets = []
238        widgets.append( dict( label='Name',
239                              widget=TextField( 'name', 40, request.name ),
240                              helptext='(Required)' ) )
241        widgets.append( dict( label='Description',
242                              widget=TextField( 'desc', 40, request.desc ),
243                              helptext='(Optional)' ) )
244        widgets = widgets + request.type.request_form.get_widgets( request.user, request.values.content, **kwd )
245        # In case there is an error on the form, make sure to populate widget fields with anything the user
246        # may have already entered.
247        self.populate_widgets_from_kwd( trans, widgets, **kwd )
248        return trans.fill_template( 'requests/common/edit_basic_request_info.mako',
249                                    cntrller=cntrller,
250                                    request_type=request.type,
251                                    request=request,
252                                    widgets=widgets,
253                                    message=message,
254                                    status=status )
255    def __save_request( self, trans, cntrller, request=None, **kwd ):
256        """
257        Saves changes to an existing request, or creates a new
258        request if received request is None.
259        """
260        params = util.Params( kwd )
261        request_type_id = params.get( 'request_type_id', None )
262        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
263        if request is None:
264            # We're creating a new request, so we need the associated request_type
265            request_type = trans.sa_session.query( trans.model.RequestType ).get( trans.security.decode_id( request_type_id ) )
266            if is_admin:
267                # The admin user is creating a request on behalf of another user
268                user_id = params.get( 'user_id', '' )
269                user = trans.sa_session.query( trans.model.User ).get( trans.security.decode_id( user_id ) )
270            else:
271                user = trans.user
272        else:
273            # We're saving changes to an existing request
274            user = request.user
275            request_type = request.type
276        name = util.restore_text( params.get( 'name', '' ) )
277        desc = util.restore_text( params.get( 'desc', '' ) )
278        notification = dict( email=[ user.email ], sample_states=[ request_type.state.id ], body='', subject='' )
279        values = []
280        for index, field in enumerate( request_type.request_form.fields ):
281            field_type = field[ 'type' ]
282            field_value = params.get( 'field_%i' % index, '' )
283            if field[ 'type' ] == 'AddressField':
284                value = util.restore_text( field_value )
285                if value == 'new':
286                    # Save this new address in the list of this user's addresses
287                    user_address = trans.model.UserAddress( user=user )
288                    self.save_widget_field( trans, user_address, index, **kwd )
289                    trans.sa_session.refresh( user )
290                    values.append( int( user_address.id ) )
291                elif value in [ '', 'none', 'None', None ]:
292                    values.append( '' )
293                else:
294                    values.append( int( value ) )
295            elif field[ 'type' ] == 'CheckboxField':
296                values.append( CheckboxField.is_checked( field_value ))
297            else:
298                values.append( util.restore_text( field_value ) )
299        form_values = trans.model.FormValues( request_type.request_form, values )
300        trans.sa_session.add( form_values )
301        trans.sa_session.flush()
302        if request is None:
303            # We're creating a new request
304            request = trans.model.Request( name, desc, request_type, user, form_values, notification )
305            trans.sa_session.add( request )
306            trans.sa_session.flush()
307            trans.sa_session.refresh( request )
308            # Create an event with state 'New' for this new request
309            if request.user != trans.user:
310                sample_event_comment = "Request created by user %s for user %s." % ( trans.user.email, request.user.email )
311            else:
312                sample_event_comment = "Request created."
313            event = trans.model.RequestEvent( request, request.states.NEW, sample_event_comment )
314            trans.sa_session.add( event )
315            trans.sa_session.flush()
316        else:
317            # We're saving changes to an existing request
318            request.name = name
319            request.desc = desc
320            request.type = request_type
321            request.user = user
322            request.notification = notification
323            request.values = form_values
324            trans.sa_session.add( request )
325            trans.sa_session.flush()
326        return request
327    @web.expose
328    @web.require_login( "submit sequencing requests" )
329    def submit_request( self, trans, cntrller, **kwd ):
330        params = util.Params( kwd )
331        request_id = params.get( 'id', None )
332        message = util.restore_text( params.get( 'message', '' ) )
333        status = util.restore_text( params.get( 'status', 'done' ) )
334        try:
335            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
336        except:
337            return invalid_id_redirect( trans, cntrller, request_id )
338        ok = True
339        if not request.samples:
340            message = 'Add at least 1 sample to this request before submitting.'
341            ok = False
342        if ok:
343            message = self.__validate_request( trans, cntrller, request )
344        if message or not ok:
345            return trans.response.send_redirect( web.url_for( controller='requests_common',
346                                                              action='edit_basic_request_info',
347                                                              cntrller=cntrller,
348                                                              id = request_id,
349                                                              status='error',
350                                                              message=message ) )
351        # Change the request state to 'Submitted'
352        if request.user.email is not trans.user:
353            sample_event_comment = "Request submitted by %s on behalf of %s." % ( trans.user.email, request.user.email )
354        else:
355            sample_event_comment = ""
356        event = trans.model.RequestEvent( request, request.states.SUBMITTED, sample_event_comment )
357        trans.sa_session.add( event )
358        # change the state of each of the samples of thus request
359        new_state = request.type.states[0]
360        for sample in request.samples:
361            event = trans.model.SampleEvent( sample, new_state, 'Samples created.' )
362            trans.sa_session.add( event )
363        trans.sa_session.add( request )
364        trans.sa_session.flush()
365        request.send_email_notification( trans, new_state )
366        message = 'The request has been submitted.'
367        return trans.response.send_redirect( web.url_for( controller=cntrller,
368                                                          action='browse_requests',
369                                                          cntrller=cntrller,
370                                                          id=request_id,
371                                                          status=status,
372                                                          message=message ) )
373    @web.expose
374    @web.require_login( "sequencing request page" )
375    def manage_request( self, trans, cntrller, **kwd ):
376        params = util.Params( kwd )
377        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
378        message = util.restore_text( params.get( 'message', ''  ) )
379        status = params.get( 'status', 'done' )
380        request_id = params.get( 'id', None )
381        try:
382            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
383        except:
384            return invalid_id_redirect( trans, cntrller, request_id )
385        sample_state_id = params.get( 'sample_state_id', None )
386        # Get the user entered sample information
387        current_samples, managing_samples, libraries = self.__get_sample_info( trans, request, **kwd )
388        selected_samples = self.__get_selected_samples( trans, request, **kwd )
389        selected_value = params.get( 'sample_operation', 'none' )
390        if selected_value != 'none' and not selected_samples:
391            message = 'Select at least one sample before selecting an operation.'
392            status = 'error'
393            return trans.response.send_redirect( web.url_for( controller='requests_common',
394                                                              action='manage_request',
395                                                              cntrller=cntrller,
396                                                              id=request_id,
397                                                              status=status,
398                                                              message=message ) )
399        sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, selected_value )
400        sample_operation_selected_value = sample_operation_select_field.get_selected( return_value=True )
401        if params.get( 'import_samples_button', False ):
402            # Import sample field values from a csv file
403            return self.__import_samples( trans, cntrller, request, current_samples, libraries, **kwd )
404        elif params.get( 'add_sample_button', False ):
405            return self.__add_sample( trans, cntrller, request, **kwd )
406        elif params.get( 'save_samples_button', False ):
407            return self.__save_sample( trans, cntrller, request, current_samples, **kwd )
408        elif params.get( 'edit_samples_button', False ):
409            managing_samples = True
410        elif params.get( 'cancel_changes_button', False ):
411            return trans.response.send_redirect( web.url_for( controller='requests_common',
412                                                              action='manage_request',
413                                                              cntrller=cntrller,
414                                                              id=request_id ) )
415            pass
416        elif params.get( 'change_state_button', False ):
417            sample_event_comment = util.restore_text( params.get( 'sample_event_comment', '' ) )
418            new_state = trans.sa_session.query( trans.model.SampleState ).get( trans.security.decode_id( sample_state_id ) )
419            for sample_id in selected_samples:
420                sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
421                event = trans.model.SampleEvent( sample, new_state, sample_event_comment )
422                trans.sa_session.add( event )
423                trans.sa_session.flush()
424            return trans.response.send_redirect( web.url_for( controller='requests_common',
425                                                              cntrller=cntrller,
426                                                              action='update_request_state',
427                                                              request_id=request_id ) )
428        elif params.get( 'cancel_change_state_button', False ):
429            return trans.response.send_redirect( web.url_for( controller='requests_common',
430                                                              action='manage_request',
431                                                              cntrller=cntrller,
432                                                              id=request_id ) )
433        elif params.get( 'change_lib_button', False ):
434            library_id = params.get( 'sample_0_library_id', None )
435            try:
436                library = trans.sa_session.query( trans.model.Library ).get( trans.security.decode_id( library_id ) )
437            except:
438                invalid_id_redirect( trans, cntrller, library_id )
439            folder_id = params.get( 'sample_0_folder_id', None )
440            try:
441                folder = trans.sa_session.query( trans.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) )
442            except:
443                invalid_id_redirect( trans, cntrller, folder_id )
444            for sample_id in selected_samples:
445                sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
446                sample.library = library
447                sample.folder = folder
448                trans.sa_session.add( sample )
449                trans.sa_session.flush()
450            trans.sa_session.refresh( request )
451            message = 'Changes made to the selected samples have been saved. '
452            return trans.response.send_redirect( web.url_for( controller='requests_common',
453                                                              action='manage_request',
454                                                              cntrller=cntrller,
455                                                              id=request_id,
456                                                              status=status,
457                                                              message=message ) )
458        elif params.get( 'cancel_change_lib_button', False ):
459            return trans.response.send_redirect( web.url_for( controller='requests_common',
460                                                              action='manage_request',
461                                                              cntrller=cntrller,
462                                                              id=trans.security.encode_id( request.id ) ) )
463        request_widgets = self.__get_request_widgets( trans, request.id )
464        sample_copy = self.__build_copy_sample_select_field( trans, current_samples )
465        libraries_select_field, folders_select_field = self.__build_library_and_folder_select_fields( trans,
466                                                                                                      request.user,
467                                                                                                      0,
468                                                                                                      libraries,
469                                                                                                      None,
470                                                                                                      **kwd )
471        # Build the sample_state_id_select_field SelectField
472        sample_state_id_select_field = self.__build_sample_state_id_select_field( trans, request, sample_state_id )
473        return trans.fill_template( '/requests/common/manage_request.mako',
474                                    cntrller=cntrller,
475                                    request=request,
476                                    selected_samples=selected_samples,
477                                    request_widgets=request_widgets,
478                                    current_samples=current_samples,
479                                    sample_copy=sample_copy,
480                                    libraries=libraries,
481                                    sample_operation_select_field=sample_operation_select_field,
482                                    libraries_select_field=libraries_select_field,
483                                    folders_select_field=folders_select_field,
484                                    sample_state_id_select_field=sample_state_id_select_field,
485                                    managing_samples=managing_samples,
486                                    status=status,
487                                    message=message )
488    @web.expose
489    @web.require_login( "delete sequencing requests" )
490    def delete_request( self, trans, cntrller, **kwd ):
491        params = util.Params( kwd )
492        id_list = util.listify( kwd.get( 'id', '' ) )
493        message = util.restore_text( params.get( 'message', '' ) )
494        status = util.restore_text( params.get( 'status', 'done' ) )
495        num_deleted = 0
496        for id in id_list:
497            ok_for_now = True
498            try:
499                request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) )
500            except:
501                message += "Invalid request ID (%s).  " % str( id )
502                status = 'error'
503                ok_for_now = False
504            if ok_for_now:
505                request.deleted = True
506                trans.sa_session.add( request )
507                # delete all the samples belonging to this request
508                for s in request.samples:
509                    s.deleted = True
510                    trans.sa_session.add( s )
511                trans.sa_session.flush()
512                num_deleted += 1
513        message += '%i requests have been deleted.' % num_deleted
514        return trans.response.send_redirect( web.url_for( controller=cntrller,
515                                                          action='browse_requests',
516                                                          status=status,
517                                                          message=message ) )
518    @web.expose
519    @web.require_login( "undelete sequencing requests" )
520    def undelete_request( self, trans, cntrller, **kwd ):
521        params = util.Params( kwd )
522        id_list = util.listify( kwd.get( 'id', '' ) )
523        message = util.restore_text( params.get( 'message', '' ) )
524        status = util.restore_text( params.get( 'status', 'done' ) )
525        num_undeleted = 0
526        for id in id_list:
527            ok_for_now = True
528            try:
529                request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( id ) )
530            except:
531                message += "Invalid request ID (%s).  " % str( id )
532                status = 'error'
533                ok_for_now = False
534            if ok_for_now:
535                request.deleted = False
536                trans.sa_session.add( request )
537                # undelete all the samples belonging to this request
538                for s in request.samples:
539                    s.deleted = False
540                    trans.sa_session.add( s )
541                trans.sa_session.flush()
542                num_undeleted += 1
543        message += '%i requests have been undeleted.' % num_undeleted
544        return trans.response.send_redirect( web.url_for( controller=cntrller,
545                                                          action='browse_requests',
546                                                          status=status,
547                                                          message=message ) )
548    @web.expose
549    @web.require_login( "sequencing request events" )
550    def request_events( self, trans, cntrller, **kwd ):
551        params = util.Params( kwd )
552        request_id = params.get( 'id', None )
553        try:
554            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
555        except:
556            return invalid_id_redirect( trans, cntrller, request_id )
557        events_list = []
558        for event in request.events:         
559            events_list.append( ( event.state, time_ago( event.update_time ), event.comment ) )
560        return trans.fill_template( '/requests/common/events.mako',
561                                    cntrller=cntrller,
562                                    events_list=events_list,
563                                    request=request )
564    @web.expose
565    @web.require_login( "edit email notification settings" )
566    def edit_email_settings( self, trans, cntrller, **kwd ):
567        """
568        Allow for changing the email notification settings where email is sent to a list of users
569        whenever the request state changes to one selected for notification.
570        """
571        params = util.Params( kwd )
572        message = util.restore_text( params.get( 'message', ''  ) )
573        status = params.get( 'status', 'done' )
574        request_id = params.get( 'id', None )
575        try:
576            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
577        except:
578            return invalid_id_redirect( trans, cntrller, request_id )
579        email_address = CheckboxField.is_checked( params.get( 'email_address', '' ) )
580        additional_email_addresses = params.get( 'additional_email_addresses', '' )
581        # Get the list of checked sample state CheckBoxFields
582        checked_sample_states = []
583        for index, sample_state in enumerate( request.type.states ):
584            if CheckboxField.is_checked( params.get( 'sample_state_%i' % sample_state.id, '' ) ):
585                checked_sample_states.append( sample_state.id )
586        if additional_email_addresses:
587            additional_email_addresses = additional_email_addresses.split( '\r\n' )
588        if email_address or additional_email_addresses:
589            # The user added 1 or more email addresses
590            email_addresses = []
591            if email_address:
592                email_addresses.append( request.user.email )
593            for email_address in additional_email_addresses:
594                email_addresses.append( util.restore_text( email_address ) )
595            # Make sure email addresses are valid
596            err_msg = ''
597            for email_address in email_addresses:
598                err_msg += self.__validate_email( email_address )
599            if err_msg:
600                status = 'error'
601                message += err_msg
602            else:
603                request.notification = dict( email=email_addresses,
604                                             sample_states=checked_sample_states,
605                                             body='',
606                                             subject='' )
607        else:
608            # The user may have eliminated email addresses that were previously set
609            request.notification = None
610            if checked_sample_states:
611                message = 'All sample states have been unchecked since no email addresses have been selected or entered.  '
612        trans.sa_session.add( request )
613        trans.sa_session.flush()
614        trans.sa_session.refresh( request )
615        message += 'The changes made to the email notification settings have been saved.'
616        return trans.response.send_redirect( web.url_for( controller='requests_common',
617                                                          action='edit_basic_request_info',
618                                                          cntrller=cntrller,
619                                                          id=request_id,
620                                                          message=message ,
621                                                          status=status ) )
622    @web.expose
623    @web.require_login( "update sequencing request state" )
624    def update_request_state( self, trans, cntrller, **kwd ):
625        params = util.Params( kwd )
626        message = params.get( 'message', '' )
627        status = params.get( 'status', 'done' )
628        request_id = params.get( 'request_id', None )
629        try:
630            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
631        except:
632            return invalid_id_redirect( trans, cntrller, request_id )
633        # Make sure all the samples of the current request have the same state
634        common_state = request.samples_have_common_state
635        if not common_state:
636            # If the current request state is complete and one of its samples moved from
637            # the final sample state, then move the request state to In-progress
638            if request.is_complete:
639                message = "At least 1 sample state moved from the final sample state, so now the request is in the '%s' state" % request.states.SUBMITTED
640                event = trans.model.RequestEvent( request, request.states.SUBMITTED, message )
641                trans.sa_session.add( event )
642                trans.sa_session.flush()
643            return trans.response.send_redirect( web.url_for( controller='requests_common',
644                                                              action='manage_request',
645                                                              cntrller=cntrller,
646                                                              id=request_id,
647                                                              status=status,
648                                                              message=message ) )
649        final_state = False
650        request_type_state = request.type.state
651        if common_state.id == request_type_state.id:
652            # since all the samples are in the final state, change the request state to 'Complete'
653            comments = "All samples of this request are in the last sample state (%s). " % request_type_state.name
654            state = request.states.COMPLETE
655            final_state = True
656        else:
657            comments = "All samples are in %s state. " % common_state.name
658            state = request.states.SUBMITTED
659        event = trans.model.RequestEvent(request, state, comments)
660        trans.sa_session.add( event )
661        trans.sa_session.flush()
662        # check if an email notification is configured to be sent when the samples
663        # are in this state
664        retval = request.send_email_notification( trans, common_state, final_state )
665        if retval:
666            message = comments + retval
667        else:
668            message = comments
669        return trans.response.send_redirect( web.url_for( controller='requests_common',
670                                                          action='manage_request',
671                                                          cntrller=cntrller,
672                                                          id=trans.security.encode_id(request.id),
673                                                          status='done',
674                                                          message=message ) )
675    def __save_sample( self, trans, cntrller, request, current_samples, **kwd ):
676        # Save all the new/unsaved samples entered by the user
677        params = util.Params( kwd )
678        message = util.restore_text( params.get( 'message', ''  ) )
679        status = params.get( 'status', 'done' )
680        managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) )
681        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
682        selected_value = params.get( 'sample_operation', 'none' )
683        # Check for duplicate sample names
684        message = ''
685        for index in range( len( current_samples ) - len( request.samples ) ):
686            sample_index = index + len( request.samples )
687            current_sample = current_samples[ sample_index ]
688            sample_name = current_sample[ 'name' ]
689            if not sample_name.strip():
690                message = 'Enter the name of sample number %i' % sample_index
691                break
692            count = 0
693            for i in range( len( current_samples ) ):
694                if sample_name == current_samples[ i ][ 'name' ]:
695                    count += 1
696            if count > 1:
697                message = "This request has %i samples with the name (%s).  Samples belonging to a request must have unique names." % ( count, sample_name )
698                break
699        if message:
700            selected_samples = self.__get_selected_samples( trans, request, **kwd )
701            request_widgets = self.__get_request_widgets( trans, request.id )
702            sample_copy = self.__build_copy_sample_select_field( trans, current_samples )
703            sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, selected_value )
704            status = 'error'
705            return trans.fill_template( '/requests/common/manage_request.mako',
706                                        cntrller=cntrller,
707                                        request=request,
708                                        selected_samples=selected_samples,
709                                        request_widgets=request_widgets,
710                                        current_samples=current_samples,
711                                        sample_copy=sample_copy,
712                                        managing_samples=managing_samples,
713                                        sample_operation_select_field=sample_operation_select_field,
714                                        status=status,
715                                        message=message )
716        if not managing_samples:
717            for index in range( len( current_samples ) - len( request.samples ) ):
718                sample_index = len( request.samples )
719                current_sample = current_samples[ sample_index ]
720                form_values = trans.model.FormValues( request.type.sample_form, current_sample[ 'field_values' ] )
721                trans.sa_session.add( form_values )
722                trans.sa_session.flush()                   
723                s = trans.model.Sample( current_sample[ 'name' ],
724                                        '',
725                                        request,
726                                        form_values,
727                                        current_sample[ 'barcode' ],
728                                        current_sample[ 'library' ],
729                                        current_sample[ 'folder' ] )
730                trans.sa_session.add( s )
731                trans.sa_session.flush()
732        else:
733            message = 'Changes made to the samples are saved. '
734            for sample_index in range( len( current_samples ) ):
735                sample = request.samples[ sample_index ]
736                current_sample = current_samples[ sample_index ]
737                sample.name = current_sample[ 'name' ]
738                sample.library = current_sample[ 'library' ]
739                sample.folder = current_sample[ 'folder' ]
740                if request.is_submitted:
741                    bc_message = self.__validate_barcode( trans, sample, current_sample[ 'barcode' ] )
742                    if bc_message:
743                        status = 'error'
744                        message += bc_message
745                    else:
746                        if not sample.bar_code:
747                            # If this is a 'new' (still in its first state) sample
748                            # change the state to the next
749                            if sample.state.id == request.type.states[0].id:
750                                event = trans.model.SampleEvent( sample,
751                                                                 request.type.states[1],
752                                                                 'Sample added to the system' )
753                                trans.sa_session.add( event )
754                                trans.sa_session.flush()
755                                # Now check if all the samples' barcode has been entered.
756                                # If yes then send notification email if configured
757                                common_state = request.samples_have_common_state
758                                if common_state:
759                                    if common_state.id == request.type.states[1].id:
760                                        event = trans.model.RequestEvent( request,
761                                                                          request.states.SUBMITTED,
762                                                                          "All samples are in %s state." % common_state.name )
763                                        trans.sa_session.add( event )
764                                        trans.sa_session.flush()
765                                        request.send_email_notification( trans, request.type.states[1] )
766                        sample.bar_code = current_samples[sample_index]['barcode']
767                trans.sa_session.add( sample )
768                trans.sa_session.flush()
769                form_values = trans.sa_session.query( trans.model.FormValues ).get( sample.values.id )
770                form_values.content = current_sample[ 'field_values' ]
771                trans.sa_session.add( form_values )
772                trans.sa_session.flush()
773        return trans.response.send_redirect( web.url_for( controller='requests_common',
774                                                          action='manage_request',
775                                                          cntrller=cntrller,
776                                                          id=trans.security.encode_id( request.id ),
777                                                          status=status,
778                                                          message=message ) )
779    @web.expose
780    @web.require_login( "find samples" )
781    def find_samples( self, trans, cntrller, **kwd ):
782        params = util.Params( kwd )
783        message = util.restore_text( params.get( 'message', ''  ) )
784        status = params.get( 'status', 'done' )
785        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
786        samples_list = []
787        results = ''
788        if params.get( 'find_samples_button', False ):
789            search_string = kwd.get( 'search_box', ''  )
790            search_type = params.get( 'search_type', ''  )
791            request_states = util.listify( params.get( 'request_states', '' ) )
792            samples = []
793            if search_type == 'barcode':
794                samples = trans.sa_session.query( trans.model.Sample ) \
795                                          .filter( and_( trans.model.Sample.table.c.deleted==False,
796                                                         func.lower( trans.model.Sample.table.c.bar_code ).like( "%" + search_string.lower() + "%" ) ) ) \
797                                          .order_by( trans.model.Sample.table.c.create_time.desc() )
798            elif search_type == 'sample name':
799                samples = trans.sa_session.query( trans.model.Sample ) \
800                                          .filter( and_( trans.model.Sample.table.c.deleted==False,
801                                                         func.lower( trans.model.Sample.table.c.name ).like( "%" + search_string.lower() + "%" ) ) ) \
802                                          .order_by( trans.model.Sample.table.c.create_time.desc() )
803            elif search_type == 'dataset':
804                samples = trans.sa_session.query( trans.model.Sample ) \
805                                          .filter( and_( trans.model.Sample.table.c.deleted==False,
806                                                         trans.model.SampleDataset.table.c.sample_id==trans.model.Sample.table.c.id,
807                                                         func.lower( trans.model.SampleDataset.table.c.name ).like( "%" + search_string.lower() + "%" ) ) ) \
808                                          .order_by( trans.model.Sample.table.c.create_time.desc() )
809            if is_admin:
810                for s in samples:
811                    if not s.request.deleted and s.request.state in request_states:
812                        samples_list.append( s )
813            else:
814                for s in samples:
815                    if s.request.user.id == trans.user.id and s.request.state in request_states and not s.request.deleted:
816                        samples_list.append( s )
817            results = 'There are %i samples matching the search parameters.' % len( samples_list )
818        # Build the request_states SelectField
819        selected_value = kwd.get( 'request_states', trans.model.Request.states.SUBMITTED )
820        states = [ v for k, v in trans.model.Request.states.items() ]
821        request_states = build_select_field( trans,
822                                             states,
823                                             'self',
824                                             'request_states',
825                                             selected_value=selected_value,
826                                             refresh_on_change=False,
827                                             multiple=True,
828                                             display='checkboxes' )
829        # Build the search_type SelectField
830        selected_value = kwd.get( 'search_type', 'sample name' )
831        types = [ 'sample name', 'barcode', 'dataset' ]
832        search_type = build_select_field( trans, types, 'self', 'search_type', selected_value=selected_value, refresh_on_change=False )
833        # Build the search_box TextField
834        search_box = TextField( 'search_box', 50, kwd.get('search_box', '' ) )
835        return trans.fill_template( '/requests/common/find_samples.mako',
836                                    cntrller=cntrller,
837                                    request_states=request_states,
838                                    samples=samples_list,
839                                    search_type=search_type,
840                                    results=results,
841                                    search_box=search_box )
842    @web.expose
843    @web.require_login( "sample events" )
844    def sample_events( self, trans, cntrller, **kwd ):
845        params = util.Params( kwd )
846        status = params.get( 'status', 'done' )
847        message = util.restore_text( params.get( 'message', '' ) )
848        sample_id = params.get( 'sample_id', None )
849        try:
850            sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
851        except:
852            return invalid_id_redirect( trans, cntrller, sample_id )
853        events_list = []
854        for event in sample.events:         
855            events_list.append( ( event.state.name,
856                                  event.state.desc,
857                                  time_ago( event.update_time ),
858                                  event.comment ) )
859        return trans.fill_template( '/requests/common/sample_events.mako',
860                                    cntrller=cntrller,
861                                    events_list=events_list,
862                                    sample=sample )
863    def __add_sample( self, trans, cntrller, request, **kwd ):
864        params = util.Params( kwd )
865        message = util.restore_text( params.get( 'message', ''  ) )
866        status = params.get( 'status', 'done' )
867        managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) )
868        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
869        # Get the widgets for rendering the request form
870        request_widgets = self.__get_request_widgets( trans, request.id )
871        current_samples, managing_samples, libraries = self.__get_sample_info( trans, request, **kwd )
872        if not current_samples:
873            # Form field names are zero-based.
874            sample_index = 0
875        else:
876            sample_index = len( current_samples )
877        if params.get( 'add_sample_button', False ):
878            num_samples_to_add = int( params.get( 'num_sample_to_copy', 1 ) )
879            # See if the user has selected a sample to copy.
880            copy_sample_index = int( params.get( 'copy_sample_index', -1 ) )
881            for index in range( num_samples_to_add ):
882                if copy_sample_index != -1:
883                    # The user has selected a sample to copy.
884                    library_id = current_samples[ copy_sample_index][ 'library_select_field' ].get_selected( return_value=True )
885                    folder_id = current_samples[ copy_sample_index ][ 'folder_select_field' ].get_selected( return_value=True )
886                    name = current_samples[ copy_sample_index ][ 'name' ] + '_%i' % ( len( current_samples ) + 1 )
887                    field_values = [ val for val in current_samples[ copy_sample_index ][ 'field_values' ] ]
888                else:
889                    # The user has not selected a sample to copy (may just be adding a sample).
890                    library_id = None
891                    folder_id = None
892                    name = 'Sample_%i' % ( len( current_samples ) + 1 )
893                    field_values = [ '' for field in request.type.sample_form.fields ]
894                # Build the library_select_field and folder_select_field for the new sample being added.
895                library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans,
896                                                                                                           user=request.user,
897                                                                                                           sample_index=len( current_samples ),
898                                                                                                           libraries=libraries,
899                                                                                                           sample=None,
900                                                                                                           library_id=library_id,
901                                                                                                           folder_id=folder_id,
902                                                                                                           **kwd )
903                # Append the new sample to the current list of samples for the request
904                current_samples.append( dict( name=name,
905                                              barcode='',
906                                              library=None,
907                                              library_id=library_id,
908                                              folder=None,
909                                              folder_id=folder_id,
910                                              field_values=field_values,
911                                              library_select_field=library_select_field,
912                                              folder_select_field=folder_select_field ) )
913        selected_samples = self.__get_selected_samples( trans, request, **kwd )
914        selected_value = params.get( 'sample_operation', 'none' )
915        sample_operation_select_field = self.__build_sample_operation_select_field( trans, is_admin, request, selected_value )
916        sample_copy = self.__build_copy_sample_select_field( trans, current_samples )
917        return trans.fill_template( '/requests/common/manage_request.mako',
918                                    cntrller=cntrller,
919                                    request=request,
920                                    selected_samples=selected_samples,
921                                    request_widgets=request_widgets,
922                                    current_samples=current_samples,
923                                    sample_operation_select_field=sample_operation_select_field,
924                                    sample_copy=sample_copy,
925                                    managing_samples=managing_samples,
926                                    message=message,
927                                    status=status )
928    def __get_sample_info( self, trans, request, **kwd ):
929        """
930        Retrieves all user entered sample information and returns a
931        list of all the samples and their field values.
932        """
933        params = util.Params( kwd )
934        managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) )
935        # Bet all data libraries accessible to this user
936        libraries = request.user.accessible_libraries( trans, [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ] )
937        # Build the list of widgets which will be used to render each sample row on the request page
938        current_samples = []
939        for index, sample in enumerate( request.samples ):
940            library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans,
941                                                                                                       request.user,
942                                                                                                       index,
943                                                                                                       libraries,
944                                                                                                       sample,
945                                                                                                       **kwd )
946            current_samples.append( dict( name=sample.name,
947                                          barcode=sample.bar_code,
948                                          library=sample.library,
949                                          folder=sample.folder,
950                                          field_values=sample.values.content,
951                                          library_select_field=library_select_field,
952                                          folder_select_field=folder_select_field ) )
953        if not managing_samples:
954            sample_index = len( request.samples )
955        else:
956            sample_index = 0
957        while True:
958            library_id = params.get( 'sample_%i_library_id' % sample_index, None )
959            folder_id = params.get( 'sample_%i_folder_id' % sample_index, None )
960            if params.get( 'sample_%i_name' % sample_index, False  ):
961                # Data library
962                try:
963                    library = trans.sa_session.query( trans.model.Library ).get( trans.security.decode_id( library_id ) )
964                    #library_id = library.id
965                except:
966                    library = None
967                if library is not None:
968                    # Folder
969                    try:
970                        folder = trans.sa_session.query( trans.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) )
971                        #folder_id = folder.id
972                    except:
973                        if library:
974                            folder = library.root_folder
975                        else:
976                            folder = None
977                else:
978                    folder = None
979                sample_info = dict( name=util.restore_text( params.get( 'sample_%i_name' % sample_index, ''  ) ),
980                                    barcode=util.restore_text( params.get( 'sample_%i_barcode' % sample_index, ''  ) ),
981                                    library=library,
982                                    folder=folder)
983                sample_info[ 'field_values' ] = []
984                for field_index in range( len( request.type.sample_form.fields ) ):
985                    sample_info[ 'field_values' ].append( util.restore_text( params.get( 'sample_%i_field_%i' % ( sample_index, field_index ), ''  ) ) )
986                if not managing_samples:
987                    sample_info[ 'library_select_field' ], sample_info[ 'folder_select_field' ] = self.__build_library_and_folder_select_fields( trans,
988                                                                                                                                                 request.user,
989                                                                                                                                                 sample_index,
990                                                                                                                                                 libraries,
991                                                                                                                                                 None,
992                                                                                                                                                 library_id,
993                                                                                                                                                 folder_id,
994                                                                                                                                                 **kwd )
995                    current_samples.append( sample_info )
996                else:
997                    sample_info[ 'library_select_field' ], sample_info[ 'folder_select_field' ] = self.__build_library_and_folder_select_fields( trans,
998                                                                                                                                                 request.user,
999                                                                                                                                                 sample_index,
1000                                                                                                                                                 libraries,
1001                                                                                                                                                 request.samples[ sample_index ],
1002                                                                                                                                                 **kwd )
1003                    current_samples[ sample_index ] =  sample_info
1004                sample_index += 1
1005            else:
1006                break
1007        return current_samples, managing_samples, libraries
1008    @web.expose
1009    @web.require_login( "delete sample from sequencing request" )
1010    def delete_sample( self, trans, cntrller, **kwd ):
1011        params = util.Params( kwd )
1012        status = params.get( 'status', 'done' )
1013        message = util.restore_text( params.get( 'message', '' ) )
1014        request_id = params.get( 'request_id', None )
1015        try:
1016            request = trans.sa_session.query( trans.model.Request ).get( trans.security.decode_id( request_id ) )
1017        except:
1018            return invalid_id_redirect( trans, cntrller, request_id )
1019        current_samples, managing_samples, libraries = self.__get_sample_info( trans, request, **kwd )
1020        sample_index = int( params.get( 'sample_id', 0 ) )
1021        sample_name = current_samples[sample_index]['name']
1022        sample = request.has_sample( sample_name )
1023        if sample:
1024            trans.sa_session.delete( sample.values )
1025            trans.sa_session.delete( sample )
1026            trans.sa_session.flush()
1027        message = 'Sample (%s) has been deleted.' % sample_name
1028        return trans.response.send_redirect( web.url_for( controller='requests_common',
1029                                                          action='manage_request',
1030                                                          cntrller=cntrller,
1031                                                          id=trans.security.encode_id( request.id ),
1032                                                          status=status,
1033                                                          message=message ) )
1034    @web.expose
1035    @web.require_login( "view data transfer page" )
1036    def view_dataset_transfer( self, trans, cntrller, **kwd ):
1037        params = util.Params( kwd )
1038        message = util.restore_text( params.get( 'message', ''  ) )
1039        status = params.get( 'status', 'done' )
1040        is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
1041        sample_id = params.get( 'sample_id', None )
1042        try:
1043            sample = trans.sa_session.query( trans.model.Sample ).get( trans.security.decode_id( sample_id ) )
1044        except:
1045            return invalid_id_redirect( trans, cntrller, sample_id )
1046        # check if a library and folder has been set for this sample yet.
1047        if not sample.library or not sample.folder:
1048            status = 'error'
1049            message = "Set a data library and folder for sequencing request (%s) to transfer datasets." % sample.name
1050            return trans.response.send_redirect( web.url_for( controller='requests_common',
1051                                                              action='manage_request',
1052                                                              cntrller=cntrller,
1053                                                              id=trans.security.encode_id( sample.request.id ),
1054                                                              status=status,
1055                                                              message=message ) )
1056        if is_admin:
1057            return trans.response.send_redirect( web.url_for( controller='requests_admin',
1058                                                              action='manage_datasets',
1059                                                              sample_id=sample_id ) )
1060           
1061        folder_path = util.restore_text( params.get( 'folder_path', ''  ) )
1062        if not folder_path:
1063            if len( sample.datasets ):
1064                folder_path = os.path.dirname( sample.datasets[-1].file_path[:-1] )
1065            else:
1066                folder_path = util.restore_text( sample.request.type.datatx_info.get( 'data_dir', '' ) )
1067        if folder_path and folder_path[-1] != os.sep:
1068            folder_path += os.sep
1069        if not sample.request.type.datatx_info['host'] \
1070            or not sample.request.type.datatx_info['username'] \
1071            or not sample.request.type.datatx_info['password']:
1072            status = 'error'
1073            message = 'The sequencer login information is incomplete. Click on sequencer information to add login details.'
1074        return trans.fill_template( '/requests/common/dataset_transfer.mako',
1075                                    cntrller=cntrller,
1076                                    sample=sample,
1077                                    dataset_files=sample.datasets,
1078                                    message=message,
1079                                    status=status,
1080                                    files=[],
1081                                    folder_path=folder_path )
1082    def __import_samples( self, trans, cntrller, request, current_samples, libraries, **kwd ):
1083        """
1084        Reads the samples csv file and imports all the samples.  The format of the csv file is:
1085        SampleName,DataLibrary,DataLibraryFolder,Field1,Field2....
1086        """
1087        params = util.Params( kwd )
1088        managing_samples = util.string_as_bool( params.get( 'managing_samples', False ) )
1089        file_obj = params.get( 'file_data', '' )
1090        try:
1091            reader = csv.reader( file_obj.file )
1092            for row in reader:
1093                library_id = None
1094                folder_id = None
1095                # FIXME: this is bad - what happens when multiple libraries have the same name??
1096                lib = trans.sa_session.query( trans.model.Library ) \
1097                                      .filter( and_( trans.model.Library.table.c.name==row[1],
1098                                                     trans.model.Library.table.c.deleted==False ) ) \
1099                                      .first()
1100                if lib:
1101                    folder = trans.sa_session.query( trans.model.LibraryFolder ) \
1102                                             .filter( and_( trans.model.LibraryFolder.table.c.name==row[2],
1103                                                            trans.model.LibraryFolder.table.c.deleted==False ) ) \
1104                                             .first()
1105                    if folder:
1106                        library_id = lib.id
1107                        folder_id = folder.id
1108                library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans,
1109                                                                                                           request.user,
1110                                                                                                           len( current_samples ),
1111                                                                                                           libraries,
1112                                                                                                           None,
1113                                                                                                           library_id,
1114                                                                                                           folder_id,
1115                                                                                                           **kwd )
1116                current_samples.append( dict( name=row[0],
1117                                              barcode='',
1118                                              library=None,
1119                                              folder=None,
1120                                              library_select_field=library_select_field,
1121                                              folder_select_field=folder_select_field,
1122                                              field_values=row[3:] ) )
1123        except Exception, e:
1124            status = 'error'
1125            message = 'Error thrown when importing samples file: %s' % str( e )
1126            return trans.response.send_redirect( web.url_for( controller='requests_common',
1127                                                              action='manage_request',
1128                                                              cntrller=cntrller,
1129                                                              id=trans.security.encode_id( request.id ),
1130                                                              status=status,
1131                                                              message=message ) )
1132        request_widgets = self.__get_request_widgets( trans, request.id )
1133        sample_copy = self.__build_copy_sample_select_field( trans, current_samples )
1134        return trans.fill_template( '/requests/common/manage_request.mako',
1135                                    cntrller=cntrller,
1136                                    request=request,
1137                                    request_widgets=request_widgets,
1138                                    current_samples=current_samples,
1139                                    sample_copy=sample_copy,
1140                                    managing_samples=managing_samples )
1141    # ===== Methods for handling form definition widgets =====
1142    def __get_request_widgets( self, trans, id ):
1143        """Get the widgets for the request"""
1144        request = trans.sa_session.query( trans.model.Request ).get( id )
1145        # The request_widgets list is a list of dictionaries
1146        request_widgets = []
1147        for index, field in enumerate( request.type.request_form.fields ):
1148            if field[ 'required' ]:
1149                required_label = 'Required'
1150            else:
1151                required_label = 'Optional'
1152            if field[ 'type' ] == 'AddressField':
1153                if request.values.content[ index ]:
1154                    request_widgets.append( dict( label=field[ 'label' ],
1155                                                  value=trans.sa_session.query( trans.model.UserAddress ).get( int( request.values.content[ index ] ) ).get_html(),
1156                                                  helptext=field[ 'helptext' ] + ' (' + required_label + ')' ) )
1157                else:
1158                    request_widgets.append( dict( label=field[ 'label' ],
1159                                                  value=None,
1160                                                  helptext=field[ 'helptext' ] + ' (' + required_label + ')' ) )
1161            else:
1162                request_widgets.append( dict( label=field[ 'label' ],
1163                                              value=request.values.content[ index ],
1164                                              helptext=field[ 'helptext' ] + ' (' + required_label + ')' ) )
1165        return request_widgets
1166    def __get_samples_widgets( self, trans, request, libraries, **kwd ):
1167        """Get the widgets for all of the samples currently associated with the request"""
1168        # The current_samples_widgets list is a list of dictionaries
1169        current_samples_widgets = []
1170        for index, sample in enumerate( request.samples ):
1171            # Build the library_select_field and folder_select_field for each existing sample
1172            library_select_field, folder_select_field = self.__build_library_and_folder_select_fields( trans,
1173                                                                                                       user=request.user,
1174                                                                                                       sample_index=index,
1175                                                                                                       libraries=libraries,
1176                                                                                                       sample=sample,
1177                                                                                                       **kwd )
1178            # Append the dictionary for the current sample to the current_samples_widgets list
1179            current_samples_widgets.append( dict( name=sample.name,
1180                                                  barcode=sample.bar_code,
1181                                                  library=sample.library,
1182                                                  folder=sample.folder,
1183                                                  field_values=sample.values.content,
1184                                                  library_select_field=library_select_field,
1185                                                  folder_select_field=folder_select_field ) )
1186        return current_samples_widgets
1187    # ===== Methods for building SelectFields used on various request forms =====
1188    def __build_copy_sample_select_field( self, trans, current_samples ):
1189        copy_sample_index_select_field = SelectField( 'copy_sample_index' )
1190        copy_sample_index_select_field.add_option( 'None', -1, selected=True ) 
1191        for index, sample_dict in enumerate( current_samples ):
1192            copy_sample_index_select_field.add_option( sample_dict[ 'name' ], index )
1193        return copy_sample_index_select_field 
1194    def __build_request_type_id_select_field( self, trans, selected_value='none' ):
1195        accessible_request_types = trans.user.accessible_request_types( trans )
1196        return build_select_field( trans, accessible_request_types, 'name', 'request_type_id', selected_value=selected_value, refresh_on_change=True )
1197    def __build_user_id_select_field( self, trans, selected_value='none' ):
1198        active_users = trans.sa_session.query( trans.model.User ) \
1199                                       .filter( trans.model.User.table.c.deleted == False ) \
1200                                       .order_by( trans.model.User.email.asc() )
1201        # A refresh_on_change is required so the user's set of addresses can be displayed.
1202        return build_select_field( trans, active_users, 'email', 'user_id', selected_value=selected_value, refresh_on_change=True )
1203    def __build_sample_operation_select_field( self, trans, is_admin, request, selected_value ):
1204        # The sample_operation SelectField is displayed only after the request has been submitted.
1205        # It's label is "For selected samples"
1206        if is_admin:
1207            if request.is_complete:
1208                bulk_operations = [ trans.model.Sample.bulk_operations.CHANGE_STATE ]
1209            if request.is_rejected:
1210                bulk_operations = [ trans.model.Sample.bulk_operations.SELECT_LIBRARY ]
1211            else:
1212                bulk_operations = [ s for i, s in trans.model.Sample.bulk_operations.items() ]
1213        else:
1214            if request.is_complete:
1215                bulk_operations = []
1216            else:
1217                bulk_operations = [ trans.model.Sample.bulk_operations.SELECT_LIBRARY ]
1218        return build_select_field( trans, bulk_operations, 'self', 'sample_operation', selected_value=selected_value, refresh_on_change=True )
1219    def __build_library_and_folder_select_fields( self, trans, user, sample_index, libraries, sample=None, library_id=None, folder_id=None, **kwd ):
1220        # Create the library_id SelectField for a specific sample. The received libraries param is a list of all the libraries
1221        # accessible to the current user, and we add them as options to the library_select_field.  If the user has selected an
1222        # existing library then display all the accessible folders of the selected library in the folder_select_field.
1223        #
1224        # The libraries dictionary looks like: { library : '1,2' }, library : '3' }.  Its keys are the libraries that
1225        # should be displayed for the current user and its values are strings of comma-separated folder ids that should
1226        # NOT be displayed.
1227        #
1228        # TODO: all object ids received in the params must be encoded.
1229        params = util.Params( kwd )
1230        library_select_field_name= "sample_%i_library_id" % sample_index
1231        folder_select_field_name = "sample_%i_folder_id" % sample_index
1232        if not library_id:
1233            library_id = params.get( library_select_field_name, 'none'  )
1234        selected_library = None
1235        selected_hidden_folder_ids = []
1236        showable_folders = []
1237        if sample and sample.library and library_id == 'none':
1238            library_id = str( sample.library.id )
1239            selected_library = sample.library
1240        # If we have a selected library, get the list of it's folders that are not accessible to the current user
1241        for library, hidden_folder_ids in libraries.items():
1242            encoded_id = trans.security.encode_id( library.id )
1243            if encoded_id == str( library_id ):
1244                selected_library = library
1245                selected_hidden_folder_ids = hidden_folder_ids.split( ',' )
1246                break
1247        # sample_%i_library_id SelectField with refresh on change enabled
1248        library_select_field = build_select_field( trans,
1249                                                   libraries.keys(),
1250                                                   'name',
1251                                                   library_select_field_name,
1252                                                   initial_value='none',
1253                                                   selected_value=str( library_id ).lower(),
1254                                                   refresh_on_change=True )
1255        # Get all accessible folders for the selected library, if one is indeed selected
1256        if selected_library:
1257            showable_folders = trans.app.security_agent.get_showable_folders( user,
1258                                                                              user.all_roles(),
1259                                                                              selected_library,
1260                                                                              [ trans.app.security_agent.permitted_actions.LIBRARY_ADD ],
1261                                                                              selected_hidden_folder_ids )
1262        if sample:
1263            # The user is editing the request, and may have previously selected a folder
1264            if sample.folder:
1265                selected_folder_id = sample.folder.id
1266            else:
1267                # If a library is selected but not a folder, use the library's root folder
1268                if sample.library:
1269                    selected_folder_id = sample.library.root_folder.id
1270                else:
1271                    # The user just selected a folder
1272                    selected_folder_id = params.get( folder_select_field_name, 'none' )
1273        elif folder_id:
1274            # TODO: not sure when this would be passed
1275                selected_folder_id = folder_id
1276        else:
1277            selected_folder_id = 'none'
1278        # TODO: Change the name of the library root folder to "Library root" to clarify to the
1279        # user that it is the root folder.  We probably should just change this in the Library code,
1280        # and update the data in the db.
1281        folder_select_field = build_select_field( trans,
1282                                                  showable_folders,
1283                                                  'name',
1284                                                  folder_select_field_name,
1285                                                  initial_value='none',
1286                                                  selected_value=selected_folder_id )
1287        return library_select_field, folder_select_field
1288    def __build_sample_state_id_select_field( self, trans, request, selected_value ):
1289        if selected_value == 'none':
1290            if request.samples:
1291                selected_value = trans.security.encode_id( request.samples[0].state.id )
1292            else:
1293                selected_value = trans.security.encode_id( request.type.states[0].id )
1294        return build_select_field( trans,
1295                                   objs=request.type.states,
1296                                   label_attr='name',
1297                                   select_field_name='sample_state_id',
1298                                   selected_value=selected_value,
1299                                   refresh_on_change=False )
1300    # ===== Methods for validation forms and fields =====
1301    def __validate_request( self, trans, cntrller, request ):
1302        """Validates the request entered by the user"""
1303        # TODO: Add checks for required sample fields here.
1304        empty_fields = []
1305        # Make sure required form fields are filled in.
1306        for index, field in enumerate( request.type.request_form.fields ):
1307            if field[ 'required' ] == 'required' and request.values.content[ index ] in [ '', None ]:
1308                empty_fields.append( field[ 'label' ] )
1309        if empty_fields:
1310            message = 'Complete the following fields of the request before submitting: '
1311            for ef in empty_fields:
1312                message += '<b>' + ef + '</b> '
1313            return message
1314        return None
1315    def __validate_barcode( self, trans, sample, barcode ):
1316        """
1317        Makes sure that the barcode about to be assigned to a sample is gobally unique.
1318        That is, barcodes must be unique across requests in Galaxy sample tracking.
1319        """
1320        message = ''
1321        unique = True
1322        for index in range( len( sample.request.samples ) ):
1323            # Check for empty bar code
1324            if not barcode.strip():
1325                message = 'Fill in the barcode for sample (%s).' % sample.name
1326                break
1327            # TODO: Add a unique constraint to sample.bar_code table column
1328            # Make sure bar code is unique
1329            for sample_has_bar_code in trans.sa_session.query( trans.model.Sample ) \
1330                                                        .filter( trans.model.Sample.table.c.bar_code == barcode ):
1331                if sample_has_bar_code and sample_has_bar_code.id != sample.id:
1332                    message = '''The bar code (%s) associated with the sample (%s) belongs to another sample. 
1333                                 Bar codes must be unique across all samples, so use a different bar code
1334                                 for this sample.''' % ( barcode, sample.name )
1335                    unique = False
1336                    break
1337            if not unique:
1338                break
1339        return message
1340    def __validate_email( self, email ):
1341        error = ''
1342        if len( email ) == 0 or "@" not in email or "." not in email:
1343            error = "(%s) is not a valid email address.  " % str( email )
1344        elif len( email ) > 255:
1345            error = "(%s) exceeds maximum allowable length.  " % str( email )
1346        return error
1347    # ===== Other miscellaneoud utility methods =====
1348    def __get_selected_samples( self, trans, request, **kwd ):
1349        selected_samples = []
1350        for sample in request.samples:
1351            if CheckboxField.is_checked( kwd.get( 'select_sample_%i' % sample.id, '' ) ):
1352                selected_samples.append( trans.security.encode_id( sample.id ) )
1353        return selected_samples
1354
1355# ===== Miscellaneoud utility methods outside of the RequestsCommon class =====
1356def invalid_id_redirect( trans, cntrller, obj_id, action='browse_requests' ):
1357    status = 'error'
1358    message = "Invalid request id (%s)" % str( obj_id )
1359    return trans.response.send_redirect( web.url_for( controller=cntrller,
1360                                                      action=action,
1361                                                      status=status,
1362                                                      message=message ) )
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。