PageRenderTime 89ms CodeModel.GetById 21ms app.highlight 60ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/galaxy/web/controllers/page.py

https://bitbucket.org/cistrome/cistrome-harvard/
Python | 762 lines | 761 code | 1 blank | 0 comment | 12 complexity | e356920c48742dc2e12b84a0e86ade1a 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
  5from galaxy.util.sanitize_html import sanitize_html, _BaseHTMLProcessor
  6from galaxy.util.odict import odict
  7from galaxy.util.json import from_json_string
  8
  9def format_bool( b ):
 10    if b:
 11        return "yes"
 12    else:
 13        return ""
 14
 15class PageListGrid( grids.Grid ):
 16    # Custom column.
 17    class URLColumn( grids.PublicURLColumn ):
 18        def get_value( self, trans, grid, item ):
 19            return url_for( action='display_by_username_and_slug', username=item.user.username, slug=item.slug )
 20    
 21    # Grid definition
 22    use_panels = True
 23    title = "Pages"
 24    model_class = model.Page
 25    default_filter = { "published" : "All", "tags" : "All", "title" : "All", "sharing" : "All" }
 26    default_sort_key = "-create_time"
 27    columns = [
 28        grids.TextColumn( "Title", key="title", attach_popup=True, filterable="advanced" ),
 29        URLColumn( "Public URL" ),
 30        grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_annotation_association_class=model.PageAnnotationAssociation, filterable="advanced" ),
 31        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced", grid_name="PageListGrid" ),
 32        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ),
 33        grids.GridColumn( "Created", key="create_time", format=time_ago ),
 34        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
 35    ]
 36    columns.append( 
 37        grids.MulticolFilterColumn(  
 38        "Search", 
 39        cols_to_filter=[ columns[0], columns[2] ], 
 40        key="free-text-search", visible=False, filterable="standard" )
 41                )
 42    global_actions = [
 43        grids.GridAction( "Add new page", dict( action='create' ) )
 44    ]
 45    operations = [
 46        grids.DisplayByUsernameAndSlugGridOperation( "View", allow_multiple=False ),
 47        grids.GridOperation( "Edit content", allow_multiple=False, url_args=dict( action='edit_content') ),
 48        grids.GridOperation( "Edit attributes", allow_multiple=False, url_args=dict( action='edit') ),
 49        grids.GridOperation( "Share or Publish", allow_multiple=False, condition=( lambda item: not item.deleted ), async_compatible=False ),
 50        grids.GridOperation( "Delete", confirm="Are you sure you want to delete this page?" ),
 51    ]
 52    def apply_query_filter( self, trans, query, **kwargs ):
 53        return query.filter_by( user=trans.user, deleted=False )
 54        
 55class PageAllPublishedGrid( grids.Grid ):
 56    # Grid definition
 57    use_panels = True
 58    use_async = True
 59    title = "Published Pages"
 60    model_class = model.Page
 61    default_sort_key = "update_time"
 62    default_filter = dict( title="All", username="All" )
 63    columns = [
 64        grids.PublicURLColumn( "Title", key="title", filterable="advanced" ),
 65        grids.OwnerAnnotationColumn( "Annotation", key="annotation", model_annotation_association_class=model.PageAnnotationAssociation, filterable="advanced" ),
 66        grids.OwnerColumn( "Owner", key="username", model_class=model.User, filterable="advanced" ),
 67        grids.CommunityRatingColumn( "Community Rating", key="rating" ), 
 68        grids.CommunityTagsColumn( "Community Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced", grid_name="PageAllPublishedGrid" ),
 69        grids.ReverseSortColumn( "Last Updated", key="update_time", format=time_ago )
 70    ]
 71    columns.append( 
 72        grids.MulticolFilterColumn(  
 73        "Search title, annotation, owner, and tags", 
 74        cols_to_filter=[ columns[0], columns[1], columns[2], columns[4] ], 
 75        key="free-text-search", visible=False, filterable="standard" )
 76                )
 77    def build_initial_query( self, trans, **kwargs ):
 78        # Join so that searching history.user makes sense.
 79        return trans.sa_session.query( self.model_class ).join( model.User.table )
 80    def apply_query_filter( self, trans, query, **kwargs ):
 81        return query.filter( self.model_class.deleted==False ).filter( self.model_class.published==True )
 82                
 83class ItemSelectionGrid( grids.Grid ):
 84    """ Base class for pages' item selection grids. """
 85    # Custom columns.
 86    class NameColumn( grids.TextColumn ):
 87        def get_value(self, trans, grid, item):
 88            if hasattr( item, "get_display_name" ):
 89                return item.get_display_name()
 90            else:
 91                return item.name
 92
 93    # Grid definition.
 94    template = "/page/select_items_grid.mako"
 95    async_template = "/page/select_items_grid_async.mako" 
 96    default_filter = { "deleted" : "False" , "sharing" : "All" }
 97    default_sort_key = "-update_time"
 98    use_async = True
 99    use_paging = True
100    num_rows_per_page = 10
101    
102    def apply_query_filter( self, trans, query, **kwargs ):
103        return query.filter_by( user=trans.user )
104                
105class HistorySelectionGrid( ItemSelectionGrid ):
106    """ Grid for selecting histories. """
107    # Grid definition.
108    title = "Saved Histories"
109    model_class = model.History
110    columns = [
111        ItemSelectionGrid.NameColumn( "Name", key="name", filterable="advanced" ),
112        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryTagAssociation, filterable="advanced"),
113        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
114        # Columns that are valid for filtering but are not visible.
115        grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
116        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
117    ]
118    columns.append(     
119        grids.MulticolFilterColumn(  
120        "Search", 
121        cols_to_filter=[ columns[0], columns[1] ], 
122        key="free-text-search", visible=False, filterable="standard" )
123                )
124                
125    def apply_query_filter( self, trans, query, **kwargs ):
126        return query.filter_by( user=trans.user, purged=False )
127        
128class HistoryDatasetAssociationSelectionGrid( ItemSelectionGrid ):
129    """ Grid for selecting HDAs. """
130    # Grid definition.
131    title = "Saved Datasets"
132    model_class = model.HistoryDatasetAssociation
133    columns = [
134        ItemSelectionGrid.NameColumn( "Name", key="name", filterable="advanced" ),
135        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.HistoryDatasetAssociationTagAssociation, filterable="advanced"),
136        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
137        # Columns that are valid for filtering but are not visible.
138        grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
139        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
140    ]
141    columns.append(     
142        grids.MulticolFilterColumn(  
143        "Search", 
144        cols_to_filter=[ columns[0], columns[1] ], 
145        key="free-text-search", visible=False, filterable="standard" )
146                )
147    def apply_query_filter( self, trans, query, **kwargs ):
148        # To filter HDAs by user, need to join HDA and History table and then filter histories by user. This is necessary because HDAs do not have
149        # a user relation.
150        return query.select_from( model.HistoryDatasetAssociation.table.join( model.History.table ) ).filter( model.History.user == trans.user )
151    
152                
153class WorkflowSelectionGrid( ItemSelectionGrid ):
154    """ Grid for selecting workflows. """
155    # Grid definition.
156    title = "Saved Workflows"
157    model_class = model.StoredWorkflow
158    columns = [
159        ItemSelectionGrid.NameColumn( "Name", key="name", filterable="advanced" ),
160        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.StoredWorkflowTagAssociation, filterable="advanced"),
161        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
162        # Columns that are valid for filtering but are not visible.
163        grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
164        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
165    ]
166    columns.append(     
167        grids.MulticolFilterColumn(  
168        "Search", 
169        cols_to_filter=[ columns[0], columns[1] ], 
170        key="free-text-search", visible=False, filterable="standard" )
171                )
172
173class PageSelectionGrid( ItemSelectionGrid ):
174    """ Grid for selecting pages. """
175    # Grid definition.
176    title = "Saved Pages"
177    model_class = model.Page
178    columns = [
179        grids.TextColumn( "Title", key="title", filterable="advanced" ),
180        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.PageTagAssociation, filterable="advanced"),
181        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
182        # Columns that are valid for filtering but are not visible.
183        grids.DeletedColumn( "Deleted", key="deleted", visible=False, filterable="advanced" ),
184        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False, visible=False ),
185    ]
186    columns.append(     
187        grids.MulticolFilterColumn(  
188        "Search",
189        cols_to_filter=[ columns[0], columns[1] ], 
190        key="free-text-search", visible=False, filterable="standard" )
191                )
192                
193class VisualizationSelectionGrid( ItemSelectionGrid ):
194    """ Grid for selecting visualizations. """
195    # Grid definition.
196    title = "Saved Visualizations"
197    model_class = model.Visualization
198    columns = [
199        grids.TextColumn( "Title", key="title", filterable="advanced" ),
200        grids.TextColumn( "Type", key="type" ),
201        grids.IndividualTagsColumn( "Tags", key="tags", model_tag_association_class=model.VisualizationTagAssociation, filterable="advanced", grid_name="VisualizationListGrid" ),
202        grids.SharingStatusColumn( "Sharing", key="sharing", filterable="advanced", sortable=False ),
203        grids.GridColumn( "Last Updated", key="update_time", format=time_ago ),
204    ]    
205    columns.append( 
206        grids.MulticolFilterColumn(  
207        "Search", 
208        cols_to_filter=[ columns[0], columns[2] ], 
209        key="free-text-search", visible=False, filterable="standard" )
210                )    
211                
212class _PageContentProcessor( _BaseHTMLProcessor ):
213    """ Processes page content to produce HTML that is suitable for display. For now, processor renders embedded objects. """
214    
215    def __init__( self, trans, encoding, type, render_embed_html_fn ):
216        _BaseHTMLProcessor.__init__( self, encoding, type)
217        self.trans = trans
218        self.ignore_content = False
219        self.num_open_tags_for_ignore = 0
220        self.render_embed_html_fn = render_embed_html_fn
221        
222    def unknown_starttag( self, tag, attrs ):
223        """ Called for each start tag; attrs is a list of (attr, value) tuples. """
224    
225        # If ignoring content, just increment tag count and ignore.
226        if self.ignore_content:
227            self.num_open_tags_for_ignore += 1
228            return
229        
230        # Not ignoring tag; look for embedded content.
231        embedded_item = False
232        for attribute in attrs:
233            if ( attribute[0] == "class" ) and ( "embedded-item" in attribute[1].split(" ") ): 
234                embedded_item = True
235                break
236        # For embedded content, set ignore flag to ignore current content and add new content for embedded item.
237        if embedded_item:
238            # Set processing attributes to ignore content.
239            self.ignore_content = True
240            self.num_open_tags_for_ignore = 1
241            
242            # Insert content for embedded element.
243            for attribute in attrs:
244                name = attribute[0]
245                if name == "id":
246                    # ID has form '<class_name>-<encoded_item_id>'
247                    item_class, item_id = attribute[1].split("-")
248                    embed_html = self.render_embed_html_fn( self.trans, item_class, item_id )
249                    self.pieces.append( embed_html )
250            return
251        
252        # Default behavior: not ignoring and no embedded content.
253        _BaseHTMLProcessor.unknown_starttag( self, tag, attrs )
254        
255    def handle_data( self, text ):
256        """ Called for each block of plain text. """
257        if self.ignore_content:
258            return
259        _BaseHTMLProcessor.handle_data( self, text )
260        
261    def unknown_endtag( self, tag ):
262        """ Called for each end tag. """
263        
264        # If ignoring content, see if current tag is the end of content to ignore.
265        if self.ignore_content:
266            self.num_open_tags_for_ignore -= 1        
267            if self.num_open_tags_for_ignore == 0:
268                # Done ignoring content.
269                self.ignore_content = False
270            return
271        
272        # Default behavior: 
273        _BaseHTMLProcessor.unknown_endtag( self, tag )
274                
275class PageController( BaseUIController, Sharable, UsesAnnotations, UsesHistory, 
276                      UsesStoredWorkflow, UsesHistoryDatasetAssociation, UsesVisualization, UsesItemRatings ):
277    
278    _page_list = PageListGrid()
279    _all_published_list = PageAllPublishedGrid()
280    _history_selection_grid = HistorySelectionGrid()
281    _workflow_selection_grid = WorkflowSelectionGrid()
282    _datasets_selection_grid = HistoryDatasetAssociationSelectionGrid()
283    _page_selection_grid = PageSelectionGrid()
284    _visualization_selection_grid = VisualizationSelectionGrid()
285    
286    @web.expose
287    @web.require_login()  
288    def list( self, trans, *args, **kwargs ):
289        """ List user's pages. """
290        # Handle operation
291        if 'operation' in kwargs and 'id' in kwargs:
292            session = trans.sa_session
293            operation = kwargs['operation'].lower()
294            ids = util.listify( kwargs['id'] )
295            for id in ids:
296                item = session.query( model.Page ).get( trans.security.decode_id( id ) )
297                if operation == "delete":
298                    item.deleted = True
299                if operation == "share or publish":
300                    return self.sharing( trans, **kwargs )
301            session.flush()
302            
303        # Build grid HTML.
304        grid = self._page_list( trans, *args, **kwargs )
305        
306        # Build list of pages shared with user.
307        shared_by_others = trans.sa_session \
308            .query( model.PageUserShareAssociation ) \
309            .filter_by( user=trans.get_user() ) \
310            .join( model.Page.table ) \
311            .filter( model.Page.deleted == False ) \
312            .order_by( desc( model.Page.update_time ) ) \
313            .all()
314        
315        # Render grid wrapped in panels
316        return trans.fill_template( "page/index.mako", grid=grid, shared_by_others=shared_by_others )
317             
318    @web.expose
319    def list_published( self, trans, *args, **kwargs ):
320        grid = self._all_published_list( trans, *args, **kwargs )
321        if 'async' in kwargs:
322            return grid
323        else:
324            # Render grid wrapped in panels
325            return trans.fill_template( "page/list_published.mako", grid=grid )
326
327             
328    @web.expose
329    @web.require_login( "create pages" )
330    def create( self, trans, page_title="", page_slug="", page_annotation="" ):
331        """
332        Create a new page
333        """
334        user = trans.get_user()
335        page_title_err = page_slug_err = page_annotation_err = ""
336        if trans.request.method == "POST":
337            if not page_title:
338                page_title_err = "Page name is required"
339            elif not page_slug:
340                page_slug_err = "Page id is required"
341            elif not VALID_SLUG_RE.match( page_slug ):
342                page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character"
343            elif trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first():
344                page_slug_err = "Page id must be unique"
345            else:
346                # Create the new stored page
347                page = model.Page()
348                page.title = page_title
349                page.slug = page_slug
350                page_annotation = sanitize_html( page_annotation, 'utf-8', 'text/html' )
351                self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
352                page.user = user
353                # And the first (empty) page revision
354                page_revision = model.PageRevision()
355                page_revision.title = page_title
356                page_revision.page = page
357                page.latest_revision = page_revision
358                page_revision.content = ""
359                # Persist
360                session = trans.sa_session
361                session.add( page )
362                session.flush()
363                # Display the management page
364                ## trans.set_message( "Page '%s' created" % page.title )
365                return trans.response.send_redirect( web.url_for( action='list' ) )
366        return trans.show_form( 
367            web.FormBuilder( web.url_for(), "Create new page", submit_text="Submit" )
368                .add_text( "page_title", "Page title", value=page_title, error=page_title_err )
369                .add_text( "page_slug", "Page identifier", value=page_slug, error=page_slug_err,
370                           help="""A unique identifier that will be used for
371                                public links to this page. A default is generated
372                                from the page title, but can be edited. This field
373                                must contain only lowercase letters, numbers, and
374                                the '-' character.""" )
375                .add_text( "page_annotation", "Page annotation", value=page_annotation, error=page_annotation_err,
376                            help="A description of the page; annotation is shown alongside published pages."),
377                template="page/create.mako" )
378        
379    @web.expose
380    @web.require_login( "edit pages" )
381    def edit( self, trans, id, page_title="", page_slug="", page_annotation="" ):
382        """
383        Edit a page's attributes.
384        """
385        encoded_id = id
386        id = trans.security.decode_id( id )
387        session = trans.sa_session
388        page = session.query( model.Page ).get( id )
389        user = trans.user
390        assert page.user == user
391        page_title_err = page_slug_err = page_annotation_err = ""
392        if trans.request.method == "POST":
393            if not page_title:
394                page_title_err = "Page name is required"
395            elif not page_slug:
396                page_slug_err = "Page id is required"
397            elif not VALID_SLUG_RE.match( page_slug ):
398                page_slug_err = "Page identifier must consist of only lowercase letters, numbers, and the '-' character"
399            elif page_slug != page.slug and trans.sa_session.query( model.Page ).filter_by( user=user, slug=page_slug, deleted=False ).first():
400                page_slug_err = "Page id must be unique"
401            elif not page_annotation:
402                page_annotation_err = "Page annotation is required"
403            else:
404                page.title = page_title
405                page.slug = page_slug
406                page_annotation = sanitize_html( page_annotation, 'utf-8', 'text/html' )
407                self.add_item_annotation( trans.sa_session, trans.get_user(), page, page_annotation )
408                session.flush()
409                # Redirect to page list.
410                return trans.response.send_redirect( web.url_for( action='list' ) )
411        else:
412            page_title = page.title
413            page_slug = page.slug
414            page_annotation = self.get_item_annotation_str( trans.sa_session, trans.user, page )
415            if not page_annotation:
416                page_annotation = ""
417        return trans.show_form( 
418            web.FormBuilder( web.url_for( id=encoded_id ), "Edit page attributes", submit_text="Submit" )
419                .add_text( "page_title", "Page title", value=page_title, error=page_title_err )
420                .add_text( "page_slug", "Page identifier", value=page_slug, error=page_slug_err,
421                           help="""A unique identifier that will be used for
422                                public links to this page. A default is generated
423                                from the page title, but can be edited. This field
424                                must contain only lowercase letters, numbers, and
425                                the '-' character.""" )
426                .add_text( "page_annotation", "Page annotation", value=page_annotation, error=page_annotation_err,
427                            help="A description of the page; annotation is shown alongside published pages."),
428            template="page/create.mako" )
429        
430    @web.expose
431    @web.require_login( "edit pages" )
432    def edit_content( self, trans, id ):
433        """
434        Render the main page editor interface. 
435        """
436        id = trans.security.decode_id( id )
437        page = trans.sa_session.query( model.Page ).get( id )
438        assert page.user == trans.user
439        return trans.fill_template( "page/editor.mako", page=page )
440        
441    @web.expose
442    @web.require_login( "use Galaxy pages" )
443    def sharing( self, trans, id, **kwargs ):
444        """ Handle page sharing. """
445
446        # Get session and page.
447        session = trans.sa_session
448        page = trans.sa_session.query( model.Page ).get( trans.security.decode_id( id ) )
449
450        # Do operation on page.
451        if 'make_accessible_via_link' in kwargs:
452            self._make_item_accessible( trans.sa_session, page )
453        elif 'make_accessible_and_publish' in kwargs:
454            self._make_item_accessible( trans.sa_session, page )
455            page.published = True
456        elif 'publish' in kwargs:
457            page.published = True
458        elif 'disable_link_access' in kwargs:
459            page.importable = False
460        elif 'unpublish' in kwargs:
461            page.published = False
462        elif 'disable_link_access_and_unpublish' in kwargs:
463            page.importable = page.published = False
464        elif 'unshare_user' in kwargs:
465            user = session.query( model.User ).get( trans.security.decode_id( kwargs['unshare_user' ] ) )
466            if not user:
467                error( "User not found for provided id" )
468            association = session.query( model.PageUserShareAssociation ) \
469                                 .filter_by( user=user, page=page ).one()
470            session.delete( association )
471
472        session.flush()
473
474        return trans.fill_template( "/sharing_base.mako",
475                                    item=page, use_panels=True )
476                                    
477    @web.expose
478    @web.require_login( "use Galaxy pages" )
479    def share( self, trans, id, email="", use_panels=False ):
480        """ Handle sharing with an individual user. """
481        msg = mtype = None
482        page = trans.sa_session.query( model.Page ).get( trans.security.decode_id( id ) )
483        if email:
484            other = trans.sa_session.query( model.User ) \
485                                    .filter( and_( model.User.table.c.email==email,
486                                                   model.User.table.c.deleted==False ) ) \
487                                    .first()
488            if not other:
489                mtype = "error"
490                msg = ( "User '%s' does not exist" % email )
491            elif other == trans.get_user():
492                mtype = "error"
493                msg = ( "You cannot share a page with yourself" )
494            elif trans.sa_session.query( model.PageUserShareAssociation ) \
495                    .filter_by( user=other, page=page ).count() > 0:
496                mtype = "error"
497                msg = ( "Page already shared with '%s'" % email )
498            else:
499                share = model.PageUserShareAssociation()
500                share.page = page
501                share.user = other
502                session = trans.sa_session
503                session.add( share )
504                self.create_item_slug( session, page )
505                session.flush()
506                trans.set_message( "Page '%s' shared with user '%s'" % ( page.title, other.email ) )
507                return trans.response.send_redirect( url_for( controller='page', action='sharing', id=id ) )
508        return trans.fill_template( "/ind_share_base.mako",
509                                    message = msg,
510                                    messagetype = mtype,
511                                    item=page,
512                                    email=email,
513                                    use_panels=use_panels )
514        
515    @web.expose
516    @web.require_login() 
517    def save( self, trans, id, content, annotations ):
518        id = trans.security.decode_id( id )
519        page = trans.sa_session.query( model.Page ).get( id )
520        assert page.user == trans.user
521        
522        # Sanitize content
523        content = sanitize_html( content, 'utf-8', 'text/html' )
524        
525        # Add a new revision to the page with the provided content.
526        page_revision = model.PageRevision()
527        page_revision.title = page.title
528        page_revision.page = page
529        page.latest_revision = page_revision
530        page_revision.content = content
531        
532        # Save annotations.
533        annotations = from_json_string( annotations )
534        for annotation_dict in annotations:
535            item_id = trans.security.decode_id( annotation_dict[ 'item_id' ] )
536            item_class = self.get_class( annotation_dict[ 'item_class' ] )
537            item = trans.sa_session.query( item_class ).filter_by( id=item_id ).first()
538            if not item:
539                raise RuntimeError( "cannot find annotated item" )
540            text = sanitize_html( annotation_dict[ 'text' ], 'utf-8', 'text/html' )
541            
542            # Add/update annotation.
543            if item_id and item_class and text:
544                # Get annotation association.
545                annotation_assoc_class = eval( "model.%sAnnotationAssociation" % item_class.__name__ )
546                annotation_assoc = trans.sa_session.query( annotation_assoc_class ).filter_by( user=trans.get_user() )
547                if item_class == model.History.__class__:
548                    annotation_assoc = annotation_assoc.filter_by( history=item )
549                elif item_class == model.HistoryDatasetAssociation.__class__:
550                    annotation_assoc = annotation_assoc.filter_by( hda=item )
551                elif item_class == model.StoredWorkflow.__class__:
552                    annotation_assoc = annotation_assoc.filter_by( stored_workflow=item )
553                elif item_class == model.WorkflowStep.__class__:
554                    annotation_assoc = annotation_assoc.filter_by( workflow_step=item )
555                annotation_assoc = annotation_assoc.first()
556                if not annotation_assoc:
557                    # Create association.
558                    annotation_assoc = annotation_assoc_class()
559                    item.annotations.append( annotation_assoc )
560                    annotation_assoc.user = trans.get_user()
561                # Set annotation user text.
562                annotation_assoc.annotation = text
563        trans.sa_session.flush()
564        
565    @web.expose
566    @web.require_login()  
567    def display( self, trans, id ):
568        id = trans.security.decode_id( id )
569        page = trans.sa_session.query( model.Page ).get( id )
570        if not page:
571            raise web.httpexceptions.HTTPNotFound()
572        return self.display_by_username_and_slug( trans, page.user.username, page.slug )
573
574    @web.expose
575    def display_by_username_and_slug( self, trans, username, slug ):
576        """ Display page based on a username and slug. """ 
577
578        # Get page.
579        session = trans.sa_session
580        user = session.query( model.User ).filter_by( username=username ).first()
581        page = trans.sa_session.query( model.Page ).filter_by( user=user, slug=slug, deleted=False ).first()
582        if page is None:
583            raise web.httpexceptions.HTTPNotFound()
584        # Security check raises error if user cannot access page.
585        self.security_check( trans, page, False, True)
586            
587        # Process page content.
588        processor = _PageContentProcessor( trans, 'utf-8', 'text/html', self._get_embed_html )
589        processor.feed( page.latest_revision.content )
590        
591        # Get rating data.
592        user_item_rating = 0
593        if trans.get_user():
594            user_item_rating = self.get_user_item_rating( trans.sa_session, trans.get_user(), page )
595            if user_item_rating:
596                user_item_rating = user_item_rating.rating
597            else:
598                user_item_rating = 0
599        ave_item_rating, num_ratings = self.get_ave_item_rating_data( trans.sa_session, page )
600        
601        # Output is string, so convert to unicode for display.
602        page_content = unicode( processor.output(), 'utf-8' )
603        return trans.fill_template_mako( "page/display.mako", item=page, item_data=page_content, 
604                                         user_item_rating = user_item_rating, ave_item_rating=ave_item_rating, num_ratings=num_ratings,
605                                         content_only=True )
606        
607    @web.expose
608    @web.require_login( "use Galaxy pages" )
609    def set_accessible_async( self, trans, id=None, accessible=False ):
610        """ Set page's importable attribute and slug. """
611        page = self.get_page( trans, id )
612
613        # Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
614        importable = accessible in ['True', 'true', 't', 'T'];
615        if page.importable != importable:
616            if importable:
617                self._make_item_accessible( trans.sa_session, page )
618            else:
619                page.importable = importable
620            trans.sa_session.flush()
621        return
622
623    @web.expose
624    @web.require_login( "modify Galaxy items" )
625    def set_slug_async( self, trans, id, new_slug ):
626        page = self.get_page( trans, id )
627        if page:
628            page.slug = new_slug
629            trans.sa_session.flush()
630            return page.slug
631            
632    @web.expose
633    @web.require_login( "rate items" )
634    @web.json
635    def rate_async( self, trans, id, rating ):
636        """ Rate a page asynchronously and return updated community data. """
637
638        page = self.get_page( trans, id, check_ownership=False, check_accessible=True )
639        if not page:
640            return trans.show_error_message( "The specified page does not exist." )
641
642        # Rate page.
643        page_rating = self.rate_item( trans.sa_session, trans.get_user(), page, rating )
644
645        return self.get_ave_item_rating_data( trans.sa_session, page )
646            
647    @web.expose
648    def get_embed_html_async( self, trans, id ):
649        """ Returns HTML for embedding a workflow in a page. """
650
651        # TODO: user should be able to embed any item he has access to. see display_by_username_and_slug for security code.
652        page = self.get_page( trans, id )
653        if page:
654            return "Embedded Page '%s'" % page.title
655
656    @web.expose
657    @web.json
658    @web.require_login( "use Galaxy pages" )
659    def get_name_and_link_async( self, trans, id=None ):
660        """ Returns page's name and link. """
661        page = self.get_page( trans, id )
662
663        if self.create_item_slug( trans.sa_session, page ):
664            trans.sa_session.flush()
665        return_dict = { "name" : page.title, "link" : url_for( action="display_by_username_and_slug", username=page.user.username, slug=page.slug ) }
666        return return_dict
667        
668    @web.expose
669    @web.require_login("select a history from saved histories")
670    def list_histories_for_selection( self, trans, **kwargs ):
671        """ Returns HTML that enables a user to select one or more histories. """
672        # Render the list view
673        return self._history_selection_grid( trans, **kwargs )
674        
675    @web.expose
676    @web.require_login("select a workflow from saved workflows")
677    def list_workflows_for_selection( self, trans, **kwargs ):
678        """ Returns HTML that enables a user to select one or more workflows. """
679        # Render the list view
680        return self._workflow_selection_grid( trans, **kwargs )
681        
682    @web.expose
683    @web.require_login("select a visualization from saved visualizations")
684    def list_visualizations_for_selection( self, trans, **kwargs ):
685        """ Returns HTML that enables a user to select one or more visualizations. """
686        # Render the list view
687        return self._visualization_selection_grid( trans, **kwargs )
688        
689    @web.expose
690    @web.require_login("select a page from saved pages")
691    def list_pages_for_selection( self, trans, **kwargs ):
692        """ Returns HTML that enables a user to select one or more pages. """
693        # Render the list view
694        return self._page_selection_grid( trans, **kwargs )
695        
696    @web.expose
697    @web.require_login("select a dataset from saved datasets")
698    def list_datasets_for_selection( self, trans, **kwargs ):
699        """ Returns HTML that enables a user to select one or more datasets. """
700        # Render the list view
701        return self._datasets_selection_grid( trans, **kwargs )
702        
703    @web.expose
704    @web.require_login("get annotation table for history")
705    def get_history_annotation_table( self, trans, id ):
706        """ Returns HTML for an annotation table for a history. """
707        history = self.get_history( trans, id, False, True )
708        
709        if history:
710            datasets = self.get_history_datasets( trans, history )
711            return trans.fill_template( "page/history_annotation_table.mako", history=history, datasets=datasets, show_deleted=False )
712            
713    @web.expose
714    def get_editor_iframe( self, trans ):
715        """ Returns the document for the page editor's iframe. """
716        return trans.fill_template( "page/wymiframe.mako" )
717        
718    def get_page( self, trans, id, check_ownership=True, check_accessible=False ):
719        """Get a page from the database by id."""
720        # Load history from database
721        id = trans.security.decode_id( id )
722        page = trans.sa_session.query( model.Page ).get( id )
723        if not page:
724            err+msg( "Page not found" )
725        else:
726            return self.security_check( trans, page, check_ownership, check_accessible )
727            
728    def get_item( self, trans, id ):
729        return self.get_page( trans, id )
730        
731    def _get_embed_html( self, trans, item_class, item_id ):
732        """ Returns HTML for embedding an item in a page. """
733        item_class = self.get_class( item_class )
734        if item_class == model.History:
735            history = self.get_history( trans, item_id, False, True )
736            history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history )
737            if history:
738                datasets = self.get_history_datasets( trans, history )
739                return trans.fill_template( "history/embed.mako", item=history, item_data=datasets )
740        elif item_class == model.HistoryDatasetAssociation:
741            dataset = self.get_dataset( trans, item_id, False, True )
742            dataset.annotation = self.get_item_annotation_str( trans.sa_session, dataset.history.user, dataset )
743            if dataset:
744                data = self.get_data( dataset )
745                return trans.fill_template( "dataset/embed.mako", item=dataset, item_data=data )
746        elif item_class == model.StoredWorkflow:
747            workflow = self.get_stored_workflow( trans, item_id, False, True )
748            workflow.annotation = self.get_item_annotation_str( trans.sa_session, workflow.user, workflow )
749            if workflow:
750                self.get_stored_workflow_steps( trans, workflow )
751                return trans.fill_template( "workflow/embed.mako", item=workflow, item_data=workflow.latest_workflow.steps )
752        elif item_class == model.Visualization:
753            visualization = self.get_visualization( trans, item_id, False, True )
754            visualization.annotation = self.get_item_annotation_str( trans.sa_session, visualization.user, visualization )
755            if visualization:
756                return trans.fill_template( "visualization/embed.mako", item=visualization, item_data=None )
757        
758        elif item_class == model.Page:
759            pass
760        
761        
762