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

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

import galaxy-central

行番号 
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="owner", 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",
61        cols_to_filter=[ columns[0], columns[1], columns[2], columns[3] ],
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( BaseController, Sharable, UsesAnnotations, UsesVisualization, UsesItemRatings ):
72    _user_list_grid = VisualizationListGrid()
73    _published_list_grid = VisualizationAllPublishedGrid()
74   
75    @web.expose
76    def list_published( self, trans, *args, **kwargs ):
77        grid = self._published_list_grid( trans, **kwargs )
78        if 'async' in kwargs:
79            return grid
80        else:
81            # Render grid wrapped in panels
82            return trans.fill_template( "visualization/list_published.mako", grid=grid )
83
84    @web.expose
85    @web.require_login( "use Galaxy visualizations", use_panels=True )
86    def index( self, trans, *args, **kwargs ):
87        """ Lists user's saved visualizations. """
88        return self.list( trans, args, kwargs )
89   
90    @web.expose
91    @web.require_login()
92    def clone(self, trans, id, *args, **kwargs):
93        viz = self.get_visualization( trans, id, check_ownership=False )
94        user = trans.get_user()
95        if viz.user == user:
96            owner = True
97        else:
98            if trans.sa_session.query( model.VisualizationUserShareAssociation ) \
99                    .filter_by( user=user, visualization=viz ).count() == 0:
100                error( "Visualization is not owned by or shared with current user" )
101            owner = False
102        new_viz = model.Visualization()
103        new_viz.title = "Clone of '%s'" % viz.title
104        new_viz.dbkey = viz.dbkey
105        new_viz.type = viz.type
106        new_viz.latest_revision = viz.latest_revision
107        if not owner:
108            new_viz.title += " shared by '%s'" % viz.user.email
109        new_viz.user = user
110        # Persist
111        session = trans.sa_session
112        session.add( new_viz )
113        session.flush()
114        # Display the management page
115        trans.set_message( 'Clone created with name "%s"' % new_viz.title )
116        return self.list( trans )
117       
118    @web.expose
119    @web.require_login( "use Galaxy visualizations", use_panels=True )
120    def list( self, trans, *args, **kwargs ):
121        # Handle operation
122        if 'operation' in kwargs and 'id' in kwargs:
123            session = trans.sa_session
124            operation = kwargs['operation'].lower()
125            ids = util.listify( kwargs['id'] )
126            for id in ids:
127                item = session.query( model.Visualization ).get( trans.security.decode_id( id ) )
128                if operation == "delete":
129                    item.deleted = True
130                if operation == "share or publish":
131                    return self.sharing( trans, **kwargs )
132            session.flush()
133           
134        # Build list of visualizations shared with user.
135        shared_by_others = trans.sa_session \
136            .query( model.VisualizationUserShareAssociation ) \
137            .filter_by( user=trans.get_user() ) \
138            .join( model.Visualization.table ) \
139            .filter( model.Visualization.deleted == False ) \
140            .order_by( desc( model.Visualization.update_time ) ) \
141            .all()
142       
143        return trans.fill_template( "visualization/list.mako", grid=self._user_list_grid( trans, *args, **kwargs ), shared_by_others=shared_by_others )
144       
145    @web.expose
146    @web.require_login( "modify Galaxy visualizations" )
147    def set_slug_async( self, trans, id, new_slug ):
148        """ Set item slug asynchronously. """
149        visualization = self.get_visualization( trans, id )
150        if visualization:
151            visualization.slug = new_slug
152            trans.sa_session.flush()
153            return visualization.slug
154           
155    @web.expose
156    @web.require_login( "use Galaxy visualizations" )
157    def set_accessible_async( self, trans, id=None, accessible=False ):
158        """ Set visualization's importable attribute and slug. """
159        visualization = self.get_visualization( trans, id )
160
161        # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
162        importable = accessible in ['True', 'true', 't', 'T'];
163        if visualization and visualization.importable != importable:
164            if importable:
165                self._make_item_accessible( trans.sa_session, visualization )
166            else:
167                visualization.importable = importable
168            trans.sa_session.flush()
169
170        return
171       
172    @web.expose
173    @web.require_login( "rate items" )
174    @web.json
175    def rate_async( self, trans, id, rating ):
176        """ Rate a visualization asynchronously and return updated community data. """
177
178        visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
179        if not visualization:
180            return trans.show_error_message( "The specified visualization does not exist." )
181
182        # Rate visualization.
183        visualization_rating = self.rate_item( trans.sa_session, trans.get_user(), visualization, rating )
184
185        return self.get_ave_item_rating_data( trans.sa_session, visualization )
186       
187    @web.expose
188    @web.require_login( "share Galaxy visualizations" )
189    def imp( self, trans, id ):
190        """ Import a visualization into user's workspace. """
191        # Set referer message.
192        referer = trans.request.referer
193        if referer is not "":
194            referer_message = "<a href='%s'>return to the previous page</a>" % referer
195        else:
196            referer_message = "<a href='%s'>go to Galaxy's start page</a>" % url_for( '/' )
197                   
198        # Do import.
199        session = trans.sa_session
200        visualization = self.get_visualization( trans, id, check_ownership=False )
201        if visualization.importable == False:
202            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 )
203        elif visualization.user == trans.user:
204            return trans.show_error_message( "You can't import this visualization because you own it.<br>You can %s" % referer_message, use_panels=True )
205        elif visualization.deleted:
206            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 )
207        else:
208            # Create imported visualization via copy. TODO: Visualizations use datasets -- do we need to check to ensure that
209            # datasets can be imported/viewed and/or copy datasets to user?
210            imported_visualization = model.Visualization()
211            imported_visualization.title = "imported: " + visualization.title
212            imported_visualization.latest_revision = visualization.latest_revision
213            imported_visualization.user = trans.user
214            # Save new visualization.
215            session = trans.sa_session
216            session.add( imported_visualization )
217            session.flush()
218           
219            # Redirect to load galaxy frames.
220            return trans.show_ok_message(
221                message="""Visualization "%s" has been imported. <br>You can <a href="%s">start using this visualization</a> or %s."""
222                % ( visualization.title, web.url_for( controller='visualization' ), referer_message ), use_panels=True )
223       
224
225    @web.expose
226    @web.require_login( "share Galaxy visualizations" )
227    def sharing( self, trans, id, **kwargs ):
228        """ Handle visualization sharing. """
229
230        # Get session and visualization.
231        session = trans.sa_session
232        visualization = self.get_visualization( trans, id, check_ownership=True )
233
234        # Do operation on visualization.
235        if 'make_accessible_via_link' in kwargs:
236            self._make_item_accessible( trans.sa_session, visualization )
237        elif 'make_accessible_and_publish' in kwargs:
238            self._make_item_accessible( trans.sa_session, visualization )
239            visualization.published = True
240        elif 'publish' in kwargs:
241            visualization.published = True
242        elif 'disable_link_access' in kwargs:
243            visualization.importable = False
244        elif 'unpublish' in kwargs:
245            visualization.published = False
246        elif 'disable_link_access_and_unpublish' in kwargs:
247            visualization.importable = visualization.published = False
248        elif 'unshare_user' in kwargs:
249            user = session.query( model.User ).get( trans.security.decode_id( kwargs['unshare_user' ] ) )
250            if not user:
251                error( "User not found for provided id" )
252            association = session.query( model.VisualizationUserShareAssociation ) \
253                                 .filter_by( user=user, visualization=visualization ).one()
254            session.delete( association )
255
256        session.flush()
257
258        return trans.fill_template( "/sharing_base.mako", item=visualization, use_panels=True )
259
260    @web.expose
261    @web.require_login( "share Galaxy visualizations" )
262    def share( self, trans, id=None, email="", use_panels=False ):
263        """ Handle sharing a visualization with a particular user. """
264        msg = mtype = None
265        visualization = self.get_visualization( trans, id, check_ownership=True )
266        if email:
267            other = trans.sa_session.query( model.User ) \
268                                    .filter( and_( model.User.table.c.email==email,
269                                                   model.User.table.c.deleted==False ) ) \
270                                    .first()
271            if not other:
272                mtype = "error"
273                msg = ( "User '%s' does not exist" % email )
274            elif other == trans.get_user():
275                mtype = "error"
276                msg = ( "You cannot share a visualization with yourself" )
277            elif trans.sa_session.query( model.VisualizationUserShareAssociation ) \
278                    .filter_by( user=other, visualization=visualization ).count() > 0:
279                mtype = "error"
280                msg = ( "Visualization already shared with '%s'" % email )
281            else:
282                share = model.VisualizationUserShareAssociation()
283                share.visualization = visualization
284                share.user = other
285                session = trans.sa_session
286                session.add( share )
287                self.create_item_slug( session, visualization )
288                session.flush()
289                trans.set_message( "Visualization '%s' shared with user '%s'" % ( visualization.title, other.email ) )
290                return trans.response.send_redirect( url_for( action='sharing', id=id ) )
291        return trans.fill_template( "/ind_share_base.mako",
292                                    message = msg,
293                                    messagetype = mtype,
294                                    item=visualization,
295                                    email=email,
296                                    use_panels=use_panels )
297       
298
299    @web.expose
300    def display_by_username_and_slug( self, trans, username, slug ):
301        """ Display visualization based on a username and slug. """
302
303        # Get visualization.
304        session = trans.sa_session
305        user = session.query( model.User ).filter_by( username=username ).first()
306        visualization = trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=slug, deleted=False ).first()
307        if visualization is None:
308            raise web.httpexceptions.HTTPNotFound()
309       
310        # Security check raises error if user cannot access visualization.
311        self.security_check( trans.get_user(), visualization, False, True)
312       
313        # Get rating data.
314        user_item_rating = 0
315        if trans.get_user():
316            user_item_rating = self.get_user_item_rating( trans.sa_session, trans.get_user(), visualization )
317            if user_item_rating:
318                user_item_rating = user_item_rating.rating
319            else:
320                user_item_rating = 0
321        ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, visualization )
322       
323        # Display.
324        visualization_config = self.get_visualization_config( trans, visualization )
325        return trans.stream_template_mako( "visualization/display.mako", item = visualization, item_data = visualization_config,
326                                            user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
327                                            content_only=True )
328       
329    @web.expose
330    @web.json
331    @web.require_login( "get item name and link" )
332    def get_name_and_link_async( self, trans, id=None ):
333        """ Returns visualization's name and link. """
334        visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
335
336        if self.create_item_slug( trans.sa_session, visualization ):
337            trans.sa_session.flush()
338        return_dict = { "name" : visualization.title, "link" : url_for( action="display_by_username_and_slug", username=visualization.user.username, slug=visualization.slug ) }
339        return return_dict
340
341    @web.expose
342    def get_item_content_async( self, trans, id ):
343        """ Returns item content in HTML format. """
344       
345        # Get visualization, making sure it's accessible.
346        visualization = self.get_visualization( trans, id, check_ownership=False, check_accessible=True )
347        if visualization is None:
348            raise web.httpexceptions.HTTPNotFound()
349       
350        # Return content.
351        visualization_config = self.get_visualization_config( trans, visualization )   
352        return trans.fill_template_mako( "visualization/item_content.mako", encoded_id=trans.security.encode_id(visualization.id),
353                                            item=visualization, item_data=visualization_config, content_only=True )
354       
355    @web.expose
356    @web.require_login( "create visualizations" )
357    def create( self, trans, visualization_title="", visualization_slug="", visualization_annotation="", visualization_dbkey="" ):
358        """
359        Create a new visualization
360        """
361        user = trans.get_user()
362        visualization_title_err = visualization_slug_err = visualization_annotation_err = ""
363        if trans.request.method == "POST":
364            if not visualization_title:
365                visualization_title_err = "visualization name is required"
366            elif not visualization_slug:
367                visualization_slug_err = "visualization id is required"
368            elif not VALID_SLUG_RE.match( visualization_slug ):
369                visualization_slug_err = "visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
370            elif trans.sa_session.query( model.Visualization ).filter_by( user=user, slug=visualization_slug, deleted=False ).first():
371                visualization_slug_err = "visualization id must be unique"
372            else:
373                # Create the new stored visualization
374                visualization = model.Visualization()
375                visualization.title = visualization_title
376                visualization.slug = visualization_slug
377                visualization.dbkey = visualization_dbkey
378                visualization.type = 'trackster' # HACK: set visualization type to trackster since it's the only viz
379                visualization_annotation = sanitize_html( visualization_annotation, 'utf-8', 'text/html' )
380                self.add_item_annotation( trans.sa_session, trans.get_user(), visualization, visualization_annotation )
381                visualization.user = user
382               
383                # And the first (empty) visualization revision
384                visualization_revision = model.VisualizationRevision()
385                visualization_revision.title = visualization_title
386                visualization_revision.config = {}
387                visualization_revision.dbkey = visualization_dbkey
388                visualization_revision.visualization = visualization
389                visualization.latest_revision = visualization_revision
390
391                # Persist
392                session = trans.sa_session
393                session.add(visualization)
394                session.add(visualization_revision)
395                session.flush()
396
397                return trans.response.send_redirect( web.url_for( action='list' ) )
398                               
399        return trans.show_form(
400            web.FormBuilder( web.url_for(), "Create new visualization", submit_text="Submit" )
401                .add_text( "visualization_title", "Visualization title", value=visualization_title, error=visualization_title_err )
402                .add_text( "visualization_slug", "Visualization identifier", value=visualization_slug, error=visualization_slug_err,
403                           help="""A unique identifier that will be used for
404                                public links to this visualization. A default is generated
405                                from the visualization title, but can be edited. This field
406                                must contain only lowercase letters, numbers, and
407                                the '-' character.""" )
408                .add_select( "visualization_dbkey", "Visualization DbKey/Build", value=visualization_dbkey, options=self._get_dbkeys( trans ), error=None)
409                .add_text( "visualization_annotation", "Visualization annotation", value=visualization_annotation, error=visualization_annotation_err,
410                            help="A description of the visualization; annotation is shown alongside published visualizations."),
411                template="visualization/create.mako" )
412       
413    @web.expose
414    @web.require_login( "edit visualizations" )
415    def edit( self, trans, id, visualization_title="", visualization_slug="", visualization_annotation="" ):
416        """
417        Edit a visualization's attributes.
418        """
419        visualization = self.get_visualization( trans, id, check_ownership=True )
420        session = trans.sa_session
421       
422        visualization_title_err = visualization_slug_err = visualization_annotation_err = ""
423        if trans.request.method == "POST":
424            if not visualization_title:
425                visualization_title_err = "Visualization name is required"
426            elif not visualization_slug:
427                visualization_slug_err = "Visualization id is required"
428            elif not VALID_SLUG_RE.match( visualization_slug ):
429                visualization_slug_err = "Visualization identifier must consist of only lowercase letters, numbers, and the '-' character"
430            elif visualization_slug != visualization.slug and trans.sa_session.query( model.Visualization ).filter_by( user=visualization.user, slug=visualization_slug, deleted=False ).first():
431                visualization_slug_err = "Visualization id must be unique"
432            else:
433                visualization.title = visualization_title
434                visualization.slug = visualization_slug
435                if visualization_annotation != "":
436                    visualization_annotation = sanitize_html( visualization_annotation, 'utf-8', 'text/html' )
437                    self.add_item_annotation( trans.sa_session, trans.get_user(), visualization, visualization_annotation )
438                session.flush()
439                # Redirect to visualization list.
440                return trans.response.send_redirect( web.url_for( action='list' ) )
441        else:
442            visualization_title = visualization.title
443            # Create slug if it's not already set.
444            if visualization.slug is None:
445                self.create_item_slug( trans.sa_session, visualization )
446            visualization_slug = visualization.slug
447            visualization_annotation = self.get_item_annotation_str( trans.sa_session, trans.user, visualization )
448            if not visualization_annotation:
449                visualization_annotation = ""
450        return trans.show_form(
451            web.FormBuilder( web.url_for( id=id ), "Edit visualization attributes", submit_text="Submit" )
452                .add_text( "visualization_title", "Visualization title", value=visualization_title, error=visualization_title_err )
453                .add_text( "visualization_slug", "Visualization identifier", value=visualization_slug, error=visualization_slug_err,
454                           help="""A unique identifier that will be used for
455                                public links to this visualization. A default is generated
456                                from the visualization title, but can be edited. This field
457                                must contain only lowercase letters, numbers, and
458                                the '-' character.""" )
459                .add_text( "visualization_annotation", "Visualization annotation", value=visualization_annotation, error=visualization_annotation_err,
460                            help="A description of the visualization; annotation is shown alongside published visualizations."),
461            template="visualization/create.mako" )
462
463   
Note: リポジトリブラウザについてのヘルプは TracBrowser を参照してください。