Merge branch 'dev' of https://github.com/galaxyproject/galaxy into revise_validation

Conflicts:
	lib/galaxy/tools/__init__.py
	static/maps/mvc/form/form-view.js.map
	static/scripts/mvc/form/form-view.js
This commit is contained in:
guerler
2015-04-10 19:59:56 -04:00
134 changed files with 3597 additions and 1970 deletions
+5
View File
@@ -2,6 +2,11 @@
set -e
# Run broad range of checks over specific white-listed files, this uses
# the default checks defined in setup.cfg.
while read p; do
flake8 $(eval echo "$p")
done <.ci/pep8_sources.txt
# Look for obviously broken stuff lots more places.
flake8 --select=E901,E902,F821,F822,F823,F831 --exclude lib/galaxy/util/pastescript/serve.py lib/{galaxy,tool_shed} test/{api,unit}
+1 -1
View File
@@ -4,7 +4,7 @@ This document briefly describes how to contribute to the [core
galaxy project](https://github.com/galaxyproject/galaxy) -
also checkout our 2013 Galaxy Community
Conference presentation on the topic
([video](http://vimeo.com/channels/581875/73486255),
([video](https://vimeo.com/channels/581875/73486255),
[presentation](https://wiki.galaxyproject.org/Documents/Presentations/GCC2013?action=AttachFile&do=view&target=BakerContribute.pdf)). For
information on contributing more broadly to the Galaxy ecosystem and a
deeper discussion of some of these points - please see the
@@ -57,7 +57,7 @@ var DatasetAssociation = Backbone.Model.extend( BASE_MVC.LoggableMixin ).extend(
this._setUpListeners();
},
/** returns misc. web urls for rendering things like re-run, display, etc. */
_generateUrls : function(){
//TODO: would be nice if the API did this
@@ -251,6 +251,11 @@ var DatasetAssociationCollection = Backbone.Collection.extend( BASE_MVC.Loggable
urlRoot : (( window.galaxy_config && galaxy_config.root )?( galaxy_config.root ):( '/' ))
+ 'api/datasets',
/** url fn */
url : function(){
return this.urlRoot
},
// ........................................................................ common queries
/** Get the ids of every item in this collection
* @returns array of encoded ids
+5 -2
View File
@@ -96,9 +96,9 @@ return Backbone.Model.extend({
convert(job_input_id, head[input.id + '-section-' + selectedCase]);
}
break;
// handle custom sub sections
// handle sections
case 'section':
convert('', node);
convert(job_input_id, node);
break;
default:
// get field
@@ -191,6 +191,9 @@ return Backbone.Model.extend({
search (index, node.cases[selectedCase].inputs);
}
break;
case 'section':
search (index, node.inputs);
break;
default:
var input_id = self.map_dict[index];
if (input_id) {
@@ -286,7 +286,7 @@ define(['utils/utils',
});
// show sub section if requested
if (input_def.expand) {
if (input_def.expanded) {
portlet.$header.trigger('click');
}
@@ -200,6 +200,13 @@ define(['utils/utils', 'mvc/ui/ui-portlet', 'mvc/ui/ui-misc',
inputs : this.options.inputs
});
// switch to classic tool form mako if the form definition is incompatible
if (this.incompatible) {
this.$el.hide();
$('#tool-form-classic').show();
return;
}
// create portlet
this.portlet = new Portlet.View({
icon : 'fa-wrench',
@@ -360,7 +360,7 @@ var HistoryPanelEdit = _super.extend(
* @param {Model} the item model to check
*/
_handleHdaVisibleChange : function( itemModel ){
if( itemModel.hidden() && !this.storage.showHidden ){
if( itemModel.hidden() && !this.showHidden ){
this.removeItemView( itemModel );
}
this._renderCounts();
@@ -968,7 +968,9 @@ var MultiPanelColumns = Backbone.View.extend( baseMVC.LoggableMixin ).extend({
// when scrolling - check for histories now in view: they will fire 'in-view' and queueHdaLoading if necc.
//TODO:?? might be able to simplify and not use pub-sub
var debouncedInView = _.debounce( _.bind( this.checkColumnsInView, this ), 100 );
var debouncedInView = _.debounce( function _debouncedInner(){
multipanel.checkColumnsInView();
}, 100 );
this.$( '.middle' ).parent().scroll( debouncedInView );
},
+2
View File
@@ -186,6 +186,7 @@
<converter file="tabular_to_dbnsfp.xml" target_datatype="snpsiftdbnsfp"/>
</datatype>
<datatype extension="sff" type="galaxy.datatypes.binary:Sff" mimetype="application/octet-stream" display_in_upload="true" description="A binary file in 'Standard Flowgram Format' with a '.sff' file extension." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Sff"/>
<datatype extension="sra" type="galaxy.datatypes.binary:Sra" mimetype="application/octet-stream" display_in_upload="true" description="A binary file archive format from the NCBI Sequence Read Archive with a '.sra' file extension." description_url="http://www.ncbi.nlm.nih.gov/books/n/helpsra/SRA_Overview_BK/#SRA_Overview_BK.4_SRA_Data_Structure"/>
<datatype extension="svg" type="galaxy.datatypes.images:Image" mimetype="image/svg+xml"/>
<datatype extension="taxonomy" type="galaxy.datatypes.tabular:Taxonomy" display_in_upload="true"/>
<datatype extension="tabular" type="galaxy.datatypes.tabular:Tabular" display_in_upload="true" description="Any data in tab delimited format (tabular)." description_url="https://wiki.galaxyproject.org/Learn/Datatypes#Tabular_.28tab_delimited.29"/>
@@ -285,6 +286,7 @@
<sniffer type="galaxy.datatypes.binary:SQlite"/>
<sniffer type="galaxy.datatypes.binary:Bam"/>
<sniffer type="galaxy.datatypes.binary:Sff"/>
<sniffer type="galaxy.datatypes.binary:Sra"/>
<sniffer type="galaxy.datatypes.xml:Phyloxml"/>
<sniffer type="galaxy.datatypes.xml:Owl"/>
<sniffer type="galaxy.datatypes.xml:GenericXml"/>
+52 -25
View File
@@ -218,7 +218,7 @@ paste.app_factory = galaxy.web.buildapp:app_factory
#tool_data_path = tool-data
# Directory where Tool Data Table related files will be placed
# when installed from a ToolShed. Defaults to tool_data_path
# when installed from a ToolShed. Defaults to tool_data_path.
#shed_tool_data_path = tool-data
# File containing old-style genome builds
@@ -369,7 +369,7 @@ paste.app_factory = galaxy.web.buildapp:app_factory
# URL of the support resource for the galaxy instance. Used in activation
# emails.
#instance_resource_url = http://wiki.galaxyproject.org/
#instance_resource_url = https://wiki.galaxyproject.org/
# E-mail domains blacklist is used for filtering out users that are using
# disposable email address during the registration. If their address domain
@@ -465,7 +465,7 @@ nglims_config_file = tool-data/nglims.yaml
# Format string used when showing date and time information.
# The string may contain:
# - the directives used by Python time.strftime() function (see
# http://docs.python.org/2/library/time.html#time.strftime ),
# https://docs.python.org/2/library/time.html#time.strftime ),
# - $locale (complete format string for the server locale),
# - $iso8601 (complete format string as specified by ISO 8601 international
# standard).
@@ -476,6 +476,15 @@ nglims_config_file = tool-data/nglims.yaml
# IPython Docker container for communicating back with Galaxy via the API.
#galaxy_infrastructure_url = http://localhost:8080
# If the above URL cannot be determined ahead of time in dynamic environments
# but the port which should be used to access Galaxy can be - this should be
# set to prevent Galaxy from having to guess. For example if Galaxy is sitting
# behind a proxy with REMOTE_USER enabled - infrastructure shouldn't talk to
# Python processes directly and this should be set to 80 or 443, etc... If
# unset this file will be read for a server block defining a port corresponding
# to the webapp.
#galaxy_infrastructure_web_port = 8080
# The URL of the page to display in Galaxy's middle pane when loaded. This can
# be an absolute or relative URL.
#welcome_url = /static/welcome.html
@@ -484,22 +493,22 @@ nglims_config_file = tool-data/nglims.yaml
#logo_url = /
# The URL linked by the "Wiki" link in the "Help" menu.
#wiki_url = http://wiki.galaxyproject.org/
#wiki_url = https://wiki.galaxyproject.org/
# The URL linked by the "Support" link in the "Help" menu.
#support_url = http://wiki.galaxyproject.org/Support
#support_url = https://wiki.galaxyproject.org/Support
# The URL linked by the "How to Cite Galaxy" link in the "Help" menu.
#citation_url = http://wiki.galaxyproject.org/CitingGalaxy
#citation_url = https://wiki.galaxyproject.org/CitingGalaxy
#The URL linked by the "Search" link in the "Help" menu.
#search_url = http://galaxyproject.org/search/usegalaxy/
#The URL linked by the "Mailing Lists" link in the "Help" menu.
#mailing_lists_url = http://wiki.galaxyproject.org/MailingLists
#mailing_lists_url = https://wiki.galaxyproject.org/MailingLists
#The URL linked by the "Videos" link in the "Help" menu.
#videos_url = http://vimeo.com/galaxyproject
#screencasts_url = https://vimeo.com/galaxyproject
# The URL linked by the "Terms and Conditions" link in the "Help" menu, as well
# as on the user registration and login forms and in the activation emails.
@@ -576,6 +585,13 @@ nglims_config_file = tool-data/nglims.yaml
# requests.
#nginx_upload_path = False
# Galaxy can also use nginx_upload_module to receive files staged out upon job
# completion by remote job runners (i.e. Pulsar) that initiate staging
# operations on the remote end. See the Galaxy nginx documentation for the
# corresponding nginx configuration.
#nginx_upload_job_files_store = False
#nginx_upload_job_files_path = False
# Have Galaxy manage dynamic proxy component for routing requests to other
# services based on Galaxy's session cookie. It will attempt to do this by
# default though you do need to install node+npm and do an npm install from
@@ -603,7 +619,7 @@ nglims_config_file = tool-data/nglims.yaml
# -- Logging and Debugging
# Verbosity of console log messages. Acceptable values can be found here:
# http://docs.python.org/library/logging.html#logging-levels
# https://docs.python.org/2/library/logging.html#logging-levels
#log_level = DEBUG
# Print database operations to the server log (warning, quite verbose!).
@@ -716,16 +732,16 @@ use_interactive = True
# -- Toolbox Search
# The following boosts are used to customize this instance's toolbox search.
# The higher the boost the more importance the scoring algorithm gives to the
# given field. Section refers to the tool group in the tool panel. Rest of
# The higher the boost, the more importance the scoring algorithm gives to the
# given field. Section refers to the tool group in the tool panel. Rest of
# the fields are tool's attributes.
# tool_name_boost = 9
# tool_section_boost = 3
# tool_description_boost = 2
# tool_help_boost = 0.5
# Limits the number of results in toolbox search. Can be used to tweak
# how many results will appear.
# Limits the number of results in toolbox search. Can be used to tweak how many
# results will appear.
# tool_search_limit = 20
# -- Users and Security
@@ -805,11 +821,12 @@ use_interactive = True
# public.
#new_user_dataset_access_role_default_private = False
# Expose user list. Setting this to true will expose the user list to authenticated users. This
# makes sharing datasets in smaller galaxy instances much easier as they can type a name/email and
# have the correct user show up. This makes less sense on large public galaxy instances where
# that data shouldn't be exposed. For semi-public galaxies, it may make sense to expose just the
# username and not email, or vice versa.
# Expose user list. Setting this to True will expose the user list to
# authenticated users. This makes sharing datasets in smaller galaxy instances
# much easier as they can type a name/email and have the correct user show up.
# This makes less sense on large public Galaxy instances where that data
# shouldn't be exposed. For semi-public Galaxies, it may make sense to expose
# just the username and not email, or vice versa.
#expose_user_name = False
#expose_user_email = False
@@ -856,8 +873,8 @@ use_interactive = True
#openid_consumer_cache_path = database/openid_consumer_cache
# XML config file that allows the use of different authentication providers
# (e.g. Active Directory) instead or in addition to local authentication
# (.sample is used if default does not exist).
# (e.g. LDAP) instead or in addition to local authentication (.sample is used
# if default does not exist).
#auth_config_file = config/auth_conf.xml
# Optional list of email addresses of API users who can make calls on behalf of
@@ -906,14 +923,14 @@ use_interactive = True
#expose_dataset_path = False
# Data manager configuration options
# Allow non-admin users to view available Data Manager options
# Allow non-admin users to view available Data Manager options.
#enable_data_manager_user_view = False
# File where Data Managers are configured (.sample used if default does not
# exist)
# exist).
#data_manager_config_file = config/data_manager_conf.xml
# File where Tool Shed based Data Managers are configured
# File where Tool Shed based Data Managers are configured.
#shed_data_manager_config_file = config/shed_data_manager_conf.xml
# Directory to store Data Manager based tool-data; defaults to tool_data_path
# Directory to store Data Manager based tool-data; defaults to tool_data_path.
#galaxy_data_manager_data_path = tool-data
# -- Job Execution
@@ -976,6 +993,17 @@ use_interactive = True
# always, onsuccess, never
#cleanup_job = always
# For sites where all users in Galaxy match users on the system on which Galaxy
# runs, the DRMAA job runner can be configured to submit jobs to the DRM as the
# actual user instead of as the user running the Galaxy server process. For
# details on these options, see the documentation at:
#
# https://wiki.galaxyproject.org/Admin/Config/Performance/Cluster
#
#drmaa_external_runjob_script = scripts/drmaa_external_runner.py
#drmaa_external_killjob_script = scripts/drmaa_external_killer.py
#external_chown_script = scripts/external_chown_script.py
# File to source to set up the environment when running jobs. By default, the
# environment in which the Galaxy server starts is used when running jobs
# locally, and the environment set up per the DRM's submission method and
@@ -987,7 +1015,6 @@ use_interactive = True
# individually.
#environment_setup_file = None
# Optional file containing job resource data entry fields definition.
# These fields will be presented to users in the tool forms and allow them to
# overwrite default job resources such as number of processors, memory and
+1 -4
View File
@@ -252,10 +252,7 @@
-->
</destination>
<destination id="real_user_cluster" runner="drmaa">
<!-- TODO: The real user options should maybe not be considered runner params. -->
<param id="galaxy_external_runjob_script">scripts/drmaa_external_runner.py</param>
<param id="galaxy_external_killjob_script">scripts/drmaa_external_killer.py</param>
<param id="galaxy_external_chown_script">scripts/external_chown_script.py</param>
<!-- Make sure to setup 3 real user parameters in galaxy.ini. -->
</destination>
<destination id="dynamic" runner="dynamic">
<!-- A destination that represents a method in the dynamic runner. -->
@@ -20,7 +20,7 @@ var ScatterplotDisplay = Backbone.View.extend({
var view = this,
config = this.model.get( 'config' ),
//TODO: very tied to datasets - should be generalized eventually
xhr = jQuery.getJSON( '/api/datasets/' + this.dataset.id, {
xhr = jQuery.getJSON(((typeof parent.galaxy_config.root == 'undefined') ? '/' : parent.galaxy_config.root) + 'api/datasets/' + this.dataset.id, {
data_type : 'raw_data',
provider : 'dataset-column',
limit : config.pagination.perPage,
File diff suppressed because one or more lines are too long
+29 -23
View File
@@ -20,19 +20,24 @@ log = logging.getLogger(__name__)
# <auth>
# <authenticator>
# <type>activedirectory</type>
# <filter>'[username]'.endswith('@students.latrobe.edu.au')</filter>
# <type>ldap</type>
# <filter>'[login]'.endswith('@students.latrobe.edu.au')</filter>
# <options>
# <auto-register>True</auto-register>
# <server>ldap://STUDENTS.ltu.edu.au</server>
# [<search-filter>(&amp;(objectClass=user)(mail={username}))</search-filter>
# [<search-filter>(&amp;(objectClass=user)(mail={login}))</search-filter>
# <search-base>dc=STUDENTS,dc=ltu,dc=edu,dc=au</search-base>
# <!-- If search-user not specified will bind anonymously to LDAP for search -->
# <search-user>jsmith</search-user>
# <search-password>mysecret</search-password>
# <search-fields>sAMAccountName</search-fields>]
# <search-fields>sAMAccountName,mail</search-fields>]
# <bind-user>{sAMAccountName}@STUDENTS.ltu.edu.au</bind-user>
# <bind-password>{password}</bind-password>
# <auto-register-username>{sAMAccountName}</auto-register-username>
# <auto-register-email>{mail}</auto-register-email>
# <!-- To allow login with username instead of email
# <login-use-username>True</login-use-username>
# -->
# </options>
# </authenticator>
# ...
@@ -44,7 +49,7 @@ class AuthManager(object):
def __init__(self, app):
self.__app = app
import galaxy.auth.providers
self.__plugins_dict = plugin_config.plugins_dict( galaxy.auth.providers, 'plugin_type' )
self.__plugins_dict = plugin_config.plugins_dict(galaxy.auth.providers, 'plugin_type' )
auth_config_file = app.config.auth_config_file
self.__init_authenticators(auth_config_file)
@@ -80,18 +85,18 @@ class AuthManager(object):
authenticators.append(authenticator)
self.authenticators = authenticators
def check_registration_allowed(self, email, password):
"""Checks if the provided email is allowed to register."""
def check_registration_allowed(self, login, password):
"""Checks if the provided email/username is allowed to register."""
message = ''
status = 'done'
for provider, options in self.active_authenticators(email, password):
for provider, options in self.active_authenticators(login, password):
allow_reg = _get_tri_state(options, 'allow-register', True)
if allow_reg is None: # i.e. challenge
auth_result, msg = provider.authenticate(email, password, options)
auth_result, msg = provider.authenticate(login, password, options)
if auth_result is True:
break
if auth_result is None:
message = 'Invalid email address or password'
message = 'Invalid email address/username or password'
status = 'error'
break
elif allow_reg is True:
@@ -102,16 +107,17 @@ class AuthManager(object):
break
return message, status
def check_auto_registration(self, trans, email, password):
def check_auto_registration(self, trans, login, password):
"""
Checks the email/password using auth providers in order. If a match is
found, returns the 'auto-register' option for that provider.
Checks the username/email & password using auth providers in order.
If a match is found, returns the 'auto-register' option for that provider.
"""
for provider, options in self.active_authenticators(email, password):
for provider, options in self.active_authenticators(login, password):
if provider is None:
log.debug( "Unable to find module: %s" % options )
else:
auth_result, auto_username = provider.authenticate(email, password, options)
auth_result, auto_email, auto_username = provider.authenticate(login, password, options)
auto_email = str(auto_email).lower()
auto_username = str(auto_username).lower()
if auth_result is True:
# make username unique
@@ -124,16 +130,16 @@ class AuthManager(object):
i += 1
else:
break # end for loop if we can't make a unique username
log.debug( "Email: %s, auto-register with username: %s" % (email, auto_username) )
return (_get_bool(options, 'auto-register', False), auto_username)
log.debug( "Email: %s, auto-register with username: %s" % (auto_email, auto_username) )
return (_get_bool(options, 'auto-register', False), auto_email, auto_username)
elif auth_result is None:
log.debug( "Email: %s, stopping due to failed non-continue" % (email) )
log.debug( "Email: %s, Username %s, stopping due to failed non-continue" % (auto_email, auto_username) )
break # end authentication (skip rest)
return (False, '')
def check_password(self, user, password):
"""Checks the email/password using auth providers."""
for provider, options in self.active_authenticators(user.email, password):
"""Checks the username/email and password using auth providers."""
for provider, options in self.active_authenticators(user, password):
if provider is None:
log.debug( "Unable to find module: %s" % options )
else:
@@ -148,7 +154,7 @@ class AuthManager(object):
"""Checks that auth provider allows password changes and current_password
matches.
"""
for provider, options in self.active_authenticators(user.email, current_password):
for provider, options in self.active_authenticators(user, current_password):
if provider is None:
log.debug( "Unable to find module: %s" % options )
else:
@@ -162,7 +168,7 @@ class AuthManager(object):
return (False, 'Password change not supported')
return (False, 'Invalid current password')
def active_authenticators(self, username, password):
def active_authenticators(self, login, password):
"""Yields AuthProvider instances for the provided configfile that match the
filters.
"""
@@ -170,7 +176,7 @@ class AuthManager(object):
for authenticator in self.authenticators:
filter_template = authenticator.filter_template
if filter_template:
filter_str = filter_template.format(username=username, password=password)
filter_str = filter_template.format(login=login, password=password)
passed_filter = eval(filter_str, {"__builtins__": None}, {'str': str})
if not passed_filter:
continue # skip to next
+7 -7
View File
@@ -16,23 +16,23 @@ class AuthProvider(object):
""" Short string providing labelling this plugin """
@abc.abstractmethod
def authenticate(self, username, password, options):
def authenticate(self, login, password, options):
"""
Check that the username and password are correct.
NOTE: Used within auto-registration to check it is ok to register this
user.
:param username: the users email address
:type username: str
:param login: the user's email address or username
:type login: str
:param password: the plain text password they typed
:type password: str
:param options: options provided in auth_config_file
:type options: dict
:returns: True: accept user, False: reject user and None: reject user
and don't try any other providers. str is the username to register
with if accepting
:rtype: (bool, str)
and don't try any other providers. str, str is the email and
username to register with if accepting
:rtype: (bool, str, str)
"""
@abc.abstractmethod
@@ -44,7 +44,7 @@ class AuthProvider(object):
NOTE: used on normal login to check authentication and update user
details if required.
:param username: the users email address
:param username: the user's email address or username
:type username: str
:param password: the plain text password they typed
:type password: str
+2 -2
View File
@@ -16,11 +16,11 @@ class AlwaysReject(AuthProvider):
"""
plugin_type = 'alwaysreject'
def authenticate(self, username, password, options):
def authenticate(self, login, password, options):
"""
See abstract method documentation.
"""
return (None, '')
return (None, '', '')
def authenticate_user(self, user, password, options):
"""
@@ -4,34 +4,37 @@ Created on 15/07/2014
@author: Andrew Robinson
"""
import logging
from galaxy.exceptions import ConfigurationError
from ..providers import AuthProvider
from galaxy.auth import _get_bool
import logging
log = logging.getLogger(__name__)
def _get_subs(d, k, params):
if k not in d:
raise ConfigurationError("Missing '%s' parameter in Active Directory options" % k)
raise ConfigurationError("Missing '%s' parameter in LDAP options" % k)
return str(d[k]).format(**params)
class ActiveDirectory(AuthProvider):
"""
Attempts to authenticate users against an Active Directory server.
class LDAP(AuthProvider):
If options include search-fields then it will attempt to search the AD for
those fields first. After that it will bind to the AD with the username
"""
Attempts to authenticate users against an LDAP server.
If options include search-fields then it will attempt to search LDAP for
those fields first. After that it will bind to LDAP with the username
(formatted as specified).
"""
plugin_type = 'activedirectory'
plugin_type = 'ldap'
def authenticate(self, username, password, options):
def authenticate(self, login, password, options):
"""
See abstract method documentation.
"""
log.debug("Username: %s" % username)
log.debug("Login: %s" % login)
log.debug("Options: %s" % options)
failure_mode = False # reject but continue
@@ -41,11 +44,12 @@ class ActiveDirectory(AuthProvider):
try:
import ldap
except:
log.debug("User: %s, ACTIVEDIRECTORY: False (no ldap)" % (username))
log.debug(
"Login: %s, LDAP: False (could not load ldap module)" % (login))
return (failure_mode, '')
# do AD search (if required)
params = {'username': username, 'password': password}
# do LDAP search (if required)
params = {'login': login, 'password': password}
if 'search-fields' in options:
try:
# setup connection
@@ -54,21 +58,24 @@ class ActiveDirectory(AuthProvider):
l.protocol_version = 3
if 'search-user' in options:
l.simple_bind_s(_get_subs(options, 'search-user', params), _get_subs(options, 'search-password', params))
l.simple_bind_s(_get_subs(options, 'search-user', params),
_get_subs(options, 'search-password', params))
else:
l.simple_bind_s()
scope = ldap.SCOPE_SUBTREE
# setup search
attributes = [_.strip().format(**params) for _ in options['search-fields'].split(',')]
result = l.search(_get_subs(options, 'search-base', params), scope, _get_subs(options, 'search-filter', params), attributes)
attributes = [_.strip().format(**params)
for _ in options['search-fields'].split(',')]
result = l.search(_get_subs(options, 'search-base', params), scope,
_get_subs(options, 'search-filter', params), attributes)
# parse results
_, suser = l.result(result, 60)
dn, attrs = suser[0]
log.debug(("AD dn: %s" % dn))
log.debug(("AD Search attributes: %s" % attrs))
log.debug(("LDAP dn: %s" % dn))
log.debug(("LDAP Search attributes: %s" % attrs))
if hasattr(attrs, 'has_key'):
for attr in attributes:
if attr in attrs:
@@ -77,8 +84,8 @@ class ActiveDirectory(AuthProvider):
params[attr] = ""
params['dn'] = dn
except Exception:
log.exception('ACTIVEDIRECTORY Search Exception for User: %s' % username)
return (failure_mode, '')
log.exception('LDAP Search Exception for login: %s' % login)
return (failure_mode, '', '')
# end search
# bind as user to check their credentials
@@ -87,19 +94,31 @@ class ActiveDirectory(AuthProvider):
ldap.set_option(ldap.OPT_REFERRALS, 0)
l = ldap.initialize(_get_subs(options, 'server', params))
l.protocol_version = 3
l.simple_bind_s(_get_subs(options, 'bind-user', params), _get_subs(options, 'bind-password', params))
l.simple_bind_s(_get_subs(
options, 'bind-user', params), _get_subs(options, 'bind-password', params))
except Exception:
log.exception('ACTIVEDIRECTORY Authenticate Exception for User %s' % username)
return (failure_mode, '')
log.exception('LDAP Authentication Exception for login %s' % login)
return (failure_mode, '', '')
log.debug("User: %s, ACTIVEDIRECTORY: True" % (username))
return (True, _get_subs(options, 'auto-register-username', params))
log.debug("Login: %s, LDAP: True" % (login))
return (True,
_get_subs(options, 'auto-register-email', params),
_get_subs(options, 'auto-register-username', params))
def authenticate_user(self, user, password, options):
"""
See abstract method documentation.
"""
return self.authenticate(user.email, password, options)[0]
if _get_bool(options, 'login-use-username', False):
return self.authenticate(user.username, password, options)[0]
else:
return self.authenticate(user.email, password, options)[0]
__all__ = ['ActiveDirectory']
class ActiveDirectory(LDAP):
""" Effectively just an alias for LDAP auth, but may contain active directory specific
logic in the future. """
plugin_type = 'activedirectory'
__all__ = ['LDAP', 'ActiveDirectory']
+2 -2
View File
@@ -13,11 +13,11 @@ class LocalDB(AuthProvider):
"""Authenticate users against the local Galaxy database (as per usual)."""
plugin_type = 'localdb'
def authenticate(self, username, password, options):
def authenticate(self, login, password, options):
"""
See abstract method documentation.
"""
return (False, '') # it can never auto-create based of localdb (chicken-egg)
return (False, '', '') # it can never auto-create based of localdb (chicken-egg)
def authenticate_user(self, user, password, options):
"""
+8 -4
View File
@@ -262,7 +262,7 @@ class Configuration( object ):
self.message_box_content = kwargs.get( 'message_box_content', None )
self.message_box_class = kwargs.get( 'message_box_class', 'info' )
self.support_url = kwargs.get( 'support_url', 'https://wiki.galaxyproject.org/Support' )
self.wiki_url = kwargs.get( 'wiki_url', 'http://wiki.galaxyproject.org/' )
self.wiki_url = kwargs.get( 'wiki_url', 'https://wiki.galaxyproject.org/' )
self.blog_url = kwargs.get( 'blog_url', None )
self.screencasts_url = kwargs.get( 'screencasts_url', None )
self.library_import_dir = kwargs.get( 'library_import_dir', None )
@@ -302,6 +302,8 @@ class Configuration( object ):
self.nginx_x_archive_files_base = kwargs.get( 'nginx_x_archive_files_base', False )
self.nginx_upload_store = kwargs.get( 'nginx_upload_store', False )
self.nginx_upload_path = kwargs.get( 'nginx_upload_path', False )
self.nginx_upload_job_files_store = kwargs.get( 'nginx_upload_job_files_store', False )
self.nginx_upload_job_files_path = kwargs.get( 'nginx_upload_job_files_path', False )
if self.nginx_upload_store:
self.nginx_upload_store = os.path.abspath( self.nginx_upload_store )
self.object_store = kwargs.get( 'object_store', 'disk' )
@@ -356,6 +358,8 @@ class Configuration( object ):
# Default URL (with schema http/https) of the Galaxy instance within the
# local network - used to remotely communicate with the Galaxy API.
web_port = kwargs.get("galaxy_infrastructure_web_port", None)
self.galaxy_infrastructure_web_port = web_port
galaxy_infrastructure_url = kwargs.get( 'galaxy_infrastructure_url', None )
galaxy_infrastructure_url_set = True
if galaxy_infrastructure_url is None:
@@ -363,9 +367,9 @@ class Configuration( object ):
# so dependending on the context a better default can be used (
# request url in a web thread, Docker parent in IE stuff, etc...)
galaxy_infrastructure_url = "http://localhost"
port = self.guess_galaxy_port()
if port:
galaxy_infrastructure_url += ":%s" % (port)
web_port = self.galaxy_infrastructure_web_port or self.guess_galaxy_port()
if web_port:
galaxy_infrastructure_url += ":%s" % (web_port)
galaxy_infrastructure_url_set = False
if "HOST_IP" in galaxy_infrastructure_url:
galaxy_infrastructure_url = string.Template(galaxy_infrastructure_url).safe_substitute({
+57
View File
@@ -24,6 +24,9 @@ from galaxy.datatypes.metadata import MetadataElement, MetadataParameter, ListPa
from galaxy.datatypes import metadata
import dataproviders
eggs.require( "pysam" )
from pysam import csamtools
log = logging.getLogger(__name__)
# Currently these supported binary data types must be manually set on upload
@@ -146,6 +149,12 @@ class Bam( Binary ):
data_sources = { "data": "bai", "index": "bigwig" }
MetadataElement( name="bam_index", desc="BAM Index File", param=metadata.FileParameter, file_ext="bai", readonly=True, no_value=None, visible=False, optional=True )
MetadataElement( name="bam_version", default=None, desc="BAM Version", param=MetadataParameter, readonly=True, visible=False, optional=True, no_value=None )
MetadataElement( name="sort_order", default=None, desc="Sort Order", param=MetadataParameter, readonly=True, visible=False, optional=True, no_value=None )
MetadataElement( name="read_groups", default=[], desc="Read Groups", param=MetadataParameter, readonly=True, visible=False, optional=True, no_value=[] )
MetadataElement( name="reference_names", default=[], desc="Chromosome Names", param=MetadataParameter, readonly=True, visible=False, optional=True, no_value=[] )
MetadataElement( name="reference_lengths", default=[], desc="Chromosome Lengths", param=MetadataParameter, readonly=True, visible=False, optional=True, no_value=[] )
MetadataElement( name="bam_header", default={}, desc="Dictionary of BAM Headers", param=MetadataParameter, readonly=True, visible=False, optional=True, no_value={} )
def _get_samtools_version( self ):
# Determine the version of samtools being used. Wouldn't it be nice if
@@ -291,6 +300,17 @@ class Bam( Binary ):
dataset.metadata.bam_index = index_file
# Remove temp file
os.unlink( stderr_name )
# Now use pysam with BAI index to determine additional metadata
try:
bam_file = csamtools.Samfile( filename=dataset.file_name, mode='rb', index_filename=index_file.file_name )
dataset.metadata.reference_names = list( bam_file.references )
dataset.metadata.reference_lengths = list( bam_file.lengths )
dataset.metadata.bam_header = bam_file.header
dataset.metadata.read_groups = [ read_group['ID'] for read_group in dataset.metadata.bam_header.get( 'RG', [] ) if 'ID' in read_group ]
dataset.metadata.sort_order = dataset.metadata.bam_header.get( 'HD', {} ).get( 'SO', None )
dataset.metadata.bam_version = dataset.metadata.bam_header.get( 'HD', {} ).get( 'VN', None )
except:
pass
def sniff( self, filename ):
# BAM is compressed in the BGZF format, and must not be uncompressed in Galaxy.
@@ -753,3 +773,40 @@ class Xlsx(Binary):
Binary.register_sniffable_binary_format("xlsx", "xlsx", Xlsx)
class Sra( Binary ):
""" Sequence Read Archive (SRA) datatype originally from mdshw5/sra-tools-galaxy"""
file_ext = 'sra'
def __init__( self, **kwd ):
Binary.__init__( self, **kwd )
def sniff( self, filename ):
""" The first 8 bytes of any NCBI sra file is 'NCBI.sra', and the file is binary.
For details about the format, see http://www.ncbi.nlm.nih.gov/books/n/helpsra/SRA_Overview_BK/#SRA_Overview_BK.4_SRA_Data_Structure
"""
try:
header = open(filename).read(8)
if binascii.b2a_hex(header) == binascii.hexlify('NCBI.sra'):
return True
else:
return False
except:
return False
def set_peek(self, dataset, is_multi_byte=False):
if not dataset.dataset.purged:
dataset.peek = 'Binary sra file'
dataset.blurb = data.nice_size(dataset.get_size())
else:
dataset.peek = 'file does not exist'
dataset.blurb = 'file purged from disk'
def display_peek(self, dataset):
try:
return dataset.peek
except:
return 'Binary sra file (%s)' % (data.nice_size(dataset.get_size()))
Binary.register_sniffable_binary_format('sra', 'sra', Sra)
-14
View File
@@ -210,19 +210,5 @@ class PopulatedExternalService( object ):
assert action, 'Action not found: %s' % actions_list
return action
def get_action_links( self ):
rval = []
param_dict = {}
param_dict['fields'] = Bunch( **self.service_instance.form_values.content )
param_dict['item'] = self.item
for item in self.service.items:
if isinstance( item, ExternalServiceParameter ):
param_dict[ item.name ] = item.get_value( param_dict )
elif isinstance( item, ExternalServiceAction ):
rval.append( item.get_action_access_link( self.item, trans, param_dict ) )
elif isinstance( item, ExternalServiceActionsGroup ):
rval.extend( item.populate( self.service_instance, item, param_dict ).get_action_links() )
else:
raise 'unknown item type found'
def __nonzero__( self ):
return self.actions.has_action()
+17 -5
View File
@@ -32,6 +32,8 @@ __all__ = [ 'PulsarLegacyJobRunner', 'PulsarRESTJobRunner', 'PulsarMQJobRunner'
NO_REMOTE_GALAXY_FOR_METADATA_MESSAGE = "Pulsar misconfiguration - Pulsar client configured to set metadata remotely, but remote Pulsar isn't properly configured with a galaxy_home directory."
NO_REMOTE_DATATYPES_CONFIG = "Pulsar client is configured to use remote datatypes configuration when setting metadata externally, but Pulsar is not configured with this information. Defaulting to datatypes_conf.xml."
GENERIC_REMOTE_ERROR = "Failed to communicate with remote job server."
FAILED_REMOTE_ERROR = "Remote job server indicated a problem running or monitoring this job."
LOST_REMOTE_ERROR = "Remote job server could not determine this job's state."
# Is there a good way to infer some default for this? Can only use
# url_for from web threads. https://gist.github.com/jmchilton/9098762
@@ -170,8 +172,13 @@ class PulsarJobRunner( AsynchronousJobRunner ):
if pulsar_status == "complete":
self.mark_as_finished(job_state)
return None
if pulsar_status == "failed":
self.fail_job(job_state)
if pulsar_status in ["failed", "lost"]:
if pulsar_status == "failed":
message = FAILED_REMOTE_ERROR
else:
message = LOST_REMOTE_ERROR
if not job_state.job_wrapper.get_job().finished:
self.fail_job(job_state, message)
return None
if pulsar_status == "running" and not job_state.running:
job_state.running = True
@@ -342,7 +349,12 @@ class PulsarJobRunner( AsynchronousJobRunner ):
encoded_job_id = self.app.security.encode_id(job_id)
job_key = self.app.security.encode_id( job_id, kind="jobs_files" )
files_endpoint = "%s/api/jobs/%s/files?job_key=%s" % (
endpoint_base = "%s/api/jobs/%s?job_key=%s"
if self.app.config.nginx_upload_job_files_path:
endpoint_base = "%s" + \
self.app.config.nginx_upload_job_files_path + \
"?job_id=%s&job_key=%s"
files_endpoint = endpoint_base % (
self.galaxy_url,
encoded_job_id,
job_key
@@ -398,12 +410,12 @@ class PulsarJobRunner( AsynchronousJobRunner ):
log.exception("Job wrapper finish method failed")
job_wrapper.fail("Unable to finish job", exception=True)
def fail_job( self, job_state ):
def fail_job( self, job_state, message=GENERIC_REMOTE_ERROR ):
"""
Seperated out so we can use the worker threads for it.
"""
self.stop_job( self.sa_session.query( self.app.model.Job ).get( job_state.job_wrapper.job_id ) )
job_state.job_wrapper.fail( getattr( job_state, "fail_message", GENERIC_REMOTE_ERROR ) )
job_state.job_wrapper.fail( getattr( job_state, "fail_message", message ) )
def check_pid( self, pid ):
try:
+39 -21
View File
@@ -12,24 +12,24 @@ class AnnotatableManagerMixin( object ):
# TODO: most of this seems to be covered by item_attrs.UsesAnnotations
# TODO: use these below (serializer/deserializer)
def user_annotation( self, trans, item, user ):
return item.get_item_annotation_str( self.app.model.context, user, item )
def user_annotation( self, item, user ):
return item.get_item_annotation_str( self.session(), user, item )
def owner_annotation( self, trans, item ):
return self.user_annotation( trans, item, item.user )
def owner_annotation( self, item ):
return self.user_annotation( item, item.user )
def delete_annotation( self, trans, item, user ):
return item.delete_item_annotation( self.app.model.context, user, item )
def delete_annotation( self, item, user ):
return item.delete_item_annotation( self.session(), user, item )
def annotate( self, trans, item, user, annotation ):
def annotate( self, item, user, annotation ):
if annotation is None:
self.delete_annotation( self, trans, item, user )
self.delete_annotation( item, user )
return None
annotation_obj = item.add_item_annotation( self.app.model.context, user, item, annotation )
annotation_obj = item.add_item_annotation( self.session(), user, item, annotation )
return annotation_obj.annotation
#def by_user( self, trans, user, **kwargs ):
#def by_user( self, user, **kwargs ):
# pass
@@ -38,13 +38,11 @@ class AnnotatableSerializerMixin( object ):
def add_serializers( self ):
self.serializers[ 'annotation' ] = self.serialize_annotation
def serialize_annotation( self, trans, item, key ):
def serialize_annotation( self, item, key, user=None, **context ):
"""
Get and serialize an `item`'s annotation.
"""
# user = item.user
#TODO: trans
user = trans.user
sa_session = self.app.model.context
returned = item.get_item_annotation_str( sa_session, user, item )
return returned
@@ -55,19 +53,39 @@ class AnnotatableDeserializerMixin( object ):
def add_deserializers( self ):
self.deserializers[ 'annotation' ] = self.deserialize_annotation
def deserialize_annotation( self, trans, item, key, val ):
def deserialize_annotation( self, item, key, val, user=None, **context ):
"""
Make sure `val` is a valid annotation and assign it, deleting any existing
if `val` is None.
"""
val = self.validate.nullable_basestring( key, val )
return self.manager.annotate( item, user, val )
sa_session = self.app.model.context
#TODO: trans
user = trans.user
if val is None:
item.delete_item_annotation( sa_session, user, item )
# TODO: I'm not entirely convinced this (or tags) are a good idea for filters since they involve a/the user
class AnnotatableFilterMixin( object ):
def _owner_annotation( self, item ):
"""
Get the annotation by the item's owner.
"""
if not item.user:
return None
for annotation in item.annotations:
if annotation.user == item.user:
return annotation.annotation
return None
annotated_item = item.add_item_annotation( sa_session, user, item, val )
return annotated_item.annotation
def filter_annotation_contains( self, item, val ):
"""
Test whether `val` is in the owner's annotation.
"""
owner_annotation = self._owner_annotation( item )
if owner_annotation is None:
return False
return val in owner_annotation
def _add_parsers( self ):
self.fn_filter_parsers.update({
'annotation' : { 'op': { 'has': self.filter_annotation_contains, } },
})
+110 -104
View File
@@ -179,7 +179,7 @@ class ModelManager( object ):
return item
# .... query foundation wrapper
def query( self, trans, eagerloads=True, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
def query( self, eagerloads=True, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
"""
Return a basic query from model_class, filters, order_by, and limit and offset.
@@ -258,11 +258,11 @@ class ModelManager( object ):
return query
# .... query resolution
def one( self, trans, **kwargs ):
def one( self, **kwargs ):
"""
Sends kwargs to build the query and returns one and only one model.
"""
query = self.query( trans, **kwargs )
query = self.query( **kwargs )
return self._one_with_recast_errors( query )
def _one_with_recast_errors( self, query ):
@@ -292,33 +292,33 @@ class ModelManager( object ):
return None
#NOTE: at this layer, all ids are expected to be decoded and in int form
def by_id( self, trans, id, **kwargs ):
def by_id( self, id, **kwargs ):
"""
Gets a model by primary id.
"""
id_filter = self.model_class.id == id
return self.one( trans, filters=id_filter, **kwargs )
return self.one( filters=id_filter, **kwargs )
# .... multirow queries
def _orm_list( self, trans, query=None, **kwargs ):
def _orm_list( self, query=None, **kwargs ):
"""
Sends kwargs to build the query return all models found.
"""
query = query or self.query( trans, **kwargs )
query = query or self.query( **kwargs )
return query.all()
#def list( self, trans, query=None, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
def list( self, trans, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
#def list( self, query=None, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
def list( self, filters=None, order_by=None, limit=None, offset=None, **kwargs ):
"""
Returns all objects matching the given filters
"""
orm_filters, fn_filters = self._split_filters( filters )
if not fn_filters:
# if no fn_filtering required, we can use the 'all orm' version with limit offset
return self._orm_list( trans, filters=orm_filters, order_by=order_by, limit=limit, offset=offset, **kwargs )
return self._orm_list( filters=orm_filters, order_by=order_by, limit=limit, offset=offset, **kwargs )
# fn filters will change the number of items returnable by limit/offset - remove them here from the orm query
query = self.query( trans, filters=orm_filters, order_by=order_by, limit=None, offset=None, **kwargs )
query = self.query( filters=orm_filters, order_by=order_by, limit=None, offset=None, **kwargs )
items = query.all()
# apply limit, offset after SQL filtering
@@ -381,12 +381,12 @@ class ModelManager( object ):
yield item
yielded += 1
def by_ids( self, trans, ids, filters=None, **kwargs ):
def by_ids( self, ids, filters=None, **kwargs ):
"""
Returns an in-order list of models with the matching ids in `ids`.
"""
ids_filter = self.model_class.id.in_( ids )
found = self.list( trans, filters=self._munge_filters( ids_filter, filters ), **kwargs )
found = self.list( filters=self._munge_filters( ids_filter, filters ), **kwargs )
# TODO: this does not order by the original 'ids' array
# ...could use get (supposedly since found are in the session, the db won't be hit twice)
@@ -420,7 +420,7 @@ class ModelManager( object ):
in_order.append( item_dict[ id ] )
return in_order
def create( self, trans, flush=True, *args, **kwargs ):
def create( self, flush=True, *args, **kwargs ):
"""
Generically create a new model.
"""
@@ -431,13 +431,13 @@ class ModelManager( object ):
self.session().flush()
return item
def copy( self, trans, item, **kwargs ):
def copy( self, item, **kwargs ):
"""
Clone or copy an item.
"""
raise exceptions.NotImplemented( 'Abstract method' )
def update( self, trans, item, new_values, flush=True, **kwargs ):
def update( self, item, new_values, flush=True, **kwargs ):
"""
Given a dictionary of new values, update `item` and return it.
@@ -452,7 +452,7 @@ class ModelManager( object ):
return item
# TODO: yagni?
def associate( self, trans, associate_with, item, foreign_key_name=None ):
def associate( self, associate_with, item, foreign_key_name=None ):
"""
Generically associate `item` with `associate_with` based on `foreign_key_name`.
"""
@@ -460,7 +460,7 @@ class ModelManager( object ):
setattr( associate_with, foreign_key_name, item )
return item
def query_associated( self, trans, associated_model_class, item, foreign_key_name=None ):
def query_associated( self, associated_model_class, item, foreign_key_name=None ):
"""
Generically query other items that have been associated with this `item`.
"""
@@ -469,7 +469,7 @@ class ModelManager( object ):
return self.session().query( associated_model_class ).filter( foreign_key == item )
# a rename of sql DELETE to differentiate from the Galaxy notion of mark_as_deleted
# def destroy( self, trans, item, **kwargs ):
# def destroy( self, item, **kwargs ):
# return item
@@ -486,13 +486,21 @@ class ModelDeserializingError( exceptions.ObjectAttributeInvalidException ):
pass
class SkipAttribute( Exception ):
"""
Raise this inside a serializer to prevent the returned dictionary from having
a the associated key or value for this attribute.
"""
pass
class ModelSerializer( object ):
"""
Turns models into JSONable dicts.
Maintains a map of requestable keys and the Callable() serializer functions
that should be called for those keys.
E.g. { 'x' : lambda trans, item, key: item.x, ... }
E.g. { 'x' : lambda item, key: item.x, ... }
Note: if a key to serialize is not listed in the Serializer.serializable_keyset
or serializers, it will not be returned.
@@ -501,7 +509,7 @@ class ModelSerializer( object ):
my_serializer = MySerializer( app )
...
keys_to_serialize = [ 'id', 'name', 'attr1', 'attr2', ... ]
item_dict = MySerializer.serialize( trans, my_item, keys_to_serialize )
item_dict = MySerializer.serialize( my_item, keys_to_serialize )
"""
#: 'service' to use for getting urls - use class var to allow overriding when testing
url_for = staticmethod( routes.url_for )
@@ -534,8 +542,9 @@ class ModelSerializer( object ):
Register a map of attribute keys -> serializing functions that will serialize
the attribute.
"""
# to be overridden in subclasses
pass
self.serializers.update({
'id' : self.serialize_id,
})
def add_view( self, view_name, key_list, include_keys_from=None ):
"""
@@ -550,7 +559,7 @@ class ModelSerializer( object ):
self.serializable_keyset.update( key_list )
return key_list
def serialize( self, trans, item, keys ):
def serialize( self, item, keys, **context ):
"""
Serialize the model `item` to a dictionary.
@@ -558,18 +567,37 @@ class ModelSerializer( object ):
built from each key in `keys` that also exists in `serializers` and
values of calling the keyed/named serializers on item.
"""
# main interface fn for converting a model to a dict
# TODO: constrain context to current_user/whos_asking when that's all we need (trans)
returned = {}
for key in keys:
# check both serializers and serializable keys
if key in self.serializers:
returned[ key ] = self.serializers[ key ]( trans, item, key )
try:
returned[ key ] = self.serializers[ key ]( item, key, **context )
except SkipAttribute, skip:
# dont add this key if the deserializer threw this
pass
elif key in self.serializable_keyset:
returned[ key ] = self.default_serializer( trans, item, key )
returned[ key ] = self.default_serializer( item, key, **context )
# ignore bad/unreg keys
return returned
def default_serializer( self, trans, item, key ):
def skip( self, msg='skipped' ):
"""
To be called from inside a serializer to skip it.
Handy for config checks, information hiding, etc.
"""
raise SkipAttribute( msg )
def _remap_from( self, original_key ):
if original_key in self.serializers:
return self.serializers[ original_key ]
if original_key in self.serializable_keyset:
return lambda i, k, **c: self.default_serializer( i, original_key, **c )
raise KeyError( 'serializer not found for remap: ' + original_key )
def default_serializer( self, item, key, **context ):
"""
Serialize the `item`'s attribute named `key`.
"""
@@ -577,22 +605,23 @@ class ModelSerializer( object ):
return getattr( item, key )
# serializers for common galaxy objects
def serialize_date( self, trans, item, key ):
def serialize_date( self, item, key, **context ):
"""
Serialize a date attribute of `item`.
"""
date = getattr( item, key )
return date.isoformat() if date is not None else None
def serialize_id( self, trans, item, key ):
def serialize_id( self, item, key, **context ):
"""
Serialize an id attribute of `item`.
"""
id = getattr( item, key )
# Note: it may not be best to encode the id at this layer
return self.app.security.encode_id( id ) if id is not None else None
# serializing to a view where a view is a predefied list of keys to serialize
def serialize_to_view( self, trans, item, view=None, keys=None, default_view=None ):
def serialize_to_view( self, item, view=None, keys=None, default_view=None, **context ):
"""
Use a predefined list of keys (the string `view`) and any additional keys
listed in `keys`.
@@ -603,7 +632,7 @@ class ModelSerializer( object ):
no `view` or `keys`: use the `default_view` if any
`view` and `keys`: combine both into one list of keys
"""
#TODO: default view + view makes no sense outside the API.index context - move default view there
#TODO: default view + view makes no sense outside the API.index context - move default view there
all_keys = []
keys = keys or []
# chose explicit over concise here
@@ -618,7 +647,7 @@ class ModelSerializer( object ):
elif default_view:
all_keys = self._view_to_keys( default_view )
return self.serialize( trans, item, all_keys )
return self.serialize( item, all_keys, **context )
def _view_to_keys( self, view=None ):
"""
@@ -668,16 +697,17 @@ class ModelDeserializer( object ):
# to be overridden in subclasses
pass
def deserialize( self, trans, item, data, flush=True ):
def deserialize( self, item, data, flush=True, **context ):
"""
Convert an incoming serialized dict into values that can be
directly assigned to an item's attributes and assign them
"""
# TODO: constrain context to current_user/whos_asking when that's all we need (trans)
sa_session = self.app.model.context
new_dict = {}
for key, val in data.items():
if key in self.deserializers:
new_dict[ key ] = self.deserializers[ key ]( trans, item, key, val )
new_dict[ key ] = self.deserializers[ key ]( item, key, val, **context )
# !important: don't error on unreg. keys -- many clients will add weird ass keys onto the model
# TODO:?? add and flush here or in manager?
@@ -688,7 +718,7 @@ class ModelDeserializer( object ):
return new_dict
# ... common deserializers for primitives
def default_deserializer( self, trans, item, key, val ):
def default_deserializer( self, item, key, val, **context ):
"""
If the incoming `val` is different than the `item` value change it
and, in either case, return the value.
@@ -700,28 +730,28 @@ class ModelDeserializer( object ):
setattr( item, key, val )
return val
def deserialize_basestring( self, trans, item, key, val, convert_none_to_empty=False ):
def deserialize_basestring( self, item, key, val, convert_none_to_empty=False, **context ):
val = '' if ( convert_none_to_empty and val is None ) else self.validate.basestring( key, val )
return self.default_deserializer( trans, item, key, val )
return self.default_deserializer( item, key, val, **context )
def deserialize_bool( self, trans, item, key, val ):
def deserialize_bool( self, item, key, val, **context ):
val = self.validate.bool( key, val )
return self.default_deserializer( trans, item, key, val )
return self.default_deserializer( item, key, val, **context )
def deserialize_int( self, trans, item, key, val, min=None, max=None ):
def deserialize_int( self, item, key, val, min=None, max=None, **context ):
val = self.validate.int_range( key, val, min, max )
return self.default_deserializer( trans, item, key, val )
return self.default_deserializer( item, key, val, **context )
#def deserialize_date( self, trans, item, key, val ):
#def deserialize_date( self, item, key, val ):
# #TODO: parse isoformat date into date object
# ... common deserializers for Galaxy
def deserialize_genome_build( self, trans, item, key, val ):
def deserialize_genome_build( self, item, key, val, **context ):
"""
Make sure `val` is a valid dbkey and assign it.
"""
val = self.validate.genome_build( key, val )
return self.default_deserializer( trans, item, key, val )
return self.default_deserializer( item, key, val, **context )
class ModelValidator( object ):
@@ -801,7 +831,7 @@ class ModelValidator( object ):
# IOW: fallback to string validation
return self.basestring( key, val )
# def slug( self, trans, item, key, val ):
# def slug( self, item, key, val ):
# """validate slug"""
# pass
@@ -854,9 +884,11 @@ class ModelFilterParser( object ):
"""
Set up, extend, or alter `orm_filter_parsers` and `fn_filter_parsers`.
"""
# note: these are the default filters for all models
self.orm_filter_parsers.update({
# (prob.) applicable to all models
'id' : { 'op': ( 'in' ), 'val': self.parse_id_list },
'id' : { 'op': ( 'in' ) },
'encoded_id' : { 'column' : 'id', 'op': ( 'in' ), 'val': self.parse_id_list },
# dates can be directly passed through the orm into a filter (no need to parse into datetime object)
'create_time' : { 'op': ( 'le', 'ge' ) },
'update_time' : { 'op': ( 'le', 'ge' ) },
@@ -895,7 +927,7 @@ class ModelFilterParser( object ):
# by convention, assume most val parsers raise ValueError
except ValueError, val_err:
raise exceptions.RequestParameterInvalidException( 'unparsable value for filter',
column=attr, operation=op, value=val )
column=attr, operation=op, value=val, ValueError=str( val_err ) )
# if neither of the above work, raise an error with how-to info
#TODO: send back all valid filter keys in exception for added user help
@@ -904,6 +936,7 @@ class ModelFilterParser( object ):
# ---- fn filters
def _parse_fn_filter( self, attr, op, val ):
"""
Attempt to parse a non-ORM filter function.
"""
# fn_filter_list is a dict: fn_filter_list[ attr ] = { 'opname1' : opfn1, 'opname2' : opfn2, etc. }
@@ -927,18 +960,33 @@ class ModelFilterParser( object ):
# ---- ORM filters
def _parse_orm_filter( self, attr, op, val ):
"""
Attempt to parse a ORM-based filter.
Using SQLAlchemy, this would yield a sql.elements.BinaryExpression.
"""
# orm_filter_list is a dict: orm_filter_list[ attr ] = <list of allowed ops>
# attr must be a whitelisted column
column = self.model_class.table.columns.get( attr )
column_map = self.orm_filter_parsers.get( attr, None )
if column is None or not column_map:
if not column_map:
# no column mapping (not whitelisted)
return None
# attr must be a whitelisted column by attr name or by key passed in column_map
# note: column_map[ 'column' ] takes precedence
column = self.model_class.table.columns.get( attr )
if 'column' in column_map:
remap_to = column_map[ 'column' ]
column = self.model_class.table.columns.get( remap_to )
if column is None:
# no orm column
return None
# op must be whitelisted: contained in the list orm_filter_list[ attr ][ 'op' ]
allowed_ops = column_map.get( 'op' )
if op not in allowed_ops:
return None
op = self._convert_op_string_to_fn( column, op )
if not op:
return None
# parse the val from string using the 'val' parser if present (otherwise, leave as string)
val_parser = column_map.get( 'val', None )
if val_parser:
@@ -963,65 +1011,23 @@ class ModelFilterParser( object ):
fn_name = 'in_'
# get the column fn using the op_string and error if not a callable attr
#TODO: special case 'not in' - or disallow?
# TODO: special case 'not in' - or disallow?
op_fn = getattr( column, fn_name, None )
if not op_fn or not callable( op_fn ):
return None
return op_fn
# ---- preset fn_filters: dictionaries of standard filter ops for standard datatypes
def string_standard_ops( self, key ):
return {
'op' : {
'eq' : lambda i, v: v == getattr( i, key ),
'contains' : lambda i, v: v in getattr( i, key ),
}
}
# --- more parsers! yay!
#TODO: These should go somewhere central - we've got ~6 parser modules/sections now
#TODO: to annotatable
def _owner_annotation( self, item ):
"""
Get the annotation by the item's owner.
"""
if not item.user:
return None
for annotation in item.annotations:
if annotation.user == item.user:
return annotation.annotation
return None
def filter_annotation_contains( self, item, val ):
"""
Test whether `val` is in the owner's annotation.
"""
owner_annotation = self._owner_annotation( item )
if owner_annotation is None:
return False
return val in owner_annotation
#TODO: to taggable
def _tag_str_gen( self, item ):
"""
Return a list of strings built from the item's tags.
"""
#TODO: which user is this? all?
for tag in item.tags:
tag_str = tag.user_tname
if tag.value is not None:
tag_str += ":" + tag.user_value
yield tag_str
def filter_has_partial_tag( self, item, val ):
"""
Return True if any tag partially contains `val`.
"""
for tag_str in self._tag_str_gen( item ):
if val in tag_str:
return True
return False
def filter_has_tag( self, item, val ):
"""
Return True if any tag exactly equals `val`.
"""
for tag_str in self._tag_str_gen( item ):
if val == tag_str:
return True
return False
#TODO: These should go somewhere central - we've got ~6 parser modules/sections now
def parse_bool( self, bool_string ):
"""
Parse a boolean from a string.
+3 -3
View File
@@ -277,7 +277,7 @@ class DatasetCollectionManager( object ):
if src_type == 'hda':
decoded_id = int( trans.app.security.decode_id( encoded_id ) )
element = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
element = self.hda_manager.get_accessible( decoded_id, trans.user )
elif src_type == 'ldda':
element = self.ldda_manager.get( trans, encoded_id )
elif src_type == 'hdca':
@@ -312,9 +312,9 @@ class DatasetCollectionManager( object ):
instance_id = int( trans.app.security.decode_id( id ) )
collection_instance = trans.sa_session.query( trans.app.model.HistoryDatasetCollectionAssociation ).get( instance_id )
if check_ownership:
self.history_manager.error_unless_owner( trans, collection_instance.history, trans.user )
self.history_manager.error_unless_owner( collection_instance.history, trans.user, current_history=trans.history )
if check_accessible:
self.history_manager.error_unless_accessible( trans, collection_instance.history, trans.user )
self.history_manager.error_unless_accessible( collection_instance.history, trans.user, current_history=trans.history )
return collection_instance
def __get_library_collection_instance( self, trans, id, check_ownership=False, check_accessible=True ):
+39 -35
View File
@@ -15,6 +15,7 @@ import logging
log = logging.getLogger( __name__ )
#TODO: for lack of a manager file for the config. May well be better in config.py? Circ imports?
class ConfigSerializer( base.ModelSerializer ):
def __init__( self, app ):
@@ -23,61 +24,64 @@ class ConfigSerializer( base.ModelSerializer ):
self.default_view = 'all'
self.add_view( 'all', self.serializers.keys() )
def default_serializer( self, trans, config, key ):
return config.get( key, None )
def _defaults_to( self, default ):
return lambda t, i, k: i.get( k, default )
def default_serializer( self, config, key ):
return getattr( config, key, None )
def add_serializers( self ):
def _defaults_to( default ):
return lambda i, k, **c: getattr( i, k, default )
self.serializers = {
#TODO: this is available from user data, remove
'is_admin_user' : lambda *a: False,
'is_admin_user' : lambda *a, **c: False,
'brand' : lambda t, i, k: i.get( k, "" ),
'brand' : _defaults_to( '' ),
#TODO: this doesn't seem right
'logo_url' : lambda t, i, k: web.url_for( i.get( k, '/') ),
'terms_url' : lambda t, i, k: i.get( k, "" ),
'logo_url' : lambda i, k, **c: self.url_for( i.get( k, '/' ) ),
'terms_url' : _defaults_to( '' ),
#TODO: don't hardcode here - hardcode defaults once in config.py
'wiki_url' : self._defaults_to( "http://galaxyproject.org/" ),
'search_url' : self._defaults_to( "http://galaxyproject.org/search/usegalaxy/" ),
'mailing_lists' : self._defaults_to( "http://wiki.galaxyproject.org/MailingLists" ),
'screencasts_url' : self._defaults_to( "http://vimeo.com/galaxyproject" ),
'citation_url' : self._defaults_to( "http://wiki.galaxyproject.org/CitingGalaxy" ),
'support_url' : self._defaults_to( "http://wiki.galaxyproject.org/Support" ),
'lims_doc_url' : self._defaults_to( "http://main.g2.bx.psu.edu/u/rkchak/p/sts" ),
'biostar_url' : lambda t, i, k: i.biostar_url,
'biostar_url_redirect' : lambda *a: web.url_for( controller='biostar', action='biostar_redirect',
qualified=True ),
'wiki_url' : _defaults_to( "http://galaxyproject.org/" ),
'search_url' : _defaults_to( "http://galaxyproject.org/search/usegalaxy/" ),
'mailing_lists' : _defaults_to( "https://wiki.galaxyproject.org/MailingLists" ),
'screencasts_url' : _defaults_to( "https://vimeo.com/galaxyproject" ),
'citation_url' : _defaults_to( "https://wiki.galaxyproject.org/CitingGalaxy" ),
'support_url' : _defaults_to( "https://wiki.galaxyproject.org/Support" ),
'lims_doc_url' : _defaults_to( "https://usegalaxy.org/u/rkchak/p/sts" ),
'biostar_url' : _defaults_to( '' ),
'biostar_url_redirect' : lambda *a, **c: self.url_for( controller='biostar', action='biostar_redirect',
qualified=True ),
'allow_user_creation' : lambda t, i, k: i.allow_user_creation,
'use_remote_user' : lambda t, i, k: i.use_remote_user,
'remote_user_logout_href' : lambda t, i, k: i.remote_user_logout_href,
'enable_cloud_launch' : self._defaults_to( False ),
'datatypes_disable_auto' : self._defaults_to( False ),
'allow_user_dataset_purge' : self._defaults_to( False ),
'enable_unique_workflow_defaults' : self._defaults_to( False ),
'allow_user_creation' : lambda i, k, **c: i.allow_user_creation,
'use_remote_user' : _defaults_to( None ),
'remote_user_logout_href' : _defaults_to( '' ),
'enable_cloud_launch' : _defaults_to( False ),
'datatypes_disable_auto' : _defaults_to( False ),
'allow_user_dataset_purge' : _defaults_to( False ),
'enable_unique_workflow_defaults' : _defaults_to( False ),
'nginx_upload_path' : self._defaults_to( web.url_for( controller='api', action='tools' ) ),
'ftp_upload_dir' : self._defaults_to( None ),
'ftp_upload_site' : self._defaults_to( None ),
'nginx_upload_path' : _defaults_to( self.url_for( controller='api', action='tools' ) ),
'ftp_upload_dir' : _defaults_to( None ),
'ftp_upload_site' : _defaults_to( None ),
'version_major' : _defaults_to( None ),
}
class AdminConfigSerializer( ConfigSerializer ):
"""Config attributes viewable by admin users"""
# config attributes viewable by admin users
def add_serializers( self ):
super( AdminConfigSerializer, self ).add_serializers()
def _defaults_to( default ):
return lambda i, k, **c: getattr( i, k, default )
self.serializers.update({
#TODO: this is available from user data, remove
'is_admin_user' : lambda *a: True,
'library_import_dir' : self._defaults_to( None ),
'user_library_import_dir' : self._defaults_to( None ),
'allow_library_path_paste' : self._defaults_to( None ),
'allow_user_creation' : self._defaults_to( False ),
'allow_user_deletion' : self._defaults_to( False ),
'library_import_dir' : _defaults_to( None ),
'user_library_import_dir' : _defaults_to( None ),
'allow_library_path_paste' : _defaults_to( False ),
'allow_user_creation' : _defaults_to( False ),
'allow_user_deletion' : _defaults_to( False ),
})
+151
View File
@@ -0,0 +1,151 @@
"""
Manager mixins to unify the interface into things that can contain: Datasets
and other (nested) containers.
(e.g. DatasetCollections, Histories, LibraryFolders)
Histories should be DatasetCollections.
Libraries should be DatasetCollections.
"""
import operator
from galaxy import model
import galaxy.exceptions
import galaxy.util
from galaxy.managers import base
import logging
log = logging.getLogger( __name__ )
# ====
class ContainerManagerMixin( object ):
"""
A class that tracks/contains two types of items:
1) some non-container object (such as datasets)
2) other sub-containers nested within this one
Levels of nesting are not considered here; In other words,
each of the methods below only work on the first level of
nesting.
"""
# TODO: this should be an open mapping (not just 2)
#: the classes that can be contained
contained_class = None
subcontainer_class = None
#: how any contents lists produced are ordered
order_contents_on = None
# ---- interface
def contents( self, container ):
"""
Returns both types of contents: filtered and in some order.
"""
iters = []
iters.append( self.contained( container ) )
iters.append( self.subcontainers( container ) )
return galaxy.util.merge_sorted_iterables( self.order_contents_on, *iters )
def contained( self, container, **kwargs ):
"""
Returns non-container objects.
"""
return self._filter_contents( container, self.contained_class, **kwargs )
def subcontainers( self, container, **kwargs ):
"""
Returns only the containers within this one.
"""
return self._filter_contents( container, self.subcontainer_class, **kwargs )
# ---- private
def _filter_contents( self, container, content_class, **kwargs ):
# TODO: use list (or by_history etc.)
container_filter = self._filter_to_contained( container, content_class )
query = self.session().query( content_class ).filter( container_filter )
return query
def _filter_to_contained( self, container, content_class ):
raise galaxy.exceptions.NotImplemented( 'Abstract class' )
def _content_manager( self, content ):
raise galaxy.exceptions.NotImplemented( 'Abstract class' )
class HistoryAsContainerManagerMixin( ContainerManagerMixin ):
contained_class = model.HistoryDatasetAssociation
subcontainer_class = model.HistoryDatasetCollectionAssociation
order_contents_on = operator.attrgetter( 'hid' )
def _filter_to_contained( self, container, content_class ):
return content_class.history == container
def _content_manager( self, content ):
# type sniffing is inevitable
if isinstance( content, model.HistoryDatasetAssociation ):
return self.hda_manager
elif isinstance( content, model.HistoryDatasetCollectionAssociation ):
return self.hdca_manager
raise TypeError( 'Unknown contents class: ' + str( content ) )
class LibraryFolderAsContainerManagerMixin( ContainerManagerMixin ):
# can contain two types of subcontainer: LibraryFolder, LibraryDatasetCollectionAssociation
# has as the top level container: Library
contained_class = model.LibraryDataset
subcontainer_class = model.LibraryFolder
# subcontainer_class = model.LibraryDatasetCollectionAssociation
order_contents_on = operator.attrgetter( 'create_time' )
def _filter_to_contained( self, container, content_class ):
if content_class == self.subcontainer_class:
return self.subcontainer_class.parent == container
return self.contained_class.folder == container
def _content_manager( self, content ):
# type snifffing is inevitable
if isinstance( content, model.LibraryDataset ):
return self.lda_manager
elif isinstance( content, model.LibraryFolder ):
return self.folder_manager
raise TypeError( 'Unknown contents class: ' + str( content ) )
class DatasetCollectionAsContainerManagerMixin( ContainerManagerMixin ):
# (note: unlike the other collections, dc's wrap both contained and subcontainers in this class)
contained_class = model.DatasetCollectionElement
subcontainer_class = model.DatasetCollection
order_contents_on = operator.attrgetter( 'element_index' )
def _filter_to_contained( self, container, content_class ):
return content_class.collection == container
def _content_manager( self, content ):
# type snifffing is inevitable
if isinstance( content, model.DatasetCollectionElement ):
return self.collection_manager
elif isinstance( content, model.DatasetCollection ):
return self.collection_manager
raise TypeError( 'Unknown contents class: ' + str( content ) )
# ====
class ContainableModelMixin:
"""
Mixin for that which goes in a container.
"""
# ---- interface
def parent_container( self, containable ):
"""
Return this item's parent container or None if unrecorded.
"""
raise galaxy.exceptions.NotImplemented( 'Abstract class' )
def set_parent_container( self, containable, new_parent_container ):
raise galaxy.exceptions.NotImplemented( 'Abstract class' )
+399 -144
View File
@@ -3,10 +3,12 @@ Manager and Serializer for Datasets.
"""
from galaxy import model
from galaxy import exceptions
import galaxy.datatypes.metadata
from galaxy.managers import base
from galaxy.managers import secured
from galaxy.managers import deletable
from galaxy.managers import rbac_secured
from galaxy.managers import users
import logging
@@ -24,184 +26,132 @@ class DatasetManager( base.ModelManager, secured.AccessibleManagerMixin, deletab
def __init__( self, app ):
super( DatasetManager, self ).__init__( app )
self.permissions = DatasetRBACPermissions( app )
# need for admin test
self.user_manager = users.UserManager( app )
def create( self, trans, flush=True, **kwargs ):
def create( self, manage_roles=None, access_roles=None, flush=True, **kwargs ):
"""
Create and return a new Dataset object.
"""
# default to NEW state on new datasets
kwargs.update( dict( state=( kwargs.get( 'state', model.Dataset.states.NEW ) ) ) )
dataset = model.Dataset( **kwargs )
self.session().add( dataset )
self.permissions.set( dataset, manage_roles, access_roles, flush=False )
self.app.model.context.add( dataset )
if flush:
self.app.model.context.flush()
self.session().flush()
return dataset
# def copy( self, dataset, **kwargs ):
# """
# Clone, update, and return the given dataset.
# """
# pass
def copy( self, dataset, **kwargs ):
raise galaxy.exceptions.NotImplemented( 'Datasets cannot be copied' )
# def to_hda( self, trans, dataset, history, **kwargs ):
# """
# Create an hda from this dataset.
# """
# pass
# def to_ldda( self, trans, dataset, library_folder, **kwargs ):
# """
# Create an ldda from this dataset.
# """
# pass
# TODO: this may be more conv. somewhere else
# TODO: how to allow admin bypass?
def error_unless_dataset_purge_allowed( self, trans, item, msg=None ):
if not self.app.config.allow_user_dataset_purge:
msg = msg or 'This instance does not allow user dataset purging'
raise exceptions.ConfigDoesNotAllowException( msg )
return item
def purge( self, trans, dataset, flush=True ):
def purge( self, dataset, flush=True ):
"""
Remove the object_store/file for this dataset from storage and mark
as purged.
:raises exceptions.ConfigDoesNotAllowException: if the instance doesn't allow
"""
self.error_unless_dataset_purge_allowed( trans, dataset )
self.error_unless_dataset_purge_allowed( dataset )
# the following also marks dataset as purged and deleted
dataset.full_delete()
self.app.model.context.add( dataset )
self.session().add( dataset )
if flush:
self.app.model.context.flush()
self.session().flush()
return dataset
# TODO: this may be more conv. somewhere else
# TODO: how to allow admin bypass?
def error_unless_dataset_purge_allowed( self, msg=None ):
if not self.app.config.allow_user_dataset_purge:
msg = msg or 'This instance does not allow user dataset purging'
raise exceptions.ConfigDoesNotAllowException( msg )
# .... accessibility
# datasets can implement the accessible interface, but accessibility is checked in an entirely different way
# than those resources that have a user attribute (histories, pages, etc.)
def is_accessible( self, trans, dataset, user ):
def is_accessible( self, dataset, user, **kwargs ):
"""
Is this dataset readable/viewable to user?
"""
if self.user_manager.is_admin( trans, user ):
if self.user_manager.is_admin( user ):
return True
if self.has_access_permission( trans, dataset, user ):
if self.has_access_permission( dataset, user ):
return True
return False
def has_access_permission( self, trans, dataset, user ):
def has_access_permission( self, dataset, user ):
"""
Return T/F if the user has role-based access to the dataset.
"""
roles = user.all_roles() if user else []
return self.app.security_agent.can_access_dataset( roles, dataset )
# TODO: these need work
def _access_permission( self, trans, dataset, user=None, role=None ):
"""
Return most recent DatasetPermissions for the dataset and user.
"""
security_agent = self.app.security_agent
access_action = security_agent.permitted_actions.DATASET_ACCESS.action
# get a list of role ids to check access for (defaulting to all_roles)
user_roles = [ role ] if role else user.all_roles()
user_role_ids = [ r.id for r in user_roles ]
query = ( self.app.model.context.query( model.DatasetPermissions )
.filter( model.DatasetPermissions.action == access_action )
.filter( model.DatasetPermissions.dataset == dataset )
.filter( model.DatasetPermissions.role_id.in_( user_role_ids ) ) )
# TODO:?? most recent?
return query.first()
def _create_access_permission( self, trans, dataset, role, flush=True ):
"""
Create a new DatasetPermissions for the given dataset and role.
"""
access_action = self.app.security_agent.permitted_actions.DATASET_ACCESS.action
permission = model.DatasetPermissions( access_action, dataset, role )
self.app.model.context.add( permission )
if flush:
self.app.model.context.flush()
return permission
# def give_access_permission( self, trans, dataset, user, flush=True ):
# """
# """
# # for now, use the user's private role
# security_agent = self.app.security_agent
# user_role = security_agent.get_private_user_role( user )
#
# existing_permission = self._access_permission( trans, dataset, role=user_role )
# print 'existing access_perm:', existing_permission
# if existing_permission:
# return dataset
#
# permission = self._create_access_permission( trans, dataset, user_role )
# print 'access_roles:', [ role.name for role in dataset.get_access_roles( trans ) ]
#
# #access_action = security_agent.permitted_actions.DATASET_ACCESS.action
# #access_action = security_agent.get_action( access_action )
# #permissions = { access_action : [ user_role ] }
# #security_agent.set_dataset_permission( dataset, permissions )
#
# #self.app.model.context.add( dataset )
# #if flush:
# # self.app.model.context.flush()
#
# dbl_chk = self._access_permission( trans, dataset, role=user_role )
# print 'access_perm:', dbl_chk
#
# return dataset
# def remove_access_permission( self, trans, dataset, user ):
# """
# """
# pass
# .... manage/modify
# def has_manage_permission( self, trans, dataset, user ):
# """
# """
# pass
#
# def give_manage_permission( self, trans, dataset, user ):
# """
# """
# pass
#
# def remove_manage_permission( self, trans, dataset, user ):
# """
# """
# pass
# TODO: implement above for groups
# TODO: datatypes?
# .... data, object_store
# TODO: SecurityAgentDatasetRBACPermissions( object ):
class DatasetRBACPermissions( object ):
def __init__( self, app ):
self.app = app
self.access = rbac_secured.AccessDatasetRBACPermission( app )
self.manage = rbac_secured.ManageDatasetRBACPermission( app )
# TODO: temporary facade over security_agent
def available_roles( self, trans, dataset, controller='root' ):
return self.app.security_agent.get_legitimate_roles( trans, dataset, controller )
# TODO: not enough by a long shot
def get( self, dataset, flush=True ):
manage = self.manage.by_dataset( dataset )
access = self.access.by_dataset( dataset )
return ( manage, access )
def set( self, dataset, manage_roles, access_roles, flush=True ):
manage = self.manage.set( dataset, manage_roles or [], flush=False )
access = self.access.set( dataset, access_roles or [], flush=flush )
return ( manage, access )
# ---- conv. settings
def set_public_with_single_manager( self, dataset, user, flush=True ):
manage = self.manage.grant( dataset, user, flush=flush )
self.access.clear( dataset, flush=False )
return ( [ manage ], [] )
def set_private_to_one_user( self, dataset, user, flush=True ):
manage = self.manage.grant( dataset, user, flush=False )
access = self.access.set_private( dataset, user, flush=flush )
return ( [ manage ], access )
class DatasetSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
def __init__( self, app ):
super( DatasetSerializer, self ).__init__( app )
self.dataset_manager = DatasetManager( app )
self.default_view = 'summary'
self.add_view( 'summary', [
'id',
'create_time', 'update_time',
'create_time',
'update_time',
'state',
'deleted', 'purged', 'purgable',
'deleted',
'purged',
'purgable',
# 'object_store_id',
# 'external_filename',
# 'extra_files_path',
'file_size', 'total_size',
'file_size',
'total_size',
'uuid',
])
# could do visualizations and/or display_apps
@@ -211,23 +161,73 @@ class DatasetSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin
deletable.PurgableSerializerMixin.add_serializers( self )
self.serializers.update({
'id': self.serialize_id,
'create_time': self.serialize_date,
'update_time': self.serialize_date,
'uuid': lambda t, i, k: str( i.uuid ) if i.uuid else None,
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
'uuid' : lambda i, k, **c: str( i.uuid ) if i.uuid else None,
'file_name' : self.serialize_file_name,
'extra_files_path' : self.serialize_extra_files_path,
'permissions' : self.serialize_permissions,
'total_size' : lambda i, k, **c: int( i.get_total_size() ),
'file_size' : lambda i, k, **c: int( i.get_size() )
})
def serialize_file_name( self, dataset, key, **context ):
"""
If the config allows or the user is admin, return the file name
of the file that contains this dataset's data.
"""
# TODO: allow admin
# conifg due to cost of operation
if not self.app.config.expose_dataset_path:
self.skip()
return dataset.file_name
def serialize_extra_files_path( self, dataset, key, **context ):
"""
If the config allows or the user is admin, return the file path.
"""
# TODO: allow admin
# conifg due to cost of operation
if not self.app.config.expose_dataset_path:
self.skip()
return dataset.extra_files_path
def serialize_permissions( self, dataset, key, **context ):
"""
"""
permissions = {}
# TODO: use rbac permissions?
return permissions
class DatasetDeserializer( base.ModelDeserializer, deletable.PurgableDeserializerMixin ):
model_manager_class = DatasetManager
def add_deserializers( self ):
super( DatasetDeserializer, self ).add_deserializers()
# not much to set here besides permissions and purged/deleted
deletable.PurgableDeserializerMixin.add_deserializers( self )
self.deserializers.update({
'permissions' : self.deserialize_permissions,
})
def deserialize_permissions( self, dataset, key, value, **context ):
"""
"""
permissions = {}
# TODO: test if different - it's an expensive op
# TODO: validation will be tricky
# TODO: use rbac permissions?
return permissions
# ============================================================================= AKA DatasetInstanceManager
class DatasetAssociationManager( base.ModelManager, secured.AccessibleManagerMixin, deletable.PurgableManagerMixin ):
class DatasetAssociationManager( base.ModelManager,
secured.AccessibleManagerMixin,
deletable.PurgableManagerMixin ):
"""
DatasetAssociation/DatasetInstances are intended to be working
proxies to a Dataset, associated with either a library or a
@@ -241,32 +241,227 @@ class DatasetAssociationManager( base.ModelManager, secured.AccessibleManagerMix
super( DatasetAssociationManager, self ).__init__( app )
self.dataset_manager = DatasetManager( app )
def is_accessible( self, trans, dataset_assoc, user ):
def is_accessible( self, dataset_assoc, user, **kwargs ):
"""
Is this DA accessible to `user`?
"""
# defer to the dataset
return self.dataset_manager.is_accessible( trans, dataset_assoc.dataset, user )
return self.dataset_manager.is_accessible( dataset_assoc.dataset, user )
# def metadata( self, trans, dataset_assoc ):
# """
# Return the metadata collection.
# """
# # get metadata
# pass
def purge( self, dataset_assoc, flush=True ):
"""
Purge this DatasetInstance and the dataset underlying it.
"""
# error here if disallowed - before jobs are stopped
# TODO: this check may belong in the controller
self.dataset_manager.error_unless_dataset_purge_allowed()
super( DatasetAssociationManager, self ).purge( dataset_assoc, flush=flush )
# def is_being_used( self, trans, dataset_assoc ):
# """
# """
# #TODO: check history_associations, library_associations
# pass
# stop any jobs outputing the dataset_assoc
if dataset_assoc.creating_job_associations:
job = dataset_assoc.creating_job_associations[0].job
if not job.finished:
# signal to stop the creating job
job.mark_deleted( self.app.config.track_jobs_in_database )
self.app.job_manager.job_stop_queue.put( job.id )
# more importantly, purge underlying dataset as well
if dataset_assoc.dataset.user_can_purge:
self.dataset_manager.purge( dataset_assoc.dataset )
return dataset_assoc
def by_user( self, user ):
"""
"""
raise galaxy.exceptions.NotImplemented( 'Abstract Method' )
# .... associated job
def creating_job( self, dataset_assoc ):
# TODO: is this needed? Can't you use the dataset_assoc.creating_job attribute? When is this None?
# TODO: this would be even better if outputs and inputs were the underlying datasets
job = None
for job_output_assoc in dataset_assoc.creating_job_associations:
job = job_output_assoc.job
break
return job
def stop_creating_job( self, dataset_assoc ):
"""
Stops an dataset_assoc's creating job if all the job's other outputs are deleted.
"""
# TODO: use in purge above
RUNNING_STATES = (
self.app.model.Job.states.QUEUED,
self.app.model.Job.states.RUNNING,
self.app.model.Job.states.NEW
)
if dataset_assoc.parent_id is None and len( dataset_assoc.creating_job_associations ) > 0:
# Mark associated job for deletion
job = dataset_assoc.creating_job_associations[0].job
if job.state in RUNNING_STATES:
# Are *all* of the job's other output datasets deleted?
if job.check_if_output_datasets_deleted():
job.mark_deleted( self.app.config.track_jobs_in_database )
self.app.job_manager.job_stop_queue.put( job.id )
return True
return False
class DatasetAssociationSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
class _UnflattenedMetadataDatasetAssociationSerializer( base.ModelSerializer,
deletable.PurgableSerializerMixin ):
def __init__( self, app ):
self.dataset_serializer = DatasetSerializer( app )
super( _UnflattenedMetadataDatasetAssociationSerializer, self ).__init__( app )
def add_serializers( self ):
super( _UnflattenedMetadataDatasetAssociationSerializer, self ).add_serializers()
deletable.PurgableSerializerMixin.add_serializers( self )
self.serializers.update({
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
# underlying dataset
'dataset' : lambda i, k, **c: self.dataset_serializer.serialize_to_view( i.dataset, view='summary', **c ),
'dataset_id' : self._proxy_to_dataset( key='id' ),
# TODO: why is this named uuid!? The da doesn't have a uuid - it's the underlying dataset's uuid!
'uuid' : self._proxy_to_dataset( key='uuid' ),
# 'dataset_uuid' : self._proxy_to_dataset( key='uuid' ),
'file_name' : self._proxy_to_dataset( serializer=self.dataset_serializer.serialize_file_name ),
'extra_files_path' : self._proxy_to_dataset( serializer=self.dataset_serializer.serialize_extra_files_path ),
'permissions' : self._proxy_to_dataset( serializer=self.dataset_serializer.serialize_permissions),
# TODO: do the sizes proxy accurately/in the same way?
'size' : lambda i, k, **c: int( i.get_size() ),
'file_size' : lambda i, k, **c: self.serializers[ 'size' ]( i, k, **c ),
'nice_size' : lambda i, k, **c: i.get_size( nice_size=True ),
# common to lddas and hdas - from mapping.py
'copied_from_history_dataset_association_id' : self.serialize_id,
'copied_from_library_dataset_dataset_association_id': self.serialize_id,
'info' : lambda i, k, **c: i.info.strip() if isinstance( i.info, basestring ) else i.info,
'blurb' : lambda i, k, **c: i.blurb,
'peek' : lambda i, k, **c: i.display_peek() if i.peek and i.peek != 'no peek' else None,
'meta_files' : self.serialize_meta_files,
'metadata' : self.serialize_metadata,
'parent_id' : self.serialize_id,
'designation' : lambda i, k, **c: i.designation,
# 'extended_metadata' : self.serialize_extended_metadata,
# 'extended_metadata_id' : self.serialize_id,
# remapped
'genome_build' : lambda i, k, **c: i.dbkey,
# derived (not mapped) attributes
'data_type' : lambda i, k, **c: i.datatype.__class__.__module__ + '.' + i.datatype.__class__.__name__,
# TODO: conversions
# TODO: metadata/extra files
})
# this an abstract superclass, so no views created
# because of that: we need to add a few keys that will use the default serializer
self.serializable_keyset.update([ 'name', 'state', 'tool_version', 'extension', 'visible', 'dbkey' ])
def _proxy_to_dataset( self, serializer=None, key=None ):
if key:
serializer = self.dataset_serializer.serializers.get( key )
if serializer:
return lambda i, k, **c: serializer( i.dataset, key or k, **c )
raise TypeError( 'kwarg serializer or key needed')
def serialize_meta_files( self, dataset_assoc, key, **context ):
"""
Cycle through meta files and return them as a list of dictionaries.
"""
meta_files = []
for meta_type in dataset_assoc.metadata.spec.keys():
if isinstance( dataset_assoc.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
meta_files.append( dict( file_type=meta_type ) )
return meta_files
def serialize_metadata( self, dataset_assoc, key, excluded=None, **context ):
"""
Cycle through metadata and return as dictionary.
"""
# dbkey is a repeat actually (metadata_dbkey == genome_build)
# excluded = [ 'dbkey' ] if excluded is None else excluded
excluded = [] if excluded is None else excluded
metadata = {}
for name, spec in dataset_assoc.metadata.spec.items():
if name in excluded:
continue
val = dataset_assoc.metadata.get( name )
# NOTE: no files
if isinstance( val, model.MetadataFile ):
# only when explicitly set: fetching filepaths can be expensive
if not self.app.config.expose_dataset_path:
continue
val = val.file_name
# TODO:? possibly split this off?
# If no value for metadata, look in datatype for metadata.
elif val is None and hasattr( dataset_assoc.datatype, name ):
val = getattr( dataset_assoc.datatype, name )
metadata[ name ] = val
return metadata
class DatasetAssociationSerializer( _UnflattenedMetadataDatasetAssociationSerializer ):
# TODO: remove this class - metadata should be a sub-object instead as in the superclass
def add_serializers( self ):
super( DatasetAssociationSerializer, self ).add_serializers()
deletable.PurgableSerializerMixin.add_serializers( self )
# remove the single nesting key here
del self.serializers[ 'metadata' ]
def serialize( self, dataset_assoc, keys, **context ):
"""
Override to add metadata as flattened keys on the serialized DatasetInstance.
"""
# if 'metadata' isn't removed from keys here serialize will retrieve the un-serializable MetadataCollection
# TODO: remove these when metadata is sub-object
KEYS_HANDLED_SEPARATELY = ( 'metadata', )
left_to_handle = self._pluck_from_list( keys, KEYS_HANDLED_SEPARATELY )
serialized = super( DatasetAssociationSerializer, self ).serialize( dataset_assoc, keys, **context )
# add metadata directly to the dict instead of as a sub-object
if 'metadata' in left_to_handle:
metadata = self._prefixed_metadata( dataset_assoc )
serialized.update( metadata )
return serialized
# TODO: this is more util/gen. use
def _pluck_from_list( self, l, elems ):
"""
Removes found elems from list l and returns list of found elems if found.
"""
found = []
for elem in elems:
try:
index = l.index( elem )
found.append( l.pop( index ) )
except ValueError:
pass
return found
def _prefixed_metadata( self, dataset_assoc ):
"""
Adds (a prefixed version of) the DatasetInstance metadata to the dict,
prefixing each key with 'metadata_'.
"""
# build the original, nested dictionary
metadata = self.serialize_metadata( dataset_assoc, 'metadata' )
# prefix each key within and return
prefixed = {}
for key, val in metadata.items():
prefixed_key = 'metadata_' + key
prefixed[ prefixed_key ] = val
return prefixed
class DatasetAssociationDeserializer( base.ModelDeserializer, deletable.PurgableDeserializerMixin ):
@@ -275,15 +470,75 @@ class DatasetAssociationDeserializer( base.ModelDeserializer, deletable.Purgable
super( DatasetAssociationDeserializer, self ).add_deserializers()
deletable.PurgableDeserializerMixin.add_deserializers( self )
self.deserializers.update({
'name' : self.deserialize_basestring,
'info' : self.deserialize_basestring,
})
self.deserializable_keyset.update( self.deserializers.keys() )
class DatasetAssociationFilters( base.ModelFilterParser, deletable.PurgableFiltersMixin ):
# TODO: untested
def deserialize_metadata( self, dataset_assoc, metadata_key, metadata_dict, **context ):
"""
"""
self.validate.type( metadata_key, metadata_dict, dict )
returned = {}
for key, val in metadata_dict.items():
returned[ key ] = self.deserialize_metadatum( dataset_assoc, key, val, **context )
return returned
def deserialize_metadatum( self, dataset_assoc, key, val, **context ):
"""
"""
if key not in dataset_assoc.datatype.metadata_spec:
return
metadata_specification = dataset_assoc.datatype.metadata_spec[ key ]
if metadata_specification.get( 'readonly' ):
return
unwrapped_val = metadata_specification.unwrap( val )
setattr( dataset_assoc.metadata, key, unwrapped_val )
# ...?
return unwrapped_val
class DatasetAssociationFilterParser( base.ModelFilterParser, deletable.PurgableFiltersMixin ):
def _add_parsers( self ):
super( DatasetAssociationFilters, self )._add_parsers()
super( DatasetAssociationFilterParser, self )._add_parsers()
deletable.PurgableFiltersMixin._add_parsers( self )
self.orm_filter_parsers.update({
'name': { 'op': ( 'eq', 'contains', 'like' ) },
'state': { 'op': ( 'eq', 'in' ) },
'name' : { 'op': ( 'eq', 'contains', 'like' ) },
'state' : { 'column' : '_state', 'op': ( 'eq', 'in' ) },
'visible' : { 'op': ( 'eq' ), 'val': self.parse_bool },
})
# self.fn_filter_parsers.update({})
self.fn_filter_parsers.update({
'genome_build' : self.string_standard_ops( 'dbkey' ),
'data_type' : {
'op': {
'eq' : self.eq_datatype,
'isinstance' : self.isinstance_datatype
}
}
})
def eq_datatype( self, dataset_assoc, class_str ):
"""
Is the `dataset_assoc` datatype equal to the registered datatype `class_str`?
"""
comparison_class = self.app.datatypes_registry.get_datatype_class_by_name( class_str )
return ( comparison_class
and dataset_assoc.datatype.__class__ == comparison_class )
def isinstance_datatype( self, dataset_assoc, class_strs ):
"""
Is the `dataset_assoc` datatype derived from any of the registered
datatypes in the comma separated string `class_strs`?
"""
parse_datatype_fn = self.app.datatypes_registry.get_datatype_class_by_name
comparison_classes = []
for class_str in class_strs.split( ',' ):
datatype_class = parse_datatype_fn( class_str )
if datatype_class:
comparison_classes.append( datatype_class )
return ( comparison_classes
and isinstance( dataset_assoc.datatype, comparison_classes ) )
+9 -10
View File
@@ -18,14 +18,13 @@ class DeletableManagerMixin( object ):
that they are no longer needed, should not be displayed, or may be actually
removed by an admin/script.
"""
def delete( self, trans, item, flush=True, **kwargs ):
def delete( self, item, flush=True, **kwargs ):
"""
Mark as deleted and return.
"""
return self._session_setattr( item, 'deleted', True, flush=flush )
def undelete( self, trans, item, flush=True, **kwargs ):
def undelete( self, item, flush=True, **kwargs ):
"""
Mark as not deleted and return.
"""
@@ -44,7 +43,7 @@ class DeletableDeserializerMixin( object ):
def add_deserializers( self ):
self.deserializers[ 'deleted' ] = self.deserialize_deleted
def deserialize_deleted( self, trans, item, key, val ):
def deserialize_deleted( self, item, key, val, **context ):
"""
Delete or undelete `item` based on `val` then return `item.deleted`.
"""
@@ -53,9 +52,9 @@ class DeletableDeserializerMixin( object ):
return item.deleted
# TODO:?? flush=False?
if new_deleted:
self.manager.delete( trans, item, flush=False )
self.manager.delete( item, flush=False )
else:
self.manager.undelete( trans, item, flush=False )
self.manager.undelete( item, flush=False )
return item.deleted
@@ -73,8 +72,7 @@ class PurgableManagerMixin( DeletableManagerMixin ):
purging is often removal of some additional, non-db resource (e.g. a dataset's
file).
"""
def purge( self, trans, item, flush=True, **kwargs ):
def purge( self, item, flush=True, **kwargs ):
"""
Mark as purged and return.
@@ -96,15 +94,16 @@ class PurgableDeserializerMixin( DeletableDeserializerMixin ):
DeletableDeserializerMixin.add_deserializers( self )
self.deserializers[ 'purged' ] = self.deserialize_purged
def deserialize_purged( self, trans, item, key, val ):
def deserialize_purged( self, item, key, val, **context ):
"""
If `val` is True, purge `item` and return `item.purged`.
"""
new_purged = self.validate.bool( key, val )
if new_purged == item.purged:
return item.purged
# do we want to error if something attempts to 'unpurge'?
if new_purged:
self.manager.purge( trans, item, flush=False )
self.manager.purge( item, flush=False )
return item.purged
+154 -357
View File
@@ -11,7 +11,6 @@ import gettext
from galaxy import model
from galaxy import exceptions
from galaxy import datatypes
import galaxy.datatypes.metadata
from galaxy import objectstore
from galaxy.managers import datasets
@@ -48,28 +47,35 @@ class HDAManager( datasets.DatasetAssociationManager,
self.user_manager = users.UserManager( app )
# .... security and permissions
def is_accessible( self, trans, hda, user ):
def is_accessible( self, hda, user, **kwargs ):
"""
Override to allow owners (those that own the associated history).
"""
if self.is_owner( trans, hda, user ):
return True
return super( HDAManager, self ).is_accessible( trans, hda, user )
# this, apparently, is not True:
# if I have a copy of a dataset and anyone who manages permissions on it revokes my access
# I can not access that dataset even if it's in my history
# if self.is_owner( hda, user, **kwargs ):
# return True
return super( HDAManager, self ).is_accessible( hda, user )
def is_owner( self, trans, hda, user ):
def is_owner( self, hda, user, current_history=None, **kwargs ):
"""
Use history to see if current user owns HDA.
"""
history = hda.history
if self.user_manager.is_admin( user ):
return True
# allow anonymous user to access current history
# TODO: some dup here with historyManager.is_owner but prevents circ import
if self.user_manager.is_admin( trans, user ):
return True
if self.user_manager.is_anonymous( user ) and history == trans.get_history():
return True
# TODO: awkward kwarg (which is my new band name); this may not belong here - move to controller?
if self.user_manager.is_anonymous( user ):
if current_history and history == current_history:
return True
return False
return history.user == user
# .... create and copy
def create( self, trans, history=None, dataset=None, flush=True, **kwargs ):
def create( self, history=None, dataset=None, flush=True, **kwargs ):
"""
Create a new hda optionally passing in it's history and dataset.
@@ -78,25 +84,19 @@ class HDAManager( datasets.DatasetAssociationManager,
"""
if not dataset:
kwargs[ 'create_dataset' ] = True
hda = super( HDAManager, self ).create( trans,
flush=flush,
history=history,
dataset=dataset,
sa_session=self.app.model.context, **kwargs )
hda = model.HistoryDatasetAssociation( history=history, dataset=dataset,
sa_session=self.app.model.context, **kwargs )
if history:
# TODO Probably Bug: set_hid is never used, and should be passed
# to history.add_dataset here.
set_hid = not ( 'hid' in kwargs )
history.add_dataset( hda )
history.add_dataset( hda, set_hid=( 'hid' not in kwargs ) )
#TODO:?? some internal sanity check here (or maybe in add_dataset) to make sure hids are not duped?
self.app.model.context.add( hda )
self.session().add( hda )
if flush:
self.app.model.context.flush()
self.session().flush()
return hda
def copy( self, trans, hda, history=None, **kwargs ):
def copy( self, hda, history=None, **kwargs ):
"""
Copy and return the given HDA.
"""
@@ -125,58 +125,36 @@ class HDAManager( datasets.DatasetAssociationManager,
# TODO: update from kwargs?
# Need to set after flushed, as MetadataFiles require dataset.id
self.app.model.context.add( copy )
self.app.model.context.flush()
self.session().add( copy )
self.session().flush()
copy.metadata = hda.metadata
# In some instances peek relies on dataset_id, i.e. gmaj.zip for viewing MAFs
if not hda.datatype.copy_safe_peek:
copy.set_peek()
self.app.model.context.flush()
self.session().flush()
return copy
def copy_ldda( self, trans, history, ldda, **kwargs ):
def copy_ldda( self, history, ldda, **kwargs ):
"""
Copy this HDA as a LDDA and return.
"""
return ldda.to_history_dataset_association( history, add_to_history=True )
# TODO: stub
# def is_a_copy( self, trans, hda ):
# pass
# TODO: stub
# def copied_from( self, trans, hda ):
# pass
# TODO: stub
# def by_user( self, trans, user ):
# pass
def purge( self, trans, hda, flush=True ):
# .... deletion and purging
def purge( self, hda, current_user=None, flush=True ):
"""
Purge this HDA and the dataset underlying it.
"""
# error here if disallowed - before jobs are stopped
# TODO: poss. move to DatasetAssociationManager
self.dataset_manager.error_unless_dataset_purge_allowed( trans, hda )
if trans.user:
trans.user.total_disk_usage -= hda.quota_amount( trans.user )
super( HDAManager, self ).purge( trans, hda, flush=flush )
if hda.creating_job_associations:
job = hda.creating_job_associations[0].job
if not job.finished:
# signal to stop the creating job
job.mark_deleted( self.app.config.track_jobs_in_database )
self.app.job_manager.job_stop_queue.put( job.id )
# more importantly, purge dataset as well
if hda.dataset.user_can_purge:
self.dataset_manager.purge( trans, hda.dataset )
super( HDAManager, self ).purge( hda, flush=flush )
# decrease the user's space used
if current_user:
current_user.total_disk_usage -= hda.quota_amount( current_user )
return hda
def error_if_uploading( self, trans, hda ):
# .... states
def error_if_uploading( self, hda ):
"""
Raise error if HDA is still uploading.
"""
@@ -185,23 +163,14 @@ class HDAManager( datasets.DatasetAssociationManager,
raise exceptions.Conflict( "Please wait until this dataset finishes uploading" )
return hda
# .... via history
# def by_history_id( self, trans, history_id, filters=None, **kwargs ):
# history_id_filter = self.model_class.history_id == history_id
# filters = self._munge_filters( history_id_filter, filters )
# return self.list( trans, filters=filters, **kwargs )
# def by_history( self, trans, history, filters=None, **kwargs ):
# return history.datasets
# .... associated
def creating_job( self, trans, hda ):
# TODO: is this needed? Can't you use the hda.creating_job attribute? When is this None?
job = None
for job_output_assoc in hda.creating_job_associations:
job = job_output_assoc.job
break
return job
def has_been_resubmitted( self, hda ):
"""
Return True if the hda's job was resubmitted at any point.
"""
job_states = model.Job.states
query = ( self._job_state_history_query( hda )
.filter( model.JobStateHistory.state == job_states.RESUBMITTED ) )
return self.app.model.context.query( query.exists() ).scalar()
def _job_state_history_query( self, hda ):
"""
@@ -220,130 +189,43 @@ class HDAManager( datasets.DatasetAssociationManager,
.enable_eagerloads( False ) )
return query
def has_been_resubmitted( self, hda ):
def data_conversion_status( self, hda ):
"""
Return True if the hda's job was resubmitted at any point.
Returns a message if an hda is not ready to be used in visualization.
"""
job_states = model.Job.states
query = ( self._job_state_history_query( hda )
.filter( model.JobStateHistory.state == job_states.RESUBMITTED ) )
return self.app.model.context.query( query.exists() ).scalar()
# .... serialization
def get_display_apps( self, trans, hda ):
"""
Return dictionary containing new-style display app urls.
"""
display_apps = []
for display_app in hda.get_display_applications( trans ).itervalues():
app_links = []
for link_app in display_app.links.itervalues():
app_links.append({
'target': link_app.url.get( 'target_frame', '_blank' ),
'href': link_app.get_display_url( hda, trans ),
'text': gettext.gettext( link_app.name )
})
if app_links:
display_apps.append( dict( label=display_app.name, links=app_links ) )
return display_apps
def get_old_display_applications( self, trans, hda ):
"""
Return dictionary containing old-style display app urls.
"""
display_apps = []
if not self.app.config.enable_old_display_applications:
return display_apps
for display_app in hda.datatype.get_display_types():
target_frame, display_links = hda.datatype.get_display_links( hda,
display_app,
self.app,
trans.request.base )
if len( display_links ) > 0:
display_label = hda.datatype.get_display_label( display_app )
app_links = []
for display_name, display_link in display_links:
app_links.append({
'target': target_frame,
'href': display_link,
'text': gettext.gettext( display_name )
})
if app_links:
display_apps.append( dict( label=display_label, links=app_links ) )
return display_apps
def get_visualizations( self, trans, hda ):
"""
Return a list of dictionaries with links to visualization pages
for those visualizations that apply to this hda.
"""
# use older system if registry is off in the config
if not self.app.visualizations_registry:
return hda.get_visualizations()
return self.app.visualizations_registry.get_visualizations( trans, hda )
# this is a weird syntax and return val
if not hda:
return self.model_class.conversion_messages.NO_DATA
if hda.state == model.Job.states.ERROR:
return self.model_class.conversion_messages.ERROR
if hda.state != model.Job.states.OK:
return self.model_class.conversion_messages.PENDING
return None
# .... data
# TODO: to data provider or Text datatype directly
def text_data( self, dataset, preview=True ):
def text_data( self, hda, preview=True ):
"""
Get data from text file, truncating if necessary.
"""
# 1 MB
MAX_PEEK_SIZE = 1000000
truncated = False
dataset_data = None
if os.path.exists( dataset.file_name ):
if isinstance( dataset.datatype, datatypes.data.Text ):
max_peek_size = 1000000 # 1 MB
if preview and os.stat( dataset.file_name ).st_size > max_peek_size:
dataset_data = open( dataset.file_name ).read( max_peek_size )
truncated = True
else:
dataset_data = open( dataset.file_name ).read( max_peek_size )
truncated = False
else:
# For now, cannot get data from non-text datasets.
dataset_data = None
return truncated, dataset_data
hda_data = None
# For now, cannot get data from non-text datasets.
if not isinstance( hda.datatype, datatypes.data.Text ):
return truncated, hda_data
if not os.path.exists( hda.file_name ):
return truncated, hda_data
# this is a weird syntax and return val
def data_conversion_status( self, trans, hda ):
"""
Returns a message if dataset is not ready to be used in visualization.
"""
if not hda:
return hda.conversion_messages.NO_DATA
if hda.state == model.Job.states.ERROR:
return hda.conversion_messages.ERROR
if hda.state != model.Job.states.OK:
return hda.conversion_messages.PENDING
return None
def stop_creating_job( self, hda ):
"""
Stops an HDA's creating job if all the job's other outputs are deleted.
"""
RUNNING_STATES = (
self.app.model.Job.states.QUEUED,
self.app.model.Job.states.RUNNING,
self.app.model.Job.states.NEW
)
if hda.parent_id is None and len( hda.creating_job_associations ) > 0:
# Mark associated job for deletion
job = hda.creating_job_associations[0].job
if job.state in RUNNING_STATES:
# Are *all* of the job's other output datasets deleted?
if job.check_if_output_datasets_deleted():
job.mark_deleted( self.app.config.track_jobs_in_database )
self.app.job_manager.job_stop_queue.put( job.id )
return True
return False
truncated = preview and os.stat( hda.file_name ).st_size > MAX_PEEK_SIZE
hda_data = open( hda.file_name ).read( MAX_PEEK_SIZE )
return truncated, hda_data
class HDASerializer( datasets.DatasetAssociationSerializer,
class HDASerializer( # datasets._UnflattenedMetadataDatasetAssociationSerializer,
datasets.DatasetAssociationSerializer,
taggable.TaggableSerializerMixin,
annotatable.AnnotatableSerializerMixin ):
# TODO: inherit from datasets.DatasetAssociationSerializer
@@ -356,6 +238,7 @@ class HDASerializer( datasets.DatasetAssociationSerializer,
self.default_view = 'summary'
self.add_view( 'summary', [
'id', 'name',
'type_id',
'history_id', 'hid',
# why include if model_class is there?
'history_content_type',
@@ -375,18 +258,16 @@ class HDASerializer( datasets.DatasetAssociationSerializer,
# remapped
'genome_build', 'misc_info', 'misc_blurb',
'file_ext', 'file_size',
'file_path',
'create_time', 'update_time',
'resubmitted',
'metadata', 'meta_files', 'data_type',
'peek',
#TODO: why is this named uuid!? The hda doesn't have a uuid - it's the underlying dataset's uuid!
'uuid',
# should be:
#'dataset_uuid',
'permissions',
'file_path',
'display_apps',
'display_types',
'visualizations',
@@ -415,173 +296,109 @@ class HDASerializer( datasets.DatasetAssociationSerializer,
annotatable.AnnotatableSerializerMixin.add_serializers( self )
self.serializers.update({
'model_class' : lambda *a: 'HistoryDatasetAssociation',
# TODO: accessible needs to go away
'accessible' : lambda *a: True,
'model_class' : lambda *a, **c: 'HistoryDatasetAssociation',
'history_content_type': lambda *a, **c: 'dataset',
'hda_ldda' : lambda *a, **c: 'hda',
'type_id' : self.serialize_type_id,
'id' : self.serialize_id,
'history_id' : self.serialize_id,
'history_content_type': lambda *a: 'dataset',
'dataset_id' : self.serialize_id,
'hda_ldda' : lambda *a: 'hda',
# remapped
'info' : lambda t, i, k: i.info.strip() if isinstance( i.info, basestring ) else '',
'misc_info' : lambda t, i, k: self.serializers[ 'info' ]( t, i, k ),
'misc_blurb' : lambda t, i, k: i.blurb,
'genome_build' : lambda t, i, k: i.dbkey,
'file_ext' : lambda t, i, k: i.extension,
'file_size' : lambda t, i, k: self.serializers[ 'size' ]( t, i, k ),
'misc_info' : self._remap_from( 'info' ),
'misc_blurb' : self._remap_from( 'blurb' ),
'file_ext' : self._remap_from( 'extension' ),
'file_path' : self._remap_from( 'file_name' ),
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
'copied_from_history_dataset_association_id' : self.serialize_id,
'copied_from_library_dataset_dataset_association_id': self.serialize_id,
'resubmitted' : lambda i, k, **c: self.hda_manager.has_been_resubmitted( i ),
'resubmitted' : lambda t, i, k: self.hda_manager.has_been_resubmitted( i ),
'meta_files' : self.serialize_meta_files,
'file_path' : self.serialize_file_path,
'size' : lambda t, i, k: int( i.get_size() ),
'nice_size' : lambda t, i, k: i.get_size( nice_size=True ),
'data_type' : lambda t, i, k: i.datatype.__class__.__module__ + '.' + i.datatype.__class__.__name__,
'peek' : lambda t, i, k: i.display_peek() if i.peek and i.peek != 'no peek' else None,
# currently we send this sub-obj flattened (see serialize and add_flattened_metadata below)
# 'metadata' : self.serialize_metadata,
# make it available here under a different key
'metadata_dict' : self.serialize_metadata,
'parent_id' : self.serialize_id,
'annotation' : self.serialize_annotation,
'tags' : self.serialize_tags,
'display_apps' : lambda t, i, k: self.hda_manager.get_display_apps( t, i ),
'display_types' : lambda t, i, k: self.hda_manager.get_old_display_applications( t, i ),
'visualizations': lambda t, i, k: self.hda_manager.get_visualizations( t, i ),
'dataset_uuid' : lambda t, i, k: str( i.dataset.uuid ) if i.dataset.uuid else None,
'uuid' : lambda t, i, k: self.serializers[ 'dataset_uuid' ]( t, i, k ),
'display_apps' : self.serialize_display_apps,
'display_types' : self.serialize_old_display_applications,
'visualizations': self.serialize_visualization_links,
# 'url' : url_for( 'history_content_typed', history_id=encoded_history_id, id=encoded_id, type="dataset" ),
# TODO: this intermittently causes a routes.GenerationException - temp use the legacy route to prevent this
# see also: https://trello.com/c/5d6j4X5y
# see also: https://sentry.galaxyproject.org/galaxy/galaxy-main/group/20769/events/9352883/
'url' : lambda t, i, k: self.url_for( 'history_content',
history_id=t.security.encode_id( i.history_id ), id=t.security.encode_id( i.id ) ),
'url' : lambda i, k, **c: self.url_for( 'history_content',
history_id=self.app.security.encode_id( i.history_id ),
id=self.app.security.encode_id( i.id ) ),
'urls' : self.serialize_urls,
'download_url' : lambda t, i, k: self.url_for( 'history_contents_display',
history_id=t.security.encode_id( i.history.id ),
history_content_id=t.security.encode_id( i.id ) ),
'api_type' : lambda *a: 'file',
'type' : lambda *a: 'file'
# TODO: backwards compat: need to go away
'download_url' : lambda i, k, **c: self.url_for( 'history_contents_display',
history_id=self.app.security.encode_id( i.history.id ),
history_content_id=self.app.security.encode_id( i.id ) ),
'parent_id' : self.serialize_id,
'accessible' : lambda *a, **c: True,
'api_type' : lambda *a, **c: 'file',
'type' : lambda *a, **c: 'file'
})
def serialize( self, trans, hda, keys ):
"""
Override to add metadata as flattened keys on the serialized HDA.
"""
# if 'metadata' isn't removed from keys here serialize will retrieve the un-serializable MetadataCollection
# TODO: remove these when metadata is sub-object
KEYS_HANDLED_SEPARATELY = ( 'metadata', )
left_to_handle = self.pluck_from_list( keys, KEYS_HANDLED_SEPARATELY )
serialized = super( HDASerializer, self ).serialize( trans, hda, keys )
def serialize_type_id( self, hda, key, **context ):
return 'dataset-' + self.serializers[ 'id' ]( hda, 'id' )
if 'metadata' in left_to_handle:
# we currently add metadata directly to the dict instead of as a sub-object
metadata = self.prefixed_metadata( trans, hda )
serialized.update( metadata )
return serialized
def serialize_display_apps( self, hda, key, trans=None, **context ):
"""
Return dictionary containing new-style display app urls.
"""
display_apps = []
for display_app in hda.get_display_applications( trans ).itervalues():
# TODO: this is more util/gen. use
def pluck_from_list( self, l, elems ):
"""
Removes found elems from list l and returns list of found elems if found.
"""
found = []
for elem in elems:
try:
index = l.index( elem )
found.append( l.pop( index ) )
except ValueError:
pass
return found
app_links = []
for link_app in display_app.links.itervalues():
app_links.append({
'target': link_app.url.get( 'target_frame', '_blank' ),
'href': link_app.get_display_url( hda, trans ),
'text': gettext.gettext( link_app.name )
})
if app_links:
display_apps.append( dict( label=display_app.name, links=app_links ) )
def prefixed_metadata( self, trans, hda ):
"""
Adds (a prefixed version of) the hda metadata to the dict, prefixing each key
with 'metadata_'.
"""
metadata = self.serialize_metadata( trans, hda, 'metadata' )
# TODO: this is factored out for removal - metadata should be a sub-object instead:
# i.e. 'metadata' : self.serialize_metadata,
prefixed = {}
for key, val in metadata.items():
prefixed_key = 'metadata_' + key
prefixed[ prefixed_key ] = val
return prefixed
return display_apps
def serialize_metadata( self, trans, hda, key, excluded=None ):
def serialize_old_display_applications( self, hda, key, trans=None, **context ):
"""
Cycle through metadata and return as dictionary.
Return dictionary containing old-style display app urls.
"""
# dbkey is a repeat actually (metadata_dbkey == genome_build)
# excluded = [ 'dbkey' ] if excluded is None else excluded
excluded = [] if excluded is None else excluded
display_apps = []
if not self.app.config.enable_old_display_applications:
return display_apps
metadata = {}
for name, spec in hda.metadata.spec.items():
if name in excluded:
continue
val = hda.metadata.get( name )
# NOTE: no files
if isinstance( val, model.MetadataFile ):
# only when explicitly set: fetching filepaths can be expensive
if not self.app.config.expose_dataset_path:
continue
val = val.file_name
# TODO:? possibly split this off?
# If no value for metadata, look in datatype for metadata.
elif val is None and hasattr( hda.datatype, name ):
val = getattr( hda.datatype, name )
metadata[ name ] = val
display_link_fn = hda.datatype.get_display_links
for display_app in hda.datatype.get_display_types():
target_frame, display_links = display_link_fn( hda, display_app, self.app, trans.request.base )
return metadata
if len( display_links ) > 0:
display_label = hda.datatype.get_display_label( display_app )
# add to serialize_metadata above
def serialize_meta_files( self, trans, hda, key ):
"""
Cycle through meta files and return them as a list of dictionaries.
"""
meta_files = []
for meta_type in hda.metadata.spec.keys():
if isinstance( hda.metadata.spec[ meta_type ].param, galaxy.datatypes.metadata.FileParameter ):
meta_files.append( dict( file_type=meta_type ) )
return meta_files
app_links = []
for display_name, display_link in display_links:
app_links.append({
'target': target_frame,
'href': display_link,
'text': gettext.gettext( display_name )
})
if app_links:
display_apps.append( dict( label=display_label, links=app_links ) )
# def file_info #TODO
# TODO: and to dataset instead (passing through object store)
def serialize_file_path( self, trans, hda, key ):
"""
Return the `file_name` of the HDA if the config exposes it, None otherwise.
"""
# TODO: allow admin
if self.app.config.expose_dataset_path:
try:
return hda.file_name
except objectstore.ObjectNotFound:
log.exception( 'objectstore.ObjectNotFound, HDA %s.', hda.id )
return None
return display_apps
def serialize_urls( self, trans, hda, key ):
def serialize_visualization_links( self, hda, key, trans=None, **context ):
"""
Return a list of dictionaries with links to visualization pages
for those visualizations that apply to this hda.
"""
# use older system if registry is off in the config
if not self.app.visualizations_registry:
return hda.get_visualizations()
return self.app.visualizations_registry.get_visualizations( trans, hda )
def serialize_urls( self, hda, key, **context ):
"""
Return web controller urls useful for this HDA.
"""
url_for = self.url_for
encoded_id = self.security.encode_id( hda.id )
encoded_id = self.app.security.encode_id( hda.id )
urls = {
'purge' : url_for( controller='dataset', action='purge_async', dataset_id=encoded_id ),
'display' : url_for( controller='dataset', action='display', dataset_id=encoded_id, preview=True ),
@@ -617,41 +434,21 @@ class HDADeserializer( datasets.DatasetAssociationDeserializer,
annotatable.AnnotatableDeserializerMixin.add_deserializers( self )
self.deserializers.update({
'name' : self.deserialize_basestring,
'visible' : self.deserialize_bool,
# remapped
'genome_build' : lambda t, i, k, v: self.deserialize_genome_build( t, i, 'dbkey', v ),
'misc_info' : lambda t, i, k, v: (
self.deserialize_basestring( t, i, 'info', v, convert_none_to_empty=True ) ),
# TODO: mixin: deletable
'deleted' : self.deserialize_bool,
# sharable
'published' : self.deserialize_bool,
'importable' : self.deserialize_bool,
'genome_build' : lambda i, k, v, **c: self.deserialize_genome_build( i, 'dbkey', v ),
'misc_info' : lambda i, k, v, **c: self.deserialize_basestring( i, 'info', v,
convert_none_to_empty=True ),
})
self.deserializable_keyset.update( self.deserializers.keys() )
class HDAFilters( datasets.DatasetAssociationFilters ):
class HDAFilterParser( datasets.DatasetAssociationFilterParser,
taggable.TaggableFilterMixin,
annotatable.AnnotatableFilterMixin ):
model_class = model.HistoryDatasetAssociation
def _add_parsers( self ):
super( HDAFilters, self )._add_parsers()
self.fn_filter_parsers.update({
# TODO: filter_string_attr_contains, filter_string_attr_eq
# 'dbkey' : { 'op': { 'has' : self.filter_annotation_contains, } },
# TODO: add this in annotatable mixin
'annotation': { 'op': { 'has': self.filter_annotation_contains, } },
# TODO: add this in taggable mixin
'tag': {
'op': {
'eq': self.filter_has_tag,
'has': self.filter_has_partial_tag,
}
}
})
super( HDAFilterParser, self )._add_parsers()
taggable.TaggableFilterMixin._add_parsers( self )
annotatable.AnnotatableFilterMixin._add_parsers( self )
+130 -144
View File
@@ -9,7 +9,7 @@ from galaxy import model
from galaxy.managers import sharable
from galaxy.managers import deletable
from galaxy.managers import hdas
from galaxy.managers.collections_util import dictify_dataset_collection_instance
from galaxy.managers import collections_util
import logging
log = logging.getLogger( __name__ )
@@ -32,7 +32,7 @@ class HistoryManager( sharable.SharableModelManager, deletable.PurgableManagerMi
self.hda_manager = hdas.HDAManager( app )
def copy( self, trans, history, user, **kwargs ):
def copy( self, history, user, **kwargs ):
"""
Copy and return the given `history`.
"""
@@ -40,60 +40,62 @@ class HistoryManager( sharable.SharableModelManager, deletable.PurgableManagerMi
# .... sharable
# overriding to handle anonymous users' current histories in both cases
def by_user( self, trans, user, **kwargs ):
def by_user( self, user, current_history=None, **kwargs ):
"""
Get all the histories for a given user (allowing anon users' theirs)
ordered by update time.
"""
# handle default and/or anonymous user (which still may not have a history yet)
if self.user_manager.is_anonymous( user ):
current_history = self.get_current( trans )
return [ current_history ] if current_history else []
return super( HistoryManager, self ).by_user( user, **kwargs )
return super( HistoryManager, self ).by_user( trans, user, **kwargs )
def is_owner( self, trans, history, user ):
def is_owner( self, history, user, current_history=None, **kwargs ):
"""
True if the current user is the owner of the given history.
"""
# anon users are only allowed to view their current history
if self.user_manager.is_anonymous( user ) and history == self.get_current( trans ):
return True
return super( HistoryManager, self ).is_owner( trans, history, user )
if self.user_manager.is_anonymous( user ):
if current_history and history == current_history:
return True
return False
return super( HistoryManager, self ).is_owner( history, user )
# TODO: possibly to sharable
def most_recent( self, trans, user, filters=None, **kwargs ):
# TODO: possibly to sharable or base
def most_recent( self, user, filters=None, current_history=None, **kwargs ):
"""
Return the most recently update history for the user.
If user is anonymous, return the current history. If the user is anonymous
and the current history is deleted, return None.
"""
# TODO: trans
if not user:
current_history = self.get_current( trans )
if self.user_manager.is_anonymous( user ):
return None if ( not current_history or current_history.deleted ) else current_history
desc_update_time = self.model_class.table.c.update_time
filters = self._munge_filters( filters, self.model_class.user_id == user.id )
# TODO: normalize this return value
return self.query( trans, filters=filters, order_by=desc_update_time, limit=1, **kwargs ).first()
return self.query( filters=filters, order_by=desc_update_time, limit=1, **kwargs ).first()
# .... purgable
def purge( self, trans, history, flush=True, **kwargs ):
def purge( self, history, flush=True, **kwargs ):
"""
Purge this history and all HDAs, Collections, and Datasets inside this history.
"""
self.hda_manager.dataset_manager.error_unless_dataset_purge_allowed( trans, history )
self.hda_manager.dataset_manager.error_unless_dataset_purge_allowed()
# First purge all the datasets
for hda in history.datasets:
if not hda.purged:
self.hda_manager.purge( trans, hda, flush=True )
self.hda_manager.purge( hda, flush=True )
# Now mark the history as purged
super( HistoryManager, self ).purge( trans, history, flush=flush, **kwargs )
super( HistoryManager, self ).purge( history, flush=flush, **kwargs )
# .... current
# TODO: make something to bypass the anon user + current history permissions issue
# def is_current_users_current_history( self, history, trans ):
# pass
def get_current( self, trans ):
"""
Return the current history.
@@ -113,10 +115,9 @@ class HistoryManager( sharable.SharableModelManager, deletable.PurgableManagerMi
"""
Set the current history by an id.
"""
return self.set_current( trans, self.by_id( trans, history_id ) )
return self.set_current( trans, self.by_id( history_id ) )
# .... serialization
# TODO: move to serializer (i.e. history with contents attr)
# TODO: replace or move to serializer
def _get_history_data( self, trans, history ):
"""
Returns a dictionary containing ``history`` and ``contents``, serialized
@@ -128,23 +129,27 @@ class HistoryManager( sharable.SharableModelManager, deletable.PurgableManagerMi
history_dictionary = {}
contents_dictionaries = []
try:
history_dictionary = history_serializer.serialize_to_view( trans, history, view='detailed' )
history_dictionary = history_serializer.serialize_to_view( history, view='detailed',
user=trans.user, trans=trans )
for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
contents_dict = {}
if isinstance( content, model.HistoryDatasetAssociation ):
contents_dict = hda_serializer.serialize_to_view( trans, content, view='detailed' )
contents_dict = hda_serializer.serialize_to_view( content, view='detailed',
user=trans.user, trans=trans )
elif isinstance( content, model.HistoryDatasetCollectionAssociation ):
try:
service = self.app.dataset_collections_service
dataset_collection_instance = service.get_dataset_collection_instance(
collection = service.get_dataset_collection_instance(
trans=trans,
instance_type='history',
id=self.app.security.encode_id( content.id ),
)
contents_dict = dictify_dataset_collection_instance( dataset_collection_instance,
security=self.app.security,
parent=dataset_collection_instance.history,
view="element" )
serializer = collections_util.dictify_dataset_collection_instance
contents_dict = serializer( collection,
security=self.app.security,
parent=collection.history,
view="element" )
except Exception, exc:
log.exception( "Error in history API at listing dataset collection: %s", exc )
# TODO: return some dict with the error
@@ -160,79 +165,6 @@ class HistoryManager( sharable.SharableModelManager, deletable.PurgableManagerMi
return { 'history': history_dictionary,
'contents': contents_dictionaries }
# remove this
def get_state_counts( self, trans, history, exclude_deleted=True, exclude_hidden=False ):
"""
Return a dictionary keyed to possible dataset states and valued with the number
of datasets in this history that have those states.
"""
# TODO: the default flags above may not make a lot of sense (T,T?)
state_counts = {}
for state in model.Dataset.states.values():
state_counts[ state ] = 0
# TODO:?? collections and coll. states?
for hda in history.datasets:
if exclude_deleted and hda.deleted:
continue
if exclude_hidden and not hda.visible:
continue
state_counts[ hda.state ] = state_counts[ hda.state ] + 1
return state_counts
# remove this
def get_state_ids( self, trans, history ):
"""
Return a dictionary keyed to possible dataset states and valued with lists
containing the ids of each HDA in that state.
"""
state_ids = {}
for state in model.Dataset.states.values():
state_ids[ state ] = []
# TODO:?? collections and coll. states?
for hda in history.datasets:
# TODO: do not encode ids at this layer
encoded_id = self.app.security.encode_id( hda.id )
state_ids[ hda.state ].append( encoded_id )
return state_ids
# TODO: remove this (is state used/useful?)
def get_history_state( self, trans, history ):
"""
Returns the history state based on the states of the HDAs it contains.
"""
states = model.Dataset.states
# (default to ERROR)
state = states.ERROR
# TODO: history_state and state_counts are classically calc'd at the same time
# so this is rel. ineff. - if we keep this...
hda_state_counts = self.get_state_counts( trans, history, exclude_deleted=False )
num_hdas = sum( hda_state_counts.values() )
if num_hdas == 0:
state = states.NEW
else:
if ( hda_state_counts[ states.RUNNING ] > 0
or hda_state_counts[ states.SETTING_METADATA ] > 0
or hda_state_counts[ states.UPLOAD ] > 0 ):
state = states.RUNNING
# TODO: this method may be more useful if we *also* polled the histories jobs here too
elif hda_state_counts[ states.QUEUED ] > 0:
state = states.QUEUED
elif ( hda_state_counts[ states.ERROR ] > 0
or hda_state_counts[ states.FAILED_METADATA ] > 0 ):
state = states.ERROR
elif hda_state_counts[ states.OK ] == num_hdas:
state = states.OK
return state
class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSerializerMixin ):
"""
@@ -283,48 +215,115 @@ class HistorySerializer( sharable.SharableModelSerializer, deletable.PurgableSer
deletable.PurgableSerializerMixin.add_serializers( self )
self.serializers.update({
'model_class' : lambda *a: 'History',
'model_class' : lambda *a, **c: 'History',
'id' : self.serialize_id,
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
'size' : lambda t, i, k: int( i.get_disk_size() ),
'nice_size' : lambda t, i, k: i.get_disk_size( nice_size=True ),
'state' : lambda t, i, k: self.history_manager.get_history_state( t, i ),
'size' : lambda i, k, **c: int( i.get_disk_size() ),
'nice_size' : lambda i, k, **c: i.get_disk_size( nice_size=True ),
'state' : self.serialize_history_state,
'url' : lambda t, i, k: self.url_for( 'history', id=t.security.encode_id( i.id ) ),
'contents_url' : lambda t, i, k: self.url_for( 'history_contents',
history_id=t.security.encode_id( i.id ) ),
'url' : lambda i, k, **c: self.url_for( 'history', id=self.app.security.encode_id( i.id ) ),
'contents_url' : lambda i, k, **c: self.url_for( 'history_contents',
history_id=self.app.security.encode_id( i.id ) ),
'empty' : lambda t, i, k: ( len( i.datasets ) + len( i.dataset_collections ) ) <= 0,
'count' : lambda trans, item, key: len( item.datasets ),
'hdas' : lambda t, i, k: [ t.security.encode_id( hda.id ) for hda in i.datasets ],
'state_details' : lambda t, i, k: self.history_manager.get_state_counts( t, i ),
'state_ids' : lambda t, i, k: self.history_manager.get_state_ids( t, i ),
'empty' : lambda i, k, **c: ( len( i.datasets ) + len( i.dataset_collections ) ) <= 0,
'count' : lambda i, k, **c: len( i.datasets ),
'hdas' : lambda i, k, **c: [ self.app.security.encode_id( hda.id ) for hda in i.datasets ],
'state_details' : self.serialize_state_counts,
'state_ids' : self.serialize_state_ids,
'contents' : self.serialize_contents
})
def serialize_contents( self, trans, history, *args ):
# remove this
def serialize_state_ids( self, history, key, **context ):
"""
Return a dictionary keyed to possible dataset states and valued with lists
containing the ids of each HDA in that state.
"""
state_ids = {}
for state in model.Dataset.states.values():
state_ids[ state ] = []
# TODO:?? collections and coll. states?
for hda in history.datasets:
# TODO: do not encode ids at this layer
encoded_id = self.app.security.encode_id( hda.id )
state_ids[ hda.state ].append( encoded_id )
return state_ids
# remove this
def serialize_state_counts( self, history, key, exclude_deleted=True, exclude_hidden=False, **context ):
"""
Return a dictionary keyed to possible dataset states and valued with the number
of datasets in this history that have those states.
"""
# TODO: the default flags above may not make a lot of sense (T,T?)
state_counts = {}
for state in model.Dataset.states.values():
state_counts[ state ] = 0
# TODO:?? collections and coll. states?
for hda in history.datasets:
if exclude_deleted and hda.deleted:
continue
if exclude_hidden and not hda.visible:
continue
state_counts[ hda.state ] = state_counts[ hda.state ] + 1
return state_counts
# TODO: remove this (is state used/useful?)
def serialize_history_state( self, history, key, **context ):
"""
Returns the history state based on the states of the HDAs it contains.
"""
states = model.Dataset.states
# (default to ERROR)
state = states.ERROR
# TODO: history_state and state_counts are classically calc'd at the same time
# so this is rel. ineff. - if we keep this...
hda_state_counts = self.serialize_state_counts( history, 'counts', exclude_deleted=False, **context )
num_hdas = sum( hda_state_counts.values() )
if num_hdas == 0:
state = states.NEW
else:
if ( hda_state_counts[ states.RUNNING ] > 0
or hda_state_counts[ states.SETTING_METADATA ] > 0
or hda_state_counts[ states.UPLOAD ] > 0 ):
state = states.RUNNING
# TODO: this method may be more useful if we *also* polled the histories jobs here too
elif hda_state_counts[ states.QUEUED ] > 0:
state = states.QUEUED
elif ( hda_state_counts[ states.ERROR ] > 0
or hda_state_counts[ states.FAILED_METADATA ] > 0 ):
state = states.ERROR
elif hda_state_counts[ states.OK ] == num_hdas:
state = states.OK
return state
def serialize_contents( self, history, *args, **context ):
contents_dictionaries = []
for content in history.contents_iter( types=[ 'dataset', 'dataset_collection' ] ):
contents_dict = {}
if isinstance( content, model.HistoryDatasetAssociation ):
contents_dict = self.hda_serializer.serialize_to_view( trans, content, view='detailed' )
elif isinstance( content, model.HistoryDatasetCollectionAssociation ):
contents_dict = self.serialize_collection( trans, content )
contents_dict = self.hda_serializer.serialize_to_view( content, view='detailed', **context )
# elif isinstance( content, model.HistoryDatasetCollectionAssociation ):
# contents_dict = self._serialize_collection( trans, content )
contents_dictionaries.append( contents_dict )
return contents_dictionaries
def serialize_collection( self, trans, collection ):
# TODO: remove trans
def _serialize_collection( self, trans, collection ):
service = self.app.dataset_collections_service
dataset_collection_instance = service.get_dataset_collection_instance(
trans=trans,
instance_type='history',
id=self.security.encode_id( collection.id ),
id=self.app.security.encode_id( collection.id ),
)
return dictify_dataset_collection_instance( dataset_collection_instance,
security=self.app.security,
parent=dataset_collection_instance.history,
view="element" )
return collections_util.dictify_dataset_collection_instance( dataset_collection_instance,
security=self.app.security, parent=dataset_collection_instance.history, view="element" )
class HistoryDeserializer( sharable.SharableModelDeserializer, deletable.PurgableDeserializerMixin ):
@@ -342,33 +341,20 @@ class HistoryDeserializer( sharable.SharableModelDeserializer, deletable.Purgabl
deletable.PurgableDeserializerMixin.add_deserializers( self )
self.deserializers.update({
'name': self.deserialize_basestring,
'genome_build': self.deserialize_genome_build,
'name' : self.deserialize_basestring,
'genome_build' : self.deserialize_genome_build,
})
class HistoryFilters( sharable.SharableModelFilters, deletable.PurgableFiltersMixin ):
class HistoryFilters( sharable.SharableModelFilters,
deletable.PurgableFiltersMixin ):
model_class = model.History
def _add_parsers( self ):
super( HistoryFilters, self )._add_parsers()
deletable.PurgableFiltersMixin._add_parsers( self )
self.orm_filter_parsers.update({
# history specific
'name': { 'op': ( 'eq', 'contains', 'like' ) },
'genome_build': { 'op': ( 'eq', 'contains', 'like' ) },
})
# TODO: I'm not entirely convinced this (or tags) are a good idea for filters since they involve a/the user
self.fn_filter_parsers.update({
# TODO: add this in annotatable mixin
'annotation': { 'op': { 'has': self.filter_annotation_contains, } },
# TODO: add this in taggable mixin
'tag': {
'op': {
'eq': self.filter_has_tag,
'has': self.filter_has_partial_tag,
}
}
'name' : { 'op': ( 'eq', 'contains', 'like' ) },
'genome_build' : { 'op': ( 'eq', 'contains', 'like' ) },
})
+2 -2
View File
@@ -26,12 +26,12 @@ class RatableSerializerMixin( object ):
self.serializers[ 'user_rating' ] = self.serialize_user_rating
self.serializers[ 'community_rating' ] = self.serialize_community_rating
def serialize_user_rating( self, trans, item, key ):
def serialize_user_rating( self, item, key, user=None, **context ):
"""
"""
pass
def serialize_community_rating( self, trans, item, key ):
def serialize_community_rating( self, item, key, **context ):
"""
"""
pass
+255
View File
@@ -0,0 +1,255 @@
from galaxy import security
import galaxy.exceptions
from galaxy import model
from galaxy.managers import users
import logging
log = logging.getLogger( __name__ )
class RBACPermissionFailedException( galaxy.exceptions.InsufficientPermissionsException ): pass
class RBACPermission( object ):
"""
Base class for wrangling/controlling the permissions ORM models (*Permissions, Roles)
that control which users can perform certain actions on their associated models
(Libraries, Datasets).
"""
permissions_class = None
permission_failed_error_class = RBACPermissionFailedException
def __init__( self, app ):
self.app = app
self.user_manager = users.UserManager( app )
def session( self ):
return self.app.model.context
# TODO: implement group
# TODO: how does admin play into this?
def is_permitted( self, item, user ):
raise NotImplementedError( "abstract parent class" )
def error_unless_permitted( self, item, user ):
if not self.is_permitted( item, user ):
error_info = dict( model_class=item.__class__, id=getattr( item, 'id', None ) )
raise self.permission_failed_error_class( **error_info )
def grant( self, item, user, flush=True ):
raise NotImplementedError( "abstract parent class" )
def revoke( self, item, user, flush=True ):
raise NotImplementedError( "abstract parent class" )
def _role_is_permitted( self, item, role ):
raise NotImplementedError( "abstract parent class" )
def _error_unless_role_permitted( self, item, role ):
if not self._role_is_permitted( item, role ):
error_info = dict( model_class=item.__class__, id=getattr( item, 'id', None ) )
raise self.permission_failed_error_class( **error_info )
def _grant_role( self, item, role, flush=True ):
raise NotImplementedError( "abstract parent class" )
def _revoke_role( self, item, role, flush=True ):
raise NotImplementedError( "abstract parent class" )
class DatasetRBACPermission( RBACPermission ):
"""
Base class for the manage and access RBAC permissions used by dataset security.
The DatasetPermissions used by the RBAC agent are associations between a Dataset
and a single Role.
DatasetPermissions are typed (but not polymorphic themselves) by a string 'action'.
There are two types:
- manage permissions : can a role manage the permissions on a dataset
- access : can a role read/look at/copy a dataset
"""
permissions_class = model.DatasetPermissions
action_name = None
# ---- double secrect probation
def __assert_action( self ):
if not self.action_name:
raise NotImplementedError( "abstract parent class" + " needs action_name" )
# ---- interface
def by_dataset( self, dataset ):
self.__assert_action()
all_permissions = self._all_types_by_dataset( dataset )
return filter( lambda p: p.action == self.action_name, all_permissions )
#TODO: list?
def by_roles( self, dataset, roles ):
permissions = self.by_dataset( dataset )
return filter( lambda p: p.role in roles, permissions )
def by_role( self, dataset, role ):
permissions = self.by_dataset( dataset )
found = filter( lambda p: p.role == role, permissions )
if not found:
return None
if len( found ) > 1:
raise galaxy.exceptions.InconsistentDatabase( dataset=dataset.id, role=role.id )
return found[0]
def set( self, dataset, roles, flush=True ):
# NOTE: this removes all previous permissions of this type
self.clear( dataset, flush=False )
permissions = []
for role in roles:
permissions.append( self._create( dataset, role, flush=False ) )
if flush:
self.session().flush()
return permissions
def clear( self, dataset, flush=True ):
permissions = self.by_dataset( dataset )
return self._delete( permissions, flush=flush )
# ---- private
def _create( self, dataset, role, flush=True ):
permission = self.permissions_class( self.action_name, dataset, role )
self.session().add( permission )
if flush:
self.session().flush()
return permission
def _roles( self, dataset ):
return [ permission.role for permission in self.by_dataset( dataset ) ]
def _all_types_by_dataset( self, dataset ):
return dataset.actions
# as a general rule, DatasetPermissions are considered disposable
# and there is no reason to update the models
#TODO: list?
def _delete( self, permissions, flush=True ):
for permission in permissions:
self.session().delete( permission )
if flush:
self.session().flush()
def _revoke_role( self, dataset, role, flush=True ):
role_permissions = self.by_roles( dataset, [ role ] )
return self._delete( role_permissions, flush=flush )
def iterable_has_all( iterable, has_these ):
for item in has_these:
if item not in iterable:
return False
return True
class DatasetManagePermissionFailedException( RBACPermissionFailedException ): pass
class ManageDatasetRBACPermission( DatasetRBACPermission ):
"""
A class that controls the dataset permissions that control
who can manage that dataset's permissions.
When checking permissions for a user, if any of the user's roles
have permission on the dataset
"""
# TODO: We may also be able to infer/record the dataset 'owner' as well.
action_name = security.RBACAgent.permitted_actions.DATASET_MANAGE_PERMISSIONS.action
permission_failed_error_class = DatasetManagePermissionFailedException
# ---- interface
def is_permitted( self, dataset, user ):
# anonymous users cannot manage permissions on datasets
if self.user_manager.is_anonymous( user ):
return False
for role in user.all_roles():
if self._role_is_permitted( dataset, role ):
return True
return False
def grant( self, dataset, user, flush=True ):
private_role = self._user_private_role( user )
return self._grant_role( dataset, private_role, flush=flush )
def revoke( self, dataset, user, flush=True ):
private_role = self._user_private_role( user )
return self._revoke_role( dataset, private_role, flush=flush )
# ---- private
def _role_is_permitted( self, dataset, role ):
return role in self._roles( dataset )
def _user_private_role( self, user ):
# error with 401 if no user
self.user_manager.error_if_anonymous( user )
return self.user_manager.private_role( user )
def _grant_role( self, dataset, role, flush=True ):
existing = self.by_role( dataset, role )
if existing:
return existing
return self._create( dataset, role, flush=flush )
def _revoke_role( self, dataset, role, flush=True ):
permission = self.by_roles( dataset, [ role ] )
return self._delete( [ permission ], flush=flush )
class DatasetAccessPermissionFailedException( RBACPermissionFailedException ): pass
class AccessDatasetRBACPermission( DatasetRBACPermission ):
"""
A class to manage access permissions on a dataset.
An user must have all the Roles of all the access permissions associated
with a dataset in order to access it.
"""
action_name = security.RBACAgent.permitted_actions.DATASET_ACCESS.action
permission_failed_error_class = DatasetAccessPermissionFailedException
# ---- interface
def is_permitted( self, dataset, user ):
current_roles = self._roles( dataset )
# NOTE: that because of short circuiting this allows
# anonymous access to public datasets
return ( self._is_public_from_roles( current_roles )
or self._user_has_all_roles( user, current_roles ) )
def grant( self, item, user ):
pass
# not so easy
# need to check for a sharing role
# then add the new user to it
def revoke( self, item, user ):
pass
# not so easy
# TODO: these are a lil off message
def is_public( self, dataset ):
current_roles = self._roles( dataset )
return self._is_public_from_roles( current_roles )
def set_private( self, dataset, user, flush=True ):
private_role = self.user_manager.private_role( user )
return self.set( dataset, [ private_role ], flush=flush )
# ---- private
def _is_public_from_roles( self, roles ):
return len( roles ) == 0
def _user_has_all_roles( self, user, roles ):
user_roles = []
if not self.user_manager.is_anonymous( user ):
user_roles = user.all_roles()
return iterable_has_all( user_roles, roles )
def _role_is_permitted( self, dataset, role ):
current_roles = self._roles( dataset )
return ( self._is_public_from_roles( current_roles )
# if there's only one role and this is it, let em in
or ( ( len( current_roles ) == 1 ) and ( role == current_roles[0] ) ) )
+34 -25
View File
@@ -2,40 +2,49 @@
Manager and Serializer for Roles.
"""
import galaxy.web
from galaxy import exceptions
from galaxy.model import orm
from sqlalchemy.orm.exc import MultipleResultsFound
from sqlalchemy.orm.exc import NoResultFound
from sqlalchemy.orm import exc as sqlalchemy_exceptions
import galaxy.exceptions
from galaxy import model
from galaxy.managers import base
import logging
log = logging.getLogger( __name__ )
# =============================================================================
class RoleManager( object ):
class RoleManager( base.ModelManager ):
"""
Interface/service object for interacting with folders.
Business logic for roles.
"""
model_class = model.Role
foreign_key_name = 'role'
def get( self, trans, decoded_role_id):
"""
Method loads the role from the DB based on the given role id.
user_assoc = model.UserRoleAssociation
group_assoc = model.GroupRoleAssociation
:param decoded_role_id: id of the role to load from the DB
:type decoded_role_id: int
def __init__( self, app ):
super( RoleManager, self ).__init__( app )
:returns: the loaded Role object
:rtype: Role
def get( self, trans, decoded_role_id ):
"""
Method loads the role from the DB based on the given role id.
:raises: InconsistentDatabase, RequestParameterInvalidException, InternalServerError
"""
try:
role = trans.sa_session.query( trans.app.model.Role ).filter( trans.model.Role.table.c.id == decoded_role_id ).one()
except MultipleResultsFound:
raise exceptions.InconsistentDatabase( 'Multiple roles found with the same id.' )
except NoResultFound:
raise exceptions.RequestParameterInvalidException( 'No role found with the id provided.' )
except Exception, e:
raise exceptions.InternalServerError( 'Error loading from the database.' + str(e) )
return role
:param decoded_role_id: id of the role to load from the DB
:type decoded_role_id: int
:returns: the loaded Role object
:rtype: Role
:raises: InconsistentDatabase, RequestParameterInvalidException, InternalServerError
"""
try:
role = ( self.session().query( self.model_class )
.filter( self.model_class.id == decoded_role_id ).one() )
except sqlalchemy_exceptions.MultipleResultsFound:
raise galaxy.exceptions.InconsistentDatabase( 'Multiple roles found with the same id.' )
except sqlalchemy_exceptions.NoResultFound:
raise galaxy.exceptions.RequestParameterInvalidException( 'No role found with the id provided.' )
except Exception, e:
raise galaxy.exceptions.InternalServerError( 'Error loading from the database.' + str(e) )
return role
+17 -17
View File
@@ -16,35 +16,35 @@ class AccessibleManagerMixin( object ):
"""
# don't want to override by_id since consumers will also want to fetch w/o any security checks
def is_accessible( self, trans, item, user ):
def is_accessible( self, item, user, **kwargs ):
"""
Return True if the item accessible to user.
"""
# override in subclasses
raise exceptions.NotImplemented( "Abstract interface Method" )
def get_accessible( self, trans, id, user, **kwargs ):
def get_accessible( self, id, user, **kwargs ):
"""
Return the item with the given id if it's accessible to user,
otherwise raise an error.
:raises exceptions.ItemAccessibilityException:
"""
item = self.by_id( trans, id )
return self.error_unless_accessible( trans, item, user )
item = self.by_id( id )
return self.error_unless_accessible( item, user, **kwargs )
def error_unless_accessible( self, trans, item, user ):
def error_unless_accessible( self, item, user, **kwargs ):
"""
Raise an error if the item is NOT accessible to user, otherwise return the item.
:raises exceptions.ItemAccessibilityException:
"""
if self.is_accessible( trans, item, user ):
if self.is_accessible( item, user, **kwargs ):
return item
raise exceptions.ItemAccessibilityException( "%s is not accessible by user" % ( self.model_class.__name__ ) )
# TODO:?? are these even useful?
def list_accessible( self, trans, user, **kwargs ):
def list_accessible( self, user, **kwargs ):
"""
Return a list of items accessible to the user, raising an error if ANY
are inaccessible.
@@ -56,7 +56,7 @@ class AccessibleManagerMixin( object ):
# items = ModelManager.list( self, trans, **kwargs )
# return [ self.error_unless_accessible( trans, item, user ) for item in items ]
def filter_accessible( self, trans, user, **kwargs ):
def filter_accessible( self, user, **kwargs ):
"""
Return a list of items accessible to the user.
"""
@@ -76,34 +76,34 @@ class OwnableManagerMixin( object ):
This can also be thought of as write/edit privileges.
"""
def is_owner( self, trans, item, user ):
def is_owner( self, item, user, **kwargs ):
"""
Return True if user owns the item.
"""
# override in subclasses
raise exceptions.NotImplemented( "Abstract interface Method" )
def get_owned( self, trans, id, user, **kwargs ):
def get_owned( self, id, user, **kwargs ):
"""
Return the item with the given id if owned by the user,
otherwise raise an error.
:raises exceptions.ItemOwnershipException:
"""
item = self.by_id( trans, id )
return self.error_unless_owner( trans, item, user )
item = self.by_id( id )
return self.error_unless_owner( item, user, **kwargs )
def error_unless_owner( self, trans, item, user ):
def error_unless_owner( self, item, user, **kwargs ):
"""
Raise an error if the item is NOT owned by user, otherwise return the item.
:raises exceptions.ItemAccessibilityException:
"""
if self.is_owner( trans, item, user ):
if self.is_owner( item, user, **kwargs ):
return item
raise exceptions.ItemOwnershipException( "%s is not owned by user" % ( self.model_class.__name__ ) )
def list_owned( self, trans, user, **kwargs ):
def list_owned( self, user, **kwargs ):
"""
Return a list of items owned by the user, raising an error if ANY
are not.
@@ -114,9 +114,9 @@ class OwnableManagerMixin( object ):
# just alias to by_user (easier/same thing)
#return self.by_user( trans, user, **kwargs )
def filter_owned( self, trans, user, **kwargs ):
def filter_owned( self, user, **kwargs ):
"""
Return a list of items owned by the user.
"""
# just alias to list_owned
return self.list_owned( trans, user, **kwargs )
return self.list_owned( user, **kwargs )
+86 -82
View File
@@ -42,30 +42,26 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
self.user_manager = users.UserManager( app )
# .... has a user
def by_user( self, trans, user, filters=None, **kwargs ):
def by_user( self, user, filters=None, **kwargs ):
"""
Return list for all items (of model_class type) associated with the given
`user`.
"""
user_filter = self.model_class.user_id == user.id
filters=self._munge_filters( user_filter, filters )
return self.list( trans, filters=filters, **kwargs )
return self.list( filters=filters, **kwargs )
# .... owned model interface
def is_owner( self, trans, item, user ):
# .... owned/accessible interfaces
def is_owner( self, item, user, **kwargs ):
"""
Return true if this sharable belongs to `user` (or `user` is an admin).
"""
if self.user_manager.is_anonymous( user ):
# note: this needs to be overridden in the case of anon users and session.current_history
return False
# ... effectively a good fit to have this here, but not semantically
if self.user_manager.is_admin( trans, user ):
if self.user_manager.is_admin( user ):
return True
return item.user == user
# .... accessible interface
def is_accessible( self, trans, item, user ):
def is_accessible( self, item, user, **kwargs ):
"""
If the item is importable, is owned by `user`, or (the valid) `user`
is in 'users shared with' list for the item: return True.
@@ -73,7 +69,7 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
if item.importable:
return True
# note: owners always have access - checking for accessible implicitly checks for ownership
if self.is_owner( trans, item, user ):
if self.is_owner( item, user, **kwargs ):
return True
if self.user_manager.is_anonymous( user ):
return False
@@ -82,16 +78,16 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
return False
# .... importable
def make_importable( self, trans, item, flush=True ):
def make_importable( self, item, flush=True ):
"""
Makes item accessible--viewable and importable--and sets item's slug.
Does not flush/commit changes, however. Item must have name, user,
importable, and slug attributes.
"""
self.create_unique_slug( trans, item, flush=False )
self.create_unique_slug( item, flush=False )
return self._session_setattr( item, 'importable', True, flush=flush )
def make_non_importable( self, trans, item, flush=True ):
def make_non_importable( self, item, flush=True ):
"""
Makes item accessible--viewable and importable--and sets item's slug.
Does not flush/commit changes, however. Item must have name, user,
@@ -99,110 +95,112 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
"""
# item must be unpublished if non-importable
if item.published:
self.unpublish( trans, item, flush=False )
self.unpublish( item, flush=False )
return self._session_setattr( item, 'importable', False, flush=flush )
# .... published
def publish( self, trans, item, flush=True ):
def publish( self, item, flush=True ):
"""
Set both the importable and published flags on `item` to True.
"""
# item must be importable to be published
if not item.importable:
self.make_importable( trans, item, flush=False )
self.make_importable( item, flush=False )
return self._session_setattr( item, 'published', True, flush=flush )
def unpublish( self, trans, item, flush=True ):
def unpublish( self, item, flush=True ):
"""
Set the published flag on `item` to False.
"""
return self._session_setattr( item, 'published', False, flush=flush )
#def _query_published( self, trans, filters=None, **kwargs ):
# """
# """
# published_filter = self.model_class.published == True
# filters = self._munge_filters( published_filter, filters )
# return self.list( trans, filters=filters, **kwargs )
#
#def list_published( self, trans, **kwargs ):
# """
# """
# query = self._query_published( trans, user, **kwargs )
# return self.list( trans, query=query, **kwargs )
def _query_published( self, filters=None, **kwargs ):
"""
Query all published items.
"""
published_filter = self.model_class.published == True
filters = self._munge_filters( published_filter, filters )
return self.list( filters=filters, **kwargs )
def list_published( self, **kwargs ):
"""
Return a list of all published items.
"""
query = self._query_published( **kwargs )
return self.list( query=query, **kwargs )
# .... user sharing
# sharing is often done via a 3rd table btwn a User and an item -> a <Item>UserShareAssociation
def get_share_assocs( self, trans, item, user=None ):
def get_share_assocs( self, item, user=None ):
"""
Get the UserShareAssociations for the `item`.
Optionally send in `user` to test for a single match.
"""
query = self.query_associated( trans, self.user_share_model, item )
query = self.query_associated( self.user_share_model, item )
if user is not None:
query = query.filter_by( user=user )
return query.all()
def share_with( self, trans, item, user, flush=True ):
def share_with( self, item, user, flush=True ):
"""
Get or create a share for the given user (or users if `user` is a list).
"""
# allow user to be a list and call recursivly
if isinstance( user, list ):
return map( lambda user: self.share_with( trans, item, user, flush=False ), user )
return map( lambda user: self.share_with( item, user, flush=False ), user )
# get or create
existing = self.get_share_assocs( trans, item, user=user )
existing = self.get_share_assocs( item, user=user )
if existing:
return existing.pop( 0 )
return self._create_user_share_assoc( trans, item, user, flush=flush )
return self._create_user_share_assoc( item, user, flush=flush )
def _create_user_share_assoc( self, trans, item, user, flush=True ):
def _create_user_share_assoc( self, item, user, flush=True ):
"""
Create a share for the given user.
"""
user_share_assoc = self.user_share_model()
self.app.model.context.add( user_share_assoc )
self.associate( trans, user_share_assoc, item )
self.session().add( user_share_assoc )
self.associate( user_share_assoc, item )
user_share_assoc.user = user
# ensure an item slug so shared users can access
if not item.slug:
self.create_unique_slug( trans, item )
self.create_unique_slug( item )
if flush:
self.app.model.context.flush()
self.session().flush()
return user_share_assoc
def unshare_with( self, trans, item, user, flush=True ):
def unshare_with( self, item, user, flush=True ):
"""
Delete a user share (or list of shares) from the database.
"""
if isinstance( user, list ):
return map( lambda user: self.unshare_with( trans, item, user, flush=False ), user )
return map( lambda user: self.unshare_with( item, user, flush=False ), user )
# Look for and delete sharing relation for user.
user_share_assoc = self.get_share_assocs( trans, item, user=user )[0]
self.app.model.context.delete( user_share_assoc )
user_share_assoc = self.get_share_assocs( item, user=user )[0]
self.session().delete( user_share_assoc )
if flush:
self.app.model.context.flush()
self.session().flush()
return user_share_assoc
#def _query_shared_with( self, trans, user, filters=None, **kwargs ):
#def _query_shared_with( self, user, filters=None, **kwargs ):
##TODO:
# """
# """
# pass
#def list_shared_with( self, trans, user, **kwargs ):
#def list_shared_with( self, user, **kwargs ):
# """
# """
# query = self._query_shared_with( trans, user, **kwargs )
# return self.list( trans, query=query, **kwargs )
# query = self._query_shared_with( user, **kwargs )
# return self.list( query=query, **kwargs )
# .... slugs
# slugs are human readable strings often used to link to sharable resources (replacing ids)
#TODO: as validator, deserializer, etc. (maybe another object entirely?)
def set_slug( self, trans, item, new_slug, flush=True ):
def set_slug( self, item, new_slug, user, flush=True ):
"""
Validate and set the new slug for `item`.
"""
@@ -211,12 +209,12 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
raise exceptions.RequestParameterInvalidException( "Invalid slug", slug=new_slug )
# error if slug is already in use
if self._slug_exists( trans, trans.user, new_slug ):
if self._slug_exists( user, new_slug ):
raise exceptions.Conflict( "Slug already exists", slug=new_slug )
item.slug = new_slug
if flush:
self.app.model.context.flush()
self.session().flush()
return item
def is_valid_slug( self, slug ):
@@ -226,13 +224,13 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
VALID_SLUG_RE = re.compile( "^[a-z0-9\-]+$" )
return VALID_SLUG_RE.match( slug )
def _existing_set_of_slugs( self, trans, user ):
query = ( self.app.model.context.query( self.model_class.slug )
def _existing_set_of_slugs( self, user ):
query = ( self.session().query( self.model_class.slug )
.filter_by( user=user ) )
return set( query.all() )
return list( set( query.all() ) )
def _slug_exists( self, trans, user, slug ):
query = ( self.app.model.context.query( self.model_class.slug )
def _slug_exists( self, user, slug ):
query = ( self.session().query( self.model_class.slug )
.filter_by( user=user, slug=slug ) )
return query.count() != 0
@@ -246,13 +244,13 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
slug_base = slug_base[:-1]
return slug_base
def _default_slug_base( self, trans, item ):
def _default_slug_base( self, item ):
# override in subclasses
if hasattr( item, 'title' ):
return item.title.lower()
return item.name.lower()
def get_unique_slug( self, trans, item ):
def get_unique_slug( self, item ):
"""
Returns a slug that is unique among user's importable items
for item's class.
@@ -261,7 +259,7 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
# Setup slug base.
if cur_slug is None or cur_slug == "":
slug_base = self._slugify( self._default_slug_base( trans, item ) )
slug_base = self._slugify( self._default_slug_base( item ) )
else:
slug_base = cur_slug
@@ -269,7 +267,7 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
# add integer to end.
new_slug = slug_base
count = 1
while ( self.app.model.context.query( item.__class__ )
while ( self.session().query( item.__class__ )
.filter_by( user=item.user, slug=new_slug, importable=True )
.count() != 0 ):
# Slug taken; choose a new slug based on count. This approach can
@@ -279,28 +277,28 @@ class SharableModelManager( base.ModelManager, secured.OwnableManagerMixin, secu
return new_slug
def create_unique_slug( self, trans, item, flush=True ):
def create_unique_slug( self, item, flush=True ):
"""
Set a new, unique slug on the item.
"""
item.slug = self.get_unique_slug( trans, item )
self.app.model.context.add( item )
item.slug = self.get_unique_slug( item )
self.session().add( item )
if flush:
self.app.model.context.flush()
self.session().flush()
return item
#def by_slug( self, trans, user, **kwargs ):
#def by_slug( self, user, **kwargs ):
# """
# """
# pass
# .... display
#def display_by_username_and_slug( self, trans, username, slug ):
#def display_by_username_and_slug( self, username, slug ):
# """ Display item by username and slug. """
#def set_public_username( self, trans, id, username, **kwargs ):
#def set_public_username( self, id, username, **kwargs ):
# """ Set user's public username and delegate to sharing() """
#def sharing( self, trans, id, **kwargs ):
#def sharing( self, id, **kwargs ):
# """ Handle item sharing. """
@@ -319,20 +317,21 @@ class SharableModelSerializer( base.ModelSerializer,
'user_id' : self.serialize_id,
'username_and_slug' : self.serialize_username_and_slug
})
# these use the default serializer but must still be white-listed
self.serializable_keyset.update([
'importable', 'published', 'slug'
])
def serialize_username_and_slug( self, trans, item, key ):
def serialize_username_and_slug( self, item, key, **context ):
if not ( item.user and item.slug and self.SINGLE_CHAR_ABBR ):
return None
#TODO: self.url_for
return ( '/' ).join(( 'u', item.user.username, self.SINGLE_CHAR_ABBR, item.slug ) )
#def published_url( self, trans, item, key ):
# def serialize_username_and_slug( self, item, key, **context ):
# """
# """
# url = url_for(controller='history', action="display_by_username_and_slug",
# #TODO: replace the above with this
# url = self.url_for(controller='history', action="display_by_username_and_slug",
# username=item.user.username, slug=item.slug )
# return url
@@ -356,7 +355,7 @@ class SharableModelDeserializer( base.ModelDeserializer,
'importable' : self.deserialize_importable,
})
def deserialize_published( self, trans, item, key, val ):
def deserialize_published( self, item, key, val, **context ):
"""
"""
val = self.validate.bool( key, val )
@@ -364,12 +363,12 @@ class SharableModelDeserializer( base.ModelDeserializer,
return val
if val:
self.manager.publish( trans, item, flush=False )
self.manager.publish( item, flush=False )
else:
self.manager.unpublish( trans, item, flush=False )
self.manager.unpublish( item, flush=False )
return item.published
def deserialize_importable( self, trans, item, key, val ):
def deserialize_importable( self, item, key, val, **context ):
"""
"""
val = self.validate.bool( key, val )
@@ -377,12 +376,12 @@ class SharableModelDeserializer( base.ModelDeserializer,
return val
if val:
self.manager.make_importable( trans, item, flush=False )
self.manager.make_importable( item, flush=False )
else:
self.manager.make_non_importable( trans, item, flush=False )
self.manager.make_non_importable( item, flush=False )
return item.published
#def deserialize_slug( self, trans, item, val ):
#def deserialize_slug( self, item, val, **context ):
# """
# """
# #TODO: call manager.set_slug
@@ -391,10 +390,15 @@ class SharableModelDeserializer( base.ModelDeserializer,
#def deserialize_user_shares():
class SharableModelFilters( base.ModelFilterParser ):
class SharableModelFilters( base.ModelFilterParser,
taggable.TaggableFilterMixin,
annotatable.AnnotatableFilterMixin ):
def _add_parsers( self ):
super( SharableModelFilters, self )._add_parsers()
taggable.TaggableFilterMixin._add_parsers( self )
annotatable.AnnotatableFilterMixin._add_parsers( self )
self.orm_filter_parsers.update({
'importable' : { 'op': ( 'eq' ), 'val': self.parse_bool },
'published' : { 'op': ( 'eq' ), 'val': self.parse_bool },
+43 -5
View File
@@ -15,7 +15,7 @@ class TaggableManagerMixin( object ):
#TODO: most of this can be done by delegating to the TagManager?
#def by_user( self, trans, user, **kwargs ):
#def by_user( self, user, **kwargs ):
# pass
@@ -24,7 +24,7 @@ class TaggableSerializerMixin( object ):
def add_serializers( self ):
self.serializers[ 'tags' ] = self.serialize_tags
def serialize_tags( self, trans, item, key ):
def serialize_tags( self, item, key, **context ):
"""
Return tags as a list of strings.
"""
@@ -45,7 +45,7 @@ class TaggableDeserializerMixin( object ):
def add_deserializers( self ):
self.deserializers[ 'tags' ] = self.deserialize_tags
def deserialize_tags( self, trans, item, key, val ):
def deserialize_tags( self, item, key, val, user=None, **context ):
"""
Make sure `val` is a valid list of tag strings and assign them.
@@ -53,8 +53,6 @@ class TaggableDeserializerMixin( object ):
"""
new_tags_list = self.validate.basestring_list( key, val )
#TODO: have to assume trans.user here...
#TODO: trans
user = trans.user
#TODO: duped from tags manager - de-dupe when moved to taggable mixin
tag_handler = self.app.tag_handler
tag_handler.delete_item_tags( user, item )
@@ -64,3 +62,43 @@ class TaggableDeserializerMixin( object ):
#TODO:!! does the creation of new_tags_list mean there are now more and more unused tag rows in the db?
return item.tags
class TaggableFilterMixin( object ):
def _tag_str_gen( self, item ):
"""
Return a list of strings built from the item's tags.
"""
#TODO: which user is this? all?
for tag in item.tags:
tag_str = tag.user_tname
if tag.value is not None:
tag_str += ":" + tag.user_value
yield tag_str
def filter_has_partial_tag( self, item, val ):
"""
Return True if any tag partially contains `val`.
"""
for tag_str in self._tag_str_gen( item ):
if val in tag_str:
return True
return False
def filter_has_tag( self, item, val ):
"""
Return True if any tag exactly equals `val`.
"""
for tag_str in self._tag_str_gen( item ):
if val == tag_str:
return True
return False
def _add_parsers( self ):
self.fn_filter_parsers.update({
'tag': {
'op': {
'eq' : self.filter_has_tag,
'has' : self.filter_has_partial_tag,
}
}
})
+69 -63
View File
@@ -31,7 +31,7 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
#TODO: incorporate security/validate_user_input.py
#TODO: incorporate UsesFormDefinitionsMixin?
def create( self, trans, **kwargs ):
def create( self, webapp_name=None, **kwargs ):
"""
Create a new user.
"""
@@ -39,7 +39,7 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
email = kwargs[ 'email' ]
username = kwargs[ 'username' ]
password = kwargs[ 'password' ]
self._error_on_duplicate_email( trans, email )
self._error_on_duplicate_email( email )
user = model.User( email=email, password=password )
user.username = username
@@ -50,9 +50,9 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
# Activation is off, every new user is active by default.
user.active = True
self.app.model.context.add( user )
self.session().add( user )
try:
self.app.model.context.flush()
self.session().flush()
#TODO:?? flush needed for permissions below? If not, make optional
except sqlalchemy.exc.IntegrityError, db_err:
raise exceptions.Conflict( db_err.message )
@@ -60,72 +60,71 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
# can throw an sqlalx.IntegrityError if username not unique
self.app.security_agent.create_private_user_role( user )
#TODO: trans
if trans.webapp.name == 'galaxy':
if webapp_name == 'galaxy':
# We set default user permissions, before we log in and set the default history permissions
permissions = self.app.config.new_user_dataset_access_role_default_private
self.app.security_agent.user_set_default_permissions( user, default_access_private=permissions )
return user
def _error_on_duplicate_email( self, trans, email ):
def _error_on_duplicate_email( self, email ):
"""
Check for a duplicate email and raise if found.
:raises exceptions.Conflict: if any are found
"""
#TODO: remove this check when unique=True is added to the email column
if self.by_email( trans, email ) is not None:
if self.by_email( email ) is not None:
raise exceptions.Conflict( 'Email must be unique', email=email )
# ---- filters
def by_email( self, trans, email, filters=None, **kwargs ):
def by_email( self, email, filters=None, **kwargs ):
"""
Find a user by their email.
"""
filters = self._munge_filters( self.model_class.email == email, filters )
try:
#TODO: use one_or_none
return super( UserManager, self ).one( trans, filters=filters, **kwargs )
return super( UserManager, self ).one( filters=filters, **kwargs )
except exceptions.ObjectNotFound, not_found:
return None
def by_email_like( self, trans, email_with_wildcards, filters=None, order_by=None, **kwargs ):
def by_email_like( self, email_with_wildcards, filters=None, order_by=None, **kwargs ):
"""
Find a user searching with SQL wildcards.
"""
filters = self._munge_filters( self.model_class.email.like( email_with_wildcards ), filters )
order_by = order_by or ( model.User.email, )
return super( UserManager, self ).list( trans, filters=filters, order_by=order_by, **kwargs )
return super( UserManager, self ).list( filters=filters, order_by=order_by, **kwargs )
# ---- admin
def is_admin( self, trans, user ):
def is_admin( self, user ):
"""
Return True if this user is an admin.
"""
admin_emails = self._admin_emails( trans )
admin_emails = self._admin_emails()
return user and admin_emails and user.email in admin_emails
def _admin_emails( self, trans ):
def _admin_emails( self ):
"""
Return a list of admin email addresses from the config file.
"""
return [ email.strip() for email in self.app.config.get( "admin_users", "" ).split( "," ) ]
def admins( self, trans, filters=None, **kwargs ):
def admins( self, filters=None, **kwargs ):
"""
Return a list of admin Users.
"""
filters = self._munge_filters( self.model_class.email.in_( self._admin_emails( trans ) ), filters )
return super( UserManager, self ).list( trans, filters=filters, **kwargs )
filters = self._munge_filters( self.model_class.email.in_( self._admin_emails() ), filters )
return super( UserManager, self ).list( filters=filters, **kwargs )
def error_unless_admin( self, trans, user, msg="Administrators only", **kwargs ):
def error_unless_admin( self, user, msg="Administrators only", **kwargs ):
"""
Raise an error if `user` is not an admin.
:raises exceptions.AdminRequiredException: if `user` is not an admin.
"""
# useful in admin only methods
if not self.is_admin( trans, user ):
if not self.is_admin( user ):
raise exceptions.AdminRequiredException( msg, **kwargs )
return user
@@ -137,22 +136,23 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
# define here for single point of change and make more readable
return user is None
def error_if_anonymous( self, trans, user, msg="Log-in required", **kwargs ):
def error_if_anonymous( self, user, msg="Log-in required", **kwargs ):
"""
Raise an error if `user` is anonymous.
"""
if user is None:
#TODO: code is correct (403), but should be named AuthenticationRequired (401 and 403 are flipped)
#TODO: code is correct (401) but should be named AuthenticationRequired (401 and 403 are flipped)
raise exceptions.AuthenticationFailed( msg, **kwargs )
return user
# ---- current
def current_user( self, trans ):
# define here for single point of change and make more readable
# TODO: trans
return trans.user
# ---- api keys
def create_api_key( self, trans, user ):
def create_api_key( self, user ):
"""
Create and return an API key for `user`.
"""
@@ -160,46 +160,48 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
return api_keys.ApiKeyManager( self.app ).create_api_key( user )
#TODO: possibly move to ApiKeyManager
def valid_api_key( self, trans, user ):
def valid_api_key( self, user ):
"""
Return this most recent APIKey for this user or None if none have been created.
"""
query = ( self.app.model.context.query( model.APIKeys )
query = ( self.session().query( model.APIKeys )
.filter_by( user=user )
.order_by( sqlalchemy.desc( model.APIKeys.create_time ) ) )
all = query.all()
for a in all:
print a.user.username, a.key, a.create_time
print all
if len( all ):
return all[0]
return None
#return query.first()
#TODO: possibly move to ApiKeyManager
def get_or_create_valid_api_key( self, trans, user ):
def get_or_create_valid_api_key( self, user ):
"""
Return this most recent APIKey for this user or create one if none have been
created.
"""
existing = self.valid_api_key( trans, user )
existing = self.valid_api_key( user )
if existing:
return existing
return self.create_api_key( self, trans, user )
return self.create_api_key( self, user )
# ---- roles
def private_role( self, trans, user ):
"""
Return the security agent's private role for `user`.
"""
#TODO: not sure we need to go through sec agent... it's just the first role of type private
# ---- preferences
# ---- roles and permissions
def private_role( self, user ):
return self.app.security_agent.get_private_user_role( user )
def quota( self, trans, user ):
def sharing_roles( self, user ):
return self.app.security_agent.get_sharing_roles( user )
def default_permissions( self, user ):
return self.app.security_agent.user_get_default_permissions( user )
def quota( self, user ):
#TODO: use quota manager
return self.app.quota_agent.get_percent( user=user )
def tags_used( self, trans, user, tag_models=None ):
def tags_used( self, user, tag_models=None ):
"""
Return a list of distinct 'user_tname:user_value' strings that the
given user has used.
@@ -214,8 +216,8 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
# create a union of subqueries for each for this user - getting only the tname and user_value
all_tags_query = None
for tag_model in tag_models:
subq = ( self.app.model.context.query( tag_model.user_tname, tag_model.user_value )
.filter( tag_model.user == trans.user ) )
subq = ( self.session().query( tag_model.user_tname, tag_model.user_value )
.filter( tag_model.user == user ) )
all_tags_query = subq if all_tags_query is None else all_tags_query.union( subq )
# if nothing init'd the query, bail
@@ -227,7 +229,7 @@ class UserManager( base.ModelManager, deletable.PurgableManagerMixin ):
tags = [( ( name + ':' + val ) if val else name ) for name, val in tags ]
return sorted( tags )
def has_requests( self, trans, user ):
def has_requests( self, user, trans ):
"""
"""
if self.is_anonymous( user ):
@@ -255,7 +257,7 @@ class UserSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
'total_disk_usage',
'nice_total_disk_usage',
'quota_percent'
'quota_percent',
#'deleted',
#'purged',
@@ -276,30 +278,43 @@ class UserSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
'id' : self.serialize_id,
'create_time' : self.serialize_date,
'update_time' : self.serialize_date,
'is_admin' : lambda t, i, k: self.user_manager.is_admin( t, i ),
'is_admin' : lambda i, k, **c: self.user_manager.is_admin( i ),
'total_disk_usage' : lambda t, i, k: float( i.total_disk_usage ),
'quota_percent' : lambda t, i, k: self.user_manager.quota( t, i ),
'total_disk_usage' : lambda i, k, **c: float( i.total_disk_usage ),
'quota_percent' : lambda i, k, **c: self.user_manager.quota( i ),
'tags_used' : lambda t, i, k: self.user_manager.tags_used( t, i ),
'tags_used' : lambda i, k, **c: self.user_manager.tags_used( i ),
#TODO: 'has_requests' is more apt
'requests' : lambda t, i, k: self.user_manager.has_requests( t, i )
'requests' : lambda i, k, trans=None, **c: self.user_manager.has_requests( i, trans )
})
def serialize_current_anonymous_user( self, trans, user, keys ):
class CurrentUserSerializer( UserSerializer ):
def serialize( self, user, keys, **kwargs ):
"""
Override to return at least some usage info if user is anonymous.
"""
# hmmm.
kwargs[ 'current_user' ] = user
if self.user_manager.is_anonymous( user ):
return self.serialize_current_anonymous_user( user, keys, **kwargs )
return super( UserSerializer, self ).serialize( user, keys, **kwargs )
def serialize_current_anonymous_user( self, user, keys, trans=None, **kwargs ):
# use the current history if any to get usage stats for trans' anonymous user
#TODO: might be better as sep. Serializer class
history = trans.history
if not history:
raise exceptions.AuthenticationRequired( 'No history for anonymous user usage stats' );
usage = trans.app.quota_agent.get_usage( trans, history=trans.history )
percent = trans.app.quota_agent.get_percent( trans=trans, usage=usage )
usage = self.app.quota_agent.get_usage( trans, history=trans.history )
percent = self.app.quota_agent.get_percent( trans=trans, usage=usage )
# a very small subset of keys available
values = {
'id' : None,
'total_disk_usage' : int( usage ),
'total_disk_usage' : float( usage ),
'nice_total_disk_usage' : util.nice_size( usage ),
'quota_percent' : percent,
}
@@ -309,25 +324,16 @@ class UserSerializer( base.ModelSerializer, deletable.PurgableSerializerMixin ):
serialized[ key ] = values[ key ]
return serialized
def serialize( self, trans, user, keys ):
"""
Override to return at least some usage info if user is anonymous.
"""
if self.user_manager.is_anonymous( user ):
return self.serialize_current_anonymous_user( trans, user, keys )
return super( UserSerializer, self ).serialize( trans, user, keys )
class AdminUserFilters( base.ModelFilterParser, deletable.PurgableFiltersMixin ):
class AdminUserFilterParser( base.ModelFilterParser, deletable.PurgableFiltersMixin ):
model_class = model.User
def _add_parsers( self ):
super( AdminUserFilters, self )._add_parsers()
super( AdminUserFilterParser, self )._add_parsers()
deletable.PurgableFiltersMixin._add_parsers( self )
#PRECONDITION: user making the query has been verified as an admin
self.orm_filter_parsers.update({
'name' : { 'op': ( 'eq', 'contains', 'like' ) },
'email' : { 'op': ( 'eq', 'contains', 'like' ) },
'username' : { 'op': ( 'eq', 'contains', 'like' ) },
'active' : { 'op': ( 'eq' ) },
+9 -8
View File
@@ -103,7 +103,6 @@ model.HistoryDatasetAssociation.table = Table( "history_dataset_association", me
Column( "state", TrimmedString( 64 ), index=True, key="_state" ),
Column( "copied_from_history_dataset_association_id", Integer, ForeignKey( "history_dataset_association.id" ), nullable=True ),
Column( "copied_from_library_dataset_dataset_association_id", Integer, ForeignKey( "library_dataset_dataset_association.id" ), nullable=True ),
Column( "hid", Integer ),
Column( "name", TrimmedString( 255 ) ),
Column( "info", TrimmedString( 255 ) ),
Column( "blurb", TrimmedString( 255 ) ),
@@ -114,12 +113,14 @@ model.HistoryDatasetAssociation.table = Table( "history_dataset_association", me
Column( "parent_id", Integer, ForeignKey( "history_dataset_association.id" ), nullable=True ),
Column( "designation", TrimmedString( 255 ) ),
Column( "deleted", Boolean, index=True, default=False ),
Column( "purged", Boolean, index=True, default=False ),
Column( "visible", Boolean ),
Column( "extended_metadata_id", Integer, ForeignKey( "extended_metadata.id" ), index=True ),
# differs from ldda
Column( "hid", Integer ),
Column( "purged", Boolean, index=True, default=False ),
Column( "hidden_beneath_collection_instance_id", ForeignKey( "history_dataset_collection_association.id" ), nullable=True ),
Column( "extended_metadata_id", Integer,
ForeignKey( "extended_metadata.id" ), index=True )
)
)
model.Dataset.table = Table( "dataset", metadata,
Column( "id", Integer, primary_key=True ),
@@ -322,11 +323,11 @@ model.LibraryDatasetDatasetAssociation.table = Table( "library_dataset_dataset_a
Column( "designation", TrimmedString( 255 ) ),
Column( "deleted", Boolean, index=True, default=False ),
Column( "visible", Boolean ),
Column( "extended_metadata_id", Integer, ForeignKey( "extended_metadata.id" ), index=True ),
# differs from hda
Column( "user_id", Integer, ForeignKey( "galaxy_user.id" ), index=True ),
Column( "message", TrimmedString( 255 ) ),
Column( "extended_metadata_id", Integer,
ForeignKey( "extended_metadata.id" ), index=True )
)
)
model.ExtendedMetadata.table = Table("extended_metadata", metadata,
+5 -3
View File
@@ -37,7 +37,7 @@ def validate_email( trans, email, user=None, check_dup=True ):
def validate_publicname( trans, publicname, user=None ):
# User names must be at least four characters in length and contain only lower-case
# User names must be at least three characters in length and contain only lower-case
# letters, numbers, and the '-' character.
if user and user.username == publicname:
return ''
@@ -45,8 +45,10 @@ def validate_publicname( trans, publicname, user=None ):
if len( publicname ) < 3:
return "Public name must be at least 3 characters in length"
else:
if len( publicname ) < 4:
return "Public name must be at least 4 characters in length"
# DCT - TODO - Simplify logic if 3 chars is okay for publicname for
# galaxy as well as toolshed
if len( publicname ) < 3:
return "Public name must be at least 3 characters in length"
if len( publicname ) > 255:
return "Public name cannot be more than 255 characters in length"
if not( VALID_PUBLICNAME_RE.match( publicname ) ):
+84 -28
View File
@@ -40,7 +40,7 @@ from galaxy.tools.parameters.basic import (BaseURLToolParameter,
DataToolParameter, DataCollectionToolParameter, HiddenToolParameter, LibraryDatasetToolParameter,
SelectToolParameter, ToolParameter, UnvalidatedValue,
IntegerToolParameter, FloatToolParameter)
from galaxy.tools.parameters.grouping import Conditional, ConditionalWhen, Repeat, UploadDataset
from galaxy.tools.parameters.grouping import Conditional, ConditionalWhen, Repeat, Section, UploadDataset
from galaxy.tools.parameters.input_translation import ToolInputTranslator
from galaxy.tools.parameters.output import ToolOutputActionGroup
from galaxy.tools.parameters.validation import LateValidationError
@@ -972,6 +972,15 @@ class Tool( object, Dictifiable ):
case.inputs = odict()
group.cases.append( case )
rval[group.name] = group
elif input_type == "section":
group = Section()
group.name = input_source.get( "name" )
group.title = input_source.get( "title" )
group.help = input_source.get( "help", None )
group.expanded = input_source.get_bool( "expanded", False )
page_source = input_source.parse_nested_inputs_source()
group.inputs = self.parse_input_elem( page_source, enctypes, context )
rval[group.name] = group
elif input_type == "upload_dataset":
elem = input_source.elem()
group = UploadDataset()
@@ -1505,6 +1514,20 @@ class Tool( object, Dictifiable ):
group_state['__current_case__'] = current_case
# Store the value of the test element
group_state[ input.test_param.name ] = value
elif isinstance( input, Section ):
group_state = state[input.name]
group_prefix = "%s|" % ( key )
self.fill_in_new_state( trans, input.inputs, group_state, context, history=history )
group_errors = self.populate_state( trans,
input.inputs,
group_state,
incoming,
history,
source,
prefix=group_prefix,
context=context )
if group_errors:
errors[ input.name ] = group_errors
elif isinstance( input, UploadDataset ):
group_state = state[input.name]
group_errors = []
@@ -1714,6 +1737,22 @@ class Tool( object, Dictifiable ):
group_state['__current_case__'] = current_case
# Store the value of the test element
group_state[ input.test_param.name ] = value
elif isinstance( input, Section ):
group_state = state[input.name]
group_old_errors = old_errors.get( input.name, {} )
group_prefix = "%s|" % ( key )
group_errors = self.update_state( trans,
input.inputs,
group_state,
incoming,
prefix=group_prefix,
context=context,
source=source,
update_only=update_only,
old_errors=group_old_errors,
item_callback=item_callback )
if group_errors:
errors[ input.name ] = group_errors
elif isinstance( input, UploadDataset ):
group_state = state[input.name]
group_errors = []
@@ -2383,6 +2422,10 @@ class Tool( object, Dictifiable ):
errors[test_param_key] = 'The selected case is unavailable/invalid.'
pass
group_state[input.test_param.name] = value
elif input.type == 'section':
group_state = state[input.name]
group_prefix = "%s|" % ( key )
populate_state(trans, input.inputs, group_state, errors, incoming, prefix=group_prefix, context=context)
else:
default_value = incoming.get(key, state.get(input.name, None))
value, error = check_state(trans, input, default_value, context)
@@ -2417,11 +2460,13 @@ class Tool( object, Dictifiable ):
if i == group_state.get('__current_case__', None):
current_state = group_state
iterate(tool_dict['cases'][i]['inputs'], input.cases[i].inputs, current_state, other_values)
elif input.type == 'section':
iterate( tool_dict['inputs'], input.inputs, group_state, other_values )
else:
# identify name
input_name = tool_dict.get('name')
# create expanded input dictionary incl. repeats and dynamic_parameters
# expand input dictionary incl. repeats and dynamic_parameters
try:
tool_dict = input.to_dict(trans, other_values=other_values)
except Exception:
@@ -2555,28 +2600,44 @@ class Tool( object, Dictifiable ):
# dataset used; parameter should be the analygous dataset in the
# current history.
history = trans.get_history()
hda_source_dict = {} # Mapping from HDA in history to source HDAs.
# Create index for hdas.
hda_source_dict = {}
for hda in history.datasets:
source_hda = hda.copied_from_history_dataset_association
while source_hda:
if source_hda.dataset.id not in hda_source_dict or source_hda.hid == hda.hid:
hda_source_dict[ source_hda.dataset.id ] = hda
source_hda = source_hda.copied_from_history_dataset_association
key = '%s_%s' % (hda.hid, hda.dataset.id)
hda_source_dict[ hda.dataset.id ] = hda_source_dict[ key ] = hda
# Ditto for dataset collections.
hdca_source_dict = {}
for hdca in history.dataset_collections:
source_hdca = hdca.copied_from_history_dataset_collection_association
while source_hdca:
if source_hdca.collection.id not in hdca_source_dict or source_hdca.hid == hdca.hid:
hdca_source_dict[ source_hdca.collection.id ] = hdca
source_hdca = source_hdca.copied_from_history_dataset_collection_association
key = '%s_%s' % (hdca.hid, hdca.collection.id)
hdca_source_dict[ hda.collection.id ] = hdca_source_dict[ key ] = hdca
# Map dataset or collection to current history
def map_to_history(value):
id = None
source = None
if isinstance(value, trans.app.model.HistoryDatasetAssociation):
id = value.dataset.id
source = hda_source_dict
elif isinstance(value, trans.app.model.HistoryDatasetCollectionAssociation):
id = value.collection.id
source = hdca_source_dict
else:
return None
key = '%s_%s' % (value.hid, id)
if key in source:
return source[ key ]
elif id in source:
return source[ id ]
else:
return None
# Unpack unvalidated values to strings, they'll be validated when the
# form is submitted (this happens when re-running a job that was
# initially run by a workflow)
#This needs to be done recursively through grouping parameters
def rerun_callback( input, value, prefixed_name, prefixed_label ):
def mapping_callback( input, value, prefixed_name, prefixed_label ):
if isinstance( value, UnvalidatedValue ):
try:
return input.to_html_value( value.value, trans.app )
@@ -2588,22 +2649,17 @@ class Tool( object, Dictifiable ):
if isinstance(value,list):
values = []
for val in value:
if isinstance(val, trans.app.model.HistoryDatasetAssociation):
if val.dataset.id in hda_source_dict:
values.append( hda_source_dict[ val.dataset.id ] )
else:
values.append( val )
new_val = map_to_history( val )
if new_val:
values.append( new_val )
else:
values.append( val )
return values
if isinstance(value, trans.app.model.HistoryDatasetAssociation):
if value.dataset.id in hda_source_dict:
return hda_source_dict[ value.dataset.id ]
if isinstance(value, trans.app.model.HistoryDatasetCollectionAssociation):
if value.collection.id in hdca_source_dict:
return hdca_source_dict[ value.collection.id ]
else:
return map_to_history( value )
elif isinstance( input, DataCollectionToolParameter ):
if value.collection.id in hdca_source_dict:
return hdca_source_dict[ value.collection.id ]
visit_input_values( tool_inputs, params, rerun_callback )
return map_to_history( value )
visit_input_values( tool_inputs, params, mapping_callback )
def _compare_tool_version( self, trans, job ):
"""
+4 -1
View File
@@ -22,7 +22,7 @@ from galaxy.tools.parameters.basic import (
LibraryDatasetToolParameter,
SelectToolParameter,
)
from galaxy.tools.parameters.grouping import Conditional, Repeat
from galaxy.tools.parameters.grouping import Conditional, Repeat, Section
from galaxy.jobs.datasets import dataset_path_rewrites
import logging
@@ -143,6 +143,9 @@ class ToolEvaluator( object ):
values = input_values[ input.name ]
current = values["__current_case__"]
do_walk( input.cases[current].inputs, values )
elif isinstance( input, Section ):
values = input_values[ input.name ]
do_walk( input.inputs, values )
else:
func( input_values, input )
+9
View File
@@ -33,6 +33,11 @@ def visit_input_values( inputs, input_values, callback, name_prefix="", label_pr
label_prefix = label_prefix
new_name_prefix = name_prefix + input.name + "|"
visit_input_values( input.cases[current].inputs, values, callback, new_name_prefix, label_prefix )
elif isinstance( input, Section ):
values = input_values[ input.name ]
label_prefix = label_prefix
new_name_prefix = name_prefix + input.name + "|"
visit_input_values( input.inputs, values, callback, new_name_prefix, label_prefix )
else:
new_value = callback( input,
input_values[input.name],
@@ -122,6 +127,10 @@ def params_to_incoming( incoming, inputs, input_values, app, name_prefix="", to_
new_name_prefix = name_prefix + input.name + "|"
incoming[ new_name_prefix + input.test_param.name ] = values[ input.test_param.name ]
params_to_incoming( incoming, input.cases[current].inputs, values, app, new_name_prefix, to_html=to_html )
elif isinstance( input, Section ):
values = input_values[ input.name ]
new_name_prefix = name_prefix + input.name + "|"
params_to_incoming( incoming, input.inputs, values, app, new_name_prefix, to_html=to_html )
else:
value = input_values.get( input.name )
if to_html:
+17 -6
View File
@@ -38,14 +38,15 @@ class ToolParameter( object, Dictifiable ):
moment but in the future should encapsulate more complex parameters (lists
of valid choices, validation logic, ...)
"""
dict_collection_visible_keys = ( 'name', 'type', 'label', 'help' )
dict_collection_visible_keys = ( 'name', 'argument', 'type', 'label', 'help' )
def __init__( self, tool, input_source, context=None ):
input_source = ensure_input_source(input_source)
self.tool = tool
self.refresh_on_change = False
self.refresh_on_change_values = []
self.name = input_source.get("name")
self.argument = input_source.get("argument")
self.name = ToolParameter.parse_name( input_source )
self.type = input_source.get("type")
self.hidden = input_source.get("hidden", False)
self.is_dynamic = False
@@ -222,9 +223,7 @@ class ToolParameter( object, Dictifiable ):
@classmethod
def build( cls, tool, param ):
"""Factory method to create parameter of correct type"""
param_name = param.get( "name" )
if not param_name:
raise ValueError( "Tool parameter '%s' requires a 'name'" % (param_name ) )
param_name = cls.parse_name( param )
param_type = param.get("type")
if not param_type:
raise ValueError( "Tool parameter '%s' requires a 'type'" % ( param_name ) )
@@ -233,6 +232,17 @@ class ToolParameter( object, Dictifiable ):
else:
return parameter_types[param_type]( tool, param )
@classmethod
def parse_name(cls, input_source):
name = input_source.get("name", None)
if name is None:
argument = input_source.get("argument")
if argument:
name = argument.lstrip("-")
else:
raise ValueError("Tool parameter must specify a name.")
return name
class TextToolParameter( ToolParameter ):
"""
@@ -813,7 +823,8 @@ class SelectToolParameter( ToolParameter ):
call_other_values = self._get_dynamic_options_call_other_values( trans, other_values )
try:
return eval( self.dynamic_options, self.tool.code_namespace, call_other_values )
except Exception:
except Exception, e:
log.debug( "Error determining dynamic options for parameter '%s' in tool '%s':", self.name, self.tool.id, exc_info=e )
return []
else:
return self.static_options
+49
View File
@@ -129,6 +129,55 @@ class Repeat( Group ):
repeat_dict[ "inputs" ] = map( input_to_dict, self.inputs.values() )
return repeat_dict
class Section( Group ):
dict_collection_visible_keys = ( 'name', 'type', 'title', 'help', 'expanded')
type = "section"
def __init__( self ):
Group.__init__( self )
self.title = None
self.inputs = None
self.help = None
self.expanded = False
@property
def title_plural( self ):
return inflector.pluralize( self.title )
def label( self ):
return "Section (%s)" % self.title
def value_to_basic( self, value, app ):
rval = {}
for input in self.inputs.itervalues():
rval[ input.name ] = input.value_to_basic( value[input.name], app )
return rval
def value_from_basic( self, value, app, ignore_errors=False ):
rval = {}
try:
for input in self.inputs.itervalues():
if not ignore_errors or input.name in value:
rval[ input.name ] = input.value_from_basic( value[ input.name ], app, ignore_errors )
except Exception, e:
if not ignore_errors:
raise e
return rval
def visit_inputs( self, prefix, value, callback ):
for input in self.inputs.itervalues():
if isinstance( input, ToolParameter ):
callback( prefix, input, value[input.name], parent = value )
else:
input.visit_inputs( prefix, value[input.name], callback )
def get_initial_value( self, trans, context, history=None ):
rval = {}
child_context = ExpressionContext( rval, context )
for child_input in self.inputs.itervalues():
rval[ child_input.name ] = child_input.get_initial_value( trans, child_context, history=history )
return rval
def to_dict( self, trans, view='collection', value_mapper=None ):
section_dict = super( Section, self ).to_dict( trans, view=view, value_mapper=value_mapper )
def input_to_dict( input ):
return input.to_dict( trans, view=view, value_mapper=value_mapper )
section_dict[ "inputs" ] = map( input_to_dict, self.inputs.values() )
return section_dict
class UploadDataset( Group ):
type = "upload_dataset"
+4
View File
@@ -8,6 +8,7 @@ from galaxy.tools.parameters.basic import (
from galaxy.tools.parameters.grouping import (
Repeat,
Conditional,
Section
)
PARAMS_UNWRAPPED = object()
@@ -44,6 +45,9 @@ class WrappedParameters( object ):
values = input_values[ input.name ]
current = values[ "__current_case__" ]
self.wrap_values( input.cases[current].inputs, values, skip_missing_values=skip_missing_values )
elif isinstance( input, Section ):
values = input_values[ input.name ]
self.wrap_values( input.inputs, values, skip_missing_values=skip_missing_values )
elif isinstance( input, DataToolParameter ) and input.multiple:
input_values[ input.name ] = \
galaxy.tools.DatasetListWrapper( input_values[ input.name ],
+7
View File
@@ -422,6 +422,13 @@ def __expand_input_elems( root_elem, prefix="" ):
__pull_up_params( root_elem, cond_elem )
root_elem.remove( cond_elem )
section_elems = root_elem.findall( 'section' )
for section_elem in section_elems:
new_prefix = __prefix_join( prefix, section_elem.get( "name" ) )
__expand_input_elems( section_elem, new_prefix )
__pull_up_params( root_elem, section_elem )
root_elem.remove( section_elem )
def __append_prefix_to_params( elem, prefix ):
for param_elem in elem.findall( 'param' ):
+6 -1
View File
@@ -166,7 +166,12 @@ class ToolTestBuilder( object ):
# an infinite loop - hence the "case_value is not None"
# check.
expanded_inputs[ case_context.for_state() ] = expanded_case_value
elif isinstance( value, galaxy.tools.parameters.grouping.Section ):
context = ParamContext( name=value.name, parent_context=parent_context )
for r_name, r_value in value.inputs.iteritems():
expanded_input = self.__process_raw_inputs( { context.for_state(): r_value }, raw_inputs, parent_context=context )
if expanded_input:
expanded_inputs.update( expanded_input )
elif isinstance( value, galaxy.tools.parameters.grouping.Repeat ):
repeat_index = 0
while True:
+1 -1
View File
@@ -38,7 +38,7 @@ class GenomeBuilds( object ):
for dataset in datasets:
rval.append( ( dataset.dbkey, "%s (%s) [History]" % ( dataset.name, dataset.dbkey ) ) )
user = trans.user
if user and 'dbkeys' in user.preferences:
if user and hasattr( user, 'preferences' ) and 'dbkeys' in user.preferences:
user_keys = loads( user.preferences['dbkeys'] )
for key, chrom_dict in user_keys.iteritems():
rval.append( ( key, "%s (%s) [Custom]" % ( chrom_dict['name'], key ) ) )
+1 -1
View File
@@ -961,7 +961,7 @@ class ResourceParser( object ):
elif param_type == 'dataset':
decoded_dataset_id = trans.security.decode_id( query_param )
parsed_param = controller.hda_manager.get_accessible( trans, decoded_dataset_id, trans.user )
parsed_param = controller.hda_manager.get_accessible( decoded_dataset_id, trans.user )
elif param_type == 'hda_or_ldda':
encoded_dataset_id = query_param
+1 -1
View File
@@ -1039,7 +1039,7 @@ class Admin( object ):
stop_msg += '.'
for job_id in job_ids:
error_msg = "This job was stopped by an administrator: %s <a href='%s' target='_blank'>Contact support</a> for additional help." \
% ( stop_msg, self.app.config.get("support_url", "http://wiki.galaxyproject.org/Support" ) )
% ( stop_msg, self.app.config.get("support_url", "https://wiki.galaxyproject.org/Support" ) )
if trans.app.config.track_jobs_in_database:
job = trans.sa_session.query( trans.app.model.Job ).get( job_id )
job.stderr = error_msg
@@ -86,7 +86,8 @@ class InteractiveEnviornmentRequest(object):
conf_file['galaxy_url'] = self.attr.galaxy_config.galaxy_infrastructure_url.rstrip('/') + '/'
else:
conf_file['galaxy_url'] = request.application_url.rstrip('/') + '/'
conf_file['galaxy_paster_port'] = self.attr.galaxy_config.guess_galaxy_port()
web_port = self.attr.galaxy_config.galaxy_infrastructure_web_port
conf_file['galaxy_paster_port'] = web_port or self.attr.galaxy_config.guess_galaxy_port()
if self.attr.PASSWORD_AUTH:
# Generate a random password + salt
+2 -1
View File
@@ -668,7 +668,8 @@ class GalaxyWebTransaction( base.DefaultWebTransaction,
"""
history = None
if self.galaxy_session:
history = self.galaxy_session.current_history
if hasattr( self.galaxy_session, 'current_history' ):
history = self.galaxy_session.current_history
if not history and util.string_as_bool( create ):
history = self.new_history()
return history
+3 -3
View File
@@ -63,7 +63,7 @@ class HistoryAnnotationsController(BaseAnnotationsController):
def _get_item_from_id(self, trans, idstr):
decoded_idstr = self.decode_id( idstr )
history = self.history_manager.get_accessible( trans, decoded_idstr, trans.user )
history = self.history_manager.get_accessible( decoded_idstr, trans.user, current_history=trans.history )
return history
@@ -77,8 +77,8 @@ class HistoryContentAnnotationsController( BaseAnnotationsController ):
def _get_item_from_id(self, trans, idstr):
decoded_idstr = self.decode_id( idstr )
hda = self.hda_manager.get_accessible( trans, decoded_idstr, trans.user )
hda = self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.get_accessible( decoded_idstr, trans.user )
hda = self.hda_manager.error_if_uploading( hda )
return hda
+5 -76
View File
@@ -9,6 +9,7 @@ from galaxy.web import require_admin
from galaxy.web.base.controller import BaseAPIController
from galaxy.managers import base
from galaxy.managers import configuration
import logging
log = logging.getLogger( __name__ )
@@ -18,8 +19,8 @@ class ConfigurationController( BaseAPIController ):
def __init__( self, app ):
super( ConfigurationController, self ).__init__( app )
self.config_serializer = ConfigSerializer( app )
self.admin_config_serializer = AdminConfigSerializer( app )
self.config_serializer = configuration.ConfigSerializer( app )
self.admin_config_serializer = configuration.AdminConfigSerializer( app )
@expose_api_anonymous_and_sessionless
def index( self, trans, **kwd ):
@@ -29,7 +30,7 @@ class ConfigurationController( BaseAPIController ):
Note: a more complete list is returned if the user is an admin.
"""
is_admin = self.user_manager.is_admin( trans, trans.user )
is_admin = self.user_manager.is_admin( trans.user )
serialization_params = self._parse_serialization_params( kwd, 'all' )
return self.get_config_dict( trans, is_admin, **serialization_params )
@@ -57,8 +58,7 @@ class ConfigurationController( BaseAPIController ):
#TODO: this should probably just be under a different route: 'admin/configuration'
serializer = self.admin_config_serializer
serialized = serializer.serialize_to_view( trans, self.app.config,
view=view, keys=keys, default_view=default_view )
serialized = serializer.serialize_to_view( self.app.config, view=view, keys=keys, default_view=default_view )
return serialized
@expose_api
@@ -85,77 +85,6 @@ class ConfigurationController( BaseAPIController ):
return rval
#TODO: for lack of a manager file for the config. May well be better in config.py? Circ imports?
class ConfigSerializer( base.ModelSerializer ):
def __init__( self, app ):
super( ConfigSerializer, self ).__init__( app )
self.default_view = 'all'
self.add_view( 'all', self.serializers.keys() )
def default_serializer( self, trans, config, key ):
return getattr( config, key, None )
def add_serializers( self ):
def _defaults_to( default ):
return lambda t, i, k: getattr( i, k, default )
self.serializers = {
#TODO: this is available from user data, remove
'is_admin_user' : lambda *a: False,
'brand' : _defaults_to( '' ),
#TODO: this doesn't seem right
'logo_url' : lambda t, i, k: self.url_for( i.get( k, '/' ) ),
'terms_url' : _defaults_to( '' ),
#TODO: don't hardcode here - hardcode defaults once in config.py
'wiki_url' : _defaults_to( "http://galaxyproject.org/" ),
'search_url' : _defaults_to( "http://galaxyproject.org/search/usegalaxy/" ),
'mailing_lists' : _defaults_to( "http://wiki.galaxyproject.org/MailingLists" ),
'screencasts_url' : _defaults_to( "http://vimeo.com/galaxyproject" ),
'citation_url' : _defaults_to( "http://wiki.galaxyproject.org/CitingGalaxy" ),
'support_url' : _defaults_to( "http://wiki.galaxyproject.org/Support" ),
'lims_doc_url' : _defaults_to( "http://main.g2.bx.psu.edu/u/rkchak/p/sts" ),
'biostar_url' : _defaults_to( '' ),
'biostar_url_redirect' : lambda *a: self.url_for( controller='biostar', action='biostar_redirect',
qualified=True ),
'use_remote_user' : _defaults_to( None ),
'remote_user_logout_href' : _defaults_to( '' ),
'enable_cloud_launch' : _defaults_to( False ),
'datatypes_disable_auto' : _defaults_to( False ),
'allow_user_dataset_purge' : _defaults_to( False ),
'enable_unique_workflow_defaults' : _defaults_to( False ),
'nginx_upload_path' : _defaults_to( self.url_for( controller='api', action='tools' ) ),
'ftp_upload_dir' : _defaults_to( None ),
'ftp_upload_site' : _defaults_to( None ),
'version_major' : _defaults_to( None ),
}
class AdminConfigSerializer( ConfigSerializer ):
# config attributes viewable by admin users
def add_serializers( self ):
super( AdminConfigSerializer, self ).add_serializers()
def _defaults_to( default ):
return lambda t, i, k: getattr( i, k, default )
self.serializers.update({
#TODO: this is available from user data, remove
'is_admin_user' : lambda *a: True,
'library_import_dir' : _defaults_to( None ),
'user_library_import_dir' : _defaults_to( None ),
'allow_library_path_paste' : _defaults_to( False ),
'allow_user_creation' : _defaults_to( False ),
'allow_user_deletion' : _defaults_to( False ),
})
def _tool_conf_to_dict(conf):
return dict(
config_filename=conf['config_filename'],
@@ -45,7 +45,7 @@ class DatasetCollectionsController(
if instance_type == "history":
history_id = payload.get( 'history_id' )
history_id = self.decode_id( history_id )
history = self.history_manager.get_owned( trans, history_id, trans.user )
history = self.history_manager.get_owned( history_id, trans.user, current_history=trans.history )
create_params[ "parent" ] = history
elif instance_type == "library":
folder_id = payload.get( 'folder_id' )
+7 -6
View File
@@ -66,7 +66,8 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin ):
else:
# Default: return dataset as dict.
if hda_ldda == 'hda':
return self.hda_serializer.serialize_to_view( trans, dataset, view=kwd.get( 'view', 'detailed' ) )
return self.hda_serializer.serialize_to_view( dataset,
view=kwd.get( 'view', 'detailed' ), user=trans.user, trans=trans )
else:
rval = dataset.to_dict()
@@ -80,7 +81,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin ):
"""
Returns state of dataset.
"""
msg = self.hda_manager.data_conversion_status( trans, dataset )
msg = self.hda_manager.data_conversion_status( dataset )
if not msg:
msg = dataset.conversion_messages.DATA
@@ -91,7 +92,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin ):
Init-like method that returns state of dataset's converted datasets.
Returns valid chroms for that dataset as well.
"""
msg = self.hda_manager.data_conversion_status( trans, dataset )
msg = self.hda_manager.data_conversion_status( dataset )
if msg:
return msg
@@ -140,7 +141,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin ):
return dataset.conversion_messages.NO_DATA
# Dataset check.
msg = self.hda_manager.data_conversion_status( trans, dataset )
msg = self.hda_manager.data_conversion_status( dataset )
if msg:
return msg
@@ -233,7 +234,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin ):
be slow because indexes need to be created.
"""
# Dataset check.
msg = self.hda_manager.data_conversion_status( trans, dataset )
msg = self.hda_manager.data_conversion_status( dataset )
if msg:
return msg
@@ -280,7 +281,7 @@ class DatasetsController( BaseAPIController, UsesVisualizationMixin ):
rval = ''
try:
hda = self.hda_manager.get_accessible( trans, decoded_content_id, trans.user )
hda = self.hda_manager.get_accessible( decoded_content_id, trans.user )
if raw:
if filename and filename != 'index':
@@ -71,7 +71,7 @@ class HistoryDatasetExtendMetadataController(BaseExtendedMetadataController):
def _get_item_from_id(self, trans, idstr, check_writable=True):
decoded_idstr = self.decode_id( idstr )
if check_writable:
return self.hda_manager.get_owned( trans, decoded_idstr, trans.user )
return self.hda_manager.get_owned( decoded_idstr, trans.user, current_history=trans.history )
else:
hda = self.hda_manager.get_accessible( trans, decoded_idstr, trans.user )
return self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.get_accessible( decoded_idstr, trans.user )
return self.hda_manager.error_if_uploading( hda )
@@ -265,8 +265,8 @@ class FolderContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrary
rval = {}
try:
decoded_hda_id = self.decode_id( from_hda_id )
hda = self.hda_manager.get_owned( trans, decoded_hda_id, trans.user )
hda = self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.get_owned( decoded_hda_id, trans.user, current_history=trans.history )
hda = self.hda_manager.error_if_uploading( hda )
folder = self.get_library_folder( trans, encoded_folder_id_16, check_accessible=True )
library = folder.parent_library
+32 -28
View File
@@ -103,8 +103,11 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
current_user = self.user_manager.current_user( trans )
if self.user_manager.is_anonymous( current_user ):
current_history = self.history_manager.get_current( trans )
if not current_history:
return []
#note: ignores filters, limit, offset
return [ self.history_serializer.serialize_to_view( trans, current_history, **serialization_params ) ]
return [ self.history_serializer.serialize_to_view( current_history,
user=current_user, trans=trans, **serialization_params ) ]
filters = []
# support the old default of not-returning/filtering-out deleted histories
@@ -116,11 +119,12 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
#TODO: eventually make order_by a param as well
order_by = sqlalchemy.desc( self.app.model.History.create_time )
histories = self.history_manager.list( trans, filters=filters, order_by=order_by, limit=limit, offset=offset )
histories = self.history_manager.list( filters=filters, order_by=order_by, limit=limit, offset=offset )
rval = []
for history in histories:
history_dict = self.history_serializer.serialize_to_view( trans, history, **serialization_params )
history_dict = self.history_serializer.serialize_to_view( history,
user=trans.user, trans=trans, **serialization_params )
rval.append( history_dict )
return rval
@@ -174,19 +178,19 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
deleted = string_as_bool( deleted )
if history_id == "most_recently_used":
history = self.history_manager.most_recent( trans, trans.user,
filters=( self.app.model.History.deleted == False ) )
history = self.history_manager.most_recent( trans.user,
filters=( self.app.model.History.deleted == False ), current_history=trans.history )
else:
history = self.history_manager.get_accessible( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( history_id ), trans.user, current_history=trans.history )
return self.history_serializer.serialize_to_view( trans, history,
**self._parse_serialization_params( kwd, 'detailed' ) )
return self.history_serializer.serialize_to_view( history,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
@expose_api_anonymous
def citations( self, trans, history_id, **kwd ):
"""
"""
history = self.history_manager.get_accessible( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( history_id ), trans.user, current_history=trans.history )
tool_ids = set([])
for dataset in history.datasets:
job = dataset.creating_job
@@ -234,19 +238,19 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
# if a history id was passed, copy that history
if copy_this_history_id:
decoded_id = self.decode_id( copy_this_history_id )
original_history = self.history_manager.get_accessible( trans, decoded_id, trans.user )
original_history = self.history_manager.get_accessible( decoded_id, trans.user, current_history=trans.history )
hist_name = hist_name or ( "Copy of '%s'" % original_history.name )
new_history = original_history.copy( name=hist_name, target_user=trans.user )
# otherwise, create a new empty history
else:
new_history = self.history_manager.create( trans, user=trans.user, name=hist_name )
new_history = self.history_manager.create( user=trans.user, name=hist_name )
trans.sa_session.add( new_history )
trans.sa_session.flush()
return self.history_serializer.serialize_to_view( trans, new_history,
**self._parse_serialization_params( kwd, 'detailed' ) )
return self.history_serializer.serialize_to_view( new_history,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
@expose_api
def delete( self, trans, id, **kwd ):
@@ -279,13 +283,13 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
if kwd.get( 'payload', None ):
purge = string_as_bool( kwd['payload'].get( 'purge', False ) )
history = self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user )
self.history_manager.delete( trans, history )
history = self.history_manager.get_owned( self.decode_id( history_id ), trans.user, current_history=trans.history )
self.history_manager.delete( history )
if purge:
self.history_manager.purge( trans, history )
self.history_manager.purge( history )
return self.history_serializer.serialize_to_view( trans, history,
**self._parse_serialization_params( kwd, 'detailed' ) )
return self.history_serializer.serialize_to_view( history,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
@expose_api
def undelete( self, trans, id, **kwd ):
@@ -304,11 +308,11 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
:returns: 'OK' if the history was undeleted
"""
history_id = id
history = self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user )
self.history_manager.undelete( trans, history )
history = self.history_manager.get_owned( self.decode_id( history_id ), trans.user, current_history=trans.history )
self.history_manager.undelete( history )
return self.history_serializer.serialize_to_view( trans, history,
**self._parse_serialization_params( kwd, 'detailed' ) )
return self.history_serializer.serialize_to_view( history,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
@expose_api
def update( self, trans, id, payload, **kwd ):
@@ -333,11 +337,11 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
any values that were different from the original and, therefore, updated
"""
#TODO: PUT /api/histories/{encoded_history_id} payload = { rating: rating } (w/ no security checks)
history = self.history_manager.get_owned( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( id ), trans.user, current_history=trans.history )
self.history_deserializer.deserialize( trans, history, payload )
return self.history_serializer.serialize_to_view( trans, history,
**self._parse_serialization_params( kwd, 'detailed' ) )
self.history_deserializer.deserialize( history, payload, user=trans.user, trans=trans )
return self.history_serializer.serialize_to_view( history,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
@expose_api
def archive_export( self, trans, id, **kwds ):
@@ -355,7 +359,7 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
"""
# PUT instead of POST because multiple requests should just result
# in one object being created.
history = self.history_manager.get_accessible( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( id ), trans.user, current_history=trans.history )
jeha = history.latest_export
up_to_date = jeha and jeha.up_to_date
if 'force' in kwds:
@@ -388,7 +392,7 @@ class HistoriesController( BaseAPIController, ExportsHistoryMixin, ImportsHistor
"""
# Seems silly to put jeha_id in here, but want GET to be immuatable?
# and this is being accomplished this way.
history = self.history_manager.get_accessible( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( id ), trans.user, current_history=trans.history )
matching_exports = filter( lambda e: trans.security.encode_id( e.id ) == jeha_id, history.exports )
if not matching_exports:
raise exceptions.ObjectNotFound()
@@ -68,7 +68,7 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
"""
rval = []
history = self.history_manager.get_accessible( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( history_id ), trans.user, current_history=trans.history )
# Allow passing in type or types - for continuity rest of methods
# take in type - but this one can be passed multiple types and
@@ -102,7 +102,7 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
if isinstance( content, trans.app.model.HistoryDatasetAssociation ):
view = 'detailed' if detailed else 'summary'
hda_dict = self.hda_serializer.serialize_to_view( trans, content, view=view )
hda_dict = self.hda_serializer.serialize_to_view( content, view=view, user=trans.user, trans=trans )
rval.append( hda_dict )
elif isinstance( content, trans.app.model.HistoryDatasetCollectionAssociation ):
@@ -132,7 +132,7 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
:rtype: dict
:returns: dictionary containing detailed HDA information
"""
history = self.history_manager.get_accessible( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( history_id ), trans.user, current_history=trans.history )
contents_type = kwd.get('type', 'dataset')
if contents_type == 'dataset':
@@ -143,9 +143,9 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
return self.__handle_unknown_contents_type( trans, contents_type )
def __show_dataset( self, trans, id, **kwd ):
hda = self.hda_manager.get_accessible( trans, self.decode_id( id ), trans.user )
return self.hda_serializer.serialize_to_view( trans, hda,
**self._parse_serialization_params( kwd, 'detailed' ) )
hda = self.hda_manager.get_accessible( self.decode_id( id ), trans.user )
return self.hda_serializer.serialize_to_view( hda,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
def __show_dataset_collection( self, trans, id, history_id, **kwd ):
try:
@@ -214,7 +214,8 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# TODO: Flush out create new collection documentation above, need some
# examples. See also bioblend and API tests for specific examples.
history = self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( history_id ), trans.user,
current_history=trans.history )
type = payload.get( 'type', 'dataset' )
if type == 'dataset':
@@ -247,17 +248,17 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# copy an existing, accessible hda
elif source == 'hda':
unencoded_hda_id = self.decode_id( content )
original = self.hda_manager.get_accessible( trans, unencoded_hda_id, trans.user )
original = self.hda_manager.get_accessible( unencoded_hda_id, trans.user )
# check for access on history that contains the original hda as well
self.history_manager.error_unless_accessible( trans, original.history, trans.user )
self.history_manager.error_unless_accessible( original.history, trans.user, current_history=trans.history )
data_copy = original.copy( copy_children=True )
hda = history.add_dataset( data_copy )
trans.sa_session.flush()
if not hda:
return None
return self.hda_serializer.serialize_to_view( trans, hda,
**self._parse_serialization_params( kwd, 'detailed' ) )
return self.hda_serializer.serialize_to_view( hda,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
def __create_dataset_collection( self, trans, history, payload, **kwd ):
source = kwd.get("source", "new_collection")
@@ -319,10 +320,10 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# anon user: ensure that history ids match up and the history is the current,
# check for uploading, and use only the subset of attribute keys manipulatable by anon users
if trans.user == None:
hda = self.hda_manager.by_id( trans, self.decode_id( id ) )
hda = self.hda_manager.by_id( self.decode_id( id ) )
if hda.history != trans.history:
raise exceptions.AuthenticationRequired( 'API authentication required for this request' )
hda = self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.error_if_uploading( hda )
anon_allowed_payload = {}
if 'deleted' in payload:
@@ -333,22 +334,22 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# logged in user: use full payload, check state if deleting, and make sure the history is theirs
else:
hda = self.hda_manager.get_owned( trans, self.decode_id( id ), trans.user )
hda = self.hda_manager.get_owned( self.decode_id( id ), trans.user, current_history=trans.history )
# only check_state if not deleting, otherwise cannot delete uploading files
check_state = not payload.get( 'deleted', False )
if check_state:
hda = self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.error_if_uploading( hda )
# make the actual changes
#TODO: is this if still needed?
if hda and isinstance( hda, trans.model.HistoryDatasetAssociation ):
self.hda_deserializer.deserialize( trans, hda, payload )
self.hda_deserializer.deserialize( hda, payload, user=trans.user, trans=trans )
#TODO: this should be an effect of deleting the hda
if payload.get( 'deleted', False ):
self.hda_manager.stop_creating_job( hda )
return self.hda_serializer.serialize_to_view( trans, hda,
**self._parse_serialization_params( kwd, 'detailed' ) )
return self.hda_serializer.serialize_to_view( hda,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
return {}
@@ -399,15 +400,15 @@ class HistoryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# payload takes priority
purge = util.string_as_bool( kwd['payload'].get( 'purge', purge ) )
hda = self.hda_manager.get_owned( trans, self.decode_id( id ), trans.user )
self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.get_owned( self.decode_id( id ), trans.user, current_history=trans.history )
self.hda_manager.error_if_uploading( hda )
if purge:
self.hda_manager.purge( trans, hda )
self.hda_manager.purge( hda )
else:
self.hda_manager.delete( trans, hda )
return self.hda_serializer.serialize_to_view( trans, hda,
**self._parse_serialization_params( kwd, 'detailed' ) )
self.hda_manager.delete( hda )
return self.hda_serializer.serialize_to_view( hda,
user=trans.user, trans=trans, **self._parse_serialization_params( kwd, 'detailed' ) )
def __handle_unknown_contents_type( self, trans, contents_type ):
raise exceptions.UnknownContentsType('Unknown contents type: %s' % type)
+14 -2
View File
@@ -83,9 +83,21 @@ class JobFilesAPIController( BaseAPIController ):
self.__check_job_can_write_to_path( trans, job, path )
# Is this writing an unneeded file? Should this just copy in Python?
input_file = payload.get( "file", payload.get( "__file", None ) ).file
if '__file_path' in payload:
file_path = payload.get( '__file_path' )
upload_store = trans.app.config.nginx_upload_job_files_store
assert upload_store, ( "Request appears to have been processed by"
" nginx_upload_module but Galaxy is not"
" configured to recognize it" )
assert file_path.startswith( upload_store ), \
( "Filename provided by nginx (%s) is not in correct"
" directory (%s)" % ( file_path, upload_store ) )
input_file = open( file_path )
else:
input_file = payload.get( "file",
payload.get( "__file", None ) ).file
try:
shutil.copyfile( input_file.name, path )
shutil.move( input_file.name, path )
finally:
input_file.close()
return {"message": "ok"}
+1 -1
View File
@@ -262,7 +262,7 @@ class JobController( BaseAPIController, UsesLibraryMixinItems ):
if 'id' in v:
if 'src' not in v or v[ 'src' ] == 'hda':
hda_id = self.decode_id( v['id'] )
dataset = self.hda_manager.get_accessible( trans, hda_id, trans.user )
dataset = self.hda_manager.get_accessible( hda_id, trans.user )
else:
dataset = self.get_library_dataset_dataset_association( trans, v['id'] )
if dataset is None:
@@ -33,7 +33,7 @@ class LibraryDatasetsController( BaseAPIController, UsesVisualizationMixin ):
def __init__( self, app ):
super( LibraryDatasetsController, self ).__init__( app )
self.folder_manager = folders.FolderManager()
self.role_manager = roles.RoleManager()
self.role_manager = roles.RoleManager( app )
@expose_api_anonymous
def show( self, trans, id, **kwd ):
+1 -1
View File
@@ -18,7 +18,7 @@ class LibrariesController( BaseAPIController ):
super( LibrariesController, self ).__init__( app )
self.folder_manager = folders.FolderManager()
self.library_manager = libraries.LibraryManager()
self.role_manager = roles.RoleManager()
self.role_manager = roles.RoleManager( app )
@expose_api_anonymous
def index( self, trans, **kwd ):
@@ -304,8 +304,8 @@ class LibraryContentsController( BaseAPIController, UsesLibraryMixin, UsesLibrar
# check permissions on (all three?) resources: hda, library, folder
# TODO: do we really need the library??
from_hda_id = self.decode_id( from_hda_id )
hda = self.hda_manager.get_owned( trans, from_hda_id, trans.user )
hda = self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.get_owned( from_hda_id, trans.user, current_history=trans.history )
hda = self.hda_manager.error_if_uploading( hda )
# library = self.get_library( trans, library_id, check_accessible=True )
folder = self.get_library_folder( trans, folder_id, check_accessible=True )
+1 -1
View File
@@ -41,7 +41,7 @@ class BaseProvenanceController( BaseAPIController ):
def _get_provenance( self, trans, item_class_name, item_id, follow=True ):
provenance_item = self.get_object( trans, item_id, item_class_name, check_ownership=False, check_accessible=False)
if item_class_name == "HistoryDatasetAssociation":
self.hda_manager.error_unless_accessible( trans, provenance_item, trans.user )
self.hda_manager.error_unless_accessible( provenance_item, trans.user )
else:
self.security_check( trans, provenance_item, check_accessible=True )
out = self._get_record( trans, provenance_item, follow )
+4 -4
View File
@@ -240,7 +240,7 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ):
history_id = payload.get("history_id", None)
if history_id:
decoded_id = self.decode_id( history_id )
target_history = self.history_manager.get_owned( trans, decoded_id, trans.user )
target_history = self.history_manager.get_owned( decoded_id, trans.user, current_history=trans.history )
else:
target_history = None
@@ -386,9 +386,9 @@ class ToolsController( BaseAPIController, UsesVisualizationMixin ):
# Dataset check.
decoded_dataset_id = self.decode_id( payload.get( 'target_dataset_id' ) )
original_dataset = self.hda_manager.get_accessible( trans, decoded_dataset_id, user=trans.user )
original_dataset = self.hda_manager.error_if_uploading( trans, original_dataset )
msg = self.hda_manager.data_conversion_status( trans, original_dataset )
original_dataset = self.hda_manager.get_accessible( decoded_dataset_id, user=trans.user )
original_dataset = self.hda_manager.error_if_uploading( original_dataset )
msg = self.hda_manager.data_conversion_status( original_dataset )
if msg:
return msg
+4 -4
View File
@@ -152,7 +152,7 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
if 'from_history_id' in payload:
from_history_id = payload.get( 'from_history_id' )
from_history_id = self.decode_id( from_history_id )
history = self.history_manager.get_accessible( trans, from_history_id, trans.user )
history = self.history_manager.get_accessible( from_history_id, trans.user, current_history=trans.history )
job_ids = map( self.decode_id, payload.get( 'job_ids', [] ) )
dataset_ids = payload.get( 'dataset_ids', [] )
@@ -318,10 +318,10 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
tool_version = payload.get( 'tool_version', None )
tool_inputs = payload.get( 'inputs', None )
annotation = payload.get( 'annotation', '' )
# load tool
tool = self._get_tool( tool_id, tool_version=tool_version, user=trans.user )
# initialize module
trans.workflow_building_mode = True
module = module_factory.from_dict( trans, {
@@ -329,7 +329,7 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
'tool_id' : tool.id,
'tool_state' : None
} )
# create tool model and default tool state (if missing)
tool_model = module.tool.to_json(trans, tool_inputs, is_workflow=True)
module.state.inputs = copy.deepcopy(tool_model['state_inputs'])
+6 -4
View File
@@ -103,13 +103,15 @@ def app_factory( global_conf, **kwargs ):
webapp = wrap_in_static( webapp, global_conf, plugin_frameworks=[ app.visualizations_registry ], **kwargs )
# Close any pooled database connections before forking
try:
galaxy.model.mapping.metadata.engine.connection_provider._pool.dispose()
galaxy.model.mapping.metadata.bind.dispose()
except:
pass
log.exception("Unable to dispose of pooled galaxy model database connections.")
try:
galaxy.model.tool_shed_install.mapping.metadata.engine.connection_provider._pool.dispose()
# This model may not actually be bound.
if galaxy.model.tool_shed_install.mapping.metadata.bind:
galaxy.model.tool_shed_install.mapping.metadata.bind.dispose()
except:
pass
log.exception("Unable to dispose of pooled toolshed install model database connections.")
if not app.config.is_uwsgi:
postfork_setup()
@@ -529,8 +529,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
def get_name_and_link_async( self, trans, id=None ):
""" Returns dataset's name and link. """
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
return_dict = { "name" : dataset.name, "link" : url_for( controller='dataset', action="display_by_username_and_slug", username=dataset.history.user.username, slug=trans.security.encode_id( dataset.id ) ) }
return return_dict
@@ -538,8 +538,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
def get_embed_html_async( self, trans, id ):
""" Returns HTML for embedding a dataset in a page. """
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
if dataset:
return "Embedded Dataset '%s'" % dataset.name
@@ -556,8 +556,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
""" Rate a dataset asynchronously and return updated community data. """
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
if not dataset:
return trans.show_error_message( "The specified dataset does not exist." )
@@ -571,8 +571,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
""" Display dataset by username and slug; because datasets do not yet have slugs, the slug is the dataset's id. """
id = slug
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
if dataset:
# Filename used for composite types.
if filename:
@@ -614,8 +614,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
""" Returns item content in HTML format. """
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
if dataset is None:
raise web.httpexceptions.HTTPNotFound()
truncated, dataset_data = self.hda_manager.text_data( dataset, preview=True )
@@ -627,8 +627,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
def annotate_async( self, trans, id, new_annotation=None, **kwargs ):
#TODO:?? why is this an access check only?
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
if not dataset:
web.httpexceptions.HTTPNotFound()
if dataset and new_annotation:
@@ -641,8 +641,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
@web.expose
def get_annotation_async( self, trans, id ):
decoded_id = self.decode_id( id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
if not dataset:
web.httpexceptions.HTTPNotFound()
annotation = self.get_item_annotation_str( trans.sa_session, trans.user, dataset )
@@ -859,22 +859,24 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
status = 'done'
try:
id = trans.app.security.decode_id( dataset_id )
history = trans.get_history()
user = trans.get_user()
hda = trans.sa_session.query( self.app.model.HistoryDatasetAssociation ).get( id )
# Invalid HDA
assert hda, 'Invalid history dataset ID'
# Walk up parent datasets to find the containing history
history = trans.get_history()
topmost_parent = hda
while topmost_parent.parent:
topmost_parent = topmost_parent.parent
assert topmost_parent in history.datasets, "Data does not belong to current history"
# If the user is anonymous, make sure the HDA is owned by the current session.
if not user:
assert trans.galaxy_session.current_history_id == trans.history.id, 'Invalid history dataset ID'
current_history_id = trans.galaxy_session.current_history_id
assert topmost_parent.history.id == current_history_id, 'Data does not belong to current user'
# If the user is known, make sure the HDA is owned by the current user.
else:
assert topmost_parent.history.user == trans.user, 'Invalid history dataset ID'
assert topmost_parent.history.user == user, 'Data does not belong to current user'
# Ensure HDA is deleted
hda.deleted = True
# HDA is purgeable
@@ -897,8 +899,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
except:
log.exception( 'Unable to purge dataset (%s) on purge of HDA (%s):' % ( hda.dataset.id, hda.id ) )
trans.sa_session.flush()
except Exception:
msg = 'HDA purge failed (encoded: %s, decoded: %s)' % ( dataset_id, id )
except Exception, exc:
msg = 'HDA purge failed (encoded: %s, decoded: %s): %s' % ( dataset_id, id, exc )
log.exception( msg )
trans.log_event( msg )
message = 'Dataset removal from disk failed'
@@ -1023,7 +1025,7 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
user = trans.get_user()
if source_history is not None:
decoded_source_history_id = self.decode_id( source_history )
history = self.history_manager.get_owned( trans, decoded_source_history_id, trans.user )
history = self.history_manager.get_owned( decoded_source_history_id, trans.user, current_history=trans.history )
current_history = trans.get_history()
else:
history = current_history = trans.get_history()
@@ -1129,8 +1131,8 @@ class DatasetInterface( BaseUIController, UsesAnnotations, UsesItemRatings, Uses
error_msg = error_msg + "You do not have permission to add datasets to %i requested histories. " % ( len( target_histories ) )
for dataset_id in dataset_ids:
decoded_id = self.decode_id( dataset_id )
data = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
data = self.hda_manager.error_if_uploading( trans, data )
data = self.hda_manager.get_accessible( decoded_id, trans.user )
data = self.hda_manager.error_if_uploading( data )
if data is None:
error_msg = error_msg + "You tried to copy a dataset that does not exist or that you do not have access to. "
@@ -254,7 +254,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
return self.rename( trans, **kwargs )
if operation == "view":
decoded_id = self.decode_id( kwargs.get( 'id', None ) )
history = self.history_manager.get_owned( trans, decoded_id, trans.user )
history = self.history_manager.get_owned( decoded_id, trans.user, current_history=trans.history )
return trans.response.send_redirect( url_for( controller='history',
action='view',
id=kwargs['id'],
@@ -268,7 +268,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
# Load the histories and ensure they all belong to the current user
histories = []
for history_id in history_ids:
history = self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( history_id ), trans.user, current_history=trans.history )
if history:
# Ensure history is owned by current user
if history.user_id != None and trans.user:
@@ -313,7 +313,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
if history_ids:
histories = []
for history_id in history_ids:
history = self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( history_id ), trans.user, current_history=trans.history )
if history.importable:
history.importable = False
histories.append( history )
@@ -439,7 +439,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
operation = kwargs['operation'].lower()
if operation == "view":
# Display history.
history = self.history_manager.get_accessible( trans, self.decode_id( ids[0] ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( ids[0] ), trans.user, current_history=trans.history )
return self.display_by_username_and_slug( trans, history.user.username, history.slug )
elif operation == "copy":
if not ids:
@@ -455,7 +455,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
for id in ids:
# No need to check security, association below won't yield a
# hit if this user isn't having the history shared with her.
history = self.history_manager.by_id( trans, self.decode_id( id ) )
history = self.history_manager.by_id( self.decode_id( id ) )
# Current user is the user with which the histories were shared
association = ( trans.sa_session.query( trans.app.model.HistoryUserShareAssociation )
.filter_by( user=trans.user, history=history ).one() )
@@ -555,7 +555,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
unencoded_history_id = trans.history.id
if id:
unencoded_history_id = self.decode_id( id )
history_to_view = self.history_manager.get_accessible( trans, unencoded_history_id, trans.user )
history_to_view = self.history_manager.get_accessible( unencoded_history_id, trans.user, current_history=trans.history )
history_data = self.history_manager._get_history_data( trans, history_to_view )
history_dictionary = history_data[ 'history' ]
@@ -595,7 +595,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
hda_dictionaries = []
user_is_owner = False
try:
history_to_view = self.history_manager.get_accessible( trans, self.decode_id( id ), trans.user )
history_to_view = self.history_manager.get_accessible( self.decode_id( id ), trans.user, current_history=trans.history )
user_is_owner = history_to_view.user == trans.user
# include all datasets: hidden, deleted, and purged
@@ -641,8 +641,9 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
current_history_id = trans.security.encode_id( current_history.id ) if current_history else None
history_dictionaries = []
for history in self.history_manager.by_user( trans, trans.user, filters=deleted_filter ):
history_dictionary = self.history_serializer.serialize_to_view( trans, history, view='detailed' )
for history in self.history_manager.by_user( trans.user, filters=deleted_filter ):
history_dictionary = self.history_serializer.serialize_to_view( history,
view='detailed', user=trans.user, trans=trans )
history_dictionaries.append( history_dictionary )
return trans.fill_template_mako( "history/view_multiple.mako",
@@ -661,7 +662,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
if history is None:
raise web.httpexceptions.HTTPNotFound()
# Security check raises error if user cannot access history.
self.history_manager.error_unless_accessible( trans, history, trans.user )
self.history_manager.error_unless_accessible( history, trans.user, current_history=trans.history )
# Get rating data.
user_item_rating = 0
@@ -701,7 +702,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
if id:
ids = galaxy.util.listify( id )
if ids:
histories = [ self.history_manager.get_accessible( trans, self.decode_id( history_id ), trans.user )
histories = [ self.history_manager.get_accessible( self.decode_id( history_id ), trans.user, current_history=trans.history )
for history_id in ids ]
elif not histories:
histories = [ trans.history ]
@@ -771,7 +772,9 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
send_to_err = err_msg
histories = []
for history_id in id:
histories.append( self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user ) )
history_id = self.decode_id( history_id )
history = self.history_manager.get_owned( history_id, trans.user, current_history=trans.history )
histories.append( history )
return trans.fill_template( "/history/share.mako",
histories=histories,
email=email,
@@ -894,7 +897,9 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
ids = galaxy.util.listify( ids )
histories = []
for history_id in ids:
histories.append( self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user ) )
history_id = self.decode_id( history_id )
history = self.history_manager.get_owned( history_id, trans.user, current_history=trans.history )
histories.append( )
return histories
def _get_users( self, trans, user, emails_or_ids ):
@@ -908,14 +913,14 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
send_to_user = None
if '@' in string:
email_address = string
send_to_user = self.user_manager.by_email( trans, email_address,
send_to_user = self.user_manager.by_email( email_address,
filters=[ trans.app.model.User.table.c.deleted == False ] )
else:
user_id = string
try:
decoded_user_id = self.decode_id( string )
send_to_user = self.user_manager.by_id( trans, decoded_user_id )
send_to_user = self.user_manager.by_id( decoded_user_id )
if send_to_user.deleted:
send_to_user = None
#TODO: in an ideal world, we would let this bubble up to web.expose which would handle it
@@ -1144,7 +1149,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
trans.app.job_manager.job_stop_queue.put( job.id )
# Regardless of whether it was previously deleted, get the most recent history or create a new one.
most_recent_history = self.history_manager.most_recent( trans, user=trans.user )
most_recent_history = self.history_manager.most_recent( user=trans.user )
if most_recent_history:
trans.set_history( most_recent_history )
return trans.show_ok_message( "History deleted, your most recent history is now active",
@@ -1188,7 +1193,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
@web.json
def rate_async( self, trans, id, rating ):
""" Rate a history asynchronously and return updated community data. """
history = self.history_manager.get_accessible( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( id ), trans.user, current_history=trans.history )
if not history:
return trans.show_error_message( "The specified history does not exist." )
# Rate history.
@@ -1232,7 +1237,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
# Get history to export.
#
if id:
history = self.history_manager.get_accessible( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( id ), trans.user, current_history=trans.history )
else:
# Use current history.
history = trans.history
@@ -1265,7 +1270,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
@web.require_login( "get history name and link" )
def get_name_and_link_async( self, trans, id=None ):
""" Returns history's name and link. """
history = self.history_manager.get_accessible( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( id ), trans.user, current_history=trans.history )
if self.create_item_slug( trans.sa_session, history ):
trans.sa_session.flush()
return_dict = {
@@ -1279,7 +1284,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
@web.require_login( "set history's accessible flag" )
def set_accessible_async( self, trans, id=None, accessible=False ):
""" Set history's importable attribute and slug. """
history = self.history_manager.get_owned( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( id ), trans.user, current_history=trans.history )
# Only set if importable value would change; this prevents a change in the update_time unless attribute really changed.
importable = accessible in ['True', 'true', 't', 'T'];
if history and history.importable != importable:
@@ -1327,11 +1332,11 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
# Do import.
if not id:
return trans.show_error_message( "You must specify a history you want to import.<br>You can %s." % referer_message, use_panels=True )
import_history = self.history_manager.by_id( trans, self.decode_id( id ) )
import_history = self.history_manager.by_id( self.decode_id( id ) )
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 self.history_manager.is_accessible( trans, import_history, trans.user ):
if not self.history_manager.is_accessible( import_history, trans.user, current_history=trans.history ):
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.
@@ -1397,7 +1402,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
histories = []
for history_id in id:
history = self.history_manager.get_owned( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( history_id ), trans.user, current_history=trans.history )
if history and history.user_id == user.id:
histories.append( history )
if not name or len( histories ) != len( name ):
@@ -1454,7 +1459,7 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
ids = galaxy.util.listify( id )
histories = []
for history_id in ids:
history = self.history_manager.get_accessible( trans, self.decode_id( history_id ), trans.user )
history = self.history_manager.get_accessible( self.decode_id( history_id ), trans.user, current_history=trans.history )
histories.append( history )
user = trans.get_user()
for history in histories:
@@ -1491,13 +1496,13 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
return trans.response.send_redirect( url_for( "/" ) )
def get_item( self, trans, id ):
return self.history_manager.get_owned( trans, self.decode_id( id ), trans.user )
return self.history_manager.get_owned( self.decode_id( id ), trans.user, current_history=trans.history )
#TODO: override of base ui controller?
def history_data( self, trans, history ):
"""
"""
return self.history_serializer.serialize_to_view( trans, history, view='detailed' )
return self.history_serializer.serialize_to_view( history, view='detailed', user=trans.user, trans=trans )
#TODO: combine these next two - poss. with a redirect flag
#@web.require_login( "switch to a history" )
@@ -1506,9 +1511,9 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
"""
"""
try:
history = self.history_manager.get_owned( trans, self.decode_id( id ), trans.user )
history = self.history_manager.get_owned( self.decode_id( id ), trans.user, current_history=trans.history )
trans.set_history( history )
return self.history_serializer.serialize_to_view( trans, history, view='detailed' )
return self.history_serializer.serialize_to_view( history, view='detailed', user=trans.user, trans=trans )
except exceptions.MessageException, msg_exc:
trans.response.status = msg_exc.err_code.code
return { 'err_msg': msg_exc.err_msg, 'err_code': msg_exc.err_code.code }
@@ -1518,13 +1523,13 @@ class HistoryController( BaseUIController, SharableMixin, UsesAnnotations, UsesI
"""
"""
history = trans.get_history( create=True )
return self.history_serializer.serialize_to_view( trans, history, view='detailed' )
return self.history_serializer.serialize_to_view( history, view='detailed', user=trans.user, trans=trans )
@web.json
def create_new_current( self, trans, name=None ):
"""
"""
new_history = trans.new_history( name )
return self.history_serializer.serialize_to_view( trans, new_history, view='detailed' )
return self.history_serializer.serialize_to_view( new_history, view='detailed', user=trans.user, trans=trans )
#TODO: /history/current to do all of the above: if ajax, return json; if post, read id and set to current
@@ -1432,7 +1432,6 @@ class LibraryCommon( BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMet
message = 'Select at least one dataset from the list of active datasets in your current history'
status = 'error'
upload_option = kwd.get( 'upload_option', 'import_from_history' )
widgets = self._get_populated_widgets( folder )
# Send list of data formats to the upload form so the "extension" select list can be populated dynamically
file_formats = trans.app.datatypes_registry.upload_file_formats
# Send list of genome builds to the form so the "dbkey" select list can be populated dynamically
@@ -1459,7 +1458,7 @@ class LibraryCommon( BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMet
last_used_build=last_used_build,
roles_select_list=roles_select_list,
history=history,
widgets=widgets,
widgets=[],
template_id=template_id,
space_to_tab=space_to_tab,
link_data_only=link_data_only,
@@ -1515,16 +1514,6 @@ class LibraryCommon( BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMet
upload_option_select_list.add_option( option_label, option_value, selected=option_value==upload_option )
return upload_option_select_list
def _get_populated_widgets( self, folder ):
# See if we have any inherited templates.
info_association, inherited = folder.get_info_association( inherited=True )
if info_association and info_association.inheritable:
widgets = folder.get_template_widgets( trans, get_contents=True )
# Retain contents of widget fields when form was submitted via refresh_on_change.
return self.populate_widgets_from_kwd( trans, widgets, **kwd )
else:
return []
@web.expose
def download_dataset_from_folder( self, trans, cntrller, id, library_id=None, **kwd ):
"""Catches the dataset id and displays file contents as directed"""
@@ -2584,8 +2573,8 @@ class LibraryCommon( BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMet
# Deny access if the user is not an admin and does not have the LIBRARY_MANAGE and DATASET_MANAGE_PERMISSIONS permissions.
if not ( is_admin or \
( trans.app.security_agent.can_manage_library_item( current_user_roles, item ) and
trans.app.security_agent.can_manage_dataset( current_user_roles, library_dataset.library_dataset_dataset_association.dataset ) ) ):
message = "You are not authorized to manage permissions on library dataset (%s)." % escape( library_dataset.name )
trans.app.security_agent.can_manage_dataset( current_user_roles, item.library_dataset_dataset_association.dataset ) ) ):
message = "You are not authorized to manage permissions on library dataset (%s)." % escape( item.name )
if cntrller == 'api':
return 403, message
return trans.response.send_redirect( web.url_for( controller='library_common',
@@ -53,17 +53,21 @@ class Mobile( BaseUIController ):
# trans.log_event( "User logged out" )
# trans.handle_user_logout()
def __login( self, trans, email="", password="" ):
def __login(self, trans, login="", password=""):
return trans.response.send_redirect( web.url_for(controller='root', action='index' ) )
# error = password_error = None
# user = trans.sa_session.query( model.User ).filter_by( email = email ).first()
# user = trans.sa_session.query( model.User ).filter(or_(
# email == login,
# username == login
# )).first()
# if not user:
# autoreg = galaxy.auth.check_auto_registration(trans, email, password, trans.app.config.auth_config_file)
# autoreg = galaxy.auth.check_auto_registration(trans, login, password, trans.app.config.auth_config_file)
# if autoreg[0]:
# kwd = {}
# kwd['username'] = autoreg[1]
# kwd['email'] = autoreg[1]
# kwd['username'] = autoreg[2]
# params = util.Params( kwd )
# message = validate_email( trans, email )
# message = validate_email( trans, kwd['email'] )
# if not message:
# message, status, user, success = self.__register( trans, 'user', False, **kwd )
# if success:
@@ -748,7 +748,7 @@ class PageController( BaseUIController, SharableMixin,
# TODO: should be moved to history controller and/or called via ajax from the template
decoded_id = self.decode_id( id )
# histories embedded in pages are set to importable when embedded, check for access here
history = self.history_manager.get_accessible( trans, decoded_id, trans.user )
history = self.history_manager.get_accessible( decoded_id, trans.user, current_history=trans.history )
# create ownership flag for template, dictify models
# note: adding original annotation since this is published - get_dict returns user-based annos
@@ -798,8 +798,8 @@ class PageController( BaseUIController, SharableMixin,
elif item_class == model.HistoryDatasetAssociation:
decoded_id = self.decode_id( item_id )
dataset = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( trans, dataset )
dataset = self.hda_manager.get_accessible( decoded_id, trans.user )
dataset = self.hda_manager.error_if_uploading( dataset )
dataset.annotation = self.get_item_annotation_str( trans.sa_session, dataset.history.user, dataset )
if dataset:
+14 -12
View File
@@ -14,7 +14,7 @@ from datetime import datetime, timedelta
from galaxy import model
from galaxy import util
from galaxy import web
from galaxy.model.orm import and_
from galaxy.model.orm import and_, or_
from galaxy.security.validate_user_input import (transform_publicname,
validate_email,
validate_password,
@@ -467,7 +467,7 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
status = kwd.get( 'status', 'done' )
header = ''
user = trans.user
email = kwd.get( 'email', '' )
login = kwd.get( 'login', '' )
if user:
# Already logged in.
redirect_url = redirect
@@ -496,7 +496,7 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
else:
header = REQUIRE_LOGIN_TEMPLATE % ( "Galaxy tool shed", "" )
return trans.fill_template( '/user/login.mako',
email=email,
login=login,
header=header,
use_panels=use_panels,
redirect_url=redirect_url,
@@ -510,21 +510,23 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
def __validate_login( self, trans, **kwd ):
"""Validates numerous cases that might happen during the login time."""
message = escape( kwd.get( 'message', '' ) )
status = kwd.get( 'status', 'error' )
email = kwd.get( 'email', '' )
login = kwd.get( 'login', '' )
password = kwd.get( 'password', '' )
username = kwd.get( 'username', '' )
redirect = kwd.get( 'redirect', trans.request.referer ).strip()
success = False
user = trans.sa_session.query( trans.app.model.User ).filter( trans.app.model.User.table.c.email == email ).first()
user = trans.sa_session.query( trans.app.model.User ).filter(or_(
trans.app.model.User.table.c.email == login,
trans.app.model.User.table.c.username == login
)).first()
log.debug("trans.app.config.auth_config_file: %s" % trans.app.config.auth_config_file)
if not user:
autoreg = trans.app.auth_manager.check_auto_registration(trans, email, password)
autoreg = trans.app.auth_manager.check_auto_registration(trans, login, password)
if autoreg[0]:
kwd['username'] = autoreg[1]
kwd['email'] = autoreg[1]
kwd['username'] = autoreg[2]
params = util.Params( kwd )
message = validate_email( trans, email ) #self.__validate( trans, params, email, password, password, username )
message = validate_email( trans, kwd['email'] ) #self.__validate( trans, params, email, password, password, username )
if not message:
message, status, user, success = self.__register( trans, 'user', False, **kwd )
if success:
@@ -552,11 +554,11 @@ class User( BaseUIController, UsesFormDefinitionsMixin, CreatesUsersMixin, Creat
elif trans.app.config.user_activation_on and not user.active: # activation is ON and the user is INACTIVE
if ( trans.app.config.activation_grace_period != 0 ): # grace period is ON
if self.is_outside_grace_period( trans, user.create_time ): # User is outside the grace period. Login is disabled and he will have the activation email resent.
message, status = self.resend_verification_email( trans, email, username )
message, status = self.resend_verification_email( trans, user.email, user.username )
else: # User is within the grace period, let him log in.
message, success, status = self.proceed_login( trans, user, redirect )
else: # Grace period is off. Login is disabled and user will have the activation email resent.
message, status = self.resend_verification_email( trans, email, username )
message, status = self.resend_verification_email( trans, user.email, user.username )
else: # activation is OFF
message, success, status = self.proceed_login( trans, user, redirect )
return ( message, status, user, success )
@@ -871,7 +871,7 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
dbkey = None
if new_dataset_id:
decoded_id = self.decode_id( new_dataset_id )
hda = self.hda_manager.get_owned( trans, decoded_id, trans.user )
hda = self.hda_manager.get_owned( decoded_id, trans.user, current_history=trans.user )
dbkey = hda.dbkey
if dbkey == '?':
dbkey = kwargs.get( "dbkey", None )
@@ -981,11 +981,11 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
viz = self.get_visualization( trans, id )
viz_config = self.get_visualization_config( trans, viz )
decoded_id = self.decode_id( viz_config[ 'dataset_id' ] )
dataset = self.hda_manager.get_owned( trans, decoded_id, trans.user )
dataset = self.hda_manager.get_owned( decoded_id, trans.user, current_history=trans.history )
else:
# Loading new visualization.
dataset = self.get_hda_or_ldda( trans, hda_ldda, dataset_id )
job = self.hda_manager.creating_job( trans, dataset )
job = self.hda_manager.creating_job( dataset )
viz_config = {
'dataset_id': dataset_id,
'tool_id': job.tool_id,
@@ -1016,8 +1016,8 @@ class VisualizationController( BaseUIController, SharableMixin, UsesVisualizatio
# get the hda if we can, then its data using the phyloviz parsers
if dataset_id:
decoded_id = self.decode_id( dataset_id )
hda = self.hda_manager.get_accessible( trans, decoded_id, trans.user )
hda = self.hda_manager.error_if_uploading( trans, hda )
hda = self.hda_manager.get_accessible( decoded_id, trans.user )
hda = self.hda_manager.error_if_uploading( hda )
else:
return trans.show_message( "Phyloviz couldn't find a dataset_id" )
+3 -2
View File
@@ -70,12 +70,13 @@ def app_factory( global_conf, **kwargs ):
webapp = wrap_in_static( webapp, global_conf, **kwargs )
# Close any pooled database connections before forking
try:
galaxy.model.mapping.metadata.engine.connection_provider._pool.dispose()
galaxy.model.mapping.metadata.bind.dispose()
except:
pass
log.exception("Unable to dispose of pooled galaxy model database connections.")
# Return
return webapp
def wrap_in_middleware( app, global_conf, **local_conf ):
"""Based on the configuration wrap `app` in a set of common and useful middleware."""
# Merge the global and local configurations
@@ -12,6 +12,7 @@ from galaxy import util
from galaxy import web
from galaxy.exceptions import RequestParameterMissingException
from galaxy.exceptions import RequestParameterInvalidException
from galaxy.exceptions import InsufficientPermissionsException
from galaxy.exceptions import ActionInputError
from galaxy.exceptions import ObjectNotFound
from galaxy.exceptions import MalformedId
+14 -6
View File
@@ -22,12 +22,20 @@ class SearchController ( BaseAPIController ):
TS config option toolshed_search_on has to be turned on and
toolshed_whoosh_index_dir has to be specified and existing.
:param search_term:
:param page:
:param jsonp:
:param callback:
:param search_term: (required)the term to search
:type search_term: str
:returns dict:
:param page: (optional)requested page of the search
:type page: int
:param jsonp: (optional)flag whether to use jsonp format response, defaults to False
:type jsonp: bool
:param callback: (optional)name of the function to wrap callback in
used only when jsonp is true, defaults to 'callback'
:type callback: str
:returns dict: object containing list of results and a hostname
"""
if not self.app.config.toolshed_search_on:
raise exceptions.ConfigDoesNotAllowException( 'Searching the TS through the API is turned off for this instance.' )
@@ -46,7 +54,7 @@ class SearchController ( BaseAPIController ):
results[ 'hostname' ] = url_for( '/', qualified = True )
if return_jsonp:
response = '%s(%s);' % ( callback, json.dumps( results ) )
response = str( '%s(%s);' % ( callback, json.dumps( results ) ) )
else:
response = json.dumps( results )
return response
+2 -2
View File
@@ -163,9 +163,9 @@ def app_factory( global_conf, **kwargs ):
webapp = wrap_in_static( webapp, global_conf, **kwargs )
# Close any pooled database connections before forking
try:
galaxy.webapps.tool_shed.model.mapping.metadata.engine.connection_provider._pool.dispose()
galaxy.webapps.tool_shed.model.mapping.metadata.bind.dispose()
except:
pass
log.exception("Unable to dispose of pooled tool_shed model database connections.")
# Return
return webapp
+1 -1
View File
@@ -196,7 +196,7 @@ def build_workflow_run_config( trans, workflow, payload ):
# Passing an existing history to use.
encoded_history_id = history_param[ 8: ]
history_id = __decode_id( trans, encoded_history_id, model_type="history" )
history = history_manager.get_owned( trans, history_id, trans.user )
history = history_manager.get_owned( history_id, trans.user, current_history=trans.history )
else:
# Send workflow outputs to new history.
history = app.model.History(name=history_param, user=trans.user)
+1 -1
View File
@@ -83,7 +83,7 @@ class PulsarExchange(object):
connection.drain_events(timeout=self.__timeout)
except socket.timeout:
pass
except (IOError, socket.error), exc:
except (IOError, socket.error) as exc:
self.__handle_io_error(exc, heartbeat_thread)
except BaseException:
log.exception("Problem consuming queue, consumer quitting in problematic fashion!")
+6 -2
View File
@@ -1,5 +1,6 @@
import os
from json import dumps
from json import loads
from .destination import submit_params
from .setup_handler import build as build_setup_handler
@@ -170,10 +171,13 @@ class JobClient(BaseJobClient):
input_path = path
if contents:
input_path = None
if action_type == 'transfer':
# action type == 'message' should either copy or transfer
# depending on default not just fallback to transfer.
if action_type in ['transfer', 'message']:
return self._upload_file(args, contents, input_path)
elif action_type == 'copy':
pulsar_path = self._raw_execute('path', args)
path_response = self._raw_execute('path', args)
pulsar_path = loads(path_response)['path']
copy(path, pulsar_path)
return {'path': pulsar_path}
+1 -1
View File
@@ -348,7 +348,7 @@ class TransferTracker(object):
if action.staging_needed:
local_action = action.staging_action_local
if local_action:
response = self.client.put_file(path, type, name=name, contents=contents)
response = self.client.put_file(path, type, name=name, contents=contents, action_type=action.action_type)
def get_path():
return response['path']
@@ -46,11 +46,6 @@ class InstallEnvironment( object ):
self.install_dir = install_dir
self.tool_shed_repository_install_dir = tool_shed_repository_install_dir
def __call__( self ):
with settings( warn_only=True, **basic_util.get_env_var_values( self ) ):
with prefix( self.__setup_environment() ):
yield
def add_env_shell_file_paths( self, paths ):
for path in paths:
self.env_shell_file_paths.append( str( path ) )
@@ -238,7 +238,7 @@ class AssertDirectoryExecutable( RecipeStep ):
return False
if os.path.isdir( full_path ):
# Make sure the owner has execute permission on the directory.
# See http://docs.python.org/2/library/stat.html
# See https://docs.python.org/2/library/stat.html
if stat.S_IXUSR & os.stat( full_path )[ stat.ST_MODE ] == 64:
return True
return False
@@ -334,7 +334,7 @@ class AssertFileExecutable( RecipeStep ):
return False
if os.path.exists( full_path ):
# Make sure the owner has execute permission on the file.
# See http://docs.python.org/2/library/stat.html
# See https://docs.python.org/2/library/stat.html
if stat.S_IXUSR & os.stat( full_path )[ stat.ST_MODE ] == 64:
return True
return False
@@ -896,7 +896,7 @@ class SetEnvironment( RecipeStep ):
in the tool_dependencies.xml file in the order discussed here. The example for this discussion is the
tool_dependencies.xml file contained in the osra repository, which is available at:
http://testtoolshed.g2.bx.psu.edu/view/bgruening/osra
https://testtoolshed.g2.bx.psu.edu/view/bgruening/osra
The first tag set defines a complex repository dependency like this. This tag set ensures that changeset
revision XXX of the repository named package_graphicsmagick_1_3 owned by YYY in the tool shed ZZZ has been
+1 -1
View File
@@ -25,7 +25,7 @@ def generate_repository_archive_filename( tool_shed_url, name, owner, changeset_
export_repository_dependencies, use_tmp_archive_dir=False ):
tool_shed = remove_protocol_from_tool_shed_url( tool_shed_url )
file_type_str = basic_util.get_file_type_str( changeset_revision, file_type )
if self.export_repository_dependencies:
if export_repository_dependencies:
repositories_archive_filename = '%s_%s_%s_%s_%s' % ( CAPSULE_WITH_DEPENDENCIES_FILENAME,
tool_shed,
name,
+2 -1
View File
@@ -14,7 +14,8 @@ method (they'll have to be created manually, which can be done after the import)
Here is a working example of how to use this script to install a repository from the test tool shed.
./import_capsule.py -a <api key> -u http://localhost:9009 -c capsule_localhost_colon_9009_filter_test1_8923f52d5c6d.tar.gz
"""
import logging
log = logging.getLogger(__name__)
import os
import sys
import argparse
@@ -26,7 +26,7 @@ def main( options ):
response_dict = submit( url, data, api_key=api_key, return_formatted=False )
print response_dict
else:
print "Invalid tool_shed: ", base_tool_shed_url, " name: ", name, " or owner: ", owner, "."
print "Invalid tool_shed: ", base_tool_shed_url, " name: ", options.name, " or owner: ", options.owner, "."
else:
print "An API key for an admin user in the Tool Shed is required to remove entries from the Tool Shed's repository registry."
@@ -11,7 +11,8 @@ usage: reset_metadata_on_repositories.py key <my_writable>
Here is a working example of how to use this script to reset metadata on certain repositories in a specified Tool Shed.
python ./reset_metadata_on_repositories.py -a 22be3b -m True -u http://localhost:9009/
"""
import logging
log = logging.getLogger(__name__)
import argparse
import os
import sys
+1 -1
View File
@@ -334,7 +334,7 @@ def tool_shed_get( app, tool_shed_url, uri ):
"""Make contact with the tool shed via the uri provided."""
registry = app.tool_shed_registry
# urllib2 auto-detects system proxies, when passed a Proxyhandler.
# Refer: http://docs.python.org/2/howto/urllib2.html#proxies
# Refer: https://docs.python.org/2/howto/urllib2.html#proxies
proxy = urllib2.ProxyHandler()
urlopener = urllib2.build_opener( proxy )
urllib2.install_opener( urlopener )
+7
View File
@@ -237,12 +237,19 @@ def new_state( trans, tool, invalid=False ):
if invalid:
# We're attempting to display a tool in the tool shed that has been determined to have errors, so is invalid.
return state
try:
# Attempt to generate the tool state using the standard Galaxy-side code
return tool.new_state( trans )
except Exception, e:
# Fall back to building tool state as below
log.debug( 'Failed to build tool state for tool "%s" using standard method, will try to fall back on custom method: %s', tool.id, e )
inputs = tool.inputs_by_page[ 0 ]
context = ExpressionContext( state.inputs, parent=None )
for input in inputs.itervalues():
try:
state.inputs[ input.name ] = input.get_initial_value( trans, context )
except:
# FIXME: not all values should be an empty list
state.inputs[ input.name ] = []
return state
+20 -9
View File
@@ -3,29 +3,40 @@
./scripts/common_startup.sh
# A good place to look for nose info: http://somethingaboutorange.com/mrl/projects/nose/
rm -f run_functional_tests.log
rm -f run_functional_tests.log
show_help() {
cat <<EOF
'${0##*/}' for testing all the tools in functional directory
'${0##*/} aaa' for testing one test case of 'aaa' ('aaa' is the file name with path)
'${0##*/} (test_path)' for testing all the tools in functional directory
'${0##*/} -id bbb' for testing one tool with id 'bbb' ('bbb' is the tool id)
'${0##*/} -sid ccc' for testing one section with sid 'ccc' ('ccc' is the string after 'section::')
'${0##*/} -list' for listing all the tool ids
'${0##*/} -api' for running all the test scripts in the ./test/api directory
'${0##*/} -toolshed' for running all the test scripts in the ./test/tool_shed/functional directory
'${0##*/} -toolshed testscriptname' for running one test script named testscriptname in the .test/tool_shed/functional directory
'${0##*/} -api (test_path)' for running all the test scripts in the ./test/api directory
'${0##*/} -toolshed (test_path)' for running all the test scripts in the ./test/tool_shed/functional directory
'${0##*/} -workflow test.xml' for running a workflow test case as defined by supplied workflow xml test file (experimental)
'${0##*/} -framework' for running through example tool tests testing framework features in test/functional/tools"
'${0##*/} -framework' for running through example tool tests testing framework features in test/functional/tools"
'${0##*/} -framework -id toolid' for testing one framework tool (in test/functional/tools/) with id 'toolid'
'${0##*/} -data_managers -id data_manager_id' for testing one Data Manager with id 'data_manager_id'
'${0##*/} -unit' for running all unit tests (doctests in lib and tests in test/unit)
'${0##*/} -unit testscriptath' running particular tests scripts
'${0##*/} -unit (test_path)' for running all unit tests (doctests in lib and tests in test/unit)
'${0##*/} -qunit' for running qunit JavaScript tests
'${0##*/} -qunit testname' for running single JavaScript test with given name
Nose tests will allow specific tests to be selected per the documentation at
https://nose.readthedocs.org/en/latest/usage.html#selecting-tests. These are
indicated with the optional parameter (test_path). A few examples are:
Run all TestUserInfo functional tests:
./run_tests.sh test/functional/test_user_info.py:TestUserInfo
Run a specific API test requiring the framework test tools:
./run_tests.sh -api -with_framework_test_tools test/api/test_tools.py:ToolsTestCase.test_map_over_with_output_format_actions
Extra options:
--verbose_errors Force some tests produce more verbose error reporting.
--no_cleanup Do not delete temp files for Python functional tests (-toolshed, -framework, etc...)
--report_file Path of HTML report to produce (for Python Galaxy functional tests).
+6 -3
View File
@@ -1,4 +1,7 @@
[flake8]
# 201 and 202 are spaces after ( and before ), 501 is line length. These
# are exceptions allowed (encouraged?) by Galaxy style guidelines.
ignore = E201,E202,E501
# These are exceptions allowed (encouraged?) by Galaxy style guidelines.
# 128 continuation line under-indented for visual indent
# 201 and 202 are spaces after ( and before )
# 203 whitespace before ':'
# 501 is line length
ignore = E128,E201,E202,E203,E501
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"dataset-model.js","sources":["../../../src/mvc/dataset/dataset-model.js"],"names":["define","STATES","BASE_MVC","_l","searchableMixin","SearchableModelMixin","DatasetAssociation","Backbone","Model","extend","LoggableMixin","mixin","defaults","state","NEW","deleted","purged","name","accessible","data_type","file_ext","file_size","meta_files","misc_blurb","misc_info","tags","initialize","attributes","options","this","debug","get","set","NOT_VIEWABLE","urls","_generateUrls","_setUpListeners","id","purge","display","edit","download","report_error","rerun","show_params","visualization","meta_download","root","window","galaxy_config","_","each","value","key","on","currModel","newState","log","inReadyState","trigger","previous","toJSON","json","prototype","call","isDeletedOrPurged","ready","contains","READY_STATES","hasDetails","has","hasData","fetch","dataset","always","delete","jQuery","when","save","undelete","url","hda","xhr","ajax","done","fail","error","messageBuriedInUnfortunatelyFormattedError","responseJSON","responseText","indexOf","searchAttributes","searchAliases","title","format","database","blurb","description","info","tag","toString","nameAndId","DatasetAssociationCollection","Collection","model","urlRoot","ids","map","item","notReady","filter","content","haveDetails","all","ajaxQueue","ajaxFn","deferred","Deferred","startingLength","length","responses","resolve","ajaxFns","chain","reverse","i","response","notify","curr","total","push","shift","matches","matchesWhat","join"],"mappings":"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"}
{"version":3,"file":"dataset-model.js","sources":["../../../src/mvc/dataset/dataset-model.js"],"names":["define","STATES","BASE_MVC","_l","searchableMixin","SearchableModelMixin","DatasetAssociation","Backbone","Model","extend","LoggableMixin","mixin","defaults","state","NEW","deleted","purged","name","accessible","data_type","file_ext","file_size","meta_files","misc_blurb","misc_info","tags","initialize","attributes","options","this","debug","get","set","NOT_VIEWABLE","urls","_generateUrls","_setUpListeners","id","purge","display","edit","download","report_error","rerun","show_params","visualization","meta_download","root","window","galaxy_config","_","each","value","key","on","currModel","newState","log","inReadyState","trigger","previous","toJSON","json","prototype","call","isDeletedOrPurged","ready","contains","READY_STATES","hasDetails","has","hasData","fetch","dataset","always","delete","jQuery","when","save","undelete","url","hda","xhr","ajax","done","fail","error","messageBuriedInUnfortunatelyFormattedError","responseJSON","responseText","indexOf","searchAttributes","searchAliases","title","format","database","blurb","description","info","tag","toString","nameAndId","DatasetAssociationCollection","Collection","model","urlRoot","ids","map","item","notReady","filter","content","haveDetails","all","ajaxQueue","ajaxFn","deferred","Deferred","startingLength","length","responses","resolve","ajaxFns","chain","reverse","i","response","notify","curr","total","push","shift","matches","matchesWhat","join"],"mappings":"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"}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"form-data.js","sources":["../../../src/mvc/form/form-data.js"],"names":["define","Backbone","Model","extend","initialize","app","this","checksum","sum","i","field_list","field","JSON","stringify","value","create","add","job_input_id","input_id","input_value","self","map_dict","result_dict","convert","identifier","head","index","node","input","name","type","section_label","block_indices","block_prefix","block_label","pos","indexOf","length","push","parseInt","substr","sort","a","b","id","test_param","selectedCase","matchCase","undefined","ignore","payload","p_id","dict","_iterate","section","$el","match","truevalue","falsevalue","cases","matchModel","model","callback","search","j","cache","inputs","result","matchResponse","response","new_id","separator","Array","parent","children","$","each","child","attr","hasClass","input_list"],"mappings":"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"}
{"version":3,"file":"form-data.js","sources":["../../../src/mvc/form/form-data.js"],"names":["define","Backbone","Model","extend","initialize","app","this","checksum","sum","i","field_list","field","JSON","stringify","value","create","add","job_input_id","input_id","input_value","self","map_dict","result_dict","convert","identifier","head","index","node","input","name","type","section_label","block_indices","block_prefix","block_label","pos","indexOf","length","push","parseInt","substr","sort","a","b","id","test_param","selectedCase","matchCase","undefined","ignore","payload","p_id","dict","_iterate","section","$el","match","truevalue","falsevalue","cases","matchModel","model","callback","search","j","cache","inputs","result","matchResponse","response","new_id","separator","Array","parent","children","$","each","child","attr","hasClass","input_list"],"mappings":"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"}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"form-section.js","sources":["../../../src/mvc/form/form-section.js"],"names":["define","Utils","Table","Ui","Portlet","Repeat","InputElement","Parameters","View","Backbone","extend","initialize","app","options","this","inputs","cls","cls_tr","table","parameters","setElement","$el","render","delAll","i","add","input","input_def","jQuery","id","uid","input_list","type","_addConditional","_addRepeat","_addSection","_addRow","self","test_param","field","onchange","value","selectedCase","data","matchCase","cases","case_def","section_id","section_row","get","nonhidden","j","hidden","fadeIn","hide","trigger","sub_section_id","sub_section","addClass","append","create","block_index","repeat","ondel","del","title","title_new","min","max","onnew","n_min","n_cache","_","size","cache","Math","input_element","label","help","button_visible","ButtonIcon","icon","tooltip","portlet","operations","$","html","visible","$content","$header","css","on","setIcon","expand","field_list","default_value","optional","element_list"],"mappings":"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"}
{"version":3,"file":"form-section.js","sources":["../../../src/mvc/form/form-section.js"],"names":["define","Utils","Table","Ui","Portlet","Repeat","InputElement","Parameters","View","Backbone","extend","initialize","app","options","this","inputs","cls","cls_tr","table","parameters","setElement","$el","render","delAll","i","add","input","input_def","jQuery","id","uid","input_list","type","_addConditional","_addRepeat","_addSection","_addRow","self","test_param","field","onchange","value","selectedCase","data","matchCase","cases","case_def","section_id","section_row","get","nonhidden","j","hidden","fadeIn","hide","trigger","sub_section_id","sub_section","addClass","append","create","block_index","repeat","ondel","del","title","title_new","min","max","onnew","n_min","n_cache","_","size","cache","Math","input_element","label","help","button_visible","ButtonIcon","icon","tooltip","portlet","operations","$","html","visible","$content","$header","css","on","setIcon","expanded","field_list","default_value","optional","element_list"],"mappings":"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"}
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"form-view.js","sources":["../../../src/mvc/form/form-view.js"],"names":["define","Utils","Portlet","Ui","FormSection","FormData","Backbone","View","extend","initialize","options","this","optionsDefault","is_workflow","narrow","initial_errors","cls","merge","console","debug","galaxy","parent","Galaxy","modal","Modal","setElement","_build","update","new_model","self","data","matchModel","input_id","node","input","input_list","_","isEqual","field","field_list","new_options","indexOf","type","i","opt","length","push","label","value","trigger","wait","active","is_dynamic","unwait","reciept","$el","empty","append","highlight","message","silent","input_element","element_list","error","$","animate","scrollTop","offset","top","errors","error_messages","matchResponse","off","_renderForm","create","current_check","checksum","on","new_check","onchange","reset","Message","section","inputs","portlet","icon","title","operations","buttons","addClass","persistent","status"],"mappings":"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"}
{"version":3,"file":"form-view.js","sources":["../../../src/mvc/form/form-view.js"],"names":["define","Utils","Portlet","Ui","FormSection","FormData","Backbone","View","extend","initialize","options","this","optionsDefault","is_workflow","narrow","initial_errors","cls","merge","console","debug","galaxy","parent","Galaxy","modal","Modal","setElement","_build","update","new_model","self","data","matchModel","input_id","node","input","input_list","_","isEqual","field","field_list","new_options","indexOf","type","i","opt","length","push","label","value","trigger","wait","active","is_dynamic","unwait","reciept","$el","empty","append","highlight","message","silent","input_element","element_list","error","$","animate","scrollTop","offset","top","errors","error_messages","matchResponse","off","_renderForm","create","current_check","checksum","on","new_check","onchange","reset","Message","section","inputs","incompatible","hide","show","portlet","icon","title","operations","buttons","addClass","persistent","status"],"mappings":"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"}
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More