root/galaxy-central/lib/galaxy/web/controllers/library_common.py @ 2

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

import galaxy-central

行番号 
1import os, os.path, shutil, urllib, StringIO, re, gzip, tempfile, shutil, zipfile, copy, glob, string
2from galaxy.web.base.controller import *
3from galaxy import util, jobs
4from galaxy.datatypes import sniff
5from galaxy.security import RBACAgent
6from galaxy.util.json import to_json_string
7from galaxy.tools.actions import upload_common
8from galaxy.model.orm import *
9from galaxy.util.streamball import StreamBall
10from galaxy.web.form_builder import AddressField, CheckboxField, SelectField, TextArea, TextField, WorkflowField
11import logging, tempfile, zipfile, tarfile, os, sys
12
13if sys.version_info[:2] < ( 2, 6 ):
14    zipfile.BadZipFile = zipfile.error
15if sys.version_info[:2] < ( 2, 5 ):
16    zipfile.LargeZipFile = zipfile.error
17
18log = logging.getLogger( __name__ )
19
20# Test for available compression types
21tmpd = tempfile.mkdtemp()
22comptypes = []
23for comptype in ( 'gz', 'bz2' ):
24    tmpf = os.path.join( tmpd, 'compression_test.tar.' + comptype )
25    try:
26        archive = tarfile.open( tmpf, 'w:' + comptype )
27        archive.close()
28        comptypes.append( comptype )
29    except tarfile.CompressionError:
30        log.exception( "Compression error when testing %s compression.  This option will be disabled for library downloads." % comptype )
31    try:
32        os.unlink( tmpf )
33    except OSError:
34        pass
35ziptype = '32'
36tmpf = os.path.join( tmpd, 'compression_test.zip' )
37try:
38    archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
39    archive.close()
40    comptypes.append( 'zip' )
41    ziptype = '64'
42except RuntimeError:
43    log.exception( "Compression error when testing zip compression. This option will be disabled for library downloads." )
44except (TypeError, zipfile.LargeZipFile):
45    # ZIP64 is only in Python2.5+.  Remove TypeError when 2.4 support is dropped
46    log.warning( 'Max zip file size is 2GB, ZIP64 not supported' )
47    comptypes.append( 'zip' )
48try:
49    os.unlink( tmpf )
50except OSError:
51    pass
52os.rmdir( tmpd )
53
54class LibraryCommon( BaseController, UsesFormDefinitionWidgets ):
55    @web.json
56    def library_item_updates( self, trans, ids=None, states=None ):
57        # Avoid caching
58        trans.response.headers['Pragma'] = 'no-cache'
59        trans.response.headers['Expires'] = '0'
60        # Create new HTML for any that have changed
61        rval = {}
62        if ids is not None and states is not None:
63            ids = map( int, ids.split( "," ) )
64            states = states.split( "," )
65            for id, state in zip( ids, states ):
66                data = trans.sa_session.query( self.app.model.LibraryDatasetDatasetAssociation ).get( id )
67                if data.state != state:
68                    job_ldda = data
69                    while job_ldda.copied_from_library_dataset_dataset_association:
70                        job_ldda = job_ldda.copied_from_library_dataset_dataset_association
71                    force_history_refresh = False
72                    rval[id] = {
73                        "state": data.state,
74                        "html": unicode( trans.fill_template( "library/common/library_item_info.mako", ldda=data ), 'utf-8' )
75                        #"force_history_refresh": force_history_refresh
76                    }
77        return rval
78    @web.expose
79    def browse_library( self, trans, cntrller, **kwd ):
80        params = util.Params( kwd )
81        message = util.restore_text( params.get( 'message', ''  ) )
82        status = params.get( 'status', 'done' )
83        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
84        library_id = params.get( 'id', None )
85        if not library_id:
86            # To handle bots
87            message = "You must specify a library id."
88            status = 'error'
89        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
90        current_user_roles = trans.get_current_user_roles()
91        try:
92            library = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( library_id ) )
93        except:
94            # Protect against attempts to phish for valid keys that return libraries
95            library = None
96        # Most security for browsing libraries is handled in the template, but do a basic check here.
97        if not library or not ( is_admin or trans.app.security_agent.can_access_library( current_user_roles, library ) ):
98            message = "Invalid library id ( %s ) specified." % str( library_id )
99            status = 'error'
100        else:
101            # If use_panels is True, the library is being accessed via an external link
102            # which did not originate from within the Galaxy instance, and the library will
103            # be displayed correctly with the mast head.
104            show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
105            created_ldda_ids = params.get( 'created_ldda_ids', '' )
106            hidden_folder_ids = util.listify( params.get( 'hidden_folder_ids', '' ) )
107            if created_ldda_ids and not message:
108                message = "%d datasets are uploading in the background to the library '%s' (each is selected).  "  % \
109                    ( len( created_ldda_ids.split( ',' ) ), library.name )
110                message += "Don't navigate away from Galaxy or use the browser's \"stop\" or \"reload\" buttons (on this tab) until the "
111                message += "message \"This job is running\" is cleared from the \"Information\" column below for each selected dataset."
112                status = "info"
113            comptypes_t = comptypes
114            if trans.app.config.nginx_x_archive_files_base:
115                comptypes_t = ['ngxzip']
116            for comptype in trans.app.config.disable_library_comptypes:
117                # TODO: do this once, not every time (we're gonna raise an
118                # exception every time after the first time)
119                try:
120                    comptypes_t.remove( comptype )
121                except:
122                    pass
123            return trans.fill_template( '/library/common/browse_library.mako',
124                                        cntrller=cntrller,
125                                        use_panels=use_panels,
126                                        library=library,
127                                        created_ldda_ids=created_ldda_ids,
128                                        hidden_folder_ids=hidden_folder_ids,
129                                        show_deleted=show_deleted,
130                                        comptypes=comptypes_t,
131                                        current_user_roles=current_user_roles,
132                                        message=message,
133                                        status=status )
134        return trans.response.send_redirect( web.url_for( use_panels=use_panels,
135                                                          controller=cntrller,
136                                                          action='browse_libraries',
137                                                          default_action=params.get( 'default_action', None ),
138                                                          message=util.sanitize_text( message ),
139                                                          status=status ) )
140    @web.expose
141    def library_info( self, trans, cntrller, **kwd ):
142        params = util.Params( kwd )
143        message = util.restore_text( params.get( 'message', ''  ) )
144        status = params.get( 'status', 'done' )
145        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
146        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
147        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
148        current_user_roles = trans.get_current_user_roles()
149        library_id = params.get( 'id', None )
150        try:
151            library = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( library_id ) )
152        except:
153            library = None
154        self._check_access( trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted )
155        if params.get( 'library_info_button', False ):
156            self._check_modify( trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted )
157            old_name = library.name
158            new_name = util.restore_text( params.get( 'name', 'No name' ) )
159            if not new_name:
160                message = 'Enter a valid name'
161                status='error'
162            else:
163                new_description = util.restore_text( params.get( 'description', '' ) )
164                new_synopsis = util.restore_text( params.get( 'synopsis', '' ) )
165                if new_synopsis in [ None, 'None' ]:
166                    new_synopsis = ''
167                library.name = new_name
168                library.description = new_description
169                library.synopsis = new_synopsis
170                # Rename the root_folder
171                library.root_folder.name = new_name
172                library.root_folder.description = new_description
173                trans.sa_session.add_all( ( library, library.root_folder ) )
174                trans.sa_session.flush()
175                message = "Information updated for library '%s'." % library.name
176                return trans.response.send_redirect( web.url_for( controller='library_common',
177                                                                  action='library_info',
178                                                                  cntrller=cntrller,
179                                                                  use_panels=use_panels,
180                                                                  id=trans.security.encode_id( library.id ),
181                                                                  show_deleted=show_deleted,
182                                                                  message=util.sanitize_text( message ),
183                                                                  status='done' ) )
184        # See if we have any associated templates
185        info_association, inherited = library.get_info_association()
186        widgets = library.get_template_widgets( trans )
187        widget_fields_have_contents = self.widget_fields_have_contents( widgets )
188        return trans.fill_template( '/library/common/library_info.mako',
189                                    cntrller=cntrller,
190                                    use_panels=use_panels,
191                                    library=library,
192                                    widgets=widgets,
193                                    widget_fields_have_contents=widget_fields_have_contents,
194                                    current_user_roles=current_user_roles,
195                                    show_deleted=show_deleted,
196                                    info_association=info_association,
197                                    inherited=inherited,
198                                    message=message,
199                                    status=status )
200    @web.expose
201    def library_permissions( self, trans, cntrller, **kwd ):
202        params = util.Params( kwd )
203        message = util.restore_text( params.get( 'message', ''  ) )
204        status = params.get( 'status', 'done' )
205        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
206        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
207        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
208        current_user_roles = trans.get_current_user_roles()
209        library_id = params.get( 'id', None )
210        try:
211            library = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( library_id ) )
212        except:
213            library = None
214        self._check_access( trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted )
215        self._check_manage( trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted )
216        if params.get( 'update_roles_button', False ):
217            # The user clicked the Save button on the 'Associate With Roles' form
218            permissions = {}
219            for k, v in trans.app.model.Library.permitted_actions.items():
220                in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( params.get( k + '_in', [] ) ) ]
221                permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
222            trans.app.security_agent.set_all_library_permissions( library, permissions )
223            trans.sa_session.refresh( library )
224            # Copy the permissions to the root folder
225            trans.app.security_agent.copy_library_permissions( library, library.root_folder )
226            message = "Permissions updated for library '%s'." % library.name
227            return trans.response.send_redirect( web.url_for( controller='library_common',
228                                                              action='library_permissions',
229                                                              cntrller=cntrller,
230                                                              use_panels=use_panels,
231                                                              id=trans.security.encode_id( library.id ),
232                                                              show_deleted=show_deleted,
233                                                              message=util.sanitize_text( message ),
234                                                              status='done' ) )
235        roles = trans.app.security_agent.get_legitimate_roles( trans, library, cntrller )
236        return trans.fill_template( '/library/common/library_permissions.mako',
237                                    cntrller=cntrller,
238                                    use_panels=use_panels,
239                                    library=library,
240                                    current_user_roles=current_user_roles,
241                                    roles=roles,
242                                    show_deleted=show_deleted,
243                                    message=message,
244                                    status=status )
245    @web.expose
246    def create_folder( self, trans, cntrller, parent_id, library_id, **kwd ):
247        params = util.Params( kwd )
248        message = util.restore_text( params.get( 'message', ''  ) )
249        status = params.get( 'status', 'done' )
250        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
251        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
252        is_admin = trans.user_is_admin() and cntrller in ( 'library_admin', 'api' )
253        current_user_roles = trans.get_current_user_roles()
254        try:
255            parent_folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( parent_id ) )
256        except:
257            parent_folder = None
258        # Check the library which actually contains the user-supplied parent folder, not the user-supplied
259        # library, which could be anything.
260        if parent_folder:
261            parent_library = parent_folder.parent_library
262        self._check_access( trans, cntrller, is_admin, parent_folder, current_user_roles, use_panels, library_id, show_deleted )
263        self._check_add( trans, cntrller, is_admin, parent_folder, current_user_roles, use_panels, library_id, show_deleted )
264        if params.get( 'new_folder_button', False ) or cntrller == 'api':
265            new_folder = trans.app.model.LibraryFolder( name=util.restore_text( params.name ),
266                                                        description=util.restore_text( params.description ) )
267            # We are associating the last used genome build with folders, so we will always
268            # initialize a new folder with the first dbkey in util.dbnames which is currently
269            # ?    unspecified (?)
270            new_folder.genome_build = util.dbnames.default_value
271            parent_folder.add_folder( new_folder )
272            trans.sa_session.add( new_folder )
273            trans.sa_session.flush()
274            # New folders default to having the same permissions as their parent folder
275            trans.app.security_agent.copy_library_permissions( parent_folder, new_folder )
276            # If we're creating in the API, we're done
277            if cntrller == 'api':
278                return 200, dict( created=new_folder )
279            # If we have an inheritable template, redirect to the folder_info page so information
280            # can be filled in immediately.
281            widgets = []
282            info_association, inherited = new_folder.get_info_association()
283            if info_association and ( not( inherited ) or info_association.inheritable ):
284                widgets = new_folder.get_template_widgets( trans )
285            if info_association:
286                message = "The new folder named '%s' has been added to the data library.  " % new_folder.name
287                message += "Additional information about this folder may be added using the inherited template."
288                return trans.fill_template( '/library/common/folder_info.mako',
289                                            cntrller=cntrller,
290                                            use_panels=use_panels,
291                                            folder=new_folder,
292                                            library_id=library_id,
293                                            widgets=widgets,
294                                            current_user_roles=current_user_roles,
295                                            show_deleted=show_deleted,
296                                            info_association=info_association,
297                                            inherited=inherited,
298                                            message=message,
299                                            status='done' )
300            # If not inheritable info_association, redirect to the library.
301            message = "The new folder named '%s' has been added to the data library." % new_folder.name
302            return trans.response.send_redirect( web.url_for( controller='library_common',
303                                                              action='browse_library',
304                                                              cntrller=cntrller,
305                                                              use_panels=use_panels,
306                                                              id=library_id,
307                                                              show_deleted=show_deleted,
308                                                              message=util.sanitize_text( message ),
309                                                              status='done' ) )
310        # We do not render any template widgets on creation pages since saving the info_association
311        # cannot occur before the associated item is saved.
312        return trans.fill_template( '/library/common/new_folder.mako',
313                                    cntrller=cntrller,
314                                    use_panels=use_panels,
315                                    library_id=library_id,
316                                    folder=parent_folder,
317                                    show_deleted=show_deleted,
318                                    message=message,
319                                    status=status )
320    @web.expose
321    def folder_info( self, trans, cntrller, id, library_id, **kwd ):
322        params = util.Params( kwd )
323        message = util.restore_text( params.get( 'message', ''  ) )
324        status = params.get( 'status', 'done' )
325        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
326        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
327        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
328        current_user_roles = trans.get_current_user_roles()
329        try:
330            folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( id ) )
331        except:
332            folder = None
333        self._check_access( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
334        if params.get( 'rename_folder_button', False ):
335            self._check_modify( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
336            old_name = folder.name
337            new_name = util.restore_text( params.name )
338            new_description = util.restore_text( params.description )
339            if not new_name:
340                message = 'Enter a valid name'
341                status='error'
342            else:
343                folder.name = new_name
344                folder.description = new_description
345                trans.sa_session.add( folder )
346                trans.sa_session.flush()
347                message = "Information updated for folder '%s'." % folder.name
348                return trans.response.send_redirect( web.url_for( controller='library_common',
349                                                                  action='folder_info',
350                                                                  cntrller=cntrller,
351                                                                  use_panels=use_panels,
352                                                                  id=id,
353                                                                  library_id=library_id,
354                                                                  show_deleted=show_deleted,
355                                                                  message=util.sanitize_text( message ),
356                                                                  status='done' ) )
357        # See if we have any associated templates
358        widgets = []
359        widget_fields_have_contents = False
360        info_association, inherited = folder.get_info_association()
361        if info_association and ( not( inherited ) or info_association.inheritable ):
362            widgets = folder.get_template_widgets( trans )
363            widget_fields_have_contents = self.widget_fields_have_contents( widgets )
364        return trans.fill_template( '/library/common/folder_info.mako',
365                                    cntrller=cntrller,
366                                    use_panels=use_panels,
367                                    folder=folder,
368                                    library_id=library_id,
369                                    widgets=widgets,
370                                    widget_fields_have_contents=widget_fields_have_contents,
371                                    current_user_roles=current_user_roles,
372                                    show_deleted=show_deleted,
373                                    info_association=info_association,
374                                    inherited=inherited,
375                                    message=message,
376                                    status=status )
377    @web.expose
378    def folder_permissions( self, trans, cntrller, id, library_id, **kwd ):
379        params = util.Params( kwd )
380        message = util.restore_text( params.get( 'message', ''  ) )
381        status = params.get( 'status', 'done' )
382        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
383        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
384        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
385        current_user_roles = trans.get_current_user_roles()
386        try:
387            folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( id ) )
388        except:
389            folder = None
390        self._check_access( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
391        self._check_manage( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
392        if params.get( 'update_roles_button', False ):
393            # The user clicked the Save button on the 'Associate With Roles' form
394            permissions = {}
395            for k, v in trans.app.model.Library.permitted_actions.items():
396                if k != 'LIBRARY_ACCESS':
397                    # LIBRARY_ACCESS is a special permission set only at the library level
398                    # and it is not inherited.
399                    in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( int( x ) ) for x in util.listify( params.get( k + '_in', [] ) ) ]
400                    permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
401            trans.app.security_agent.set_all_library_permissions( folder, permissions )
402            trans.sa_session.refresh( folder )
403            message = "Permissions updated for folder '%s'." % folder.name
404            return trans.response.send_redirect( web.url_for( controller='library_common',
405                                                              action='folder_permissions',
406                                                              cntrller=cntrller,
407                                                              use_panels=use_panels,
408                                                              id=trans.security.encode_id( folder.id ),
409                                                              library_id=library_id,
410                                                              show_deleted=show_deleted,
411                                                              message=util.sanitize_text( message ),
412                                                              status='done' ) )
413        # If the library is public all roles are legitimate, but if the library
414        # is restricted, only those roles associated with the LIBRARY_ACCESS
415        # permission are legitimate.
416        roles = trans.app.security_agent.get_legitimate_roles( trans, folder.parent_library, cntrller )
417        return trans.fill_template( '/library/common/folder_permissions.mako',
418                                    cntrller=cntrller,
419                                    use_panels=use_panels,
420                                    folder=folder,
421                                    library_id=library_id,
422                                    current_user_roles=current_user_roles,
423                                    roles=roles,
424                                    show_deleted=show_deleted,
425                                    message=message,
426                                    status=status )
427    @web.expose
428    def ldda_edit_info( self, trans, cntrller, library_id, folder_id, id, **kwd ):
429        params = util.Params( kwd )
430        message = util.restore_text( params.get( 'message', ''  ) )
431        status = params.get( 'status', 'done' )
432        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
433        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
434        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
435        current_user_roles = trans.get_current_user_roles()
436        try:
437            ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( id ) )
438        except:
439            ldda = None
440        self._check_access( trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted )
441        self._check_modify( trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted )
442        dbkey = params.get( 'dbkey', '?' )
443        if isinstance( dbkey, list ):
444            dbkey = dbkey[0]
445        file_formats = [ dtype_name for dtype_name, dtype_value in trans.app.datatypes_registry.datatypes_by_extension.iteritems() if dtype_value.allow_datatype_change ]
446        file_formats.sort()
447        # See if we have any associated templates
448        widgets = []
449        info_association, inherited = ldda.get_info_association()
450        if info_association and ( not( inherited ) or info_association.inheritable ):
451            widgets = ldda.get_template_widgets( trans )
452        if params.get( 'change', False ):
453            # The user clicked the Save button on the 'Change data type' form
454            if ldda.datatype.allow_datatype_change and trans.app.datatypes_registry.get_datatype_by_extension( params.datatype ).allow_datatype_change:
455                trans.app.datatypes_registry.change_datatype( ldda, params.datatype )
456                trans.sa_session.flush()
457                message = "Data type changed for library dataset '%s'." % ldda.name
458                status = 'done'
459            else:
460                message = "You are unable to change datatypes in this manner. Changing %s to %s is not allowed." % ( ldda.extension, params.datatype )
461                status = 'error'
462        elif params.get( 'save', False ):
463            # The user clicked the Save button on the 'Edit Attributes' form
464            old_name = ldda.name
465            new_name = util.restore_text( params.get( 'name', '' ) )
466            new_info = util.restore_text( params.get( 'info', '' ) )
467            new_message = util.restore_text( params.get( 'message', '' ) )
468            if not new_name:
469                message = 'Enter a valid name'
470                status = 'error'
471            else:
472                ldda.name = new_name
473                ldda.info = new_info
474                ldda.message = new_message
475                # The following for loop will save all metadata_spec items
476                for name, spec in ldda.datatype.metadata_spec.items():
477                    if spec.get("readonly"):
478                        continue
479                    optional = params.get( "is_" + name, None )
480                    if optional and optional == 'true':
481                        # optional element... == 'true' actually means it is NOT checked (and therefore ommitted)
482                        setattr( ldda.metadata, name, None )
483                    else:
484                        setattr( ldda.metadata, name, spec.unwrap( params.get ( name, None ) ) )
485                ldda.metadata.dbkey = dbkey
486                ldda.datatype.after_setting_metadata( ldda )
487                trans.sa_session.flush()
488                message = "Attributes updated for library dataset '%s'." % ldda.name
489                status = 'done'
490        elif params.get( 'detect', False ):
491            # The user clicked the Auto-detect button on the 'Edit Attributes' form
492            for name, spec in ldda.datatype.metadata_spec.items():
493                # We need to be careful about the attributes we are resetting
494                if name not in [ 'name', 'info', 'dbkey' ]:
495                    if spec.get( 'default' ):
496                        setattr( ldda.metadata, name, spec.unwrap( spec.get( 'default' ) ) )
497            ldda.datatype.set_meta( ldda )
498            ldda.datatype.after_setting_metadata( ldda )
499            trans.sa_session.flush()
500            message = "Information updated for library dataset '%s'." % ldda.name
501            status = 'done'
502        if "dbkey" in ldda.datatype.metadata_spec and not ldda.metadata.dbkey:
503            # Copy dbkey into metadata, for backwards compatability
504            # This looks like it does nothing, but getting the dbkey
505            # returns the metadata dbkey unless it is None, in which
506            # case it resorts to the old dbkey.  Setting the dbkey
507            # sets it properly in the metadata
508            ldda.metadata.dbkey = ldda.dbkey
509        return trans.fill_template( "/library/common/ldda_edit_info.mako",
510                                    cntrller=cntrller,
511                                    use_panels=use_panels,
512                                    ldda=ldda,
513                                    library_id=library_id,
514                                    file_formats=file_formats,
515                                    widgets=widgets,
516                                    current_user_roles=current_user_roles,
517                                    show_deleted=show_deleted,
518                                    info_association=info_association,
519                                    inherited=inherited,
520                                    message=message,
521                                    status=status )
522    @web.expose
523    def ldda_info( self, trans, cntrller, library_id, folder_id, id, **kwd ):
524        params = util.Params( kwd )
525        message = util.restore_text( params.get( 'message', ''  ) )
526        status = params.get( 'status', 'done' )
527        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
528        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
529        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
530        current_user_roles = trans.get_current_user_roles()
531        try:
532            ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( id ) )
533        except:
534            ldda = None
535        self._check_access( trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted )
536        if is_admin:
537            # Get all associated hdas and lddas that use the same disk file.
538            associated_hdas = trans.sa_session.query( trans.model.HistoryDatasetAssociation ) \
539                                              .filter( and_( trans.model.HistoryDatasetAssociation.deleted == False,
540                                                             trans.model.HistoryDatasetAssociation.dataset_id == ldda.dataset_id ) ) \
541                                              .all()
542            associated_lddas = trans.sa_session.query( trans.model.LibraryDatasetDatasetAssociation ) \
543                                               .filter( and_( trans.model.LibraryDatasetDatasetAssociation.deleted == False,
544                                                              trans.model.LibraryDatasetDatasetAssociation.dataset_id == ldda.dataset_id,
545                                                              trans.model.LibraryDatasetDatasetAssociation.id != ldda.id ) ) \
546                                               .all()
547        else:
548            associated_hdas = []
549            associated_lddas = []
550        # See if we have any associated templates
551        widgets = []
552        widget_fields_have_contents = False
553        info_association, inherited = ldda.get_info_association()
554        if info_association and ( not( inherited ) or info_association.inheritable ):
555            widgets = ldda.get_template_widgets( trans )
556            widget_fields_have_contents = self.widget_fields_have_contents( widgets )
557        return trans.fill_template( '/library/common/ldda_info.mako',
558                                    cntrller=cntrller,
559                                    use_panels=use_panels,
560                                    ldda=ldda,
561                                    library=ldda.library_dataset.folder.parent_library,
562                                    associated_hdas=associated_hdas,
563                                    associated_lddas=associated_lddas,
564                                    show_deleted=show_deleted,
565                                    widgets=widgets,
566                                    widget_fields_have_contents=widget_fields_have_contents,
567                                    current_user_roles=current_user_roles,
568                                    info_association=info_association,
569                                    inherited=inherited,
570                                    message=message,
571                                    status=status )
572    @web.expose
573    def ldda_permissions( self, trans, cntrller, library_id, folder_id, id, **kwd ):
574        params = util.Params( kwd )
575        message = util.restore_text( params.get( 'message', ''  ) )
576        status = params.get( 'status', 'done' )
577        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
578        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
579        ids = util.listify( id )
580        lddas = []
581        libraries = []
582        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
583        current_user_roles = trans.get_current_user_roles()
584        for id in ids:
585            try:
586                ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( id ) )
587            except:
588                ldda = None
589            if ldda:
590                library = ldda.library_dataset.folder.parent_library
591            self._check_access( trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted )
592            lddas.append( ldda )
593            libraries.append( library )
594        library = libraries[0]
595        if filter( lambda x: x != library, libraries ):
596            message = "Library datasets specified span multiple libraries."
597            return trans.response.send_redirect( web.url_for( controller='library_common',
598                                                              action='browse_library',
599                                                              id=library_id,
600                                                              cntrller=cntrller,
601                                                              use_panels=use_panels,
602                                                              message=util.sanitize_text( message ),
603                                                              status='error' ) )
604        # If access to the dataset is restricted, then use the roles associated with the DATASET_ACCESS permission to
605        # determine the legitimate roles.  If the dataset is public, see if access to the library is restricted.  If
606        # it is, use the roles associated with the LIBRARY_ACCESS permission to determine the legitimate roles.  If both
607        # the dataset and the library are public, all roles are legitimate.  All of the datasets will have the same
608        # permissions at this point.
609        ldda = lddas[0]
610        if trans.app.security_agent.dataset_is_public( ldda.dataset ):
611            # The dataset is public, so check access to the library
612            roles = trans.app.security_agent.get_legitimate_roles( trans, library, cntrller )
613        else:
614            roles = trans.app.security_agent.get_legitimate_roles( trans, ldda.dataset, cntrller )
615        if params.get( 'update_roles_button', False ):
616            a = trans.app.security_agent.get_action( trans.app.security_agent.permitted_actions.DATASET_ACCESS.action )
617            permissions, in_roles, error, message = \
618                trans.app.security_agent.derive_roles_from_access( trans, trans.app.security.decode_id( library_id ), cntrller, library=True, **kwd )
619            for ldda in lddas:
620                # Set the DATASET permissions on the Dataset.
621                if error:
622                    # Keep the original role associations for the DATASET_ACCESS permission on the ldda.
623                    permissions[ a ] = ldda.get_access_roles( trans )
624                trans.app.security_agent.set_all_dataset_permissions( ldda.dataset, permissions )
625                trans.sa_session.refresh( ldda.dataset )
626            # Set the LIBRARY permissions on the LibraryDataset.  The LibraryDataset and
627            # LibraryDatasetDatasetAssociation will be set with the same permissions.
628            permissions = {}
629            for k, v in trans.app.model.Library.permitted_actions.items():
630                if k != 'LIBRARY_ACCESS':
631                    # LIBRARY_ACCESS is a special permission set only at the library level and it is not inherited.
632                    in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
633                    permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
634            for ldda in lddas:
635                trans.app.security_agent.set_all_library_permissions( ldda.library_dataset, permissions )
636                trans.sa_session.refresh( ldda.library_dataset )
637                # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
638                trans.app.security_agent.set_all_library_permissions( ldda, permissions )
639                trans.sa_session.refresh( ldda )
640            if error:
641                status = 'error'
642            else:
643                if len( lddas ) == 1:
644                    message = "Permissions updated for dataset '%s'." % ldda.name
645                else:
646                    message = 'Permissions updated for %d datasets.' % len( lddas )
647                status= 'done'
648            return trans.fill_template( "/library/common/ldda_permissions.mako",
649                                        cntrller=cntrller,
650                                        use_panels=use_panels,
651                                        lddas=lddas,
652                                        library_id=library_id,
653                                        roles=roles,
654                                        show_deleted=show_deleted,
655                                        message=message,
656                                        status=status )
657        if len( ids ) > 1:
658            # Ensure that the permissions across all library items are identical, otherwise we can't update them together.
659            check_list = []
660            for ldda in lddas:
661                permissions = []
662                # Check the library level permissions - the permissions on the LibraryDatasetDatasetAssociation
663                # will always be the same as the permissions on the associated LibraryDataset.
664                for library_permission in trans.app.security_agent.get_permissions( ldda.library_dataset ):
665                    if library_permission.action not in permissions:
666                        permissions.append( library_permission.action )
667                for dataset_permission in trans.app.security_agent.get_permissions( ldda.dataset ):
668                    if dataset_permission.action not in permissions:
669                        permissions.append( dataset_permission.action )
670                permissions.sort()
671                if not check_list:
672                    check_list = permissions
673                if permissions != check_list:
674                    message = 'The datasets you selected do not have identical permissions, so they can not be updated together'
675                    trans.response.send_redirect( web.url_for( controller='library_common',
676                                                               action='browse_library',
677                                                               cntrller=cntrller,
678                                                               use_panels=use_panels,
679                                                               id=library_id,
680                                                               show_deleted=show_deleted,
681                                                               message=util.sanitize_text( message ),
682                                                               status='error' ) )
683        # Display permission form, permissions will be updated for all lddas simultaneously.
684        return trans.fill_template( "/library/common/ldda_permissions.mako",
685                                    cntrller=cntrller,
686                                    use_panels=use_panels,
687                                    lddas=lddas,
688                                    library_id=library_id,
689                                    roles=roles,
690                                    show_deleted=show_deleted,
691                                    message=message,
692                                    status=status )
693    @web.expose
694    def upload_library_dataset( self, trans, cntrller, library_id, folder_id, **kwd ):
695        params = util.Params( kwd )
696        message = util.restore_text( params.get( 'message', ''  ) )
697        status = params.get( 'status', 'done' )
698        ldda_message = util.restore_text( params.get( 'ldda_message', '' ) )
699        deleted = util.string_as_bool( params.get( 'deleted', False ) )
700        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
701        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
702        replace_id = params.get( 'replace_id', None )
703        replace_dataset = None
704        upload_option = params.get( 'upload_option', 'upload_file' )
705        if params.get( 'files_0|space_to_tab', False ):
706            space_to_tab = params.get( 'files_0|space_to_tab', '' )
707        else:
708            space_to_tab = params.get( 'space_to_tab', '' )
709        link_data_only = params.get( 'link_data_only', '' )
710        dbkey = params.get( 'dbkey', '?' )
711        if isinstance( dbkey, list ):
712            last_used_build = dbkey[0]
713        else:
714            last_used_build = dbkey
715        roles = params.get( 'roles', '' )
716        is_admin = trans.user_is_admin() and cntrller in ( 'library_admin', 'api' )
717        current_user_roles = trans.get_current_user_roles()
718        if replace_id not in [ None, 'None' ]:
719            try:
720                replace_dataset = trans.sa_session.query( trans.app.model.LibraryDataset ).get( trans.security.decode_id( replace_id ) )
721            except:
722                replace_dataset = None
723            self._check_access( trans, cntrller, is_admin, replace_dataset, current_user_roles, use_panels, library_id, show_deleted )
724            self._check_modify( trans, cntrller, is_admin, replace_dataset, current_user_roles, use_panels, library_id, show_deleted )
725            library = replace_dataset.folder.parent_library
726            folder = replace_dataset.folder
727            # The name is stored - by the time the new ldda is created, replace_dataset.name
728            # will point to the new ldda, not the one it's replacing.
729            replace_dataset_name = replace_dataset.name
730            if not last_used_build:
731                last_used_build = replace_dataset.library_dataset_dataset_association.dbkey
732            # Don't allow multiple datasets to be uploaded when replacing a dataset with a new version
733            upload_option = 'upload_file'
734        else:
735            folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) )
736            self._check_access( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
737            self._check_add( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
738            library = folder.parent_library
739        if folder and last_used_build in [ 'None', None, '?' ]:
740            last_used_build = folder.genome_build
741        if params.get( 'runtool_btn', False ) or params.get( 'ajax_upload', False ) or cntrller == 'api':
742            error = False
743            if upload_option == 'upload_paths' and not trans.app.config.allow_library_path_paste:
744                error = True
745                message = '"allow_library_path_paste" is not defined in the Galaxy configuration file'
746            elif roles:
747                # Check to see if the user selected roles to associate with the DATASET_ACCESS permission
748                # on the dataset that would cause accessibility issues.
749                vars = dict( DATASET_ACCESS_in=roles )
750                permissions, in_roles, error, message = \
751                    trans.app.security_agent.derive_roles_from_access( trans, library.id, cntrller, library=True, **vars )
752            if error:
753                if cntrller == 'api':
754                    return 400, message
755                trans.response.send_redirect( web.url_for( controller='library_common',
756                                                           action='upload_library_dataset',
757                                                           cntrller=cntrller,
758                                                           library_id=library_id,
759                                                           folder_id=folder_id,
760                                                           replace_id=replace_id,
761                                                           upload_option=upload_option,
762                                                           show_deleted=show_deleted,
763                                                           message=util.sanitize_text( message ),
764                                                           status='error' ) )
765
766            else:
767                # See if we have any inherited templates.
768                info_association, inherited = folder.get_info_association( inherited=True )
769                if info_association and info_association.inheritable:
770                    template_id = str( info_association.template.id )
771                    widgets = folder.get_template_widgets( trans, get_contents=True )
772                    processed_widgets = []
773                    # The list of widgets may include an AddressField which we need to save if it is new
774                    for index, widget_dict in enumerate( widgets ):
775                        widget = widget_dict[ 'widget' ]
776                        if isinstance( widget, AddressField ):
777                            value = util.restore_text( params.get( 'field_%i' % index, '' ) )
778                            if value == 'new':
779                                if self.field_param_values_ok( index, 'AddressField', **kwd ):
780                                    # Save the new address
781                                    address = trans.app.model.UserAddress( user=trans.user )
782                                    self.save_widget_field( trans, address, index, **kwd )
783                                    widget.value = str( address.id )
784                                    widget_dict[ 'widget' ] = widget
785                                    processed_widgets.append( widget_dict )
786                                    # It is now critical to update the value of 'field_%i', replacing the string
787                                    # 'new' with the new address id.  This is necessary because the upload_dataset()
788                                    # method below calls the handle_library_params() method, which does not parse the
789                                    # widget fields, it instead pulls form values from kwd.  See the FIXME comments in the
790                                    # handle_library_params() method, and the CheckboxField code in the next conditional.
791                                    kwd[ 'field_%i' % index ] = str( address.id )
792                                else:
793                                    # The invalid address won't be saved, but we cannot display error
794                                    # messages on the upload form due to the ajax upload already occurring.
795                                    # When we re-engineer the upload process ( currently under way ), we
796                                    # will be able to check the form values before the ajax upload occurs
797                                    # in the background.  For now, we'll do nothing...
798                                    pass
799                        elif isinstance( widget, CheckboxField ):
800                            # We need to check the value from kwd since util.Params would have munged the list if
801                            # the checkbox is checked.
802                            value = kwd.get( 'field_%i' % index, '' )
803                            if CheckboxField.is_checked( value ):
804                                widget.value = 'true'
805                                widget_dict[ 'widget' ] = widget
806                                processed_widgets.append( widget_dict )
807                                kwd[ 'field_%i' % index ] = 'true'
808                        else:
809                            processed_widgets.append( widget_dict )
810                    widgets = processed_widgets
811                else:
812                    template_id = 'None'
813                    widgets = []
814                created_outputs_dict = trans.webapp.controllers[ 'library_common' ].upload_dataset( trans,
815                                                                                                    cntrller=cntrller,
816                                                                                                    library_id=trans.security.encode_id( library.id ),
817                                                                                                    folder_id=trans.security.encode_id( folder.id ),
818                                                                                                    template_id=template_id,
819                                                                                                    widgets=widgets,
820                                                                                                    replace_dataset=replace_dataset,
821                                                                                                    **kwd )
822                if created_outputs_dict:
823                    if cntrller == 'api':
824                        # created_outputs_dict can only ever be a string if cntrller == 'api'
825                        if type( created_outputs_dict ) == str:
826                            return 400, created_outputs_dict
827                        return 200, created_outputs_dict
828                    total_added = len( created_outputs_dict.keys() )
829                    ldda_id_list = [ str( v.id ) for k, v in created_outputs_dict.items() ]
830                    created_ldda_ids=",".join( ldda_id_list )
831                    if replace_dataset:
832                        message = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset_name, folder.name )
833                    else:
834                        if not folder.parent:
835                            # Libraries have the same name as their root_folder
836                            message = "Added %d datasets to the library '%s' (each is selected).  " % ( total_added, folder.name )
837                        else:
838                            message = "Added %d datasets to the folder '%s' (each is selected).  " % ( total_added, folder.name )
839                        if cntrller == 'library_admin':
840                            message += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
841                            status='done'
842                        else:
843                            # Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need
844                            # to check one of them to see if the current user can manage permissions on them.
845                            check_ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( ldda_id_list[0] )
846                            if trans.app.security_agent.can_manage_library_item( current_user_roles, check_ldda ):
847                                if replace_dataset:
848                                    default_action = ''
849                                else:
850                                    message += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
851                                    default_action = 'manage_permissions'
852                            else:
853                                default_action = 'add'
854                            trans.response.send_redirect( web.url_for( controller='library_common',
855                                                                       action='browse_library',
856                                                                       cntrller=cntrller,
857                                                                       id=library_id,
858                                                                       default_action=default_action,
859                                                                       created_ldda_ids=created_ldda_ids,
860                                                                       show_deleted=show_deleted,
861                                                                       message=util.sanitize_text( message ),
862                                                                       status='done' ) )
863                else:
864                    created_ldda_ids = ''
865                    message = "Upload failed"
866                    status='error'
867                    if cntrller == 'api':
868                        return 400, message
869                    response_code = 400
870                trans.response.send_redirect( web.url_for( controller='library_common',
871                                                           action='browse_library',
872                                                           cntrller=cntrller,
873                                                           id=library_id,
874                                                           created_ldda_ids=created_ldda_ids,
875                                                           show_deleted=show_deleted,
876                                                           message=util.sanitize_text( message ),
877                                                           status=status ) )
878        # Note: if the upload form was submitted due to refresh_on_change for a form field, we cannot re-populate
879        # the field for the selected file ( files_0|file_data ) if the user selected one.  This is because the value
880        # attribute of the html input file type field is typically ignored by browsers as a security precaution.
881       
882        # See if we have any inherited templates.
883        info_association, inherited = folder.get_info_association( inherited=True )
884        if info_association and info_association.inheritable:
885            widgets = folder.get_template_widgets( trans, get_contents=True )
886            # Retain contents of widget fields when form was submitted via refresh_on_change.
887            widgets = self.populate_widgets_from_kwd( trans, widgets, **kwd )
888        else:
889            widgets = []
890        # Send list of data formats to the upload form so the "extension" select list can be populated dynamically
891        file_formats = trans.app.datatypes_registry.upload_file_formats
892        # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
893        def get_dbkey_options( last_used_build ):
894            for dbkey, build_name in util.dbnames:
895                yield build_name, dbkey, ( dbkey==last_used_build )
896        dbkeys = get_dbkey_options( last_used_build )
897        # Send the current history to the form to enable importing datasets from history to library
898        history = trans.get_history()
899        trans.sa_session.refresh( history )
900        if upload_option == 'upload_file' and trans.app.config.nginx_upload_path:
901            # If we're using nginx upload, override the form action -
902            # url_for is intentionally not used on the base URL here -
903            # nginx_upload_path is expected to include the proxy prefix if the
904            # administrator intends for it to be part of the URL.
905            action = trans.app.config.nginx_upload_path + '?nginx_redir=' + web.url_for( controller='library_common', action='upload_library_dataset' )
906        else:
907            action = web.url_for( controller='library_common', action='upload_library_dataset' )
908        upload_option_select_list = self._build_upload_option_select_list( trans, upload_option )
909        roles_select_list = self._build_roles_select_list( trans, cntrller, library, util.listify( roles ) )
910        return trans.fill_template( '/library/common/upload.mako',
911                                    cntrller=cntrller,
912                                    upload_option_select_list=upload_option_select_list,
913                                    upload_option=upload_option,
914                                    action=action,
915                                    library_id=library_id,
916                                    folder_id=folder_id,
917                                    replace_dataset=replace_dataset,
918                                    file_formats=file_formats,
919                                    dbkeys=dbkeys,
920                                    last_used_build=last_used_build,
921                                    roles_select_list=roles_select_list,
922                                    history=history,
923                                    widgets=widgets,
924                                    space_to_tab=space_to_tab,
925                                    link_data_only=link_data_only,
926                                    show_deleted=show_deleted,
927                                    ldda_message=ldda_message,
928                                    message=message,
929                                    status=status )
930    def upload_dataset( self, trans, cntrller, library_id, folder_id, replace_dataset=None, **kwd ):
931        # Set up the traditional tool state/params
932        tool_id = 'upload1'
933        tool = trans.app.toolbox.tools_by_id[ tool_id ]
934        state = tool.new_state( trans )
935        errors = tool.update_state( trans, tool.inputs_by_page[0], state.inputs, kwd )
936        tool_params = state.inputs
937        dataset_upload_inputs = []
938        for input_name, input in tool.inputs.iteritems():
939            if input.type == "upload_dataset":
940                dataset_upload_inputs.append( input )
941        # Library-specific params
942        params = util.Params( kwd ) # is this filetoolparam safe?
943        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
944        message = util.restore_text( params.get( 'message', ''  ) )
945        status = params.get( 'status', 'done' )
946        server_dir = util.restore_text( params.get( 'server_dir', '' ) )
947        if replace_dataset not in [ None, 'None' ]:
948            replace_id = trans.security.encode_id( replace_dataset.id )
949        else:
950            replace_id = None
951        upload_option = params.get( 'upload_option', 'upload_file' )
952        response_code = 200
953        if upload_option == 'upload_directory':
954            if server_dir in [ None, 'None', '' ]:
955                response_code = 400
956            if cntrller == 'library_admin' or ( cntrller == 'api' and trans.user_is_admin ):
957                import_dir = trans.app.config.library_import_dir
958                import_dir_desc = 'library_import_dir'
959                full_dir = os.path.join( import_dir, server_dir )
960            else:
961                import_dir = trans.app.config.user_library_import_dir
962                import_dir_desc = 'user_library_import_dir'
963                if server_dir == trans.user.email:
964                    full_dir = os.path.join( import_dir, server_dir )
965                else:
966                    full_dir = os.path.join( import_dir, trans.user.email, server_dir )
967            if import_dir:
968                message = 'Select a directory'
969            else:
970                response_code = 403
971                message = '"%s" is not defined in the Galaxy configuration file' % import_dir_desc
972        elif upload_option == 'upload_paths':
973            if not trans.app.config.allow_library_path_paste:
974                response_code = 403
975                message = '"allow_library_path_paste" is not defined in the Galaxy configuration file'
976        # Some error handling should be added to this method.
977        try:
978            # FIXME: instead of passing params here ( chiech have been process by util.Params(), the original kwd
979            # should be passed so that complex objects that may have been included in the initial request remain.
980            library_bunch = upload_common.handle_library_params( trans, params, folder_id, replace_dataset )
981        except:
982            response_code = 500
983            message = "Unable to parse upload parameters, please report this error."
984        # Proceed with (mostly) regular upload processing if we're still errorless
985        if response_code == 200:
986            precreated_datasets = upload_common.get_precreated_datasets( trans, tool_params, trans.app.model.LibraryDatasetDatasetAssociation, controller=cntrller )
987            if upload_option == 'upload_file':
988                tool_params = upload_common.persist_uploads( tool_params )
989                uploaded_datasets = upload_common.get_uploaded_datasets( trans, cntrller, tool_params, precreated_datasets, dataset_upload_inputs, library_bunch=library_bunch )
990            elif upload_option == 'upload_directory':
991                uploaded_datasets, response_code, message = self.get_server_dir_uploaded_datasets( trans, cntrller, params, full_dir, import_dir_desc, library_bunch, response_code, message )
992            elif upload_option == 'upload_paths':
993                uploaded_datasets, response_code, message = self.get_path_paste_uploaded_datasets( trans, cntrller, params, library_bunch, response_code, message )
994            upload_common.cleanup_unused_precreated_datasets( precreated_datasets )
995            if upload_option == 'upload_file' and not uploaded_datasets:
996                response_code = 400
997                message = 'Select a file, enter a URL or enter text'
998        if response_code != 200:
999            if cntrller == 'api':
1000                return ( response_code, message )
1001            trans.response.send_redirect( web.url_for( controller='library_common',
1002                                                       action='upload_library_dataset',
1003                                                       cntrller=cntrller,
1004                                                       library_id=library_id,
1005                                                       folder_id=folder_id,
1006                                                       replace_id=replace_id,
1007                                                       upload_option=upload_option,
1008                                                       show_deleted=show_deleted,
1009                                                       message=util.sanitize_text( message ),
1010                                                       status='error' ) )
1011        json_file_path = upload_common.create_paramfile( trans, uploaded_datasets )
1012        data_list = [ ud.data for ud in uploaded_datasets ]
1013        return upload_common.create_job( trans, tool_params, tool, json_file_path, data_list, folder=library_bunch.folder )
1014    def make_library_uploaded_dataset( self, trans, cntrller, params, name, path, type, library_bunch, in_folder=None ):
1015        library_bunch.replace_dataset = None # not valid for these types of upload
1016        uploaded_dataset = util.bunch.Bunch()
1017        # Remove compressed file extensions, if any
1018        new_name = name
1019        if new_name.endswith( '.gz' ):
1020            new_name = new_name.rstrip( '.gz' )
1021        elif new_name.endswith( '.zip' ):
1022            new_name = new_name.rstrip( '.zip' )
1023        uploaded_dataset.name = new_name
1024        uploaded_dataset.path = path
1025        uploaded_dataset.type = type
1026        uploaded_dataset.ext = None
1027        uploaded_dataset.file_type = params.file_type
1028        uploaded_dataset.dbkey = params.dbkey
1029        uploaded_dataset.space_to_tab = params.space_to_tab
1030        if in_folder:
1031            uploaded_dataset.in_folder = in_folder
1032        uploaded_dataset.data = upload_common.new_upload( trans, cntrller, uploaded_dataset, library_bunch )
1033        if params.get( 'link_data_only', False ):
1034            uploaded_dataset.link_data_only = True
1035            uploaded_dataset.data.file_name = os.path.abspath( path )
1036            # Since we are not copying the file into Galaxy's managed
1037            # default file location, the dataset should never be purgable.
1038            uploaded_dataset.data.dataset.purgable = False
1039            trans.sa_session.add_all( ( uploaded_dataset.data, uploaded_dataset.data.dataset ) )
1040            trans.sa_session.flush()
1041        return uploaded_dataset
1042    def get_server_dir_uploaded_datasets( self, trans, cntrller, params, full_dir, import_dir_desc, library_bunch, response_code, message ):
1043        files = []
1044        try:
1045            for entry in os.listdir( full_dir ):
1046                # Only import regular files
1047                path = os.path.join( full_dir, entry )
1048                if os.path.islink( full_dir ) and params.get( 'link_data_only', False ):
1049                    # If we're linking instead of copying and the
1050                    # sub-"directory" in the import dir is actually a symlink,
1051                    # dereference the symlink, but not any of its contents.
1052                    link_path = os.readlink( full_dir )
1053                    if os.path.isabs( link_path ):
1054                        path = os.path.join( link_path, entry )
1055                    else:
1056                        path = os.path.abspath( os.path.join( link_path, entry ) )
1057                elif os.path.islink( path ) and os.path.isfile( path ) and params.get( 'link_data_only', False ):
1058                    # If we're linking instead of copying and the "file" in the
1059                    # sub-directory of the import dir is actually a symlink,
1060                    # dereference the symlink (one dereference only, Vasili).
1061                    link_path = os.readlink( path )
1062                    if os.path.isabs( link_path ):
1063                        path = link_path
1064                    else:
1065                        path = os.path.abspath( os.path.join( os.path.dirname( path ), link_path ) )
1066                if os.path.isfile( path ):
1067                    files.append( path )
1068        except Exception, e:
1069            message = "Unable to get file list for configured %s, error: %s" % ( import_dir_desc, str( e ) )
1070            response_code = 500
1071            return None, response_code, message
1072        if not files:
1073            message = "The directory '%s' contains no valid files" % full_dir
1074            response_code = 400
1075            return None, response_code, message
1076        uploaded_datasets = []
1077        for file in files:
1078            name = os.path.basename( file )
1079            uploaded_datasets.append( self.make_library_uploaded_dataset( trans, cntrller, params, name, file, 'server_dir', library_bunch ) )
1080        return uploaded_datasets, 200, None
1081    def get_path_paste_uploaded_datasets( self, trans, cntrller, params, library_bunch, response_code, message ):
1082        if params.get( 'filesystem_paths', '' ) == '':
1083            message = "No paths entered in the upload form"
1084            response_code = 400
1085            return None, response_code, message
1086        preserve_dirs = True
1087        if params.get( 'dont_preserve_dirs', False ):
1088            preserve_dirs = False
1089        # locate files
1090        bad_paths = []
1091        uploaded_datasets = []
1092        for line in [ l.strip() for l in params.filesystem_paths.splitlines() if l.strip() ]:
1093            path = os.path.abspath( line )
1094            if not os.path.exists( path ):
1095                bad_paths.append( path )
1096                continue
1097            # don't bother processing if we're just going to return an error
1098            if not bad_paths:
1099                if os.path.isfile( path ):
1100                    name = os.path.basename( path )
1101                    uploaded_datasets.append( self.make_library_uploaded_dataset( trans, cntrller, params, name, path, 'path_paste', library_bunch ) )
1102                for basedir, dirs, files in os.walk( line ):
1103                    for file in files:
1104                        file_path = os.path.abspath( os.path.join( basedir, file ) )
1105                        if preserve_dirs:
1106                            in_folder = os.path.dirname( file_path.replace( path, '', 1 ).lstrip( '/' ) )
1107                        else:
1108                            in_folder = None
1109                        uploaded_datasets.append( self.make_library_uploaded_dataset( trans,
1110                                                                                      cntrller,
1111                                                                                      params,
1112                                                                                      file,
1113                                                                                      file_path,
1114                                                                                      'path_paste',
1115                                                                                      library_bunch,
1116                                                                                      in_folder ) )
1117        if bad_paths:
1118            message = "Invalid paths:<br><ul><li>%s</li></ul>" % "</li><li>".join( bad_paths )
1119            response_code = 400
1120            return None, response_code, message
1121        return uploaded_datasets, 200, None
1122    @web.expose
1123    def add_history_datasets_to_library( self, trans, cntrller, library_id, folder_id, hda_ids='', **kwd ):
1124        params = util.Params( kwd )
1125        message = util.restore_text( params.get( 'message', ''  ) )
1126        status = params.get( 'status', 'done' )
1127        ldda_message = util.restore_text( params.get( 'ldda_message', '' ) )
1128        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1129        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1130        replace_id = params.get( 'replace_id', None )
1131        replace_dataset = None
1132        upload_option = params.get( 'upload_option', 'import_from_history' )
1133        if params.get( 'files_0|space_to_tab', False ):
1134            space_to_tab = params.get( 'files_0|space_to_tab', '' )
1135        else:
1136            space_to_tab = params.get( 'space_to_tab', '' )
1137        link_data_only = params.get( 'link_data_only', '' )
1138        dbkey = params.get( 'dbkey', '?' )
1139        if isinstance( dbkey, list ):
1140            last_used_build = dbkey[0]
1141        else:
1142            last_used_build = dbkey
1143        roles = params.get( 'roles', '' )
1144        is_admin = trans.user_is_admin() and cntrller in ( 'library_admin', 'api' )
1145        current_user_roles = trans.get_current_user_roles()
1146        if replace_id not in [ None, 'None' ]:
1147            try:
1148                replace_dataset = trans.sa_session.query( trans.app.model.LibraryDataset ).get( trans.security.decode_id( replace_id ) )
1149            except:
1150                replace_dataset = None
1151            self._check_access( trans, cntrller, is_admin, replace_dataset, current_user_roles, use_panels, library_id, show_deleted )
1152            self._check_modify( trans, cntrller, is_admin, replace_dataset, current_user_roles, use_panels, library_id, show_deleted )
1153            library = replace_dataset.folder.parent_library
1154            folder = replace_dataset.folder
1155            last_used_build = replace_dataset.library_dataset_dataset_association.dbkey
1156        else:
1157            folder = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) )
1158            self._check_access( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
1159            self._check_add( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
1160            library = folder.parent_library
1161            last_used_build = folder.genome_build
1162        # See if the current history is empty
1163        history = trans.get_history()
1164        trans.sa_session.refresh( history )
1165        if not history.active_datasets:
1166            message = 'Your current history is empty'
1167            return trans.response.send_redirect( web.url_for( controller='library_common',
1168                                                              action='browse_library',
1169                                                              cntrller=cntrller,
1170                                                              id=library_id,
1171                                                              show_deleted=show_deleted,
1172                                                              message=util.sanitize_text( message ),
1173                                                              status='error' ) )
1174        if params.get( 'add_history_datasets_to_library_button', False ):
1175            hda_ids = util.listify( hda_ids )
1176            if hda_ids:
1177                dataset_names = []
1178                created_ldda_ids = ''
1179                for hda_id in hda_ids:
1180                    try:
1181                        hda = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( hda_id ) )
1182                    except:
1183                        hda = None
1184                    self._check_access( trans, cntrller, is_admin, hda, current_user_roles, use_panels, library_id, show_deleted )
1185                    if roles:
1186                        role_ids = roles.split( ',' )
1187                        role_obj_list = [ trans.sa_session.query( trans.model.Role ).get( role_id ) for role_id in role_ids ]
1188                    else:
1189                        role_obj_list = []
1190                    ldda = hda.to_library_dataset_dataset_association( trans,
1191                                                                       target_folder=folder,
1192                                                                       replace_dataset=replace_dataset,
1193                                                                       roles=role_obj_list,
1194                                                                       ldda_message=ldda_message )
1195                    created_ldda_ids = '%s,%s' % ( created_ldda_ids, str( ldda.id ) )
1196                    dataset_names.append( ldda.name )
1197                    if not replace_dataset:
1198                        # If replace_dataset is None, the Library level permissions will be taken from the folder and applied to the new
1199                        # LDDA and LibraryDataset.
1200                        trans.app.security_agent.copy_library_permissions( folder, ldda )
1201                        trans.app.security_agent.copy_library_permissions( folder, ldda.library_dataset )
1202                    # Permissions must be the same on the LibraryDatasetDatasetAssociation and the associated LibraryDataset
1203                    trans.app.security_agent.copy_library_permissions( ldda.library_dataset, ldda )
1204                if created_ldda_ids:
1205                    created_ldda_ids = created_ldda_ids.lstrip( ',' )
1206                    ldda_id_list = created_ldda_ids.split( ',' )
1207                    total_added = len( ldda_id_list )
1208                    if replace_dataset:
1209                        message = "Added %d dataset versions to the library dataset '%s' in the folder '%s'." % ( total_added, replace_dataset.name, folder.name )
1210                    else:
1211                        if not folder.parent:
1212                            # Libraries have the same name as their root_folder
1213                            message = "Added %d datasets to the library '%s' (each is selected).  " % ( total_added, folder.name )
1214                        else:
1215                            message = "Added %d datasets to the folder '%s' (each is selected).  " % ( total_added, folder.name )
1216                        if cntrller == 'library_admin':
1217                            message += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
1218                        else:
1219                            # Since permissions on all LibraryDatasetDatasetAssociations must be the same at this point, we only need
1220                            # to check one of them to see if the current user can manage permissions on them.
1221                            check_ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( ldda_id_list[0] )
1222                            if trans.app.security_agent.can_manage_library_item( current_user_roles, check_ldda ):
1223                                if replace_dataset:
1224                                    default_action = ''
1225                                else:
1226                                    message += "Click the Go button at the bottom of this page to edit the permissions on these datasets if necessary."
1227                                    default_action = 'manage_permissions'
1228                            else:
1229                                default_action = 'add'
1230                    return trans.response.send_redirect( web.url_for( controller='library_common',
1231                                                                      action='browse_library',
1232                                                                      cntrller=cntrller,
1233                                                                      id=library_id,
1234                                                                      created_ldda_ids=created_ldda_ids,
1235                                                                      show_deleted=show_deleted,
1236                                                                      message=util.sanitize_text( message ),
1237                                                                      status='done' ) )
1238            else:
1239                message = 'Select at least one dataset from the list of active datasets in your current history'
1240                status = 'error'
1241                upload_option = params.get( 'upload_option', 'import_from_history' )
1242                widgets = self._get_populated_widgets( folder )
1243                # Send list of data formats to the upload form so the "extension" select list can be populated dynamically
1244                file_formats = trans.app.datatypes_registry.upload_file_formats
1245                # Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
1246                def get_dbkey_options( last_used_build ):
1247                    for dbkey, build_name in util.dbnames:
1248                        yield build_name, dbkey, ( dbkey==last_used_build )
1249                dbkeys = get_dbkey_options( last_used_build )
1250                # Send the current history to the form to enable importing datasets from history to library
1251                history = trans.get_history()
1252                trans.sa_session.refresh( history )
1253                action = 'add_history_datasets_to_library'
1254                upload_option_select_list = self._build_upload_option_select_list( trans, upload_option )
1255                roles_select_list = self._build_roles_select_list( trans, cntrller, library, util.listify( roles ) )
1256                return trans.fill_template( "/library/common/upload.mako",
1257                                            cntrller=cntrller,
1258                                            upload_option_select_list=upload_option_select_list,
1259                                            upload_option=upload_option,
1260                                            action=action,
1261                                            library_id=library_id,
1262                                            folder_id=folder_id,
1263                                            replace_dataset=replace_dataset,
1264                                            file_formats=file_formats,
1265                                            dbkeys=dbkeys,
1266                                            last_used_build=last_used_build,
1267                                            roles_select_list=roles_select_list,
1268                                            history=history,
1269                                            widgets=widgets,
1270                                            space_to_tab=space_to_tab,
1271                                            link_data_only=link_data_only,
1272                                            show_deleted=show_deleted,
1273                                            ldda_message=ldda_message,
1274                                            message=message,
1275                                            status=status )
1276    def _build_roles_select_list( self, trans, cntrller, library, selected_role_ids=[] ):
1277        # Get the list of legitimate roles to display on the upload form.  If the library is public,
1278        # all active roles are legitimate.  If the library is restricted by the LIBRARY_ACCESS permission, only
1279        # the set of all roles associated with users that have that permission are legitimate.
1280        legitimate_roles = trans.app.security_agent.get_legitimate_roles( trans, library, cntrller )
1281        if legitimate_roles:
1282            # Build the roles multi-select list using the list of legitimate roles, making sure to select any that
1283            # were selected before refresh_on_change, if one occurred.
1284            roles_select_list = SelectField( "roles", multiple="true", size="5" )
1285            for role in legitimate_roles:
1286                selected = str( role.id ) in selected_role_ids
1287                roles_select_list.add_option( text=role.name, value=str( role.id ), selected=selected )
1288            return roles_select_list
1289        else:
1290            return None
1291    def _build_upload_option_select_list( self, trans, upload_option ):
1292        # Build the upload_option select list
1293        upload_refresh_on_change_values = [ option_value for option_value, option_label in trans.model.LibraryDataset.upload_options ]
1294        upload_option_select_list = SelectField( 'upload_option',
1295                                                 refresh_on_change=True,
1296                                                 refresh_on_change_values=upload_refresh_on_change_values )
1297        for option_value, option_label in trans.model.LibraryDataset.upload_options:
1298            upload_option_select_list.add_option( option_label, option_value, selected=option_value==upload_option )
1299        return upload_option_select_list
1300    def _get_populated_widgets( self, folder ):
1301        # See if we have any inherited templates.
1302        info_association, inherited = folder.get_info_association( inherited=True )
1303        if info_association and info_association.inheritable:
1304            widgets = folder.get_template_widgets( trans, get_contents=True )
1305            # Retain contents of widget fields when form was submitted via refresh_on_change.
1306            return self.populate_widgets_from_kwd( trans, widgets, **kwd )
1307        else:
1308            return []
1309    @web.expose
1310    def download_dataset_from_folder( self, trans, cntrller, id, library_id=None, **kwd ):
1311        """Catches the dataset id and displays file contents as directed"""
1312        show_deleted = util.string_as_bool( kwd.get( 'show_deleted', False ) )
1313        params = util.Params( kwd )       
1314        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1315        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
1316        current_user_roles = trans.get_current_user_roles()
1317        try:
1318            ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( id ) )
1319        except:
1320            ldda = None
1321        self._check_access( trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted )
1322        composite_extensions = trans.app.datatypes_registry.get_composite_extensions( )
1323        ext = ldda.extension
1324        if ext in composite_extensions:
1325            # is composite - must return a zip of contents and the html file itself - ugh - should be reversible at upload!
1326            # use act_on_multiple_datasets( self, trans, cntrller, library_id, ldda_ids='', **kwd ) since it does what we need
1327            kwd['do_action'] = 'zip'
1328            return self.act_on_multiple_datasets( trans, cntrller, library_id, ldda_ids=[id,], **kwd )
1329        else:
1330            mime = trans.app.datatypes_registry.get_mimetype_by_extension( ldda.extension.lower() )
1331            trans.response.set_content_type( mime )
1332            fStat = os.stat( ldda.file_name )
1333            trans.response.headers[ 'Content-Length' ] = int( fStat.st_size )
1334            valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
1335            fname = ldda.name
1336            fname = ''.join( c in valid_chars and c or '_' for c in fname )[ 0:150 ]
1337            trans.response.headers[ "Content-Disposition" ] = "attachment; filename=%s" % fname
1338            try:
1339                return open( ldda.file_name )
1340            except:
1341                message = 'This dataset contains no content'
1342        return trans.response.send_redirect( web.url_for( controller='library_common',
1343                                                          action='browse_library',
1344                                                          cntrller=cntrller,
1345                                                          use_panels=use_panels,
1346                                                          id=library_id,
1347                                                          show_deleted=show_deleted,
1348                                                          message=util.sanitize_text( message ),
1349                                                          status='error' ) )
1350    @web.expose
1351    def library_dataset_info( self, trans, cntrller, id, library_id, **kwd ):
1352        params = util.Params( kwd )
1353        message = util.restore_text( params.get( 'message', ''  ) )
1354        status = params.get( 'status', 'done' )
1355        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1356        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1357        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
1358        current_user_roles = trans.get_current_user_roles()
1359        try:
1360            library_dataset = trans.sa_session.query( trans.app.model.LibraryDataset ).get( trans.security.decode_id( id ) )
1361        except:
1362            library_dataset = None
1363        self._check_access( trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted )
1364        if params.get( 'edit_attributes_button', False ):
1365            self._check_modify( trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted )
1366            old_name = library_dataset.name
1367            new_name = util.restore_text( params.get( 'name', '' ) )
1368            new_info = util.restore_text( params.get( 'info', '' ) )
1369            if not new_name:
1370                message = 'Enter a valid name'
1371                status = 'error'
1372            else:
1373                library_dataset.name = new_name
1374                library_dataset.info = new_info
1375                trans.sa_session.add( library_dataset )
1376                trans.sa_session.flush()
1377                message = "Information updated for library dataset '%s'." % library_dataset.name
1378                status = 'done'
1379        # See if we have any associated templates
1380        widgets = []
1381        widget_fields_have_contents = False
1382        info_association, inherited = library_dataset.library_dataset_dataset_association.get_info_association()
1383        if info_association and ( not( inherited ) or info_association.inheritable ):
1384            widgets = library_dataset.library_dataset_dataset_association.get_template_widgets( trans )
1385            widget_fields_have_contents = self.widget_fields_have_contents( widgets )
1386        return trans.fill_template( '/library/common/library_dataset_info.mako',
1387                                    cntrller=cntrller,
1388                                    use_panels=use_panels,
1389                                    library_dataset=library_dataset,
1390                                    library_id=library_id,
1391                                    current_user_roles=current_user_roles,
1392                                    info_association=info_association,
1393                                    inherited=inherited,
1394                                    widgets=widgets,
1395                                    widget_fields_have_contents=widget_fields_have_contents,
1396                                    show_deleted=show_deleted,
1397                                    message=message,
1398                                    status=status )
1399    @web.expose
1400    def library_dataset_permissions( self, trans, cntrller, id, library_id, **kwd ):
1401        params = util.Params( kwd )
1402        message = util.restore_text( params.get( 'message', ''  ) )
1403        status = params.get( 'status', 'done' )
1404        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1405        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1406        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
1407        current_user_roles = trans.get_current_user_roles()
1408        try:
1409            library_dataset = trans.sa_session.query( trans.app.model.LibraryDataset ).get( trans.security.decode_id( id ) )
1410        except:
1411            library_dataset = None
1412        self._check_access( trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted )
1413        self._check_manage( trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted )
1414        if params.get( 'update_roles_button', False ):
1415            # The user clicked the Save button on the 'Associate With Roles' form
1416            permissions = {}
1417            for k, v in trans.app.model.Library.permitted_actions.items():
1418                if k != 'LIBRARY_ACCESS':
1419                    # LIBRARY_ACCESS is a special permission set only at the library level
1420                    # and it is not inherited.
1421                    in_roles = [ trans.sa_session.query( trans.app.model.Role ).get( x ) for x in util.listify( kwd.get( k + '_in', [] ) ) ]
1422                    permissions[ trans.app.security_agent.get_action( v.action ) ] = in_roles
1423            # Set the LIBRARY permissions on the LibraryDataset
1424            # NOTE: the LibraryDataset and LibraryDatasetDatasetAssociation will be set with the same permissions
1425            trans.app.security_agent.set_all_library_permissions( library_dataset, permissions )
1426            trans.sa_session.refresh( library_dataset )
1427            # Set the LIBRARY permissions on the LibraryDatasetDatasetAssociation
1428            trans.app.security_agent.set_all_library_permissions( library_dataset.library_dataset_dataset_association, permissions )
1429            trans.sa_session.refresh( library_dataset.library_dataset_dataset_association )
1430            message = "Permisisons updated for library dataset '%s'." % library_dataset.name
1431            status = 'done'
1432        roles = trans.app.security_agent.get_legitimate_roles( trans, library_dataset, cntrller )
1433        return trans.fill_template( '/library/common/library_dataset_permissions.mako',
1434                                    cntrller=cntrller,
1435                                    use_panels=use_panels,
1436                                    library_dataset=library_dataset,
1437                                    library_id=library_id,
1438                                    roles=roles,
1439                                    current_user_roles=current_user_roles,
1440                                    show_deleted=show_deleted,
1441                                    message=message,
1442                                    status=status )
1443    @web.expose
1444    def make_library_item_public( self, trans, cntrller, library_id, item_type, id, **kwd ):
1445        params = util.Params( kwd )
1446        message = util.restore_text( params.get( 'message', ''  ) )
1447        status = params.get( 'status', 'done' )
1448        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1449        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1450        current_user_roles = trans.get_current_user_roles()
1451        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
1452        if item_type == 'library':
1453            library = trans.sa_session.query( trans.model.Library ).get( trans.security.decode_id( id ) )
1454            self._check_access( trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted )
1455            self._check_manage( trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted )
1456            contents = util.string_as_bool( params.get( 'contents', 'False' ) )
1457            trans.app.security_agent.make_library_public( library, contents=contents )
1458            if contents:
1459                message = "The data library (%s) and all it's contents have been made publicly accessible." % library.name
1460            else:
1461                message = "The data library (%s) has been made publicly accessible, but access to it's contents has been left unchanged." % library.name
1462        elif item_type == 'folder':
1463            folder = trans.sa_session.query( trans.model.LibraryFolder ).get( trans.security.decode_id( id ) )
1464            self._check_access( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
1465            self._check_manage( trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted )
1466            trans.app.security_agent.make_folder_public( folder )
1467            message = "All of the contents of folder (%s) have been made publicly accessible." % folder.name
1468        elif item_type == 'ldda':
1469            ldda = trans.sa_session.query( trans.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( id ) )
1470            self._check_access( trans, cntrller, is_admin, ldda.library_dataset, current_user_roles, use_panels, library_id, show_deleted )
1471            self._check_manage( trans, cntrller, is_admin, ldda.library_dataset, current_user_roles, use_panels, library_id, show_deleted )
1472            trans.app.security_agent.make_dataset_public( ldda.dataset )
1473            message = "The libary dataset (%s) has been made publicly accessible." % ldda.name
1474        else:
1475            message = "Invalid item_type (%s) received." % str( item_type )
1476            status = 'error'
1477        return trans.response.send_redirect( web.url_for( controller='library_common',
1478                                                          action='browse_library',
1479                                                          cntrller=cntrller,
1480                                                          use_panels=use_panels,
1481                                                          id=library_id,
1482                                                          show_deleted=show_deleted,
1483                                                          message=util.sanitize_text( message ),
1484                                                          status=status ) )
1485    @web.expose
1486    def act_on_multiple_datasets( self, trans, cntrller, library_id, ldda_ids='', **kwd ):
1487        class NgxZip( object ):
1488            def __init__( self, url_base ):
1489                self.files = {}
1490                self.url_base = url_base
1491            def add( self, file, relpath ):
1492                self.files[file] = relpath
1493            def __str__( self ):
1494                rval = ''
1495                for fname, relpath in self.files.items():
1496                    crc = '-'
1497                    size = os.stat( fname ).st_size
1498                    quoted_fname = urllib.quote_plus( fname, '/' )
1499                    rval += '%s %i %s%s %s\r\n' % ( crc, size, self.url_base, quoted_fname, relpath )
1500                return rval
1501        # Perform an action on a list of library datasets.
1502        params = util.Params( kwd )
1503        message = util.restore_text( params.get( 'message', ''  ) )
1504        status = params.get( 'status', 'done' )
1505        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1506        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1507        action = params.get( 'do_action', None )
1508        lddas = []
1509        error = False
1510        is_admin = trans.user_is_admin() and cntrller == 'library_admin'
1511        current_user_roles = trans.get_current_user_roles()
1512        if not ldda_ids:
1513            error = True
1514            message = 'You must select at least one dataset.'
1515        elif not action:
1516            error = True
1517            message = 'You must select an action to perform on the selected datasets.'
1518        else:
1519            # Set up the list of lddas for later, and get permission checks out of the way so we don't have to do it in multiple places later.
1520            ldda_ids = util.listify( ldda_ids )
1521            for ldda_id in ldda_ids:
1522                try:
1523                    ldda = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( ldda_id ) )
1524                except:
1525                    ldda = None
1526                if not ldda or ( not is_admin and not trans.app.security_agent.can_access_library_item( current_user_roles, ldda, trans.user ) ):
1527                    error = True
1528                    message = "Invalid library dataset id ( %s ) specified." % str( ldda_id )
1529                    break
1530                lddas.append( ldda )
1531            if action == 'import_to_history' or action == 'add':
1532                if trans.get_history() is None:
1533                    # Must be a bot sending a request without having a history.
1534                    error = True
1535                    message = "You do not have a current history"
1536            elif action == 'manage_permissions':
1537                if not is_admin:
1538                    for ldda in lddas:
1539                        if not ( trans.app.security_agent.can_manage_library_item( current_user_roles, ldda ) and \
1540                                 trans.app.security_agent.can_manage_dataset( current_user_roles, ldda.dataset ) ):
1541                            error = True
1542                            message = "You are not authorized to manage permissions on library dataset '%s'." % ldda.name
1543                            break
1544            elif action == 'delete':
1545                if not is_admin:
1546                    for ldda in lddas:
1547                        if not trans.app.security_agent.can_modify_library_item( current_user_roles, ldda ):
1548                            error = True
1549                            message = "You are not authorized to modify library dataset '%s'." % ldda.name
1550                            break
1551        if error:
1552            return trans.response.send_redirect( web.url_for( controller='library_common',
1553                                                              action='browse_library',
1554                                                              cntrller=cntrller,
1555                                                              use_panels=use_panels,
1556                                                              id=library_id,
1557                                                              show_deleted=show_deleted,
1558                                                              message=util.sanitize_text( message ),
1559                                                              status='error' ) )
1560        if action == 'import_to_history' or action == 'add':
1561            history = trans.get_history()
1562            total_imported_lddas = 0
1563            message = ''
1564            status = 'done'
1565            for ldda in lddas:
1566                if ldda.dataset.state in [ 'new', 'upload', 'queued', 'running', 'empty', 'discarded' ]:
1567                    message += "Cannot import dataset '%s' since its state is '%s'.  " % ( ldda.name, ldda.dataset.state )
1568                    status = 'error'
1569                elif ldda.dataset.state in [ 'ok', 'error' ]:
1570                    hda = ldda.to_history_dataset_association( target_history=history, add_to_history=True )
1571                    total_imported_lddas += 1
1572            if total_imported_lddas:
1573                trans.sa_session.add( history )
1574                trans.sa_session.flush()
1575                message += "%i dataset(s) have been imported into your history.  " % total_imported_lddas
1576        elif action == 'manage_permissions':
1577            trans.response.send_redirect( web.url_for( controller='library_common',
1578                                                       action='ldda_permissions',
1579                                                       cntrller=cntrller,
1580                                                       use_panels=use_panels,
1581                                                       library_id=library_id,
1582                                                       folder_id=trans.security.encode_id( lddas[0].library_dataset.folder.id ),
1583                                                       id=",".join( ldda_ids ),
1584                                                       show_deleted=show_deleted,
1585                                                       message=util.sanitize_text( message ),
1586                                                       status=status ) )
1587        elif action == 'delete':
1588            for ldda in lddas:
1589                # Do not delete the association, just delete the library_dataset.  The
1590                # cleanup_datasets.py script handles everything else.
1591                ld = ldda.library_dataset
1592                ld.deleted = True
1593                trans.sa_session.add( ld )
1594            trans.sa_session.flush()
1595            message = "The selected datasets have been removed from this data library"
1596        elif action in ['zip','tgz','tbz','ngxzip']:
1597            error = False
1598            killme = string.punctuation + string.whitespace
1599            trantab = string.maketrans(killme,'_'*len(killme))
1600            try:
1601                outext = 'zip'
1602                if action == 'zip':
1603                    # Can't use mkstemp - the file must not exist first
1604                    tmpd = tempfile.mkdtemp()
1605                    tmpf = os.path.join( tmpd, 'library_download.' + action )
1606                    if ziptype == '64' and trans.app.config.upstream_gzip:
1607                        archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_STORED, True )
1608                    elif ziptype == '64':
1609                        archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
1610                    elif trans.app.config.upstream_gzip:
1611                        archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_STORED )
1612                    else:
1613                        archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED )
1614                    archive.add = lambda x, y: archive.write( x, y.encode('CP437') )
1615                elif action == 'tgz':
1616                    if trans.app.config.upstream_gzip:
1617                        archive = util.streamball.StreamBall( 'w|' )
1618                        outext = 'tar'
1619                    else:
1620                        archive = util.streamball.StreamBall( 'w|gz' )
1621                        outext = 'tgz'
1622                elif action == 'tbz':
1623                    archive = util.streamball.StreamBall( 'w|bz2' )
1624                    outext = 'tbz2'
1625                elif action == 'ngxzip':
1626                    archive = NgxZip( trans.app.config.nginx_x_archive_files_base )
1627            except (OSError, zipfile.BadZipfile):
1628                error = True
1629                log.exception( "Unable to create archive for download" )
1630                message = "Unable to create archive for download, please report this error"
1631                status = 'error'
1632            except:
1633                 error = True
1634                 log.exception( "Unexpected error %s in create archive for download" % sys.exc_info()[0])
1635                 message = "Unable to create archive for download, please report - %s" % sys.exc_info()[0]
1636                 status = 'error'
1637            if not error:
1638                composite_extensions = trans.app.datatypes_registry.get_composite_extensions( )
1639                seen = []
1640                for ldda in lddas:
1641                    if ldda.dataset.state in [ 'new', 'upload', 'queued', 'running', 'empty', 'discarded' ]:
1642                        continue
1643                    ext = ldda.extension
1644                    is_composite = ext in composite_extensions
1645                    path = ""
1646                    parent_folder = ldda.library_dataset.folder
1647                    while parent_folder is not None:
1648                        # Exclude the now-hidden "root folder"
1649                        if parent_folder.parent is None:
1650                            path = os.path.join( parent_folder.library_root[0].name, path )
1651                            break
1652                        path = os.path.join( parent_folder.name, path )
1653                        parent_folder = parent_folder.parent
1654                    path += ldda.name
1655                    while path in seen:
1656                        path += '_'
1657                    seen.append( path )
1658                    zpath = os.path.split(path)[-1] # comes as base_name/fname
1659                    outfname,zpathext = os.path.splitext(zpath)
1660                    if is_composite:
1661                        # need to add all the components from the extra_files_path to the zip
1662                        if zpathext == '':
1663                            zpath = '%s.html' % zpath # fake the real nature of the html file
1664                        try:
1665                            archive.add(ldda.dataset.file_name,zpath) # add the primary of a composite set
1666                        except IOError:
1667                            error = True
1668                            log.exception( "Unable to add composite parent %s to temporary library download archive" % ldda.dataset.file_name)
1669                            message = "Unable to create archive for download, please report this error"
1670                            status = 'error'
1671                            continue                               
1672                        flist = glob.glob(os.path.join(ldda.dataset.extra_files_path,'*.*')) # glob returns full paths
1673                        for fpath in flist:
1674                            efp,fname = os.path.split(fpath)
1675                            if fname > '':
1676                                fname = fname.translate(trantab)
1677                            try:
1678                                archive.add( fpath,fname )
1679                            except IOError:
1680                                error = True
1681                                log.exception( "Unable to add %s to temporary library download archive %s" % (fname,outfname))
1682                                message = "Unable to create archive for download, please report this error"
1683                                status = 'error'
1684                                continue
1685                    else: # simple case
1686                        try:
1687                            archive.add( ldda.dataset.file_name, path )
1688                        except IOError:
1689                            error = True
1690                            log.exception( "Unable to write %s to temporary library download archive" % ldda.dataset.file_name)
1691                            message = "Unable to create archive for download, please report this error"
1692                            status = 'error'                           
1693                if not error:
1694                    lname = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( library_id ) ).name
1695                    fname = lname.replace( ' ', '_' ) + '_files'
1696                    if action == 'zip':
1697                        archive.close()
1698                        tmpfh = open( tmpf )
1699                        # clean up now
1700                        try:
1701                            os.unlink( tmpf )
1702                            os.rmdir( tmpd )
1703                        except OSError:
1704                            error = True
1705                            log.exception( "Unable to remove temporary library download archive and directory" )
1706                            message = "Unable to create archive for download, please report this error"
1707                            status = 'error'
1708                        if not error:
1709                            trans.response.set_content_type( "application/x-zip-compressed" )
1710                            trans.response.headers[ "Content-Disposition" ] = "attachment; filename=%s.%s" % (fname,outext)
1711                            return tmpfh
1712                    elif action == 'ngxzip':
1713                        trans.response.set_content_type( "application/zip" )
1714                        trans.response.headers[ "Content-Disposition" ] = "attachment; filename=%s.%s" % (fname,outext)
1715                        trans.response.headers[ "X-Archive-Files" ] = "zip"
1716                        return archive
1717                    else:
1718                        trans.response.set_content_type( "application/x-tar" )
1719                        trans.response.headers[ "Content-Disposition" ] = "attachment; filename=%s.%s" % (fname,outext)
1720                        archive.wsgi_status = trans.response.wsgi_status()
1721                        archive.wsgi_headeritems = trans.response.wsgi_headeritems()
1722                        return archive.stream
1723        else:
1724            status = 'error'
1725            message = 'Invalid action ( %s ) specified.' % action
1726        return trans.response.send_redirect( web.url_for( controller='library_common',
1727                                                          action='browse_library',
1728                                                          cntrller=cntrller,
1729                                                          use_panels=use_panels,
1730                                                          id=library_id,
1731                                                          show_deleted=show_deleted,
1732                                                          message=util.sanitize_text( message ),
1733                                                          status=status ) )
1734    def get_item_and_stuff( self, trans, item_type, library_id, folder_id, ldda_id, is_admin ):
1735        # Return an item, description, action and an id based on the item_type.
1736        message = None
1737        current_user_roles = trans.get_current_user_roles()
1738        if item_type == 'library':
1739            try:
1740                item = trans.sa_session.query( trans.app.model.Library ).get( trans.security.decode_id( library_id ) )
1741            except:
1742                item = None
1743            item_desc = 'data library'
1744            action = 'library_info'
1745            id = library_id
1746        elif item_type == 'folder':
1747            try:
1748                item = trans.sa_session.query( trans.app.model.LibraryFolder ).get( trans.security.decode_id( folder_id ) )
1749            except:
1750                item = None
1751            item_desc = 'folder'
1752            action = 'folder_info'
1753            id = folder_id
1754        elif item_type == 'ldda':
1755            try:
1756                item = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ).get( trans.security.decode_id( ldda_id ) )
1757            except:
1758                item = None
1759            item_desc = 'dataset'
1760            action = 'ldda_edit_info'
1761            id = ldda_id
1762        else:
1763            item = None
1764            message = "Invalid library item type ( %s )" % str( item_type )
1765        if not item or not ( is_admin or trans.app.security_agent.can_access_library_item( current_user_roles, item, trans.user ) ):
1766            if message is None:
1767                message = "Invalid %s id ( %s ) specified." % ( item_desc, str( id ) )
1768            return trans.response.send_redirect( web.url_for( controller='library_common',
1769                                                              action='browse_library',
1770                                                              cntrller='library', # cheating a bit here
1771                                                              id=library_id,
1772                                                              message=util.sanitize_text( message ),
1773                                                              status='error' ) )
1774        return item, item_desc, action, id
1775    @web.expose
1776    def add_template( self, trans, cntrller, item_type, library_id, folder_id=None, ldda_id=None, **kwd ):
1777        # Template can only be added to a Library, Folder or LibraryDatasetDatasetAssociation.
1778        forms = self.get_all_forms( trans,
1779                                    filter=dict( deleted=False ),
1780                                    form_type=trans.app.model.FormDefinition.types.LIBRARY_INFO_TEMPLATE )
1781        if not forms:
1782            message = "There are no forms on which to base the template, so create a form and then add the template."
1783            return trans.response.send_redirect( web.url_for( controller='forms',
1784                                                              action='create_request',
1785                                                              message=message,
1786                                                              status='done',
1787                                                              form_type=trans.app.model.FormDefinition.types.LIBRARY_INFO_TEMPLATE ) )
1788        params = util.Params( kwd )
1789        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1790        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1791        message = util.restore_text( params.get( 'message', ''  ) )
1792        action = ''
1793        status = params.get( 'status', 'done' )
1794        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
1795        current_user_roles = trans.get_current_user_roles()
1796        try:
1797            item, item_desc, action, id = self.get_item_and_stuff( trans, item_type, library_id, folder_id, ldda_id, is_admin )
1798        except ValueError:
1799            # At this point, the client has already redirected, so this is just here to prevent the unnecessary traceback
1800            return None
1801        if not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, item ) ):
1802            message = "You are not authorized to modify %s '%s'." % ( item_desc, item.name )
1803            return trans.response.send_redirect( web.url_for( controller='library_common',
1804                                                              action='browse_library',
1805                                                              cntrller=cntrller,
1806                                                              id=library_id,
1807                                                              show_deleted=show_deleted,
1808                                                              message=util.sanitize_text( message ),
1809                                                              status='error' ) )
1810        # If the inheritable checkbox is checked, the param will be in the request
1811        inheritable = CheckboxField.is_checked( params.get( 'inheritable', '' ) )
1812        if params.get( 'add_template_button', False ):
1813            form_id = params.get( 'form_id', 'none' )
1814            if form_id not in [ None, 'None', 'none' ]:
1815                form = trans.sa_session.query( trans.app.model.FormDefinition ).get( trans.security.decode_id( form_id ) )
1816                form_values = trans.app.model.FormValues( form, [] )
1817                trans.sa_session.add( form_values )
1818                trans.sa_session.flush()
1819                if item_type == 'library':
1820                    assoc = trans.app.model.LibraryInfoAssociation( item, form, form_values, inheritable=inheritable )
1821                elif item_type == 'folder':
1822                    assoc = trans.app.model.LibraryFolderInfoAssociation( item, form, form_values, inheritable=inheritable )
1823                elif item_type == 'ldda':
1824                    assoc = trans.app.model.LibraryDatasetDatasetInfoAssociation( item, form, form_values )
1825                trans.sa_session.add( assoc )
1826                trans.sa_session.flush()
1827                message = 'A template based on the form "%s" has been added to this %s.' % ( form.name, item_desc )
1828                trans.response.send_redirect( web.url_for( controller='library_common',
1829                                                           action=action,
1830                                                           cntrller=cntrller,
1831                                                           use_panels=use_panels,
1832                                                           library_id=library_id,
1833                                                           folder_id=folder_id,
1834                                                           id=id,
1835                                                           show_deleted=show_deleted,
1836                                                           message=message,
1837                                                           status='done' ) )
1838            else:
1839                message = "Select a form on which to base the template."
1840                status = "error"
1841        def generate_template_stuff( trans, forms, form_id ):
1842            # Returns the following:
1843            # - a list of template ids
1844            # - a list of dictionaries whose keys are template ids and whose values are templates widgets.
1845            #   The dictionary built using the received forms param
1846            # - a select list whose options are templates
1847            template_ids = [ 'none' ]
1848            widgets = []
1849            for form in forms:
1850                template_ids.append( trans.security.encode_id( form.id ) )
1851            template_select_list = SelectField( 'form_id',
1852                                    refresh_on_change=True,
1853                                    refresh_on_change_values=template_ids[1:] )
1854            if form_id == 'none':
1855                template_select_list.add_option( 'Select one', 'none', selected=True )
1856                decoded_form_id = None
1857            else:
1858                template_select_list.add_option( 'Select one', 'none' )
1859                decoded_form_id = trans.security.decode_id( form_id )
1860            for form in forms:
1861                if decoded_form_id and decoded_form_id == form.id:
1862                    template_select_list.add_option( form.name, trans.security.encode_id( form.id ), selected=True )
1863                    widgets = form.get_widgets( trans.user )
1864                else:
1865                    template_select_list.add_option( form.name, trans.security.encode_id( form.id ) )
1866            return template_ids, widgets, template_select_list
1867        if params.get( 'refresh', False ):
1868            template_ids, widgets, template_select_list = generate_template_stuff( trans, forms, kwd.get( 'form_id' ) )
1869        else:
1870            template_ids, widgets, template_select_list = generate_template_stuff( trans, forms, 'none' )
1871        return trans.fill_template( '/library/common/select_template.mako',
1872                                    cntrller=cntrller,
1873                                    use_panels=use_panels,
1874                                    item_name=item.name,
1875                                    item_desc=item_desc,
1876                                    item_type=item_type,
1877                                    library_id=library_id,
1878                                    folder_id=folder_id,
1879                                    ldda_id=ldda_id,
1880                                    template_ids=template_ids,
1881                                    widgets=widgets,
1882                                    template_select_list=template_select_list,
1883                                    inheritable_checked=inheritable,
1884                                    show_deleted=show_deleted,
1885                                    message=message,
1886                                    status=status )
1887    @web.expose
1888    def manage_template_inheritance( self, trans, cntrller, item_type, library_id, folder_id=None, ldda_id=None, **kwd ):
1889        params = util.Params( kwd )
1890        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1891        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1892        message = util.restore_text( params.get( 'message', ''  ) )
1893        status = params.get( 'status', 'done' )
1894        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
1895        current_user_roles = trans.get_current_user_roles()
1896        try:
1897            item, item_desc, action, id = self.get_item_and_stuff( trans, item_type, library_id, folder_id, ldda_id, is_admin )
1898        except ValueError:
1899            return None
1900        if not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, item ) ):
1901            message = "You are not authorized to modify %s '%s'." % ( item_desc, item.name )
1902            return trans.response.send_redirect( web.url_for( controller='library_common',
1903                                                              action='browse_library',
1904                                                              cntrller=cntrller,
1905                                                              id=library_id,
1906                                                              show_deleted=show_deleted,
1907                                                              message=util.sanitize_text( message ),
1908                                                              status='error' ) )
1909        info_association, inherited = item.get_info_association( restrict=True )
1910        if info_association:
1911            if info_association.inheritable:
1912                message = "The template for this %s will no longer be inherited to contained folders and datasets." % item_desc
1913            else:
1914                message = "The template for this %s will now be inherited to contained folders and datasets." % item_desc
1915            info_association.inheritable = not( info_association.inheritable )
1916            trans.sa_session.add( info_association )
1917            trans.sa_session.flush()
1918        return trans.response.send_redirect( web.url_for( controller='library_common',
1919                                                          action=action,
1920                                                          cntrller=cntrller,
1921                                                          use_panels=use_panels,
1922                                                          library_id=library_id,
1923                                                          folder_id=folder_id,
1924                                                          id=id,
1925                                                          show_deleted=show_deleted,
1926                                                          message=util.sanitize_text( message ),
1927                                                          status='done' ) )
1928    @web.expose
1929    def edit_template( self, trans, cntrller, item_type, library_id, folder_id=None, ldda_id=None, edited=False, **kwd ):
1930        # Edit the template itself, keeping existing field contents, if any.
1931        params = util.Params( kwd )
1932        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1933        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1934        message = util.restore_text( params.get( 'message', ''  ) )
1935        status = params.get( 'status', 'done' )
1936        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
1937        current_user_roles = trans.get_current_user_roles()
1938        try:
1939            item, item_desc, action, id = self.get_item_and_stuff( trans, item_type, library_id, folder_id, ldda_id, is_admin )
1940        except ValueError:
1941            return None
1942        if not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, item ) ):
1943            message = "You are not authorized to modify %s '%s'." % ( item_desc, item.name )
1944            return trans.response.send_redirect( web.url_for( controller='library_common',
1945                                                              action='browse_library',
1946                                                              cntrller=cntrller,
1947                                                              id=library_id,
1948                                                              show_deleted=show_deleted,
1949                                                              message=util.sanitize_text( message ),
1950                                                              status='error' ) )
1951        # An info_association must exist at this point
1952        info_association, inherited = item.get_info_association( restrict=True )
1953        template = info_association.template
1954        info = info_association.info
1955        form_values = trans.sa_session.query( trans.app.model.FormValues ).get( info.id )
1956        if edited:
1957            # The form on which the template is based has been edited, so we need to update the
1958            # info_association with the current form
1959            fdc = trans.sa_session.query( trans.app.model.FormDefinitionCurrent ).get( template.form_definition_current_id )
1960            info_association.template = fdc.latest_form
1961            trans.sa_session.add( info_association )
1962            trans.sa_session.flush()
1963            message = "The template for this %s has been updated with your changes." % item_desc
1964            return trans.response.send_redirect( web.url_for( controller='library_common',
1965                                                              action=action,
1966                                                              cntrller=cntrller,
1967                                                              use_panels=use_panels,
1968                                                              library_id=library_id,
1969                                                              folder_id=folder_id,
1970                                                              id=id,
1971                                                              show_deleted=show_deleted,
1972                                                              message=util.sanitize_text( message ),
1973                                                              status='done' ) )
1974        # "template" is a FormDefinition, so since we're changing it, we need to use the latest version of it.
1975        vars = dict( id=trans.security.encode_id( template.form_definition_current_id ),
1976                     response_redirect=web.url_for( controller='library_common',
1977                                                    action='edit_template',
1978                                                    cntrller=cntrller,
1979                                                    item_type=item_type,
1980                                                    library_id=library_id,
1981                                                    folder_id=folder_id,
1982                                                    ldda_id=ldda_id,
1983                                                    edited=True,
1984                                                    **kwd ) )
1985        return trans.response.send_redirect( web.url_for( controller='forms', action='edit', **vars ) )
1986    @web.expose
1987    def edit_template_info( self, trans, cntrller, item_type, library_id, folder_id=None, ldda_id=None, **kwd ):
1988        # Edit the contents of the template fields without altering the template itself.
1989        params = util.Params( kwd )
1990        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
1991        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
1992        message = util.restore_text( params.get( 'message', ''  ) )
1993        status = params.get( 'status', 'done' )
1994        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
1995        current_user_roles = trans.get_current_user_roles()
1996        try:
1997            item, item_desc, action, id = self.get_item_and_stuff( trans, item_type, library_id, folder_id, ldda_id, is_admin )
1998        except ValueError:
1999            return None
2000        if not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, item ) ):
2001            message = "You are not authorized to modify %s '%s'." % ( item_desc, item.name )
2002            return trans.response.send_redirect( web.url_for( controller='library_common',
2003                                                              action='browse_library',
2004                                                              cntrller=cntrller,
2005                                                              id=library_id,
2006                                                              show_deleted=show_deleted,
2007                                                              message=util.sanitize_text( message ),
2008                                                              status='error' ) )
2009        # We need the type of each template field widget
2010        widgets = item.get_template_widgets( trans )
2011        # The list of widgets may include an AddressField which we need to save if it is new
2012        for index, widget_dict in enumerate( widgets ):
2013            widget = widget_dict[ 'widget' ]
2014            if isinstance( widget, AddressField ):
2015                value = util.restore_text( params.get( 'field_%i' % index, '' ) )
2016                if value == 'new':
2017                    if params.get( 'edit_info_button', False ):
2018                        if self.field_param_values_ok( index, 'AddressField', **kwd ):
2019                            # Save the new address
2020                            address = trans.app.model.UserAddress( user=trans.user )
2021                            self.save_widget_field( trans, address, index, **kwd )
2022                            widget.value = str( address.id )
2023                        else:
2024                            message = 'Required fields are missing contents.'
2025                            return trans.response.send_redirect( web.url_for( controller='library_common',
2026                                                                              action=action,
2027                                                                              cntrller=cntrller,
2028                                                                              use_panels=use_panels,
2029                                                                              library_id=library_id,
2030                                                                              folder_id=folder_id,
2031                                                                              id=id,
2032                                                                              show_deleted=show_deleted,
2033                                                                              message=util.sanitize_text( message ),
2034                                                                              status='error' ) )
2035                    else:
2036                        # Form was submitted via refresh_on_change
2037                        widget.value = 'new'
2038                elif value == unicode( 'none' ):
2039                    widget.value = ''
2040                else:
2041                    widget.value = value
2042            elif isinstance( widget, CheckboxField ):
2043                # We need to check the value from kwd since util.Params would have munged the list if
2044                # the checkbox is checked.
2045                value = kwd.get( 'field_%i' % index, '' )
2046                if CheckboxField.is_checked( value ):
2047                    widget.value = 'true'
2048            else:
2049                widget.value = util.restore_text( params.get( 'field_%i' % index, '' ) )
2050        # Save updated template field contents
2051        field_contents = self.clean_field_contents( widgets, **kwd )
2052        if field_contents:
2053            # Since information templates are inherited, the template fields can be displayed on the information
2054            # page for a folder or ldda when it has no info_association object.  If the user has added
2055            # field contents on an inherited template via a parent's info_association, we'll need to create a new
2056            # form_values and info_association for the current object.  The value for the returned inherited variable
2057            # is not applicable at this level.
2058            info_association, inherited = item.get_info_association( restrict=True )
2059            if info_association:
2060                template = info_association.template
2061                info = info_association.info
2062                form_values = trans.sa_session.query( trans.app.model.FormValues ).get( info.id )
2063                # Update existing content only if it has changed
2064                if form_values.content != field_contents:
2065                    form_values.content = field_contents
2066                    trans.sa_session.add( form_values )
2067                    trans.sa_session.flush()
2068            else:
2069                # Inherit the next available info_association so we can get the template
2070                info_association, inherited = item.get_info_association()
2071                template = info_association.template
2072                # Create a new FormValues object
2073                form_values = trans.app.model.FormValues( template, field_contents )
2074                trans.sa_session.add( form_values )
2075                trans.sa_session.flush()
2076                # Create a new info_association between the current library item and form_values
2077                if item_type == 'folder':
2078                    # A LibraryFolder is a special case because if it inherited the template from it's parent,
2079                    # we want to set inheritable to True for it's info_association.  This allows for the default
2080                    # inheritance to be False for each level in the Library hierarchy unless we're creating a new
2081                    # level in the hierarchy, in which case we'll inherit the "inheritable" setting from the parent
2082                    # level.
2083                    info_association = trans.app.model.LibraryFolderInfoAssociation( item, template, form_values, inheritable=inherited )
2084                    trans.sa_session.add( info_association )
2085                    trans.sa_session.flush()
2086                elif item_type == 'ldda':
2087                    # TODO: Currently info_associations at teh ldda level are not inheritable to the associated LibraryDataset.
2088                    # We need to figure out if this is optimal.
2089                    info_association = trans.app.model.LibraryDatasetDatasetInfoAssociation( item, template, form_values )
2090                    trans.sa_session.add( info_association )
2091                    trans.sa_session.flush()
2092        message = 'The information has been updated.'
2093        return trans.response.send_redirect( web.url_for( controller='library_common',
2094                                                          action=action,
2095                                                          cntrller=cntrller,
2096                                                          use_panels=use_panels,
2097                                                          library_id=library_id,
2098                                                          folder_id=folder_id,
2099                                                          id=id,
2100                                                          show_deleted=show_deleted,
2101                                                          message=util.sanitize_text( message ),
2102                                                          status='done' ) )
2103    @web.expose
2104    def delete_template( self, trans, cntrller, item_type, library_id, id=None, folder_id=None, ldda_id=None, **kwd ):
2105        # Only adding a new template to a library or folder is currently allowed.  Editing an existing template is
2106        # a future enhancement.
2107        params = util.Params( kwd )
2108        show_deleted = util.string_as_bool( params.get( 'show_deleted', False ) )
2109        use_panels = util.string_as_bool( params.get( 'use_panels', False ) )
2110        message = util.restore_text( params.get( 'message', ''  ) )
2111        status = params.get( 'status', 'done' )
2112        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
2113        current_user_roles = trans.get_current_user_roles()
2114        try:
2115            item, item_desc, action, id = self.get_item_and_stuff( trans, item_type, library_id, folder_id, ldda_id, is_admin )
2116        except ValueError:
2117            return None
2118        if not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, item ) ):
2119            message = "You are not authorized to modify %s '%s'." % ( item_desc, item.name )
2120            return trans.response.send_redirect( web.url_for( controller='library_common',
2121                                                              action='browse_library',
2122                                                              cntrller=cntrller,
2123                                                              id=library_id,
2124                                                              show_deleted=show_deleted,
2125                                                              message=util.sanitize_text( message ),
2126                                                              status='error' ) )
2127        info_association, inherited = item.get_info_association()
2128        if not info_association:
2129            message = "There is no template for this %s" % item_type
2130            status = 'error'
2131        else:
2132            info_association.deleted = True
2133            trans.sa_session.add( info_association )
2134            trans.sa_session.flush()
2135            message = 'The template for this %s has been deleted.' % item_type
2136            status = 'done'
2137        return trans.response.send_redirect( web.url_for( controller='library_common',
2138                                                          action=action,
2139                                                          cntrller=cntrller,
2140                                                          use_panels=use_panels,
2141                                                          library_id=library_id,
2142                                                          folder_id=folder_id,
2143                                                          id=id,
2144                                                          show_deleted=show_deleted,
2145                                                          message=util.sanitize_text( message ),
2146                                                          status=status ) )
2147    @web.expose
2148    def delete_library_item( self, trans, cntrller, library_id, item_id, item_type, **kwd ):
2149        # This action will handle deleting all types of library items.  State is saved for libraries and
2150        # folders ( i.e., if undeleted, the state of contents of the library or folder will remain, so previously
2151        # deleted / purged contents will have the same state ).  When a library or folder has been deleted for
2152        # the amount of time defined in the cleanup_datasets.py script, the library or folder and all of its
2153        # contents will be purged.  The association between this method and the cleanup_datasets.py script
2154        # enables clean maintenance of libraries and library dataset disk files.  This is also why the item_types
2155        # are not any of the associations ( the cleanup_datasets.py script handles everything ).
2156        show_deleted = util.string_as_bool( kwd.get( 'show_deleted', False ) )
2157        item_types = { 'library': trans.app.model.Library,
2158                       'folder': trans.app.model.LibraryFolder,
2159                       'library_dataset': trans.app.model.LibraryDataset }
2160        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
2161        current_user_roles = trans.get_current_user_roles()
2162        if item_type not in item_types:
2163            message = 'Bad item_type specified: %s' % str( item_type )
2164            status = 'error'
2165        else:
2166            if item_type == 'library_dataset':
2167                item_desc = 'Dataset'
2168            else:
2169                item_desc = item_type.capitalize()
2170            try:
2171                library_item = trans.sa_session.query( item_types[ item_type ] ).get( trans.security.decode_id( item_id ) )
2172            except:
2173                library_item = None
2174            if not library_item or not ( is_admin or trans.app.security_agent.can_access_library_item( current_user_roles, library_item, trans.user ) ):
2175                message = 'Invalid %s id ( %s ) specifield.' % ( item_desc, item_id )
2176                status = 'error'
2177            elif not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, library_item ) ):
2178                message = "You are not authorized to delete %s '%s'." % ( item_desc, library_item.name )
2179                status = 'error'
2180            else:
2181                library_item.deleted = True
2182                trans.sa_session.add( library_item )
2183                trans.sa_session.flush()
2184                message = util.sanitize_text( "%s '%s' has been marked deleted" % ( item_desc, library_item.name ) )
2185                status = 'done'
2186        if item_type == 'library':
2187            return trans.response.send_redirect( web.url_for( controller=cntrller,
2188                                                              action='browse_libraries',
2189                                                              message=message,
2190                                                              status=status ) )
2191        else:
2192            return trans.response.send_redirect( web.url_for( controller='library_common',
2193                                                              action='browse_library',
2194                                                              cntrller=cntrller,
2195                                                              id=library_id,
2196                                                              show_deleted=show_deleted,
2197                                                              message=message,
2198                                                              status=status ) )
2199    @web.expose
2200    def undelete_library_item( self, trans, cntrller, library_id, item_id, item_type, **kwd ):
2201        # This action will handle undeleting all types of library items
2202        show_deleted = util.string_as_bool( kwd.get( 'show_deleted', False ) )
2203        item_types = { 'library': trans.app.model.Library,
2204                       'folder': trans.app.model.LibraryFolder,
2205                       'library_dataset': trans.app.model.LibraryDataset }
2206        is_admin = ( trans.user_is_admin() and cntrller == 'library_admin' )
2207        current_user_roles = trans.get_current_user_roles()
2208        if item_type not in item_types:
2209            message = 'Bad item_type specified: %s' % str( item_type )
2210            status = ERROR
2211        else:
2212            if item_type == 'library_dataset':
2213                item_desc = 'Dataset'
2214            else:
2215                item_desc = item_type.capitalize()
2216            try:
2217                library_item = trans.sa_session.query( item_types[ item_type ] ).get( trans.security.decode_id( item_id ) )
2218            except:
2219                library_item = None
2220            if not library_item or not ( is_admin or trans.app.security_agent.can_access_library_item( current_user_roles, library_item, trans.user ) ):
2221                message = 'Invalid %s id ( %s ) specifield.' % ( item_desc, item_id )
2222                status = 'error'
2223            elif library_item.purged:
2224                message = '%s %s has been purged, so it cannot be undeleted' % ( item_desc, library_item.name )
2225                status = ERROR
2226            elif not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, library_item ) ):
2227                message = "You are not authorized to delete %s '%s'." % ( item_desc, library_item.name )
2228                status = 'error'
2229            else:
2230                library_item.deleted = False
2231                trans.sa_session.add( library_item )
2232                trans.sa_session.flush()
2233                message = util.sanitize_text( "%s '%s' has been marked undeleted" % ( item_desc, library_item.name ) )
2234                status = SUCCESS
2235        if item_type == 'library':
2236            return trans.response.send_redirect( web.url_for( controller=cntrller,
2237                                                              action='browse_libraries',
2238                                                              message=message,
2239                                                              status=status ) )
2240        else:
2241            return trans.response.send_redirect( web.url_for( controller='library_common',
2242                                                              action='browse_library',
2243                                                              cntrller=cntrller,
2244                                                              id=library_id,
2245                                                              show_deleted=show_deleted,
2246                                                              message=message,
2247                                                              status=status ) )
2248    def _check_access( self, trans, cntrller, is_admin, item, current_user_roles, use_panels, library_id, show_deleted ):
2249        can_access = True
2250        if isinstance( item, trans.model.HistoryDatasetAssociation ):
2251            # Make sure the user has the DATASET_ACCESS permission on the history_dataset_association.
2252            if not item:
2253                message = "Invalid history dataset (%s) specified." % str( item )
2254                can_access = False
2255            elif not trans.app.security_agent.can_access_dataset( current_user_roles, item.dataset ) and item.history.user==trans.user:
2256                message = "You do not have permission to access the history dataset with id (%s)." % str( item.id )
2257                can_access = False
2258        else:
2259            # Make sure the user has the LIBRARY_ACCESS permission on the library item.
2260            if not item:
2261                message = "Invalid library item (%s) specified." % str( item )
2262                can_access = False
2263            elif not ( is_admin or trans.app.security_agent.can_access_library_item( current_user_roles, item, trans.user ) ):
2264                if isinstance( item, trans.model.Library ):
2265                    item_type = 'data library'
2266                elif isinstance( item, trans.model.LibraryFolder ):
2267                    item_type = 'folder'
2268                else:
2269                    item_type = '(unknown item type)'
2270                message = "You do not have permission to access the %s with id (%s)." % ( item_type, str( item.id ) )
2271                can_access = False
2272        if not can_access:
2273            if cntrller == 'api':
2274                return 400, message
2275            if isinstance( item, trans.model.Library ):
2276                return trans.response.send_redirect( web.url_for( controller=cntrller,
2277                                                                  action='browse_libraries',
2278                                                                  cntrller=cntrller,
2279                                                                  use_panels=use_panels,
2280                                                                  message=util.sanitize_text( message ),
2281                                                                  status='error' ) )
2282            return trans.response.send_redirect( web.url_for( controller='library_common',
2283                                                              action='browse_library',
2284                                                              cntrller=cntrller,
2285                                                              use_panels=use_panels,
2286                                                              id=library_id,
2287                                                              show_deleted=show_deleted,
2288                                                              message=util.sanitize_text( message ),
2289                                                              status='error' ) )
2290    def _check_add( self, trans, cntrller, is_admin, item, current_user_roles, use_panels, library_id, show_deleted ):
2291        # Deny access if the user is not an admin and does not have the LIBRARY_ADD permission.
2292        if not ( is_admin or trans.app.security_agent.can_add_library_item( current_user_roles, item ) ):
2293            message = "You are not authorized to add an item to (%s)." % item.name
2294            # Redirect to the real parent library since we know we have access to it.
2295            if cntrller == 'api':
2296                return 403, message
2297            return trans.response.send_redirect( web.url_for( controller='library_common',
2298                                                              action='browse_library',
2299                                                              cntrller=cntrller,
2300                                                              use_panels=use_panels,
2301                                                              id=library_id,
2302                                                              show_deleted=show_deleted,
2303                                                              message=util.sanitize_text( message ),
2304                                                              status='error' ) )
2305    def _check_manage( self, trans, cntrller, is_admin, item, current_user_roles, use_panels, library_id, show_deleted ):
2306        if isinstance( item, trans.model.LibraryDataset ):
2307            # Deny access if the user is not an admin and does not have the LIBRARY_MANAGE and DATASET_MANAGE_PERMISSIONS permissions.
2308            if not ( is_admin or \
2309                     ( trans.app.security_agent.can_manage_library_item( current_user_roles, item ) and
2310                       trans.app.security_agent.can_manage_dataset( current_user_roles, library_dataset.library_dataset_dataset_association.dataset ) ) ):
2311                message = "You are not authorized to manage permissions on library dataset (%s)." % library_dataset.name
2312                if cntrller == 'api':
2313                    return 403, message
2314                return trans.response.send_redirect( web.url_for( controller='library_common',
2315                                                                  action='browse_library',
2316                                                                  id=library_id,
2317                                                                  cntrller=cntrller,
2318                                                                  use_panels=use_panels,
2319                                                                  message=util.sanitize_text( message ),
2320                                                                  status='error' ) )
2321        # Deny access if the user is not an admin and does not have the LIBRARY_MANAGE permission.
2322        if not ( is_admin or trans.app.security_agent.can_manage_library_item( current_user_roles, item ) ):
2323            message = "You are not authorized to manage permissions on (%s)." % item.name
2324            if cntrller == 'api':
2325                return 403, message
2326            return trans.response.send_redirect( web.url_for( controller='library_common',
2327                                                              action='browse_library',
2328                                                              id=library_id,
2329                                                              cntrller=cntrller,
2330                                                              use_panels=use_panels,
2331                                                              message=util.sanitize_text( message ),
2332                                                              status='error' ) )
2333    def _check_modify( self, trans, cntrller, is_admin, item, current_user_roles, use_panels, library_id, show_deleted ):
2334        # Deny modification if the user is not an admin and does not have the LIBRARY_MODIFY permission.
2335        if not ( is_admin or trans.app.security_agent.can_modify_library_item( current_user_roles, item ) ):
2336            message = "You are not authorized to modify (%s)." % item.name
2337            if cntrller == 'api':
2338                return 403, message
2339            return trans.response.send_redirect( web.url_for( controller='library_common',
2340                                                              action='browse_library',
2341                                                              cntrller=cntrller,
2342                                                              id=library_id,
2343                                                              use_panels=use_panels,
2344                                                              show_deleted=show_deleted,
2345                                                              message=util.sanitize_text( message ),
2346                                                              status='error' ) )
2347
2348# ---- Utility methods -------------------------------------------------------
2349
2350def active_folders( trans, folder ):
2351    # Much faster way of retrieving all active sub-folders within a given folder than the
2352    # performance of the mapper.  This query also eagerloads the permissions on each folder.
2353    return trans.sa_session.query( trans.app.model.LibraryFolder ) \
2354                           .filter_by( parent=folder, deleted=False ) \
2355                           .options( eagerload_all( "actions" ) ) \
2356                           .order_by( trans.app.model.LibraryFolder.table.c.name ) \
2357                           .all()
2358def activatable_folders( trans, folder ):
2359    return trans.sa_session.query( trans.app.model.LibraryFolder ) \
2360                           .filter_by( parent=folder, purged=False ) \
2361                           .options( eagerload_all( "actions" ) ) \
2362                           .order_by( trans.app.model.LibraryFolder.table.c.name ) \
2363                           .all()
2364def active_folders_and_lddas( trans, folder ):
2365    folders = active_folders( trans, folder )
2366    # This query is much faster than the folder.active_library_datasets property
2367    lddas = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
2368                            .filter_by( deleted=False ) \
2369                            .join( "library_dataset" ) \
2370                            .filter( trans.app.model.LibraryDataset.table.c.folder_id==folder.id ) \
2371                            .order_by( trans.app.model.LibraryDatasetDatasetAssociation.table.c.name ) \
2372                            .all()
2373    return folders, lddas
2374def activatable_folders_and_lddas( trans, folder ):
2375    folders = activatable_folders( trans, folder )
2376    # This query is much faster than the folder.activatable_library_datasets property
2377    lddas = trans.sa_session.query( trans.app.model.LibraryDatasetDatasetAssociation ) \
2378                            .join( "library_dataset" ) \
2379                            .filter( trans.app.model.LibraryDataset.table.c.folder_id==folder.id ) \
2380                            .join( "dataset" ) \
2381                            .filter( trans.app.model.Dataset.table.c.deleted==False ) \
2382                            .order_by( trans.app.model.LibraryDatasetDatasetAssociation.table.c.name ) \
2383                            .all()
2384    return folders, lddas
2385def branch_deleted( folder ):
2386    # Return True if a folder belongs to a branch that has been deleted
2387    if folder.deleted:
2388        return True
2389    if folder.parent:
2390        return branch_deleted( folder.parent )
2391    return False
2392def get_containing_library_from_library_dataset( trans, library_dataset ):
2393    """Given a library_dataset, get the containing library"""
2394    folder = library_dataset.folder
2395    while folder.parent:
2396        folder = folder.parent
2397    # We have folder set to the library's root folder, which has the same name as the library
2398    for library in trans.sa_session.query( trans.model.Library ) \
2399                                   .filter( and_( trans.model.Library.table.c.deleted == False,
2400                                                  trans.model.Library.table.c.name == folder.name ) ):
2401        # Just to double-check
2402        if library.root_folder == folder:
2403            return library
2404    return None
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。