PageRenderTime 72ms CodeModel.GetById 26ms app.highlight 39ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/galaxy/web/controllers/visualization.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 457 lines | 446 code | 7 blank | 4 comment | 12 complexity | 10d22404979008dff44e127c3831f4d4 MD5 | raw file
  1from galaxy import model
  2from galaxy.model.item_attrs import *
  3from galaxy.web.base.controller import *
  4from galaxy.web.framework.helpers import time_ago, grids, iff
  5from galaxy.util.sanitize_html import sanitize_html
  6
  7
  8class VisualizationListGrid( grids.Grid ):
  9    # Grid definition
 10    title = "Saved Visualizations"
 11    model_class = model.Visualization
 12    default_sort_key = "-update_time"
 13    default_filter = dict( title="All", deleted="False", tags="All", sharing="All" )
 14    columns = [
 15        grids.TextColumn( "Title", key="title", attach_popup=True,
 16                         link=( lambda item: dict( controller="tracks", action="browser", id=item.id ) ) ),
 17        grids.TextColumn( "Dbkey", key="dbkey" ),
 18        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationListGrid" ),
 19        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ),
 20        grids.GridColumn( "Created", key="create_time", format=time_ago ),
 21        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
 22    ]    
 23    columns.append( 
 24        grids.MulticolFilterColumn(  
 25        "Search", 
 26        cols_to_filter=[ columns[0], columns[2] ], 
 27        key="free-text-search", visible=False, filterable="standard" )
 28                )
 29    global_actions = [
 30        grids.GridAction( "Create new visualization", dict( action='create' ) )
 31    ]
 32    operations = [
 33        grids.GridOperation( "View/Edit", allow_multiple=False, url_args=dict( controller='tracks', action='browser' ) ),
 34        grids.GridOperation( "Edit Attributes", allow_multiple=False, url_args=dict( action='edit') ),
 35        grids.GridOperation( "Clone", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False, url_args=dict( action='clone') ),
 36        grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
 37        grids.GridOperation( "Delete", condition=( lambda item: not item.deleted ), async_compatible=True, confirm="Are you sure you want to delete this visualization?" ),
 38    ]
 39    def apply_query_filter( self, trans, query, **kwargs ):
 40        return query.filter_by( user=trans.user, deleted=False )
 41        
 42class VisualizationAllPublishedGrid( grids.Grid ):
 43    # Grid definition
 44    use_panels = True
 45    use_async = True
 46    title = "Published Visualizations"
 47    model_class = model.Visualization
 48    default_sort_key = "update_time"
 49    default_filter = dict( title="All", username="All" )
 50    columns = [
 51        grids.PublicURLColumn( "Title", key="title", filterable="advanced" ),
 52        grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_annotation_association_class=model.VisualizationAnnotationAssociation, filterable="advanced" ),
 53        grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced" ),
 54        grids.CommunityRatingColumn( "Community Rating", key="rating" ), 
 55        grids.CommunityTagsColumn( "Community Tags", key="tags", model_tag_association_class=model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationAllPublishedGrid" ),
 56        grids.ReverseSortColumn( "Last Updated", key="update_time", format=time_ago )
 57    ]
 58    columns.append( 
 59        grids.MulticolFilterColumn(  
 60        "Search title, annotation, owner, and tags", 
 61        cols_to_filter=[ columns[0], columns[1], columns[2], columns[4] ], 
 62        key="free-text-search", visible=False, filterable="standard" )
 63                )
 64    def build_initial_query( self, trans, **kwargs ):
 65        # Join so that searching history.user makes sense.
 66        return trans.sa_session.query( self.model_class ).join( model.User.table )
 67    def apply_query_filter( self, trans, query, **kwargs ):
 68        return query.filter( self.model_class.deleted==False ).filter( self.model_class.published==True )
 69
 70
 71class VisualizationController( BaseUIController, Sharable, UsesAnnotations, 
 72                                UsesHistoryDatasetAssociation, UsesVisualization, 
 73                                UsesItemRatings ):
 74    _user_list_grid = VisualizationListGrid()
 75    _published_list_grid = VisualizationAllPublishedGrid()
 76    
 77    @web.expose
 78    def list_published( self, trans, *args, **kwargs ):
 79        grid = self._published_list_grid( trans, **kwargs )
 80        if 'async' in kwargs:
 81            return grid
 82        else:
 83            # Render grid wrapped in panels
 84            return trans.fill_template( "visualization/list_published.mako", grid=grid )
 85
 86    @web.expose
 87    @web.require_login( "use Galaxy visualizations", use_panels=True )
 88    def index( self, trans, *args, **kwargs ):
 89        """ Lists user's saved visualizations. """
 90        return self.list( trans, *args, **kwargs )
 91    
 92    @web.expose
 93    @web.require_login()
 94    def clone(self, trans, id, *args, **kwargs):
 95        visualization = self.get_visualization( trans, id, check_ownership=False )            
 96        user = trans.get_user()
 97        owner = ( visualization.user == user )
 98        new_title = "Copy of '%s'" % visualization.title
 99        if not owner:
100            new_title += " shared by %s" % visualization.user.email
101            
102        cloned_visualization = visualization.copy( user=trans.user, title=new_title )
103        
104        # Persist
105        session = trans.sa_session
106        session.add( cloned_visualization )
107        session.flush()
108        
109        # Display the management page
110        trans.set_message( 'Copy created with name "%s"' % cloned_visualization.title )
111        return self.list( trans )
112        
113    @web.expose
114    @web.require_login( "use Galaxy visualizations", use_panels=True )
115    def list( self, trans, *args, **kwargs ):
116        # Handle operation
117        if 'operation' in kwargs and 'id' in kwargs:
118            session = trans.sa_session
119            operation = kwargs['operation'].lower()
120            ids = util.listify( kwargs['id'] )
121            for id in ids:
122                item = session.query( model.Visualization ).get( trans.security.decode_id( id ) )
123                if operation == "delete":
124                    item.deleted = True
125                if operation == "share or publish":
126                    return self.sharing( trans, **kwargs )
127            session.flush()
128            
129        # Build list of visualizations shared with user.
130        shared_by_others = trans.sa_session \
131            .query( model.VisualizationUserShareAssociation ) \
132            .filter_by( user=trans.get_user() ) \
133            .join( model.Visualization.table ) \
134            .filter( model.Visualization.deleted == False ) \
135            .order_by( desc( model.Visualization.update_time ) ) \
136            .all()
137        
138        return trans.fill_template( "visualization/list.mako", grid=self._user_list_grid( trans, *args, **kwargs ), shared_by_others=shared_by_others )
139        
140    @web.expose
141    @web.require_login( "modify Galaxy visualizations" )
142    def set_slug_async( self, trans, id, new_slug ):
143        """ Set item slug asynchronously. """
144        visualization = self.get_visualization( trans, id )
145        if visualization:
146            visualization.slug = new_slug
147            trans.sa_session.flush()
148            return visualization.slug
149            
150    @web.expose
151    @web.require_login( "use Galaxy visualizations" )
152    def set_accessible_async( self, trans, id=None, accessible=False ):
153        """ Set visualization's importable attribute and slug. """
154        visualization = self.get_visualization( trans, id )
155
156        # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
157        importable = accessible in ['True', 'true', 't', 'T'];
158        if visualization and visualization.importable != importable:
159            if importable:
160                self._make_item_accessible( trans.sa_session, visualization )
161            else:
162                visualization.importable = importable
163            trans.sa_session.flush()
164
165        return
166        
167    @web.expose
168    @web.require_login( "rate items" )
169    @web.json
170    def rate_async( self, trans, id, rating ):
171        """ Rate a visualization asynchronously and return updated community data. """
172
173        visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
174        if not visualization:
175            return trans.show_error_message( "The specified visualization does not exist." )
176
177        # Rate visualization.
178        visualization_rating = self.rate_item( trans.sa_session, trans.get_user(), visualization, rating )
179
180        return self.get_ave_item_rating_data( trans.sa_session, visualization )
181        
182    @web.expose
183    @web.require_login( "share Galaxy visualizations" )
184    def imp( self, trans, id ):
185        """ Import a visualization into user's workspace. """
186        # Set referer message.
187        referer = trans.request.referer
188        if referer is not "":
189            referer_message = "<a href='%s'>return to the previous page</a>" % referer
190        else:
191            referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
192                    
193        # Do import.
194        session = trans.sa_session
195        visualization = self.get_visualization( trans, id, check_ownership=False )
196        if visualization.importable == False:
197            return trans.show_error_message( "The owner of this visualization has disabled imports via this link.<br>You can %s" % referer_message, use_panels=True )
198        elif visualization.deleted:
199            return trans.show_error_message( "You can't import this visualization because it has been deleted.<br>You can %s" % referer_message, use_panels=True )
200        else:
201            # Create imported visualization via copy. 
202            #   TODO: need to handle custom db keys.
203            
204            imported_visualization = visualization.copy( user=trans.user, title="imported: " + visualization.title )
205            
206            # Persist
207            session = trans.sa_session
208            session.add( imported_visualization )
209            session.flush()
210            
211            # Redirect to load galaxy frames.
212            return trans.show_ok_message(
213                message="""Visualization "%s" has been imported. <br>You can <a href="%s">start using this visualization</a> or %s.""" 
214                % ( visualization.title, web.url_for( controller='visualization' ), referer_message ), use_panels=True )
215        
216
217    @web.expose
218    @web.require_login( "share Galaxy visualizations" )
219    def sharing( self, trans, id, **kwargs ):
220        """ Handle visualization sharing. """
221
222        # Get session and visualization.
223        session = trans.sa_session
224        visualization = self.get_visualization( trans, id, check_ownership=True )
225
226        # Do operation on visualization.
227        if 'make_accessible_via_link' in kwargs:
228            self._make_item_accessible( trans.sa_session, visualization )
229        elif 'make_accessible_and_publish' in kwargs:
230            self._make_item_accessible( trans.sa_session, visualization )
231            visualization.published = True
232        elif 'publish' in kwargs:
233            visualization.published = True
234        elif 'disable_link_access' in kwargs:
235            visualization.importable = False
236        elif 'unpublish' in kwargs:
237            visualization.published = False
238        elif 'disable_link_access_and_unpublish' in kwargs:
239            visualization.importable = visualization.published = False
240        elif 'unshare_user' in kwargs:
241            user = session.query( model.User ).get( trans.security.decode_id( kwargs['unshare_user' ] ) )
242            if not user:
243                error( "User not found for provided id" )
244            association = session.query( model.VisualizationUserShareAssociation ) \
245                                 .filter_by( user=user, visualization=visualization ).one()
246            session.delete( association )
247
248        session.flush()
249
250        return trans.fill_template( "/sharing_base.mako", item=visualization, use_panels=True )
251
252    @web.expose
253    @web.require_login( "share Galaxy visualizations" )
254    def share( self, trans, id=None, email="", use_panels=False ):
255        """ Handle sharing a visualization with a particular user. """
256        msg = mtype = None
257        visualization = self.get_visualization( trans, id, check_ownership=True )
258        if email:
259            other = trans.sa_session.query( model.User ) \
260                                    .filter( and_( model.User.table.c.email==email,
261                                                   model.User.table.c.deleted==False ) ) \
262                                    .first()
263            if not other:
264                mtype = "error"
265                msg = ( "User '%s' does not exist" % email )
266            elif other == trans.get_user():
267                mtype = "error"
268                msg = ( "You cannot share a visualization with yourself" )
269            elif trans.sa_session.query( model.VisualizationUserShareAssociation ) \
270                    .filter_by( user=other, visualization=visualization ).count() > 0:
271                mtype = "error"
272                msg = ( "Visualization already shared with '%s'" % email )
273            else:
274                share = model.VisualizationUserShareAssociation()
275                share.visualization = visualization
276                share.user = other
277                session = trans.sa_session
278                session.add( share )
279                self.create_item_slug( session, visualization )
280                session.flush()
281                trans.set_message( "Visualization '%s' shared with user '%s'" % ( visualization.title, other.email ) )
282                return trans.response.send_redirect( url_for( action='sharing', id=id ) )
283        return trans.fill_template( "/ind_share_base.mako",
284                                    message = msg,
285                                    messagetype = mtype,
286                                    item=visualization,
287                                    email=email,
288                                    use_panels=use_panels )
289        
290
291    @web.expose
292    def display_by_username_and_slug( self, trans, username, slug ):
293        """ Display visualization based on a username and slug. """
294
295        # Get visualization.
296        session = trans.sa_session
297        user = session.query( model.User ).filter_by( username=username ).first()
298        visualization = trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=slug, deleted=False ).first()
299        if visualization is None:
300            raise web.httpexceptions.HTTPNotFound()
301        
302        # Security check raises error if user cannot access visualization.
303        self.security_check( trans, visualization, False, True)
304        
305        # Get rating data.
306        user_item_rating = 0
307        if trans.get_user():
308            user_item_rating = self.get_user_item_rating( trans.sa_session, trans.get_user(), visualization )
309            if user_item_rating:
310                user_item_rating = user_item_rating.rating
311            else:
312                user_item_rating = 0
313        ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, visualization )
314        
315        # Display.
316        visualization_config = self.get_visualization_config( trans, visualization )
317        return trans.stream_template_mako( "visualization/display.mako", item = visualization, item_data = visualization_config, 
318                                            user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
319                                            content_only=True )
320        
321    @web.expose
322    @web.json
323    @web.require_login( "get item name and link" )
324    def get_name_and_link_async( self, trans, id=None ):
325        """ Returns visualization's name and link. """
326        visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
327
328        if self.create_item_slug( trans.sa_session, visualization ):
329            trans.sa_session.flush()
330        return_dict = { "name" : visualization.title, "link" : url_for( action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug ) }
331        return return_dict
332
333    @web.expose
334    def get_item_content_async( self, trans, id ):
335        """ Returns item content in HTML format. """
336        
337        # Get visualization, making sure it's accessible.
338        visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
339        if visualization is None:
340            raise web.httpexceptions.HTTPNotFound()
341        
342        # Return content.
343        visualization_config = self.get_visualization_config( trans, visualization )    
344        return trans.fill_template_mako( "visualization/item_content.mako", encoded_id=trans.security.encode_id(visualization.id), 
345                                            item=visualization, item_data=visualization_config, content_only=True )
346        
347    @web.expose
348    @web.require_login( "create visualizations" )
349    def create( self, trans, visualization_title="", visualization_slug="", visualization_annotation="", visualization_dbkey="" ):
350        """
351        Create a new visualization
352        """
353        user = trans.get_user()
354        visualization_title_err = visualization_slug_err = visualization_annotation_err = ""
355        if trans.request.method == "POST":
356            if not visualization_title:
357                visualization_title_err = "visualization name is required"
358            elif not visualization_slug:
359                visualization_slug_err = "visualization id is required"
360            elif not VALID_SLUG_RE.match( visualization_slug ):
361                visualization_slug_err = "visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
362            elif trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=visualization_slug, deleted=False ).first():
363                visualization_slug_err = "visualization id must be unique"
364            else:
365                # Create the new stored visualization
366                visualization = model.Visualization()
367                visualization.title = visualization_title
368                visualization.slug = visualization_slug
369                visualization.dbkey = visualization_dbkey
370                visualization.type = 'trackster' # HACK: set visualization type to trackster since it's the only viz
371                visualization_annotation = sanitize_html( visualization_annotation, 'utf-8', 'text/html' )
372                self.add_item_annotation( trans.sa_session, trans.get_user(), visualization, visualization_annotation )
373                visualization.user = user
374                
375                # And the first (empty) visualization revision
376                visualization_revision = model.VisualizationRevision()
377                visualization_revision.title = visualization_title
378                visualization_revision.config = {}
379                visualization_revision.dbkey = visualization_dbkey
380                visualization_revision.visualization = visualization
381                visualization.latest_revision = visualization_revision
382
383                # Persist
384                session = trans.sa_session
385                session.add(visualization)
386                session.add(visualization_revision)
387                session.flush()
388
389                return trans.response.send_redirect( web.url_for( action='list' ) )
390                                
391        return trans.show_form( 
392            web.FormBuilder( web.url_for(), "Create new visualization", submit_text="Submit" )
393                .add_text( "visualization_title", "Visualization title", value=visualization_title, error=visualization_title_err )
394                .add_text( "visualization_slug", "Visualization identifier", value=visualization_slug, error=visualization_slug_err,
395                           help="""A unique identifier that will be used for
396                                public links to this visualization. A default is generated
397                                from the visualization title, but can be edited. This field
398                                must contain only lowercase letters, numbers, and
399                                the '-' character.""" )
400                .add_select( "visualization_dbkey", "Visualization DbKey/Build", value=visualization_dbkey, options=self._get_dbkeys( trans ), error=None)
401                .add_text( "visualization_annotation", "Visualization annotation", value=visualization_annotation, error=visualization_annotation_err,
402                            help="A description of the visualization; annotation is shown alongside published visualizations."),
403                template="visualization/create.mako" )
404        
405    @web.expose
406    @web.require_login( "edit visualizations" )
407    def edit( self, trans, id, visualization_title="", visualization_slug="", visualization_annotation="" ):
408        """
409        Edit a visualization's attributes.
410        """
411        visualization = self.get_visualization( trans, id, check_ownership=True )
412        session = trans.sa_session
413        
414        visualization_title_err = visualization_slug_err = visualization_annotation_err = ""
415        if trans.request.method == "POST":
416            if not visualization_title:
417                visualization_title_err = "Visualization name is required"
418            elif not visualization_slug:
419                visualization_slug_err = "Visualization id is required"
420            elif not VALID_SLUG_RE.match( visualization_slug ):
421                visualization_slug_err = "Visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
422            elif visualization_slug != visualization.slug and trans.sa_session.query( model.Visualization ).filter_by( user=visualization.user, slug=visualization_slug, deleted=False ).first():
423                visualization_slug_err = "Visualization id must be unique"
424            else:
425                visualization.title = visualization_title
426                visualization.slug = visualization_slug
427                if visualization_annotation != "":
428                    visualization_annotation = sanitize_html( visualization_annotation, 'utf-8', 'text/html' )
429                    self.add_item_annotation( trans.sa_session, trans.get_user(), visualization, visualization_annotation )
430                session.flush()
431                # Redirect to visualization list.
432                return trans.response.send_redirect( web.url_for( action='list' ) )
433        else:
434            visualization_title = visualization.title
435            # Create slug if it's not already set.
436            if visualization.slug is None:
437                self.create_item_slug( trans.sa_session, visualization )
438            visualization_slug = visualization.slug
439            visualization_annotation = self.get_item_annotation_str( trans.sa_session, trans.user, visualization )
440            if not visualization_annotation:
441                visualization_annotation = ""
442        return trans.show_form( 
443            web.FormBuilder( web.url_for( id=id ), "Edit visualization attributes", submit_text="Submit" )
444                .add_text( "visualization_title", "Visualization title", value=visualization_title, error=visualization_title_err )
445                .add_text( "visualization_slug", "Visualization identifier", value=visualization_slug, error=visualization_slug_err,
446                           help="""A unique identifier that will be used for
447                                public links to this visualization. A default is generated
448                                from the visualization title, but can be edited. This field
449                                must contain only lowercase letters, numbers, and
450                                the '-' character.""" )
451                .add_text( "visualization_annotation", "Visualization annotation", value=visualization_annotation, error=visualization_annotation_err,
452                            help="A description of the visualization; annotation is shown alongside published visualizations."),
453            template="visualization/create.mako" )
454
455    def get_item( self, trans, id ):
456        return self.get_visualization( trans, id )
457