mirror of
https://github.com/galaxyproject/galaxy.git
synced 2026-09-24 16:30:27 +08:00
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:
@@ -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
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
},
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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>(&(objectClass=user)(mail={username}))</search-filter>
|
||||
# [<search-filter>(&(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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
+46
-27
@@ -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']
|
||||
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
@@ -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 ),
|
||||
})
|
||||
|
||||
@@ -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
@@ -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 ) )
|
||||
|
||||
@@ -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
@@ -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
@@ -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' ) },
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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] ) ) )
|
||||
@@ -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
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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' ) },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 ) ):
|
||||
|
||||
@@ -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 ):
|
||||
"""
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 ],
|
||||
|
||||
@@ -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' ):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 ) ) )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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' )
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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 ):
|
||||
|
||||
@@ -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 )
|
||||
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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,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" )
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 )
|
||||
|
||||
@@ -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
@@ -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).
|
||||
|
||||
@@ -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 @@
|
||||
{"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 @@
|
||||
{"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":"AAGAA,QAAQ,eAAgB,WACxB,MAAOC,UAASC,MAAMC,QAElBC,WAAY,SAASC,GACjBC,KAAKD,IAAMA,GAKfE,SAAU,WACN,GAAIC,GAAM,EACV,KAAK,GAAIC,KAAKH,MAAKD,IAAIK,WAAY,CAC/B,GAAIC,GAAQL,KAAKD,IAAIK,WAAWD,EAChCD,IAAOI,KAAKC,UAAUF,EAAMG,OAASH,EAAMG,SAE/C,MAAON,IAKXO,OAAQ,WAWJ,QAASC,GAAIC,EAAcC,EAAUC,GACjCC,EAAKC,SAASJ,GAAgBC,EAC9BI,EAAYL,GAAgBE,EAIhC,QAASI,GAAQC,EAAYC,GACzB,IAAK,GAAIC,KAASD,GAAM,CACpB,GAAIE,GAAOF,EAAKC,EAChB,IAAIC,EAAKC,MAAO,CAEZ,GAAIA,GAAQD,EAAKC,MAGbX,EAAeO,CAOnB,QANkB,IAAdA,IACAP,GAAgB,KAEpBA,GAAgBW,EAAMC,KAGdD,EAAME,MAEV,IAAK,SAED,GAAIC,GAAgB,WAGhBC,KACAC,EAAe,IACnB,KAAK,GAAIC,KAAeP,GAAM,CAC1B,GAAIQ,GAAMD,EAAYE,QAAQL,EACnB,KAAPI,IACAA,GAAOJ,EAAcM,OACrBL,EAAcM,KAAKC,SAASL,EAAYM,OAAOL,KAC1CF,IACDA,EAAeC,EAAYM,OAAO,EAAGL,KAMjDH,EAAcS,KAAK,SAASC,EAAEC,GAAK,MAAOD,GAAIC,GAG9C,IAAIjB,GAAQ,CACZ,KAAK,GAAIjB,KAAKuB,GACVT,EAAQN,EAAe,IAAMS,IAASC,EAAKM,EAAeD,EAAcvB,IAE5E,MAEJ,KAAK,cAED,GAAIK,GAAQM,EAAKf,IAAIK,WAAWkB,EAAMgB,IAAI9B,OAG1CE,GAAKC,EAAe,IAAMW,EAAMiB,WAAWhB,KAAMD,EAAMgB,GAAI9B,EAG3D,IAAIgC,GAAe1B,EAAK2B,UAAUnB,EAAOd,EACrB,KAAhBgC,GACAvB,EAAQN,EAAcQ,EAAKG,EAAMgB,GAAK,YAAcE,GAExD,MAEJ,KAAK,UACDvB,EAAQ,GAAII,EACZ,MACJ,SAEI,GAAIhB,GAAQS,EAAKf,IAAIK,WAAWkB,EAAMgB,GACtC,IAAIjC,GAASA,EAAMG,MAAO,CAEtB,GAAIA,GAAQH,EAAMG,OAGlB,KAAqBkC,SAAjBpB,EAAMqB,QAAwBrB,EAAMqB,QAAUnC,KAE9CE,EAAKC,EAAcW,EAAMgB,GAAI9B,GAGzBc,EAAMsB,SACN,IAAK,GAAIC,KAAQvB,GAAMsB,QACnBlC,EAAKmC,EAAMvB,EAAMgB,GAAIhB,EAAMsB,QAAQC,QA5FvE,GAAI/B,GAAOd,KAGP8C,IACJ9C,MAAK+C,SAAS/C,KAAKD,IAAIiD,QAAQC,IAAKH,EAGpC,IAAI9B,KAmGJ,OAlGAhB,MAAKe,YA+FLE,EAAQ,GAAI6B,GAGL9B,GAKXkC,MAAO,SAAUvC,GACb,MAAOX,MAAKe,UAAYf,KAAKe,SAASJ,IAK1C8B,UAAW,SAASnB,EAAOd,GAEM,WAAzBc,EAAMiB,WAAWf,OAEbhB,EADS,QAATA,EACQc,EAAMiB,WAAWY,WAAa,OAE9B7B,EAAMiB,WAAWa,YAAc,QAK/C,KAAK,GAAIjD,KAAKmB,GAAM+B,MAChB,GAAI/B,EAAM+B,MAAMlD,GAAGK,OAASA,EACxB,MAAOL,EAKf,OAAO,IAKXmD,WAAY,SAASC,EAAOC,GAQxB,QAASC,GAAQnB,EAAInB,GACjB,IAAK,GAAIhB,KAAKgB,GAAM,CAChB,GAAIE,GAAOF,EAAKhB,GACZiB,EAAQC,EAAKE,IAIjB,QAHU,IAANe,IACAlB,EAAQkB,EAAK,IAAMlB,GAEfC,EAAKG,MACT,IAAK,SACD,IAAK,GAAIkC,KAAKrC,GAAKsC,MACfF,EAAQrC,EAAQ,IAAMsC,EAAGrC,EAAKsC,MAAMD,GAExC,MACJ,KAAK,cACD,GAAIlD,GAAQa,EAAKkB,YAAclB,EAAKkB,WAAW/B,MAC3CgC,EAAe1B,EAAK2B,UAAUpB,EAAMb,EACpB,KAAhBgC,GACAiB,EAAQrC,EAAOC,EAAKgC,MAAMb,GAAcoB,OAE5C,MACJ,SACI,GAAIhD,GAAWE,EAAKC,SAASK,EACzBR,IACA4C,EAAS5C,EAAUS,KA7BvC,GAAIwC,MAGA/C,EAAOd,IAoCX,OAHAyD,GAAO,GAAIF,EAAMK,QAGVC,GAKXC,cAAe,SAASC,GAQpB,QAASN,GAAQnB,EAAInB,GACjB,GAAoB,gBAATA,GAAmB,CAC1B,GAAIP,GAAWE,EAAKC,SAASuB,EACzB1B,KACAiD,EAAOjD,GAAYO,OAGvB,KAAK,GAAIhB,KAAKgB,GAAM,CAChB,GAAI6C,GAAS7D,CACb,IAAW,KAAPmC,EAAW,CACX,GAAI2B,GAAY,GACZ9C,aAAgB+C,SAChBD,EAAY,KAEhBD,EAAS1B,EAAK2B,EAAYD,EAE9BP,EAAQO,EAAQ7C,EAAKhB,KAtBjC,GAAI0D,MAGA/C,EAAOd,IA4BX,OAHAyD,GAAO,GAAIM,GAGJF,GAKXd,SAAU,SAASoB,EAAQrB,GAEvB,GAAIhC,GAAOd,KACPoE,EAAWC,EAAEF,GAAQC,UACzBA,GAASE,KAAK,WAEV,GAAIC,GAAQvE,KAGRsC,EAAK+B,EAAEE,GAAOC,KAAK,KAGvB,IAAIH,EAAEE,GAAOE,SAAS,eAAgB,CAElC3B,EAAKR,KAGL,IAAIhB,GAAQR,EAAKf,IAAI2E,WAAWpC,EAC5BhB,KACAwB,EAAKR,IACDhB,MAAQA,IAKhBR,EAAKiC,SAASwB,EAAOzB,EAAKR,QAE1BxB,GAAKiC,SAASwB,EAAOzB"}
|
||||
{"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":"AAGAA,QAAQ,eAAgB,WACxB,MAAOC,UAASC,MAAMC,QAElBC,WAAY,SAASC,GACjBC,KAAKD,IAAMA,GAKfE,SAAU,WACN,GAAIC,GAAM,EACV,KAAK,GAAIC,KAAKH,MAAKD,IAAIK,WAAY,CAC/B,GAAIC,GAAQL,KAAKD,IAAIK,WAAWD,EAChCD,IAAOI,KAAKC,UAAUF,EAAMG,OAASH,EAAMG,SAE/C,MAAON,IAKXO,OAAQ,WAWJ,QAASC,GAAIC,EAAcC,EAAUC,GACjCC,EAAKC,SAASJ,GAAgBC,EAC9BI,EAAYL,GAAgBE,EAIhC,QAASI,GAAQC,EAAYC,GACzB,IAAK,GAAIC,KAASD,GAAM,CACpB,GAAIE,GAAOF,EAAKC,EAChB,IAAIC,EAAKC,MAAO,CAEZ,GAAIA,GAAQD,EAAKC,MAGbX,EAAeO,CAOnB,QANkB,IAAdA,IACAP,GAAgB,KAEpBA,GAAgBW,EAAMC,KAGdD,EAAME,MAEV,IAAK,SAED,GAAIC,GAAgB,WAGhBC,KACAC,EAAe,IACnB,KAAK,GAAIC,KAAeP,GAAM,CAC1B,GAAIQ,GAAMD,EAAYE,QAAQL,EACnB,KAAPI,IACAA,GAAOJ,EAAcM,OACrBL,EAAcM,KAAKC,SAASL,EAAYM,OAAOL,KAC1CF,IACDA,EAAeC,EAAYM,OAAO,EAAGL,KAMjDH,EAAcS,KAAK,SAASC,EAAEC,GAAK,MAAOD,GAAIC,GAG9C,IAAIjB,GAAQ,CACZ,KAAK,GAAIjB,KAAKuB,GACVT,EAAQN,EAAe,IAAMS,IAASC,EAAKM,EAAeD,EAAcvB,IAE5E,MAEJ,KAAK,cAED,GAAIK,GAAQM,EAAKf,IAAIK,WAAWkB,EAAMgB,IAAI9B,OAG1CE,GAAKC,EAAe,IAAMW,EAAMiB,WAAWhB,KAAMD,EAAMgB,GAAI9B,EAG3D,IAAIgC,GAAe1B,EAAK2B,UAAUnB,EAAOd,EACrB,KAAhBgC,GACAvB,EAAQN,EAAcQ,EAAKG,EAAMgB,GAAK,YAAcE,GAExD,MAEJ,KAAK,UACDvB,EAAQN,EAAcU,EACtB,MACJ,SAEI,GAAIhB,GAAQS,EAAKf,IAAIK,WAAWkB,EAAMgB,GACtC,IAAIjC,GAASA,EAAMG,MAAO,CAEtB,GAAIA,GAAQH,EAAMG,OAGlB,KAAqBkC,SAAjBpB,EAAMqB,QAAwBrB,EAAMqB,QAAUnC,KAE9CE,EAAKC,EAAcW,EAAMgB,GAAI9B,GAGzBc,EAAMsB,SACN,IAAK,GAAIC,KAAQvB,GAAMsB,QACnBlC,EAAKmC,EAAMvB,EAAMgB,GAAIhB,EAAMsB,QAAQC,QA5FvE,GAAI/B,GAAOd,KAGP8C,IACJ9C,MAAK+C,SAAS/C,KAAKD,IAAIiD,QAAQC,IAAKH,EAGpC,IAAI9B,KAmGJ,OAlGAhB,MAAKe,YA+FLE,EAAQ,GAAI6B,GAGL9B,GAKXkC,MAAO,SAAUvC,GACb,MAAOX,MAAKe,UAAYf,KAAKe,SAASJ,IAK1C8B,UAAW,SAASnB,EAAOd,GAEM,WAAzBc,EAAMiB,WAAWf,OAEbhB,EADS,QAATA,EACQc,EAAMiB,WAAWY,WAAa,OAE9B7B,EAAMiB,WAAWa,YAAc,QAK/C,KAAK,GAAIjD,KAAKmB,GAAM+B,MAChB,GAAI/B,EAAM+B,MAAMlD,GAAGK,OAASA,EACxB,MAAOL,EAKf,OAAO,IAKXmD,WAAY,SAASC,EAAOC,GAQxB,QAASC,GAAQnB,EAAInB,GACjB,IAAK,GAAIhB,KAAKgB,GAAM,CAChB,GAAIE,GAAOF,EAAKhB,GACZiB,EAAQC,EAAKE,IAIjB,QAHU,IAANe,IACAlB,EAAQkB,EAAK,IAAMlB,GAEfC,EAAKG,MACT,IAAK,SACD,IAAK,GAAIkC,KAAKrC,GAAKsC,MACfF,EAAQrC,EAAQ,IAAMsC,EAAGrC,EAAKsC,MAAMD,GAExC,MACJ,KAAK,cACD,GAAIlD,GAAQa,EAAKkB,YAAclB,EAAKkB,WAAW/B,MAC3CgC,EAAe1B,EAAK2B,UAAUpB,EAAMb,EACpB,KAAhBgC,GACAiB,EAAQrC,EAAOC,EAAKgC,MAAMb,GAAcoB,OAE5C,MACJ,KAAK,UACDH,EAAQrC,EAAOC,EAAKuC,OACpB,MACJ,SACI,GAAIhD,GAAWE,EAAKC,SAASK,EACzBR,IACA4C,EAAS5C,EAAUS,KAhCvC,GAAIwC,MAGA/C,EAAOd,IAuCX,OAHAyD,GAAO,GAAIF,EAAMK,QAGVC,GAKXC,cAAe,SAASC,GAQpB,QAASN,GAAQnB,EAAInB,GACjB,GAAoB,gBAATA,GAAmB,CAC1B,GAAIP,GAAWE,EAAKC,SAASuB,EACzB1B,KACAiD,EAAOjD,GAAYO,OAGvB,KAAK,GAAIhB,KAAKgB,GAAM,CAChB,GAAI6C,GAAS7D,CACb,IAAW,KAAPmC,EAAW,CACX,GAAI2B,GAAY,GACZ9C,aAAgB+C,SAChBD,EAAY,KAEhBD,EAAS1B,EAAK2B,EAAYD,EAE9BP,EAAQO,EAAQ7C,EAAKhB,KAtBjC,GAAI0D,MAGA/C,EAAOd,IA4BX,OAHAyD,GAAO,GAAIM,GAGJF,GAKXd,SAAU,SAASoB,EAAQrB,GAEvB,GAAIhC,GAAOd,KACPoE,EAAWC,EAAEF,GAAQC,UACzBA,GAASE,KAAK,WAEV,GAAIC,GAAQvE,KAGRsC,EAAK+B,EAAEE,GAAOC,KAAK,KAGvB,IAAIH,EAAEE,GAAOE,SAAS,eAAgB,CAElC3B,EAAKR,KAGL,IAAIhB,GAAQR,EAAKf,IAAI2E,WAAWpC,EAC5BhB,KACAwB,EAAKR,IACDhB,MAAQA,IAKhBR,EAAKiC,SAASwB,EAAOzB,EAAKR,QAE1BxB,GAAKiC,SAASwB,EAAOzB"}
|
||||
@@ -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 @@
|
||||
{"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
Reference in New Issue
Block a user