Hi Ted,

This looks like a good solution to me.  I'll add your contributions to the 
Galaxy code base as soon as I get a chance.

Thanks for all of your efforts on this!

Greg Von Kuster


On Jan 7, 2013, at 3:22 PM, Ted Goldstein wrote:

> Hi  Greg and James,
> This has become a plumbing project.  What starts off as fixing a leaky faucet 
> ends being a $200 bill replacing the entire sink ;-).  But I don't see anyway 
> around it.
> 
> Yes, there is a lot of power in the allowed method since it uses a function 
> or a lambda. There are two problems with this: idea (1) it feels wrong to 
> have a predicate method called allowed modify the state of the object.
> and worse,(2)  the Python context that the lambda is capturing is at global 
> instantiation time.  But the data that I need captured and saved in the page 
> exist only after the user has made a set of choices. Thus it only exists in a 
> previous call to thePage.list() method.
> 
> Another problem. 
> Since this is a multi operation, the state that needs to be passed cannot go 
> into the  form URL itself of the form. I see that the call to get the URL  of 
> the form in grid_base.mako at line 702 does not take any operation arguments. 
>  Which is correct.  Normally the submit button on an HTML form does not 
> modify the form's URL  The usual solution from an HTML perspective is to have 
> hidden input fields on an HTML form.   So the solution would be to extend the 
> Grid class to generate and use hidden input fields.  This would be done on  a 
> copy of the grid object;. So there needs to be a new Grid method.
> 
> (my changes in bold)
> 
> class Grid
> ...
>     def clone_with_pass_through_data(self, data_dict):
>         # make a copy of myself for use in passing though data
>         clone = deepcopy(self)
>         clone.pass_through_data = data_dict
>         return clone
> 
> 
> my  example use in Page.list()
>                # Clone a copy of grid to have the pageids.
>                 grid = 
> self._libraries_selection_grid.clone_with_pass_through_data({"pageids": 
> pageidsraw});
>                 return trans.fill_template( 
> "page/library_datasets_select_grid.mako", pagetitles=pagetitles, grid=grid, 
> **kwargs)
> 
> pass_through_data   has a three line implementation in grid_base.mako
>    <form action="${url()}" method="post" onsubmit="return false;">
>         <input type="hidden" name="webapp" value="${webapp}"/>
> 
>       %for pass_name, pass_value in grid.pass_through_data.items():
>             <input type="hidden" name="${pass_name}" value="${pass_value}"/>
>         %endfor
>  ..
> 
> But  nothing is simple, form parameters  arguments are more complex than they 
> sem at first.
> 
> 
>       function go_to_URL() {
>             // Not async request.
>             url_args['async'] = false;
> 
>             // Build argument string.
>             var arg_str = "";
>             for (var arg in url_args) {
>                 arg_str = arg_str + arg + "=" + url_args[arg] + "&";
>             }
> 
>             // add in hidden fields
>             $('input[type=hidden]').each(function() {
>                 if (this.name == "webapp" || this.name == 
> "select_all_checkbox")
>                    return
>                 else
>                     arg_str = arg_str + this.name + "=" + this.value + "&";
>             })
> 
> 
>             // Go.
>             window.location = encodeURI( "${h.url_for()}?" + arg_str );
>         }
> 
> 
> What do you think of this idea?
> 
> Ted
>    
> 
> 
> On Jan 7, 2013, at 7:00 AM, James Taylor wrote:
> 
>> Ted, have you considered if you can do what you need with the 'allowed' 
>> method of GridOperation? It let's you define a callable condition that 
>> determines if that operation is allowed on a given item. It does not have 
>> access to trans but it would be reasonable to add that. 
>> 
>> 
>> --
>> James Taylor, Assistant Professor, Biology/CS, Emory University
>> 
>> 
>> On Mon, Jan 7, 2013 at 2:55 AM, Ted Goldstein <t...@soe.ucsc.edu> wrote:
>> HI Greg,
>> I thought of this. But there is potential bug in it which has security 
>> implications.  Though it is a low probability event, I have seen this sort 
>> of leakage happen in other environments where user data was stuck into 
>> globally shared objects.   The grid object is shared by all requests.  You 
>> are storing user specific data in the GridOperation. So if there is an 
>> unfortunate context switch in the middle of the setting the ids int the grid 
>> operation but before they are used,  the user specific data of one user will 
>> be visible to the other user.   Of course the solution is to make sure the 
>> grid is not shared (e.g. it is instantiated or  deep-copied) in the function 
>> or at least in the request thread that uses it.   
>> 
>> Thanks,  I will do this with on a deepcopy of the Grid to ensure that it 
>> doesn't et shared between threads.   You might want to chsnge the code in 
>> repository.py
>> 
>> Best regards,
>> 
>> Ted
>> 
>> 
>> On Jan 6, 2013, at 4:12 PM, Greg Von Kuster wrote:
>> 
>>> Hi Ted,
>>> 
>>> If I understand what your trying to do, I believe I've implemented grid 
>>> operations similar to what your attempting in several places in the Galaxy 
>>> tool shed, so checking the various grid classes and associated methods in 
>>> /lib/galaxy/webapps/community/controllers will give you some examples.
>>> 
>>> The trick is that you need to define the grid operations outside of the 
>>> grid class itself, but in the method that calls the grid (but just before 
>>> the call to display it) so the request parameters will include the selected 
>>> page ids.  
>>> 
>>> For an example of what I'm talking about, see the following method on about 
>>> line #740 in my ~/lib/galaxy/webapps/community/controllers/repository.py 
>>> code file.
>>> 
>>> Here is the relevant snippet of code.
>>> 
>>> def browse_valid_repositories( self, trans, **kwd ):
>>> ...irrelevant code not displayed...
>>>         repository_id = None
>>>         for k, v in kwd.items():
>>>             changset_revision_str = 'changeset_revision_'
>>>             if k.startswith( changset_revision_str ):
>>>                 repository_id = trans.security.encode_id( int( k.lstrip( 
>>> changset_revision_str ) ) )
>>>                 repository = suc.get_repository_in_tool_shed( trans, 
>>> repository_id )
>>>                 if repository.tip( trans.app ) != v:
>>>                     return trans.response.send_redirect( web.url_for( 
>>> controller='repository',
>>>                                                                       
>>> action='preview_tools_in_changeset',
>>>                                                                       
>>> repository_id=trans.security.encode_id( repository.id ),
>>>                                                                       
>>> changeset_revision=v ) )
>>>         url_args = dict( action='browse_valid_repositories',
>>>                          operation='preview_tools_in_changeset',
>>>                          repository_id=repository_id )
>>>         self.valid_repository_grid.operations = [ grids.GridOperation( 
>>> "Preview and install",
>>>                                                                         
>>> url_args=url_args,
>>>                                                                         
>>> allow_multiple=False,
>>>                                                                         
>>> async_compatible=False ) ]
>>>         return self.valid_repository_grid( trans, **kwd )
>>> 
>>> Sorry if I misunderstood your dilemma and this does not help you.
>>> 
>>> Greg Von Kuster
>>> 
>>> On Jan 6, 2013, at 4:52 PM, Ted Goldstein wrote:
>>> 
>>>> Hi,
>>>> Based on a suggestion by James Taylor, I am working on extending the 
>>>> security model so that pages can use the same RBAC permission system used 
>>>> in libraries. In fact, many of my pages have assets (images, etc) which 
>>>> are stored in a library. So this is a natural model. Therefore I want to 
>>>> make a connection between Pages and Libraries. that is visible to the user 
>>>> and extends the security model of page access. The owner of the page 
>>>> essentially grants anyone with access to a library access to the page as 
>>>> well. This part is going well.  It is few lines of code in the page access 
>>>> security code.
>>>> 
>>>> The implementation is to that I created a many-to-many relationship table 
>>>> LibraryPageAssociation, that  "Connects" Page objects to Library Objects.  
>>>> Grid objects provide a convenient interface to selecting multiple of each. 
>>>> In fact, I just extend the  regular PageListGrid  instantiated  by the 
>>>> /page/list  action  to select the pages by adding a  new GridOperation 
>>>> labeled "Select Libraries"  which then brings up new Grid 
>>>> LibrarySelectGrid (similar to the one visualization.py, but simpler) it 
>>>> has one GridOperation labeled "Connect Pages to Libraries"   
>>>> (allows_multi=True).
>>>> 
>>>> Here is the problem:  I can pass the page ids (enoded) into the mako page 
>>>> that carries the LibrarySelectGrid.  But I don't see a way to parametriize 
>>>>  "Connect Pages to Libraries"  GridOperation to pass anything out of the 
>>>> page except the Grid Sttate itself.   Do I have to extend GridOperation or 
>>>> am I missing something?  I have tried a number of things with url_args and 
>>>> global_operation lambda.  But these execute at controller instantiation 
>>>> time -- much too early. I need the page_ids that the user selects in the 
>>>> PageListGrid.
>>>> 
>>>> See the ???? in the bold region below.
>>>> 
>>>> Thanks,
>>>> Ted
>>>> 
>>>> 
>>>> diff -r 13098c7dc32a lib/galaxy/webapps/galaxy/controllers/page.py
>>>> --- a/lib/galaxy/webapps/galaxy/controllers/page.py        Sat Jan 05 
>>>> 17:40:49 2013 -0800
>>>> +++ b/lib/galaxy/webapps/galaxy/controllers/page.py        Sun Jan 06 
>>>> 13:47:05 2013 -0800
>>>> @@ -1,3 +1,4 @@
>>>> +import pdb
>>>>  from sqlalchemy import desc, and_
>>>>  from galaxy import model, web
>>>>  from galaxy.web import error, url_for
>>>> @@ -8,9 +9,6 @@
>>>>  from galaxy.util.odict import odict
>>>>  from galaxy.util.json import from_json_string
>>>>  from galaxy.web.base.controller import *
>>>> -"""
>>>> -from galaxy.webapps.galaxy.controllers.library import LibraryListGrid;
>>>> -"""
>>>>  
>>>>  
>>>>  def format_bool( b ):
>>>> @@ -54,6 +52,7 @@
>>>>          grids.GridOperation( "Edit content", allow_multiple=False, 
>>>> url_args=dict( action='edit_content') ),
>>>>          grids.GridOperation( "Edit attributes", allow_multiple=False, 
>>>> url_args=dict( action='edit') ),
>>>>          grids.GridOperation( "Share or Publish", allow_multiple=False, 
>>>> condition=( lambda item: not item.deleted ), async_compatible=False ),
>>>> +        grids.GridOperation( "Select Library", allow_multiple=True,  
>>>> condition=( lambda item: not item.deleted ), async_compatible=False ),
>>>>          grids.GridOperation( "Delete", confirm="Are you sure you want to 
>>>> delete this page?" ),
>>>>      ]
>>>>      def apply_query_filter( self, trans, query, **kwargs ):
>>>> @@ -198,24 +197,37 @@
>>>>          key="free-text-search", visible=False, filterable="standard" )
>>>>                  )
>>>>  
>>>> -"""
>>>> -class PageLibrariesSelectionGrid( LibraryListGrid ):
>>>> -    ""
>>>> -    Grid enables user to select multiple Libraries, which are then 
>>>> connected to the page.
>>>> -    ""
>>>> -    title = "Select Librariies"
>>>> -    template='/tracks/history_select_grid.mako'
>>>> +class LibrarySelectionGrid( ItemSelectionGrid ):
>>>> +    """ Grid for selecting library. """
>>>> +    # Grid definition.
>>>> +    title = "Select Libraries "
>>>>      model_class = model.Library
>>>> -    datasets_action = 'list_library_datasets'
>>>> -    datasets_param = "f-library"
>>>>      columns = [
>>>> -        NameColumn( "Library Name", key="name", filterable="standard" )
>>>> +       grids.TextColumn( "Library Name", key="name", 
>>>> filterable="standard" ),
>>>> +       grids.TextColumn( "Description", key="description", 
>>>> filterable="advanced" ),
>>>> +       grids.TextColumn( "Synopsis", key="synopsis", 
>>>> filterable="advanced" )
>>>>      ]
>>>> -    num_rows_per_page = 10
>>>> -    use_async = True
>>>> -    use_paging = True
>>>> -"""
>>>> -        
>>>> +    operations = [
>>>> +            grids.GridOperation( "Connect to Library", 
>>>> allow_multiple=True,
>>>> +                url_args=???????????,
>>>> +                async_compatible=False ),
>>>> +    ]
>>>> +
>>>> +    def apply_query_filter( self, trans, query, **kwargs ):
>>>> +        """Return all data libraries that the received user can access"""
>>>> +        user = trans.user
>>>> +        current_user_role_ids = [ role.id for role in user.all_roles() ]
>>>> +        library_access_action = 
>>>> trans.app.model.Library.permitted_actions.LIBRARY_ACCESS.action
>>>> +        restricted_library_ids = [ lp.library_id for lp in 
>>>> trans.sa_session.query( trans.model.LibraryPermissions ) \
>>>> +                                                                          
>>>>  .filter( trans.model.LibraryPermissions.table.c.action == 
>>>> library_access_action ) \
>>>> +                                                                          
>>>>  .distinct() ]
>>>> +        accessible_restricted_library_ids = [ lp.library_id for lp in 
>>>> trans.sa_session.query( trans.model.LibraryPermissions ) \
>>>> +                                                                          
>>>>             .filter( and_( trans.model.LibraryPermissions.table.c.action 
>>>> == library_access_action,
>>>> +                                                                          
>>>>                            
>>>> trans.model.LibraryPermissions.table.c.role_id.in_( current_user_role_ids 
>>>> ) ) ) ]
>>>> +        # Filter to get libraries accessible by the current user.  Get 
>>>> both 
>>>> +        # public libraries and restricted libraries accessible by the 
>>>> current user.
>>>> +        return query.filter( and_( trans.model.Library.table.c.deleted == 
>>>> False, ( or_( not_( trans.model.Library.table.c.id.in_( 
>>>> restricted_library_ids ) ), trans.model.Library.table.c.id.in_( 
>>>> accessible_restricted_library_ids ) ) ) ) )  .order_by( 
>>>> trans.app.model.Library.name )
>>>> +                
>>>>                  
>>>>  @ -309,16 +321,28 @@
>>>>      _datasets_selection_grid = HistoryDatasetAssociationSelectionGrid()
>>>>      _page_selection_grid = PageSelectionGrid()
>>>>      _visualization_selection_grid = VisualizationSelectionGrid()
>>>> -    """
>>>> -    _libraries_selection_grid = LibrariesSelectionGrid()
>>>> -    """
>>>> +    _libraries_selection_grid = LibrarySelectionGrid()
>>>>      
>>>>      @web.expose
>>>>      @web.require_login()  
>>>>      def list( self, trans, *args, **kwargs ):
>>>>          """ List user's pages. """
>>>>          # Handle operation
>>>> -        if 'operation' in kwargs and 'id' in kwargs:
>>>> +
>>>> +
>>>> +        # library connect step 1
>>>> +        if 'operation' in kwargs and kwargs['operation'] == 'select 
>>>> library':
>>>> +            ids = util.listify( kwargs['id'] )
>>>> +            session = trans.sa_session
>>>> +            pages = [session.query( model.Page ).get( 
>>>> trans.security.decode_id( id ) ) for id in ids]
>>>> +            return trans.fill_template( 
>>>> "page/library_datasets_select_grid.mako",  pages=pages, 
>>>> grid=self._libraries_selection_grid, **kwargs)
>>>> +
>>>> +        # library connect step 2
>>>> +        elif 'operation' in kwargs and kwargs['operation'] == 'connect to 
>>>> library':
>>>> +            # TBD
>>>> +            pdb.set_trace()
>>>> +
>>>> +        elif 'operation' in kwargs and 'id' in kwargs:
>>>>              session = trans.sa_session
>>>>              operation = kwargs['operation'].lower()
>>>>              ids = util.listify( kwargs['id'] )
>>>> @@ -765,14 +789,12 @@
>>>>          # Render the list view
>>>>          return self._workflow_selection_grid( trans, **kwargs )
>>>>          
>>>> -    """
>>>>      @web.expose
>>>>      @web.require_login("select libraries from all accessible libraries")
>>>>      def list_libraries_for_selection( self, trans, **kwargs ):
>>>>          " Returns HTML that enables a user to select one or more 
>>>> libraries. "
>>>>          # Render the list view
>>>>          return self._libraries_selection_grid( trans, **kwargs )
>>>> -    """
>>>>  
>>>>      @web.expose
>>>>      @web.require_login("select a visualization from saved visualizations")
>>>> 
>>>> ___________________________________________________________
>>>> Please keep all replies on the list by using "reply all"
>>>> in your mail client.  To manage your subscriptions to this
>>>> and other Galaxy lists, please use the interface at:
>>>> 
>>>>  http://lists.bx.psu.edu/
>>> 
>> 
>> 
>> ___________________________________________________________
>> Please keep all replies on the list by using "reply all"
>> in your mail client.  To manage your subscriptions to this
>> and other Galaxy lists, please use the interface at:
>> 
>>   http://lists.bx.psu.edu/
>> 
> 

___________________________________________________________
Please keep all replies on the list by using "reply all"
in your mail client.  To manage your subscriptions to this
and other Galaxy lists, please use the interface at:

  http://lists.bx.psu.edu/

Reply via email to