1 new changeset in galaxy-central: http://bitbucket.org/galaxy/galaxy-central/changeset/61cf27e60560/ changeset: 61cf27e60560 user: natefoo date: 2011-09-22 23:13:53 summary: Abstraction for web methods, implemented for the Quota operations and the framework is there for everything else now. Works like this: * If your form/API params are the same across all forms for a model object (e.g. all Quota operations), they should be placed in mixins in galaxy.web.params and can be used universally by both the UI and API. * Validating params and performing the requestion actions go in mixins in galaxy.actions. These return a value upon success or raise a MessageException upon failure. * Fetching objects with checks relevant to the web methods can be done via BaseController.get_object and its wrappers (see other helpers in the Mixins in galaxy.web.base.controller as well). * This leaves UI controller methods to just handle the stuff related to filling in web forms. * The API methods should be making use of paste.httpexceptions.* for handling error conditions. Also, I think it'd be best to generally return an item's API value from most methods and include the "message" as an entry in the return dict rather than just by itself as a string. None of this is required, but it would be great to convert the existing stuff to this new model. galaxy.exceptions is a collection point for all custom exceptions. The User API now returns current disk usage with a User's detailed information. Exceptions in the API are now logged by the expose_api decorator and hidden from the client. affected #: 56 files (-1 bytes) --- a/lib/galaxy/model/__init__.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/model/__init__.py Thu Sep 22 17:13:53 2011 -0400 @@ -47,7 +47,7 @@ class User( object, APIItem ): api_collection_visible_keys = ( 'id', 'email' ) - api_element_visible_keys = ( 'id', 'email', 'username' ) + api_element_visible_keys = ( 'id', 'email', 'username', 'total_disk_usage', 'nice_total_disk_usage' ) def __init__( self, email=None, password=None ): self.email = email self.password = password @@ -82,6 +82,9 @@ def set_disk_usage( self, bytes ): self.disk_usage = bytes total_disk_usage = property( get_disk_usage, set_disk_usage ) + @property + def nice_total_disk_usage( self ): + return self.get_disk_usage( nice_size=True ) def calculate_disk_usage( self ): dataset_ids = [] total = 0 --- a/lib/galaxy/quota/__init__.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/quota/__init__.py Thu Sep 22 17:13:53 2011 -0400 @@ -33,6 +33,8 @@ return usage def get_percent( self, trans=None, user=False, history=False, usage=False, quota=False ): return None + def get_user_quotas( self, user ): + return [] class QuotaAgent( NoQuotaAgent ): """Class that handles galaxy quotas""" @@ -150,3 +152,20 @@ gqa = self.model.GroupQuotaAssociation( group, quota ) self.sa_session.add( gqa ) self.sa_session.flush() + def get_user_quotas( self, user ): + rval = [] + if not user: + dqa = self.sa_session.query( self.model.DefaultQuotaAssociation ) \ + .filter( self.model.DefaultQuotaAssociation.table.c.type==self.model.DefaultQuotaAssociation.types.UNREGISTERED ).first() + if dqa: + rval.append( dqa.quota ) + else: + dqa = self.sa_session.query( self.model.DefaultQuotaAssociation ) \ + .filter( self.model.DefaultQuotaAssociation.table.c.type==self.model.DefaultQuotaAssociation.types.REGISTERED ).first() + if dqa: + rval.append( dqa.quota ) + for uqa in user.quotas: + rval.append( uqa.quota ) + for gqa in [ uga.group for uga in user.groups ]: + rval.append( gqa.quota ) + return rval --- a/lib/galaxy/util/__init__.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/util/__init__.py Thu Sep 22 17:13:53 2011 -0400 @@ -179,7 +179,7 @@ return default return out -class Params: +class Params( object ): """ Stores and 'sanitizes' parameters. Alphanumeric characters and the non-alphanumeric ones that are deemed safe are let to pass through (see L{valid_chars}). --- a/lib/galaxy/web/api/forms.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/forms.py Thu Sep 22 17:13:53 2011 -0400 @@ -2,14 +2,14 @@ API operations on FormDefinition objects. """ import logging -from galaxy.web.base.controller import BaseController, url_for +from galaxy.web.base.controller import BaseAPIController, url_for from galaxy import web from galaxy.forms.forms import form_factory from elementtree.ElementTree import XML log = logging.getLogger( __name__ ) -class FormDefinitionAPIController( BaseController ): +class FormDefinitionAPIController( BaseAPIController ): @web.expose_api def index( self, trans, **kwd ): --- a/lib/galaxy/web/api/histories.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/histories.py Thu Sep 22 17:13:53 2011 -0400 @@ -9,22 +9,23 @@ from galaxy.model.orm import * import galaxy.datatypes from galaxy.util.bunch import Bunch -from galaxy.web.api.util import * log = logging.getLogger( __name__ ) -class HistoriesController( BaseController ): +class HistoriesController( BaseAPIController, UsesHistory ): @web.expose_api - def index( self, trans, **kwd ): + def index( self, trans, deleted='False', **kwd ): """ GET /api/histories + GET /api/histories/deleted Displays a collection (list) of histories. """ rval = [] + deleted = util.string_as_bool( deleted ) - try: - query = trans.sa_session.query( trans.app.model.History ).filter_by( user=trans.user, deleted=False ).order_by( + try: + query = trans.sa_session.query( trans.app.model.History ).filter_by( user=trans.user, deleted=deleted ).order_by( desc(trans.app.model.History.table.c.update_time)).all() except Exception, e: rval = "Error in history API" @@ -44,13 +45,15 @@ return rval @web.expose_api - def show( self, trans, id, **kwd ): + def show( self, trans, id, deleted='False', **kwd ): """ GET /api/histories/{encoded_history_id} + GET /api/histories/deleted/{encoded_history_id} Displays information about a history. """ history_id = id params = util.Params( kwd ) + deleted = util.string_as_bool( deleted ) def traverse( datasets ): rval = {} @@ -64,7 +67,7 @@ return rval try: - history = get_history_for_access( trans, history_id ) + history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True, deleted=deleted ) except Exception, e: return str( e ) @@ -124,7 +127,7 @@ purge = util.string_as_bool( kwd['payload'].get( 'purge', False ) ) try: - history = get_history_for_modification( trans, history_id ) + history = self.get_history( trans, history_id, check_ownership=True, check_accessible=False, deleted=True ) except Exception, e: return str( e ) @@ -146,3 +149,15 @@ trans.sa_session.flush() return 'OK' + + @web.expose_api + def undelete( self, trans, id, **kwd ): + """ + POST /api/histories/deleted/{encoded_quota_id}/undelete + Undeletes a quota + """ + history = self.get_history( trans, history_id, check_ownership=True, check_accessible=False, deleted=True ) + history.deleted = False + trans.sa_session.add( history ) + trans.sa_session.flush() + return 'OK' --- a/lib/galaxy/web/api/history_contents.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/history_contents.py Thu Sep 22 17:13:53 2011 -0400 @@ -7,7 +7,6 @@ from galaxy.web.base.controller import * from galaxy.util.sanitize_html import sanitize_html from galaxy.model.orm import * -from galaxy.web.api.util import * import pkg_resources pkg_resources.require( "Routes" ) @@ -15,7 +14,7 @@ log = logging.getLogger( __name__ ) -class HistoryContentsController( BaseController ): +class HistoryContentsController( BaseAPIController, UsesHistoryDatasetAssociation, UsesHistory ): @web.expose_api def index( self, trans, history_id, **kwd ): @@ -24,7 +23,7 @@ Displays a collection (list) of history contents """ try: - history = get_history_for_access( trans, history_id ) + history = self.get_history( trans, history_id, check_ownership=True, check_accessible=True ) except Exception, e: return str( e ) @@ -51,7 +50,7 @@ """ content_id = id try: - content = get_history_content_for_access( trans, content_id ) + content = self.get_history_dataset_association( trans, content_id, check_ownership=True, check_accessible=True ) except Exception, e: return str( e ) try: @@ -63,7 +62,7 @@ # http://routes.groovie.org/generating.html # url_for is being phased out, so new applications should use url item['download_url'] = url(controller='dataset', action='display', dataset_id=trans.security.encode_id(content.id), to_ext=content.ext) - item = encode_all_ids( trans, item ) + item = self.encode_all_ids( trans, item ) except Exception, e: item = "Error in history API at listing dataset" log.error( item + ": %s" % str(e) ) @@ -80,7 +79,7 @@ from_ld_id = payload.get( 'from_ld_id', None ) try: - history = get_history_for_modification( trans, history_id ) + history = self.get_history( trans, history_id, check_ownership=True, check_accessible=False ) except Exception, e: return str( e ) --- a/lib/galaxy/web/api/libraries.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/libraries.py Thu Sep 22 17:13:53 2011 -0400 @@ -10,7 +10,7 @@ log = logging.getLogger( __name__ ) -class LibrariesController( BaseController ): +class LibrariesController( BaseAPIController ): @web.expose_api def index( self, trans, **kwd ): --- a/lib/galaxy/web/api/library_contents.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/library_contents.py Thu Sep 22 17:13:53 2011 -0400 @@ -7,11 +7,10 @@ from galaxy.web.base.controller import * from galaxy.util.sanitize_html import sanitize_html from galaxy.model.orm import * -from galaxy.web.api.util import * log = logging.getLogger( __name__ ) -class LibraryContentsController( BaseController ): +class LibraryContentsController( BaseAPIController, UsesLibrary, UsesLibraryItems ): @web.expose_api def index( self, trans, library_id, **kwd ): @@ -74,12 +73,12 @@ GET /api/libraries/{encoded_library_id}/contents/{encoded_content_id} Displays information about a library content (file or folder). """ - content_id = id - try: - content = get_library_content_for_access( trans, content_id ) - except Exception, e: - return str( e ) - return encode_all_ids( trans, content.get_api_value( view='element' ) ) + class_name, content_id = self.__decode_library_content_id( trans, id ) + if class_name == 'LibraryFolder': + content = self.get_library_folder( trans, content_id, check_ownership=False, check_accessibility=True ) + else: + content = self.get_library_dataset( trans, content_id, check_ownership=False, check_accessibility=True ) + return self.encode_all_ids( trans, content.get_api_value( view='element' ) ) @web.expose_api def create( self, trans, library_id, payload, **kwd ): @@ -102,8 +101,8 @@ else: folder_id = payload.pop( 'folder_id' ) try: - # _for_modification is not necessary, that security happens in the library_common controller. - parent = get_library_folder_for_access( trans, library_id, folder_id ) + # security is checked in the downstream controller + parent = self.get_library_folder( trans, folder_id, check_ownership=False, check_accessibility=False ) except Exception, e: return str( e ) # The rest of the security happens in the library_common controller. @@ -128,3 +127,11 @@ name = v.name, url = url_for( 'library_content', library_id=library_id, id=encoded_id ) ) ) return rval + + def __decode_library_content_id( self, trans, content_id ): + if ( len( content_id ) % 16 == 0 ): + return 'LibraryDataset', content_id + elif ( content_id.startswith( 'F' ) ): + return 'LibraryFolder', content_id[1:] + else: + raise HTTPBadRequest( 'Malformed library content id ( %s ) specified, unable to decode.' % str( content_id ) ) --- a/lib/galaxy/web/api/permissions.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/permissions.py Thu Sep 22 17:13:53 2011 -0400 @@ -10,7 +10,7 @@ log = logging.getLogger( __name__ ) -class PermissionsController( BaseController ): +class PermissionsController( BaseAPIController ): # Method not ideally named @web.expose_api --- a/lib/galaxy/web/api/quotas.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/quotas.py Thu Sep 22 17:13:53 2011 -0400 @@ -2,24 +2,29 @@ API operations on Quota objects. """ import logging -from galaxy.web.base.controller import BaseController, url_for +from galaxy.web.base.controller import BaseAPIController, Admin, UsesQuota, url_for from galaxy import web, util from elementtree.ElementTree import XML -from galaxy.web.api.util import * + +from galaxy.web.params import QuotaParamParser +from galaxy.actions.admin import AdminActions + +from paste.httpexceptions import HTTPBadRequest +from galaxy.exceptions import * log = logging.getLogger( __name__ ) -class QuotaAPIController( BaseController ): +class QuotaAPIController( BaseAPIController, Admin, AdminActions, UsesQuota, QuotaParamParser ): @web.expose_api @web.require_admin - def index( self, trans, deleted=False, **kwd ): + def index( self, trans, deleted='False', **kwd ): """ GET /api/quotas GET /api/quotas/deleted Displays a collection (list) of quotas. """ - #return str( trans.webapp.api_mapper ) rval = [] + deleted = util.string_as_bool( deleted ) query = trans.sa_session.query( trans.app.model.Quota ) if deleted: route = 'deleted_quota' @@ -36,16 +41,13 @@ @web.expose_api @web.require_admin - def show( self, trans, id, deleted=False, **kwd ): + def show( self, trans, id, deleted='False', **kwd ): """ GET /api/quotas/{encoded_quota_id} GET /api/quotas/deleted/{encoded_quota_id} Displays information about a quota. """ - try: - quota = get_quota_for_access( trans, id, deleted=deleted ) - except BadRequestException, e: - return str( e ) + quota = self.get_quota( trans, id, deleted=util.string_as_bool( deleted ) ) return quota.get_api_value( view='element', value_mapper={ 'id': trans.security.encode_id } ) @web.expose_api @@ -56,20 +58,18 @@ Creates a new quota. """ try: - self._validate_in_users_and_groups( trans, payload ) + self.validate_in_users_and_groups( trans, payload ) except Exception, e: - trans.response.status = 400 - return str( e ) - - status, result = trans.webapp.controllers['admin'].create_quota( trans, cntrller='api', **payload ) - if status != 200 or type( result ) != trans.app.model.Quota: - trans.response.status = status - return str( result ) - else: - encoded_id = trans.security.encode_id( result.id ) - return dict( id = encoded_id, - name = result.name, - url = url_for( 'quotas', id=encoded_id ) ) + raise HTTPBadRequest( detail=str( e ) ) + params = self.get_quota_params( payload ) + try: + quota, message = self._create_quota( params ) + except ActionInputError, e: + raise HTTPBadRequest( detail=str( e ) ) + item = quota.get_api_value( value_mapper={ 'id': trans.security.encode_id } ) + item['url'] = url_for( 'quota', id=trans.security.encode_id( quota.id ) ) + item['message'] = message + return item @web.expose_api @web.require_admin @@ -79,32 +79,34 @@ Modifies a quota. """ try: - self._validate_in_users_and_groups( trans, payload ) - quota = get_quota_for_access( trans, id, deleted=False ) # deleted quotas are not technically members of this collection + self.validate_in_users_and_groups( trans, payload ) except Exception, e: - trans.response.status = 400 - return str( e ) - # TODO: Doing it this way makes the update non-atomic if a method fails after an earlier one has succeeded. + raise HTTPBadRequest( detail=str( e ) ) + + quota = self.get_quota( trans, id, deleted=False ) + + # FIXME: Doing it this way makes the update non-atomic if a method fails after an earlier one has succeeded. payload['id'] = id + params = self.get_quota_params( payload ) methods = [] if payload.get( 'name', None ) or payload.get( 'description', None ): - methods.append( trans.webapp.controllers['admin'].rename_quota ) + methods.append( self._rename_quota ) if payload.get( 'amount', None ): - methods.append( trans.webapp.controllers['admin'].edit_quota ) + methods.append( self._edit_quota ) if payload.get( 'default', None ) == 'no': - methods.append( trans.webapp.controllers['admin'].unset_quota_default ) + methods.append( self._unset_quota_default ) elif payload.get( 'default', None ): - methods.append( trans.webapp.controllers['admin'].set_quota_default ) + methods.append( self._set_quota_default ) if payload.get( 'in_users', None ) or payload.get( 'in_groups', None ): - methods.append( trans.webapp.controllers['admin'].manage_users_and_groups_for_quota ) + methods.append( self._manage_users_and_groups_for_quota ) messages = [] for method in methods: - status, result = method( trans, cntrller='api', **payload ) - if status != 200: - trans.response.status = status - return str( result ) - messages.append( result ) + try: + message = method( quota, params ) + except ActionInputError, e: + raise HTTPBadRequest( detail=str( e ) ) + messages.append( message ) return '; '.join( messages ) @web.expose_api @@ -114,27 +116,20 @@ DELETE /api/quotas/{encoded_quota_id} Deletes a quota """ - try: - get_quota_for_access( trans, id, deleted=False ) # deleted quotas are not technically members of this collection - except BadRequestException, e: - return str( e ) + quota = self.get_quota( trans, id, deleted=False ) # deleted quotas are not technically members of this collection + # a request body is optional here payload = kwd.get( 'payload', {} ) payload['id'] = id + params = self.get_quota_params( payload ) - status, result = trans.webapp.controllers['admin'].mark_quota_deleted( trans, cntrller='api', **payload ) - if status != 200: - trans.response.status = status - return str( result ) - rval = result - - if util.string_as_bool( payload.get( 'purge', False ) ): - status, result = trans.webapp.controllers['admin'].purge_quota( trans, cntrller='api', **payload ) - if status != 200: - trans.response.status = status - return str( result ) - rval += '; %s' % result - return rval + try: + message = self._mark_quota_deleted( quota, params ) + if util.string_as_bool( payload.get( 'purge', False ) ): + message += self._purge_quota( quota, params ) + except ActionInputError, e: + raise HTTPBadRequest( detail=str( e ) ) + return message @web.expose_api @web.require_admin @@ -143,39 +138,9 @@ POST /api/quotas/deleted/{encoded_quota_id}/undelete Undeletes a quota """ - status, result = trans.webapp.controllers['admin'].undelete_quota( trans, cntrller='api', id=id ) - if status != 200: - trans.response.status = status - return str( result ) - return result - - def _validate_in_users_and_groups( self, trans, payload ): - """ - For convenience, in_users and in_groups can be encoded IDs or emails/group names - """ - def get_id( item, model_class, column ): - try: - return trans.security.decode_id( item ) - except: - pass # maybe an email/group name - # this will raise if the item is invalid - return trans.sa_session.query( model_class ).filter( column == item ).first().id - new_in_users = [] - new_in_groups = [] - invalid = [] - for item in util.listify( payload.get( 'in_users', [] ) ): - try: - new_in_users.append( get_id( item, trans.app.model.User, trans.app.model.User.table.c.email ) ) - except: - invalid.append( item ) - for item in util.listify( payload.get( 'in_groups', [] ) ): - try: - new_in_groups.append( get_id( item, trans.app.model.Group, trans.app.model.Group.table.c.name ) ) - except: - invalid.append( item ) - if invalid: - msg = "The following value(s) for associated users and/or groups could not be parsed: %s." % ', '.join( invalid ) - msg += " Valid values are email addresses of users, names of groups, or IDs of both." - raise Exception( msg ) - payload['in_users'] = map( str, new_in_users ) - payload['in_groups'] = map( str, new_in_groups ) + quota = self.get_quota( trans, id, deleted=True ) + params = self.get_quota_params( payload ) + try: + return self._undelete_quota( quota, params ) + except ActionInputError, e: + raise HTTPBadRequest( detail=str( e ) ) --- a/lib/galaxy/web/api/request_types.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/request_types.py Thu Sep 22 17:13:53 2011 -0400 @@ -2,7 +2,7 @@ API operations on RequestType objects. """ import logging -from galaxy.web.base.controller import BaseController, url_for +from galaxy.web.base.controller import BaseAPIController, url_for from galaxy import web from galaxy.sample_tracking.request_types import request_type_factory from elementtree.ElementTree import XML @@ -10,7 +10,7 @@ log = logging.getLogger( __name__ ) -class RequestTypeAPIController( BaseController ): +class RequestTypeAPIController( BaseAPIController ): @web.expose_api def index( self, trans, **kwd ): """ --- a/lib/galaxy/web/api/requests.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/requests.py Thu Sep 22 17:13:53 2011 -0400 @@ -11,7 +11,7 @@ log = logging.getLogger( __name__ ) -class RequestsAPIController( BaseController ): +class RequestsAPIController( BaseAPIController ): update_types = Bunch( REQUEST = 'request_state' ) update_type_values = [v[1] for v in update_types.items()] @web.expose_api --- a/lib/galaxy/web/api/roles.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/roles.py Thu Sep 22 17:13:53 2011 -0400 @@ -2,13 +2,13 @@ API operations on Role objects. """ import logging -from galaxy.web.base.controller import BaseController, url_for +from galaxy.web.base.controller import BaseAPIController, url_for from galaxy import web from elementtree.ElementTree import XML log = logging.getLogger( __name__ ) -class RoleAPIController( BaseController ): +class RoleAPIController( BaseAPIController ): @web.expose_api def index( self, trans, **kwd ): """ --- a/lib/galaxy/web/api/samples.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/samples.py Thu Sep 22 17:13:53 2011 -0400 @@ -9,7 +9,7 @@ log = logging.getLogger( __name__ ) -class SamplesAPIController( BaseController ): +class SamplesAPIController( BaseAPIController ): update_types = Bunch( SAMPLE = [ 'sample_state', 'run_details' ], SAMPLE_DATASET = [ 'sample_dataset_transfer_status' ] ) update_type_values = [] --- a/lib/galaxy/web/api/users.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/users.py Thu Sep 22 17:13:53 2011 -0400 @@ -2,59 +2,79 @@ API operations on User objects. """ import logging -from galaxy.web.base.controller import BaseController, url_for -from galaxy import web +from galaxy.web.base.controller import BaseAPIController, url_for +from galaxy import web, util from elementtree.ElementTree import XML +from paste.httpexceptions import * log = logging.getLogger( __name__ ) -class UserAPIController( BaseController ): +class UserAPIController( BaseAPIController ): @web.expose_api - def index( self, trans, **kwd ): + def index( self, trans, deleted='False', **kwd ): """ GET /api/users + GET /api/users/deleted Displays a collection (list) of users. """ - if not trans.user_is_admin(): - trans.response.status = 403 - return "You are not authorized to view the list of users." rval = [] - for user in trans.sa_session.query( trans.app.model.User ).filter( trans.app.model.User.table.c.deleted == False ): + query = trans.sa_session.query( trans.app.model.User ) + deleted = util.string_as_bool( deleted ) + if deleted: + route = 'deleted_user' + query = query.filter( trans.app.model.User.table.c.deleted == True ) + # only admins can see deleted users + if not trans.user_is_admin(): + return [] + else: + route = 'user' + query = query.filter( trans.app.model.User.table.c.deleted == False ) + # special case: user can see only their own user + if not trans.user_is_admin(): + item = trans.user.get_api_value( value_mapper={ 'id': trans.security.encode_id } ) + item['url'] = url_for( route, id=encoded_id ) + return item + for user in query: item = user.get_api_value( value_mapper={ 'id': trans.security.encode_id } ) encoded_id = trans.security.encode_id( user.id ) - item['url'] = url_for( 'user', id=encoded_id ) + item['url'] = url_for( route, id=encoded_id ) rval.append( item ) return rval @web.expose_api - def show( self, trans, id, **kwd ): + def show( self, trans, id, deleted='False', **kwd ): """ GET /api/users/{encoded_user_id} + GET /api/users/deleted/{encoded_user_id} Displays information about a user. """ - if not trans.user_is_admin(): - trans.response.status = 403 - return "You are not authorized to view user info." - user_id = id + deleted = util.string_as_bool( deleted ) try: - decoded_user_id = trans.security.decode_id( user_id ) - except TypeError: - trans.response.status = 400 - return "Malformed user id ( %s ) specified, unable to decode." % str( user_id ) - try: - user = trans.sa_session.query( trans.app.model.User ).get( decoded_user_id ) + user = self.get_user( trans, id, deleted=deleted ) + if not trans.user_is_admin(): + assert trans.user == user + assert not user.deleted except: - trans.response.status = 400 - return "That user does not exist." - item = user.get_api_value( view='element', value_mapper={ 'id': trans.security.encode_id } ) - item['url'] = url_for( 'user', id=user_id ) + if trans.user_is_admin(): + raise + else: + raise HTTPBadRequest( detail='Invalid user id ( %s ) specified' % id ) + item = user.get_api_value( view='element', value_mapper={ 'id': trans.security.encode_id, + 'total_disk_usage': float } ) return item - @web.expose_api - def create( self, trans, payload, **kwd ): - """ - POST /api/users - Creates a new user. - """ - trans.response.status = 403 - return "Not implemented." + @web.expose + def create( self, trans, **kwd ): + raise HTTPNotImplemented() + + @web.expose + def update( self, trans, **kwd ): + raise HTTPNotImplemented() + + @web.expose + def delete( self, trans, **kwd ): + raise HTTPNotImplemented() + + @web.expose + def undelete( self, trans, **kwd ): + raise HTTPNotImplemented() --- a/lib/galaxy/web/api/util.py Thu Sep 22 12:18:38 2011 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,116 +0,0 @@ -""" -Utility methods for API controllers -""" - -class BadRequestException( Exception ): - pass - -def get_history_for_access( trans, history_id ): - try: - decoded_history_id = trans.security.decode_id( history_id ) - except TypeError: - trans.response.status = 400 - raise BadRequestException( "Malformed history id ( %s ) specified, unable to decode." % str( history_id ) ) - try: - history = trans.sa_session.query( trans.app.model.History ).get( decoded_history_id ) - assert history - if history.user != trans.user and not trans.user_is_admin(): - assert trans.sa_session.query( trans.app.model.HistoryUserShareAssociation ).filter_by( user=trans.user, history=history ).count() != 0 - except: - trans.response.status = 400 - raise BadRequestException( "Invalid history id ( %s ) specified." % str( history_id ) ) - return history - -def get_history_for_modification( trans, history_id ): - history = get_history_for_access( trans, history_id ) - try: - assert trans.user_is_admin() or history.user == trans.user - except: - trans.response.status = 400 - raise BadRequestException( "Invalid history id ( %s ) specified." % str( history_id ) ) - return history - -def get_history_content_for_access( trans, content_id ): - # Note that we could check the history provided in the URL heirarchy here, - # but it's irrelevant, we care about the history associated with the hda. - try: - decoded_content_id = trans.security.decode_id( content_id ) - model_class = trans.app.model.HistoryDatasetAssociation - except: - trans.response.status = 400 - raise BadRequestException( "Malformed history content id ( %s ) specified, unable to decode." % str( content_id ) ) - try: - content = trans.sa_session.query( model_class ).get( decoded_content_id ) - assert content - if content.history.user != trans.user and not trans.user_is_admin(): - assert trans.sa_session.query(trans.app.model.HistoryUserShareAssociation).filter_by(user=trans.user, history=content.history).count() != 0 - except: - trans.response.status = 400 - raise BadRequestException( "Invalid history content id ( %s ) specified." % ( str( content_id ) ) ) - return content - -def get_library_folder_for_access( trans, library_id, folder_id ): - """ - When we know we're looking for a folder, take either the 'F' + encoded_id or bare encoded_id. - """ - if ( len( folder_id ) % 16 == 0 ): - folder_id = 'F' + folder_id - return get_library_content_for_access( trans, folder_id ) - -def get_library_content_for_access( trans, content_id ): - try: - if ( len( content_id ) % 16 == 0 ): - model_class = trans.app.model.LibraryDataset - decoded_content_id = trans.security.decode_id( content_id ) - elif ( content_id.startswith( 'F' ) ): - model_class = trans.app.model.LibraryFolder - decoded_content_id = trans.security.decode_id( content_id[1:] ) - else: - raise Exception( 'Bad id' ) - except: - trans.response.status = 400 - raise BadRequestException( "Malformed library content id ( %s ) specified, unable to decode." % str( content_id ) ) - try: - content = trans.sa_session.query( model_class ).get( decoded_content_id ) - assert content - assert trans.user_is_admin() or trans.app.security_agent.can_access_library_item( trans.get_current_user_roles(), content, trans.user ) - except: - trans.response.status = 400 - raise BadRequestException( "Invalid library content id ( %s ) specified." % str( content_id ) ) - return content - -def get_quota_for_access( trans, quota_id, deleted=False ): - """ - No security - the only methods that use this do so with admin users. If - quota modification becomes a role, this will need to be updated. - """ - try: - decoded_quota_id = trans.security.decode_id( quota_id ) - except TypeError: - trans.response.status = 400 - raise BadRequestException( "Malformed quota id ( %s ) specified, unable to decode." % str( quota_id ) ) - try: - quota = trans.sa_session.query( trans.app.model.Quota ).get( decoded_quota_id ) - assert quota is not None - if deleted: - assert quota.deleted - else: - assert not quota.deleted - except: - trans.response.status = 400 - raise BadRequestException( "Invalid quota id ( %s ) specified." % str( quota_id ) ) - return quota - -def encode_all_ids( trans, rval ): - """ - encodes all integer values in the dict rval whose keys are 'id' or end with '_id' - """ - if type( rval ) != dict: - return rval - for k, v in rval.items(): - if k == 'id' or k.endswith( '_id' ): - try: - rval[k] = trans.security.encode_id( v ) - except: - pass # probably already encoded - return rval --- a/lib/galaxy/web/api/workflows.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/api/workflows.py Thu Sep 22 17:13:53 2011 -0400 @@ -7,13 +7,13 @@ from galaxy import util from galaxy import web from galaxy.tools.parameters import visit_input_values, DataToolParameter -from galaxy.web.base.controller import BaseController, url_for +from galaxy.web.base.controller import BaseAPIController, url_for from galaxy.workflow.modules import module_factory from galaxy.jobs.actions.post import ActionBox log = logging.getLogger(__name__) -class WorkflowsAPIController(BaseController): +class WorkflowsAPIController(BaseAPIController): @web.expose_api def index(self, trans, **kwd): """ --- a/lib/galaxy/web/base/controller.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/base/controller.py Thu Sep 22 17:13:53 2011 -0400 @@ -13,6 +13,8 @@ from galaxy.visualization.tracks.data_providers import get_data_provider from galaxy.visualization.tracks.visual_analytics import get_tool_def from galaxy.security.validate_user_input import validate_username +from paste.httpexceptions import * +from galaxy.exceptions import * from Cheetah.Template import Template @@ -35,10 +37,11 @@ def __init__( self, app ): """Initialize an interface for application 'app'""" self.app = app + self.sa_session = app.model.context def get_toolbox(self): """Returns the application toolbox""" return self.app.toolbox - def get_class( self, trans, class_name ): + def get_class( self, class_name ): """ Returns the class object that a string denotes. Without this method, we'd have to do eval(<class_name>). """ if class_name == 'History': item_class = trans.model.History @@ -53,27 +56,144 @@ elif class_name == 'Tool': item_class = trans.model.Tool elif class_name == 'Job': - item_class == trans.model.Job + item_class = trans.model.Job + elif class_name == 'User': + item_class = trans.model.User + elif class_name == 'Group': + item_class = trans.model.Group + elif class_name == 'Role': + item_class = trans.model.Role + elif class_name == 'Quota': + item_class = trans.model.Quota + elif class_name == 'Library': + item_class = trans.model.Library + elif class_name == 'LibraryFolder': + item_class = trans.model.LibraryFolder + elif class_name == 'LibraryDatasetDatasetAssociation': + item_class = trans.model.LibraryDatasetDatasetAssociation + elif class_name == 'LibraryDataset': + item_class = trans.model.LibraryDataset else: item_class = None return item_class + def get_object( self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None ): + """ + Convenience method to get a model object with the specified checks. + """ + try: + decoded_id = trans.security.decode_id( id ) + except: + raise MessageException( "Malformed %s id ( %s ) specified, unable to decode" % ( class_name, str( id ) ), type='error' ) + try: + item_class = self.get_class( class_name ) + assert item_class is not None + item = trans.sa_session.query( item_class ).get( decoded_id ) + assert item is not None + except: + raise MessageException( "Invalid %s id ( %s ) specified" % ( class_name, display_id ), type="error" ) + if check_ownership or check_accessible: + self.security_check( trans, item, check_ownership, check_accessible, encoded_id ) + if deleted == True and not item.deleted: + raise ItemDeletionException( '%s "%s" is not deleted' % ( class_name, getattr( item, 'name', id ) ), type="warning" ) + elif deleted == False and item.deleted: + raise ItemDeletionException( '%s "%s" is deleted' % ( class_name, getattr( item, 'name', id ) ), type="warning" ) + return item + def get_user( self, trans, id, check_ownership=False, check_accessible=False, deleted=None ): + return self.get_object( trans, id, 'User', check_ownership=False, check_accessible=False, deleted=deleted ) + def get_group( self, trans, id, check_ownership=False, check_accessible=False, deleted=None ): + return self.get_object( trans, id, 'Group', check_ownership=False, check_accessible=False, deleted=deleted ) + def get_role( self, trans, id, check_ownership=False, check_accessible=False, deleted=None ): + return self.get_object( trans, id, 'Role', check_ownership=False, check_accessible=False, deleted=deleted ) + def encode_all_ids( self, trans, rval ): + """ + Encodes all integer values in the dict rval whose keys are 'id' or end with '_id' + + It might be useful to turn this in to a decorator + """ + if type( rval ) != dict: + return rval + for k, v in rval.items(): + if k == 'id' or k.endswith( '_id' ): + try: + rval[k] = trans.security.encode_id( v ) + except: + pass # probably already encoded + return rval Root = BaseController +class BaseUIController( BaseController ): + def get_object( self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None ): + try: + return BaseController.get_object( self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None ) + except MessageException, e: + raise # handled in the caller + except: + log.exception( "Execption in get_object check for %s %s:" % ( class_name, str( id ) ) ) + raise Exception( 'Server error retrieving %s id ( %s ).' % ( class_name, str( id ) ) ) + +class BaseAPIController( BaseController ): + def get_object( self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None ): + try: + return BaseController.get_object( self, trans, id, class_name, check_ownership=False, check_accessible=False, deleted=None ) + except ItemDeletionException, e: + raise HTTPBadRequest( detail="Invalid %s id ( %s ) specified" % ( class_name, str( id ) ) ) + except MessageException, e: + raise HTTPBadRequest( detail=e.err_msg ) + except Exception, e: + log.exception( "Execption in get_object check for %s %s:" % ( class_name, str( id ) ) ) + raise HTTPInternalServerError( comment=str( e ) ) + def validate_in_users_and_groups( self, trans, payload ): + """ + For convenience, in_users and in_groups can be encoded IDs or emails/group names in the API. + """ + def get_id( item, model_class, column ): + try: + return trans.security.decode_id( item ) + except: + pass # maybe an email/group name + # this will raise if the item is invalid + return trans.sa_session.query( model_class ).filter( column == item ).first().id + new_in_users = [] + new_in_groups = [] + invalid = [] + for item in util.listify( payload.get( 'in_users', [] ) ): + try: + new_in_users.append( get_id( item, trans.app.model.User, trans.app.model.User.table.c.email ) ) + except: + invalid.append( item ) + for item in util.listify( payload.get( 'in_groups', [] ) ): + try: + new_in_groups.append( get_id( item, trans.app.model.Group, trans.app.model.Group.table.c.name ) ) + except: + invalid.append( item ) + if invalid: + msg = "The following value(s) for associated users and/or groups could not be parsed: %s." % ', '.join( invalid ) + msg += " Valid values are email addresses of users, names of groups, or IDs of both." + raise Exception( msg ) + payload['in_users'] = map( str, new_in_users ) + payload['in_groups'] = map( str, new_in_groups ) + def not_implemented( self, trans, **kwd ): + raise HTTPNotImplemented() + class SharableItemSecurity: """ Mixin for handling security for sharable items. """ - def security_check( self, user, item, check_ownership=False, check_accessible=False ): + def security_check( self, trans, item, check_ownership=False, check_accessible=False ): """ Security checks for an item: checks if (a) user owns item or (b) item is accessible to user. """ if check_ownership: # Verify ownership. - if not user: - error( "Must be logged in to manage Galaxy items" ) - if item.user != user: - error( "%s is not owned by current user" % item.__class__.__name__ ) + if not trans.user: + raise ItemOwnershipException( "Must be logged in to manage Galaxy items", type='error' ) + if item.user != trans.user: + raise ItemOwnershipException( "%s is not owned by the current user" % item.__class__.__name__, type='error' ) if check_accessible: - # Verify accessible. - if ( item.user != user ) and ( not item.importable ) and ( user not in item.users_shared_with_dot_users ): - error( "%s is not accessible to current user" % item.__class__.__name__ ) + if type( item ) in ( trans.app.model.LibraryFolder, trans.app.model.LibraryDatasetDatasetAssociation, trans.app.model.LibraryDataset ): + if not ( trans.user_is_admin() or trans.app.security_agent.can_access_library_i9tem( trans.get_current_user_roles(), item, trans.user ) ): + raise ItemAccessibilityException( "%s is not accessible to the current user" % item.__class__.__name__, type='error' ) + else: + # Verify accessible. + if ( item.user != trans.user ) and ( not item.importable ) and ( trans.user not in item.users_shared_with_dot_users ): + raise ItemAccessibilityException( "%s is not accessible to the current user" % item.__class__.__name__, type='error' ) return item # @@ -95,7 +215,7 @@ except: data = None if not data: - raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable( "Invalid dataset id: %s." % str( dataset_id ) ) + raise HTTPRequestRangeNotSatisfiable( "Invalid dataset id: %s." % str( dataset_id ) ) if check_ownership: # Verify ownership. user = trans.get_user() @@ -111,6 +231,16 @@ else: error( "You are not allowed to access this dataset" ) return data + def get_history_dataset_association( self, trans, dataset_id, check_ownership=True, check_accessible=False ): + """Get a HistoryDatasetAssociation from the database by id, verifying ownership.""" + hda = self.get_object( trans, id, 'HistoryDatasetAssociation', check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted ) + self.security_check( trans, history, check_ownership=check_ownership, check_accessible=False ) # check accessibility here + if check_accessible: + if trans.app.security_agent.can_access_dataset( trans.get_current_user_roles(), hda.dataset ): + if hda.state == trans.model.Dataset.states.UPLOAD: + error( "Please wait until this dataset finishes uploading before attempting to view it." ) + else: + error( "You are not allowed to access this dataset" ) def get_data( self, dataset, preview=True ): """ Gets a dataset's data. """ # Get data from file, truncating if necessary. @@ -126,6 +256,21 @@ truncated = False return truncated, dataset_data +class UsesLibrary: + def get_library( self, trans, id, check_ownership=False, check_accessible=True ): + l = self.get_object( trans, id, 'Library' ) + if check_accessible and not ( trans.user_is_admin() or trans.app.security_agent.can_access_library( trans.get_current_user_roles(), l ) ): + error( "LibraryFolder is not accessible to the current user" ) + return l + +class UsesLibraryItems( SharableItemSecurity ): + def get_library_folder( self, trans, id, check_ownership=False, check_accessible=True ): + return self.get_object( trans, id, 'LibraryFolder', check_ownership=False, check_accessible=check_accessible ) + def get_library_dataset_dataset_association( self, trans, id, check_ownership=False, check_accessible=True ): + return self.get_object( trans, id, 'LibraryDatasetDatasetAssociation', check_ownership=False, check_accessible=check_accessible ) + def get_library_dataset( self, trans, id, check_ownership=False, check_accessible=True ): + return self.get_object( trans, id, 'LibraryDataset', check_ownership=False, check_accessible=check_accessible ) + class UsesVisualization( SharableItemSecurity ): """ Mixin for controllers that use Visualization objects. """ @@ -157,7 +302,7 @@ if not visualization: error( "Visualization not found" ) else: - return self.security_check( trans.get_user(), visualization, check_ownership, check_accessible ) + return self.security_check( trans, visualization, check_ownership, check_accessible ) def get_visualization_config( self, trans, visualization ): """ Returns a visualization's configuration. Only works for trackster visualizations right now. """ @@ -218,7 +363,7 @@ if not workflow: error( "Workflow not found" ) else: - return self.security_check( trans.get_user(), workflow, check_ownership, check_accessible ) + return self.security_check( trans, workflow, check_ownership, check_accessible ) def get_stored_workflow_steps( self, trans, stored_workflow ): """ Restores states for a stored workflow's steps. """ for step in stored_workflow.latest_workflow.steps: @@ -246,17 +391,10 @@ class UsesHistory( SharableItemSecurity ): """ Mixin for controllers that use History objects. """ - def get_history( self, trans, id, check_ownership=True, check_accessible=False ): + def get_history( self, trans, id, check_ownership=True, check_accessible=False, deleted=None ): """Get a History from the database by id, verifying ownership.""" - # Load history from database - try: - history = trans.sa_session.query( trans.model.History ).get( trans.security.decode_id( id ) ) - except TypeError: - history = None - if not history: - error( "History not found" ) - else: - return self.security_check( trans.get_user(), history, check_ownership, check_accessible ) + history = self.get_object( trans, id, 'History', check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted ) + return self.security_check( trans, history, check_ownership, check_accessible ) def get_history_datasets( self, trans, history, show_deleted=False, show_hidden=False, show_purged=False ): """ Returns history's datasets. """ query = trans.sa_session.query( trans.model.HistoryDatasetAssociation ) \ @@ -1113,6 +1251,10 @@ """ Return item based on id. """ raise "Unimplemented Method" +class UsesQuota( object ): + def get_quota( self, trans, id, check_ownership=False, check_accessible=False, deleted=None ): + return self.get_object( trans, id, 'Quota', check_ownership=False, check_accessible=False, deleted=deleted ) + """ Deprecated: `BaseController` used to be available under the name `Root` """ @@ -1524,538 +1666,6 @@ message=util.sanitize_text( message ), status='done' ) ) - # Galaxy Quota Stuff - @web.expose - @web.require_admin - def quotas( self, trans, **kwargs ): - if 'operation' in kwargs: - operation = kwargs['operation'].lower() - if operation == "quotas": - return self.quota( trans, **kwargs ) - if operation == "create": - return self.create_quota( trans, **kwargs ) - if operation == "delete": - return self.mark_quota_deleted( trans, **kwargs ) - if operation == "undelete": - return self.undelete_quota( trans, **kwargs ) - if operation == "purge": - return self.purge_quota( trans, **kwargs ) - if operation == "change amount": - return self.edit_quota( trans, **kwargs ) - if operation == "manage users and groups": - return self.manage_users_and_groups_for_quota( trans, **kwargs ) - if operation == "rename": - return self.rename_quota( trans, **kwargs ) - if operation == "edit": - return self.edit_quota( trans, **kwargs ) - # Render the list view - return self.quota_list_grid( trans, **kwargs ) - - @web.expose - @web.require_admin - def create_quota( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - name = util.restore_text( params.get( 'name', '' ) ) - description = util.restore_text( params.get( 'description', '' ) ) - amount = util.restore_text( params.get( 'amount', '' ).strip() ) - if amount.lower() in ( 'unlimited', 'none', 'no limit' ): - create_amount = None - else: - try: - create_amount = util.size_to_bytes( amount ) - except AssertionError: - create_amount = False - operation = params.get( 'operation', '' ) - default = params.get( 'default', 'no' ) - in_users = util.listify( params.get( 'in_users', [] ) ) - out_users = util.listify( params.get( 'out_users', [] ) ) - in_groups = util.listify( params.get( 'in_groups', [] ) ) - out_groups = util.listify( params.get( 'out_groups', [] ) ) - if params.get( 'create_quota_button', False ) or cntrller == 'api': - if not name or not description: - message = "Enter a valid name and a description." - status = 'error' - elif trans.sa_session.query( trans.app.model.Quota ).filter( trans.app.model.Quota.table.c.name==name ).first(): - message = "Quota names must be unique and a quota with that name already exists, so choose another name." - status = 'error' - elif not params.get( 'amount', None ): - message = "Enter a valid quota amount." - status = 'error' - elif create_amount is False: - message = "Unable to parse the provided amount." - status = 'error' - elif operation not in trans.app.model.Quota.valid_operations: - message = "Enter a valid operation." - status = 'error' - elif default != 'no' and default not in trans.app.model.DefaultQuotaAssociation.types.__dict__.values(): - message = "Enter a valid default type." - status = 'error' - elif default != 'no' and operation != '=': - message = "Operation for a default quota must be '='." - status = 'error' - operation = '=' - else: - # Create the quota - quota = trans.app.model.Quota( name=name, description=description, amount=create_amount, operation=operation ) - trans.sa_session.add( quota ) - # If this is a default quota, create the DefaultQuotaAssociation - if default != 'no': - trans.app.quota_agent.set_default_quota( default, quota ) - else: - # Create the UserQuotaAssociations - for user in [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in in_users ]: - uqa = trans.app.model.UserQuotaAssociation( user, quota ) - trans.sa_session.add( uqa ) - # Create the GroupQuotaAssociations - for group in [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in in_groups ]: - gqa = trans.app.model.GroupQuotaAssociation( group, quota ) - trans.sa_session.add( gqa ) - trans.sa_session.flush() - message = "Quota '%s' has been created with %d associated users and %d associated groups." % ( quota.name, len( in_users ), len( in_groups ) ) - if cntrller == 'api': - return 200, quota - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) - in_users = map( int, in_users ) - in_groups = map( int, in_groups ) - new_in_users = [] - new_in_groups = [] - for user in trans.sa_session.query( trans.app.model.User ) \ - .filter( trans.app.model.User.table.c.deleted==False ) \ - .order_by( trans.app.model.User.table.c.email ): - if user.id in in_users: - new_in_users.append( ( user.id, user.email ) ) - else: - out_users.append( ( user.id, user.email ) ) - for group in trans.sa_session.query( trans.app.model.Group ) \ - .filter( trans.app.model.Group.table.c.deleted==False ) \ - .order_by( trans.app.model.Group.table.c.name ): - if group.id in in_groups: - new_in_groups.append( ( group.id, group.name ) ) - else: - out_groups.append( ( group.id, group.name ) ) - if cntrller == 'api': - if status == 'error': - return 400, message - return 500, message # should never get here... - return trans.fill_template( '/admin/quota/quota_create.mako', - webapp=webapp, - name=name, - description=description, - amount=amount, - operation=operation, - default=default, - in_users=new_in_users, - out_users=out_users, - in_groups=new_in_groups, - out_groups=out_groups, - message=message, - status=status ) - - @web.expose - @web.require_admin - def rename_quota( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - id = params.get( 'id', None ) - error = True - try: - assert id, 'No quota ids received for renaming' - quota = get_quota( trans, id ) - assert quota, 'Quota id (%s) is invalid' % id - error = False - except AssertionError, e: - message = str( e ) - if error: - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - if params.get( 'rename_quota_button', False ) or cntrller == 'api': - new_name = util.restore_text( params.get( 'name', quota.name ) ) - new_description = util.restore_text( params.get( 'description', quota.description ) ) - if not new_name: - message = 'Enter a valid name' - status='error' - elif new_name != quota.name and trans.sa_session.query( trans.app.model.Quota ).filter( trans.app.model.Quota.table.c.name==new_name ).first(): - message = 'A quota with that name already exists' - status = 'error' - else: - old_name = quota.name - quota.name = new_name - quota.description = new_description - trans.sa_session.add( quota ) - trans.sa_session.flush() - message = "Quota '%s' has been renamed to '%s'" % ( old_name, new_name ) - if cntrller == 'api': - return 200, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) - if cntrller == 'api': - if status == 'error': - return 400, message - return 500, message # should never get here... - return trans.fill_template( '/admin/quota/quota_rename.mako', - quota=quota, - webapp=webapp, - message=message, - status=status ) - - @web.expose - @web.require_admin - def manage_users_and_groups_for_quota( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - id = params.get( 'id', None ) - error = True - try: - assert id, 'No quota ids received for managing users and groups' - quota = get_quota( trans, id ) - assert quota, 'Quota id (%s) is invalid' % id - assert not quota.default, 'Default quotas cannot be associated with specific users and groups' - error = False - except AssertionError, e: - message = str( e ) - if error: - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - if params.get( 'quota_members_edit_button', False ) or cntrller == 'api': - in_users = [ trans.sa_session.query( trans.app.model.User ).get( x ) for x in util.listify( params.in_users ) ] - in_groups = [ trans.sa_session.query( trans.app.model.Group ).get( x ) for x in util.listify( params.in_groups ) ] - trans.app.quota_agent.set_entity_quota_associations( quotas=[ quota ], users=in_users, groups=in_groups ) - trans.sa_session.refresh( quota ) - message = "Quota '%s' has been updated with %d associated users and %d associated groups" % ( quota.name, len( in_users ), len( in_groups ) ) - if cntrller == 'api': - return 200, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status=status ) ) - # api cannot get to here - in_users = [] - out_users = [] - in_groups = [] - out_groups = [] - for user in trans.sa_session.query( trans.app.model.User ) \ - .filter( trans.app.model.User.table.c.deleted==False ) \ - .order_by( trans.app.model.User.table.c.email ): - if user in [ x.user for x in quota.users ]: - in_users.append( ( user.id, user.email ) ) - else: - out_users.append( ( user.id, user.email ) ) - for group in trans.sa_session.query( trans.app.model.Group ) \ - .filter( trans.app.model.Group.table.c.deleted==False ) \ - .order_by( trans.app.model.Group.table.c.name ): - if group in [ x.group for x in quota.groups ]: - in_groups.append( ( group.id, group.name ) ) - else: - out_groups.append( ( group.id, group.name ) ) - return trans.fill_template( '/admin/quota/quota.mako', - quota=quota, - in_users=in_users, - out_users=out_users, - in_groups=in_groups, - out_groups=out_groups, - webapp=webapp, - message=message, - status=status ) - - @web.expose - @web.require_admin - def edit_quota( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - id = params.get( 'id', None ) - if not id: - message = "No quota ids received for editing" - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - quota = get_quota( trans, id ) - if params.get( 'edit_quota_button', False ) or cntrller == 'api': - amount = util.restore_text( params.get( 'amount', '' ).strip() ) - if amount.lower() in ( 'unlimited', 'none', 'no limit' ): - new_amount = None - else: - try: - new_amount = util.size_to_bytes( amount ) - except AssertionError: - new_amount = False - operation = params.get( 'operation', None ) - if not params.get( 'amount', None ): - message = 'Enter a valid amount' - status='error' - elif new_amount is False: - message = 'Unable to parse the provided amount' - status = 'error' - elif operation not in trans.app.model.Quota.valid_operations: - message = 'Enter a valid operation' - status = 'error' - else: - quota.amount = new_amount - quota.operation = operation - trans.sa_session.add( quota ) - trans.sa_session.flush() - message = "Quota '%s' is now '%s'" % ( quota.name, quota.operation + quota.display_amount ) - if cntrller == 'api': - return 200, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) - if cntrller == 'api': - if status == 'error': - return 400, message - return 500, message # should never get here... - return trans.fill_template( '/admin/quota/quota_edit.mako', - quota=quota, - webapp=webapp, - message=message, - status=status ) - - @web.expose - @web.require_admin - def set_quota_default( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - default = params.get( 'default', '' ) - id = params.get( 'id', None ) - if not id: - message = "No quota ids received for managing defaults" - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - quota = get_quota( trans, id ) - if params.get( 'set_default_quota_button', False ) or cntrller == 'api': - if default != 'no' and default not in trans.app.model.DefaultQuotaAssociation.types.__dict__.values(): - message = "Enter a valid default type." - status = 'error' - else: - if default != 'no': - trans.app.quota_agent.set_default_quota( default, quota ) - message = "Quota '%s' is now the default for %s users" % ( quota.name, default ) - if cntrller == 'api': - return 200, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) - if not default: - default = 'no' - if cntrller == 'api': - if status == 'error': - return 400, message - return 500, message # should never get here... - return trans.fill_template( '/admin/quota/quota_set_default.mako', - quota=quota, - webapp=webapp, - default=default, - message=message, - status=status ) - @web.expose - @web.require_admin - def unset_quota_default( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - message = util.restore_text( params.get( 'message', '' ) ) - status = params.get( 'status', 'done' ) - default = params.get( 'default', '' ) - id = params.get( 'id', None ) - if not id: - message = "No quota ids received for managing defaults" - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - quota = get_quota( trans, id ) - if not quota.default: - message = "Quota '%s' is not a default." % quota.name - status = 'error' - else: - message = "Quota '%s' is no longer the default for %s users." % ( quota.name, quota.default[0].type ) - status = 'done' - for dqa in quota.default: - trans.sa_session.delete( dqa ) - trans.sa_session.flush() - if cntrller == 'api': - if status == 'done': - return 200, message - elif status == 'error': - return 400, message - return 500, message # should never get here... - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status=status ) ) - - @web.expose - @web.require_admin - def mark_quota_deleted( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - id = kwd.get( 'id', None ) - ids = util.listify( id ) - error = True - quotas = [] - try: - assert id, 'No quota ids received for deleting' - for quota_id in ids: - quota = get_quota( trans, quota_id ) - assert quota, 'Quota id (%s) is invalid' % id - assert not quota.default, "Quota '%s' is a default, please unset it as a default before deleting it" % ( quota.name ) - quotas.append( quota ) - error = False - except AssertionError, e: - message = str( e ) - if error: - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - message = "Deleted %d quotas: " % len( ids ) - for quota in quotas: - quota.deleted = True - trans.sa_session.add( quota ) - message += " %s " % quota.name - trans.sa_session.flush() - if cntrller == 'api': - return 200, message - trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) - @web.expose - @web.require_admin - def undelete_quota( self, trans, cntrller='admin', **kwd ): - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - id = kwd.get( 'id', None ) - ids = util.listify( id ) - error = True - quotas = [] - try: - assert id, 'No quota ids received for undeleting' - for quota_id in ids: - quota = get_quota( trans, quota_id ) - assert quota, 'Quota id (%s) is invalid' % id - assert quota.deleted, "Quota '%s' has not been deleted, so it cannot be undeleted." % quota.name - quotas.append( quota ) - error = False - except AssertionError, e: - message = str( e ) - if error: - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - message = "Undeleted %d quotas: " % len( ids ) - for quota in quotas: - quota.deleted = False - trans.sa_session.add( quota ) - trans.sa_session.flush() - message += " %s " % quota.name - if cntrller == 'api': - return 200, message - trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) - @web.expose - @web.require_admin - def purge_quota( self, trans, cntrller='admin', **kwd ): - # This method should only be called for a Quota that has previously been deleted. - # Purging a deleted Quota deletes all of the following from the database: - # - UserQuotaAssociations where quota_id == Quota.id - # - GroupQuotaAssociations where quota_id == Quota.id - params = util.Params( kwd ) - webapp = params.get( 'webapp', 'galaxy' ) - id = kwd.get( 'id', None ) - ids = util.listify( id ) - error = True - quotas = [] - try: - assert id, 'No quota ids received for undeleting' - for quota_id in ids: - quota = get_quota( trans, quota_id ) - assert quota, 'Quota id (%s) is invalid' % id - assert quota.deleted, "Quota '%s' has not been deleted, so it cannot be purged." % quota.name - quotas.append( quota ) - error = False - except AssertionError, e: - message = str( e ) - if error: - if cntrller == 'api': - return 400, message - return trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=message, - status='error' ) ) - message = "Purged %d quotas: " % len( ids ) - for quota in quotas: - # Delete UserQuotaAssociations - for uqa in quota.users: - trans.sa_session.delete( uqa ) - # Delete GroupQuotaAssociations - for gqa in quota.groups: - trans.sa_session.delete( gqa ) - trans.sa_session.flush() - message += " %s " % quota.name - if cntrller == 'api': - return 200, message - trans.response.send_redirect( web.url_for( controller='admin', - action='quotas', - webapp=webapp, - message=util.sanitize_text( message ), - status='done' ) ) # Galaxy Group Stuff @web.expose @web.require_admin --- a/lib/galaxy/web/buildapp.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/buildapp.py Thu Sep 22 17:13:53 2011 -0400 @@ -22,12 +22,12 @@ import galaxy.datatypes.registry import galaxy.web.framework -def add_controllers( webapp, app ): +def add_ui_controllers( webapp, app ): """ Search for controllers in the 'galaxy.web.controllers' module and add them to the webapp. """ - from galaxy.web.base.controller import BaseController + from galaxy.web.base.controller import BaseUIController from galaxy.web.base.controller import ControllerUnavailable import galaxy.web.controllers controller_dir = galaxy.web.controllers.__path__[0] @@ -45,11 +45,11 @@ # Look for a controller inside the modules for key in dir( module ): T = getattr( module, key ) - if isclass( T ) and T is not BaseController and issubclass( T, BaseController ): - webapp.add_controller( name, T( app ) ) + if isclass( T ) and T is not BaseUIController and issubclass( T, BaseUIController ): + webapp.add_ui_controller( name, T( app ) ) def add_api_controllers( webapp, app ): - from galaxy.web.base.controller import BaseController + from galaxy.web.base.controller import BaseAPIController from galaxy.web.base.controller import ControllerUnavailable import galaxy.web.api controller_dir = galaxy.web.api.__path__[0] @@ -66,7 +66,7 @@ module = getattr( module, comp ) for key in dir( module ): T = getattr( module, key ) - if isclass( T ) and T is not BaseController and issubclass( T, BaseController ): + if isclass( T ) and T is not BaseAPIController and issubclass( T, BaseAPIController ): webapp.add_api_controller( name, T( app ) ) def app_factory( global_conf, **kwargs ): @@ -87,7 +87,7 @@ atexit.register( app.shutdown ) # Create the universe WSGI application webapp = galaxy.web.framework.WebApplication( app, session_cookie='galaxysession' ) - add_controllers( webapp, app ) + add_ui_controllers( webapp, app ) # Force /history to go to /root/history -- needed since the tests assume this webapp.add_route( '/history', controller='root', action='history' ) # These two routes handle our simple needs at the moment @@ -129,9 +129,9 @@ webapp.api_mapper.resource( 'request_type', 'request_types', path_prefix='/api' ) webapp.api_mapper.resource( 'role', 'roles', path_prefix='/api' ) webapp.api_mapper.resource_with_deleted( 'quota', 'quotas', path_prefix='/api' ) - webapp.api_mapper.resource( 'user', 'users', path_prefix='/api' ) + webapp.api_mapper.resource_with_deleted( 'user', 'users', path_prefix='/api' ) webapp.api_mapper.resource( 'workflow', 'workflows', path_prefix='/api' ) - webapp.api_mapper.resource( 'history', 'histories', path_prefix='/api' ) + webapp.api_mapper.resource_with_deleted( 'history', 'histories', path_prefix='/api' ) #webapp.api_mapper.connect( 'run_workflow', '/api/workflow/{workflow_id}/library/{library_id}', controller='workflows', action='run', workflow_id=None, library_id=None, conditions=dict(method=["GET"]) ) webapp.finalize_config() --- a/lib/galaxy/web/controllers/admin.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/admin.py Thu Sep 22 17:13:53 2011 -0400 @@ -5,6 +5,10 @@ import logging log = logging.getLogger( __name__ ) +from galaxy.actions.admin import AdminActions +from galaxy.web.params import QuotaParamParser +from galaxy.exceptions import * + class UserListGrid( grids.Grid ): class EmailColumn( grids.TextColumn ): def get_value( self, trans, grid, user ): @@ -344,7 +348,7 @@ allow_multiple=False, url_args=dict( webapp="galaxy", action="edit_quota" ) ), grids.GridOperation( "Manage users and groups", - condition=( lambda item: not item.default ), + condition=( lambda item: not item.default and not item.deleted ), allow_multiple=False, url_args=dict( webapp="galaxy", action="manage_users_and_groups_for_quota" ) ), grids.GridOperation( "Set as different type of default", @@ -352,11 +356,11 @@ allow_multiple=False, url_args=dict( webapp="galaxy", action="set_quota_default" ) ), grids.GridOperation( "Set as default", - condition=( lambda item: not item.default ), + condition=( lambda item: not item.default and not item.deleted ), allow_multiple=False, url_args=dict( webapp="galaxy", action="set_quota_default" ) ), grids.GridOperation( "Unset as default", - condition=( lambda item: item.default ), + condition=( lambda item: item.default and not item.deleted ), allow_multiple=False, url_args=dict( webapp="galaxy", action="unset_quota_default" ) ), grids.GridOperation( "Delete", @@ -380,9 +384,252 @@ preserve_state = False use_paging = True -class AdminGalaxy( BaseController, Admin ): +class AdminGalaxy( BaseUIController, Admin, AdminActions, QuotaParamParser ): user_list_grid = UserListGrid() role_list_grid = RoleListGrid() group_list_grid = GroupListGrid() quota_list_grid = QuotaListGrid() + + # Galaxy Quota Stuff + @web.expose + @web.require_admin + def quotas( self, trans, **kwargs ): + if 'operation' in kwargs: + operation = kwargs.pop('operation').lower() + if operation == "quotas": + return self.quota( trans, **kwargs ) + if operation == "create": + return self.create_quota( trans, **kwargs ) + if operation == "delete": + return self.mark_quota_deleted( trans, **kwargs ) + if operation == "undelete": + return self.undelete_quota( trans, **kwargs ) + if operation == "purge": + return self.purge_quota( trans, **kwargs ) + if operation == "change amount": + return self.edit_quota( trans, **kwargs ) + if operation == "manage users and groups": + return self.manage_users_and_groups_for_quota( trans, **kwargs ) + if operation == "rename": + return self.rename_quota( trans, **kwargs ) + if operation == "edit": + return self.edit_quota( trans, **kwargs ) + # Render the list view + return self.quota_list_grid( trans, **kwargs ) + + @web.expose + @web.require_admin + def create_quota( self, trans, **kwd ): + params = self.get_quota_params( kwd ) + if params.get( 'create_quota_button', False ): + try: + quota, message = self._create_quota( trans, params ) + return trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( message ), + status='done' ) ) + except MessageException, e: + params.message = str( e ) + params.status = 'error' + in_users = map( int, params.in_users ) + in_groups = map( int, params.in_groups ) + new_in_users = [] + new_in_groups = [] + for user in trans.sa_session.query( trans.app.model.User ) \ + .filter( trans.app.model.User.table.c.deleted==False ) \ + .order_by( trans.app.model.User.table.c.email ): + if user.id in in_users: + new_in_users.append( ( user.id, user.email ) ) + else: + params.out_users.append( ( user.id, user.email ) ) + for group in trans.sa_session.query( trans.app.model.Group ) \ + .filter( trans.app.model.Group.table.c.deleted==False ) \ + .order_by( trans.app.model.Group.table.c.name ): + if group.id in in_groups: + new_in_groups.append( ( group.id, group.name ) ) + else: + params.out_groups.append( ( group.id, group.name ) ) + return trans.fill_template( '/admin/quota/quota_create.mako', + webapp=params.webapp, + name=params.name, + description=params.description, + amount=params.amount, + operation=params.operation, + default=params.default, + in_users=new_in_users, + out_users=params.out_users, + in_groups=new_in_groups, + out_groups=params.out_groups, + message=params.message, + status=params.status ) + + @web.expose + @web.require_admin + def rename_quota( self, trans, **kwd ): + quota, params = self._quota_op( trans, 'rename_quota_button', self._rename_quota, kwd ) + if not quota: + return + return trans.fill_template( '/admin/quota/quota_rename.mako', + id=params.id, + name=params.name or quota.name, + description=params.description or quota.description, + webapp=params.webapp, + message=params.message, + status=params.status ) + + @web.expose + @web.require_admin + def manage_users_and_groups_for_quota( self, trans, **kwd ): + quota, params = self._quota_op( trans, 'quota_members_edit_button', self._manage_users_and_groups_for_quota, kwd ) + if not quota: + return + in_users = [] + out_users = [] + in_groups = [] + out_groups = [] + for user in trans.sa_session.query( trans.app.model.User ) \ + .filter( trans.app.model.User.table.c.deleted==False ) \ + .order_by( trans.app.model.User.table.c.email ): + if user in [ x.user for x in quota.users ]: + in_users.append( ( user.id, user.email ) ) + else: + out_users.append( ( user.id, user.email ) ) + for group in trans.sa_session.query( trans.app.model.Group ) \ + .filter( trans.app.model.Group.table.c.deleted==False ) \ + .order_by( trans.app.model.Group.table.c.name ): + if group in [ x.group for x in quota.groups ]: + in_groups.append( ( group.id, group.name ) ) + else: + out_groups.append( ( group.id, group.name ) ) + return trans.fill_template( '/admin/quota/quota.mako', + id=params.id, + name=quota.name, + in_users=in_users, + out_users=out_users, + in_groups=in_groups, + out_groups=out_groups, + webapp=params.webapp, + message=params.message, + status=params.status ) + + @web.expose + @web.require_admin + def edit_quota( self, trans, **kwd ): + quota, params = self._quota_op( trans, 'edit_quota_button', self._edit_quota, kwd ) + if not quota: + return + return trans.fill_template( '/admin/quota/quota_edit.mako', + id=params.id, + operation=params.operation or quota.operation, + display_amount=params.amount or quota.display_amount, + webapp=params.webapp, + message=params.message, + status=params.status ) + + @web.expose + @web.require_admin + def set_quota_default( self, trans, **kwd ): + quota, params = self._quota_op( trans, 'set_default_quota_button', self._set_quota_default, kwd ) + if not quota: + return + if params.default: + default = params.default + elif quota.default: + default = quota.default[0].type + else: + default = "no" + return trans.fill_template( '/admin/quota/quota_set_default.mako', + id=params.id, + default=default, + webapp=params.webapp, + message=params.message, + status=params.status ) + + @web.expose + @web.require_admin + def unset_quota_default( self, trans, **kwd ): + quota, params = self._quota_op( trans, True, self._unset_quota_default, kwd ) + if not quota: + return + return trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( params.message ), + status='error' ) ) + + + @web.expose + @web.require_admin + def mark_quota_deleted( self, trans, **kwd ): + quota, params = self._quota_op( trans, True, self._mark_quota_deleted, kwd, listify=True ) + if not quota: + return + return trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( params.message ), + status='error' ) ) + + @web.expose + @web.require_admin + def undelete_quota( self, trans, **kwd ): + quota, params = self._quota_op( trans, True, self._undelete_quota, kwd, listify=True ) + if not quota: + return + return trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( params.message ), + status='error' ) ) + + @web.expose + @web.require_admin + def purge_quota( self, trans, **kwd ): + quota, params = self._quota_op( trans, True, self._purge_quota, kwd, listify=True ) + if not quota: + return + return trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( params.message ), + status='error' ) ) + + def _quota_op( self, trans, do_op, op_method, kwd, listify=False ): + params = self.get_quota_params( kwd ) + if listify: + quota = [] + messages = [] + for id in util.listify( params.id ): + try: + quota.append( self.get_quota( trans, id ) ) + except MessageException, e: + messages.append( str( e ) ) + if messages: + return None, trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( ', '.join( messages ) ), + status='error' ) ) + else: + try: + quota = self.get_quota( trans, params.id, deleted=False ) + except MessageException, e: + return None, trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( str( e ) ), + status='error' ) ) + if do_op == True or ( do_op != False and params.get( do_op, False ) ): + try: + message = op_method( quota, params ) + return None, trans.response.send_redirect( web.url_for( controller='admin', + action='quotas', + webapp=params.webapp, + message=util.sanitize_text( message ), + status='done' ) ) + except MessageException, e: + params.message = e.err_msg + params.status = e.type + return quota, params --- a/lib/galaxy/web/controllers/async.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/async.py Thu Sep 22 17:13:53 2011 -0400 @@ -11,7 +11,7 @@ log = logging.getLogger( __name__ ) -class ASync( BaseController ): +class ASync( BaseUIController ): @web.expose def default(self, trans, tool_id=None, data_id=None, data_secret=None, **kwd): --- a/lib/galaxy/web/controllers/dataset.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/dataset.py Thu Sep 22 17:13:53 2011 -0400 @@ -145,7 +145,7 @@ .filter( model.History.deleted==False ) \ .filter( self.model_class.visible==True ) -class DatasetInterface( BaseController, UsesAnnotations, UsesHistory, UsesHistoryDatasetAssociation, UsesItemRatings ): +class DatasetInterface( BaseUIController, UsesAnnotations, UsesHistory, UsesHistoryDatasetAssociation, UsesItemRatings ): stored_list_grid = HistoryDatasetAssociationListGrid() --- a/lib/galaxy/web/controllers/error.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/error.py Thu Sep 22 17:13:53 2011 -0400 @@ -1,6 +1,6 @@ from galaxy.web.base.controller import * -class Error( BaseController ): +class Error( BaseUIController ): @web.expose def index( self, trans ): raise Exception, "Fake error" \ No newline at end of file --- a/lib/galaxy/web/controllers/external_service.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/external_service.py Thu Sep 22 17:13:53 2011 -0400 @@ -63,7 +63,7 @@ grids.GridAction( "Create new external service", dict( controller='external_service', action='create_external_service' ) ) ] -class ExternalService( BaseController, UsesFormDefinitions ): +class ExternalService( BaseUIController, UsesFormDefinitions ): external_service_grid = ExternalServiceGrid() @web.expose --- a/lib/galaxy/web/controllers/external_services.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/external_services.py Thu Sep 22 17:13:53 2011 -0400 @@ -9,7 +9,7 @@ for model_class in [Sample]: class_name_to_class[ model_class.__name__ ] = model_class -class ExternalServiceController( BaseController ): +class ExternalServiceController( BaseUIController ): @web.expose @web.require_admin def access_action( self, trans, external_service_action, item, item_type, **kwd ): --- a/lib/galaxy/web/controllers/forms.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/forms.py Thu Sep 22 17:13:53 2011 -0400 @@ -66,7 +66,7 @@ grids.GridAction( "Create new form", dict( controller='forms', action='create_form_definition' ) ) ] -class Forms( BaseController ): +class Forms( BaseUIController ): # Empty TextField empty_field = { 'name': '', 'label': '', --- a/lib/galaxy/web/controllers/history.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/history.py Thu Sep 22 17:13:53 2011 -0400 @@ -175,7 +175,7 @@ # A public history is published, has a slug, and is not deleted. return query.filter( self.model_class.published == True ).filter( self.model_class.slug != None ).filter( self.model_class.deleted == False ) -class HistoryController( BaseController, Sharable, UsesAnnotations, UsesItemRatings, UsesHistory ): +class HistoryController( BaseUIController, Sharable, UsesAnnotations, UsesItemRatings, UsesHistory ): @web.expose def index( self, trans ): return "" @@ -701,7 +701,7 @@ if not import_history: return trans.show_error_message( "The specified history does not exist.<br>You can %s." % referer_message, use_panels=True ) # History is importable if user is admin or it's accessible. TODO: probably want to have app setting to enable admin access to histories. - if not trans.user_is_admin() and not self.security_check( user, import_history, check_ownership=False, check_accessible=True ): + if not trans.user_is_admin() and not self.security_check( trans, import_history, check_ownership=False, check_accessible=True ): return trans.show_error_message( "You cannot access this history.<br>You can %s." % referer_message, use_panels=True ) if user: #dan: I can import my own history. @@ -780,7 +780,7 @@ if history is None: raise web.httpexceptions.HTTPNotFound() # Security check raises error if user cannot access history. - self.security_check( trans.get_user(), history, False, True) + self.security_check( trans, history, False, True) # Get datasets. datasets = self.get_history_datasets( trans, history ) --- a/lib/galaxy/web/controllers/library.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/library.py Thu Sep 22 17:13:53 2011 -0400 @@ -65,7 +65,7 @@ # public libraries and restricted libraries accessible by the current user. return query.filter( or_( not_( trans.model.Library.table.c.id.in_( restricted_library_ids ) ), trans.model.Library.table.c.id.in_( accessible_restricted_library_ids ) ) ) -class Library( BaseController ): +class Library( BaseUIController ): library_list_grid = LibraryListGrid() --- a/lib/galaxy/web/controllers/library_admin.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/library_admin.py Thu Sep 22 17:13:53 2011 -0400 @@ -69,7 +69,7 @@ preserve_state = False use_paging = True -class LibraryAdmin( BaseController ): +class LibraryAdmin( BaseUIController ): library_list_grid = LibraryListGrid() --- a/lib/galaxy/web/controllers/library_common.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/library_common.py Thu Sep 22 17:13:53 2011 -0400 @@ -68,7 +68,7 @@ pass os.rmdir( tmpd ) -class LibraryCommon( BaseController, UsesFormDefinitions ): +class LibraryCommon( BaseUIController, UsesFormDefinitions ): @web.json def library_item_updates( self, trans, ids=None, states=None ): # Avoid caching --- a/lib/galaxy/web/controllers/mobile.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/mobile.py Thu Sep 22 17:13:53 2011 -0400 @@ -1,6 +1,6 @@ from galaxy.web.base.controller import * -class Mobile( BaseController ): +class Mobile( BaseUIController ): @web.expose def index( self, trans, **kwargs ): return trans.fill_template( "mobile/index.mako" ) --- a/lib/galaxy/web/controllers/page.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/page.py Thu Sep 22 17:13:53 2011 -0400 @@ -272,7 +272,7 @@ # Default behavior: _BaseHTMLProcessor.unknown_endtag( self, tag ) -class PageController( BaseController, Sharable, UsesAnnotations, UsesHistory, +class PageController( BaseUIController, Sharable, UsesAnnotations, UsesHistory, UsesStoredWorkflow, UsesHistoryDatasetAssociation, UsesVisualization, UsesItemRatings ): _page_list = PageListGrid() @@ -533,7 +533,7 @@ annotations = from_json_string( annotations ) for annotation_dict in annotations: item_id = trans.security.decode_id( annotation_dict[ 'item_id' ] ) - item_class = self.get_class( trans, annotation_dict[ 'item_class' ] ) + item_class = self.get_class( annotation_dict[ 'item_class' ] ) item = trans.sa_session.query( item_class ).filter_by( id=item_id ).first() if not item: raise RuntimeError( "cannot find annotated item" ) @@ -582,7 +582,7 @@ if page is None: raise web.httpexceptions.HTTPNotFound() # Security check raises error if user cannot access page. - self.security_check( trans.get_user(), page, False, True) + self.security_check( trans, page, False, True) # Process page content. processor = _PageContentProcessor( trans, 'utf-8', 'text/html', self._get_embed_html ) @@ -723,14 +723,14 @@ if not page: err+msg( "Page not found" ) else: - return self.security_check( trans.get_user(), page, check_ownership, check_accessible ) + return self.security_check( trans, page, check_ownership, check_accessible ) def get_item( self, trans, id ): return self.get_page( trans, id ) def _get_embed_html( self, trans, item_class, item_id ): """ Returns HTML for embedding an item in a page. """ - item_class = self.get_class( trans, item_class ) + item_class = self.get_class( item_class ) if item_class == model.History: history = self.get_history( trans, item_id, False, True ) history.annotation = self.get_item_annotation_str( trans.sa_session, history.user, history ) --- a/lib/galaxy/web/controllers/request_type.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/request_type.py Thu Sep 22 17:13:53 2011 -0400 @@ -72,7 +72,7 @@ grids.GridAction( "Create new request type", dict( controller='request_type', action='create_request_type' ) ) ] -class RequestType( BaseController, UsesFormDefinitions ): +class RequestType( BaseUIController, UsesFormDefinitions ): request_type_grid = RequestTypeGrid() @web.expose --- a/lib/galaxy/web/controllers/requests.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/requests.py Thu Sep 22 17:13:53 2011 -0400 @@ -15,7 +15,7 @@ def apply_query_filter( self, trans, query, **kwd ): return query.filter_by( user=trans.user ) -class Requests( BaseController ): +class Requests( BaseUIController ): request_grid = UserRequestsGrid() @web.expose --- a/lib/galaxy/web/controllers/requests_admin.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/requests_admin.py Thu Sep 22 17:13:53 2011 -0400 @@ -94,7 +94,7 @@ return query return query.filter_by( sample_id=trans.security.decode_id( sample_id ) ) -class RequestsAdmin( BaseController, UsesFormDefinitions ): +class RequestsAdmin( BaseUIController, UsesFormDefinitions ): request_grid = AdminRequestsGrid() datatx_grid = DataTransferGrid() --- a/lib/galaxy/web/controllers/requests_common.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/requests_common.py Thu Sep 22 17:13:53 2011 -0400 @@ -93,7 +93,7 @@ confirm="Samples cannot be added to this request after it is submitted. Click OK to submit." ) ] -class RequestsCommon( BaseController, UsesFormDefinitions ): +class RequestsCommon( BaseUIController, UsesFormDefinitions ): @web.json def sample_state_updates( self, trans, ids=None, states=None ): # Avoid caching --- a/lib/galaxy/web/controllers/root.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/root.py Thu Sep 22 17:13:53 2011 -0400 @@ -11,7 +11,7 @@ log = logging.getLogger( __name__ ) -class RootController( BaseController, UsesHistory, UsesAnnotations ): +class RootController( BaseUIController, UsesHistory, UsesAnnotations ): @web.expose def default(self, trans, target1=None, target2=None, **kwd): --- a/lib/galaxy/web/controllers/tag.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/tag.py Thu Sep 22 17:13:53 2011 -0400 @@ -8,9 +8,9 @@ log = logging.getLogger( __name__ ) -class TagsController ( BaseController ): +class TagsController ( BaseUIController ): def __init__( self, app ): - BaseController.__init__( self, app ) + BaseUIController.__init__( self, app ) self.tag_handler = app.tag_handler @web.expose @web.require_login( "edit item tags" ) @@ -72,7 +72,7 @@ if item_id is not None: item = self._get_item( trans, item_class, trans.security.decode_id( item_id ) ) user = trans.user - item_class = self.get_class( trans, item_class ) + item_class = self.get_class( item_class ) q = q.encode( 'utf-8' ) if q.find( ":" ) == -1: return self._get_tag_autocomplete_names( trans, q, limit, timestamp, user, item, item_class ) --- a/lib/galaxy/web/controllers/tool_runner.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/tool_runner.py Thu Sep 22 17:13:53 2011 -0400 @@ -17,7 +17,7 @@ self.debug = None self.from_noframe = None -class ToolRunner( BaseController ): +class ToolRunner( BaseUIController ): #Hack to get biomart to work, ideally, we could pass tool_id to biomart and receive it back @web.expose --- a/lib/galaxy/web/controllers/tracks.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/tracks.py Thu Sep 22 17:13:53 2011 -0400 @@ -162,7 +162,7 @@ def apply_query_filter( self, trans, query, **kwargs ): return query.filter( self.model_class.user_id == trans.user.id ) -class TracksController( BaseController, UsesVisualization, UsesHistoryDatasetAssociation ): +class TracksController( BaseUIController, UsesVisualization, UsesHistoryDatasetAssociation ): """ Controller for track browser interface. Handles building a new browser from datasets in the current history, and display of the resulting browser. --- a/lib/galaxy/web/controllers/ucsc_proxy.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/ucsc_proxy.py Thu Sep 22 17:13:53 2011 -0400 @@ -11,7 +11,7 @@ log = logging.getLogger( __name__ ) -class UCSCProxy( BaseController ): +class UCSCProxy( BaseUIController ): def create_display(self, store): """Creates a more meaningulf display name""" --- a/lib/galaxy/web/controllers/user.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/user.py Thu Sep 22 17:13:53 2011 -0400 @@ -49,7 +49,7 @@ def build_initial_query( self, trans, **kwd ): return trans.sa_session.query( self.model_class ).filter( self.model_class.user_id == trans.user.id ) -class User( BaseController, UsesFormDefinitions ): +class User( BaseUIController, UsesFormDefinitions ): user_openid_grid = UserOpenIDGrid() installed_len_files = None --- a/lib/galaxy/web/controllers/visualization.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/visualization.py Thu Sep 22 17:13:53 2011 -0400 @@ -68,7 +68,7 @@ return query.filter( self.model_class.deleted==False ).filter( self.model_class.published==True ) -class VisualizationController( BaseController, Sharable, UsesAnnotations, +class VisualizationController( BaseUIController, Sharable, UsesAnnotations, UsesHistoryDatasetAssociation, UsesVisualization, UsesItemRatings ): _user_list_grid = VisualizationListGrid() @@ -299,7 +299,7 @@ raise web.httpexceptions.HTTPNotFound() # Security check raises error if user cannot access visualization. - self.security_check( trans.get_user(), visualization, False, True) + self.security_check( trans, visualization, False, True) # Get rating data. user_item_rating = 0 @@ -453,4 +453,4 @@ def get_item( self, trans, id ): return self.get_visualization( trans, id ) - \ No newline at end of file + --- a/lib/galaxy/web/controllers/workflow.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/controllers/workflow.py Thu Sep 22 17:13:53 2011 -0400 @@ -103,7 +103,7 @@ if self.cur_tag == self.target_tag: self.tag_content += text -class WorkflowController( BaseController, Sharable, UsesStoredWorkflow, UsesAnnotations, UsesItemRatings ): +class WorkflowController( BaseUIController, Sharable, UsesStoredWorkflow, UsesAnnotations, UsesItemRatings ): stored_list_grid = StoredWorkflowListGrid() published_list_grid = StoredWorkflowAllPublishedGrid() @@ -199,7 +199,7 @@ if stored_workflow is None: raise web.httpexceptions.HTTPNotFound() # Security check raises error if user cannot access workflow. - self.security_check( trans.get_user(), stored_workflow, False, True) + self.security_check( trans, stored_workflow, False, True) # Get data for workflow's steps. self.get_stored_workflow_steps( trans, stored_workflow ) @@ -1016,7 +1016,7 @@ id = trans.security.decode_id( id ) trans.workflow_building_mode = True stored = trans.sa_session.query( model.StoredWorkflow ).get( id ) - self.security_check( trans.get_user(), stored, False, True ) + self.security_check( trans, stored, False, True ) # Convert workflow to dict. workflow_dict = self._workflow_to_dict( trans, stored ) --- a/lib/galaxy/web/framework/__init__.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/framework/__init__.py Thu Sep 22 17:13:53 2011 -0400 @@ -10,6 +10,7 @@ import base import pickle from galaxy import util +from galaxy.exceptions import MessageException from galaxy.util.json import to_json_string, from_json_string pkg_resources.require( "simplejson" ) @@ -19,6 +20,7 @@ pkg_resources.require( "PasteDeploy" ) from paste.deploy.converters import asbool +import paste.httpexceptions pkg_resources.require( "Mako" ) import mako.template @@ -103,6 +105,9 @@ except NoResultFound: error_message = 'Provided API key is not valid.' return error + if provided_key.user.deleted: + error_message = 'User account is deactivated, please contact an administrator.' + return error newest_key = provided_key.user.api_keys[0] if newest_key.key != provided_key.key: error_message = 'Provided API key has expired.' @@ -135,10 +140,16 @@ trans.response.status = 400 return "That user does not exist." - if trans.debug: - return simplejson.dumps( func( self, trans, *args, **kwargs ), indent=4, sort_keys=True ) - else: - return simplejson.dumps( func( self, trans, *args, **kwargs ) ) + try: + if trans.debug: + return simplejson.dumps( func( self, trans, *args, **kwargs ), indent=4, sort_keys=True ) + else: + return simplejson.dumps( func( self, trans, *args, **kwargs ) ) + except paste.httpexceptions.HTTPException: + raise # handled + except: + log.exception( 'Uncaught exception in exposed API method:' ) + raise paste.httpexceptions.HTTPServerError() if not hasattr(func, '_orig'): decorator._orig = func decorator.exposed = True @@ -164,14 +175,6 @@ NOT_SET = object() -class MessageException( Exception ): - """ - Exception to make throwing errors from deep in controllers easier - """ - def __init__( self, err_msg, type="info" ): - self.err_msg = err_msg - self.type = type - def error( message ): raise MessageException( message, type='error' ) --- a/lib/galaxy/web/framework/base.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/framework/base.py Thu Sep 22 17:13:53 2011 -0400 @@ -37,8 +37,8 @@ """ collection_path = kwargs.get( 'path_prefix', '' ) + '/' + collection_name + '/deleted' member_path = collection_path + '/:id' - self.connect( 'deleted_' + collection_name, collection_path, controller=collection_name, action='index', deleted=True ) - self.connect( 'deleted_' + member_name, member_path, controller=collection_name, action='show', deleted=True ) + self.connect( 'deleted_' + collection_name, collection_path, controller=collection_name, action='index', deleted=True, conditions=dict( method=['GET'] ) ) + self.connect( 'deleted_' + member_name, member_path, controller=collection_name, action='show', deleted=True, conditions=dict( method=['GET'] ) ) self.connect( 'undelete_deleted_' + member_name, member_path + '/undelete', controller=collection_name, action='undelete', conditions=dict( method=['POST'] ) ) self.resource( member_name, collection_name, **kwargs ) @@ -68,7 +68,7 @@ self.mapper.explicit = False self.api_mapper = routes.Mapper() self.transaction_factory = DefaultWebTransaction - def add_controller( self, controller_name, controller ): + def add_ui_controller( self, controller_name, controller ): """ Add a controller class to this application. A controller class has methods which handle web requests. To connect a URL to a controller's --- a/lib/galaxy/web/security/__init__.py Thu Sep 22 12:18:38 2011 -0400 +++ b/lib/galaxy/web/security/__init__.py Thu Sep 22 17:13:53 2011 -0400 @@ -55,4 +55,3 @@ def get_new_guid( self ): # Generate a unique, high entropy 128 bit random number return get_random_bytes( 16 ) - --- a/templates/admin/quota/quota.mako Thu Sep 22 12:18:38 2011 -0400 +++ b/templates/admin/quota/quota.mako Thu Sep 22 17:13:53 2011 -0400 @@ -32,7 +32,7 @@ $('#groups_remove_button').click(function() { return !$('#in_groups option:selected').remove().appendTo('#out_groups'); }); - $('form#associate_role_user_group').submit(function() { + $('form#associate_quota_user_group').submit(function() { $('#in_users option').each(function(i) { $(this).attr("selected", "selected"); }); @@ -48,30 +48,30 @@ %endif <div class="toolForm"> - <div class="toolFormTitle">Quota '${quota.name}'</div> + <div class="toolFormTitle">Quota '${name}'</div><div class="toolFormBody"> - <form name="associate_quota_user_group" id="associate_quota_user_group" action="${h.url_for( action='manage_users_and_groups_for_quota', id=trans.security.encode_id( quota.id ) )}" method="post" > + <form name="associate_quota_user_group" id="associate_quota_user_group" action="${h.url_for( action='manage_users_and_groups_for_quota', id=id )}" method="post" ><input name="webapp" type="hidden" value="${webapp}" size=40"/><div class="form-row"><div style="float: left; margin-right: 10px;"> - <label>Users associated with '${quota.name}'</label> + <label>Users associated with '${name}'</label> ${render_select( "in_users", in_users )}<br/><input type="submit" id="users_remove_button" value=">>"/></div><div> - <label>Users not associated with '${quota.name}'</label> + <label>Users not associated with '${name}'</label> ${render_select( "out_users", out_users )}<br/><input type="submit" id="users_add_button" value="<<"/></div></div><div class="form-row"><div style="float: left; margin-right: 10px;"> - <label>Groups associated with '${quota.name}'</label> + <label>Groups associated with '${name}'</label> ${render_select( "in_groups", in_groups )}<br/><input type="submit" id="groups_remove_button" value=">>"/></div><div> - <label>Groups not associated with '${quota.name}'</label> + <label>Groups not associated with '${name}'</label> ${render_select( "out_groups", out_groups )}<br/><input type="submit" id="groups_add_button" value="<<"/></div> --- a/templates/admin/quota/quota_edit.mako Thu Sep 22 12:18:38 2011 -0400 +++ b/templates/admin/quota/quota_edit.mako Thu Sep 22 17:13:53 2011 -0400 @@ -14,7 +14,7 @@ from galaxy.web.form_builder import SelectField operation_selectfield = SelectField( 'operation' ) for op in ( '=', '+', '-' ): - selected = op == quota.operation + selected = op == operation operation_selectfield.add_option( op, op, selected ) %> @@ -27,10 +27,10 @@ <div class="toolFormBody"><form name="library" action="${h.url_for( controller='admin', action='edit_quota' )}" method="post" ><input name="webapp" type="hidden" value="${webapp}"/> - <input name="id" type="hidden" value="${trans.security.encode_id( quota.id )}"/> + <input name="id" type="hidden" value="${id}"/><div class="form-row"><label>Amount</label> - <input name="amount" type="textfield" value="${quota.display_amount}" size=40"/> + <input name="amount" type="textfield" value="${display_amount}" size=40"/><div class="toolParamHelp" style="clear: both;"> Examples: "10000MB", "99 gb", "0.2T", "unlimited" </div> --- a/templates/admin/quota/quota_rename.mako Thu Sep 22 12:18:38 2011 -0400 +++ b/templates/admin/quota/quota_rename.mako Thu Sep 22 17:13:53 2011 -0400 @@ -22,14 +22,14 @@ <input name="webapp" type="hidden" value="${webapp}" size=40"/><label>Name:</label><div style="float: left; width: 250px; margin-right: 10px;"> - <input type="text" name="name" value="${quota.name}" size="40"/> + <input type="text" name="name" value="${name}" size="40"/></div><div style="clear: both"></div></div><div class="form-row"><label>Description:</label><div style="float: left; width: 250px; margin-right: 10px;"> - <input name="description" type="textfield" value="${quota.description}" size=40"/> + <input name="description" type="textfield" value="${description}" size=40"/></div><div style="clear: both"></div></div> @@ -41,7 +41,7 @@ </div><div class="form-row"><div style="float: left; width: 250px; margin-right: 10px;"> - <input type="hidden" name="id" value="${trans.security.encode_id( quota.id )}"/> + <input type="hidden" name="id" value="${id}"/></div><div style="clear: both"></div></div> --- a/templates/admin/quota/quota_set_default.mako Thu Sep 22 12:18:38 2011 -0400 +++ b/templates/admin/quota/quota_set_default.mako Thu Sep 22 17:13:53 2011 -0400 @@ -25,11 +25,11 @@ %endif <div class="toolForm"> - <div class="toolFormTitle">Create quota</div> + <div class="toolFormTitle">Set quota default</div><div class="toolFormBody"><form name="set_quota_default" id="set_quota_default" action="${h.url_for( action='set_quota_default' )}" method="post" ><input name="webapp" type="hidden" value="${webapp}" size=40"/> - <input name="id" type="hidden" value="${trans.security.encode_id( quota.id )}"/> + <input name="id" type="hidden" value="${id}"/><div class="form-row"><label>Is this quota a default for a class of users (if yes, what type)?</label> ${default_selectfield.get_html()} --- a/test/functional/test_history_functions.py Thu Sep 22 12:18:38 2011 -0400 +++ b/test/functional/test_history_functions.py Thu Sep 22 17:13:53 2011 -0400 @@ -203,7 +203,7 @@ self.login( email=regular_user2.email ) self.import_history_via_url( self.security.encode_id( history3.id ), admin_user.email, - strings_displayed_after_submit=[ 'History is not accessible to current user' ] ) + strings_displayed_after_submit=[ 'History is not accessible to the current user' ] ) self.logout() self.login( email=admin_user.email ) # Test sharing history3 with an invalid user Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.