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

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

import galaxy-central

行番号 
1import logging, os, string, shutil, re, socket, mimetypes, smtplib, urllib, tempfile, zipfile, glob, sys
2
3from galaxy.web.base.controller import *
4from galaxy.web.framework.helpers import time_ago, iff, grids
5from galaxy import util, datatypes, jobs, web, model
6from cgi import escape, FieldStorage
7from galaxy.datatypes.display_applications.util import encode_dataset_user, decode_dataset_user
8from galaxy.util.sanitize_html import sanitize_html
9from galaxy.model.item_attrs import *
10
11from email.MIMEText import MIMEText
12import pkg_resources;
13pkg_resources.require( "Paste" )
14import paste.httpexceptions
15
16if sys.version_info[:2] < ( 2, 6 ):
17    zipfile.BadZipFile = zipfile.error
18if sys.version_info[:2] < ( 2, 5 ):
19    zipfile.LargeZipFile = zipfile.error
20
21tmpd = tempfile.mkdtemp()
22comptypes=[]
23ziptype = '32'
24tmpf = os.path.join( tmpd, 'compression_test.zip' )
25try:
26    archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
27    archive.close()
28    comptypes.append( 'zip' )
29    ziptype = '64'
30except RuntimeError:
31    log.exception( "Compression error when testing zip compression. This option will be disabled for library downloads." )
32except (TypeError, zipfile.LargeZipFile):    # ZIP64 is only in Python2.5+.  Remove TypeError when 2.4 support is dropped
33    log.warning( 'Max zip file size is 2GB, ZIP64 not supported' )   
34    comptypes.append( 'zip' )
35try:
36    os.unlink( tmpf )
37except OSError:
38    pass
39os.rmdir( tmpd )
40
41log = logging.getLogger( __name__ )
42
43error_report_template = """
44GALAXY TOOL ERROR REPORT
45------------------------
46
47This error report was sent from the Galaxy instance hosted on the server
48"${host}"
49-----------------------------------------------------------------------------
50This is in reference to dataset id ${dataset_id} from history id ${history_id}
51-----------------------------------------------------------------------------
52You should be able to view the history containing the related history item
53
54${hid}: ${history_item_name}
55
56by logging in as a Galaxy admin user to the Galaxy instance referenced above
57and pointing your browser to the following link.
58
59${history_view_link}
60-----------------------------------------------------------------------------
61The user '${email}' provided the following information:
62
63${message}
64-----------------------------------------------------------------------------
65job id: ${job_id}
66tool id: ${job_tool_id}
67-----------------------------------------------------------------------------
68job command line:
69${job_command_line}
70-----------------------------------------------------------------------------
71job stderr:
72${job_stderr}
73-----------------------------------------------------------------------------
74job stdout:
75${job_stdout}
76-----------------------------------------------------------------------------
77job info:
78${job_info}
79-----------------------------------------------------------------------------
80job traceback:
81${job_traceback}
82-----------------------------------------------------------------------------
83(This is an automated message).
84"""
85
86class HistoryDatasetAssociationListGrid( grids.Grid ):
87    # Custom columns for grid.
88    class HistoryColumn( grids.GridColumn ):
89        def get_value( self, trans, grid, hda):
90            return hda.history.name
91           
92    class StatusColumn( grids.GridColumn ):
93        def get_value( self, trans, grid, hda ):
94            if hda.deleted:
95                return "deleted"
96            return ""
97        def get_accepted_filters( self ):
98            """ Returns a list of accepted filters for this column. """
99            accepted_filter_labels_and_vals = { "Active" : "False", "Deleted" : "True", "All": "All" }
100            accepted_filters = []
101            for label, val in accepted_filter_labels_and_vals.items():
102               args = { self.key: val }
103               accepted_filters.append( grids.GridColumnFilter( label, args) )
104            return accepted_filters
105
106    # Grid definition
107    title = "Saved Datasets"
108    model_class = model.HistoryDatasetAssociation
109    template='/dataset/grid.mako'
110    default_sort_key = "-update_time"
111    columns = [
112        grids.TextColumn( "Name", key="name",
113                            # Link name to dataset's history.
114                            link=( lambda item: iff( item.history.deleted, None, dict( operation="switch", id=item.id ) ) ), filterable="advanced", attach_popup=True ),
115        HistoryColumn( "History", key="history",
116                        link=( lambda item: iff( item.history.deleted, None, dict( operation="switch_history", id=item.id ) ) ) ),
117        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryDatasetAssociationTagAssociation, filterable="advanced", grid_name="HistoryDatasetAssocationListGrid" ),
118        StatusColumn( "Status", key="deleted", attach_popup=False ),
119        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
120    ]
121    columns.append(
122        grids.MulticolFilterColumn( 
123        "Search",
124        cols_to_filter=[ columns[0], columns[2] ],
125        key="free-text-search", visible=False, filterable="standard" )
126                )
127    operations = [
128        grids.GridOperation( "Copy to current history", condition=( lambda item: not item.deleted ), async_compatible=False ),
129    ]
130    standard_filters = []
131    default_filter = dict( name="All", deleted="False", tags="All" )
132    preserve_state = False
133    use_paging = True
134    num_rows_per_page = 50
135    def build_initial_query( self, trans, **kwargs ):
136        # Show user's datasets that are not deleted, not in deleted histories, and not hidden.
137        # To filter HDAs by user, need to join model class/HDA and History table so that it is
138        # possible to filter by user. However, for dictionary-based filtering to work, need a
139        # primary table for the query.
140        return trans.sa_session.query( self.model_class ).select_from( self.model_class.table.join( model.History.table ) ) \
141                .filter( model.History.user == trans.user ) \
142                .filter( self.model_class.deleted==False ) \
143                .filter( model.History.deleted==False ) \
144                .filter( self.model_class.visible==True )
145       
146class DatasetInterface( BaseController, UsesAnnotations, UsesHistoryDatasetAssociation, UsesItemRatings ):
147       
148    stored_list_grid = HistoryDatasetAssociationListGrid()
149
150    @web.expose
151    def errors( self, trans, id ):
152        hda = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id )
153        return trans.fill_template( "dataset/errors.mako", hda=hda )
154    @web.expose
155    def stderr( self, trans, id ):
156        dataset = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id )
157        job = dataset.creating_job_associations[0].job
158        trans.response.set_content_type( 'text/plain' )
159        return job.stderr
160    @web.expose
161    def report_error( self, trans, id, email='', message="" ):
162        smtp_server = trans.app.config.smtp_server
163        if smtp_server is None:
164            return trans.show_error_message( "Mail is not configured for this galaxy instance" )
165        to_address = trans.app.config.error_email_to
166        if to_address is None:
167            return trans.show_error_message( "Error reporting has been disabled for this galaxy instance" )
168        # Get the dataset and associated job
169        hda = trans.sa_session.query( model.HistoryDatasetAssociation ).get( id )
170        job = hda.creating_job_associations[0].job
171        # Get the name of the server hosting the Galaxy instance from which this report originated
172        host = trans.request.host
173        history_view_link = "%s/history/view?id=%s" % ( str( host ), trans.security.encode_id( hda.history_id ) )
174        # Build the email message
175        msg = MIMEText( string.Template( error_report_template )
176            .safe_substitute( host=host,
177                              dataset_id=hda.dataset_id,
178                              history_id=hda.history_id,
179                              hid=hda.hid,
180                              history_item_name=hda.get_display_name(),
181                              history_view_link=history_view_link,
182                              job_id=job.id,
183                              job_tool_id=job.tool_id,
184                              job_command_line=job.command_line,
185                              job_stderr=job.stderr,
186                              job_stdout=job.stdout,
187                              job_info=job.info,
188                              job_traceback=job.traceback,
189                              email=email,
190                              message=message ) )
191        frm = to_address
192        # Check email a bit
193        email = email.strip()
194        parts = email.split()
195        if len( parts ) == 1 and len( email ) > 0:
196            to = to_address + ", " + email
197        else:
198            to = to_address
199        msg[ 'To' ] = to
200        msg[ 'From' ] = frm
201        msg[ 'Subject' ] = "Galaxy tool error report from " + email
202        # Send it
203        try:
204            s = smtplib.SMTP()
205            s.connect( smtp_server )
206            s.sendmail( frm, [ to ], msg.as_string() )
207            s.close()
208            return trans.show_ok_message( "Your error report has been sent" )
209        except Exception, e:
210            return trans.show_error_message( "An error occurred sending the report by email: %s" % str( e ) )
211   
212    @web.expose
213    def default(self, trans, dataset_id=None, **kwd):
214        return 'This link may not be followed from within Galaxy.'
215   
216    @web.expose
217    def archive_composite_dataset( self, trans, data=None, **kwd ):
218        # save a composite object into a compressed archive for downloading
219        params = util.Params( kwd )
220        valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
221        outfname = data.name[0:150]
222        outfname = ''.join(c in valid_chars and c or '_' for c in outfname)
223        if (params.do_action == None):
224            params.do_action = 'zip' # default
225        msg = util.restore_text( params.get( 'msg', ''  ) )
226        messagetype = params.get( 'messagetype', 'done' )
227        if not data:
228            msg = "You must select at least one dataset"
229            messagetype = 'error'
230        else:
231            error = False
232            try:
233                if (params.do_action == 'zip'):
234                    # Can't use mkstemp - the file must not exist first
235                    tmpd = tempfile.mkdtemp()
236                    tmpf = os.path.join( tmpd, 'library_download.' + params.do_action )
237                    if ziptype == '64':
238                        archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED, True )
239                    else:
240                        archive = zipfile.ZipFile( tmpf, 'w', zipfile.ZIP_DEFLATED )
241                    archive.add = lambda x, y: archive.write( x, y.encode('CP437') )
242                elif params.do_action == 'tgz':
243                    archive = util.streamball.StreamBall( 'w|gz' )
244                elif params.do_action == 'tbz':
245                    archive = util.streamball.StreamBall( 'w|bz2' )
246            except (OSError, zipfile.BadZipFile):
247                error = True
248                log.exception( "Unable to create archive for download" )
249                msg = "Unable to create archive for %s for download, please report this error" % outfname
250                messagetype = 'error'
251            if not error:
252                current_user_roles = trans.get_current_user_roles()
253                ext = data.extension
254                path = data.file_name
255                fname = os.path.split(path)[-1]
256                efp = data.extra_files_path
257                htmlname = os.path.splitext(outfname)[0]
258                if not htmlname.endswith(ext):
259                    htmlname = '%s_%s' % (htmlname,ext)
260                archname = '%s.html' % htmlname # fake the real nature of the html file
261                try:
262                    archive.add(data.file_name,archname)
263                except IOError:
264                    error = True
265                    log.exception( "Unable to add composite parent %s to temporary library download archive" % data.file_name)
266                    msg = "Unable to create archive for download, please report this error"
267                    messagetype = 'error'
268                flist = glob.glob(os.path.join(efp,'*.*')) # glob returns full paths
269                for fpath in flist:
270                    efp,fname = os.path.split(fpath)
271                    try:
272                        archive.add( fpath,fname )
273                    except IOError:
274                        error = True
275                        log.exception( "Unable to add %s to temporary library download archive" % fname)
276                        msg = "Unable to create archive for download, please report this error"
277                        messagetype = 'error'
278                        continue
279                if not error:   
280                    if params.do_action == 'zip':
281                        archive.close()
282                        tmpfh = open( tmpf )
283                        # clean up now
284                        try:
285                            os.unlink( tmpf )
286                            os.rmdir( tmpd )
287                        except OSError:
288                            error = True
289                            msg = "Unable to remove temporary library download archive and directory"
290                            log.exception( msg )
291                            messagetype = 'error'
292                        if not error:
293                            trans.response.set_content_type( "application/x-zip-compressed" )
294                            trans.response.headers[ "Content-Disposition" ] = "attachment; filename=%s.zip" % outfname
295                            return tmpfh
296                    else:
297                        trans.response.set_content_type( "application/x-tar" )
298                        outext = 'tgz'
299                        if params.do_action == 'tbz':
300                            outext = 'tbz'
301                        trans.response.headers[ "Content-Disposition" ] = "attachment; filename=%s.%s" % (outfname,outext)
302                        archive.wsgi_status = trans.response.wsgi_status()
303                        archive.wsgi_headeritems = trans.response.wsgi_headeritems()
304                        return archive.stream
305        return trans.show_error_message( msg )
306
307
308   
309    @web.expose
310    def display(self, trans, dataset_id=None, preview=False, filename=None, to_ext=None, **kwd):
311        """Catches the dataset id and displays file contents as directed"""
312        composite_extensions = trans.app.datatypes_registry.get_composite_extensions( )
313        composite_extensions.append('html') # for archiving composite datatypes
314        # DEPRECATION: We still support unencoded ids for backward compatibility
315        try:
316            data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( trans.security.decode_id( dataset_id ) )
317            if data is None:
318                raise ValueError( 'Invalid reference dataset id: %s.' % dataset_id )
319        except:
320            try:
321                data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( int( dataset_id ) )
322            except:
323                data = None
324        if not data:
325            raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( dataset_id ) )
326        current_user_roles = trans.get_current_user_roles()
327        if not trans.app.security_agent.can_access_dataset( current_user_roles, data.dataset ):
328            return trans.show_error_message( "You are not allowed to access this dataset" )
329       
330        if data.state == trans.model.Dataset.states.UPLOAD:
331            return trans.show_error_message( "Please wait until this dataset finishes uploading before attempting to view it." )
332       
333        if filename and filename != "index":
334            # For files in extra_files_path
335            file_path = os.path.join( data.extra_files_path, filename )
336            if os.path.exists( file_path ):
337                mime, encoding = mimetypes.guess_type( file_path )
338                if not mime:
339                    try:
340                        mime = trans.app.datatypes_registry.get_mimetype_by_extension( ".".split( file_path )[-1] )
341                    except:
342                        mime = "text/plain"
343           
344                trans.response.set_content_type( mime )
345                return open( file_path )
346            else:
347                return "Could not find '%s' on the extra files path %s." % (filename,file_path)
348       
349        mime = trans.app.datatypes_registry.get_mimetype_by_extension( data.extension.lower() )
350        trans.response.set_content_type(mime)
351        trans.log_event( "Display dataset id: %s" % str( dataset_id ) )
352       
353        if to_ext or isinstance(data.datatype, datatypes.binary.Binary): # Saving the file, or binary file
354            if data.extension in composite_extensions:
355                return self.archive_composite_dataset( trans, data, **kwd )
356            else:                   
357                trans.response.headers['Content-Length'] = int( os.stat( data.file_name ).st_size )
358                if not to_ext:
359                    to_ext = data.extension
360                valid_chars = '.,^_-()[]0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
361                fname = data.name
362                fname = ''.join(c in valid_chars and c or '_' for c in fname)[0:150]
363                trans.response.headers["Content-Disposition"] = "attachment; filename=Galaxy%s-[%s].%s" % (data.hid, fname, to_ext)
364                return open( data.file_name )
365        if not os.path.exists( data.file_name ):
366            raise paste.httpexceptions.HTTPNotFound( "File Not Found (%s)." % data.file_name )
367       
368        max_peek_size = 1000000 # 1 MB
369        if not preview or isinstance(data.datatype, datatypes.images.Image) or os.stat( data.file_name ).st_size < max_peek_size:
370            return open( data.file_name )
371        else:
372            trans.response.set_content_type( "text/html" )
373            return trans.stream_template_mako( "/dataset/large_file.mako",
374                                            truncated_data = open( data.file_name ).read(max_peek_size),
375                                            data = data )
376                       
377    @web.expose
378    @web.require_login( "see all available datasets" )
379    def list( self, trans, **kwargs ):
380        """List all available datasets"""
381        status = message = None
382
383        if 'operation' in kwargs:
384            operation = kwargs['operation'].lower()
385            hda_ids = util.listify( kwargs.get( 'id', [] ) )
386           
387            # Display no message by default
388            status, message = None, None
389
390            # Load the hdas and ensure they all belong to the current user
391            hdas = []
392            for encoded_hda_id in hda_ids:
393                hda_id = trans.security.decode_id( encoded_hda_id )
394                hda = trans.sa_session.query( model.HistoryDatasetAssociation ).filter_by( id=hda_id ).first()
395                if hda:
396                    # Ensure history is owned by current user
397                    if hda.history.user_id != None and trans.user:
398                        assert trans.user.id == hda.history.user_id, "HistoryDatasetAssocation does not belong to current user"
399                    hdas.append( hda )
400                else:
401                    log.warn( "Invalid history_dataset_association id '%r' passed to list", hda_id )
402
403            if hdas:
404                if operation == "switch" or operation == "switch_history":
405                    # Switch to a history that the HDA resides in.
406                   
407                    # Convert hda to histories.
408                    histories = []
409                    for hda in hdas:
410                        histories.append( hda.history )
411                       
412                    # Use history controller to switch the history. TODO: is this reasonable?
413                    status, message = trans.webapp.controllers['history']._list_switch( trans, histories )
414                   
415                    # Current history changed, refresh history frame; if switching to a dataset, set hda seek.
416                    trans.template_context['refresh_frames'] = ['history']
417                    if operation == "switch":
418                        hda_ids = [ trans.security.encode_id( hda.id ) for hda in hdas ]
419                        trans.template_context[ 'seek_hda_ids' ] = hda_ids
420                elif operation == "copy to current history":
421                    # Copy a dataset to the current history.
422                    target_histories = [ trans.get_history() ]
423                    status, message = self._copy_datasets( trans, hda_ids, target_histories )
424                   
425                    # Current history changed, refresh history frame.
426                    trans.template_context['refresh_frames'] = ['history']
427
428        # Render the list view
429        return self.stored_list_grid( trans, status=status, message=message, **kwargs )
430       
431    @web.expose
432    def imp( self, trans, dataset_id=None, **kwd ):
433        """ Import another user's dataset via a shared URL; dataset is added to user's current history. """
434        msg = ""
435       
436        # Set referer message.
437        referer = trans.request.referer
438        if referer is not "":
439            referer_message = "<a href='%s'>return to the previous page</a>" % referer
440        else:
441            referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
442       
443        # Error checking.
444        if not dataset_id:
445            return trans.show_error_message( "You must specify a dataset to import. You can %s." % referer_message, use_panels=True )
446           
447        # Do import.
448        cur_history = trans.get_history( create=True )
449        status, message = self._copy_datasets( trans, [ dataset_id ], [ cur_history ], imported=True )
450        message = "Dataset imported. <br>You can <a href='%s'>start using the dataset</a> or %s." % ( url_for('/'),  referer_message )
451        return trans.show_message( message, type=status, use_panels=True )
452       
453    @web.expose
454    @web.json
455    @web.require_login( "use Galaxy datasets" )
456    def get_name_and_link_async( self, trans, id=None ):
457        """ Returns dataset's name and link. """
458        dataset = self.get_dataset( trans, id, False, True )
459        return_dict = { "name" : dataset.name, "link" : url_for( action="display_by_username_and_slug", username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) ) }
460        return return_dict
461               
462    @web.expose
463    def get_embed_html_async( self, trans, id ):
464        """ Returns HTML for embedding a dataset in a page. """
465        dataset = self.get_dataset( trans, id, False, True )
466        if dataset:
467            return "Embedded Dataset '%s'" % dataset.name
468
469    @web.expose
470    @web.require_login( "use Galaxy datasets" )
471    def set_accessible_async( self, trans, id=None, accessible=False ):
472        """ Does nothing because datasets do not have an importable/accessible attribute. This method could potentially set another attribute. """
473        return
474       
475    @web.expose
476    @web.require_login( "rate items" )
477    @web.json
478    def rate_async( self, trans, id, rating ):
479        """ Rate a dataset asynchronously and return updated community data. """
480
481        dataset = self.get_dataset( trans, id, check_ownership=False, check_accessible=True )
482        if not dataset:
483            return trans.show_error_message( "The specified dataset does not exist." )
484
485        # Rate dataset.
486        dataset_rating = self.rate_item( rate_item, trans.get_user(), dataset, rating )
487
488        return self.get_ave_item_rating_data( trans.sa_session, dataset )
489       
490    @web.expose
491    def display_by_username_and_slug( self, trans, username, slug, preview=True ):
492        """ Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id. """
493        dataset = self.get_dataset( trans, slug, False, True )
494        if dataset:
495            truncated, dataset_data = self.get_data( dataset, preview )
496            dataset.annotation = self.get_item_annotation_str( trans.sa_session, dataset.history.user, dataset )
497           
498            # If data is binary or an image, stream without template; otherwise, use display template.
499            # TODO: figure out a way to display images in display template.
500            if isinstance(dataset.datatype, datatypes.binary.Binary) or isinstance(dataset.datatype, datatypes.images.Image):
501                mime = trans.app.datatypes_registry.get_mimetype_by_extension( dataset.extension.lower() )
502                trans.response.set_content_type( mime )
503                return open( dataset.file_name )
504            else:
505                # Get rating data.
506                user_item_rating = 0
507                if trans.get_user():
508                    user_item_rating = self.get_user_item_rating( trans.sa_session, trans.get_user(), dataset )
509                    if user_item_rating:
510                        user_item_rating = user_item_rating.rating
511                    else:
512                        user_item_rating = 0
513                ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, dataset )
514               
515                return trans.fill_template_mako( "/dataset/display.mako", item=dataset, item_data=dataset_data, truncated=truncated,
516                                                user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings )
517        else:
518            raise web.httpexceptions.HTTPNotFound()
519           
520    @web.expose
521    def get_item_content_async( self, trans, id ):
522        """ Returns item content in HTML format. """
523
524        dataset = self.get_dataset( trans, id, False, True )
525        if dataset is None:
526            raise web.httpexceptions.HTTPNotFound()
527        truncated, dataset_data = self.get_data( dataset, preview=True )
528        # Get annotation.
529        dataset.annotation = self.get_item_annotation_str( trans.sa_session, trans.user, dataset )
530        return trans.stream_template_mako( "/dataset/item_content.mako", item=dataset, item_data=dataset_data, truncated=truncated )
531       
532    @web.expose
533    def annotate_async( self, trans, id, new_annotation=None, **kwargs ):
534        dataset = self.get_dataset( trans, id, False, True )
535        if not dataset:
536            web.httpexceptions.HTTPNotFound()
537        if dataset and new_annotation:
538            # Sanitize annotation before adding it.
539            new_annotation = sanitize_html( new_annotation, 'utf-8', 'text/html' )
540            self.add_item_annotation( trans.sa_session, trans.get_user(), dataset, new_annotation )
541            trans.sa_session.flush()
542            return new_annotation
543   
544    @web.expose
545    def get_annotation_async( self, trans, id ):
546        dataset = self.get_dataset( trans, id, False, True )
547        if not dataset:
548            web.httpexceptions.HTTPNotFound()
549        return self.get_item_annotation_str( trans.sa_session, trans.user, dataset )
550
551    @web.expose
552    def display_at( self, trans, dataset_id, filename=None, **kwd ):
553        """Sets up a dataset permissions so it is viewable at an external site"""
554        site = filename
555        data = trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get( dataset_id )
556        if not data:
557            raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( dataset_id ) )
558        if 'display_url' not in kwd or 'redirect_url' not in kwd:
559            return trans.show_error_message( 'Invalid parameters specified for "display at" link, please contact a Galaxy administrator' )
560        try:
561              redirect_url = kwd['redirect_url'] % urllib.quote_plus( kwd['display_url'] )
562        except:
563              redirect_url = kwd['redirect_url'] # not all will need custom text
564        current_user_roles = trans.get_current_user_roles()
565        if trans.app.security_agent.dataset_is_public( data.dataset ):
566            return trans.response.send_redirect( redirect_url ) # anon access already permitted by rbac
567        if trans.app.security_agent.can_access_dataset( current_user_roles, data.dataset ):
568            trans.app.host_security_agent.set_dataset_permissions( data, trans.user, site )
569            return trans.response.send_redirect( redirect_url )
570        else:
571            return trans.show_error_message( "You are not allowed to view this dataset at external sites.  Please contact your Galaxy administrator to acquire management permissions for this dataset." )
572
573    @web.expose
574    def display_application( self, trans, dataset_id=None, user_id=None, app_name = None, link_name = None, app_action = None, action_param = None, **kwds ):
575        """Access to external display applications"""
576        if kwds:
577            log.debug( "Unexpected Keywords passed to display_application: %s" % kwds ) #route memory?
578        #decode ids
579        data, user = decode_dataset_user( trans, dataset_id, user_id )
580        if not data:
581            raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid reference dataset id: %s." % str( dataset_id ) )
582        if user is None:
583            user = trans.user
584        if user:
585            user_roles = user.all_roles()
586        else:
587            user_roles = []
588        if None in [ app_name, link_name ]:
589            return trans.show_error_message( "A display application name and link name must be provided." )
590       
591        if trans.app.security_agent.can_access_dataset( user_roles, data.dataset ):
592            msg = []
593            refresh = False
594            display_app = trans.app.datatypes_registry.display_applications.get( app_name )
595            assert display_app, "Unknown display application has been requested: %s" % app_name
596            dataset_hash, user_hash = encode_dataset_user( trans, data, user )
597            display_link = display_app.get_link( link_name, data, dataset_hash, user_hash, trans )
598            assert display_link, "Unknown display link has been requested: %s" % link_name
599            if data.state == data.states.ERROR:
600                msg.append( ( 'This dataset is in an error state, you cannot view it at an external display application.', 'error' ) )
601            elif data.deleted:
602                msg.append( ( 'This dataset has been deleted, you cannot view it at an external display application.', 'error' ) )
603            elif data.state != data.states.OK:
604                msg.append( ( 'You must wait for this dataset to be created before you can view it at an external display application.', 'info' ) )
605                refresh = True
606            else:
607                #We have permissions, dataset is not deleted and is in OK state, allow access
608                if display_link.display_ready():
609                    if app_action in [ 'data', 'param' ]:
610                        assert action_param, "An action param must be provided for a data or param action"
611                        #data is used for things with filenames that could be passed off to a proxy
612                        #in case some display app wants all files to be in the same 'directory',
613                        #data can be forced to param, but not the other way (no filename for other direction)
614                        #get param name from url param name
615                        action_param = display_link.get_param_name_by_url( action_param )
616                        value = display_link.get_param_value( action_param )
617                        assert value, "An invalid parameter name was provided: %s" % action_param
618                        assert value.parameter.viewable, "This parameter is not viewable."
619                        if value.parameter.type == 'data':
620                            content_length = os.path.getsize( value.file_name )
621                            rval = open( value.file_name )
622                        else:
623                            rval = str( value )
624                            content_length = len( rval )
625                        trans.response.set_content_type( value.mime_type() )
626                        trans.response.headers[ 'Content-Length' ] = content_length
627                        return rval
628                    elif app_action == None:
629                        #redirect user to url generated by display link
630                        return trans.response.send_redirect( display_link.display_url() )
631                    else:
632                        msg.append( ( 'Invalid action provided: %s' % app_action, 'error' ) )
633                else:
634                    if app_action == None:
635                        if trans.history != data.history:
636                            msg.append( ( 'You must import this dataset into your current history before you can view it at the desired display application.', 'error' ) )
637                        else:
638                            refresh = True
639                            msg.append( ( 'This display application is being prepared.', 'info' ) )
640                            if not display_link.preparing_display():
641                                display_link.prepare_display()
642                    else:
643                        raise Exception( 'Attempted a view action (%s) on a non-ready display application' % app_action )
644            return trans.fill_template_mako( "dataset/display_application/display.mako", msg = msg, display_app = display_app, display_link = display_link, refresh = refresh )
645        return trans.show_error_message( 'You do not have permission to view this dataset at an external display application.' )
646
647    def _undelete( self, trans, id ):
648        try:
649            id = int( id )
650        except ValueError, e:
651            return False
652        history = trans.get_history()
653        data = trans.sa_session.query( self.app.model.HistoryDatasetAssociation ).get( id )
654        if data and data.undeletable:
655            # Walk up parent datasets to find the containing history
656            topmost_parent = data
657            while topmost_parent.parent:
658                topmost_parent = topmost_parent.parent
659            assert topmost_parent in history.datasets, "Data does not belong to current history"
660            # Mark undeleted
661            data.mark_undeleted()
662            trans.sa_session.flush()
663            trans.log_event( "Dataset id %s has been undeleted" % str(id) )
664            return True
665        return False
666
667    def _unhide( self, trans, id ):
668        try:
669            id = int( id )
670        except ValueError, e:
671            return False
672        history = trans.get_history()
673        data = trans.sa_session.query( self.app.model.HistoryDatasetAssociation ).get( id )
674        if data:
675            # Walk up parent datasets to find the containing history
676            topmost_parent = data
677            while topmost_parent.parent:
678                topmost_parent = topmost_parent.parent
679            assert topmost_parent in history.datasets, "Data does not belong to current history"
680            # Mark undeleted
681            data.mark_unhidden()
682            trans.sa_session.flush()
683            trans.log_event( "Dataset id %s has been unhidden" % str(id) )
684            return True
685        return False
686   
687    @web.expose
688    def undelete( self, trans, id ):
689        if self._undelete( trans, id ):
690            return trans.response.send_redirect( web.url_for( controller='root', action='history', show_deleted = True ) )
691        raise "Error undeleting"
692
693    @web.expose
694    def unhide( self, trans, id ):
695        if self._unhide( trans, id ):
696            return trans.response.send_redirect( web.url_for( controller='root', action='history', show_hidden = True ) )
697        raise "Error unhiding"
698
699
700    @web.expose
701    def undelete_async( self, trans, id ):
702        if self._undelete( trans, id ):
703            return "OK"
704        raise "Error undeleting"
705   
706    @web.expose
707    def copy_datasets( self, trans, source_dataset_ids="", target_history_ids="", new_history_name="", do_copy=False, **kwd ):
708        params = util.Params( kwd )
709        user = trans.get_user()
710        history = trans.get_history()
711        create_new_history = False
712        refresh_frames = []
713        if source_dataset_ids:
714            if not isinstance( source_dataset_ids, list ):
715                source_dataset_ids = source_dataset_ids.split( "," )
716            source_dataset_ids = map( int, source_dataset_ids )
717        else:
718            source_dataset_ids = []
719        if target_history_ids:
720            if not isinstance( target_history_ids, list ):
721                target_history_ids = target_history_ids.split( "," )
722            if "create_new_history" in target_history_ids:
723                create_new_history = True
724                target_history_ids.remove( "create_new_history" )
725            target_history_ids = map( int, target_history_ids )
726        else:
727            target_history_ids = []
728        done_msg = error_msg = ""
729        if do_copy:
730            invalid_datasets = 0
731            if not source_dataset_ids or not ( target_history_ids or create_new_history ):
732                error_msg = "You must provide both source datasets and target histories."
733                if create_new_history:
734                    target_history_ids.append( "create_new_history" )
735            else:
736                if create_new_history:
737                    new_history = trans.app.model.History()
738                    if new_history_name:
739                        new_history.name = new_history_name
740                    new_history.user = user
741                    trans.sa_session.add( new_history )
742                    trans.sa_session.flush()
743                    target_history_ids.append( new_history.id )
744                if user:
745                    target_histories = [ hist for hist in map( trans.sa_session.query( trans.app.model.History ).get, target_history_ids ) if ( hist is not None and hist.user == user )]
746                else:
747                    target_histories = [ history ]
748                if len( target_histories ) != len( target_history_ids ):
749                    error_msg = error_msg + "You do not have permission to add datasets to %i requested histories.  " % ( len( target_history_ids ) - len( target_histories ) )
750                for data in map( trans.sa_session.query( trans.app.model.HistoryDatasetAssociation ).get, source_dataset_ids ):
751                    if data is None:
752                        error_msg = error_msg + "You tried to copy a dataset that does not exist.  "
753                        invalid_datasets += 1
754                    elif data.history != history:
755                        error_msg = error_msg + "You tried to copy a dataset which is not in your current history.  "
756                        invalid_datasets += 1
757                    else:
758                        for hist in target_histories:
759                            hist.add_dataset( data.copy( copy_children = True ) )
760                if history in target_histories:
761                    refresh_frames = ['history']
762                trans.sa_session.flush()
763                done_msg = "%i datasets copied to %i histories." % ( len( source_dataset_ids ) - invalid_datasets, len( target_histories ) )
764                trans.sa_session.refresh( history )
765        elif create_new_history:
766            target_history_ids.append( "create_new_history" )
767        source_datasets = history.active_datasets
768        target_histories = [history]
769        if user:
770           target_histories = user.active_histories
771        return trans.fill_template( "/dataset/copy_view.mako",
772                                    source_dataset_ids = source_dataset_ids,
773                                    target_history_ids = target_history_ids,
774                                    source_datasets = source_datasets,
775                                    target_histories = target_histories,
776                                    new_history_name = new_history_name,
777                                    done_msg = done_msg,
778                                    error_msg = error_msg,
779                                    refresh_frames = refresh_frames )
780
781    def _copy_datasets( self, trans, dataset_ids, target_histories, imported=False ):
782        """ Helper method for copying datasets. """
783        user = trans.get_user()
784        done_msg = error_msg = ""
785       
786        invalid_datasets = 0
787        if not dataset_ids or not target_histories:
788            error_msg = "You must provide both source datasets and target histories."
789        else:
790            # User must own target histories to copy datasets to them.
791            for history in target_histories:
792                if user != history.user:
793                    error_msg = error_msg + "You do not have permission to add datasets to %i requested histories.  " % ( len( target_histories ) )
794            for dataset_id in dataset_ids:
795                data = self.get_dataset( trans, dataset_id, False, True )
796                if data is None:
797                    error_msg = error_msg + "You tried to copy a dataset that does not exist or that you do not have access to.  "
798                    invalid_datasets += 1
799                else:
800                    for hist in target_histories:
801                        dataset_copy = data.copy( copy_children = True )
802                        if imported:
803                            dataset_copy.name = "imported: " + dataset_copy.name
804                        hist.add_dataset( dataset_copy )
805            trans.sa_session.flush()
806            num_datasets_copied = len( dataset_ids ) - invalid_datasets
807            done_msg = "%i dataset%s copied to %i histor%s." % \
808                ( num_datasets_copied, iff( num_datasets_copied == 1, "", "s"), len( target_histories ), iff( len ( target_histories ) == 1, "y", "ies") )
809            trans.sa_session.refresh( history )
810       
811        if error_msg != "":
812            status = ERROR
813            message = error_msg
814        else:
815            status = SUCCESS
816            message = done_msg
817        return status, message
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。