mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-10-25 10:24:38 +08:00
Merge pull request #2340 from carlfeberhard/history.sharing-api
History/Sharable model user sharing API
This commit is contained in:
@@ -147,6 +147,7 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
|
||||
"""
|
||||
Get or create a share for the given user (or users if `user` is a list).
|
||||
"""
|
||||
# precondition: user has been validated
|
||||
# allow user to be a list and call recursivly
|
||||
if isinstance( user, list ):
|
||||
return map( lambda user: self.share_with( item, user, flush=False ), user )
|
||||
@@ -307,19 +308,7 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
|
||||
self.session().flush()
|
||||
return item
|
||||
|
||||
# def by_slug( self, user, **kwargs ):
|
||||
# """
|
||||
# """
|
||||
# pass
|
||||
|
||||
# .... display
|
||||
# def display_by_username_and_slug( self, username, slug ):
|
||||
# """ Display item by username and slug. """
|
||||
|
||||
# def set_public_username( self, id, username, **kwargs ):
|
||||
# """ Set user's public username and delegate to sharing() """
|
||||
# def sharing( self, id, **kwargs ):
|
||||
# """ Handle item sharing. """
|
||||
# TODO: def by_slug( self, user, **kwargs ):
|
||||
|
||||
|
||||
class SharableModelSerializer( base.ModelSerializer,
|
||||
@@ -335,7 +324,8 @@ class SharableModelSerializer( base.ModelSerializer,
|
||||
|
||||
self.serializers.update({
|
||||
'user_id' : self.serialize_id,
|
||||
'username_and_slug' : self.serialize_username_and_slug
|
||||
'username_and_slug' : self.serialize_username_and_slug,
|
||||
'users_shared_with' : self.serialize_users_shared_with
|
||||
})
|
||||
# these use the default serializer but must still be white-listed
|
||||
self.serializable_keyset.update([
|
||||
@@ -347,19 +337,24 @@ class SharableModelSerializer( base.ModelSerializer,
|
||||
return None
|
||||
return ( '/' ).join(( 'u', item.user.username, self.SINGLE_CHAR_ABBR, item.slug ) )
|
||||
|
||||
# def serialize_username_and_slug( self, item, key, **context ):
|
||||
# """
|
||||
# """
|
||||
# #TODO: replace the above with this
|
||||
# url = self.url_for(controller='history', action="display_by_username_and_slug",
|
||||
# username=item.user.username, slug=item.slug )
|
||||
# return url
|
||||
|
||||
# the only ones that needs any fns:
|
||||
# user/user_id
|
||||
# user_shares?
|
||||
# username_and_slug?
|
||||
|
||||
def serialize_users_shared_with( self, item, key, user=None, **context ):
|
||||
"""
|
||||
Returns a list of encoded ids for users the item has been shared.
|
||||
|
||||
Skipped if the requesting user is not the owner.
|
||||
"""
|
||||
# TODO: still an open question as to whether key removal based on user
|
||||
# should be handled here or at a higher level (even if we didn't have to pass user (via thread context, etc.))
|
||||
if not self.manager.is_owner( item, user ):
|
||||
self.skip()
|
||||
|
||||
share_assocs = self.manager.get_share_assocs( item )
|
||||
return [ self.serialize_id( share, 'user_id' ) for share in share_assocs ]
|
||||
|
||||
|
||||
class SharableModelDeserializer( base.ModelDeserializer,
|
||||
taggable.TaggableDeserializerMixin, annotatable.AnnotatableDeserializerMixin, ratable.RatableDeserializerMixin ):
|
||||
@@ -371,8 +366,9 @@ class SharableModelDeserializer( base.ModelDeserializer,
|
||||
ratable.RatableDeserializerMixin.add_deserializers( self )
|
||||
|
||||
self.deserializers.update({
|
||||
'published' : self.deserialize_published,
|
||||
'importable' : self.deserialize_importable,
|
||||
'published' : self.deserialize_published,
|
||||
'importable' : self.deserialize_importable,
|
||||
'users_shared_with' : self.deserialize_users_shared_with,
|
||||
})
|
||||
|
||||
def deserialize_published( self, item, key, val, **context ):
|
||||
@@ -399,15 +395,32 @@ class SharableModelDeserializer( base.ModelDeserializer,
|
||||
self.manager.make_importable( item, flush=False )
|
||||
else:
|
||||
self.manager.make_non_importable( item, flush=False )
|
||||
return item.published
|
||||
return item.importable
|
||||
|
||||
# def deserialize_slug( self, item, val, **context ):
|
||||
# """
|
||||
# """
|
||||
# #TODO: call manager.set_slug
|
||||
# pass
|
||||
# TODO: def deserialize_slug( self, item, val, **context ):
|
||||
|
||||
# def deserialize_user_shares():
|
||||
def deserialize_users_shared_with( self, item, key, val, **context ):
|
||||
"""
|
||||
Accept a list of encoded user_ids, validate them as users, and then
|
||||
add or remove user shares in order to update the users_shared_with to
|
||||
match the given list finally returning the new list of shares.
|
||||
"""
|
||||
unencoded_ids = [ self.app.security.decode_id( id_ ) for id_ in val ]
|
||||
new_users_shared_with = set( self.manager.user_manager.by_ids( unencoded_ids ) )
|
||||
current_shares = self.manager.get_share_assocs( item )
|
||||
currently_shared_with = set([ share.user for share in current_shares ])
|
||||
|
||||
needs_adding = new_users_shared_with - currently_shared_with
|
||||
for user in needs_adding:
|
||||
current_shares.append( self.manager.share_with( item, user, flush=False ) )
|
||||
|
||||
needs_removing = currently_shared_with - new_users_shared_with
|
||||
for user in needs_removing:
|
||||
current_shares.remove( self.manager.unshare_with( item, user, flush=False ) )
|
||||
|
||||
self.manager.session().flush()
|
||||
# TODO: or should this return the list of ids?
|
||||
return current_shares
|
||||
|
||||
|
||||
class SharableModelFilters( base.ModelFilterParser,
|
||||
|
||||
@@ -437,6 +437,18 @@ class HistorySerializerTestCase( BaseTestCase ):
|
||||
serialized = self.history_serializer.serialize( history1, sharable_attrs )
|
||||
self.assertKeys( serialized, sharable_attrs )
|
||||
|
||||
self.log( 'should return user_id for user with whom it\'s been shared if the requester is the owner' )
|
||||
non_owner = self.user_manager.create( **user3_data )
|
||||
self.history_manager.share_with( history1, non_owner )
|
||||
serialized = self.history_serializer.serialize( history1, [ 'users_shared_with' ], user=user2 )
|
||||
self.assertKeys( serialized, [ 'users_shared_with' ] )
|
||||
self.assertIsInstance( serialized[ 'users_shared_with' ], list )
|
||||
self.assertEqual( serialized[ 'users_shared_with' ][0], self.app.security.encode_id( non_owner.id ) )
|
||||
|
||||
self.log( 'should not return users_shared_with if the requester is not the owner' )
|
||||
serialized = self.history_serializer.serialize( history1, [ 'users_shared_with' ], user=non_owner )
|
||||
self.assertFalse( hasattr( serialized, 'users_shared_with' ) )
|
||||
|
||||
def test_purgable( self ):
|
||||
user2 = self.user_manager.create( **user2_data )
|
||||
history1 = self.history_manager.create( name='history1', user=user2 )
|
||||
@@ -595,6 +607,42 @@ class HistoryDeserializerTestCase( BaseTestCase ):
|
||||
deserializer.deserialize( item, { 'community_rating' : 4 }, user=user2 )
|
||||
self.assertEqual( manager.ratings_count( item ), 1 )
|
||||
|
||||
def test_sharable( self ):
|
||||
manager = self.history_manager
|
||||
deserializer = self.history_deserializer
|
||||
|
||||
user2 = self.user_manager.create( **user2_data )
|
||||
item = manager.create( name='history1', user=user2 )
|
||||
non_owner = self.user_manager.create( **user3_data )
|
||||
|
||||
self.log( 'should allow adding a share by adding a user id to users_shared_with' )
|
||||
non_owner_id = self.app.security.encode_id( non_owner.id )
|
||||
deserializer.deserialize( item, { 'users_shared_with' : [ non_owner_id ] }, user=user2 )
|
||||
user_shares = manager.get_share_assocs( item )
|
||||
self.assertEqual( len( user_shares ), 1 )
|
||||
self.assertEqual( user_shares[0].user_id, non_owner.id )
|
||||
|
||||
self.log( 're-adding an existing user id should do nothing' )
|
||||
deserializer.deserialize( item, { 'users_shared_with' : [ non_owner_id, non_owner_id ] }, user=user2 )
|
||||
user_shares = manager.get_share_assocs( item )
|
||||
self.assertEqual( len( user_shares ), 1 )
|
||||
self.assertEqual( user_shares[0].user_id, non_owner.id )
|
||||
|
||||
self.log( 'should allow removing a share by not having it in users_shared_with' )
|
||||
deserializer.deserialize( item, { 'users_shared_with' : [] }, user=user2 )
|
||||
user_shares = manager.get_share_assocs( item )
|
||||
self.assertEqual( len( user_shares ), 0 )
|
||||
|
||||
self.log( 'adding a bad user id should error' )
|
||||
self.assertRaises( AttributeError,
|
||||
deserializer.deserialize, item, { 'users_shared_with' : [ None ] }, user=user2 )
|
||||
|
||||
self.log( 'adding a non-existing user id should do nothing' )
|
||||
non_user_id = self.app.security.encode_id( 99 )
|
||||
deserializer.deserialize( item, { 'users_shared_with' : [ non_user_id ] }, user=user2 )
|
||||
user_shares = manager.get_share_assocs( item )
|
||||
self.assertEqual( len( user_shares ), 0 )
|
||||
|
||||
|
||||
# =============================================================================
|
||||
class HistoryFiltersTestCase( BaseTestCase ):
|
||||
|
||||
@@ -7,6 +7,8 @@ import shutil
|
||||
|
||||
from galaxy.web import security
|
||||
from galaxy import objectstore
|
||||
from galaxy import model
|
||||
from galaxy.datatypes import registry
|
||||
from galaxy.model import mapping
|
||||
from galaxy.util.bunch import Bunch
|
||||
|
||||
@@ -59,6 +61,12 @@ class MockApp( object ):
|
||||
self.visualizations_registry = MockVisualizationsRegistry()
|
||||
self.tag_handler = tags.GalaxyTagManager( self )
|
||||
self.quota_agent = quota.QuotaAgent( self.model )
|
||||
self.init_datatypes()
|
||||
|
||||
def init_datatypes( self ):
|
||||
datatypes_registry = registry.Registry()
|
||||
datatypes_registry.load_datatypes()
|
||||
model.set_datatypes_registry( datatypes_registry )
|
||||
|
||||
|
||||
class MockAppConfig( Bunch ):
|
||||
|
||||
Reference in New Issue
Block a user