",
- 'This dataset is unrestricted so everybody with the link can access it. Just share this page.',
+ 'This dataset is unrestricted so everybody with the link can access it. Just share this page.',
"
')
+ return "\n".join(rval)
diff --git a/lib/galaxy/datatypes/qualityscore.py b/lib/galaxy/datatypes/qualityscore.py
index 770af6def39..8b55ff34870 100644
--- a/lib/galaxy/datatypes/qualityscore.py
+++ b/lib/galaxy/datatypes/qualityscore.py
@@ -56,7 +56,7 @@ class QualityScoreSOLiD (QualityScore):
if not(readlen):
readlen = len(line.split())
assert len(line.split()) == readlen # SOLiD reads should be of the same length
- except:
+ except Exception:
break
goodblock += 1
if goodblock > 10:
@@ -64,7 +64,7 @@ class QualityScoreSOLiD (QualityScore):
else:
break # we found a non-empty line, but it's not a header
fh.close()
- except:
+ except Exception:
pass
return False
@@ -106,13 +106,13 @@ class QualityScore454 (QualityScore):
break
try:
[int(x) for x in line.split()]
- except:
+ except Exception:
break
return True
else:
break # we found a non-empty line, but it's not a header
fh.close()
- except:
+ except Exception:
pass
return False
diff --git a/lib/galaxy/datatypes/sequence.py b/lib/galaxy/datatypes/sequence.py
index ee47ad70bbd..a2083fab2b5 100644
--- a/lib/galaxy/datatypes/sequence.py
+++ b/lib/galaxy/datatypes/sequence.py
@@ -81,7 +81,7 @@ class SequenceSplitLocations(data.Text):
if 'start' not in section or 'end' not in section or 'sequences' not in section:
return False
return True
- except:
+ except Exception:
pass
return False
@@ -376,7 +376,7 @@ class Fasta(Sequence):
else:
break # we found a non-empty line, but it's not a fasta header
fh.close()
- except:
+ except Exception:
pass
return False
@@ -543,7 +543,7 @@ class csFasta(Sequence):
else:
break # we found a non-empty line, but it's not a header
fh.close()
- except:
+ except Exception:
pass
return False
@@ -634,7 +634,7 @@ class BaseFastq (Sequence):
return False
return True
return False
- except:
+ except Exception:
return False
def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, **kwd):
@@ -930,7 +930,7 @@ class Maf(Alignment):
return True
else:
return False
- except:
+ except Exception:
return False
@@ -968,7 +968,7 @@ class MafCustomTrack(data.Text):
dataset.metadata.vp_chromosome = chrom
dataset.metadata.vp_start = forward_strand_start
dataset.metadata.vp_end = forward_strand_end
- except:
+ except Exception:
pass
@@ -1019,7 +1019,7 @@ class Axt(data.Text):
return False
try:
map(int, [hdr[0], hdr[2], hdr[3], hdr[5], hdr[6], hdr[8]])
- except:
+ except Exception:
return False
if hdr[7] not in data.valid_strand:
return False
@@ -1060,7 +1060,7 @@ class Lav(data.Text):
return True
else:
return False
- except:
+ except Exception:
return False
@@ -1205,7 +1205,7 @@ class Genbank(data.Text):
try:
with open(filename, 'r') as handle:
return 'LOCUS ' == handle.read(6)
- except:
+ except Exception:
pass
return False
@@ -1236,7 +1236,7 @@ class MemePsp(Sequence):
for item in l.split():
try:
float(item)
- except:
+ except ValueError:
return False
return True
try:
@@ -1269,7 +1269,7 @@ class MemePsp(Sequence):
# We found a non-empty line,
# but it's not a psp id width.
return False
- except:
+ except Exception:
return False
# We've reached EOF in less than 100 lines.
return True
diff --git a/lib/galaxy/datatypes/sniff.py b/lib/galaxy/datatypes/sniff.py
index 18b5ad1387f..970beefa63d 100644
--- a/lib/galaxy/datatypes/sniff.py
+++ b/lib/galaxy/datatypes/sniff.py
@@ -54,7 +54,7 @@ def stream_to_open_named_file(stream, fd, filename, source_encoding=None, source
is_multi_byte = False
try:
codecs.lookup(target_encoding)
- except:
+ except Exception:
target_encoding = util.DEFAULT_ENCODING # utf-8
if not source_encoding:
source_encoding = util.DEFAULT_ENCODING # sys.getdefaultencoding() would mimic old behavior (defaults to ascii)
@@ -70,7 +70,7 @@ def stream_to_open_named_file(stream, fd, filename, source_encoding=None, source
try:
if text_type(chunk[:2]) == text_type(util.gzip_magic):
is_compressed = True
- except:
+ except Exception:
pass
if not is_compressed:
# See if we have a multi-byte character file
@@ -408,7 +408,7 @@ def guess_ext(fname, sniff_order, is_multi_byte=False):
if datatype.sniff(fname):
file_ext = datatype.file_ext
break
- except:
+ except Exception:
pass
# Ugly hack for tsv vs tabular sniffing, we want to prefer tabular
# to tsv but it doesn't have a sniffer - is TSV was sniffed just check
diff --git a/lib/galaxy/datatypes/tabular.py b/lib/galaxy/datatypes/tabular.py
index 234a436a648..7858d28a638 100644
--- a/lib/galaxy/datatypes/tabular.py
+++ b/lib/galaxy/datatypes/tabular.py
@@ -62,7 +62,7 @@ class TabularData(data.Text):
and dataset.state == dataset.states.OK \
and dataset.metadata.columns > 0 \
and dataset.metadata.data_lines != 0
- except:
+ except Exception:
return False
def get_chunk(self, trans, dataset, offset=0, ck_size=None):
@@ -156,7 +156,7 @@ class TabularData(data.Text):
if isinstance(spec.param, metadata.ColumnParameter):
try:
i = int(getattr(dataset.metadata, name)) - 1
- except:
+ except Exception:
i = -1
if 0 <= i < columns and column_headers[i] is None:
column_headers[i] = column_parameter_alias.get(name, name)
@@ -294,14 +294,14 @@ class Tabular(TabularData):
try:
int(column_text)
return True
- except:
+ except ValueError:
return False
def is_float(column_text):
try:
float(column_text)
return True
- except:
+ except ValueError:
if column_text.strip().lower() == 'na':
return True # na is special cased to be a float
return False
@@ -488,7 +488,7 @@ class Sam(Tabular):
fh.close()
if count < 5 and count > 0:
return True
- except:
+ except Exception:
pass
return False
@@ -650,10 +650,10 @@ class Pileup(Tabular):
chrom = int(hdr[1])
assert chrom >= 0
assert hdr[2] in ['A', 'C', 'G', 'T', 'N', 'a', 'c', 'g', 't', 'n']
- except:
+ except Exception:
return False
return True
- except:
+ except Exception:
return False
# Dataproviders
@@ -919,14 +919,14 @@ class BaseCSV(TabularData):
try:
int(column_text)
return True
- except:
+ except ValueError:
return False
def is_float(self, column_text):
try:
float(column_text)
return True
- except:
+ except ValueError:
if column_text.strip().lower() == 'na':
return True # na is special cased to be a float
return False
@@ -990,7 +990,7 @@ class BaseCSV(TabularData):
if not csv.Sniffer().has_header(open(filename, 'r').read(self.big_peek_size)):
return False
return True
- except:
+ except Exception:
# Not readable by Python's csv using this dialect
return False
@@ -1137,7 +1137,7 @@ class ConnectivityTable(Tabular):
j += 1
i += 1
return False
- except:
+ except Exception:
return False
def get_chunk(self, trans, dataset, chunk):
diff --git a/lib/galaxy/datatypes/text.py b/lib/galaxy/datatypes/text.py
index add725de535..aca40c5e358 100644
--- a/lib/galaxy/datatypes/text.py
+++ b/lib/galaxy/datatypes/text.py
@@ -55,7 +55,7 @@ class Html(Text):
if hdr and hdr[0].lower().find('') >= 0:
return True
return False
- except:
+ except Exception:
return True
@@ -104,7 +104,7 @@ class Json(Text):
def display_peek(self, dataset):
try:
return dataset.peek
- except:
+ except Exception:
return "JSON file (%s)" % (nice_size(dataset.get_size()))
@@ -130,7 +130,7 @@ class Ipynb(Json):
return True
else:
return False
- except:
+ except Exception:
return False
def display_data(self, trans, dataset, preview=False, filename=None, to_ext=None, **kwd):
@@ -420,7 +420,7 @@ class SnpEffDb(Text):
if m:
snpeff_version = m.groups()[0] + m.groups()[1]
fh.close()
- except:
+ except Exception:
pass
return snpeff_version
@@ -465,7 +465,7 @@ class SnpEffDb(Text):
fh.write("annotations: %s\n" % ','.join(annotations))
if regulations:
fh.write("regulations: %s\n" % ','.join(regulations))
- except:
+ except Exception:
pass
diff --git a/lib/galaxy/datatypes/tracks.py b/lib/galaxy/datatypes/tracks.py
index 6468737a49f..dbb9e04eb11 100644
--- a/lib/galaxy/datatypes/tracks.py
+++ b/lib/galaxy/datatypes/tracks.py
@@ -78,7 +78,7 @@ class UCSCTrackHub(Html):
def display_peek(self, dataset):
try:
return dataset.peek
- except:
+ except Exception:
return "Track Hub structure: Visualization in UCSC Track Hub"
def sniff(self, filename):
diff --git a/lib/galaxy/datatypes/util/generic_util.py b/lib/galaxy/datatypes/util/generic_util.py
index b285070b70f..3709992361f 100644
--- a/lib/galaxy/datatypes/util/generic_util.py
+++ b/lib/galaxy/datatypes/util/generic_util.py
@@ -14,6 +14,6 @@ def count_special_lines(word, filename, invert=False):
cmd.extend([word, filename])
out = subprocess.Popen(cmd, stdout=subprocess.PIPE)
return int(out.communicate()[0].split()[0])
- except:
+ except Exception:
pass
return 0
diff --git a/lib/galaxy/dependencies/__init__.py b/lib/galaxy/dependencies/__init__.py
index 29254585c80..091841db3e0 100644
--- a/lib/galaxy/dependencies/__init__.py
+++ b/lib/galaxy/dependencies/__init__.py
@@ -63,7 +63,7 @@ class ConditionalDependencies(object):
try:
name = name.replace('-', '_').replace('.', '_')
return getattr(self, 'check_' + name)()
- except:
+ except Exception:
return False
def check_psycopg2(self):
diff --git a/lib/galaxy/jobs/__init__.py b/lib/galaxy/jobs/__init__.py
index bb412fb559d..457ed0682db 100644
--- a/lib/galaxy/jobs/__init__.py
+++ b/lib/galaxy/jobs/__init__.py
@@ -1166,7 +1166,7 @@ class JobWrapper(object, HasResourceParameters):
# TODO: After failing here, consider returning from the function.
try:
self.reclaim_ownership()
- except:
+ except Exception:
log.exception('(%s) Failed to change ownership of %s, failing' % (job.id, self.working_directory))
return self.fail(job.info, stdout=stdout, stderr=stderr, exit_code=tool_exit_code)
@@ -1316,7 +1316,7 @@ class JobWrapper(object, HasResourceParameters):
dataset.set_peek(line_count=context['line_count'], is_multi_byte=True)
else:
dataset.set_peek(line_count=context['line_count'])
- except:
+ except Exception:
if (not dataset.datatype.composite_type and dataset.dataset.is_multi_byte()) or self.tool.is_multi_byte:
dataset.set_peek(is_multi_byte=True)
else:
@@ -1467,7 +1467,7 @@ class JobWrapper(object, HasResourceParameters):
galaxy.tools.imp_exp.JobImportHistoryArchiveWrapper(self.app, self.job_id).cleanup_after_job()
if delete_files:
self.app.object_store.delete(self.get_job(), base_dir='job_work', entire_dir=True, dir_only=True, obj_dir=True)
- except:
+ except Exception:
log.exception("Unable to cleanup job %d", self.job_id)
def _collect_extra_files(self, dataset, job_working_directory):
@@ -1765,7 +1765,7 @@ class JobWrapper(object, HasResourceParameters):
if external_chown_script and job.user is not None:
try:
self._change_ownership(self.user_system_pwent[0], str(self.user_system_pwent[3]))
- except:
+ except Exception:
log.exception('(%s) Failed to change ownership of %s, making world-writable instead' % (job.id, self.working_directory))
os.chmod(self.working_directory, 0o777)
diff --git a/lib/galaxy/jobs/deferred/__init__.py b/lib/galaxy/jobs/deferred/__init__.py
index f8ab70a20a4..292fabd6483 100644
--- a/lib/galaxy/jobs/deferred/__init__.py
+++ b/lib/galaxy/jobs/deferred/__init__.py
@@ -39,7 +39,7 @@ class DeferredJobQueue(object):
module_name = 'galaxy.jobs.deferred.' + name
try:
module = __import__(module_name)
- except:
+ except ImportError:
log.exception('Deferred job plugin appears to exist but is not loadable: %s', module_name)
continue
for comp in module_name.split(".")[1:]:
@@ -76,7 +76,7 @@ class DeferredJobQueue(object):
while self.running:
try:
self.__monitor_step()
- except:
+ except Exception:
log.exception('Exception in monitor_step')
self.sleeper.sleep(1)
log.info('job queue stopped')
@@ -100,7 +100,7 @@ class DeferredJobQueue(object):
# Recovered jobs are passed in by ID
assert type(job) is int
job = self.sa_session.query(model.DeferredJob).get(job)
- except:
+ except Exception:
pass
if job.is_check_time:
try:
diff --git a/lib/galaxy/jobs/handler.py b/lib/galaxy/jobs/handler.py
index 9818cdf7ba7..2acb8b15f85 100644
--- a/lib/galaxy/jobs/handler.py
+++ b/lib/galaxy/jobs/handler.py
@@ -1,20 +1,33 @@
"""
Galaxy job handler, prepares, runs, tracks, and finishes Galaxy jobs
"""
-
import datetime
-import os
-import time
import logging
+import os
import threading
-from Queue import Queue, Empty
+import time
+from Queue import (
+ Empty,
+ Queue
+)
-from sqlalchemy.sql.expression import and_, or_, select, func, true, null
+from sqlalchemy.sql.expression import (
+ and_,
+ func,
+ null,
+ or_,
+ select,
+ true
+)
from galaxy import model
-from galaxy.util.sleeper import Sleeper
-from galaxy.jobs import JobWrapper, TaskWrapper, JobDestination
+from galaxy.jobs import (
+ JobDestination,
+ JobWrapper,
+ TaskWrapper
+)
from galaxy.jobs.mapper import JobNotReadyException
+from galaxy.util.sleeper import Sleeper
log = logging.getLogger(__name__)
@@ -196,7 +209,7 @@ class JobHandlerQueue(object):
# to the sleep.
if not self.app.job_manager.job_lock:
self.__monitor_step()
- except:
+ except Exception:
log.exception("Exception in monitor_step")
# Sleep
self.sleeper.sleep(1)
@@ -703,7 +716,7 @@ class JobHandlerStopQueue(object):
while self.running:
try:
self.monitor_step()
- except:
+ except Exception:
log.exception("Exception in monitor_step")
# Sleep
self.sleeper.sleep(1)
diff --git a/lib/galaxy/jobs/output_checker.py b/lib/galaxy/jobs/output_checker.py
index 14072957a65..7cd5549c58a 100644
--- a/lib/galaxy/jobs/output_checker.py
+++ b/lib/galaxy/jobs/output_checker.py
@@ -1,9 +1,9 @@
import re
-from .error_level import StdioErrorLevel
import traceback
-
from logging import getLogger
+from .error_level import StdioErrorLevel
+
log = getLogger(__name__)
@@ -126,7 +126,7 @@ def check_output(tool, stdout, stderr, tool_exit_code, job):
success = True
# On any exception, return True.
- except:
+ except Exception:
tb = traceback.format_exc()
log.warning("Tool check encountered unexpected exception; " +
"assuming tool was successful: " + tb)
diff --git a/lib/galaxy/jobs/runners/__init__.py b/lib/galaxy/jobs/runners/__init__.py
index dfbe125fcf6..675d7ba484b 100644
--- a/lib/galaxy/jobs/runners/__init__.py
+++ b/lib/galaxy/jobs/runners/__init__.py
@@ -1,28 +1,34 @@
"""
Base classes for job runner plugins.
"""
-
-import os
-import time
-import string
-import logging
import datetime
-import threading
+import logging
+import os
+import string
import subprocess
-
-from Queue import Queue, Empty
+import threading
+import time
+from Queue import (
+ Empty,
+ Queue
+)
import galaxy.jobs
-from galaxy.jobs.command_factory import build_command
from galaxy import model
-from galaxy.util import DATABASE_MAX_STRING_SIZE, shrink_stream_by_size
-from galaxy.util import in_directory
-from galaxy.util import ParamsWithSpecs
-from galaxy.util import ExecutionTimer
-from galaxy.util.bunch import Bunch
-from galaxy.jobs.runners.util.job_script import write_script
-from galaxy.jobs.runners.util.job_script import job_script
+from galaxy.jobs.command_factory import build_command
from galaxy.jobs.runners.util.env import env_to_statement
+from galaxy.jobs.runners.util.job_script import (
+ job_script,
+ write_script
+)
+from galaxy.util import (
+ DATABASE_MAX_STRING_SIZE,
+ ExecutionTimer,
+ in_directory,
+ ParamsWithSpecs,
+ shrink_stream_by_size
+)
+from galaxy.util.bunch import Bunch
from .state_handler_factory import build_state_handlers
@@ -94,15 +100,15 @@ class BaseJobRunner(object):
else:
# arg should be a JobWrapper/TaskWrapper
job_id = arg.get_id_tag()
- except:
+ except Exception:
job_id = 'unknown'
try:
name = method.__name__
- except:
+ except Exception:
name = 'unknown'
try:
method(arg)
- except:
+ except Exception:
log.exception("(%s) Unhandled exception calling %s" % (job_id, name))
# Causes a runner's `queue_job` method to be called from a worker thread
@@ -366,7 +372,7 @@ class BaseJobRunner(object):
handler(self.app, self, job_state)
if job_state.runner_state_handled:
break
- except:
+ except Exception:
log.exception('Caught exception in runner state handler')
def fail_job(self, job_state, exception=False):
@@ -610,14 +616,14 @@ class AsynchronousJobRunner(BaseJobRunner):
try:
# This should be an 8-bit exit code, but read ahead anyway:
exit_code_str = open(job_state.exit_code_file, "r").read(32)
- except:
+ except Exception:
# By default, the exit code is 0, which typically indicates success.
exit_code_str = "0"
try:
# Decode the exit code. If it's bogus, then just use 0.
exit_code = int(exit_code_str)
- except:
+ except ValueError:
log.warning("(%s/%s) Exit code '%s' invalid. Using 0." % (galaxy_id_tag, external_job_id, exit_code_str))
exit_code = 0
@@ -628,7 +634,7 @@ class AsynchronousJobRunner(BaseJobRunner):
try:
job_state.job_wrapper.finish(stdout, stderr, exit_code)
- except:
+ except Exception:
log.exception("(%s/%s) Job wrapper finish method failed" % (galaxy_id_tag, external_job_id))
job_state.job_wrapper.fail("Unable to finish job", exception=True)
diff --git a/lib/galaxy/jobs/runners/cli.py b/lib/galaxy/jobs/runners/cli.py
index c7e3cea179b..107dea18907 100644
--- a/lib/galaxy/jobs/runners/cli.py
+++ b/lib/galaxy/jobs/runners/cli.py
@@ -82,7 +82,7 @@ class ShellJobRunner(AsynchronousJobRunner):
try:
self.write_executable_script(ajs.job_file, script)
- except:
+ except Exception:
log.exception("(%s) failure writing job script" % galaxy_id_tag)
job_wrapper.fail("failure preparing job script", exception=True)
return
diff --git a/lib/galaxy/jobs/runners/condor.py b/lib/galaxy/jobs/runners/condor.py
index db3c821398f..5217034c7f6 100644
--- a/lib/galaxy/jobs/runners/condor.py
+++ b/lib/galaxy/jobs/runners/condor.py
@@ -105,7 +105,7 @@ class CondorJobRunner(AsynchronousJobRunner):
)
try:
self.write_executable_script(executable, script)
- except:
+ except Exception:
job_wrapper.fail("failure preparing job script", exception=True)
log.exception("(%s) failure preparing job script" % galaxy_id_tag)
return
diff --git a/lib/galaxy/jobs/runners/drmaa.py b/lib/galaxy/jobs/runners/drmaa.py
index 416c46f1578..c0df4839c16 100644
--- a/lib/galaxy/jobs/runners/drmaa.py
+++ b/lib/galaxy/jobs/runners/drmaa.py
@@ -110,7 +110,7 @@ class DRMAAJobRunner(AsynchronousJobRunner):
"""Get any native DRM arguments specified by the site configuration"""
try:
return url.split('/')[2] or None
- except:
+ except Exception:
return None
def queue_job(self, job_wrapper):
@@ -151,7 +151,7 @@ class DRMAAJobRunner(AsynchronousJobRunner):
script = self.get_job_file(job_wrapper, exit_code_path=ajs.exit_code_file)
try:
self.write_executable_script(ajs.job_file, script)
- except:
+ except Exception:
job_wrapper.fail("failure preparing job script", exception=True)
log.exception("(%s) failure writing job script" % galaxy_id_tag)
return
@@ -183,7 +183,7 @@ class DRMAAJobRunner(AsynchronousJobRunner):
log.warning('(%s) drmaa.Session.runJob() failed, will retry: %s', galaxy_id_tag, e)
fail_msg = "Unable to run this job due to a cluster error, please retry it later"
time.sleep(5)
- except:
+ except Exception:
log.exception('(%s) drmaa.Session.runJob() failed unconditionally', galaxy_id_tag)
trynum = 5
else:
diff --git a/lib/galaxy/jobs/runners/godocker.py b/lib/galaxy/jobs/runners/godocker.py
index f15774a6dd0..387c6436261 100644
--- a/lib/galaxy/jobs/runners/godocker.py
+++ b/lib/galaxy/jobs/runners/godocker.py
@@ -336,16 +336,16 @@ class GodockerJobRunner(AsynchronousJobRunner):
job_destination = job_wrapper.job_destination
try:
docker_cpu = int(job_destination.params["docker_cpu"])
- except:
+ except Exception:
docker_cpu = 1
try:
docker_ram = int(job_destination.params["docker_memory"])
- except:
+ except Exception:
docker_ram = 1
try:
docker_image = self._find_container(job_wrapper).container_id
log.debug("GoDocker runner using container %s.", docker_image)
- except:
+ except Exception:
log.error("Unable to find docker_image for job %s, failing." % job_wrapper.job_id)
return False
@@ -367,7 +367,7 @@ class GodockerJobRunner(AsynchronousJobRunner):
for i in volume:
temp = dict({"name": i})
volumes.append(temp)
- except:
+ except Exception:
log.debug("godocker_volume not set, using default.")
dt = datetime.now()
@@ -379,7 +379,7 @@ class GodockerJobRunner(AsynchronousJobRunner):
command = "#!/bin/bash\n" + "cd " + job_wrapper.working_directory + "\n" + venv + "\n" + job_wrapper.runner_command_line
else:
command = "#!/bin/bash\n" + "cd " + job_wrapper.working_directory + "\n" + job_wrapper.runner_command_line
- except:
+ except Exception:
command = "#!/bin/bash\n" + "cd " + job_wrapper.working_directory + "\n" + job_wrapper.runner_command_line
# GoDocker Job model schema
diff --git a/lib/galaxy/jobs/runners/kubernetes.py b/lib/galaxy/jobs/runners/kubernetes.py
index b6027437044..1c5e899a1a6 100644
--- a/lib/galaxy/jobs/runners/kubernetes.py
+++ b/lib/galaxy/jobs/runners/kubernetes.py
@@ -131,7 +131,7 @@ class KubernetesJobRunner(AsynchronousJobRunner):
if "k8s_supplemental_group_id" in self.runner_params:
try:
return int(self.runner_params["k8s_supplemental_group_id"])
- except:
+ except Exception:
log.warning("Supplemental group passed for Kubernetes runner needs to be an integer, value "
+ self.runner_params["k8s_supplemental_group_id"] + " passed is invalid")
return None
@@ -141,7 +141,7 @@ class KubernetesJobRunner(AsynchronousJobRunner):
if "k8s_fs_group_id" in self.runner_params:
try:
return int(self.runner_params["k8s_fs_group_id"])
- except:
+ except Exception:
log.warning("FS group passed for Kubernetes runner needs to be an integer, value "
+ self.runner_params["k8s_fs_group_id"] + " passed is invalid")
return None
diff --git a/lib/galaxy/jobs/runners/local.py b/lib/galaxy/jobs/runners/local.py
index aed42306a4a..14dd9be9186 100644
--- a/lib/galaxy/jobs/runners/local.py
+++ b/lib/galaxy/jobs/runners/local.py
@@ -128,7 +128,7 @@ class LocalJobRunner(BaseJobRunner):
# Finish the job!
try:
job_wrapper.finish(stdout, stderr, exit_code)
- except:
+ except Exception:
log.exception("Job wrapper finish method failed")
self._fail_job_local(job_wrapper, "Unable to finish job")
diff --git a/lib/galaxy/jobs/runners/pbs.py b/lib/galaxy/jobs/runners/pbs.py
index 9961717b660..a3ea473d37f 100644
--- a/lib/galaxy/jobs/runners/pbs.py
+++ b/lib/galaxy/jobs/runners/pbs.py
@@ -143,7 +143,7 @@ class PBSJobRunner(AsynchronousJobRunner):
# stripping the - comes later (in parse_destination_params)
for i, opt in enumerate(opts):
opts[i] = '-' + opt
- except:
+ except Exception:
opts = []
for opt in opts:
param, value = opt.split(None, 1)
@@ -168,7 +168,7 @@ class PBSJobRunner(AsynchronousJobRunner):
arg = PBS_ARGMAP[arg]
arg = arg.lstrip('-')
args[arg] = value
- except:
+ except Exception:
log.warning('Unrecognized long argument in destination params: %s' % arg)
return self.__args_to_attrs(args)
@@ -371,7 +371,7 @@ class PBSJobRunner(AsynchronousJobRunner):
self.check_single_job(pbs_server_name, job_id)
log.warning("(%s/%s) PBS job was not in state check list, but was found with individual state check" % (galaxy_job_id, job_id))
new_watched.append(pbs_job_state)
- except:
+ except Exception:
errno, text = pbs.error()
if errno == 15001:
# 15001 == job not in queue
@@ -525,7 +525,7 @@ class PBSJobRunner(AsynchronousJobRunner):
pbs.pbs_deljob(c, job_id, '')
log.debug("%s Removed from PBS queue before job completion"
% job_tag)
- except:
+ except Exception:
e = traceback.format_exc()
log.debug("%s Unable to stop job: %s" % (job_tag, e))
finally:
diff --git a/lib/galaxy/jobs/runners/slurm.py b/lib/galaxy/jobs/runners/slurm.py
index 1b9ca47e59b..f6a5fb7d808 100644
--- a/lib/galaxy/jobs/runners/slurm.py
+++ b/lib/galaxy/jobs/runners/slurm.py
@@ -104,7 +104,7 @@ class SlurmJobRunner(DRMAAJobRunner):
try:
self.queue_job(ajs.job_wrapper)
return
- except:
+ except Exception:
ajs.fail_message = "This job failed due to a cluster node failure, and an attempt to resubmit the job failed."
elif slurm_state == 'CANCELLED':
# Check to see if the job was killed for exceeding memory consumption
@@ -164,7 +164,7 @@ class SlurmJobRunner(DRMAAJobRunner):
return 'This job was terminated because it used more memory than it was allocated.'
elif any(_ in stripped_line for _ in SLURM_MEMORY_LIMIT_EXCEEDED_PARTIAL_WARNINGS):
return 'This job was cancelled probably because it used more memory than it was allocated.'
- except:
+ except Exception:
log.exception('Error reading end of %s:', efile_path)
return False
diff --git a/lib/galaxy/jobs/runners/state_handler_factory.py b/lib/galaxy/jobs/runners/state_handler_factory.py
index 6f1d24ddb72..844364c1e6b 100644
--- a/lib/galaxy/jobs/runners/state_handler_factory.py
+++ b/lib/galaxy/jobs/runners/state_handler_factory.py
@@ -1,9 +1,7 @@
import logging
-from galaxy.util.submodules import submodules
-
import galaxy.jobs.runners.state_handlers
-
+from galaxy.util.submodules import submodules
log = logging.getLogger(__name__)
diff --git a/lib/galaxy/jobs/runners/tasks.py b/lib/galaxy/jobs/runners/tasks.py
index b3685abeeab..63d29e17db7 100644
--- a/lib/galaxy/jobs/runners/tasks.py
+++ b/lib/galaxy/jobs/runners/tasks.py
@@ -52,7 +52,7 @@ class TaskedJobRunner(BaseJobRunner):
parallelism = job_wrapper.get_parallelism()
try:
splitter = getattr(__import__('galaxy.jobs.splitters', globals(), locals(), [parallelism.method]), parallelism.method)
- except:
+ except Exception:
job_wrapper.change_state(model.Job.states.ERROR)
job_wrapper.fail("Job Splitting Failed, no match for '%s'" % parallelism)
return
@@ -125,7 +125,7 @@ class TaskedJobRunner(BaseJobRunner):
# Finish the job
try:
job_wrapper.finish(stdout, stderr, job_exit_code)
- except:
+ except Exception:
log.exception("Job wrapper finish method failed")
job_wrapper.fail("Unable to finish job", exception=True)
diff --git a/lib/galaxy/jobs/runners/util/cli/job/slurm.py b/lib/galaxy/jobs/runners/util/cli/job/slurm.py
index a6a88ead1f3..9b3813ced72 100644
--- a/lib/galaxy/jobs/runners/util/cli/job/slurm.py
+++ b/lib/galaxy/jobs/runners/util/cli/job/slurm.py
@@ -41,7 +41,7 @@ class Slurm(BaseJobExec):
if not k.startswith('-'):
k = argmap[k]
scriptargs[k] = v
- except:
+ except Exception:
log.warning('Unrecognized long argument passed to Slurm CLI plugin: %s' % k)
# Generated template.
diff --git a/lib/galaxy/jobs/runners/util/cli/job/torque.py b/lib/galaxy/jobs/runners/util/cli/job/torque.py
index d4e0f49af32..2b2819dfc36 100644
--- a/lib/galaxy/jobs/runners/util/cli/job/torque.py
+++ b/lib/galaxy/jobs/runners/util/cli/job/torque.py
@@ -1,7 +1,7 @@
from logging import getLogger
try:
import xml.etree.cElementTree as et
-except:
+except ImportError:
import xml.etree.ElementTree as et
try:
diff --git a/lib/galaxy/jobs/transfer_manager.py b/lib/galaxy/jobs/transfer_manager.py
index 8e06024832c..3b85d7d5784 100644
--- a/lib/galaxy/jobs/transfer_manager.py
+++ b/lib/galaxy/jobs/transfer_manager.py
@@ -147,7 +147,7 @@ class TransferManager(object):
# restart the transfer if socket communication fails repeatedly.
try:
os.kill(tj.pid, 0)
- except:
+ except Exception:
self.sa_session.refresh(tj)
if tj.state == tj.states.RUNNING:
log.error('Transfer job %s is marked as running but pid %s appears to be dead.' % (tj.id, tj.pid))
diff --git a/lib/galaxy/managers/context.py b/lib/galaxy/managers/context.py
index 291cddc5b91..0bb24625b37 100644
--- a/lib/galaxy/managers/context.py
+++ b/lib/galaxy/managers/context.py
@@ -27,11 +27,11 @@ class ProvidesAppContext(object):
action.user = user
else:
action.user = self.user
- except:
+ except Exception:
action.user = None
try:
action.session_id = self.galaxy_session.id
- except:
+ except Exception:
action.session_id = None
self.sa_session.add(action)
self.sa_session.flush()
@@ -46,23 +46,23 @@ class ProvidesAppContext(object):
event.tool_id = tool_id
try:
event.message = message % kwargs
- except:
+ except Exception:
event.message = message
try:
event.history = self.get_history()
- except:
+ except Exception:
event.history = None
try:
event.history_id = self.history.id
- except:
+ except Exception:
event.history_id = None
try:
event.user = self.user
- except:
+ except Exception:
event.user = None
try:
event.session_id = self.galaxy_session.id
- except:
+ except Exception:
event.session_id = None
self.sa_session.add(event)
self.sa_session.flush()
diff --git a/lib/galaxy/managers/folders.py b/lib/galaxy/managers/folders.py
index d681e6a78da..506518b0b2e 100644
--- a/lib/galaxy/managers/folders.py
+++ b/lib/galaxy/managers/folders.py
@@ -132,7 +132,7 @@ class FolderManager(object):
"""
parent_folder = self.get(trans, parent_folder_id)
current_user_roles = trans.get_current_user_roles()
- if not (trans.user_is_admin or trans.app.security_agent.can_add_library_item(current_user_roles, parent_folder)):
+ if not (trans.user_is_admin() or trans.app.security_agent.can_add_library_item(current_user_roles, parent_folder)):
raise InsufficientPermissionsException('You do not have proper permission to create folders under given folder.')
new_folder = trans.app.model.LibraryFolder(name=new_folder_name, description=new_folder_description)
# We are associating the last used genome build with folders, so we will always
@@ -230,7 +230,7 @@ class FolderManager(object):
"""
Return true if the user has permissions to add item to the given folder.
"""
- if trans.user_is_admin:
+ if trans.user_is_admin():
return True
current_user_roles = trans.get_current_user_roles()
add_roles = set(trans.app.security_agent.get_roles_for_action(folder, trans.app.security_agent.permitted_actions.LIBRARY_ADD))
diff --git a/lib/galaxy/managers/libraries.py b/lib/galaxy/managers/libraries.py
index b94217ee53f..ed7ef58c39b 100644
--- a/lib/galaxy/managers/libraries.py
+++ b/lib/galaxy/managers/libraries.py
@@ -50,7 +50,7 @@ class LibraryManager(object):
"""
Create a new library.
"""
- if not trans.user_is_admin:
+ if not trans.user_is_admin():
raise exceptions.ItemAccessibilityException('Only administrators can create libraries.')
else:
library = trans.app.model.Library(name=name, description=description, synopsis=synopsis)
diff --git a/lib/galaxy/managers/library_datasets.py b/lib/galaxy/managers/library_datasets.py
index 20ce8c3789a..115f3cb66e8 100644
--- a/lib/galaxy/managers/library_datasets.py
+++ b/lib/galaxy/managers/library_datasets.py
@@ -195,17 +195,26 @@ class LibraryDatasetsManager(object):
if len(expired_ldda_versions) > 0:
rval['has_versions'] = True
rval['expired_versions'] = expired_ldda_versions
+
+ ldda = ld.library_dataset_dataset_association
+ if ldda.creating_job_associations:
+ if ldda.creating_job_associations[0].job.stdout:
+ rval['job_stdout'] = ldda.creating_job_associations[0].job.stdout.strip()
+ if ldda.creating_job_associations[0].job.stderr:
+ rval['job_stderr'] = ldda.creating_job_associations[0].job.stderr.strip()
+ if ldda.dataset.uuid:
+ rval['uuid'] = str(ldda.dataset.uuid)
rval['deleted'] = ld.deleted
rval['folder_id'] = 'F' + rval['folder_id']
rval['full_path'] = full_path
- rval['file_size'] = util.nice_size(int(ld.library_dataset_dataset_association.get_size()))
- rval['date_uploaded'] = ld.library_dataset_dataset_association.create_time.strftime("%Y-%m-%d %I:%M %p")
- rval['can_user_modify'] = trans.app.security_agent.can_modify_library_item(current_user_roles, ld) or trans.user_is_admin()
- rval['is_unrestricted'] = trans.app.security_agent.dataset_is_public(ld.library_dataset_dataset_association.dataset)
- rval['tags'] = self.tag_manager.get_tags_str(ld.library_dataset_dataset_association.tags)
+ rval['file_size'] = util.nice_size(int(ldda.get_size()))
+ rval['date_uploaded'] = ldda.create_time.strftime("%Y-%m-%d %I:%M %p")
+ rval['can_user_modify'] = trans.user_is_admin() or trans.app.security_agent.can_modify_library_item(current_user_roles, ld)
+ rval['is_unrestricted'] = trans.app.security_agent.dataset_is_public(ldda.dataset)
+ rval['tags'] = self.tag_manager.get_tags_str(ldda.tags)
# Manage dataset permission is always attached to the dataset itself, not the the ld or ldda to maintain consistency
- rval['can_user_manage'] = trans.app.security_agent.can_manage_dataset(current_user_roles, ld.library_dataset_dataset_association.dataset) or trans.user_is_admin()
+ rval['can_user_manage'] = trans.user_is_admin() or trans.app.security_agent.can_manage_dataset(current_user_roles, ldda.dataset)
return rval
def _build_path(self, trans, folder):
diff --git a/lib/galaxy/managers/users.py b/lib/galaxy/managers/users.py
index 149836db847..a1b4009f4c5 100644
--- a/lib/galaxy/managers/users.py
+++ b/lib/galaxy/managers/users.py
@@ -233,14 +233,6 @@ 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, user, trans):
- """
- """
- if self.is_anonymous(user):
- return False
- request_types = self.app.security_agent.get_accessible_request_types(trans, user)
- return bool(user.requests or request_types)
-
class UserSerializer(base.ModelSerializer, deletable.PurgableSerializerMixin):
model_manager_class = UserManager
@@ -292,7 +284,6 @@ class UserSerializer(base.ModelSerializer, deletable.PurgableSerializerMixin):
'quota' : lambda i, k, **c: self.user_manager.quota(i, total=True),
'tags_used' : lambda i, k, **c: self.user_manager.tags_used(i),
- 'has_requests' : lambda i, k, trans=None, **c: self.user_manager.has_requests(i, trans)
})
diff --git a/lib/galaxy/model/__init__.py b/lib/galaxy/model/__init__.py
index 587b6049125..b680c4b7538 100644
--- a/lib/galaxy/model/__init__.py
+++ b/lib/galaxy/model/__init__.py
@@ -1172,7 +1172,7 @@ class DeferredJob(object):
def set_last_check(self, seconds):
try:
self._last_check = int(seconds)
- except:
+ except ValueError:
self._last_check = time.time()
last_check = property(get_last_check, set_last_check)
@@ -4324,7 +4324,7 @@ class FormDefinition(object, Dictifiable):
try:
# This field has a saved value.
value = str(contents[field['name']])
- except:
+ except Exception:
# If there was an error getting the saved value, we'll still
# display the widget, but it will be empty.
if field_type == 'AddressField':
diff --git a/lib/galaxy/model/custom_types.py b/lib/galaxy/model/custom_types.py
index d5065420898..33cecd8371b 100644
--- a/lib/galaxy/model/custom_types.py
+++ b/lib/galaxy/model/custom_types.py
@@ -291,10 +291,10 @@ class MetadataType(JSONType):
ret = metadata_pickler.loads(str(value))
if ret:
ret = dict(ret.__dict__)
- except:
+ except Exception:
try:
ret = json_decoder.decode(str(_sniffnfix_pg9_hex(value)))
- except:
+ except Exception:
ret = None
return ret
diff --git a/lib/galaxy/model/mapping.py b/lib/galaxy/model/mapping.py
index b9b8c184265..97923d892cc 100644
--- a/lib/galaxy/model/mapping.py
+++ b/lib/galaxy/model/mapping.py
@@ -2534,7 +2534,7 @@ def db_next_hid(self, n=1):
table.update(table.c.id == self.id).execute(hid_counter=(next_hid + n))
trans.commit()
return next_hid
- except:
+ except Exception:
trans.rollback()
raise
diff --git a/lib/galaxy/model/metadata.py b/lib/galaxy/model/metadata.py
index fa6e9b3537a..1cb3e508e3d 100644
--- a/lib/galaxy/model/metadata.py
+++ b/lib/galaxy/model/metadata.py
@@ -91,7 +91,7 @@ class MetadataCollection(object):
def get(self, key, default=None):
try:
return self.__getattr__(key) or default
- except:
+ except Exception:
return default
def items(self):
diff --git a/lib/galaxy/model/migrate/check.py b/lib/galaxy/model/migrate/check.py
index 9674b75c0cb..5a1729b1596 100644
--- a/lib/galaxy/model/migrate/check.py
+++ b/lib/galaxy/model/migrate/check.py
@@ -36,7 +36,7 @@ def create_or_verify_database(url, galaxy_config_file, engine_options={}, app=No
try:
# Declare the database to be under a repository's version control
db_schema = schema.ControlledSchema.create(engine, migrate_repository)
- except:
+ except Exception:
# The database is already under version control
db_schema = schema.ControlledSchema(engine, migrate_repository)
# Apply all scripts to get to current version
diff --git a/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py b/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py
index 485a9904d07..4a86c22d983 100644
--- a/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py
+++ b/lib/galaxy/model/migrate/versions/0005_cleanup_datasets_fix.py
@@ -692,7 +692,7 @@ def __guess_dataset_by_filename(filename):
if fields:
if fields[-1].startswith('dataset_') and fields[-1].endswith('.dat'): # dataset_%d.dat
return Dataset.get(int(fields[-1][len('dataset_'): -len('.dat')]))
- except:
+ except Exception:
pass # some parsing error, we can't guess Dataset
return None
diff --git a/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py b/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py
index 68c526937c8..dd19c219f12 100644
--- a/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py
+++ b/lib/galaxy/model/migrate/versions/0024_page_slug_unique_constraint.py
@@ -29,7 +29,7 @@ def upgrade(migrate_engine):
i = Index("ix_page_slug", Page_table.c.slug, unique=False)
i.create()
- except:
+ except Exception:
# Mysql doesn't have a named index, but alter should work
diff --git a/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py b/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py
index a7ea25d6943..19c833666bc 100644
--- a/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py
+++ b/lib/galaxy/model/migrate/versions/0032_stored_workflow_slug_column.py
@@ -30,7 +30,7 @@ def upgrade(migrate_engine):
try:
i = Index("ix_stored_workflow_slug", StoredWorkflow_table.c.slug, mysql_length=200)
i.create()
- except:
+ except Exception:
# Mysql doesn't have a named index, but alter should work
StoredWorkflow_table.c.slug.alter(unique=False)
diff --git a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py
index 7e1f8e306c0..119f9f7b452 100644
--- a/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py
+++ b/lib/galaxy/model/migrate/versions/0033_published_cols_for_histories_and_workflows.py
@@ -31,7 +31,7 @@ def upgrade(migrate_engine):
try:
i = Index("ix_history_published", History_table.c.published)
i.create()
- except:
+ except Exception:
# Mysql doesn't have a named index, but alter should work
History_table.c.published.alter(unique=False)
@@ -49,7 +49,7 @@ def upgrade(migrate_engine):
try:
i = Index("ix_stored_workflow_published", StoredWorkflow_table.c.published)
i.create()
- except:
+ except Exception:
# Mysql doesn't have a named index, but alter should work
StoredWorkflow_table.c.published.alter(unique=False)
@@ -67,7 +67,7 @@ def upgrade(migrate_engine):
try:
i = Index("ix_page_importable", Page_table.c.importable)
i.create()
- except:
+ except Exception:
# Mysql doesn't have a named index, but alter should work
Page_table.c.importable.alter(unique=False)
diff --git a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py
index 1060e45e445..7ded92a29a3 100644
--- a/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py
+++ b/lib/galaxy/model/migrate/versions/0041_workflow_invocation.py
@@ -38,7 +38,7 @@ def upgrade(migrate_engine):
for table in tables:
try:
table.create()
- except:
+ except Exception:
log.warning("Failed to create table '%s', ignoring (might result in wrong schema)" % table.name)
diff --git a/lib/galaxy/model/migrate/versions/0042_workflow_invocation_fix.py b/lib/galaxy/model/migrate/versions/0042_workflow_invocation_fix.py
index 88ae7c478db..bfa0eb3ce80 100644
--- a/lib/galaxy/model/migrate/versions/0042_workflow_invocation_fix.py
+++ b/lib/galaxy/model/migrate/versions/0042_workflow_invocation_fix.py
@@ -25,7 +25,7 @@ def upgrade(migrate_engine):
t = Table(table_name, metadata, autoload=True)
t.drop()
metadata.remove(t)
- except:
+ except Exception:
log.exception("Failed to drop table '%s', ignoring (might result in wrong schema)" % table_name)
# 2) Readd
@@ -46,7 +46,7 @@ def upgrade(migrate_engine):
for table in [WorkflowInvocation_table, WorkflowInvocationStep_table]:
try:
table.create()
- except:
+ except Exception:
log.exception("Failed to create table '%s', ignoring (might result in wrong schema)" % table.name)
diff --git a/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py b/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py
index 9b354080ec2..a688dd18571 100644
--- a/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py
+++ b/lib/galaxy/model/migrate/versions/0056_workflow_outputs.py
@@ -27,7 +27,7 @@ def upgrade(migrate_engine):
for table in tables:
try:
table.create()
- except:
+ except Exception:
log.warning("Failed to create table '%s', ignoring (might result in wrong schema)" % table.name)
diff --git a/lib/galaxy/model/migrate/versions/0061_tasks.py b/lib/galaxy/model/migrate/versions/0061_tasks.py
index 6a067ee0ca0..eaf535bc0eb 100644
--- a/lib/galaxy/model/migrate/versions/0061_tasks.py
+++ b/lib/galaxy/model/migrate/versions/0061_tasks.py
@@ -39,7 +39,7 @@ def upgrade(migrate_engine):
for table in tables:
try:
table.create()
- except:
+ except Exception:
log.warning("Failed to create table '%s', ignoring (might result in wrong schema)" % table.name)
diff --git a/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py b/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py
index 2b689e12ec9..c2d42a4502f 100644
--- a/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py
+++ b/lib/galaxy/model/migrate/versions/0076_fix_form_values_data_corruption.py
@@ -39,7 +39,7 @@ def upgrade(migrate_engine):
# Encoding errors? Just to be safe.
print("Attempting to fix row %s" % row['id'])
print("Prior to replacement: %s" % field_values_str)
- except:
+ except Exception:
pass
field_values_dict = {}
# look for each field name in the values and extract its value (string)
@@ -77,7 +77,7 @@ def upgrade(migrate_engine):
migrate_engine.execute(cmd)
try:
print("Post replacement: %s" % json_values)
- except:
+ except Exception:
pass
if corrupted_rows:
print('Fixed %i corrupted rows.' % corrupted_rows)
diff --git a/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py b/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py
index 9e6f25a424c..c30f63f7189 100644
--- a/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py
+++ b/lib/galaxy/model/migrate/versions/0115_longer_user_password_field.py
@@ -13,7 +13,7 @@ def upgrade(migrate_engine):
user = Table('galaxy_user', meta, autoload=True)
try:
user.c.password.alter(type=String(255))
- except:
+ except Exception:
log.exception("Altering password column failed")
diff --git a/lib/galaxy/model/tool_shed_install/migrate/check.py b/lib/galaxy/model/tool_shed_install/migrate/check.py
index 36e40483c8d..7773146965e 100644
--- a/lib/galaxy/model/tool_shed_install/migrate/check.py
+++ b/lib/galaxy/model/tool_shed_install/migrate/check.py
@@ -30,7 +30,7 @@ def create_or_verify_database(url, engine_options={}, app=None):
try:
# Declare the database to be under a repository's version control
db_schema = schema.ControlledSchema.create(engine, migrate_repository)
- except:
+ except Exception:
# The database is already under version control
db_schema = schema.ControlledSchema(engine, migrate_repository)
# Apply all scripts to get to current version
diff --git a/lib/galaxy/sample_tracking/external_service_types.py b/lib/galaxy/sample_tracking/external_service_types.py
index a1b04b88005..1bd4e50be42 100644
--- a/lib/galaxy/sample_tracking/external_service_types.py
+++ b/lib/galaxy/sample_tracking/external_service_types.py
@@ -1,10 +1,15 @@
-import os
import logging
-from galaxy.util.odict import odict
-from galaxy import util, model
-from galaxy.forms.forms import form_factory
+import os
+
+from galaxy import (
+ model,
+ util
+)
from galaxy.external_services.service import ExternalServiceActionsGroup
+from galaxy.forms.forms import form_factory
from galaxy.sample_tracking.data_transfer import data_transfer_factories
+from galaxy.util.odict import odict
+
log = logging.getLogger(__name__)
@@ -20,7 +25,7 @@ class ExternalServiceTypesCollection(object):
self.app = app
try:
self.load_all(config_filename)
- except:
+ except Exception:
log.exception("ExternalServiceTypesCollection error reading %s", config_filename)
def load_all(self, config_filename):
@@ -37,7 +42,7 @@ class ExternalServiceTypesCollection(object):
log.debug("Loaded external_service_type: %s %s" % (external_service_type.name, external_service_type.config_version))
if visible:
self.visible_external_service_types.append(external_service_type.id)
- except:
+ except Exception:
log.exception("error reading external_service_type from path: %s", file_path)
def load_external_service_type(self, config_file, visible=True):
diff --git a/lib/galaxy/sample_tracking/request_types.py b/lib/galaxy/sample_tracking/request_types.py
index 6ca651f3b22..dd05dd34786 100644
--- a/lib/galaxy/sample_tracking/request_types.py
+++ b/lib/galaxy/sample_tracking/request_types.py
@@ -2,7 +2,8 @@
RequestType
"""
from galaxy.model import RequestType
-from sample import sample_state_factory
+
+from .sample import sample_state_factory
RENAME_DATASET_OPTIONS = dict([(f_type.lower(), f_descript) for f_type, f_descript in RequestType.rename_dataset_options.items()])
diff --git a/lib/galaxy/tools/__init__.py b/lib/galaxy/tools/__init__.py
index efe64712c45..1c607ada3ad 100755
--- a/lib/galaxy/tools/__init__.py
+++ b/lib/galaxy/tools/__init__.py
@@ -794,7 +794,7 @@ class Tool(object, Dictifiable):
if tests_source:
try:
self.__tests = parse_tests(self, tests_source)
- except:
+ except Exception:
self.__tests = None
log.exception("Failed to parse tool tests")
else:
@@ -1011,7 +1011,7 @@ class Tool(object, Dictifiable):
group.cases.append(case)
try:
possible_cases.remove(case.value)
- except:
+ except Exception:
log.warning("Tool %s: a when tag has been defined for '%s (%s) --> %s', but does not appear to be selectable." %
(self.id, group.name, group.test_param.name, case.value))
for unspecified_case in possible_cases:
@@ -1131,8 +1131,8 @@ class Tool(object, Dictifiable):
self.__help = Template(rst_to_html(help_text), input_encoding='utf-8',
output_encoding='utf-8', default_filters=['decode.utf8'],
encoding_errors='replace')
- except:
- log.exception("error in help for tool %s", self.name)
+ except Exception:
+ log.exception("Exception while parsing help for tool with id '%s'", self.id)
# Handle deprecated multi-page help text in XML case.
if hasattr(tool_source, "root"):
@@ -1151,8 +1151,8 @@ class Tool(object, Dictifiable):
default_filters=['decode.utf8'],
encoding_errors='replace')
for x in self.__help_by_page]
- except:
- log.exception("error in multi-page help for tool %s", self.name)
+ except Exception:
+ log.exception("Exception while parsing multi-page help for tool with id '%s'", self.id)
# Pad out help pages to match npages ... could this be done better?
while len(self.__help_by_page) < self.npages:
self.__help_by_page.append(self.__help)
@@ -1422,7 +1422,7 @@ class Tool(object, Dictifiable):
if not prefixed_name.startswith('__'):
messages[prefixed_name] = error if previous_value == value else '%s Using default: \'%s\'.' % (error, value)
parent[input.name] = value
- except:
+ except Exception:
messages[prefixed_name] = 'Attempt to replace invalid value for \'%s\' failed.' % (prefixed_label)
else:
messages[prefixed_name] = error
diff --git a/lib/galaxy/tools/actions/__init__.py b/lib/galaxy/tools/actions/__init__.py
index a71581552f4..cc47db76e62 100644
--- a/lib/galaxy/tools/actions/__init__.py
+++ b/lib/galaxy/tools/actions/__init__.py
@@ -542,7 +542,7 @@ class DefaultToolAction(object):
try:
# For backward compatibility, some tools may not have versions yet.
job.tool_version = tool.version
- except:
+ except AttributeError:
job.tool_version = "1.0.0"
return job, galaxy_session
@@ -837,7 +837,7 @@ def determine_output_format(output, parameter_context, input_datasets, input_dat
check = '${%s}' % check
if str(fill_template(check, context=parameter_context)) == when_elem.get('value', None):
ext = when_elem.get('format', ext)
- except: # bad tag input value; possibly referencing a param within a different conditional when block or other nonexistent grouping construct
+ except Exception: # bad tag input value; possibly referencing a param within a different conditional when block or other nonexistent grouping construct
continue
else:
check = when_elem.get('input_dataset', None)
diff --git a/lib/galaxy/tools/actions/metadata.py b/lib/galaxy/tools/actions/metadata.py
index 34cbcce9c7b..e58ead289ae 100644
--- a/lib/galaxy/tools/actions/metadata.py
+++ b/lib/galaxy/tools/actions/metadata.py
@@ -60,7 +60,7 @@ class SetMetadataToolAction(ToolAction):
try:
# For backward compatibility, some tools may not have versions yet.
job.tool_version = tool.version
- except:
+ except AttributeError:
job.tool_version = "1.0.1"
job.state = job.states.WAITING # we need to set job state to something other than NEW, or else when tracking jobs in db it will be picked up before we have added input / output parameters
job.set_handler(tool.get_job_handler(job_params))
diff --git a/lib/galaxy/tools/actions/upload_common.py b/lib/galaxy/tools/actions/upload_common.py
index 09d6e988a99..6025d092fe7 100644
--- a/lib/galaxy/tools/actions/upload_common.py
+++ b/lib/galaxy/tools/actions/upload_common.py
@@ -176,7 +176,7 @@ def get_precreated_datasets(trans, params, data_obj, controller='root'):
for id in async_datasets:
try:
data = trans.sa_session.query(data_obj).get(int(id))
- except:
+ except Exception:
log.exception('Unable to load precreated dataset (%s) sent in upload form' % id)
continue
if data_obj is trans.app.model.HistoryDatasetAssociation:
diff --git a/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py b/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py
index b4d197188ad..60ae6ac8329 100644
--- a/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py
+++ b/lib/galaxy/tools/deps/resolvers/unlinked_tool_shed_packages.py
@@ -54,7 +54,7 @@ class UnlinkedToolShedPackageDependencyResolver(BaseGalaxyPackageDependencyResol
else:
# Pick the preferred one
return self._select_preferred_dependency(possibles).dependency
- except:
+ except Exception:
log.exception("Unexpected error hunting for dependency '%s' '%s''%s'", name, version, type)
return NullDependency(version=version, name=name)
@@ -126,7 +126,7 @@ class UnlinkedToolShedPackageDependencyResolver(BaseGalaxyPackageDependencyResol
else:
#Pick the preferred one
return self._select_preferred_dependency(possibles, by_owner=False).dependency
- except:
+ except Exception:
log.exception("Unexpected error hunting for dependency '%s' default '%s'", name, type)
return NullDependency(version=None, name=name)
"""
diff --git a/lib/galaxy/tools/imp_exp/__init__.py b/lib/galaxy/tools/imp_exp/__init__.py
index 2389f2c87ff..a4fc924d7d4 100644
--- a/lib/galaxy/tools/imp_exp/__init__.py
+++ b/lib/galaxy/tools/imp_exp/__init__.py
@@ -244,7 +244,7 @@ class JobImportHistoryArchiveWrapper(object, UsesAnnotations):
try:
imported_job.create_time = datetime.datetime.strptime(job_attrs["create_time"], "%Y-%m-%dT%H:%M:%S.%f")
imported_job.update_time = datetime.datetime.strptime(job_attrs["update_time"], "%Y-%m-%dT%H:%M:%S.%f")
- except:
+ except Exception:
pass
self.sa_session.add(imported_job)
self.sa_session.flush()
@@ -485,7 +485,7 @@ class JobExportHistoryArchiveWrapper(object, UsesAnnotations):
# Get the job's parameters
try:
params_objects = job.get_param_values(trans.app)
- except:
+ except Exception:
# Could not get job params.
continue
diff --git a/lib/galaxy/tools/parameters/__init__.py b/lib/galaxy/tools/parameters/__init__.py
index ea3f3b826b2..c0343ff2c47 100644
--- a/lib/galaxy/tools/parameters/__init__.py
+++ b/lib/galaxy/tools/parameters/__init__.py
@@ -100,7 +100,7 @@ def visit_input_values(inputs, input_values, callback, name_prefix='', label_pre
case_error = None
try:
input.get_current_case(values[input.test_param.name])
- except:
+ except Exception:
case_error = 'The selected case is unavailable/invalid.'
pass
callback_helper(input.test_param, values, new_name_prefix, label_prefix, parent_prefix=name_prefix, context=context, error=case_error)
diff --git a/lib/galaxy/tools/parameters/basic.py b/lib/galaxy/tools/parameters/basic.py
index 0a49164fe4d..74f74e03a90 100644
--- a/lib/galaxy/tools/parameters/basic.py
+++ b/lib/galaxy/tools/parameters/basic.py
@@ -153,7 +153,7 @@ class ToolParameter(object, Dictifiable):
if ignore_errors:
try:
return self.to_python(value, app)
- except:
+ except Exception:
return value
else:
return self.to_python(value, app)
@@ -313,7 +313,7 @@ class IntegerToolParameter(TextToolParameter):
if self.value:
try:
int(self.value)
- except:
+ except ValueError:
raise ValueError("An integer is required")
elif self.value is None and not self.optional:
raise ValueError("The settings for the field named '%s' require a 'value' setting and optionally a default value which must be an integer" % self.name)
@@ -322,12 +322,12 @@ class IntegerToolParameter(TextToolParameter):
if self.min:
try:
self.min = int(self.min)
- except:
+ except ValueError:
raise ValueError("An integer is required")
if self.max:
try:
self.max = int(self.max)
- except:
+ except ValueError:
raise ValueError("An integer is required")
if self.min is not None or self.max is not None:
self.validators.append(validation.InRangeValidator(None, self.min, self.max))
@@ -335,7 +335,7 @@ class IntegerToolParameter(TextToolParameter):
def from_json(self, value, trans, other_values={}):
try:
return int(value)
- except:
+ except (TypeError, ValueError):
if contains_workflow_parameter(value) and trans.workflow_building_mode is workflow_building_modes.ENABLED:
return value
if not value and self.optional:
@@ -348,7 +348,7 @@ class IntegerToolParameter(TextToolParameter):
def to_python(self, value, app):
try:
return int(value)
- except Exception as err:
+ except (TypeError, ValueError) as err:
if contains_workflow_parameter(value):
return value
if not value and self.optional:
@@ -391,19 +391,19 @@ class FloatToolParameter(TextToolParameter):
if self.value:
try:
float(self.value)
- except:
+ except ValueError:
raise ValueError("A real number is required")
elif self.value is None and not self.optional:
raise ValueError("The settings for this field require a 'value' setting and optionally a default value which must be a real number")
if self.min:
try:
self.min = float(self.min)
- except:
+ except ValueError:
raise ValueError("A real number is required")
if self.max:
try:
self.max = float(self.max)
- except:
+ except ValueError:
raise ValueError("A real number is required")
if self.min is not None or self.max is not None:
self.validators.append(validation.InRangeValidator(None, self.min, self.max))
@@ -411,7 +411,7 @@ class FloatToolParameter(TextToolParameter):
def from_json(self, value, trans, other_values={}):
try:
return float(value)
- except:
+ except (TypeError, ValueError):
if contains_workflow_parameter(value) and trans.workflow_building_mode is workflow_building_modes.ENABLED:
return value
if not value and self.optional:
@@ -424,7 +424,7 @@ class FloatToolParameter(TextToolParameter):
def to_python(self, value, app):
try:
return float(value)
- except Exception as err:
+ except (TypeError, ValueError) as err:
if contains_workflow_parameter(value):
return value
if not value and self.optional:
@@ -434,7 +434,7 @@ class FloatToolParameter(TextToolParameter):
def get_initial_value(self, trans, other_values):
try:
return float(self.value)
- except:
+ except Exception:
return None
@@ -542,7 +542,7 @@ class FileToolParameter(ToolParameter):
# or should we jsonify?
try:
return value['local_filename']
- except:
+ except KeyError:
return None
raise Exception("FileToolParameter cannot be persisted")
@@ -912,7 +912,7 @@ class SelectToolParameter(ToolParameter):
value = options[0][1]
else:
value = None
- elif len(value) == 1:
+ elif len(value) == 1 or not self.multiple:
value = value[0]
return value
@@ -1147,7 +1147,7 @@ class ColumnListParameter(SelectToolParameter):
if len(dataset.metadata.column_types) >= len(cnames):
numerics = [i for i, x in enumerate(dataset.metadata.column_types) if x in ['int', 'float']]
column_list = [column_list[i] for i in numerics]
- except:
+ except Exception:
column_list = self.get_column_list(trans, other_values)
else:
column_list = self.get_column_list(trans, other_values)
@@ -1582,12 +1582,12 @@ class DataToolParameter(BaseDataToolParameter):
if self.min:
try:
self.min = int(self.min)
- except:
+ except ValueError:
raise ValueError("An integer is required for min property.")
if self.max:
try:
self.max = int(self.max)
- except:
+ except ValueError:
raise ValueError("An integer is required for max property.")
if not self.multiple and (self.min is not None):
raise ValueError("Cannot specify min property on single data parameter '%s'. Set multiple=\"true\" to enable this option." % self.name)
@@ -1696,7 +1696,7 @@ class DataToolParameter(BaseDataToolParameter):
if value:
try:
return ", ".join(["%s: %s" % (item.hid, item.name) for item in value])
- except:
+ except Exception:
pass
return "No dataset."
diff --git a/lib/galaxy/tools/parameters/dynamic_options.py b/lib/galaxy/tools/parameters/dynamic_options.py
index aa5220db301..514f02b1feb 100644
--- a/lib/galaxy/tools/parameters/dynamic_options.py
+++ b/lib/galaxy/tools/parameters/dynamic_options.py
@@ -72,7 +72,7 @@ class StaticValueFilter(Filter):
filter_value = self.value
try:
filter_value = User.expand_user_properties(trans.user, filter_value)
- except:
+ except Exception:
pass
for fields in options:
if (self.keep and fields[self.column] == filter_value) or (not self.keep and fields[self.column] != filter_value):
diff --git a/lib/galaxy/tools/parameters/grouping.py b/lib/galaxy/tools/parameters/grouping.py
index 5a7bc707dd3..91156d40fa7 100644
--- a/lib/galaxy/tools/parameters/grouping.py
+++ b/lib/galaxy/tools/parameters/grouping.py
@@ -322,7 +322,7 @@ class UploadDataset(Group):
if not dataset_name and 'filename' in data_file:
dataset_name = get_file_name(data_file['filename'])
return Bunch(type='file', path=data_file['local_filename'], name=dataset_name, purge_source=purge)
- except:
+ except Exception:
# The uploaded file should've been persisted by the upload tool action
return Bunch(type=None, path=None, name=None)
@@ -401,6 +401,7 @@ class UploadDataset(Group):
warnings.append("All FTP uploaded file selections were ignored.")
elif ftp_files is not None and trans.user is not None: # look for files uploaded via FTP
user_ftp_dir = trans.user_ftp_dir
+ assert not os.path.islink(user_ftp_dir), "User FTP directory cannot be a symbolic link"
for (dirpath, dirnames, filenames) in os.walk(user_ftp_dir):
for filename in filenames:
for ftp_filename in ftp_files:
@@ -486,6 +487,7 @@ class UploadDataset(Group):
# TODO: warning to the user (could happen if session has become invalid)
else:
user_ftp_dir = trans.user_ftp_dir
+ assert not os.path.islink(user_ftp_dir), "User FTP directory cannot be a symbolic link"
for (dirpath, dirnames, filenames) in os.walk(user_ftp_dir):
for filename in filenames:
path = relpath(os.path.join(dirpath, filename), user_ftp_dir)
diff --git a/lib/galaxy/tools/parameters/validation.py b/lib/galaxy/tools/parameters/validation.py
index 1189a8fd69f..895bc17b3ce 100644
--- a/lib/galaxy/tools/parameters/validation.py
+++ b/lib/galaxy/tools/parameters/validation.py
@@ -410,7 +410,7 @@ class MetadataInDataTableColumnValidator(Validator):
metadata_column = elem.get("metadata_column", 0)
try:
metadata_column = int(metadata_column)
- except:
+ except ValueError:
pass
message = elem.get("message", "Value for metadata %s was not found in %s." % (metadata_name, table_name))
line_startswith = elem.get("line_startswith", None)
diff --git a/lib/galaxy/tools/parser/output_actions.py b/lib/galaxy/tools/parser/output_actions.py
index 9dfacff74b5..35798452b5a 100644
--- a/lib/galaxy/tools/parser/output_actions.py
+++ b/lib/galaxy/tools/parser/output_actions.py
@@ -544,7 +544,7 @@ class BooleanFilter(ToolOutputActionOptionFilter):
try:
value = fields[self.column]
value = self.cast(value)
- except:
+ except Exception:
value = False # unable to cast or access value; treat as false
if self.keep == bool(value):
rval.append(fields)
diff --git a/lib/galaxy/tools/parser/xml.py b/lib/galaxy/tools/parser/xml.py
index 42ad1c0bced..a38a45190a2 100644
--- a/lib/galaxy/tools/parser/xml.py
+++ b/lib/galaxy/tools/parser/xml.py
@@ -726,7 +726,7 @@ class StdioParser(object):
else:
try:
exit_code.range_start = int(code_range)
- except:
+ except Exception:
log.error(code_range)
log.warning("Invalid range start for tool's exit_code %s: exit_code ignored" % code_range)
continue
diff --git a/lib/galaxy/tools/toolbox/base.py b/lib/galaxy/tools/toolbox/base.py
index 442be386cbf..d3fbb617043 100644
--- a/lib/galaxy/tools/toolbox/base.py
+++ b/lib/galaxy/tools/toolbox/base.py
@@ -655,7 +655,7 @@ class AbstractToolBox(Dictifiable, ManagesIntegratedToolPanelMixin, object):
panel_dict[key] = workflow
# Always load workflows into the integrated_panel_dict.
integrated_panel_dict.update_or_append(index, key, workflow)
- except:
+ except Exception:
log.exception("Error loading workflow: %s", workflow_id)
def _load_label_tag_set(self, item, panel_dict, integrated_panel_dict, load_panel_dict, index=None):
diff --git a/lib/galaxy/tools/util/maf_utilities.py b/lib/galaxy/tools/util/maf_utilities.py
index 2851cf2cac2..89c5705b51d 100644
--- a/lib/galaxy/tools/util/maf_utilities.py
+++ b/lib/galaxy/tools/util/maf_utilities.py
@@ -168,7 +168,7 @@ class RegionAlignment(object):
for name in skip:
try:
names.remove(name)
- except:
+ except ValueError:
pass
return names
@@ -295,7 +295,7 @@ def maf_index_by_uid(maf_uid, index_location_file):
return bx.align.maf.MultiIndexed(maf_files, keep_open=True, parse_e_rows=False)
except Exception as e:
raise Exception('MAF UID (%s) found, but configuration appears to be malformed: %s' % (maf_uid, e))
- except:
+ except Exception:
pass
return None
@@ -304,7 +304,7 @@ def maf_index_by_uid(maf_uid, index_location_file):
def open_or_build_maf_index(maf_file, index_filename, species=None):
try:
return (bx.align.maf.Indexed(maf_file, index_filename=index_filename, keep_open=True, parse_e_rows=False), None)
- except:
+ except Exception:
return build_maf_index(maf_file, species=species)
@@ -675,7 +675,7 @@ def parse_species_option(species):
def remove_temp_index_file(index_filename):
try:
os.unlink(index_filename)
- except:
+ except Exception:
pass
# Below are methods to deal with FASTA files
@@ -713,7 +713,7 @@ def get_attributes_from_fasta_header(header):
region = region[1].lstrip(':').split('-')
attributes['start'] = int(region[0])
attributes['end'] = int(region[1])
- except:
+ except Exception:
# fields 0 is not a region coordinate
pass
if len(fields) > 2:
diff --git a/lib/galaxy/tools/wrappers.py b/lib/galaxy/tools/wrappers.py
index 1ff41a5eb29..257909e3aef 100644
--- a/lib/galaxy/tools/wrappers.py
+++ b/lib/galaxy/tools/wrappers.py
@@ -60,7 +60,7 @@ class RawObjectWrapper(ToolParameterValueWrapper):
def __str__(self):
try:
return "%s:%s" % (self.obj.__module__, self.obj.__class__.__name__)
- except:
+ except Exception:
# Most likely None, which lacks __module__.
return str(self.obj)
@@ -212,7 +212,7 @@ class DatasetFilenameWrapper(ToolParameterValueWrapper):
def get(self, key, default=None):
try:
return getattr(self, key)
- except:
+ except Exception:
return default
def items(self):
@@ -223,7 +223,7 @@ class DatasetFilenameWrapper(ToolParameterValueWrapper):
try:
# TODO: allow this to work when working with grouping
ext = tool.inputs[name].extensions[0]
- except:
+ except Exception:
ext = 'data'
self.dataset = wrap_with_safe_string(NoneDataset(datatypes_registry=datatypes_registry, ext=ext), no_wrap_classes=ToolParameterValueWrapper)
else:
diff --git a/lib/galaxy/util/__init__.py b/lib/galaxy/util/__init__.py
index a04f37c0621..2b617cd558c 100644
--- a/lib/galaxy/util/__init__.py
+++ b/lib/galaxy/util/__init__.py
@@ -8,6 +8,7 @@ from __future__ import absolute_import
import binascii
import collections
import errno
+import importlib
import json
import os
import random
@@ -28,7 +29,7 @@ except ImportError:
from datetime import datetime
from hashlib import md5
-from os.path import normpath, relpath
+from os.path import relpath
from xml.etree import ElementInclude, ElementTree
from xml.etree.ElementTree import ParseError
@@ -49,6 +50,7 @@ except ImportError:
from .inflection import English, Inflector
from .logging import get_logger
+from .path import safe_contains, safe_makedirs, safe_relpath # noqa: F401
inflector = Inflector(English)
@@ -309,11 +311,11 @@ def get_file_size(value, default=None):
try:
# try built-in
return os.path.getsize(value)
- except:
+ except Exception:
try:
# try built-in one name attribute
return os.path.getsize(value.name)
- except:
+ except Exception:
try:
# try tell() of end of object
offset = value.tell()
@@ -321,7 +323,7 @@ def get_file_size(value, default=None):
rval = value.tell()
value.seek(offset)
return rval
- except:
+ except Exception:
# return default value
return default
@@ -607,31 +609,38 @@ def which(file):
return None
-def safe_makedirs(path):
- """ Safely make a directory, do not fail if it already exist or
- is created during execution.
- """
- if not os.path.exists(path):
- try:
- os.makedirs(path)
- except OSError as e:
- # review source for Python 2.7 this would only ever happen
- # for the last path anyway so need to recurse - this exception
- # means the last part of the path was already in existence.
- if e.errno != errno.EEXIST:
- raise
-
-
def in_directory(file, directory, local_path_module=os.path):
"""
Return true, if the common prefix of both is equal to directory
- e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b
- """
+ e.g. /a/b/c/d.rst and directory is /a/b, the common prefix is /a/b.
+ This function isn't used exclusively for security checks, but if it is
+ used for such checks it is assumed that ``directory`` is a "trusted" path -
+ supplied by Galaxy or by the admin and ``file`` is something generated by
+ a tool, configuration, external web server, or user supplied input.
- # Make both absolute.
- directory = local_path_module.abspath(directory)
- file = local_path_module.abspath(file)
- return local_path_module.commonprefix([file, directory]) == directory
+ local_path_module is used by Pulsar to check Windows paths while running on
+ a POSIX-like system.
+
+ >>> base_dir = tempfile.mkdtemp()
+ >>> safe_dir = os.path.join(base_dir, "user")
+ >>> os.mkdir(safe_dir)
+ >>> good_file = os.path.join(safe_dir, "1")
+ >>> with open(good_file, "w") as f: f.write("hello")
+ >>> in_directory(good_file, safe_dir)
+ True
+ >>> in_directory("/other/file/is/here.txt", safe_dir)
+ False
+ >>> unsafe_link = os.path.join(safe_dir, "2")
+ >>> os.symlink("/other/file/bad.fasta", unsafe_link)
+ >>> in_directory(unsafe_link, safe_dir)
+ False
+ """
+ if local_path_module != os.path:
+ _safe_contains = importlib.import_module('galaxy.util.path.%s' % local_path_module.__name__).safe_contains
+ else:
+ directory = os.path.realpath(directory)
+ _safe_contains = safe_contains
+ return _safe_contains(directory, file)
def merge_sorted_iterables(operator, *iterables):
@@ -1039,7 +1048,7 @@ def read_dbnames(filename):
try: # manual build (i.e. microbes)
int(fields[0])
man_builds.append((fields[1], fields[0]))
- except: # UCSC build
+ except Exception: # UCSC build
db_base = fields[0].rstrip('0123456789')
if db_base not in ucsc_builds:
ucsc_builds[db_base] = []
@@ -1048,10 +1057,10 @@ def read_dbnames(filename):
build_rev = re.compile(r'\d+$')
try:
build_rev = int(build_rev.findall(fields[0])[0])
- except:
+ except Exception:
build_rev = 0
ucsc_builds[db_base].append((build_rev, fields[0], fields[1]))
- except:
+ except Exception:
continue
sort_names = sorted(name_to_db_base.keys())
for name in sort_names:
@@ -1089,9 +1098,9 @@ def read_build_sites(filename, check_builds=True):
else:
site_dict = {'name': site_name, 'url': site}
build_sites.append(site_dict)
- except:
+ except Exception:
continue
- except:
+ except Exception:
log.error("ERROR: Unable to read builds for site file %s", filename)
return build_sites
@@ -1181,7 +1190,7 @@ def umask_fix_perms(path, umask, unmasked_perms, gid=None):
try:
desired_group = grp.getgrgid(gid)
current_group = grp.getgrgid(st.st_gid)
- except:
+ except Exception:
desired_group = gid
current_group = st.st_gid
log.warning('Unable to honor primary group (%s) for %s, group remains %s, error was: %s' % (desired_group,
@@ -1237,7 +1246,7 @@ def nice_size(size):
if size < 0:
size = abs(size)
prefix = '-'
- except:
+ except Exception:
return '??? bytes'
for ind, word in enumerate(words):
step = 1024 ** (ind + 1)
@@ -1256,7 +1265,7 @@ def size_to_bytes(size):
# Assume input in bytes if we can convert directly to an int
try:
return int(size)
- except:
+ except ValueError:
pass
# Otherwise it must have non-numeric characters
size_re = re.compile('([\d\.]+)\s*([tgmk]b?|b|bytes?)$')
@@ -1481,6 +1490,7 @@ def build_url(base_url, port=80, scheme='http', pathspec=None, params=None, dose
parsed_url = urlparse.urlparse(base_url)
if scheme != 'http':
parsed_url.scheme = scheme
+ assert parsed_url.scheme in ('http', 'https', 'ftp'), 'Invalid URL scheme: %s' % scheme
if port != 80:
url = '%s://%s:%d/%s' % (parsed_url.scheme, parsed_url.netloc.rstrip('/'), int(port), parsed_url.path)
else:
@@ -1524,22 +1534,6 @@ def download_to_file(url, dest_file_path, timeout=30, chunk_size=2 ** 20):
f.write(chunk)
-def safe_relpath(path):
- """
- Given what we expect to be a relative path, determine whether the path
- would exist inside the current directory.
-
- :type path: string
- :param path: a path to check
- :rtype: bool
- :returns: ``True`` if path is relative and does not reference a path
- in a parent directory, ``False`` otherwise.
- """
- if path.startswith(os.sep) or normpath(path).startswith(os.pardir):
- return False
- return True
-
-
class ExecutionTimer(object):
def __init__(self):
diff --git a/lib/galaxy/util/checkers.py b/lib/galaxy/util/checkers.py
index 467d136d6f8..f3ced9f08db 100644
--- a/lib/galaxy/util/checkers.py
+++ b/lib/galaxy/util/checkers.py
@@ -71,7 +71,7 @@ def check_gzip(file_path, check_content=True):
temp.close()
if magic_check != util.gzip_magic:
return (False, False)
- except:
+ except Exception:
return (False, False)
# We support some binary data types, so check if the compressed binary file is valid
# If the file is Bam, it should already have been detected as such, so we'll just check
@@ -80,7 +80,7 @@ def check_gzip(file_path, check_content=True):
header = gzip.open(file_path).read(4)
if header == b'.sff':
return (True, True)
- except:
+ except Exception:
return(False, False)
if not check_content:
@@ -103,7 +103,7 @@ def check_bz2(file_path, check_content=True):
temp.close()
if magic_check != util.bz2_magic:
return (False, False)
- except:
+ except Exception:
return(False, False)
if not check_content:
diff --git a/lib/galaxy/util/dictifiable.py b/lib/galaxy/util/dictifiable.py
index 0046466474b..450a3a8b00e 100644
--- a/lib/galaxy/util/dictifiable.py
+++ b/lib/galaxy/util/dictifiable.py
@@ -23,7 +23,7 @@ class Dictifiable:
# first and then default to to_dict?
try:
return item.to_dict(view=view, value_mapper=value_mapper)
- except:
+ except Exception:
if key in value_mapper:
return value_mapper.get(key)(item)
if type(item) == datetime.datetime:
diff --git a/lib/galaxy/util/hash_util.py b/lib/galaxy/util/hash_util.py
index a81d43aee42..9405b4b1df4 100644
--- a/lib/galaxy/util/hash_util.py
+++ b/lib/galaxy/util/hash_util.py
@@ -44,7 +44,7 @@ def hmac_new(key, value):
def is_hashable(value):
try:
hash(value)
- except:
+ except Exception:
return False
return True
diff --git a/lib/galaxy/util/heartbeat.py b/lib/galaxy/util/heartbeat.py
index 9bd8b3f7fc2..f474a0f8252 100644
--- a/lib/galaxy/util/heartbeat.py
+++ b/lib/galaxy/util/heartbeat.py
@@ -96,7 +96,7 @@ class Heartbeat(threading.Thread):
self.file.write("End dump\n\n")
self.file.flush()
self.print_nonsleeping(threads)
- except:
+ except Exception:
self.file.write("Caught exception attempting to dump thread states:")
traceback.print_exc(None, self.file)
self.file.write("\n")
diff --git a/lib/galaxy/util/image_util.py b/lib/galaxy/util/image_util.py
index 16c64d60913..8eb3a18cff4 100644
--- a/lib/galaxy/util/image_util.py
+++ b/lib/galaxy/util/image_util.py
@@ -9,7 +9,7 @@ try:
except ImportError:
try:
from PIL import Image as PIL
- except:
+ except ImportError:
PIL = None
log = logging.getLogger(__name__)
@@ -22,7 +22,7 @@ def image_type(filename):
im = PIL.open(filename)
fmt = im.format
im.close()
- except:
+ except Exception:
# We continue to try with imghdr, so this is a rare case of an
# exception we expect to happen frequently, so we're not logging
pass
diff --git a/lib/galaxy/util/object_wrapper.py b/lib/galaxy/util/object_wrapper.py
index 5eac6719c6e..63f448bbbfb 100644
--- a/lib/galaxy/util/object_wrapper.py
+++ b/lib/galaxy/util/object_wrapper.py
@@ -155,7 +155,7 @@ def wrap_with_safe_string(value, no_wrap_classes=None):
try:
wrapped_class_name = value.__name__
wrapped_class = value
- except:
+ except Exception:
wrapped_class_name = value.__class__.__name__
wrapped_class = value.__class__
value_mod = inspect.getmodule(value)
diff --git a/lib/galaxy/util/path/__init__.py b/lib/galaxy/util/path/__init__.py
new file mode 100644
index 00000000000..3cde6fb0934
--- /dev/null
+++ b/lib/galaxy/util/path/__init__.py
@@ -0,0 +1,185 @@
+"""Path manipulation functions.
+"""
+from __future__ import absolute_import
+
+import errno
+import imp
+from functools import partial
+from itertools import starmap
+from operator import getitem
+from os import (
+ makedirs,
+ walk,
+)
+from os.path import (
+ abspath,
+ exists,
+ isabs,
+ join,
+ normpath,
+ pardir,
+ realpath,
+ relpath,
+)
+
+from six import string_types
+from six.moves import filterfalse, map, zip
+
+
+def safe_contains(prefix, path, whitelist=None):
+ """Ensure a path is contained within another path.
+
+ Given any two filesystem paths, ensure that ``path`` is contained in ``prefix``. If ``path`` exists (either as an
+ absolute path or relative to ``prefix``), it is canonicalized with :func:`os.path.realpath` to ensure it is not a
+ symbolic link that points outside of ``prefix``. If it is a symbolic link and ``whitelist`` is set, the symbolic link
+ may also point inside a ``whitelist`` path.
+
+ The ``path`` is checked against ``whitelist`` using either its absolute pathname (if passed in as absolute) or
+ relative to ``prefix`` and canonicalized (if applicable). It is *not* ``os.path.join()``ed with each ``whitelist``
+ directory.
+
+ :type prefix: string
+ :param prefix: a directory under which ``path`` is to be checked
+ :type path: string
+ :param path: a filename to check
+ :type whitelist: list of strings
+ :param whitelist: list of additional paths under which ``path`` may be located
+ :rtype: bool
+ :returns: ``True`` if ``path`` is contained within ``prefix`` or ``whitelist``, ``False`` otherwise.
+ """
+ return any(__contains(prefix, path, whitelist=whitelist))
+
+
+def safe_makedirs(path):
+ """Safely make a directory, do not fail if it already exists or is created during execution.
+
+ :type path: string
+ :param path: a directory to create
+ """
+ # prechecking for existence is faster than try/except
+ if not exists(path):
+ try:
+ makedirs(path)
+ except OSError as e:
+ # reviewing the source for Python 2.7, this would only ever happen for the last path element anyway so no
+ # need to recurse - this exception means the last part of the path was already in existence.
+ if e.errno != errno.EEXIST:
+ raise
+
+
+def safe_relpath(path):
+ """Determine whether a relative path references a path outside its root.
+
+ This is a path computation: the filesystem is not accessed to confirm the existence or nature of ``path``.
+
+ :type path: string
+ :param path: a path to check
+ :rtype: bool
+ :returns: ``True`` if path is relative and does not reference a path in a parent directory, ``False``
+ otherwise.
+ """
+ return not (isabs(path) or normpath(path).startswith(pardir))
+
+
+def unsafe_walk(path, whitelist=None):
+ """Walk a path and ensure that none of its contents are symlinks outside the path.
+
+ It is assumed that ``path`` itself has already been validated e.g. with :func:`safe_relpath` or
+ :func:`safe_contains`.
+
+ :type path: string
+ :param path: a directory to check for unsafe contents
+ :type whitelist: list of strings
+ :param whitelist: list of additional paths under which contents may be located
+ :rtype: iterator
+ :returns: Iterator of "bad" files found under ``path``
+ """
+ return filterfalse(partial(safe_contains, path, whitelist=whitelist), __walk(abspath(path)))
+
+
+def __listify(item):
+ """A non-splitting version of :func:`galaxy.util.listify`.
+ """
+ if not item:
+ return []
+ elif isinstance(item, list) or isinstance(item, tuple):
+ return item
+ else:
+ return [item]
+
+
+# helpers
+
+
+def __walk(path):
+ for dirpath, dirnames, filenames in walk(path):
+ for name in dirnames + filenames:
+ yield join(dirpath, name)
+
+
+def __contains(prefix, path, whitelist=None):
+ real = realpath(join(prefix, path))
+ yield not relpath(real, prefix).startswith(pardir)
+ for wldir in whitelist or []:
+ yield not relpath(real, wldir).startswith(pardir)
+
+
+# cross-platform support
+
+
+def _build_self(target, path_module):
+ """Populate a module with the same exported functions as this module, but using the given os.path module.
+
+ :type target: module
+ :param target: module on which to set ``galaxy.util.path`` functions
+ :type path_module: ``ntpath`` or ``posixpath`` module
+ :param path_module: module implementing ``os.path`` API to use for path functions
+ """
+ __copy_self().__set_fxns_on(target, path_module)
+
+
+def __copy_self(names=__name__, parent=None):
+ """Returns a copy of this module that can be modified without modifying `galaxy.util.path`` in ``sys.modules``.
+ """
+ if isinstance(names, string_types):
+ names = iter(names.split('.'))
+ try:
+ name = names.next()
+ except StopIteration:
+ return parent
+ path = parent and parent.__path__
+ parent = imp.load_module(name, *imp.find_module(name, path))
+ return __copy_self(names, parent)
+
+
+def __set_fxns_on(target, path_module):
+ """Overrides imported os.path functions with the ones from path_module and populates target with the global
+ functions from this module.
+ """
+ for name in __pathfxns__:
+ globals()[name] = getattr(path_module, name)
+ __get = partial(getitem, globals())
+ __set = partial(setattr, target)
+ # this is actually izip(..., imap(...))
+ __fxns = zip(__all__, map(__get, __all__))
+ # list() to execute
+ list(starmap(__set, __fxns))
+
+
+__pathfxns__ = (
+ 'abspath',
+ 'exists',
+ 'isabs',
+ 'join',
+ 'normpath',
+ 'pardir',
+ 'realpath',
+ 'relpath',
+)
+
+__all__ = (
+ 'safe_contains',
+ 'safe_makedirs',
+ 'safe_relpath',
+ 'unsafe_walk',
+)
diff --git a/lib/galaxy/util/path/ntpath.py b/lib/galaxy/util/path/ntpath.py
new file mode 100644
index 00000000000..3c86944963a
--- /dev/null
+++ b/lib/galaxy/util/path/ntpath.py
@@ -0,0 +1,11 @@
+"""Galaxy "safe" path functions forced to work with Windows-style paths regardless of current platform
+"""
+from __future__ import absolute_import
+
+import ntpath # noqa: I100 See https://github.com/PyCQA/flake8-import-order/pull/115
+import sys
+
+from . import _build_self
+
+
+_build_self(sys.modules[__name__], ntpath)
diff --git a/lib/galaxy/util/path/posixpath.py b/lib/galaxy/util/path/posixpath.py
new file mode 100644
index 00000000000..28232649b57
--- /dev/null
+++ b/lib/galaxy/util/path/posixpath.py
@@ -0,0 +1,11 @@
+"""Galaxy "safe" path functions forced to work with POSIX-style paths regardless of current platform
+"""
+from __future__ import absolute_import
+
+import posixpath
+import sys
+
+from . import _build_self
+
+
+_build_self(sys.modules[__name__], posixpath)
diff --git a/lib/galaxy/util/sanitize_html.py b/lib/galaxy/util/sanitize_html.py
index bc94574bef2..525c0d8ed4d 100644
--- a/lib/galaxy/util/sanitize_html.py
+++ b/lib/galaxy/util/sanitize_html.py
@@ -6,24 +6,10 @@ import re
import sgmllib
from six import unichr
+from six.moves.html_entities import name2codepoint
from galaxy.util import unicodify
-
-# reversable htmlentitydefs mappings for Python 2.2
-try:
- from htmlentitydefs import name2codepoint, codepoint2name
-except:
- import htmlentitydefs
- name2codepoint = {}
- codepoint2name = {}
- for (name, codepoint) in htmlentitydefs.entitydefs.iteritems():
- if codepoint.startswith(''):
- codepoint = unichr(int(codepoint[2:-1]))
- name2codepoint[name] = ord(codepoint)
- codepoint2name[ord(codepoint)] = name
-
-
_cp1252 = {
unichr(128): unichr(8364), # euro sign
unichr(130): unichr(8218), # single low-9 quotation mark
@@ -363,7 +349,7 @@ class _HTMLSanitizer(_BaseHTMLProcessor):
# declare xlink namespace, if needed
if self.mathmlOK or self.svgOK:
- if filter(lambda n, v: n.startswith('xlink:'), attrs):
+ if any(map(lambda n, v: n.startswith('xlink:'), attrs)):
if not ('xmlns:xlink', 'http://www.w3.org/1999/xlink') in attrs:
attrs.append(('xmlns:xlink', 'http://www.w3.org/1999/xlink'))
diff --git a/lib/galaxy/visualization/data_providers/basic.py b/lib/galaxy/visualization/data_providers/basic.py
index 8695db08464..15a3106c5a0 100644
--- a/lib/galaxy/visualization/data_providers/basic.py
+++ b/lib/galaxy/visualization/data_providers/basic.py
@@ -123,12 +123,12 @@ class ColumnDataProvider(BaseDataProvider):
if type == 'int':
try:
val = int(val)
- except:
+ except ValueError:
return None
elif type == 'float':
try:
val = float(val)
- except:
+ except ValueError:
return None
return val
diff --git a/lib/galaxy/visualization/data_providers/genome.py b/lib/galaxy/visualization/data_providers/genome.py
index 460765eb3d5..3fe859599fd 100644
--- a/lib/galaxy/visualization/data_providers/genome.py
+++ b/lib/galaxy/visualization/data_providers/genome.py
@@ -11,9 +11,9 @@ import sys
from json import loads
import pysam
-from bx.interval_index_file import Indexes
from bx.bbi.bigbed_file import BigBedFile
from bx.bbi.bigwig_file import BigWigFile
+from bx.interval_index_file import Indexes
from pysam import ctabix
from galaxy.datatypes.interval import Bed, Gff, Gtf
@@ -237,7 +237,7 @@ class GenomeDataProvider(BaseDataProvider):
except AttributeError:
try:
column_names = range(self.original_dataset.metadata.columns)
- except: # Give up
+ except Exception: # Give up
return []
# Dataset must have column types; if not, cannot create filters.
@@ -276,25 +276,6 @@ class GenomeDataProvider(BaseDataProvider):
class FilterableMixin:
def get_filters(self):
""" Returns a dataset's filters. """
-
- # is_ functions taken from Tabular.set_meta
- def is_int(column_text):
- try:
- int(column_text)
- return True
- except:
- return False
-
- def is_float(column_text):
- try:
- float(column_text)
- return True
- except:
- if column_text.strip().lower() == 'na':
- return True # na is special cased to be a float
- return False
-
- #
# Get filters.
# TODOs:
# (a) might be useful to move this into each datatype's set_meta method;
@@ -449,7 +430,7 @@ class IntervalDataProvider(GenomeDataProvider):
if length >= 5 and filter_cols and filter_cols[0] == "Score":
try:
payload.append(float(feature[4]))
- except:
+ except Exception:
payload.append(feature[4])
rval.append(payload)
@@ -540,7 +521,7 @@ class BedDataProvider(GenomeDataProvider):
try:
payload.append(float(feature[4]))
- except:
+ except Exception:
payload.append(feature[4])
rval.append(payload)
@@ -1697,7 +1678,7 @@ def package_gff_feature(feature, no_detail=False, filter_cols=[]):
try:
f = float(feature.score)
payload.append(f)
- except:
+ except Exception:
payload.append(feature.score)
elif col in feature.attributes:
if feature.attributes[col] == 'nan':
@@ -1706,7 +1687,7 @@ def package_gff_feature(feature, no_detail=False, filter_cols=[]):
try:
f = float(feature.attributes[col])
payload.append(f)
- except:
+ except Exception:
payload.append(feature.attributes[col])
else:
# Dummy value.
diff --git a/lib/galaxy/visualization/genomes.py b/lib/galaxy/visualization/genomes.py
index 27dc2dfcd44..f4931a4afb3 100644
--- a/lib/galaxy/visualization/genomes.py
+++ b/lib/galaxy/visualization/genomes.py
@@ -166,7 +166,7 @@ class Genome(object):
try:
next(len_file_enumerate)
next_chroms = True
- except:
+ except StopIteration:
# No more chroms to read.
pass
diff --git a/lib/galaxy/visualization/plugins/interactive_environments.py b/lib/galaxy/visualization/plugins/interactive_environments.py
index c97f1508f28..edb108eb864 100644
--- a/lib/galaxy/visualization/plugins/interactive_environments.py
+++ b/lib/galaxy/visualization/plugins/interactive_environments.py
@@ -2,15 +2,17 @@ import json
import logging
import os
import random
+import shlex
import stat
import string
import tempfile
import uuid
+from itertools import product
from subprocess import PIPE, Popen
from sys import platform as _platform
import yaml
-from six.moves import configparser
+from six.moves import configparser, shlex_quote
from galaxy import model, web
from galaxy.containers import build_container_interfaces
@@ -132,7 +134,7 @@ class InteractiveEnvironmentRequest(object):
# their defaults dictionary instead.
default_dict = {
'container_interface': None,
- 'command': 'docker {docker_args}',
+ 'command': 'docker',
'command_inject': '-e DEBUG=false -e DEFAULT_CONTAINER_RUNTIME=120',
'docker_hostname': 'localhost',
'wx_tempdir': 'False',
@@ -273,40 +275,40 @@ class InteractiveEnvironmentRequest(object):
def _get_name_for_run(self):
return CONTAINER_NAME_PREFIX + uuid.uuid4().hex
+ def base_docker_cmd(self, subcmd=None):
+ # This is the basic docker command such as "sudo -u docker docker" or just "docker"
+ # Previously, {docker_args} was required to be in the string, this is no longer the case
+ base = shlex.split(self.attr.viz_config.get("docker", "command").format(docker_args='').strip())
+ if subcmd:
+ base.append(subcmd)
+ return base
+
def docker_cmd(self, image, env_override=None, volumes=None):
"""
Generate and return the docker command to execute
"""
- if volumes is None:
- volumes = []
- env = self._get_env_for_run(env_override)
- import_volume_def = self._get_import_volume_for_run()
- env_str = ' '.join('-e "%s=%s"' % (key, item) for key, item in env.items())
- volume_str = ' '.join('-v "%s"' % volume for volume in volumes) if self.use_volumes else ''
- import_volume_str = '-v "{import_volume}"'.format(import_volume=import_volume_def) if import_volume_def else ''
- name = None
- # This is the basic docker command such as "sudo -u docker docker {docker_args}"
- # or just "docker {docker_args}"
- command = self.attr.viz_config.get("docker", "command")
- # Then we format in the entire docker command in place of
- # {docker_args}, so as to let the admin not worry about which args are
- # getting passed
+ def _flag_opts(flag, opts):
+ return [arg for pair in product((flag,), opts) for arg in pair]
+
command_inject = self.attr.viz_config.get("docker", "command_inject")
# --name should really not be set, but we'll try to honor it anyway
- if '--name' not in command_inject:
- name = self._get_name_for_run()
- command = command.format(docker_args='run {command_inject} {name} {environment} -d -P {import_volume_str} {volume_str} {image}')
+ name = ['--name=%s' % self._get_name_for_run()] if '--name' not in command_inject else []
+ env = self._get_env_for_run(env_override)
+ import_volume_def = self._get_import_volume_for_run()
+ if volumes is None:
+ volumes = []
+ if import_volume_def:
+ volumes.insert(0, import_volume_def)
- # Once that's available, we format again with all of our arguments
- command = command.format(
- command_inject=command_inject,
- name='--name=%s' % name if name is not None else '',
- environment=env_str,
- import_volume_str=import_volume_str,
- volume_str=volume_str,
- image=image,
+ return (
+ self.base_docker_cmd('run') +
+ shlex.split(command_inject) +
+ name +
+ _flag_opts('-e', ['='.join(map(str, t)) for t in env.items()]) +
+ ['-d', '-P'] +
+ _flag_opts('-v', map(str, volumes)) +
+ [image]
)
- return command
@property
def use_volumes(self):
@@ -398,9 +400,9 @@ class InteractiveEnvironmentRequest(object):
log.info("Starting docker container for IE {0} with command [{1}]".format(
self.attr.viz_id,
- raw_cmd
+ ' '.join([shlex_quote(x) for x in raw_cmd])
))
- p = Popen(raw_cmd, stdout=PIPE, stderr=PIPE, close_fds=True, shell=True)
+ p = Popen(raw_cmd, stdout=PIPE, stderr=PIPE, close_fds=True)
stdout, stderr = p.communicate()
if p.returncode != 0:
log.error("Container Launch error\n\n%s\n%s" % (stdout, stderr))
@@ -504,14 +506,13 @@ class InteractiveEnvironmentRequest(object):
:returns: inspect_data, a dict of docker inspect output
"""
- command = self.attr.viz_config.get("docker", "command")
- command = command.format(docker_args="inspect %s" % container_id)
+ raw_cmd = self.base_docker_cmd('inspect') + [container_id]
log.info("Inspecting docker container {0} with command [{1}]".format(
container_id,
- command
+ ' '.join([shlex_quote(x) for x in raw_cmd])
))
- p = Popen(command, stdout=PIPE, stderr=PIPE, close_fds=True, shell=True)
+ p = Popen(raw_cmd, stdout=PIPE, stderr=PIPE, close_fds=True)
stdout, stderr = p.communicate()
if p.returncode != 0:
log.error("Container Launch error\n\n%s\n%s" % (stdout, stderr))
diff --git a/lib/galaxy/visualization/plugins/plugin.py b/lib/galaxy/visualization/plugins/plugin.py
index 82ccdd701fd..931ff694d48 100644
--- a/lib/galaxy/visualization/plugins/plugin.py
+++ b/lib/galaxy/visualization/plugins/plugin.py
@@ -268,7 +268,7 @@ class InteractiveEnvironmentPlugin(VisualizationPlugin):
if self.config.get('plugin_type', 'visualization') == "interactive_environment":
try:
request = self.INTENV_REQUEST_FACTORY(trans, self)
- except:
+ except Exception:
log.exception("IE plugin request handling failed")
return trans.fill_template('message.mako',
message='Loading the interactive environment failed, please contact the {admin_tag} for assistance'.format(
diff --git a/lib/galaxy/web/base/controller.py b/lib/galaxy/web/base/controller.py
index db9f8be0c8a..1b610532438 100644
--- a/lib/galaxy/web/base/controller.py
+++ b/lib/galaxy/web/base/controller.py
@@ -185,7 +185,7 @@ class BaseUIController(BaseController):
check_ownership=check_ownership, check_accessible=check_accessible, deleted=deleted)
except exceptions.MessageException:
raise # handled in the caller
- except:
+ except Exception:
log.exception("Exception in get_object check for %s %s:", class_name, str(id))
raise Exception('Server error retrieving %s id ( %s ).' % (class_name, str(id)))
@@ -1084,7 +1084,7 @@ class UsesVisualizationMixin(UsesLibraryMixinItems):
try:
data = trans.sa_session.query(trans.app.model.HistoryDatasetAssociation).get(int(dataset_id))
- except:
+ except Exception:
raise HTTPRequestRangeNotSatisfiable("Invalid dataset id: %s." % str(dataset_id))
if check_ownership:
diff --git a/lib/galaxy/web/framework/decorators.py b/lib/galaxy/web/framework/decorators.py
index 9d56f851995..ef23ed0a127 100644
--- a/lib/galaxy/web/framework/decorators.py
+++ b/lib/galaxy/web/framework/decorators.py
@@ -147,7 +147,7 @@ def expose_api(func, to_json=True, user_required=True):
user = trans.sa_session.query(trans.app.model.User).get(decoded_user_id)
trans.api_inherit_admin = trans.user_is_admin()
trans.set_user(user)
- except:
+ except Exception:
trans.response.status = 400
return "That user does not exist."
try:
@@ -157,7 +157,7 @@ def expose_api(func, to_json=True, user_required=True):
return rval
except paste.httpexceptions.HTTPException:
raise # handled
- except:
+ except Exception:
log.exception('Uncaught exception in exposed API method:')
raise paste.httpexceptions.HTTPServerError()
return expose(_save_orig_fn(decorator, func))
@@ -183,7 +183,7 @@ def __extract_payload_from_request(trans, func, kwargs):
# 40000000000000e5 will be parsed as a scientific notation float. This is as opposed to hex strings
# in larger JSON structures where quoting prevents this (further below)
payload[k] = loads(v, parse_float=util.parse_non_hex_float)
- except:
+ except Exception:
# may not actually be json, just continue
pass
payload = util.recursively_stringify_dictionary_keys(payload)
@@ -274,7 +274,7 @@ def _future_expose_api(func, to_json=True, user_required=True, user_or_session_r
user = trans.sa_session.query(trans.app.model.User).get(decoded_user_id)
trans.api_inherit_admin = trans.user_is_admin()
trans.set_user(user)
- except:
+ except Exception:
error_code = error_codes.USER_INVALID_RUN_AS
return __api_error_response(trans, err_code=error_code, status_code=400)
try:
diff --git a/lib/galaxy/web/framework/helpers/grids.py b/lib/galaxy/web/framework/helpers/grids.py
index e28eb336ec4..fbfc11faf38 100644
--- a/lib/galaxy/web/framework/helpers/grids.py
+++ b/lib/galaxy/web/framework/helpers/grids.py
@@ -440,7 +440,7 @@ class Grid(object):
# Ensure ids are integers
try:
id = list(map(int, id))
- except:
+ except Exception:
decorators.error("Invalid id")
return id
diff --git a/lib/galaxy/web/framework/middleware/error.py b/lib/galaxy/web/framework/middleware/error.py
index 581d9896f78..0c337164b2a 100644
--- a/lib/galaxy/web/framework/middleware/error.py
+++ b/lib/galaxy/web/framework/middleware/error.py
@@ -153,7 +153,7 @@ class ErrorMiddleware(object):
sr_checker = ResponseStartChecker(start_response)
app_iter = self.application(environ, sr_checker)
return self.make_catching_iter(app_iter, environ, sr_checker)
- except:
+ except Exception:
exc_info = sys.exc_info()
try:
for expect in environ.get('paste.expected_exceptions', []):
@@ -244,7 +244,7 @@ class CatchingIter(six.Iterator):
return close_response
else:
raise StopIteration
- except:
+ except Exception:
self.closed = True
close_response = self._close()
exc_info = sys.exc_info()
@@ -275,7 +275,7 @@ class CatchingIter(six.Iterator):
try:
self.app_iterable.close()
return None
- except:
+ except Exception:
close_response = self.error_middleware.exception_handler(
sys.exc_info(), self.environ)
return close_response
@@ -356,7 +356,7 @@ def handle_exception(exc_info, error_stream, html=True,
from paste.exceptions.errormiddleware import handle_exception
try:
do stuff
- except:
+ except Exception:
handle_exception(
sys.exc_info(), sys.stderr, html=False, ...other config...)
@@ -440,7 +440,7 @@ def handle_exception(exc_info, error_stream, html=True,
def send_report(rep, exc_data, html=True):
try:
rep.report(exc_data)
- except:
+ except Exception:
output = StringIO()
traceback.print_exc(file=output)
if html:
diff --git a/lib/galaxy/web/framework/middleware/sentry.py b/lib/galaxy/web/framework/middleware/sentry.py
index 0817fc5fbb3..4718e98a288 100644
--- a/lib/galaxy/web/framework/middleware/sentry.py
+++ b/lib/galaxy/web/framework/middleware/sentry.py
@@ -9,7 +9,7 @@ raven.middleware
try:
from raven import Client
from raven.utils.wsgi import get_current_url, get_headers, get_environ
-except:
+except ImportError:
Client = None
from galaxy.web.stack import register_postfork_function
diff --git a/lib/galaxy/web/framework/webapp.py b/lib/galaxy/web/framework/webapp.py
index 3b185bb5a1f..a240f73aee9 100644
--- a/lib/galaxy/web/framework/webapp.py
+++ b/lib/galaxy/web/framework/webapp.py
@@ -340,7 +340,7 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
return self.response.cookies[name].value
else:
return self.request.cookies[name].value
- except:
+ except Exception:
return None
def set_cookie(self, value, name='galaxysession', path='/', age=90, version='1'):
@@ -654,7 +654,7 @@ class GalaxyWebTransaction(base.DefaultWebTransaction,
try:
users_last_session = user.galaxy_sessions[0]
last_accessed = True
- except:
+ except Exception:
users_last_session = None
last_accessed = False
if (prev_galaxy_session.current_history and not
diff --git a/lib/galaxy/web/stack/__init__.py b/lib/galaxy/web/stack/__init__.py
index 3b7689be4d5..e23899db6f5 100644
--- a/lib/galaxy/web/stack/__init__.py
+++ b/lib/galaxy/web/stack/__init__.py
@@ -16,7 +16,7 @@ except ImportError:
try:
from uwsgidecorators import postfork as uwsgi_postfork
-except:
+except (AttributeError, ImportError):
uwsgi_postfork = lambda x: x # noqa: E731
if uwsgi is not None and hasattr(uwsgi, 'numproc'):
print("WARNING: This is a uwsgi process but the uwsgidecorators library"
diff --git a/lib/galaxy/webapps/galaxy/api/authenticate.py b/lib/galaxy/webapps/galaxy/api/authenticate.py
index 3f24607b1bd..59995ef91e6 100644
--- a/lib/galaxy/webapps/galaxy/api/authenticate.py
+++ b/lib/galaxy/webapps/galaxy/api/authenticate.py
@@ -10,16 +10,15 @@ Returns:
"api_key": "baa4d6e3a156d3033f05736255f195f9"
}
"""
-
+import logging
from base64 import b64decode
from urllib import unquote
-from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless
-from galaxy.managers import api_keys
from galaxy import exceptions
+from galaxy.managers import api_keys
+from galaxy.web import _future_expose_api_anonymous_and_sessionless as expose_api_anonymous_and_sessionless
from galaxy.web.base.controller import BaseAPIController
-import logging
log = logging.getLogger(__name__)
@@ -82,7 +81,7 @@ class AuthenticationController(BaseAPIController):
if len(split) == 1:
try:
email, password = b64decode(split[0]).split(':')
- except:
+ except Exception:
raise exceptions.ActionInputError()
# If there are only two elements, check the first and ensure it says
@@ -92,7 +91,7 @@ class AuthenticationController(BaseAPIController):
if split[0].strip().lower() == 'basic':
try:
email, password = b64decode(split[1]).split(':')
- except:
+ except Exception:
raise exceptions.ActionInputError()
else:
raise exceptions.ActionInputError()
diff --git a/lib/galaxy/webapps/galaxy/api/folder_contents.py b/lib/galaxy/webapps/galaxy/api/folder_contents.py
index 3ff8c2e0629..63ff60d4564 100644
--- a/lib/galaxy/webapps/galaxy/api/folder_contents.py
+++ b/lib/galaxy/webapps/galaxy/api/folder_contents.py
@@ -109,6 +109,7 @@ class FolderContentsController(BaseAPIController, UsesLibraryMixin, UsesLibraryM
is_unrestricted=is_unrestricted,
is_private=is_private,
can_manage=can_manage,
+ state=library_dataset_dict['state'],
file_size=nice_size))
if content_item.library_dataset_dataset_association.message:
return_item.update(dict(message=content_item.library_dataset_dataset_association.message))
diff --git a/lib/galaxy/webapps/galaxy/api/forms.py b/lib/galaxy/webapps/galaxy/api/forms.py
index 66ba15bcc8a..bd44665a150 100644
--- a/lib/galaxy/webapps/galaxy/api/forms.py
+++ b/lib/galaxy/webapps/galaxy/api/forms.py
@@ -2,10 +2,12 @@
API operations on FormDefinition objects.
"""
import logging
-from galaxy.web.base.controller import BaseAPIController, url_for
+
+from xml.etree.ElementTree import XML
+
from galaxy import web
from galaxy.forms.forms import form_factory
-from xml.etree.ElementTree import XML
+from galaxy.web.base.controller import BaseAPIController, url_for
log = logging.getLogger(__name__)
@@ -43,7 +45,7 @@ class FormDefinitionAPIController(BaseAPIController):
return "Malformed form definition id ( %s ) specified, unable to decode." % str(form_definition_id)
try:
form_definition = trans.sa_session.query(trans.app.model.FormDefinition).get(decoded_form_definition_id)
- except:
+ except Exception:
form_definition = None
if not form_definition or not trans.user_is_admin():
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/group_roles.py b/lib/galaxy/webapps/galaxy/api/group_roles.py
index a4d42cbbaf5..46372b7b021 100644
--- a/lib/galaxy/webapps/galaxy/api/group_roles.py
+++ b/lib/galaxy/webapps/galaxy/api/group_roles.py
@@ -2,8 +2,9 @@
API operations on Group objects.
"""
import logging
-from galaxy.web.base.controller import BaseAPIController, url_for
+
from galaxy import web
+from galaxy.web.base.controller import BaseAPIController, url_for
log = logging.getLogger(__name__)
@@ -20,7 +21,7 @@ class GroupRolesAPIController(BaseAPIController):
decoded_group_id = trans.security.decode_id(group_id)
try:
group = trans.sa_session.query(trans.app.model.Group).get(decoded_group_id)
- except:
+ except Exception:
group = None
if not group:
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/group_users.py b/lib/galaxy/webapps/galaxy/api/group_users.py
index 04d47f4c0b9..ab0f3de4f88 100644
--- a/lib/galaxy/webapps/galaxy/api/group_users.py
+++ b/lib/galaxy/webapps/galaxy/api/group_users.py
@@ -2,8 +2,9 @@
API operations on Group objects.
"""
import logging
-from galaxy.web.base.controller import BaseAPIController, url_for
+
from galaxy import web
+from galaxy.web.base.controller import BaseAPIController, url_for
log = logging.getLogger(__name__)
@@ -20,7 +21,7 @@ class GroupUsersAPIController(BaseAPIController):
decoded_group_id = trans.security.decode_id(group_id)
try:
group = trans.sa_session.query(trans.app.model.Group).get(decoded_group_id)
- except:
+ except Exception:
group = None
if not group:
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/groups.py b/lib/galaxy/webapps/galaxy/api/groups.py
index f398f865e5c..1798008b952 100644
--- a/lib/galaxy/webapps/galaxy/api/groups.py
+++ b/lib/galaxy/webapps/galaxy/api/groups.py
@@ -2,11 +2,11 @@
API operations on Group objects.
"""
import logging
+
from sqlalchemy import false
-from galaxy.web.base.controller import BaseAPIController, url_for
from galaxy import web
-
+from galaxy.web.base.controller import BaseAPIController, url_for
log = logging.getLogger(__name__)
@@ -86,7 +86,7 @@ class GroupAPIController(BaseAPIController):
return "Malformed group id ( %s ) specified, unable to decode." % str(group_id)
try:
group = trans.sa_session.query(trans.app.model.Group).get(decoded_group_id)
- except:
+ except Exception:
group = None
if not group:
trans.response.status = 400
@@ -112,7 +112,7 @@ class GroupAPIController(BaseAPIController):
return "Malformed group id ( %s ) specified, unable to decode." % str(group_id)
try:
group = trans.sa_session.query(trans.app.model.Group).get(decoded_group_id)
- except:
+ except Exception:
group = None
if not group:
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/jobs.py b/lib/galaxy/webapps/galaxy/api/jobs.py
index df2b0360efd..86f1ef415cb 100644
--- a/lib/galaxy/webapps/galaxy/api/jobs.py
+++ b/lib/galaxy/webapps/galaxy/api/jobs.py
@@ -94,7 +94,7 @@ class JobController(BaseAPIController, UsesLibraryMixinItems):
try:
decoded_history_id = self.decode_id(history_id)
query = query.filter(trans.app.model.Job.history_id == decoded_history_id)
- except:
+ except Exception:
raise exceptions.ObjectAttributeInvalidException()
out = []
diff --git a/lib/galaxy/webapps/galaxy/api/library_contents.py b/lib/galaxy/webapps/galaxy/api/library_contents.py
index 5992a8e8432..56dc0e681ec 100644
--- a/lib/galaxy/webapps/galaxy/api/library_contents.py
+++ b/lib/galaxy/webapps/galaxy/api/library_contents.py
@@ -1,19 +1,36 @@
"""
API operations on the contents of a data library.
"""
-from galaxy import util
-from galaxy import web
-from galaxy import exceptions
-from galaxy import managers
-from galaxy.web import _future_expose_api as expose_api
-from galaxy.web.base.controller import BaseAPIController, UsesLibraryMixin, UsesLibraryMixinItems
-from galaxy.web.base.controller import HTTPBadRequest, url_for
-from galaxy.managers.collections_util import api_payload_to_create_params, dictify_dataset_collection_instance
-from galaxy.model import ExtendedMetadata, ExtendedMetadataIndex
-from sqlalchemy.orm.exc import MultipleResultsFound
-from sqlalchemy.orm.exc import NoResultFound
-
import logging
+
+from sqlalchemy.orm.exc import (
+ MultipleResultsFound,
+ NoResultFound,
+)
+
+from galaxy import (
+ exceptions,
+ managers,
+ util,
+ web
+)
+from galaxy.managers.collections_util import (
+ api_payload_to_create_params,
+ dictify_dataset_collection_instance
+)
+from galaxy.model import (
+ ExtendedMetadata,
+ ExtendedMetadataIndex
+)
+from galaxy.web import _future_expose_api as expose_api
+from galaxy.web.base.controller import (
+ BaseAPIController,
+ HTTPBadRequest,
+ url_for,
+ UsesLibraryMixin,
+ UsesLibraryMixinItems
+)
+
log = logging.getLogger(__name__)
@@ -384,7 +401,7 @@ class LibraryContentsController(BaseAPIController, UsesLibraryMixin, UsesLibrary
try:
ld.library_dataset_dataset_association.dataset.full_delete()
trans.sa_session.add(ld.dataset)
- except:
+ except Exception:
pass
# flush now to preserve deleted state in case of later interruption
trans.sa_session.flush()
diff --git a/lib/galaxy/webapps/galaxy/api/library_datasets.py b/lib/galaxy/webapps/galaxy/api/library_datasets.py
index dc9f7287075..b4970a1aee3 100644
--- a/lib/galaxy/webapps/galaxy/api/library_datasets.py
+++ b/lib/galaxy/webapps/galaxy/api/library_datasets.py
@@ -24,6 +24,7 @@ from galaxy.managers import (
)
from galaxy.tools.actions import upload_common
from galaxy.tools.parameters import populate_state
+from galaxy.util.path import safe_contains, safe_relpath, unsafe_walk
from galaxy.util.streamball import StreamBall
from galaxy.web import (
_future_expose_api as expose_api,
@@ -416,29 +417,45 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin):
source = kwd.get('source', None)
if source not in ['userdir_file', 'userdir_folder', 'importdir_file', 'importdir_folder', 'admin_path']:
raise exceptions.RequestParameterMissingException('You have to specify "source" parameter. Possible values are "userdir_file", "userdir_folder", "admin_path", "importdir_file" and "importdir_folder". ')
- if source in ['importdir_file', 'importdir_folder']:
- if not trans.user_is_admin:
+ elif source in ['importdir_file', 'importdir_folder']:
+ if not trans.user_is_admin():
raise exceptions.AdminRequiredException('Only admins can import from importdir.')
if not trans.app.config.library_import_dir:
raise exceptions.ConfigDoesNotAllowException('The configuration of this Galaxy instance does not allow admins to import into library from importdir.')
import_base_dir = trans.app.config.library_import_dir
+ if not safe_relpath(path):
+ # admins shouldn't be able to explicitly specify a path outside server_dir, but symlinks are allowed.
+ # the reasoning here is that galaxy admins may not have direct filesystem access or can only access
+ # library_import_dir via FTP (which cannot create symlinks), and may rely on sysadmins to set up the
+ # import directory. if they have filesystem access, all bets are off.
+ raise exceptions.RequestParameterInvalidException('The given path is invalid.')
path = os.path.join(import_base_dir, path)
- if source in ['userdir_file', 'userdir_folder']:
+ elif source in ['userdir_file', 'userdir_folder']:
+ unsafe = None
user_login = trans.user.email
user_base_dir = trans.app.config.user_library_import_dir
if user_base_dir is None:
raise exceptions.ConfigDoesNotAllowException('The configuration of this Galaxy instance does not allow upload from user directories.')
full_dir = os.path.join(user_base_dir, user_login)
- if not path.lower().startswith(full_dir.lower()):
+ if not safe_contains(full_dir, path, whitelist=trans.app.config.user_library_import_symlink_whitelist):
+ # the path is a symlink outside the user dir
path = os.path.join(full_dir, path)
+ log.error('User attempted to import a path that resolves to a path outside of their import dir: %s -> %s', path, os.path.realpath(path))
+ raise exceptions.RequestParameterInvalidException('The given path is invalid.')
+ path = os.path.join(full_dir, path)
+ for unsafe in unsafe_walk(path, whitelist=[full_dir] + trans.app.config.user_library_import_symlink_whitelist):
+ # the path is a dir and contains files that symlink outside the user dir
+ log.error('User attempted to import a directory containing a path that resolves to a path outside of their import dir: %s -> %s', unsafe, os.path.realpath(unsafe))
+ if unsafe:
+ raise exceptions.RequestParameterInvalidException('The given path is invalid.')
if not os.path.exists(path):
raise exceptions.RequestParameterInvalidException('Given path does not exist on the host.')
if not self.folder_manager.can_add_item(trans, folder):
raise exceptions.InsufficientPermissionsException('You do not have proper permission to add items to the given folder.')
- if source == 'admin_path':
+ elif source == 'admin_path':
if not trans.app.config.allow_library_path_paste:
raise exceptions.ConfigDoesNotAllowException('The configuration of this Galaxy instance does not allow admins to import into library from path.')
- if not trans.user_is_admin:
+ if not trans.user_is_admin():
raise exceptions.AdminRequiredException('Only admins can import from path.')
# Set up the traditional tool state/params
@@ -457,12 +474,12 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin):
if source in ['importdir_folder']:
kwd['filesystem_paths'] = os.path.join(import_base_dir, path)
# user wants to import one file only
- if source in ["userdir_file", "importdir_file"]:
+ elif source in ["userdir_file", "importdir_file"]:
file = os.path.abspath(path)
abspath_datasets.append(trans.webapp.controllers['library_common'].make_library_uploaded_dataset(
trans, 'api', kwd, os.path.basename(file), file, 'server_dir', library_bunch))
# user wants to import whole folder
- if source == "userdir_folder":
+ elif source == "userdir_folder":
uploaded_datasets_bunch = trans.webapp.controllers['library_common'].get_path_paste_uploaded_datasets(
trans, 'api', kwd, library_bunch, 200, '')
uploaded_datasets = uploaded_datasets_bunch[0]
@@ -708,7 +725,7 @@ class LibraryDatasetsController(BaseAPIController, UsesVisualizationMixin):
trans.response.headers["Content-Disposition"] = 'attachment; filename="%s"' % fname
try:
return open(dataset.file_name)
- except:
+ except Exception:
raise exceptions.InternalServerError("This dataset contains no content.")
else:
raise exceptions.RequestParameterInvalidException("Wrong format parameter specified")
diff --git a/lib/galaxy/webapps/galaxy/api/remote_files.py b/lib/galaxy/webapps/galaxy/api/remote_files.py
index 9dc39fc8222..7360f8ec12d 100644
--- a/lib/galaxy/webapps/galaxy/api/remote_files.py
+++ b/lib/galaxy/webapps/galaxy/api/remote_files.py
@@ -107,7 +107,7 @@ class RemoteFilesAPIController(BaseAPIController):
subfolders and returns a flat list.
"""
response = []
- if os.path.exists(directory):
+ if os.path.exists(directory) and not os.path.islink(directory):
for (dirpath, dirnames, filenames) in os.walk(directory):
for filename in filenames:
path = os.path.relpath(os.path.join(dirpath, filename), directory)
@@ -130,7 +130,7 @@ class RemoteFilesAPIController(BaseAPIController):
"""
userdir_jstree = None
jstree_paths = []
- if os.path.exists(directory):
+ if os.path.exists(directory) and not os.path.islink(directory):
for (dirpath, dirnames, filenames) in os.walk(directory):
for dirname in dirnames:
dir_path = os.path.relpath(os.path.join(dirpath, dirname), directory)
diff --git a/lib/galaxy/webapps/galaxy/api/request_types.py b/lib/galaxy/webapps/galaxy/api/request_types.py
index d6dba58acaa..7288266feb9 100644
--- a/lib/galaxy/webapps/galaxy/api/request_types.py
+++ b/lib/galaxy/webapps/galaxy/api/request_types.py
@@ -2,10 +2,12 @@
API operations on RequestType objects.
"""
import logging
-from galaxy.web.base.controller import BaseAPIController, url_for
+
+from xml.etree.ElementTree import XML
+
from galaxy import web
from galaxy.sample_tracking.request_types import request_type_factory
-from xml.etree.ElementTree import XML
+from galaxy.web.base.controller import BaseAPIController, url_for
log = logging.getLogger(__name__)
@@ -39,7 +41,7 @@ class RequestTypeAPIController(BaseAPIController):
return "Malformed request type id ( %s ) specified, unable to decode." % str(request_type_id)
try:
request_type = trans.sa_session.query(trans.app.model.RequestType).get(decoded_request_type_id)
- except:
+ except Exception:
request_type = None
if not request_type: # or not trans.user_is_admin():
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/requests.py b/lib/galaxy/webapps/galaxy/api/requests.py
index 7b35a54541d..bd59edb843c 100644
--- a/lib/galaxy/webapps/galaxy/api/requests.py
+++ b/lib/galaxy/webapps/galaxy/api/requests.py
@@ -23,6 +23,9 @@ class RequestsAPIController(BaseAPIController):
GET /api/requests
Displays a collection (list) of sequencing requests.
"""
+ if not trans.app.config.enable_legacy_sample_tracking_api:
+ trans.response.status = 403
+ return "The configuration of this Galaxy instance does not allow accessing this API."
# if admin user then return all requests
if trans.user_is_admin():
query = trans.sa_session.query(trans.app.model.Request) \
@@ -49,6 +52,9 @@ class RequestsAPIController(BaseAPIController):
GET /api/requests/{encoded_request_id}
Displays details of a sequencing request.
"""
+ if not trans.app.config.enable_legacy_sample_tracking_api:
+ trans.response.status = 403
+ return "The configuration of this Galaxy instance does not allow accessing this API."
try:
request_id = trans.security.decode_id(id)
except TypeError:
@@ -56,7 +62,7 @@ class RequestsAPIController(BaseAPIController):
return "Malformed id ( %s ) specified, unable to decode." % (str(id))
try:
request = trans.sa_session.query(trans.app.model.Request).get(request_id)
- except:
+ except Exception:
request = None
if not request or not (trans.user_is_admin() or request.user.id == trans.user.id):
trans.response.status = 400
@@ -75,6 +81,9 @@ class RequestsAPIController(BaseAPIController):
Updates a request state, sample state or sample dataset transfer status
depending on the update_type
"""
+ if not trans.app.config.enable_legacy_sample_tracking_api:
+ trans.response.status = 403
+ return "The configuration of this Galaxy instance does not allow accessing this API."
update_type = None
if 'update_type' not in payload:
trans.response.status = 400
@@ -91,18 +100,41 @@ class RequestsAPIController(BaseAPIController):
return "Malformed request id ( %s ) specified, unable to decode." % str(id)
try:
request = trans.sa_session.query(trans.app.model.Request).get(request_id)
- except:
+ except Exception:
request = None
if not request or not (trans.user_is_admin() or request.user.id == trans.user.id):
trans.response.status = 400
return "Invalid request id ( %s ) specified." % str(request_id)
# check update type
if update_type == 'request_state':
- return self.__update_request_state(trans, encoded_request_id=id)
-
- def __update_request_state(self, trans, encoded_request_id):
- requests_common_cntrller = trans.webapp.controllers['requests_common']
- status, output = requests_common_cntrller.update_request_state(trans,
- cntrller='api',
- request_id=encoded_request_id)
- return status, output
+ # Make sure all the samples of the current request have the same state
+ common_state = request.samples_have_common_state
+ if not common_state:
+ # If the current request state is complete and one of its samples moved from
+ # the final sample state, then move the request state to In-progress
+ if request.is_complete:
+ message = "At least 1 sample state moved from the final sample state, so now the request's state is (%s)" % request.states.SUBMITTED
+ event = trans.model.RequestEvent(request, request.states.SUBMITTED, message)
+ trans.sa_session.add(event)
+ trans.sa_session.flush()
+ else:
+ final_state = False
+ request_type_state = request.type.final_sample_state
+ if common_state.id == request_type_state.id:
+ # since all the samples are in the final state, change the request state to 'Complete'
+ comment = "All samples of this sequencing request are in the final sample state (%s). " % request_type_state.name
+ state = request.states.COMPLETE
+ final_state = True
+ else:
+ comment = "All samples of this sequencing request are in the (%s) sample state. " % common_state.name
+ state = request.states.SUBMITTED
+ event = trans.model.RequestEvent(request, state, comment)
+ trans.sa_session.add(event)
+ trans.sa_session.flush()
+ # See if an email notification is configured to be sent when the samples are in this state.
+ retval = request.send_email_notification(trans, common_state, final_state)
+ if retval:
+ message = comment + retval
+ else:
+ message = comment
+ return 200, message
diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py
index 2cf06a265f3..cbb2a0d1a6e 100644
--- a/lib/galaxy/webapps/galaxy/api/roles.py
+++ b/lib/galaxy/webapps/galaxy/api/roles.py
@@ -42,7 +42,7 @@ class RoleAPIController(BaseAPIController):
return "Malformed role id ( %s ) specified, unable to decode." % str(role_id)
try:
role = trans.sa_session.query(trans.app.model.Role).get(decoded_role_id)
- except:
+ except Exception:
role = None
if not role or not (trans.user_is_admin() or trans.app.security_agent.ok_to_display(trans.user, role)):
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/samples.py b/lib/galaxy/webapps/galaxy/api/samples.py
index 356b194b9e2..44a2157946e 100644
--- a/lib/galaxy/webapps/galaxy/api/samples.py
+++ b/lib/galaxy/webapps/galaxy/api/samples.py
@@ -24,6 +24,9 @@ class SamplesAPIController(BaseAPIController):
GET /api/requests/{encoded_request_id}/samples
Displays a collection (list) of sample of a sequencing request.
"""
+ if not trans.app.config.enable_legacy_sample_tracking_api:
+ trans.response.status = 403
+ return "The configuration of this Galaxy instance does not allow accessing this API."
try:
request_id = trans.security.decode_id(kwd['request_id'])
except TypeError:
@@ -31,7 +34,7 @@ class SamplesAPIController(BaseAPIController):
return "Malformed request id ( %s ) specified, unable to decode." % str(kwd['request_id'])
try:
request = trans.sa_session.query(trans.app.model.Request).get(request_id)
- except:
+ except Exception:
request = None
if not request or not (trans.user_is_admin() or request.user.id == trans.user.id):
trans.response.status = 400
@@ -52,6 +55,9 @@ class SamplesAPIController(BaseAPIController):
PUT /api/samples/{encoded_sample_id}
Updates a sample or objects related ( mapped ) to a sample.
"""
+ if not trans.app.config.enable_legacy_sample_tracking_api:
+ trans.response.status = 403
+ return "The configuration of this Galaxy instance does not allow accessing this API."
update_type = None
if 'update_type' not in payload:
trans.response.status = 400
@@ -69,7 +75,7 @@ class SamplesAPIController(BaseAPIController):
return "Malformed sample_id (%s) specified, unable to decode." % str(sample_id)
try:
sample = trans.sa_session.query(trans.app.model.Sample).get(decoded_sample_id)
- except:
+ except Exception:
sample = None
if not sample:
trans.response.status = 400
@@ -83,7 +89,7 @@ class SamplesAPIController(BaseAPIController):
if deferred_plugin:
try:
trans.app.job_manager.deferred_job_queue.plugins[deferred_plugin].create_job(trans, sample=sample, **payload)
- except:
+ except Exception:
log.exception('update() called with a deferred job plugin (%s) but creating the deferred job failed:' % deferred_plugin)
status, output = requests_admin_controller.edit_template_info(trans,
cntrller='api',
diff --git a/lib/galaxy/webapps/galaxy/api/tool_data.py b/lib/galaxy/webapps/galaxy/api/tool_data.py
index 0fbf91c5f22..98b109d7bf9 100644
--- a/lib/galaxy/webapps/galaxy/api/tool_data.py
+++ b/lib/galaxy/webapps/galaxy/api/tool_data.py
@@ -1,11 +1,15 @@
import os
-from galaxy import exceptions
-from galaxy import web
-from galaxy.web import _future_expose_api as expose_api
-from galaxy.web import _future_expose_api_raw as expose_api_raw
-from galaxy.web.base.controller import BaseAPIController
import galaxy.queue_worker
+from galaxy import (
+ exceptions,
+ web
+)
+from galaxy.web import (
+ _future_expose_api as expose_api,
+ _future_expose_api_raw as expose_api_raw
+)
+from galaxy.web.base.controller import BaseAPIController
class ToolData(BaseAPIController):
@@ -62,7 +66,7 @@ class ToolData(BaseAPIController):
try:
data_table = trans.app.tool_data_tables.data_tables.get(decoded_tool_data_id)
- except:
+ except Exception:
data_table = None
if not data_table:
trans.response.status = 400
diff --git a/lib/galaxy/webapps/galaxy/api/users.py b/lib/galaxy/webapps/galaxy/api/users.py
index 63c9e3a78a0..4db33275697 100644
--- a/lib/galaxy/webapps/galaxy/api/users.py
+++ b/lib/galaxy/webapps/galaxy/api/users.py
@@ -179,7 +179,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cre
if not trans.user_is_admin():
assert trans.user == user
assert not user.deleted
- except:
+ except Exception:
raise exceptions.RequestParameterInvalidException('Invalid user id specified', id=id)
return self.user_serializer.serialize_to_view(user, view='detailed')
@@ -283,7 +283,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cre
try:
with open(path, 'r') as stream:
config = yaml.safe_load(stream)
- except:
+ except Exception:
log.warning('Config file (%s) could not be found or is malformed.' % path)
return {}
@@ -889,7 +889,7 @@ class UserAPIController(BaseAPIController, UsesTagsMixin, CreatesUsersMixin, Cre
new_linecount = new_len.get_converted_dataset(trans, 'linecount')
build_dict['len'] = new_len.id
build_dict['linecount'] = new_linecount.id
- except:
+ except Exception:
raise MessageException('Failed to convert dataset.')
dbkeys[key] = build_dict
user.preferences['dbkeys'] = json.dumps(dbkeys)
diff --git a/lib/galaxy/webapps/galaxy/api/workflows.py b/lib/galaxy/webapps/galaxy/api/workflows.py
index b1cc2711906..ebf18f32da9 100644
--- a/lib/galaxy/webapps/galaxy/api/workflows.py
+++ b/lib/galaxy/webapps/galaxy/api/workflows.py
@@ -600,7 +600,7 @@ class WorkflowsAPIController(BaseAPIController, UsesStoredWorkflowMixin, UsesAnn
def __api_import_shared_workflow(self, trans, workflow_id, payload, **kwd):
try:
stored_workflow = self.get_stored_workflow(trans, workflow_id, check_ownership=False)
- except:
+ except Exception:
raise exceptions.ObjectNotFound("Malformed workflow id ( %s ) specified." % workflow_id)
if stored_workflow.importable is False:
raise exceptions.ItemAccessibilityException('The owner of this workflow has disabled imports via this link.')
diff --git a/lib/galaxy/webapps/galaxy/buildapp.py b/lib/galaxy/webapps/galaxy/buildapp.py
index 00c63e0bf47..ba92d5b35e9 100644
--- a/lib/galaxy/webapps/galaxy/buildapp.py
+++ b/lib/galaxy/webapps/galaxy/buildapp.py
@@ -53,7 +53,7 @@ def paste_app_factory(global_conf, **kwargs):
try:
app = galaxy.app.UniverseApplication(global_conf=global_conf, **kwargs)
galaxy.app.app = app
- except:
+ except Exception:
traceback.print_exc()
sys.exit(1)
# Call app's shutdown method when the interpeter exits, this cleanly stops
@@ -145,13 +145,13 @@ def paste_app_factory(global_conf, **kwargs):
# Close any pooled database connections before forking
try:
galaxy.model.mapping.metadata.bind.dispose()
- except:
+ except Exception:
log.exception("Unable to dispose of pooled galaxy model database connections.")
try:
# 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:
+ except Exception:
log.exception("Unable to dispose of pooled toolshed install model database connections.")
app.application_stack.register_postfork_function(postfork_setup)
diff --git a/lib/galaxy/webapps/galaxy/controllers/dataset.py b/lib/galaxy/webapps/galaxy/controllers/dataset.py
index c93c37e28a6..cf7ffc324ff 100644
--- a/lib/galaxy/webapps/galaxy/controllers/dataset.py
+++ b/lib/galaxy/webapps/galaxy/controllers/dataset.py
@@ -2,17 +2,26 @@ import logging
import os
import urllib
-from markupsafe import escape
import paste.httpexceptions
+from markupsafe import escape
from six import string_types, text_type
from sqlalchemy import false, true
-from galaxy import datatypes, model, util, web
-from galaxy import managers
+from galaxy import (
+ datatypes,
+ managers,
+ model,
+ util,
+ web
+)
from galaxy.datatypes.display_applications.util import decode_dataset_user, encode_dataset_user
from galaxy.exceptions import RequestParameterInvalidException
from galaxy.model.item_attrs import UsesAnnotations, UsesItemRatings
-from galaxy.util import inflector, smart_str, sanitize_text
+from galaxy.util import (
+ inflector,
+ sanitize_text,
+ smart_str
+)
from galaxy.util.sanitize_html import sanitize_html
from galaxy.web import form_builder
from galaxy.web.base.controller import BaseUIController, ERROR, SUCCESS, url_for, UsesExtendedMetadataMixin
@@ -131,7 +140,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
try:
job = self._get_job_for_dataset(trans, dataset_id)
stdout = job.stdout
- except:
+ except Exception:
stdout = "Invalid dataset ID or you are not allowed to access this dataset"
return smart_str(stdout)
@@ -143,7 +152,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
try:
job = self._get_job_for_dataset(trans, dataset_id)
stderr = job.stderr
- except:
+ except Exception:
stderr = "Invalid dataset ID or you are not allowed to access this dataset"
return smart_str(stderr)
@@ -154,7 +163,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
try:
job = self._get_job_for_dataset(trans, dataset_id)
exit_code = job.exit_code
- except:
+ except Exception:
exit_code = "Invalid dataset ID or you are not allowed to access this dataset"
return exit_code
@@ -182,10 +191,10 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
data = trans.sa_session.query(trans.app.model.HistoryDatasetAssociation).get(self.decode_id(hda_id))
if data is None:
raise ValueError('Invalid reference dataset id: %s.' % hda_id)
- except:
+ except Exception:
try:
data = trans.sa_session.query(trans.app.model.HistoryDatasetAssociation).get(int(hda_id))
- except:
+ except Exception:
data = None
if not data:
raise paste.httpexceptions.HTTPRequestRangeNotSatisfiable("Invalid reference dataset id: %s." % str(hda_id))
@@ -722,7 +731,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
return trans.show_error_message('Invalid parameters specified for "display at" link, please contact a Galaxy administrator')
try:
redirect_url = kwd['redirect_url'] % urllib.quote_plus(kwd['display_url'])
- except:
+ except Exception:
redirect_url = kwd['redirect_url'] # not all will need custom text
if trans.app.security_agent.dataset_is_public(data.dataset):
return trans.response.send_redirect(redirect_url) # anon access already permitted by rbac
@@ -901,7 +910,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
def _unhide(self, trans, dataset_id):
try:
id = self.decode_id(dataset_id)
- except:
+ except Exception:
return False
history = trans.get_history()
hda = trans.sa_session.query(self.app.model.HistoryDatasetAssociation).get(id)
@@ -959,7 +968,7 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
hda.dataset.full_delete()
trans.log_event("Dataset id %s has been purged upon the the purge of HDA id %s" % (hda.dataset.id, hda.id))
trans.sa_session.add(hda.dataset)
- except:
+ except Exception:
log.exception('Unable to purge dataset (%s) on purge of HDA (%s):' % (hda.dataset.id, hda.id))
trans.sa_session.flush()
except Exception as exc:
@@ -1061,14 +1070,14 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
# Load parameter objects, if a parameter type has changed, it's possible for the value to no longer be valid
try:
params_objects = job.get_param_values(trans.app, ignore_errors=False)
- except:
+ except Exception:
params_objects = job.get_param_values(trans.app, ignore_errors=True)
# use different param_objects in the following line, since we want to display original values as much as possible
upgrade_messages = tool.check_and_update_param_values(job.get_param_values(trans.app, ignore_errors=True),
trans,
update_values=False)
has_parameter_errors = True
- except:
+ except Exception:
pass
if job is None:
return trans.show_error_message("Job information is not available for this dataset.")
diff --git a/lib/galaxy/webapps/galaxy/controllers/external_service.py b/lib/galaxy/webapps/galaxy/controllers/external_service.py
deleted file mode 100644
index bb78257241d..00000000000
--- a/lib/galaxy/webapps/galaxy/controllers/external_service.py
+++ /dev/null
@@ -1,369 +0,0 @@
-from __future__ import absolute_import
-
-import logging
-
-from galaxy import model, util
-from markupsafe import escape
-from galaxy.web.base.controller import BaseUIController, web, UsesFormDefinitionsMixin
-from galaxy.web.form_builder import TextField, SelectField
-from galaxy.web.framework.helpers import time_ago, iff, grids
-from .requests_common import invalid_id_redirect
-
-log = logging.getLogger(__name__)
-
-
-class ExternalServiceGrid(grids.Grid):
- # Custom column types
- class NameColumn(grids.TextColumn):
- def get_value(self, trans, grid, external_service):
- return escape(external_service.name)
-
- class ExternalServiceTypeColumn(grids.TextColumn):
- def get_value(self, trans, grid, external_service):
- try:
- return trans.app.external_service_types.all_external_service_types[external_service.external_service_type_id].name
- except KeyError:
- return 'Error in loading external_service type: %s' % external_service.external_service_type_id
-
- # Grid definition
- title = "External Services"
- template = "admin/external_service/grid.mako"
- model_class = model.ExternalService
- default_sort_key = "-create_time"
- num_rows_per_page = 50
- preserve_state = True
- use_paging = True
- default_filter = dict(deleted="False")
- columns = [
- NameColumn("Name",
- key="name",
- link=(lambda item: iff(item.deleted, None, dict(operation="view", id=item.id))),
- attach_popup=True,
- filterable="advanced"),
- grids.TextColumn("Description",
- key='description',
- filterable="advanced"),
- ExternalServiceTypeColumn("External Service Type"),
- grids.GridColumn("Last Updated",
- key="update_time",
- format=time_ago),
- grids.DeletedColumn("Deleted",
- key="deleted",
- visible=False,
- filterable="advanced")
- ]
- columns.append(grids.MulticolFilterColumn("Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard"))
- operations = [
- grids.GridOperation("Edit", allow_multiple=False, condition=(lambda item: not item.deleted)),
- grids.GridOperation("Delete", allow_multiple=True, condition=(lambda item: not item.deleted)),
- grids.GridOperation("Undelete", condition=(lambda item: item.deleted)),
- ]
- global_actions = [
- grids.GridAction("Reload external service types", dict(controller='external_service', action='reload_external_service_types')),
- grids.GridAction("Create new external service", dict(controller='external_service', action='create_external_service'))
- ]
-
-
-class ExternalService(BaseUIController, UsesFormDefinitionsMixin):
- external_service_grid = ExternalServiceGrid()
-
- @web.expose
- @web.require_admin
- def browse_external_services(self, trans, **kwd):
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- if operation == "view":
- return self.view_external_service(trans, **kwd)
- elif operation == "edit":
- return self.edit_external_service(trans, **kwd)
- elif operation == "delete":
- return self.delete_external_service(trans, **kwd)
- elif operation == "undelete":
- return self.undelete_external_service(trans, **kwd)
- # Render the grid view
- return self.external_service_grid(trans, **kwd)
-
- @web.expose
- @web.require_admin
- def create_external_service(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- external_service_type_id = params.get('external_service_type_id', 'none')
- widgets = self.__build_external_service_widgets(trans, external_service=None, **kwd)
- external_service_type = None
- error = False
- if not trans.app.external_service_types.visible_external_service_types:
- error = True
- message = 'There are no visible external_service types in the external_service types config file'
- elif params.get('create_external_service_button', False):
- if external_service_type_id == 'none':
- error = True
- message = 'Provide an external_service_type_id to create a new external service.'
- else:
- self.__save_external_service(trans, **kwd)
- message = 'The external_service has been created.'
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='browse_external_services',
- message=message,
- status=status))
- elif external_service_type_id != 'none':
- # Form submission via refresh_on_change
- trans.app.external_service_types.reload(external_service_type_id)
- external_service_type = self.get_external_service_type(trans, external_service_type_id)
- widgets.extend(external_service_type.form_definition.get_widgets(trans.user, **kwd))
- if error:
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='browse_external_services',
- message=message,
- status='error'))
- return trans.fill_template('/admin/external_service/create_external_service.mako',
- widgets=widgets,
- message=message,
- status=status,
- external_service_type=external_service_type)
-
- @web.expose
- @web.require_admin
- def view_external_service(self, trans, **kwd):
- external_service_id = kwd.get('id', None)
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- except:
- return invalid_id_redirect(trans, 'external_service', external_service_id, 'external_service', action='browse_external_services')
- external_service_type = self.get_external_service_type(trans, external_service.external_service_type_id)
- return trans.fill_template('/admin/external_service/view_external_service.mako',
- external_service=external_service,
- external_service_type=external_service_type)
-
- @web.expose
- @web.require_admin
- def edit_external_service(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- external_service_id = params.get('id', None)
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- except:
- return invalid_id_redirect(trans, 'external_service', external_service_id, 'external_service', action='browse_external_services')
- if params.get('edit_external_service_button', False):
- external_service = self.__save_external_service(trans, **kwd)
- trans.sa_session.refresh(external_service)
- message = 'Changes made to external service (%s) have been saved' % external_service.name
- widgets = self.__build_external_service_widgets(trans, external_service, **kwd)
- widgets.extend(external_service.form_definition.get_widgets(trans.user, external_service.form_values.content, **kwd))
- external_service_type = self.get_external_service_type(trans, external_service.external_service_type_id)
- return trans.fill_template('/admin/external_service/edit_external_service.mako',
- external_service=external_service,
- widgets=widgets,
- message=message,
- status=status,
- external_service_type=external_service_type)
-
- def __save_external_service(self, trans, **kwd):
- # Here we save a newly created external_service or save changed
- # attributes of an existing external_service.
- params = util.Params(kwd)
- external_service_id = params.get('id', None)
- name = util.restore_text(params.get('external_service_name', ''))
- description = util.restore_text(params.get('external_service_description', ''))
- version = util.restore_text(params.get('external_service_version', ''))
- external_service_type_id = params.get('external_service_type_id', '')
- if external_service_id:
- # We're saving changed attributes of an existing external_service.
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- external_service.name = name
- external_service.description = description
- external_service.version = version
- external_service.form_values.content = self.get_form_values(trans, trans.user, external_service.form_definition, **kwd)
- trans.sa_session.add(external_service)
- trans.sa_session.add(external_service.form_values)
- trans.sa_session.flush()
- else:
- # We're saving a newly created external_service
- external_service_type = self.get_external_service_type(trans, external_service_type_id)
- external_service = trans.model.ExternalService(name, description, external_service_type_id, version)
- external_service.form_definition = external_service_type.form_definition
- # Get the form values from kwd, some of which may be different than the defaults in the external service
- # type config because the user could have overwritten them.
- values = self.get_form_values(trans, trans.user, external_service.form_definition, **kwd)
- external_service.form_values = trans.model.FormValues(external_service.form_definition, values)
- trans.sa_session.add(external_service)
- trans.sa_session.add(external_service.form_definition)
- trans.sa_session.add(external_service.form_values)
- trans.sa_session.flush()
- return external_service
-
- @web.expose
- @web.require_admin
- def edit_external_service_form_definition(self, trans, **kwd):
- util.Params(kwd)
- external_service_id = kwd.get('id', None)
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- except:
- return invalid_id_redirect(trans, 'external_service', external_service_id, 'external_service', action='browse_external_services')
- vars = dict(id=trans.security.encode_id(external_service.form_definition.form_definition_current_id),
- response_redirect=web.url_for(controller='external_service',
- action='update_external_service_form_definition',
- **kwd))
- return trans.response.send_redirect(web.url_for(controller='forms', action='edit_form_definition', **vars))
-
- @web.expose
- @web.require_admin
- def update_external_service_form_definition(self, trans, **kwd):
- util.Params(kwd)
- external_service_id = kwd.get('id', None)
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- except:
- return invalid_id_redirect(trans, 'external_service', external_service_id, 'external_service', action='browse_external_services')
- external_service.form_definition = external_service.form_definition.current.latest_form
- trans.sa_session.add(external_service)
- trans.sa_session.flush()
- message = "The form definition for the '%s' external service has been updated with your changes." % external_service.name
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='edit_external_service',
- message=message,
- status='done',
- **kwd))
-
- @web.expose
- @web.require_admin
- def delete_external_service(self, trans, **kwd):
- external_service_id = kwd.get('id', '')
- external_service_id_list = util.listify(external_service_id)
- for external_service_id in external_service_id_list:
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- except:
- return invalid_id_redirect(trans, 'external_service', external_service_id, 'external_service', action='browse_external_services')
- external_service.deleted = True
- trans.sa_session.add(external_service)
- trans.sa_session.flush()
- message = '%i external services has been deleted' % len(external_service_id_list)
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='browse_external_services',
- message=message,
- status='done'))
-
- @web.expose
- @web.require_admin
- def undelete_external_service(self, trans, **kwd):
- external_service_id = kwd.get('id', '')
- external_service_id_list = util.listify(external_service_id)
- for external_service_id in external_service_id_list:
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- except:
- return invalid_id_redirect(trans, 'external_service', external_service_id, 'external_service', action='browse_external_services')
- external_service.deleted = False
- trans.sa_session.add(external_service)
- trans.sa_session.flush()
- status = 'done'
- message = '%i external services have been undeleted' % len(external_service_id_list)
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='browse_external_services',
- message=message,
- status=status))
-
- @web.expose
- @web.require_admin
- def reload_external_service_types(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- external_service_type_id = params.get('external_service_type_id', 'none')
- if params.get('reload_external_service_type_button', False):
- new_external_service_type = trans.app.external_service_types.reload(external_service_type_id)
- status = 'done'
- message = 'Reloaded external service type: %s' % new_external_service_type.name
- external_service_type_select_field = self.__build_external_service_type_select_field(trans,
- external_service_type_id,
- refresh_on_change=False,
- visible_external_service_types_only=False)
- if not trans.app.external_service_types.visible_external_service_types:
- message = 'There are no visible external service types in the external service types config file.'
- status = 'error'
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='browse_external_services',
- message=message,
- status=status))
- return trans.fill_template('/admin/external_service/reload_external_service_types.mako',
- external_service_type_select_field=external_service_type_select_field,
- message=message,
- status=status)
-
- def get_external_service_type(self, trans, external_service_type_id, action='browse_external_services'):
- try:
- return trans.app.external_service_types.all_external_service_types[external_service_type_id]
- except KeyError:
- message = 'Error in loading external service type: %s' % external_service_type_id
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action=action,
- message=message,
- status='error'))
-
- # ===== Methods for building SelectFields used on various admin_requests forms
- def __build_external_service_widgets(self, trans, external_service=None, **kwd):
- params = util.Params(kwd)
- if external_service:
- name = external_service.name
- description = external_service.description
- version = external_service.version
- seq_type = external_service.external_service_type_id
- else:
- name = util.restore_text(params.get('external_service_name', ''))
- description = util.restore_text(params.get('external_service_description', ''))
- version = util.restore_text(params.get('external_service_version', ''))
- selected_seq_type = params.get('external_service_type_id', '')
- if selected_seq_type in trans.app.external_service_types.all_external_service_types:
- seq_type = trans.app.external_service_types.all_external_service_types[selected_seq_type].id
- else:
- seq_type = 'none'
- widgets = [dict(label='Name',
- widget=TextField('external_service_name', 40, name),
- helptext=''),
- dict(label='Description',
- widget=TextField('external_service_description', 40, description),
- helptext=''),
- dict(label='Version',
- widget=TextField('external_service_version', 40, version),
- helptext='')]
- # Do not show the external_service_type selectfield when editing a external_service
- if not external_service:
- widgets.append(dict(label='External service type',
- widget=self.__build_external_service_type_select_field(trans, seq_type, visible_external_service_types_only=True),
- helptext=''))
- return widgets
-
- def __build_external_service_type_select_field(self, trans, selected_value, refresh_on_change=True, visible_external_service_types_only=False):
- external_service_types = trans.app.external_service_types.all_external_service_types
- if visible_external_service_types_only:
- objs_list = [external_service_types[seq_type_id] for seq_type_id in trans.app.external_service_types.visible_external_service_types]
- else:
- objs_list = external_service_types.values()
- refresh_on_change_values = ['none']
- refresh_on_change_values.extend([trans.security.encode_id(obj.id) for obj in objs_list])
- select_external_service_type = SelectField('external_service_type_id',
- refresh_on_change=refresh_on_change,
- refresh_on_change_values=refresh_on_change_values)
- if selected_value == 'none':
- select_external_service_type.add_option('Select one', 'none', selected=True)
- else:
- select_external_service_type.add_option('Select one', 'none')
- for seq_type in objs_list:
- if seq_type.version:
- option_name = " ".join([seq_type.name, "version", seq_type.version])
- else:
- option_name = seq_type.name
- if selected_value == seq_type.id:
- select_external_service_type.add_option(option_name, seq_type.id, selected=True)
- else:
- select_external_service_type.add_option(option_name, seq_type.id)
- return select_external_service_type
diff --git a/lib/galaxy/webapps/galaxy/controllers/external_services.py b/lib/galaxy/webapps/galaxy/controllers/external_services.py
deleted file mode 100644
index af51eb5ba23..00000000000
--- a/lib/galaxy/webapps/galaxy/controllers/external_services.py
+++ /dev/null
@@ -1,28 +0,0 @@
-import logging
-
-from galaxy import web
-from galaxy.model import ExternalService, Sample
-from galaxy.web.base.controller import BaseUIController
-
-log = logging.getLogger(__name__)
-
-class_name_to_class = {}
-
-for model_class in [Sample]:
- class_name_to_class[model_class.__name__] = model_class
-
-
-class ExternalServiceController(BaseUIController):
- @web.expose
- @web.require_admin
- def access_action(self, trans, external_service_action, item, item_type, **kwd):
- if item_type in class_name_to_class:
- item_type = class_name_to_class.get(item_type)
- item = item_type.get(item)
- external_service_action_parsed = external_service_action.split('|')
- populated_external_service = ExternalService.get(external_service_action_parsed.pop(0)).populate_actions(trans, item)
- populated_action = populated_external_service.perform_action_by_name(external_service_action_parsed)
- results = populated_action.handle_results(trans)
- return results
- else:
- raise Exception('unknown item class type')
diff --git a/lib/galaxy/webapps/galaxy/controllers/library.py b/lib/galaxy/webapps/galaxy/controllers/library.py
index 7dbac8ca561..dacb9e8c6bf 100644
--- a/lib/galaxy/webapps/galaxy/controllers/library.py
+++ b/lib/galaxy/webapps/galaxy/controllers/library.py
@@ -91,6 +91,7 @@ class Library(BaseUIController):
'app': app})
@web.expose
+ @web.require_admin
def index(self, trans, **kwd):
message = escape(kwd.get('message', ''))
status = escape(kwd.get('status', 'done'))
@@ -101,6 +102,7 @@ class Library(BaseUIController):
status=status)
@web.expose
+ @web.require_admin
def browse_libraries(self, trans, **kwd):
if 'operation' in kwd:
operation = kwd['operation'].lower()
diff --git a/lib/galaxy/webapps/galaxy/controllers/library_common.py b/lib/galaxy/webapps/galaxy/controllers/library_common.py
index a32c264b98a..c977f319600 100644
--- a/lib/galaxy/webapps/galaxy/controllers/library_common.py
+++ b/lib/galaxy/webapps/galaxy/controllers/library_common.py
@@ -26,6 +26,11 @@ from galaxy.util import (
inflector,
unicodify
)
+from galaxy.util.path import (
+ safe_contains,
+ safe_relpath,
+ unsafe_walk
+)
from galaxy.util.streamball import StreamBall
from galaxy.web.base.controller import (
BaseUIController,
@@ -79,12 +84,13 @@ except ImportError:
try:
os.rmdir(tmpd)
-except:
+except Exception:
pass
class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMetadataMixin, UsesLibraryMixinItems):
@web.json
+ @web.require_admin
def library_item_updates(self, trans, ids=None, states=None):
# Avoid caching
trans.response.headers['Pragma'] = 'no-cache'
@@ -108,6 +114,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
return rval
@web.expose
+ @web.require_admin
def browse_library(self, trans, cntrller='library', **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -124,7 +131,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
library = trans.sa_session.query(trans.app.model.Library).get(trans.security.decode_id(library_id))
- except:
+ except Exception:
# Protect against attempts to phish for valid keys that return libraries
library = None
# Most security for browsing libraries is handled in the template, but do a basic check here.
@@ -180,6 +187,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=status))
@web.expose
+ @web.require_admin
def library_info(self, trans, cntrller, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -190,7 +198,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
library_id = kwd.get('id', None)
try:
library = trans.sa_session.query(trans.app.model.Library).get(trans.security.decode_id(library_id))
- except:
+ except Exception:
library = None
self._check_access(trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted)
if kwd.get('library_info_button', False):
@@ -239,6 +247,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def library_permissions(self, trans, cntrller, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -249,7 +258,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
library_id = kwd.get('id', None)
try:
library = trans.sa_session.query(trans.app.model.Library).get(trans.security.decode_id(library_id))
- except:
+ except Exception:
library = None
self._check_access(trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted)
self._check_manage(trans, cntrller, is_admin, library, current_user_roles, use_panels, library_id, show_deleted)
@@ -295,7 +304,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
parent_folder = trans.sa_session.query(trans.app.model.LibraryFolder).get(trans.security.decode_id(parent_id))
- except:
+ except Exception:
parent_folder = None
# Check the library which actually contains the user-supplied parent folder, not the user-supplied
# library, which could be anything.
@@ -361,6 +370,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def folder_info(self, trans, cntrller, id, library_id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -370,7 +380,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
folder = trans.sa_session.query(trans.app.model.LibraryFolder).get(trans.security.decode_id(id))
- except:
+ except Exception:
folder = None
self._check_access(trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted)
if kwd.get('rename_folder_button', False):
@@ -417,6 +427,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def folder_permissions(self, trans, cntrller, id, library_id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -426,7 +437,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
folder = trans.sa_session.query(trans.app.model.LibraryFolder).get(trans.security.decode_id(id))
- except:
+ except Exception:
folder = None
self._check_access(trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted)
self._check_manage(trans, cntrller, is_admin, folder, current_user_roles, use_panels, library_id, show_deleted)
@@ -467,6 +478,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def ldda_edit_info(self, trans, cntrller, library_id, folder_id, id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -476,7 +488,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
ldda = trans.sa_session.query(trans.app.model.LibraryDatasetDatasetAssociation).get(trans.security.decode_id(id))
- except:
+ except Exception:
ldda = None
self._check_access(trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted)
self._check_modify(trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted)
@@ -618,6 +630,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def ldda_info(self, trans, cntrller, library_id, folder_id, id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -667,6 +680,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def ldda_permissions(self, trans, cntrller, library_id, folder_id, id, **kwd):
message = str(escape(kwd.get('message', '')))
status = kwd.get('status', 'done')
@@ -680,7 +694,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
for id in ids:
try:
ldda = trans.sa_session.query(trans.app.model.LibraryDatasetDatasetAssociation).get(trans.security.decode_id(id))
- except:
+ except Exception:
ldda = None
if ldda:
library = ldda.library_dataset.folder.parent_library
@@ -864,6 +878,9 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
elif upload_option == 'upload_paths' and not is_admin:
error = True
message = 'Uploading files via filesystem paths can only be performed by administrators'
+ elif upload_option not in ('upload_file', 'upload_directory', 'upload_paths'):
+ error = True
+ message = 'Invalid upload_option'
elif roles:
# Check to see if the user selected roles to associate with the DATASET_ACCESS permission
# on the dataset that would cause accessibility issues.
@@ -1079,17 +1096,26 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
if upload_option == 'upload_directory':
if server_dir in [None, 'None', '']:
response_code = 400
- if cntrller == 'library_admin' or (cntrller == 'api' and trans.user_is_admin):
+ if trans.user_is_admin() and cntrller in ('library_admin', 'api'):
import_dir = trans.app.config.library_import_dir
import_dir_desc = 'library_import_dir'
- full_dir = os.path.join(import_dir, server_dir)
else:
import_dir = trans.app.config.user_library_import_dir
+ if server_dir != trans.user.email:
+ import_dir = os.path.join(import_dir, trans.user.email)
import_dir_desc = 'user_library_import_dir'
- if server_dir == trans.user.email:
- full_dir = os.path.join(import_dir, server_dir)
- else:
- full_dir = os.path.join(import_dir, trans.user.email, server_dir)
+ full_dir = os.path.join(import_dir, server_dir)
+ unsafe = None
+ if safe_relpath(server_dir):
+ if import_dir_desc == 'user_library_import_dir' and safe_contains(import_dir, full_dir, whitelist=trans.app.config.user_library_import_symlink_whitelist):
+ for unsafe in unsafe_walk(full_dir, whitelist=[import_dir] + trans.app.config.user_library_import_symlink_whitelist):
+ log.error('User attempted to import a path that resolves to a path outside of their import dir: %s -> %s', unsafe, os.path.realpath(unsafe))
+ else:
+ log.error('User attempted to import a directory path that resolves to a path outside of their import dir: %s -> %s', server_dir, os.path.realpath(full_dir))
+ unsafe = True
+ if unsafe:
+ response_code = 403
+ message = 'Invalid server_dir'
if import_dir:
message = 'Select a directory'
else:
@@ -1104,7 +1130,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
# FIXME: instead of passing params here ( which have been processed by util.Params(), the original kwd
# should be passed so that complex objects that may have been included in the initial request remain.
library_bunch = upload_common.handle_library_params(trans, kwd, folder_id, replace_dataset)
- except:
+ except Exception:
response_code = 500
message = "Unable to parse upload parameters, please report this error."
# Proceed with (mostly) regular upload processing if we're still errorless
@@ -1283,6 +1309,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
return None
@web.expose
+ @web.require_admin
def add_history_datasets_to_library(self, trans, cntrller, library_id, folder_id, hda_ids='', **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -1314,7 +1341,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
if replace_id not in [None, 'None']:
try:
replace_dataset = trans.sa_session.query(trans.app.model.LibraryDataset).get(trans.security.decode_id(replace_id))
- except:
+ except Exception:
replace_dataset = None
self._check_access(trans, cntrller, is_admin, replace_dataset, current_user_roles, use_panels, library_id, show_deleted)
self._check_modify(trans, cntrller, is_admin, replace_dataset, current_user_roles, use_panels, library_id, show_deleted)
@@ -1350,7 +1377,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
for hda_id in hda_ids:
try:
hda = trans.sa_session.query(trans.app.model.HistoryDatasetAssociation).get(trans.security.decode_id(hda_id))
- except:
+ except Exception:
hda = None
self._check_access(trans, cntrller, is_admin, hda, current_user_roles, use_panels, library_id, show_deleted)
if roles:
@@ -1516,7 +1543,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
if not os.path.isdir(path):
try:
os.makedirs(path)
- except:
+ except Exception:
continue
elif option_value == 'upload_paths':
if not is_admin or not trans.app.config.allow_library_path_paste:
@@ -1525,6 +1552,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
return upload_option_select_list
@web.expose
+ @web.require_admin
def download_dataset_from_folder(self, trans, cntrller, id, library_id=None, **kwd):
"""Catches the dataset id and displays file contents as directed"""
show_deleted = util.string_as_bool(kwd.get('show_deleted', False))
@@ -1533,7 +1561,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
ldda = trans.sa_session.query(trans.app.model.LibraryDatasetDatasetAssociation).get(trans.security.decode_id(id))
- except:
+ except Exception:
ldda = None
self._check_access(trans, cntrller, is_admin, ldda, current_user_roles, use_panels, library_id, show_deleted)
composite_extensions = trans.app.datatypes_registry.get_composite_extensions()
@@ -1552,7 +1580,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
trans.response.headers["Content-Disposition"] = 'attachment; filename="%s"' % fname
try:
return open(ldda.file_name)
- except:
+ except Exception:
message = 'This dataset contains no content'
return trans.response.send_redirect(web.url_for(controller='library_common',
action='browse_library',
@@ -1564,6 +1592,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status='error'))
@web.expose
+ @web.require_admin
def library_dataset_info(self, trans, cntrller, id, library_id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -1573,7 +1602,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
library_dataset = trans.sa_session.query(trans.app.model.LibraryDataset).get(trans.security.decode_id(id))
- except:
+ except Exception:
library_dataset = None
self._check_access(trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted)
if kwd.get('edit_attributes_button', False):
@@ -1612,6 +1641,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def library_dataset_permissions(self, trans, cntrller, id, library_id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -1621,7 +1651,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
current_user_roles = trans.get_current_user_roles()
try:
library_dataset = trans.sa_session.query(trans.app.model.LibraryDataset).get(trans.security.decode_id(id))
- except:
+ except Exception:
library_dataset = None
self._check_access(trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted)
self._check_manage(trans, cntrller, is_admin, library_dataset, current_user_roles, use_panels, library_id, show_deleted)
@@ -1660,6 +1690,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def make_library_item_public(self, trans, cntrller, library_id, item_type, id, **kwd):
message = escape(kwd.get('message', ''))
status = kwd.get('status', 'done')
@@ -1702,6 +1733,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=status))
@web.expose
+ @web.require_admin
def act_on_multiple_datasets(self, trans, cntrller, library_id=None, ldda_ids='', **kwd):
# This method is called from 1 of 3 places:
# - this controller's download_dataset_from_folder() method
@@ -1785,7 +1817,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
ldda = self.get_library_dataset_dataset_association(trans, ldda_id)
assert not ldda.dataset.purged
lddas.append(ldda)
- except:
+ except Exception:
ldda = None
message += "Invalid library dataset id (%s) specified. " % str(ldda_id)
if not error:
@@ -1877,7 +1909,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
log.exception("Unable to create archive for download")
message = "Unable to create archive for download, please report this error"
status = 'error'
- except:
+ except Exception:
error = True
log.exception("Unexpected error in create archive for download")
message = "Unable to create archive for download, please report - %s" % sys.exc_info()[0]
@@ -1994,6 +2026,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def import_datasets_to_histories(self, trans, cntrller, library_id='', folder_id='', ldda_ids='', target_history_id='', target_history_ids='', new_history_name='', **kwd):
# This method is called from one of the following places:
# - a menu option for a library dataset ( ldda_ids is a single ldda id )
@@ -2124,6 +2157,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def manage_template_inheritance(self, trans, cntrller, item_type, library_id, folder_id=None, ldda_id=None, **kwd):
show_deleted = util.string_as_bool(kwd.get('show_deleted', False))
use_panels = util.string_as_bool(kwd.get('use_panels', False))
@@ -2169,6 +2203,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status='done'))
@web.expose
+ @web.require_admin
def move_library_item(self, trans, cntrller, item_type, item_id, source_library_id='', make_target_current=True, **kwd):
# This method is called from one of the following places:
# - a menu option for a library dataset ( item_type is 'ldda' and item_id is a single ldda id )
@@ -2386,6 +2421,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=escape(status))
@web.expose
+ @web.require_admin
def delete_library_item(self, trans, cntrller, library_id, item_id, item_type, **kwd):
# This action will handle deleting all types of library items. State is saved for libraries and
# folders ( i.e., if undeleted, the state of contents of the library or folder will remain, so previously
@@ -2418,7 +2454,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
for library_item_id in library_item_ids:
try:
library_item = trans.sa_session.query(item_types[item_type]).get(trans.security.decode_id(library_item_id))
- except:
+ except Exception:
library_item = None
if not library_item or not (is_admin or trans.app.security_agent.can_access_library_item(current_user_roles, library_item, trans.user)):
invalid_items += 1
@@ -2454,6 +2490,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
status=status))
@web.expose
+ @web.require_admin
def undelete_library_item(self, trans, cntrller, library_id, item_id, item_type, **kwd):
# This action will handle undeleting all types of library items
status = kwd.get('status', 'done')
@@ -2482,7 +2519,7 @@ class LibraryCommon(BaseUIController, UsesFormDefinitionsMixin, UsesExtendedMeta
for library_item_id in library_item_ids:
try:
library_item = trans.sa_session.query(item_types[item_type]).get(trans.security.decode_id(library_item_id))
- except:
+ except Exception:
library_item = None
if not library_item or not (is_admin or trans.app.security_agent.can_access_library_item(current_user_roles, library_item, trans.user)):
invalid_items += 1
@@ -2735,7 +2772,7 @@ def get_comptypes(trans):
# exception every time after the first time)
try:
comptypes_t.remove(comptype)
- except:
+ except ValueError:
pass
return comptypes_t
diff --git a/lib/galaxy/webapps/galaxy/controllers/request_type.py b/lib/galaxy/webapps/galaxy/controllers/request_type.py
deleted file mode 100644
index 2534da88976..00000000000
--- a/lib/galaxy/webapps/galaxy/controllers/request_type.py
+++ /dev/null
@@ -1,477 +0,0 @@
-from __future__ import absolute_import
-
-import logging
-
-from sqlalchemy import false
-from markupsafe import escape
-
-from galaxy import model, util
-from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin, web
-from galaxy.web.form_builder import build_select_field, TextField
-from galaxy.web.framework.helpers import iff, grids
-from .requests_common import invalid_id_redirect
-
-log = logging.getLogger(__name__)
-
-
-class RequestTypeGrid(grids.Grid):
- # Custom column types
- class NameColumn(grids.TextColumn):
- def get_value(self, trans, grid, request_type):
- return escape(request_type.name)
-
- class DescriptionColumn(grids.TextColumn):
- def get_value(self, trans, grid, request_type):
- return escape(request_type.desc)
-
- class RequestFormColumn(grids.TextColumn):
- def get_value(self, trans, grid, request_type):
- return escape(request_type.request_form.name)
-
- class SampleFormColumn(grids.TextColumn):
- def get_value(self, trans, grid, request_type):
- return escape(request_type.sample_form.name)
-
- class ExternalServiceColumn(grids.IntegerColumn):
- def get_value(self, trans, grid, request_type):
- if request_type.external_services:
- return len(request_type.external_services)
- return 'No external service assigned'
- # Grid definition
- title = "Request Types"
- template = "admin/request_type/grid.mako"
- model_class = model.RequestType
- default_sort_key = "-create_time"
- num_rows_per_page = 50
- preserve_state = True
- use_paging = True
- default_filter = dict(deleted="False")
- columns = [
- NameColumn("Name",
- key="name",
- link=(lambda item: iff(item.deleted, None, dict(operation="view_request_type", id=item.id))),
- attach_popup=True,
- filterable="advanced"),
- DescriptionColumn("Description",
- key='desc',
- filterable="advanced"),
- RequestFormColumn("Request Form",
- link=(lambda item: iff(item.deleted, None, dict(operation="view_form_definition", id=item.request_form.id)))),
- SampleFormColumn("Sample Form",
- link=(lambda item: iff(item.deleted, None, dict(operation="view_form_definition", id=item.sample_form.id)))),
- ExternalServiceColumn("External Services"),
- grids.DeletedColumn("Deleted",
- key="deleted",
- visible=False,
- filterable="advanced")
- ]
- columns.append(grids.MulticolFilterColumn("Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard"))
- operations = [
- grids.GridOperation("Edit request type", allow_multiple=False, condition=(lambda item: not item.deleted)),
- grids.GridOperation("Edit permissions", allow_multiple=False, condition=(lambda item: not item.deleted)),
- grids.GridOperation("Use run details template", allow_multiple=False, condition=(lambda item: not item.deleted and not item.run_details)),
- grids.GridOperation("Delete", allow_multiple=True, condition=(lambda item: not item.deleted)),
- grids.GridOperation("Undelete", condition=(lambda item: item.deleted)),
- ]
- global_actions = [
- grids.GridAction("Create new request type", dict(controller='request_type', action='create_request_type'))
- ]
-
-
-class RequestType(BaseUIController, UsesFormDefinitionsMixin):
- request_type_grid = RequestTypeGrid()
-
- @web.expose
- @web.require_admin
- def browse_request_types(self, trans, **kwd):
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- obj_id = kwd.get('id', None)
- if operation == "view_form_definition":
- return self.view_form_definition(trans, **kwd)
- elif operation == "view_request_type":
- return self.view_request_type(trans, **kwd)
- elif operation == "use run details template":
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='add_template',
- cntrller='requests_admin',
- item_type='request_type',
- form_type=trans.model.FormDefinition.types.RUN_DETAILS_TEMPLATE,
- request_type_id=obj_id))
- elif operation == "edit request type":
- return self.view_editable_request_type(trans, **kwd)
- elif operation == "delete":
- return self.delete_request_type(trans, **kwd)
- elif operation == "undelete":
- return self.undelete_request_type(trans, **kwd)
- elif operation == "edit permissions":
- return self.request_type_permissions(trans, **kwd)
- elif operation == "view_external_service":
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='view_external_service',
- **kwd))
- # Render the grid view
- return self.request_type_grid(trans, **kwd)
-
- @web.expose
- @web.require_admin
- def create_request_type(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- rt_info_widgets, rt_states_widgets = self.__get_populated_request_type_widgets(trans, **kwd)
- external_service_select_fields_list = []
- # get all the external services selected till now
- external_services_list = self.__get_external_services(trans, **kwd)
- for index, external_service in enumerate(external_services_list):
- external_service_select_field = self.__build_external_service_select_field(trans,
- 'external_service_id_%i' % index,
- external_service)
- external_service_select_fields_list.append(external_service_select_field)
- if params.get('add_state_button', False):
- # Append a new tuple to the set of states which will result in
- # empty state name and description TextFields being displayed on
- # the form.
- rt_states_widgets.append(("", ""))
- elif params.get('remove_state_button', False):
- index = int(params.get('remove_state_button', '').split(" ")[2])
- del rt_states_widgets[index - 1]
- elif params.get('add_external_service_button', False):
- # create a new one
- external_service_select_field = self.__build_external_service_select_field(trans,
- 'external_service_id_%i' % len(external_services_list))
- external_service_select_fields_list.append(external_service_select_field)
- elif params.get('create_request_type_button', False):
- self.__save_request_type(trans, action='create_request_type', **kwd)
- message = 'The request type has been created.'
- return trans.response.send_redirect(web.url_for(controller='request_type',
- action='browse_request_types',
- message=message,
- status=status))
- # A request_type requires at least one possible sample state so that
- # it can be used to create a sequencing request
- if not len(rt_states_widgets):
- rt_states_widgets.append(("New", "First sample state"))
- return trans.fill_template('/admin/request_type/create_request_type.mako',
- rt_info_widgets=rt_info_widgets,
- rt_states_widgets=rt_states_widgets,
- external_service_select_fields_list=external_service_select_fields_list,
- message=message,
- status=status)
-
- def __get_external_services(self, trans, request_type=None, **kwd):
- params = util.Params(kwd)
- external_services_list = []
- i = 0
- while True:
- if 'external_service_id_%i' % i in kwd:
- id = params.get('external_service_id_%i' % i, '')
- try:
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(id))
- except:
- return invalid_id_redirect(trans, 'request_type', id, 'external service', action='browse_request_types')
- external_services_list.append(external_service)
- i += 1
- else:
- break
- return external_services_list
-
- @web.expose
- @web.require_admin
- def view_editable_request_type(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_type_id = params.get('id', None)
- try:
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'request type', action='browse_request_types')
- # See if we have any associated templates
- widgets = request_type.get_template_widgets(trans)
- widget_fields_have_contents = self.widget_fields_have_contents(widgets)
- # get all the external services selected till now
- external_service_select_fields_list = []
- for index, external_service in enumerate(request_type.external_services):
- external_service_select_field = self.__build_external_service_select_field(trans,
- 'external_service_id_%i' % index,
- external_service)
- external_service_select_fields_list.append(external_service_select_field)
- return trans.fill_template('/admin/request_type/edit_request_type.mako',
- request_type=request_type,
- widgets=widgets,
- widget_fields_have_contents=widget_fields_have_contents,
- external_service_select_fields_list=external_service_select_fields_list,
- message=message,
- status=status)
-
- @web.expose
- @web.require_admin
- def edit_request_type(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_type_id = params.get('id', None)
- try:
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'request type', action='browse_request_types')
- # See if we have any associated templates
- widgets = request_type.get_template_widgets(trans)
- widget_fields_have_contents = self.widget_fields_have_contents(widgets)
- # get all the external services selected till now
- external_service_select_fields_list = []
- external_services_list = self.__get_external_services(trans, request_type, **kwd)
- if params.get('edit_request_type_button', False):
- request_type = self.__save_request_type(trans, action='edit_request_type', **kwd)
- message = 'Changes made to request type (%s) have been saved' % request_type.name
- elif params.get('add_external_service_button', False):
- external_services_list.append(None)
- elif params.get('remove_external_service_button', False):
- index = int(kwd['remove_external_service_button'].split(' ')[3]) - 1
- del external_services_list[index]
- for index, external_service in enumerate(external_services_list):
- external_service_select_field = self.__build_external_service_select_field(trans,
- 'external_service_id_%i' % index,
- external_service)
- external_service_select_fields_list.append(external_service_select_field)
- return trans.fill_template('/admin/request_type/edit_request_type.mako',
- request_type=request_type,
- widgets=widgets,
- widget_fields_have_contents=widget_fields_have_contents,
- external_service_select_fields_list=external_service_select_fields_list,
- message=message,
- status=status)
-
- def __save_request_type(self, trans, action, **kwd):
- # Here we save a newly created request_type or save changed
- # attributes of an existing request_type.
- params = util.Params(kwd)
- request_type_id = params.get('id', None)
- name = util.restore_text(params.get('name', ''))
- desc = util.restore_text(params.get('desc', ''))
- request_form_id = params.get('request_form_id', 'none')
- sample_form_id = params.get('sample_form_id', 'none')
- # validate
- if not name or request_form_id == 'none' or sample_form_id == 'none':
- message = 'Enter the name, request form, sample form and at least one sample state associated with this request type.'
- return trans.response.send_redirect(web.url_for(controller='request_type',
- action=action,
- message=message,
- status='error'))
- try:
- request_form = trans.sa_session.query(trans.model.FormDefinition).get(trans.security.decode_id(request_form_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'form definition', action='browse_request_types')
-
- try:
- sample_form = trans.sa_session.query(trans.model.FormDefinition).get(trans.security.decode_id(sample_form_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'form definition', action='browse_request_types')
- if request_type_id:
- # We're saving changed attributes of an existing request_type.
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- request_type.name = name
- request_type.desc = desc
- request_type.request_form = request_form
- request_type.sample_form = sample_form
- for sample_state in request_type.states:
- sample_state_id = trans.security.encode_id(sample_state.id)
- name = util.restore_text(params.get('state_name_%s' % sample_state_id, ''))
- desc = util.restore_text(params.get('state_desc_%s' % sample_state_id, ''))
- sample_state.name = name
- sample_state.desc = desc
- trans.sa_session.add(sample_state)
- trans.sa_session.flush()
- trans.sa_session.add(request_type)
- trans.sa_session.flush()
- else:
- # We're saving a newly created request_type
- request_type = trans.model.RequestType(name=name,
- desc=desc,
- request_form=request_form,
- sample_form=sample_form)
- trans.sa_session.add(request_type)
- trans.sa_session.flush()
- i = 0
- while True:
- if 'state_name_%i' % i in kwd:
- name = util.restore_text(params.get('state_name_%i' % i, ''))
- desc = util.restore_text(params.get('state_desc_%i' % i, ''))
- sample_state = trans.model.SampleState(name, desc, request_type)
- trans.sa_session.add(sample_state)
- trans.sa_session.flush()
- i += 1
- else:
- break
- # delete existing associations
- request_type.delete_external_service_associations(trans)
- # save the external services associated with this request_type
- external_services_list = self.__get_external_services(trans, **kwd)
- for external_service in external_services_list:
- request_type.add_external_service_association(trans, external_service)
- return request_type
-
- def __get_populated_request_type_widgets(self, trans, **kwd):
- request_form_definitions = self.get_all_forms(trans,
- filter=dict(deleted=False),
- form_type=trans.model.FormDefinition.types.REQUEST)
- sample_form_definitions = self.get_all_forms(trans,
- filter=dict(deleted=False),
- form_type=trans.model.FormDefinition.types.SAMPLE)
- if not request_form_definitions or not sample_form_definitions:
- return [], []
- params = util.Params(kwd)
- request_form_id = params.get('request_form_id', 'none')
- sample_form_id = params.get('sample_form_id', 'none')
- request_form_id_select_field = build_select_field(trans,
- objs=request_form_definitions,
- label_attr='name',
- select_field_name='request_form_id',
- selected_value=request_form_id,
- refresh_on_change=False)
- sample_form_id_select_field = build_select_field(trans,
- objs=sample_form_definitions,
- label_attr='name',
- select_field_name='sample_form_id',
- selected_value=sample_form_id,
- refresh_on_change=False)
- rt_info_widgets = [dict(label='Name',
- widget=TextField('name', 40, util.restore_text(params.get('name', '')))),
- dict(label='Description',
- widget=TextField('desc', 40, util.restore_text(params.get('desc', '')))),
- dict(label='Request form',
- widget=request_form_id_select_field),
- dict(label='Sample form',
- widget=sample_form_id_select_field)]
- # Unsaved sample states being defined for this request type
- rt_states = []
- i = 0
- while True:
- if 'state_name_%i' % i in kwd:
- rt_states.append((util.restore_text(params.get('state_name_%i' % i, '')),
- util.restore_text(params.get('state_desc_%i' % i, ''))))
- i += 1
- else:
- break
- return rt_info_widgets, rt_states
-
- @web.expose
- @web.require_admin
- def view_request_type(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_type_id = kwd.get('id', None)
- try:
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'request type', action='browse_request_types')
- # See if we have any associated templates
- widgets = request_type.get_template_widgets(trans)
- widget_fields_have_contents = self.widget_fields_have_contents(widgets)
- return trans.fill_template('/admin/request_type/view_request_type.mako',
- request_type=request_type,
- widgets=widgets,
- widget_fields_have_contents=widget_fields_have_contents,
- message=message,
- status=status)
-
- @web.expose
- @web.require_admin
- def delete_request_type(self, trans, **kwd):
- request_type_id = kwd.get('id', '')
- request_type_id_list = util.listify(request_type_id)
- for request_type_id in request_type_id_list:
- try:
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'request type', action='browse_request_types')
- request_type.deleted = True
- trans.sa_session.add(request_type)
- trans.sa_session.flush()
- message = '%i request types has been deleted' % len(request_type_id_list)
- return trans.response.send_redirect(web.url_for(controller='request_type',
- action='browse_request_types',
- message=message,
- status='done'))
-
- @web.expose
- @web.require_admin
- def undelete_request_type(self, trans, **kwd):
- request_type_id = kwd.get('id', '')
- request_type_id_list = util.listify(request_type_id)
- for request_type_id in request_type_id_list:
- try:
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'request type', action='browse_request_types')
- request_type.deleted = False
- trans.sa_session.add(request_type)
- trans.sa_session.flush()
- status = 'done'
- message = '%i request types have been undeleted' % len(request_type_id_list)
- return trans.response.send_redirect(web.url_for(controller='request_type',
- action='browse_request_types',
- message=message,
- status=status))
-
- @web.expose
- @web.require_admin
- def request_type_permissions(self, trans, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_type_id = kwd.get('id', '')
- try:
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- except:
- return invalid_id_redirect(trans, 'request_type', request_type_id, 'request type', action='browse_request_types')
- roles = trans.sa_session.query(trans.model.Role) \
- .filter(trans.model.Role.table.c.deleted == false()) \
- .order_by(trans.model.Role.table.c.name)
- if params.get('update_roles_button', False):
- permissions = {}
- for k, v in trans.model.RequestType.permitted_actions.items():
- in_roles = [trans.sa_session.query(trans.model.Role).get(x) for x in util.listify(params.get(k + '_in', []))]
- permissions[trans.app.security_agent.get_action(v.action)] = in_roles
- trans.app.security_agent.set_request_type_permissions(request_type, permissions)
- trans.sa_session.refresh(request_type)
- message = "Permissions updated for request type '%s'" % request_type.name
- return trans.fill_template('/admin/request_type/request_type_permissions.mako',
- request_type=request_type,
- roles=roles,
- status=status,
- message=message)
-
- @web.expose
- @web.require_admin
- def view_form_definition(self, trans, **kwd):
- form_definition_id = kwd.get('id', None)
- try:
- form_definition = trans.sa_session.query(trans.model.FormDefinition).get(trans.security.decode_id(form_definition_id))
- except:
- return invalid_id_redirect(trans, 'request_type', form_definition_id, 'form definition', action='browse_request_types')
- return trans.fill_template('/admin/forms/view_form_definition.mako',
- form_definition=form_definition)
-
- # ===== Methods for building SelectFields used on various admin_requests forms
- def __build_external_service_select_field(self, trans, select_field_name, external_service=None):
- if external_service:
- selected_value = trans.security.encode_id(external_service.id)
- else:
- selected_value = 'none'
- all_external_services = trans.sa_session.query(trans.model.ExternalService).filter(trans.model.ExternalService.table.c.deleted == false()).all()
- for e in all_external_services:
- external_service_type = e.get_external_service_type(trans)
- e.label = '%s - %s' % (e.name, external_service_type.name)
- return build_select_field(trans,
- objs=all_external_services,
- label_attr='label',
- select_field_name=select_field_name,
- selected_value=selected_value,
- refresh_on_change=False)
diff --git a/lib/galaxy/webapps/galaxy/controllers/requests.py b/lib/galaxy/webapps/galaxy/controllers/requests.py
deleted file mode 100644
index ab4da8c3d92..00000000000
--- a/lib/galaxy/webapps/galaxy/controllers/requests.py
+++ /dev/null
@@ -1,98 +0,0 @@
-from __future__ import absolute_import
-
-import logging
-
-from sqlalchemy import false
-
-from galaxy.web.base.controller import BaseUIController, web
-from galaxy.web.framework.helpers import grids
-from .requests_common import RequestsGrid
-
-log = logging.getLogger(__name__)
-
-
-class UserRequestsGrid(RequestsGrid):
- operations = [operation for operation in RequestsGrid.operations]
- operations.append(grids.GridOperation("Edit", allow_multiple=False, condition=(lambda item: item.is_unsubmitted and not item.deleted)))
- operations.append(grids.GridOperation("Delete", allow_multiple=True, condition=(lambda item: item.is_new and not item.deleted)))
- operations.append(grids.GridOperation("Undelete", allow_multiple=True, condition=(lambda item: item.deleted)))
-
- def apply_query_filter(self, trans, query, **kwd):
- return query.filter_by(user=trans.user)
-
-
-class Requests(BaseUIController):
- request_grid = UserRequestsGrid()
-
- @web.expose
- @web.require_login("view sequencing requests")
- def index(self, trans):
- return trans.fill_template("requests/index.mako")
-
- @web.expose
- @web.require_login("create/submit sequencing requests")
- def find_samples_index(self, trans):
- return trans.fill_template("requests/find_samples_index.mako")
-
- @web.expose
- def browse_requests(self, trans, **kwd):
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- if operation == "edit":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_basic_request_info',
- cntrller='requests',
- **kwd))
- if operation == "add_samples":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='add_samples',
- cntrller='requests',
- **kwd))
- if operation == "edit_samples":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller='requests',
- **kwd))
- if operation == "view_request":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='view_request',
- cntrller='requests',
- **kwd))
- if operation == "delete":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='delete_request',
- cntrller='requests',
- **kwd))
- if operation == "undelete":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='undelete_request',
- cntrller='requests',
- **kwd))
- if operation == "view_request_history":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='view_request_history',
- cntrller='requests',
- **kwd))
-
- # If there are requests that have been rejected, show a message as a reminder to the user
- rejected = 0
- for request in trans.sa_session.query(trans.app.model.Request) \
- .filter(trans.app.model.Request.table.c.deleted == false()) \
- .filter(trans.app.model.Request.table.c.user_id == trans.user.id):
- if request.is_rejected:
- rejected = rejected + 1
- if rejected:
- status = 'warning'
- message = "%d requests (highlighted in red) were rejected. Click on the request name for details." % rejected
- kwd['status'] = status
- kwd['message'] = message
- # Allow the user to create a new request only if they have permission to access a request type.
- accessible_request_types = trans.app.security_agent.get_accessible_request_types(trans, trans.user)
- if accessible_request_types:
- self.request_grid.global_actions = [grids.GridAction("Create new request", dict(controller='requests_common',
- action='create_request',
- cntrller='requests'))]
- else:
- self.request_grid.global_actions = []
- # Render the list view
- return self.request_grid(trans, **kwd)
diff --git a/lib/galaxy/webapps/galaxy/controllers/requests_admin.py b/lib/galaxy/webapps/galaxy/controllers/requests_admin.py
deleted file mode 100644
index 2e375ff272a..00000000000
--- a/lib/galaxy/webapps/galaxy/controllers/requests_admin.py
+++ /dev/null
@@ -1,473 +0,0 @@
-from __future__ import absolute_import
-import logging
-import os
-
-from galaxy import model, util
-from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin, web
-from galaxy.web.form_builder import build_select_field
-from galaxy.web.framework.helpers import time_ago, grids
-from .requests_common import RequestsGrid, invalid_id_redirect
-from markupsafe import escape
-
-
-log = logging.getLogger(__name__)
-
-
-class AdminRequestsGrid(RequestsGrid):
- class UserColumn(grids.TextColumn):
- def get_value(self, trans, grid, request):
- return escape(request.user.email)
- # Grid definition
- columns = [col for col in RequestsGrid.columns]
- columns.append(UserColumn("User",
- model_class=model.User,
- key='username'))
- operations = [operation for operation in RequestsGrid.operations]
- operations.append(grids.GridOperation("Edit", allow_multiple=False, condition=(lambda item: not item.deleted)))
- operations.append(grids.GridOperation("Reject", allow_multiple=False, condition=(lambda item: not item.deleted and item.is_submitted)))
- operations.append(grids.GridOperation("Delete", allow_multiple=True, condition=(lambda item: not item.deleted)))
- operations.append(grids.GridOperation("Undelete", condition=(lambda item: item.deleted)))
- global_actions = [
- grids.GridAction("Create new request", dict(controller='requests_common',
- action='create_request',
- cntrller='requests_admin'))
- ]
-
-
-class DataTransferGrid(grids.Grid):
- # Custom column types
- class NameColumn(grids.TextColumn):
- def get_value(self, trans, grid, sample_dataset):
- return escape(sample_dataset.name)
-
- class SizeColumn(grids.TextColumn):
- def get_value(self, trans, grid, sample_dataset):
- return sample_dataset.size
-
- class StatusColumn(grids.TextColumn):
- def get_value(self, trans, grid, sample_dataset):
- return sample_dataset.status
-
- class ExternalServiceColumn(grids.TextColumn):
- def get_value(self, trans, grid, sample_dataset):
- try:
- return escape(sample_dataset.external_service.name)
- except:
- return 'None'
- # Grid definition
- title = "Sample Datasets"
- template = "admin/requests/sample_datasets_grid.mako"
- model_class = model.SampleDataset
- default_sort_key = "-create_time"
- num_rows_per_page = 50
- preserve_state = True
- use_paging = False
- columns = [
- NameColumn("Name",
- link=(lambda item: dict(operation="view", id=item.id)),
- attach_popup=True,
- filterable="advanced"),
- SizeColumn("Size",
- filterable="advanced"),
- grids.GridColumn("Last Updated",
- key="update_time",
- format=time_ago),
- ExternalServiceColumn('External service',
- link=(lambda item: dict(operation="view_external_service", id=item.external_service.id)), ),
- StatusColumn("Transfer Status",
- filterable="advanced",
- label_id_prefix='datasetTransferStatus-'),
- ]
- columns.append(grids.MulticolFilterColumn("Search",
- cols_to_filter=[columns[0]],
- key="free-text-search",
- visible=False,
- filterable="standard"))
- operations = [
- grids.GridOperation("Transfer",
- allow_multiple=True,
- condition=(lambda item: item.status in [model.SampleDataset.transfer_status.NOT_STARTED])),
- grids.GridOperation("Rename",
- allow_multiple=True,
- allow_popup=False,
- condition=(lambda item: item.status in [model.SampleDataset.transfer_status.NOT_STARTED])),
- grids.GridOperation("Delete",
- allow_multiple=True,
- condition=(lambda item: item.status in [model.SampleDataset.transfer_status.NOT_STARTED]))
- ]
-
- def apply_query_filter(self, trans, query, **kwd):
- sample_id = kwd.get('sample_id', None)
- if not sample_id:
- return query
- return query.filter_by(sample_id=trans.security.decode_id(sample_id))
-
-
-class RequestsAdmin(BaseUIController, UsesFormDefinitionsMixin):
- request_grid = AdminRequestsGrid()
- datatx_grid = DataTransferGrid()
-
- @web.expose
- @web.require_admin
- def index(self, trans):
- return trans.fill_template("/admin/requests/index.mako")
-
- @web.expose
- @web.require_admin
- def browse_requests(self, trans, **kwd):
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- if operation == "edit":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_basic_request_info',
- cntrller='requests_admin',
- **kwd))
- if operation == "add_samples":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='add_samples',
- cntrller='requests_admin',
- **kwd))
- if operation == "edit_samples":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller='requests_admin',
- **kwd))
- if operation == "view_request":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='view_request',
- cntrller='requests_admin',
- **kwd))
- if operation == "view_request_history":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='view_request_history',
- cntrller='requests_admin',
- **kwd))
- if operation == "reject":
- return self.reject_request(trans, **kwd)
- if operation == "view_type":
- return trans.response.send_redirect(web.url_for(controller='request_type',
- action='view_request_type',
- **kwd))
- if operation == "delete":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='delete_request',
- cntrller='requests_admin',
- **kwd))
- if operation == "undelete":
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='undelete_request',
- cntrller='requests_admin',
- **kwd))
- # Render the list view
- return self.request_grid(trans, **kwd)
-
- @web.expose
- @web.require_admin
- def reject_request(self, trans, **kwd):
- params = util.Params(kwd)
- request_id = params.get('id', '')
- status = params.get('status', 'done')
- message = params.get('message', 'done')
- if params.get('cancel_reject_button', False):
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='view_request',
- cntrller='requests_admin',
- id=request_id))
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, 'requests_admin', request_id)
- # Validate
- comment = util.restore_text(params.get('comment', ''))
- if not comment:
- status = 'error'
- message = 'A reason for rejecting the request is required.'
- return trans.fill_template('/admin/requests/reject.mako',
- cntrller='requests_admin',
- request=request,
- status=status,
- message=message)
- # Create an event with state 'Rejected' for this request
- event_comment = "Sequencing request marked rejected by %s. Reason: %s " % (trans.user.email, comment)
- event = trans.model.RequestEvent(request, request.states.REJECTED, event_comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- message = 'Sequencing request (%s) has been rejected.' % request.name
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='browse_requests',
- status=status,
- message=message,
- **kwd))
-
- # Data transfer from sequencer/external_service
- @web.expose
- @web.require_admin
- def manage_datasets(self, trans, **kwd):
- def handle_error(**kwd):
- kwd['status'] = 'error'
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='manage_datasets',
- **kwd))
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- # When this method is called due to a grid operation, the sample ID
- # will be in the param 'id'. But when this method is called via a
- # redirect from another method, the ID will be in 'sample_id'. So,
- # check for 'id' if 'sample_id' is not provided.
- sample_id = params.get('sample_id', None)
- if sample_id is None:
- sample_id = params.get('id', None)
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- return invalid_id_redirect(trans, 'requests_admin', sample_id, 'sample')
- if 'operation' in kwd:
- operation = kwd['operation'].lower()
- sample_dataset_id = params.get('id', None)
- if not sample_dataset_id:
- message = 'Select at least 1 dataset to %s.' % operation
- kwd['message'] = message
- del kwd['operation']
- handle_error(**kwd)
- id_list = util.listify(sample_dataset_id)
- selected_sample_datasets = []
- for sample_dataset_id in id_list:
- try:
- sample_dataset = trans.sa_session.query(trans.model.SampleDataset).get(trans.security.decode_id(sample_dataset_id))
- except:
- return invalid_id_redirect(trans, 'requests_admin', sample_dataset_id, 'sample dataset')
- selected_sample_datasets.append(sample_dataset)
- if operation == "view":
- return trans.fill_template('/admin/requests/view_sample_dataset.mako',
- cntrller='requests_admin',
- sample_dataset=selected_sample_datasets[0])
- elif operation == "delete":
- not_deleted = []
- for sample_dataset in selected_sample_datasets:
- # Make sure the dataset has been transferred before deleting it.
- if sample_dataset in sample_dataset.sample.untransferred_dataset_files:
- # Save the sample dataset
- sample = sample_dataset.sample
- trans.sa_session.delete(sample_dataset)
- trans.sa_session.flush()
- else:
- not_deleted.append(sample_dataset.name)
- message = '%i datasets have been deleted.' % (len(id_list) - len(not_deleted))
- if not_deleted:
- status = 'warning'
- message = message + ' %s could not be deleted because their transfer status is not "Not Started". ' % str(not_deleted)
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='manage_datasets',
- sample_id=trans.security.encode_id(sample.id),
- status=status,
- message=message))
- elif operation == "rename":
- # If one of the selected sample datasets is in the NOT_STARTED state,
- # then display an error message. A NOT_STARTED state implies the dataset
- # has not yet been transferred.
- no_datasets_transferred = True
- for selected_sample_dataset in selected_sample_datasets:
- if selected_sample_dataset in selected_sample_dataset.sample.untransferred_dataset_files:
- no_datasets_transferred = False
- break
- if no_datasets_transferred:
- status = 'error'
- message = 'A dataset can be renamed only if it has been transferred.'
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='manage_datasets',
- sample_id=trans.security.encode_id(selected_sample_datasets[0].sample.id),
- status=status,
- message=message))
- return trans.fill_template('/admin/requests/rename_datasets.mako',
- sample=selected_sample_datasets[0].sample,
- id_list=id_list)
- elif operation == "transfer":
- self.initiate_data_transfer(trans,
- trans.security.encode_id(selected_sample_datasets[0].sample.id),
- sample_datasets=selected_sample_datasets)
- elif operation == "view_external_service":
- return trans.response.send_redirect(web.url_for(controller='external_service',
- action='view_external_service',
- **kwd))
-
- # Render the grid view
- request_id = trans.security.encode_id(sample.request.id)
- library_id = trans.security.encode_id(sample.library.id)
- self.datatx_grid.title = 'Manage "%s" datasets' % sample.name
- self.datatx_grid.global_actions = [grids.GridAction("Browse target data library",
- dict(controller='library_common',
- action='browse_library',
- cntrller='library_admin',
- id=library_id)),
- grids.GridAction("Browse this request",
- dict(controller='requests_common',
- action='view_request',
- cntrller='requests_admin',
- id=request_id))]
- return self.datatx_grid(trans, **kwd)
-
- @web.expose
- @web.require_admin
- def rename_datasets(self, trans, **kwd):
- # This method is called from the DataTransferGrid when a user is renaming 1 or more
- # SampleDatasets.
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- sample_id = kwd.get('sample_id', None)
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- return invalid_id_redirect(trans, 'requests_admin', sample_id, 'sample')
- # id_list is list of SampleDataset ids, which is a subset of all
- # of the SampleDatasets associated with the Sample. The user may
- # or may not have selected all of the SampleDatasets for renaming.
- id_list = util.listify(kwd.get('id_list', []))
- # Get all of the SampleDatasets
- sample_datasets = []
- for sample_dataset_id in id_list:
- sample_dataset = trans.sa_session.query(trans.app.model.SampleDataset).get(trans.security.decode_id(sample_dataset_id))
- sample_datasets.append(sample_dataset)
- if params.get('rename_datasets_button', False):
- incorrect_dataset_names = []
- for sample_dataset in sample_datasets:
- encoded_id = trans.security.encode_id(sample_dataset.id)
- selected_option = util.restore_text(params.get('rename_datasets_for_sample_%s' % encoded_id, ''))
- new_name = util.restore_text(params.get('new_name_%s' % encoded_id, ''))
- if not new_name:
- incorrect_dataset_names.append(sample_dataset.name)
- continue
- new_name = util.sanitize_for_filename(new_name)
- if selected_option == 'none':
- sample_dataset.name = new_name
- else:
- sample_dataset.name = '%s_%s' % (selected_option, new_name)
- trans.sa_session.add(sample_dataset)
- trans.sa_session.flush()
- if len(sample_datasets) == len(incorrect_dataset_names):
- status = 'error'
- message = 'All datasets renamed incorrectly.'
- elif len(incorrect_dataset_names):
- status = 'done'
- message = 'Changes saved successfully. The following datasets were renamed incorrectly: %s.' % str(incorrect_dataset_names)
- else:
- message = 'Changes saved successfully.'
- return trans.fill_template('/admin/requests/rename_datasets.mako',
- sample=sample,
- id_list=id_list,
- message=message,
- status=status)
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='manage_datasets',
- sample_id=sample_id))
-
- def __ensure_library_add_permission(self, trans, target_library, target_folder):
- """
- Ensures the current admin user has ADD_LIBRARY permission on the target data library and folder.
- """
- current_user_roles = trans.user.all_roles()
- current_user_private_role = trans.app.security_agent.get_private_user_role(trans.user)
- flush_needed = False
- if not trans.app.security_agent.can_add_library_item(current_user_roles, target_library):
- lp = trans.model.LibraryPermissions(trans.app.security_agent.permitted_actions.LIBRARY_ADD.action,
- target_library,
- current_user_private_role)
- trans.sa_session.add(lp)
- flush_needed = True
- if not trans.app.security_agent.can_add_library_item(current_user_roles, target_folder):
- lfp = trans.model.LibraryFolderPermissions(trans.app.security_agent.permitted_actions.LIBRARY_ADD.action,
- target_folder,
- current_user_private_role)
- trans.sa_session.add(lfp)
- flush_needed = True
- if flush_needed:
- trans.sa_session.flush()
-
- @web.expose
- @web.require_admin
- def initiate_data_transfer(self, trans, sample_id, sample_datasets=[], sample_dataset_id=''):
- # Initiate the transfer of the datasets from the external service to the target Galaxy data library.
- # The admin user must have LIBRARY_ADD permission for the target library and folder
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- return invalid_id_redirect(trans, 'requests_admin', sample_id, 'sample')
- message = ""
- status = "done"
- # Make sure the current admin user has LIBRARY_ADD permission on the target data library and folder.
- self.__ensure_library_add_permission(trans, sample.library, sample.folder)
- if sample_dataset_id and not sample_datasets:
- # Either a list of SampleDataset objects or a comma-separated string of
- # encoded SampleDataset ids can be received. If the latter, parse the
- # sample_dataset_id string to build the list of sample_datasets.
- id_list = util.listify(sample_dataset_id)
- for sample_dataset_id in id_list:
- sample_dataset = trans.sa_session.query(trans.model.SampleDataset).get(trans.security.decode_id(sample_dataset_id))
- sample_datasets.append(sample_dataset)
- if trans.app.config.enable_beta_job_managers:
- # For now, assume that all SampleDatasets use the same external service ( this may not be optimal ).
- if sample_datasets:
- external_service_type_id = sample_datasets[0].external_service.external_service_type_id
- # Here external_service_type_id will be something like '454_life_sciences'
- external_service = sample.request.type.get_external_service(external_service_type_id)
- external_service_type = external_service.get_external_service_type(trans)
- external_service.load_data_transfer_settings(trans)
- # For now only scp is supported.
- scp_configs = external_service.data_transfer[trans.model.ExternalService.data_transfer_protocol.SCP]
- if not scp_configs['automatic_transfer']:
- deferred_plugin = 'ManualDataTransferPlugin'
- else:
- raise Exception("Automatic data transfer using scp is not yet supported.")
- trans.app.job_manager.deferred_job_queue.plugins[deferred_plugin].create_job(trans,
- sample=sample,
- sample_datasets=sample_datasets,
- external_service=external_service,
- external_service_type=external_service_type)
- else:
- message = "Message queue transfer is no longer supported, please set enable_beta_job_managers = True in galaxy.ini"
- status = "error"
- return trans.response.send_redirect(web.url_for(controller='requests_admin',
- action='manage_datasets',
- sample_id=trans.security.encode_id(sample.id),
- message=message,
- status=status))
-
- @web.expose
- def update_sample_dataset_status(self, trans, cntrller, sample_dataset_ids, new_status, error_msg=None):
- # check if the new status is a valid transfer status
- possible_status_list = [v[1] for v in trans.app.model.SampleDataset.transfer_status.items()]
- if new_status not in possible_status_list:
- trans.response.status = 400
- return 400, "The requested transfer status ( %s ) is not a valid transfer status." % new_status
- for id in util.listify(sample_dataset_ids):
- try:
- sd_id = trans.security.decode_id(id)
- sample_dataset = trans.sa_session.query(trans.app.model.SampleDataset).get(sd_id)
- except:
- trans.response.status = 400
- return 400, "Invalid sample dataset id ( %s ) specified." % str(id)
- sample_dataset.status = new_status
- sample_dataset.error_msg = error_msg
- trans.sa_session.add(sample_dataset)
- trans.sa_session.flush()
- return 200, 'Done'
- # Methods for building SelectFields used on various admin_requests forms
-
- def __build_sample_id_select_field(self, trans, request, selected_value):
- return build_select_field(trans, request.samples, 'name', 'sample_id', selected_value=selected_value, refresh_on_change=False)
-
-
-# Methods for building SelectFields used on various admin_requests forms - used outside this controller =====
-def build_rename_datasets_for_sample_select_field(trans, sample_dataset, selected_value='none'):
- options = []
- for option_index, option in enumerate(sample_dataset.file_path.split(os.sep)[:-1]):
- option = option.strip()
- if option:
- options.append(option)
- return build_select_field(trans,
- objs=options,
- label_attr='self',
- select_field_name='rename_datasets_for_sample_%s' % trans.security.encode_id(sample_dataset.id),
- selected_value=selected_value,
- refresh_on_change=False)
diff --git a/lib/galaxy/webapps/galaxy/controllers/requests_common.py b/lib/galaxy/webapps/galaxy/controllers/requests_common.py
deleted file mode 100644
index db764265984..00000000000
--- a/lib/galaxy/webapps/galaxy/controllers/requests_common.py
+++ /dev/null
@@ -1,1959 +0,0 @@
-import csv
-import logging
-import re
-
-from sqlalchemy import and_, false, func, select
-from markupsafe import escape
-
-from galaxy import model, util, web
-from galaxy.util import unicodify
-from galaxy.security.validate_user_input import validate_email
-from galaxy.web.base.controller import BaseUIController, UsesFormDefinitionsMixin
-from galaxy.web.form_builder import build_select_field, CheckboxField, SelectField, TextField
-from galaxy.web.framework.helpers import grids, iff, time_ago
-
-log = logging.getLogger(__name__)
-
-
-class RequestsGrid(grids.Grid):
- # Custom column types
- class NameColumn(grids.TextColumn):
- def get_value(self, trans, grid, request):
- return escape(request.name)
-
- class DescriptionColumn(grids.TextColumn):
- def get_value(self, trans, grid, request):
- return escape(request.desc)
-
- class SamplesColumn(grids.GridColumn):
- def get_value(self, trans, grid, request):
- return str(len(request.samples))
-
- class TypeColumn(grids.TextColumn):
- def get_value(self, trans, grid, request):
- return request.type.name
-
- class StateColumn(grids.StateColumn):
- def get_value(self, trans, grid, request):
- state = request.state
- if state == request.states.REJECTED:
- state_color = 'error'
- elif state == request.states.NEW:
- state_color = 'new'
- elif state == request.states.SUBMITTED:
- state_color = 'running'
- elif state == request.states.COMPLETE:
- state_color = 'ok'
- else:
- state_color = state
- return '
%s
' % (state_color, state)
-
- def filter(self, trans, user, query, column_filter):
- """ Modify query to filter request by state. """
- if column_filter == "All":
- return query
- if column_filter:
- return query.join(model.RequestEvent.table) \
- .filter(self.model_class.table.c.id == model.RequestEvent.table.c.request_id) \
- .filter(model.RequestEvent.table.c.state == column_filter) \
- .filter(model.RequestEvent.table.c.id.in_(select(columns=[func.max(model.RequestEvent.table.c.id)],
- from_obj=model.RequestEvent.table,
- group_by=model.RequestEvent.table.c.request_id)))
-
- # Grid definition
- title = "Sequencing Requests"
- template = "requests/grid.mako"
- model_class = model.Request
- default_sort_key = "-update_time"
- num_rows_per_page = 50
- use_paging = True
- default_filter = dict(state="All", deleted="False")
- columns = [
- NameColumn("Name",
- key="name",
- link=(lambda item: dict(operation="view_request", id=item.id)),
- attach_popup=True,
- filterable="advanced"),
- DescriptionColumn("Description",
- key='desc',
- filterable="advanced"),
- SamplesColumn("Samples",
- link=(lambda item: iff(item.deleted, None, dict(operation="edit_samples", id=item.id)))),
- TypeColumn("Type",
- link=(lambda item: iff(item.deleted, None, dict(operation="view_type", id=item.type.id)))),
- grids.GridColumn("Last Updated", key="update_time", format=time_ago),
- grids.DeletedColumn("Deleted",
- key="deleted",
- visible=False,
- filterable="advanced"),
- StateColumn("State",
- key='state',
- filterable="advanced",
- link=(lambda item: iff(item.deleted, None, dict(operation="view_request_history", id=item.id))))
- ]
- columns.append(grids.MulticolFilterColumn("Search",
- cols_to_filter=[columns[0], columns[1]],
- key="free-text-search",
- visible=False,
- filterable="standard"))
- operations = [
- grids.GridOperation("Submit",
- allow_multiple=False,
- condition=(lambda item: not item.deleted and item.is_unsubmitted and item.samples),
- confirm="Samples cannot be added to this request after it is submitted. Click OK to submit.")
- ]
-
-
-class RequestsCommon(BaseUIController, UsesFormDefinitionsMixin):
- @web.json
- def sample_state_updates(self, trans, ids=None, states=None):
- # Avoid caching
- trans.response.headers['Pragma'] = 'no-cache'
- trans.response.headers['Expires'] = '0'
- # Create new HTML for any that have changed
- rval = {}
- if ids is not None and states is not None:
- ids = map(int, ids.split(","))
- states = states.split(",")
- for tup in zip(ids, states):
- id, state = tup
- if id and state:
- sample = trans.sa_session.query(self.app.model.Sample).get(id)
- if sample.state.name != state:
- rval[id] = {"state": sample.state.name,
- "html_state": unicodify(trans.fill_template("requests/common/sample_state.mako",
- sample=sample),
- 'utf-8')}
- return rval
-
- @web.json
- def sample_datasets_updates(self, trans, ids=None, datasets=None):
- # Avoid caching
- trans.response.headers['Pragma'] = 'no-cache'
- trans.response.headers['Expires'] = '0'
- # Create new HTML for any that have changed
- rval = {}
- if ids is not None and datasets is not None:
- ids = map(int, ids.split(","))
- number_of_datasets_list = map(int, datasets.split(","))
- for tup in zip(ids, number_of_datasets_list):
- id, number_of_datasets = tup
- if id and number_of_datasets:
- sample = trans.sa_session.query(self.app.model.Sample).get(id)
- if len(sample.datasets) != number_of_datasets:
- rval[id] = {"datasets": len(sample.datasets),
- "html_datasets": unicodify(trans.fill_template("requests/common/sample_datasets.mako",
- sample=sample),
- 'utf-8')}
- return rval
-
- @web.json
- def dataset_transfer_status_updates(self, trans, ids=None, transfer_status_list=None):
- # Avoid caching
- trans.response.headers['Pragma'] = 'no-cache'
- trans.response.headers['Expires'] = '0'
- # Create new HTML for any that have changed
- rval = {}
- if ids is not None and transfer_status_list is not None:
- ids = ids.split(",")
- transfer_status_list = transfer_status_list.split(",")
- for tup in zip(ids, transfer_status_list):
- id, transfer_status = tup
- if id and transfer_status:
- sample_dataset = trans.sa_session.query(self.app.model.SampleDataset).get(trans.security.decode_id(id))
- if sample_dataset.status != transfer_status:
- rval[id] = {"status": sample_dataset.status,
- "html_status": unicodify(trans.fill_template("requests/common/sample_dataset_transfer_status.mako",
- sample_dataset=sample_dataset),
- 'utf-8')}
- return rval
-
- @web.expose
- @web.require_login("create sequencing requests")
- def create_request(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_type_id = params.get('request_type_id', 'none')
- if request_type_id != 'none':
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- else:
- request_type = None
- # user_id will not be 'none' if an admin user is submitting this request on behalf of another user
- # and they selected that user's id from the user_id SelectField.
- user_id_encoded = True
- user_id = params.get('user_id', 'none')
- if user_id != 'none':
- try:
- user = trans.sa_session.query(trans.model.User).get(trans.security.decode_id(user_id))
- except TypeError:
- # We must have an email address rather than an encoded user id
- # This is because the galaxy.base.js creates a search+select box
- # when there are more than 20 items in a SelectField.
- user = trans.sa_session.query(trans.model.User) \
- .filter(trans.model.User.table.c.email == util.restore_text(user_id)) \
- .first()
- user_id_encoded = False
-
- elif not is_admin:
- user = trans.user
- else:
- user = None
- if params.get('create_request_button', False) or params.get('add_sample_button', False):
- name = util.restore_text(params.get('name', ''))
- if is_admin and user_id == 'none':
- message = 'Select the user on behalf of whom you are submitting this request.'
- status = 'error'
- elif user is None:
- message = 'Invalid user ID (%s)' % str(user_id)
- status = 'error'
- # when creating a request from the user perspective, check if the
- # user has access permission to this request_type
- elif cntrller == 'requests' and not trans.app.security_agent.can_access_request_type(user.all_roles(), request_type):
- message = '%s does not have access permission to the "%s" request type.' % (user.email, request_type.name)
- status = 'error'
- elif not name:
- message = 'Enter the name of the request.'
- status = 'error'
- else:
- request = self.__save_request(trans, cntrller, **kwd)
- message = 'The sequencing request has been created.'
- if params.get('create_request_button', False):
- return trans.response.send_redirect(web.url_for(controller=cntrller,
- action='browse_requests',
- message=message,
- status='done'))
- elif params.get('add_sample_button', False):
- request_id = trans.security.encode_id(request.id)
- return self.add_sample(trans, cntrller, request_id, **kwd)
- request_type_select_field = self.__build_request_type_id_select_field(trans, selected_value=request_type_id)
- # Widgets to be rendered on the request form
- widgets = []
- if request_type is not None or status == 'error':
- # Either the user selected a request_type or an error exists on the form.
- widgets.append(dict(label='Name of the Experiment',
- widget=TextField('name', 40, util.restore_text(params.get('name', ''))),
- helptext='(Required)'))
- widgets.append(dict(label='Description',
- widget=TextField('desc', 40, util.restore_text(params.get('desc', ''))),
- helptext='(Optional)'))
- if request_type is not None:
- widgets += request_type.request_form.get_widgets(user, **kwd)
- # In case there is an error on the form, make sure to populate widget fields with anything the user
- # may have already entered.
- widgets = self.populate_widgets_from_kwd(trans, widgets, **kwd)
- if request_type is not None or status == 'error':
- # Either the user selected a request_type or an error exists on the form.
- if is_admin:
- if not user_id_encoded and user:
- selected_user_id = trans.security.encode_id(user.id)
- else:
- selected_user_id = user_id
- user_widget = dict(label='Select user',
- widget=self.__build_user_id_select_field(trans, selected_value=selected_user_id),
- helptext='Submit the request on behalf of the selected user (Required)')
- widgets = [user_widget] + widgets
- return trans.fill_template('/requests/common/create_request.mako',
- cntrller=cntrller,
- request_type_select_field=request_type_select_field,
- request_type_select_field_selected=request_type_id,
- widgets=widgets,
- message=message,
- status=status)
-
- @web.expose
- @web.require_login("view request")
- def view_request(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_id = params.get('id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- # Build a list of sample widgets (based on the attributes of each sample) for display.
- displayable_sample_widgets = self.__get_sample_widgets(trans, request, request.samples, **kwd)
- request_widgets = self.__get_request_widgets(trans, request.id)
- return trans.fill_template('/requests/common/view_request.mako',
- cntrller=cntrller,
- request=request,
- request_widgets=request_widgets,
- displayable_sample_widgets=displayable_sample_widgets,
- status=status,
- message=message)
-
- @web.expose
- @web.require_login("edit sequencing requests")
- def edit_basic_request_info(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_id = params.get('id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- name = util.restore_text(params.get('name', ''))
- if params.get('edit_basic_request_info_button', False):
- if not name:
- status = 'error'
- message = 'Enter the name of the request'
- else:
- request = self.__save_request(trans, cntrller, request=request, **kwd)
- message = 'The changes made to request (%s) have been saved.' % request.name
- # Widgets to be rendered on the request form
- widgets = []
- widgets.append(dict(label='Name',
- widget=TextField('name', 40, request.name),
- helptext='(Required)'))
- widgets.append(dict(label='Description',
- widget=TextField('desc', 40, request.desc),
- helptext='(Optional)'))
- widgets = widgets + request.type.request_form.get_widgets(request.user, request.values.content, **kwd)
- # In case there is an error on the form, make sure to populate widget fields with anything the user
- # may have already entered.
- widgets = self.populate_widgets_from_kwd(trans, widgets, **kwd)
- return trans.fill_template('requests/common/edit_basic_request_info.mako',
- cntrller=cntrller,
- request_type=request.type,
- request=request,
- widgets=widgets,
- message=message,
- status=status)
-
- def __save_request(self, trans, cntrller, request=None, **kwd):
- """
- Saves changes to an existing request, or creates a new
- request if received request is None.
- """
- params = util.Params(kwd)
- request_type_id = params.get('request_type_id', None)
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- if request is None:
- # We're creating a new request, so we need the associated request_type
- request_type = trans.sa_session.query(trans.model.RequestType).get(trans.security.decode_id(request_type_id))
- if is_admin:
- # The admin user is creating a request on behalf of another user
- user_id = params.get('user_id', '')
- user = trans.sa_session.query(trans.model.User).get(trans.security.decode_id(user_id))
- else:
- user = trans.user
- else:
- # We're saving changes to an existing request
- user = request.user
- request_type = request.type
- name = util.restore_text(params.get('name', ''))
- desc = util.restore_text(params.get('desc', ''))
- notification = dict(email=[user.email], sample_states=[request_type.final_sample_state.id], body='', subject='')
- values = self.get_form_values(trans, user, request_type.request_form, **kwd)
- if request is None:
- form_values = trans.model.FormValues(request_type.request_form, values)
- trans.sa_session.add(form_values)
- # We're creating a new request
- request = trans.model.Request(name, desc, request_type, user, form_values, notification)
- trans.sa_session.add(request)
- trans.sa_session.flush()
- trans.sa_session.refresh(request)
- # Create an event with state 'New' for this new request
- comment = "Sequencing request created by %s" % trans.user.email
- if request.user != trans.user:
- comment += " on behalf of %s." % request.user.email
- event = trans.model.RequestEvent(request, request.states.NEW, comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- else:
- # We're saving changes to an existing request
- request.name = name
- request.desc = desc
- request.type = request_type
- request.user = user
- request.notification = notification
- request.values.content = values
- trans.sa_session.add(request)
- trans.sa_session.add(request.values)
- trans.sa_session.flush()
- return request
-
- @web.expose
- @web.require_login("submit sequencing requests")
- def submit_request(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- request_id = params.get('id', None)
- message = util.restore_text(params.get('message', ''))
- status = util.restore_text(params.get('status', 'done'))
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- ok = True
- if not request.samples:
- message = 'Add at least 1 sample to this request before submitting.'
- ok = False
- if ok:
- message = self.__validate_request(trans, cntrller, request)
- if message or not ok:
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_basic_request_info',
- cntrller=cntrller,
- id=request_id,
- status='error',
- message=message))
- # Change the request state to 'Submitted'
- comment = "Sequencing request submitted by %s" % trans.user.email
- if request.user != trans.user:
- comment += " on behalf of %s." % request.user.email
- event = trans.model.RequestEvent(request, request.states.SUBMITTED, comment)
- trans.sa_session.add(event)
- # Change the state of each of the samples of this request
- # request.type.states is the list of SampleState objects configured
- # by the admin for this RequestType.
- trans.sa_session.add(event)
- trans.sa_session.flush()
- # Samples will not have an associated SampleState until the request is submitted, at which
- # time all samples of the request will be set to the first SampleState configured for the
- # request's RequestType configured by the admin.
- initial_sample_state_after_request_submitted = request.type.states[0]
- for sample in request.samples:
- event_comment = 'Sequencing request submitted and sample state set to %s.' % request.type.states[0].name
- event = trans.model.SampleEvent(sample,
- initial_sample_state_after_request_submitted,
- event_comment)
- trans.sa_session.add(event)
- trans.sa_session.add(request)
- trans.sa_session.flush()
- request.send_email_notification(trans, initial_sample_state_after_request_submitted)
- message = 'The sequencing request has been submitted.'
- # show the request page after submitting the request
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='view_request',
- cntrller=cntrller,
- id=request_id,
- status=status,
- message=message))
-
- @web.expose
- @web.require_login("edit samples")
- def edit_samples(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_id = params.get('id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- if params.get('cancel_changes_button', False):
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- id=request_id))
- libraries = trans.app.security_agent.get_accessible_libraries(trans, request.user)
- # Build a list of sample widgets (based on the attributes of each sample) for display.
- displayable_sample_widgets = self.__get_sample_widgets(trans, request, request.samples, **kwd)
- encoded_selected_sample_ids = self.__get_encoded_selected_sample_ids(trans, request, **kwd)
- sample_operation = params.get('sample_operation', 'none')
-
- def handle_error(**kwd):
- kwd['status'] = 'error'
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- **kwd))
- if not encoded_selected_sample_ids and sample_operation != 'none':
- # Probably occurred due to refresh_on_change...is there a better approach?
- kwd['sample_operation'] = 'none'
- message = 'Select at least one sample before selecting an operation.'
- kwd['message'] = message
- handle_error(**kwd)
- if params.get('save_samples_button', False):
- if encoded_selected_sample_ids:
- # We need the list of displayable_sample_widgets to include the same number
- # of objects that that request.samples has so that we can enumerate over each
- # list without problems. We have to be careful here since the user may have
- # used the multi-select check boxes when editing sample widgets, but didn't
- # select all of them. We'll first get the set of samples corresponding to the
- # checked sample ids.
- samples = []
- selected_samples = []
- for encoded_sample_id in encoded_selected_sample_ids:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(encoded_sample_id))
- selected_samples.append(sample)
- # Now build the list of samples, inserting None for samples that have not been checked.
- for sample in request.samples:
- if sample in selected_samples:
- samples.append(sample)
- else:
- samples.append(None)
- # The __save_samples method requires sample_widgets, not sample objects, so we'll get what we
- # need by calling __get_sample_widgets(). However, we need to take care here because __get_sample_widgets()
- # is used to populate the sample widget dicts from kwd, and the method assumes that a None object in the
- # received list of samples should be populated from the db. Since we're just re-using the method here to
- # change our list of samples into a list of sample widgets, we'll need to make sure to keep track of our
- # None objects.
- sample_widgets = [obj for obj in samples]
- sample_widgets = self.__get_sample_widgets(trans, request, sample_widgets, **kwd)
- # Replace each sample widget dict with a None object if necessary
- for index, obj in enumerate(samples):
- if obj is None:
- sample_widgets[index] = None
- else:
- sample_widgets = displayable_sample_widgets
- return self.__save_samples(trans, cntrller, request, sample_widgets, saving_new_samples=False, **kwd)
- request_widgets = self.__get_request_widgets(trans, request.id)
- sample_copy_select_field = self.__build_copy_sample_select_field(trans, displayable_sample_widgets)
- libraries_select_field, folders_select_field = self.__build_library_and_folder_select_fields(trans,
- request.user,
- 'sample_operation',
- libraries,
- None,
- **kwd)
- sample_operation_select_field = self.__build_sample_operation_select_field(trans, is_admin, request, sample_operation)
- sample_state_id = params.get('sample_state_id', None)
- sample_state_id_select_field = self.__build_sample_state_id_select_field(trans, request, sample_state_id)
- return trans.fill_template('/requests/common/edit_samples.mako',
- cntrller=cntrller,
- request=request,
- encoded_selected_sample_ids=encoded_selected_sample_ids,
- request_widgets=request_widgets,
- displayable_sample_widgets=displayable_sample_widgets,
- sample_copy_select_field=sample_copy_select_field,
- libraries=libraries,
- sample_operation_select_field=sample_operation_select_field,
- libraries_select_field=libraries_select_field,
- folders_select_field=folders_select_field,
- sample_state_id_select_field=sample_state_id_select_field,
- status=status,
- message=message)
-
- @web.expose
- def update_sample_state(self, trans, cntrller, sample_ids, new_state, comment=None):
- for sample_id in sample_ids:
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- if cntrller == 'api':
- trans.response.status = 400
- return "Invalid sample id ( %s ) specified, unable to decode." % str(sample_id)
- else:
- return invalid_id_redirect(trans, cntrller, sample_id, 'sample')
- if comment is None:
- comment = 'Sample state set to %s' % str(new_state)
- event = trans.model.SampleEvent(sample, new_state, comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- if cntrller == 'api':
- return 200, 'Done'
-
- @web.expose
- @web.require_login("delete sequencing requests")
- def delete_request(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- id_list = util.listify(kwd.get('id', ''))
- message = util.restore_text(params.get('message', ''))
- status = util.restore_text(params.get('status', 'done'))
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- num_deleted = 0
- not_deleted = []
- for id in id_list:
- ok_for_now = True
- try:
- # This block will handle bots that do not send valid request ids.
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(id))
- except:
- ok_for_now = False
- if ok_for_now:
- # We will only allow the request to be deleted by a non-admin user if not request.submitted
- if is_admin or not request.is_submitted:
- request.deleted = True
- trans.sa_session.add(request)
- # Delete all the samples belonging to this request
- for s in request.samples:
- s.deleted = True
- trans.sa_session.add(s)
- comment = "Sequencing request marked deleted by %s." % trans.user.email
- # There is no DELETED state for a request, so keep the current request state
- event = trans.model.RequestEvent(request, request.state, comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- num_deleted += 1
- else:
- not_deleted.append(request)
- message += '%i requests have been deleted.' % num_deleted
- if not_deleted:
- message += ' Contact the administrator to delete the following submitted requests: '
- for request in not_deleted:
- message += '%s, ' % request.name
- message = message.rstrip(', ')
- return trans.response.send_redirect(web.url_for(controller=cntrller,
- action='browse_requests',
- status=status,
- message=message))
-
- @web.expose
- @web.require_login("undelete sequencing requests")
- def undelete_request(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- id_list = util.listify(kwd.get('id', ''))
- message = util.restore_text(params.get('message', ''))
- status = util.restore_text(params.get('status', 'done'))
- num_undeleted = 0
- for id in id_list:
- ok_for_now = True
- try:
- # This block will handle bots that do not send valid request ids.
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(id))
- except:
- ok_for_now = False
- if ok_for_now:
- request.deleted = False
- trans.sa_session.add(request)
- # Undelete all the samples belonging to this request
- for s in request.samples:
- s.deleted = False
- trans.sa_session.add(s)
- comment = "Sequencing request marked undeleted by %s." % trans.user.email
- event = trans.model.RequestEvent(request, request.state, comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- num_undeleted += 1
- message += '%i requests have been undeleted.' % num_undeleted
- return trans.response.send_redirect(web.url_for(controller=cntrller,
- action='browse_requests',
- status=status,
- message=message))
-
- @web.expose
- @web.require_login("sequencing request history")
- def view_request_history(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- request_id = params.get('id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- return trans.fill_template('/requests/common/view_request_history.mako',
- cntrller=cntrller,
- request=request)
-
- @web.expose
- @web.require_login("edit email notification settings")
- def edit_email_settings(self, trans, cntrller, **kwd):
- """
- Allow for changing the email notification settings where email is sent to a list of users
- whenever the request state changes to one selected for notification.
- """
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_id = params.get('id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- email_address = CheckboxField.is_checked(params.get('email_address', ''))
- additional_email_addresses = params.get('additional_email_addresses', '')
- # Get the list of checked sample state CheckBoxFields
- checked_sample_states = []
- for index, sample_state in enumerate(request.type.states):
- if CheckboxField.is_checked(params.get('sample_state_%i' % sample_state.id, '')):
- checked_sample_states.append(sample_state.id)
- if additional_email_addresses:
- additional_email_addresses = additional_email_addresses.split('\r\n')
- if email_address or additional_email_addresses:
- # The user added 1 or more email addresses
- email_addresses = []
- if email_address:
- email_addresses.append(request.user.email)
- for email_address in additional_email_addresses:
- email_addresses.append(util.restore_text(email_address))
- # Make sure email addresses are valid
- err_msg = ''
- for email_address in email_addresses:
- err_msg += validate_email(trans, email_address, check_dup=False)
- if err_msg:
- status = 'error'
- message += err_msg
- else:
- request.notification = dict(email=email_addresses,
- sample_states=checked_sample_states,
- body='',
- subject='')
- else:
- # The user may have eliminated email addresses that were previously set
- request.notification = None
- if checked_sample_states:
- message = 'All sample states have been unchecked since no email addresses have been selected or entered. '
- trans.sa_session.add(request)
- trans.sa_session.flush()
- trans.sa_session.refresh(request)
- message += 'The changes made to the email notification settings have been saved.'
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_basic_request_info',
- cntrller=cntrller,
- id=request_id,
- message=message,
- status=status))
-
- @web.expose
- @web.require_login("update sequencing request state")
- def update_request_state(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- message = params.get('message', '')
- status = params.get('status', 'done')
- request_id = params.get('request_id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- # Make sure all the samples of the current request have the same state
- common_state = request.samples_have_common_state
- if not common_state:
- # If the current request state is complete and one of its samples moved from
- # the final sample state, then move the request state to In-progress
- if request.is_complete:
- message = "At least 1 sample state moved from the final sample state, so now the request's state is (%s)" % request.states.SUBMITTED
- event = trans.model.RequestEvent(request, request.states.SUBMITTED, message)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- if cntrller == 'api':
- return 200, message
- else:
- final_state = False
- request_type_state = request.type.final_sample_state
- if common_state.id == request_type_state.id:
- # since all the samples are in the final state, change the request state to 'Complete'
- comment = "All samples of this sequencing request are in the final sample state (%s). " % request_type_state.name
- state = request.states.COMPLETE
- final_state = True
- else:
- comment = "All samples of this sequencing request are in the (%s) sample state. " % common_state.name
- state = request.states.SUBMITTED
- event = trans.model.RequestEvent(request, state, comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- # See if an email notification is configured to be sent when the samples are in this state.
- retval = request.send_email_notification(trans, common_state, final_state)
- if retval:
- message = comment + retval
- else:
- message = comment
- if cntrller == 'api':
- return 200, message
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- id=request_id,
- status=status,
- message=message))
-
- @web.expose
- @web.require_login("find samples")
- def find_samples(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- samples_list = []
- results = ''
- if params.get('find_samples_button', False):
- search_string = kwd.get('search_box', '')
- search_type = params.get('search_type', '')
- request_states = util.listify(params.get('request_states', ''))
- samples = []
- if search_type == 'bar_code':
- samples = trans.sa_session.query(trans.model.Sample) \
- .filter(and_(trans.model.Sample.table.c.deleted == false(),
- func.lower(trans.model.Sample.table.c.bar_code).like("%" + search_string.lower() + "%"))) \
- .order_by(trans.model.Sample.table.c.create_time.desc())
- elif search_type == 'sample name':
- samples = trans.sa_session.query(trans.model.Sample) \
- .filter(and_(trans.model.Sample.table.c.deleted == false(),
- func.lower(trans.model.Sample.table.c.name).like("%" + search_string.lower() + "%"))) \
- .order_by(trans.model.Sample.table.c.create_time.desc())
- elif search_type == 'dataset':
- samples = trans.sa_session.query(trans.model.Sample) \
- .filter(and_(trans.model.Sample.table.c.deleted == false(),
- trans.model.SampleDataset.table.c.sample_id == trans.model.Sample.table.c.id,
- func.lower(trans.model.SampleDataset.table.c.name).like("%" + search_string.lower() + "%"))) \
- .order_by(trans.model.Sample.table.c.create_time.desc())
- elif search_type == 'form value':
- samples = []
- if search_string.find('=') != -1:
- field_label, field_value = search_string.split('=')
- all_samples = trans.sa_session.query(trans.model.Sample) \
- .filter(trans.model.Sample.table.c.deleted == false()) \
- .order_by(trans.model.Sample.table.c.create_time.desc())
- for sample in all_samples:
- # find the field in the sample form with the given label
- for field in sample.request.type.sample_form.fields:
- if field_label == field['label']:
- # check if the value is equal to the value in the search string
- if sample.values.content[field['name']] == field_value:
- samples.append(sample)
- if is_admin:
- for s in samples:
- if not s.request.deleted and s.request.state in request_states:
- samples_list.append(s)
- else:
- for s in samples:
- if s.request.user.id == trans.user.id and s.request.state in request_states and not s.request.deleted:
- samples_list.append(s)
- results = 'There are %i samples matching the search parameters.' % len(samples_list)
- # Build the request_states SelectField
- selected_value = kwd.get('request_states', trans.model.Request.states.SUBMITTED)
- states = [v for k, v in trans.model.Request.states.items()]
- request_states = build_select_field(trans,
- states,
- 'self',
- 'request_states',
- selected_value=selected_value,
- refresh_on_change=False,
- multiple=True,
- display='checkboxes')
- # Build the search_type SelectField
- selected_value = kwd.get('search_type', 'sample name')
- types = ['sample name', 'bar_code', 'dataset', 'form value']
- search_type = build_select_field(trans, types, 'self', 'search_type', selected_value=selected_value, refresh_on_change=False)
- # Build the search_box TextField
- search_box = TextField('search_box', 50, kwd.get('search_box', ''))
- return trans.fill_template('/requests/common/find_samples.mako',
- cntrller=cntrller,
- request_states=request_states,
- samples=samples_list,
- search_type=search_type,
- results=results,
- search_box=search_box)
-
- @web.expose
- @web.require_login("sample events")
- def view_sample_history(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- sample_id = params.get('sample_id', None)
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- return invalid_id_redirect(trans, cntrller, sample_id, 'sample')
- return trans.fill_template('/requests/common/view_sample_history.mako',
- cntrller=cntrller,
- sample=sample)
-
- @web.expose
- @web.require_login("add samples")
- def add_samples(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- request_id = params.get('id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- libraries = trans.app.security_agent.get_accessible_libraries(trans, request.user)
- # Build a list of sample widgets (based on the attributes of each sample) for display.
- displayable_sample_widgets = self.__get_sample_widgets(trans, request, request.samples, **kwd)
- if params.get('import_samples_button', False):
- # Import sample field values from a csv file
- # TODO: should this be a mapper?
- workflows = [w.latest_workflow for w in trans.user.stored_workflows if not w.deleted]
- return self.__import_samples(trans, cntrller, request, displayable_sample_widgets, libraries, workflows, **kwd)
- elif params.get('add_sample_button', False):
- return self.add_sample(trans, cntrller, request_id, **kwd)
- elif params.get('save_samples_button', False):
- return self.__save_samples(trans, cntrller, request, displayable_sample_widgets, saving_new_samples=True, **kwd)
- request_widgets = self.__get_request_widgets(trans, request.id)
- sample_copy_select_field = self.__build_copy_sample_select_field(trans, displayable_sample_widgets)
- libraries_select_field, folders_select_field = self.__build_library_and_folder_select_fields(trans,
- request.user,
- 'sample_operation',
- libraries,
- None,
- **kwd)
- return trans.fill_template('/requests/common/add_samples.mako',
- cntrller=cntrller,
- request=request,
- request_widgets=request_widgets,
- displayable_sample_widgets=displayable_sample_widgets,
- sample_copy_select_field=sample_copy_select_field,
- libraries=libraries,
- libraries_select_field=libraries_select_field,
- folders_select_field=folders_select_field,
- status=status,
- message=message)
-
- @web.expose
- @web.require_login("add sample")
- def add_sample(self, trans, cntrller, request_id, **kwd):
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- # Get the widgets for rendering the request form
- request_widgets = self.__get_request_widgets(trans, request.id)
- displayable_sample_widgets = self.__get_sample_widgets(trans, request, request.samples, **kwd)
- if params.get('add_sample_button', False):
- libraries = trans.app.security_agent.get_accessible_libraries(trans, request.user)
- num_samples_to_add = int(params.get('num_sample_to_copy', 1))
- # See if the user has selected a sample to copy.
- copy_sample_index = int(params.get('copy_sample_index', -1))
- for index in range(num_samples_to_add):
- field_values = {}
- if copy_sample_index != -1:
- # The user has selected a sample to copy.
- library_id = displayable_sample_widgets[copy_sample_index]['library_select_field'].get_selected(return_value=True)
- folder_id = displayable_sample_widgets[copy_sample_index]['folder_select_field'].get_selected(return_value=True)
- name = displayable_sample_widgets[copy_sample_index]['name'] + '_%i' % (len(displayable_sample_widgets) + 1)
- history_id = displayable_sample_widgets[copy_sample_index]['history_select_field'].get_selected(return_value=True)
- workflow_id = displayable_sample_widgets[copy_sample_index]['workflow_select_field'][0].get_selected(return_value=True)
- # DBTODO Do something nicer with the workflow fieldset. Remove [0] indexing and copy mappings as well.
- for field_name in displayable_sample_widgets[copy_sample_index]['field_values']:
- field_values[field_name] = ''
- else:
- # The user has not selected a sample to copy, just adding a new generic sample.
- library_id = None
- folder_id = None
- history_id = None
- workflow_id = None
- name = 'Sample_%i' % (len(displayable_sample_widgets) + 1)
- for field in request.type.sample_form.fields:
- field_values[field['name']] = ''
- # Build the library_select_field and folder_select_field for the new sample being added.
- library_select_field, folder_select_field = self.__build_library_and_folder_select_fields(trans,
- user=request.user,
- sample_index=len(displayable_sample_widgets),
- libraries=libraries,
- sample=None,
- library_id=library_id,
- folder_id=folder_id,
- **kwd)
- history_select_field = self.__build_history_select_field(trans=trans,
- user=request.user,
- sample_index=len(displayable_sample_widgets),
- history_id=history_id,
- **kwd)
- workflow_select_field = self.__build_workflow_select_field(trans=trans,
- user=request.user,
- request=request,
- sample_index=len(displayable_sample_widgets),
- workflow_id=workflow_id,
- history_id=history_id,
- **kwd)
- # Append the new sample to the current list of samples for the request
- displayable_sample_widgets.append(dict(id=None,
- name=name,
- bar_code='',
- library=None,
- library_id=library_id,
- history=None,
- workflow=None,
- history_select_field=history_select_field,
- workflow_select_field=workflow_select_field,
- folder=None,
- folder_id=folder_id,
- field_values=field_values,
- library_select_field=library_select_field,
- folder_select_field=folder_select_field))
- sample_copy_select_field = self.__build_copy_sample_select_field(trans, displayable_sample_widgets)
- return trans.fill_template('/requests/common/add_samples.mako',
- cntrller=cntrller,
- request=request,
- request_widgets=request_widgets,
- displayable_sample_widgets=displayable_sample_widgets,
- sample_copy_select_field=sample_copy_select_field,
- message=message,
- status=status)
-
- @web.expose
- @web.require_login("view request")
- def view_sample(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- sample_id = params.get('id', None)
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- return invalid_id_redirect(trans, cntrller, sample_id, 'sample')
- # See if we have any associated templates
- widgets = sample.get_template_widgets(trans)
- widget_fields_have_contents = self.widget_fields_have_contents(widgets)
- if is_admin:
- external_services = sample.populate_external_services(trans=trans)
- else:
- external_services = None
- return trans.fill_template('/requests/common/view_sample.mako',
- cntrller=cntrller,
- sample=sample,
- widgets=widgets,
- widget_fields_have_contents=widget_fields_have_contents,
- status=status,
- message=message,
- external_services=external_services)
-
- @web.expose
- @web.require_login("delete sample from sequencing request")
- def delete_sample(self, trans, cntrller, **kwd):
- params = util.Params(kwd)
- status = params.get('status', 'done')
- message = util.restore_text(params.get('message', ''))
- request_id = params.get('request_id', None)
- try:
- request = trans.sa_session.query(trans.model.Request).get(trans.security.decode_id(request_id))
- except:
- return invalid_id_redirect(trans, cntrller, request_id)
- displayable_sample_widgets = self.__get_sample_widgets(trans, request, request.samples, **kwd)
- sample_index = int(params.get('sample_id', 0))
- sample_name = displayable_sample_widgets[sample_index]['name']
- sample = request.get_sample(sample_name)
- if sample:
- trans.sa_session.delete(sample.values)
- trans.sa_session.delete(sample)
- trans.sa_session.flush()
- message = 'Sample (%s) has been deleted.' % sample_name
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- id=trans.security.encode_id(request.id),
- status=status,
- message=message))
-
- @web.expose
- @web.require_login("view data transfer page")
- def view_sample_datasets(self, trans, cntrller, **kwd):
- # The link on the number of selected datasets will only appear if there is at least 1 selected dataset.
- # If there are 0 selected datasets, there is no link, so this method will only be reached from the requests
- # controller if there are selected datasets.
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- is_admin = cntrller == 'requests_admin' and trans.user_is_admin()
- sample_id = params.get('sample_id', None)
- try:
- sample = trans.sa_session.query(trans.model.Sample).get(trans.security.decode_id(sample_id))
- except:
- return invalid_id_redirect(trans, cntrller, sample_id, 'sample')
- external_service_id = params.get('external_service_id', None)
- external_service = trans.sa_session.query(trans.model.ExternalService).get(trans.security.decode_id(external_service_id))
- # See if a library and folder have been set for this sample.
- if is_admin and not sample.library or not sample.folder:
- status = 'error'
- message = "Select a target data library and folder for the sample before selecting the datasets."
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- id=trans.security.encode_id(sample.request.id),
- status=status,
- message=message))
- transfer_status = params.get('transfer_status', None)
- if transfer_status in [None, 'None']:
- title = 'All selected datasets for "%s"' % sample.name
- sample_datasets = sample.datasets
- elif transfer_status == trans.model.SampleDataset.transfer_status.IN_QUEUE:
- title = 'Datasets of "%s" that are in the transfer queue' % sample.name
- sample_datasets = sample.queued_dataset_files
- elif transfer_status == trans.model.SampleDataset.transfer_status.TRANSFERRING:
- title = 'Datasets of "%s" that are being transferred' % sample.name
- sample_datasets = sample.transferring_dataset_files
- elif transfer_status == trans.model.SampleDataset.transfer_status.ADD_TO_LIBRARY:
- title = 'Datasets of "%s" that are being added to the target data library' % sample.name
- sample_datasets = sample.adding_to_library_dataset_files
- elif transfer_status == trans.model.SampleDataset.transfer_status.COMPLETE:
- title = 'Datasets of "%s" that are available in the target data library' % sample.name
- sample_datasets = sample.transferred_dataset_files
- elif transfer_status == trans.model.SampleDataset.transfer_status.ERROR:
- title = 'Datasets of "%s" that resulted in a transfer error' % sample.name
- sample_datasets = sample.transfer_error_dataset_files
- return trans.fill_template('/requests/common/view_sample_datasets.mako',
- cntrller=cntrller,
- title=title,
- external_service=external_service,
- sample=sample,
- sample_datasets=sample_datasets,
- transfer_status=transfer_status,
- message=message,
- status=status)
-
- def __import_samples(self, trans, cntrller, request, displayable_sample_widgets, libraries, workflows, **kwd):
- """
- Reads the samples csv file and imports all the samples. The csv file must be in the following format. The [:FieldValue]
- is optional, the form field will contain the value after the ':' if included.
- SampleName,DataLibraryName,DataLibraryFolderName,HistoryName,WorkflowName,Field1Name:Field1Value,Field2Name:Field2Value...
- """
- params = util.Params(kwd)
- file_obj = params.get('file_data', '')
- try:
- reader = csv.reader(file_obj.file)
- for row in reader:
- library_id = None
- library = None
- folder_id = None
- folder = None
- history_id = None
- history = None
- workflow_id = None
- workflow = None
- # Get the library
- library = trans.sa_session.query(trans.model.Library) \
- .filter(and_(trans.model.Library.table.c.name == row[1],
- trans.model.Library.table.c.deleted == false())) \
- .first()
- if library:
- # Get the folder
- for folder in trans.sa_session.query(trans.model.LibraryFolder) \
- .filter(and_(trans.model.LibraryFolder.table.c.name == row[2],
- trans.model.LibraryFolder.table.c.deleted == false())):
- if folder.parent_library == library:
- break
- if folder:
- library_id = trans.security.encode_id(library.id)
- folder_id = trans.security.encode_id(folder.id)
- library_select_field, folder_select_field = self.__build_library_and_folder_select_fields(trans,
- request.user,
- len(displayable_sample_widgets),
- libraries,
- None,
- library_id,
- folder_id,
- **kwd)
- # Get the history
- history = trans.sa_session.query(trans.model.History) \
- .filter(and_(trans.model.History.table.c.name == row[3],
- trans.model.History.table.c.deleted == false(),
- trans.model.History.user_id == trans.user.id)) \
- .first()
- if history:
- history_id = trans.security.encode_id(history.id)
- else:
- history_id = 'none'
- history_select_field = self.__build_history_select_field(trans=trans,
- user=request.user,
- sample_index=len(displayable_sample_widgets),
- history_id=history_id)
- # Get the workflow
- workflow = trans.sa_session.query(trans.model.StoredWorkflow) \
- .filter(and_(trans.model.StoredWorkflow.table.c.name == row[4],
- trans.model.StoredWorkflow.table.c.deleted == false(),
- trans.model.StoredWorkflow.user_id == trans.user.id)) \
- .first()
- if workflow:
- workflow_id = trans.security.encode_id(workflow.id)
- else:
- workflow_id = 'none'
- workflow_select_field = self.__build_workflow_select_field(trans=trans,
- user=request.user,
- request=request,
- sample_index=len(displayable_sample_widgets),
- workflow_id=workflow_id,
- history_id=history_id)
- field_values = {}
- field_names = row[5:]
- for field_name in field_names:
- if field_name.find(':') >= 0:
- field_list = field_name.split(':')
- field_name = field_list[0]
- field_value = field_list[1]
- else:
- field_value = ''
- field_values[field_name] = field_value
- displayable_sample_widgets.append(dict(id=None,
- name=row[0],
- bar_code='',
- library=library,
- library_id=library_id,
- library_select_field=library_select_field,
- folder=folder,
- folder_id=folder_id,
- folder_select_field=folder_select_field,
- history=history,
- history_id=history_id,
- history_select_field=history_select_field,
- workflow=workflow,
- workflow_id=workflow_id,
- workflow_select_field=workflow_select_field,
- field_values=field_values))
- except Exception as e:
- if str(e) == "'unicode' object has no attribute 'file'":
- message = "Select a file"
- else:
- message = 'Error attempting to create samples from selected file: %s.' % str(e)
- message += ' Make sure the selected csv file uses the format: SampleName,DataLibrary,DataLibraryFolder,FieldValue1,FieldValue2...'
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='add_sample',
- cntrller=cntrller,
- request_id=trans.security.encode_id(request.id),
- add_sample_button='Add sample',
- status='error',
- message=message))
- request_widgets = self.__get_request_widgets(trans, request.id)
- sample_copy_select_field = self.__build_copy_sample_select_field(trans, displayable_sample_widgets)
- return trans.fill_template('/requests/common/add_samples.mako',
- cntrller=cntrller,
- request=request,
- request_widgets=request_widgets,
- displayable_sample_widgets=displayable_sample_widgets,
- sample_copy_select_field=sample_copy_select_field)
-
- def __save_samples(self, trans, cntrller, request, sample_widgets, saving_new_samples=False, **kwd):
- # Here we handle saving all new samples added by the user as well as saving
- # changes to any subset of the request's samples. A sample will not have an
- # associated SampleState until the request is submitted, at which time the
- # sample is automatically associated with the first SampleState configured by
- # the admin for the request's RequestType.
- params = util.Params(kwd)
- message = util.restore_text(params.get('message', ''))
- status = params.get('status', 'done')
- sample_operation = params.get('sample_operation', 'none')
- if saving_new_samples:
- redirect_action = 'add_samples'
- else:
- redirect_action = 'edit_samples'
- # Check for duplicate sample names within the request
- self.__validate_sample_names(trans, cntrller, request, sample_widgets, **kwd)
- log.debug("SAVING SAMPLES!")
- log.debug("saving_new_samples is %s" % saving_new_samples)
- if not saving_new_samples:
- library = None
- folder = None
-
- def handle_error(**kwd):
- kwd['status'] = 'error'
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action=redirect_action,
- cntrller=cntrller,
- **kwd))
- # Here we handle saving changes to single samples as well as saving changes to
- # selected sets of samples. If samples are selected, the sample_operation param
- # will have a value other than 'none', and the samples param will be a list of
- # encoded sample ids. There are currently only 2 multi-select operations;
- # model.Sample.bulk_operations.CHANGE_STATE and model.sample.bulk_operations.SELECT_LIBRARY.
- # If sample_operation is 'none, then the samples param will be a list of sample objects.
- if sample_operation == trans.model.Sample.bulk_operations.CHANGE_STATE:
- sample_state_id = params.get('sample_state_id', None)
- if sample_state_id in [None, 'none']:
- message = "Select a new state from the Change current state list before clicking the Save button."
- kwd['message'] = message
- del kwd['save_samples_button']
- handle_error(**kwd)
- sample_event_comment = util.restore_text(params.get('sample_event_comment', ''))
- new_state = trans.sa_session.query(trans.model.SampleState).get(trans.security.decode_id(sample_state_id))
- # Send the encoded sample_ids to update_sample_state.
- # TODO: make changes necessary to just send the samples...
- encoded_selected_sample_ids = self.__get_encoded_selected_sample_ids(trans, request, **kwd)
- # Make sure all samples have a unique bar_code if the state is changing
- for sample_index in range(len(sample_widgets)):
- current_sample = sample_widgets[sample_index]
- if current_sample is None:
- # We have a None value because the user did not select this sample
- # on which to perform the action.
- continue
- request_sample = request.samples[sample_index]
- bar_code = current_sample['bar_code']
- if bar_code:
- # If the sample has a new bar_code, make sure it is unique.
- bc_message = self.__validate_bar_code(trans, request_sample, bar_code)
- if bc_message:
- message += bc_message
- kwd['message'] = message
- del kwd['save_samples_button']
- handle_error(**kwd)
- self.update_sample_state(trans, cntrller, encoded_selected_sample_ids, new_state, comment=sample_event_comment)
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- cntrller=cntrller,
- action='update_request_state',
- request_id=trans.security.encode_id(request.id)))
- elif sample_operation == trans.model.Sample.bulk_operations.SELECT_LIBRARY:
- library_id = params.get('sample_operation_library_id', 'none')
- folder_id = params.get('sample_operation_folder_id', 'none')
- library, folder = self.__get_library_and_folder(trans, library_id, folder_id)
- for sample_index in range(len(sample_widgets)):
- current_sample = sample_widgets[sample_index]
- if current_sample is None:
- # We have a None value because the user did not select this sample
- # on which to perform the action.
- continue
- current_sample['library'] = library
- current_sample['folder'] = folder
- self.__update_samples(trans, cntrller, request, sample_widgets, **kwd)
- message = 'Changes made to the samples have been saved. '
- else:
- # Saving a newly created sample. The sample will not have an associated SampleState
- # until the request is submitted, at which time all samples of the request will be
- # set to the first SampleState configured for the request's RequestType configured
- # by the admin ( i.e., the sample's SampleState would be set to request.type.states[0] ).
- new_samples = []
- for index in range(len(sample_widgets) - len(request.samples)):
- sample_index = len(request.samples)
- sample_widget = sample_widgets[sample_index]
- form_values = trans.model.FormValues(request.type.sample_form, sample_widget['field_values'])
- trans.sa_session.add(form_values)
- trans.sa_session.flush()
- if request.is_submitted:
- bar_code = sample_widget['bar_code']
- else:
- bar_code = ''
- sample = trans.model.Sample(name=sample_widget['name'],
- desc='',
- request=request,
- form_values=form_values,
- bar_code=bar_code,
- library=sample_widget['library'],
- folder=sample_widget['folder'],
- history=sample_widget['history'],
- workflow=sample_widget['workflow_dict'])
- trans.sa_session.add(sample)
- trans.sa_session.flush()
- new_samples.append(sample)
- # If this sample is added when the request is already submitted then these new samples
- # should be in the first sample state when saved
- if request.is_submitted:
- initial_sample_state_after_request_submitted = request.type.states[0]
- for sample in new_samples:
- event_comment = 'Sample added and sample state set to %s.' % request.type.states[0].name
- event = trans.model.SampleEvent(sample,
- initial_sample_state_after_request_submitted,
- event_comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action=redirect_action,
- cntrller=cntrller,
- id=trans.security.encode_id(request.id),
- status=status,
- message=message))
-
- def __update_samples(self, trans, cntrller, request, sample_widgets, **kwd):
- # The list of sample_widgets must have the same number of objects as request.samples,
- # but some of the objects can be None. Those that are not None correspond to samples
- # selected by the user for performing an action on multiple samples simultaneously.
- # The items in the sample_widgets list have already been populated with any changed
- # param values (changed implies the value in kwd is different from the attribute value
- # in the database) in kwd before this method is reached.
- def handle_error(**kwd):
- kwd['status'] = 'error'
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- **kwd))
- for index, sample_widget in enumerate(sample_widgets):
- if sample_widget is not None:
- # sample_widget will be None if the user checked sample check boxes and selected an action
- # to perform on multiple samples, but did not select certain samples.
- sample = request.samples[index]
- # Get the sample's form values to see if they have changed.
- form_values = trans.sa_session.query(trans.model.FormValues).get(sample.values.id)
- if sample.name != sample_widget['name'] or \
- sample.bar_code != sample_widget['bar_code'] or \
- sample.library != sample_widget['library'] or \
- sample.folder != sample_widget['folder'] or \
- sample.history != sample_widget['history'] or \
- sample.workflow != sample_widget['workflow_dict'] or \
- form_values.content != sample_widget['field_values']:
- # Information about this sample has been changed.
- sample.name = sample_widget['name']
- bar_code = sample_widget['bar_code']
- # If the sample has a new bar_code, make sure it is unique.
- if bar_code:
- bc_message = self.__validate_bar_code(trans, sample, bar_code)
- if bc_message:
- kwd['message'] = bc_message
- del kwd['save_samples_button']
- handle_error(**kwd)
- if not sample.bar_code:
- # If the sample's associated SampleState is still the initial state
- # configured by the admin for the request's RequestType, this must be
- # the first time a bar code was added to the sample, so change its state
- # to the next associated SampleState.
- if sample.state.id == request.type.states[0].id:
- # Change the sample state only if its request_type
- # has at least 2 states
- if len(request.type.states) >= 2:
- next_sample_state = request.type.states[1]
- else:
- next_sample_state = request.type.states[0]
- event = trans.model.SampleEvent(sample,
- next_sample_state,
- 'Bar code associated with the sample')
- trans.sa_session.add(event)
- trans.sa_session.flush()
- # Next step is to update the request event history if bar codes
- # have been assigned to all the samples of this request
- common_state = request.samples_have_common_state
- if request.is_submitted and common_state and len(request.type.states) >= 2:
- comment = "All samples of this request are in the (%s) sample state. " % common_state.name
- event = trans.model.RequestEvent(request, request.states.SUBMITTED, comment)
- trans.sa_session.add(event)
- trans.sa_session.flush()
- request.send_email_notification(trans, next_sample_state)
-
- sample.bar_code = bar_code
- sample.library = sample_widget['library']
- sample.folder = sample_widget['folder']
- sample.history = sample_widget['history']
- sample.workflow = sample_widget['workflow_dict']
- form_values.content = sample_widget['field_values']
- trans.sa_session.add_all((sample, form_values))
- trans.sa_session.flush()
-
- def __get_library_and_folder(self, trans, library_id, folder_id):
- try:
- library = trans.sa_session.query(trans.model.Library).get(trans.security.decode_id(library_id))
- except:
- library = None
- if library and folder_id == 'none':
- folder = library.root_folder
- elif library and folder_id != 'none':
- try:
- folder = trans.sa_session.query(trans.model.LibraryFolder).get(trans.security.decode_id(folder_id))
- except:
- if library:
- folder = library.root_folder
- else:
- folder = None
- else:
- folder = None
- return library, folder
-
- def __get_history(self, trans, history_id):
- try:
- history = trans.sa_session.query(trans.model.History).get(trans.security.decode_id(history_id))
- return history
- except:
- return None
-
- def __get_workflow(self, trans, workflow_id):
- try:
- workflow = trans.sa_session.query(trans.model.Workflow).get(trans.security.decode_id(workflow_id))
- return workflow
- except:
- return None
-
- def __get_active_folders(self, folder, active_folders_list):
- """Return all of the active folders for the received library"""
- active_folders_list.extend(folder.active_folders)
- for sub_folder in folder.active_folders:
- self.__get_active_folders(sub_folder, active_folders_list)
- return active_folders_list
-
- # ===== Methods for handling form definition widgets =====
- def __get_request_widgets(self, trans, id):
- """Get the widgets for the request"""
- request = trans.sa_session.query(trans.model.Request).get(id)
- # The request_widgets list is a list of dictionaries
- request_widgets = []
- for index, field in enumerate(request.type.request_form.fields):
- field_value = request.values.content[field['name']]
- if field['required']:
- required_label = 'Required'
- else:
- required_label = 'Optional'
- if field['type'] == 'AddressField':
- if field_value:
- request_widgets.append(dict(label=field['label'],
- value=trans.sa_session.query(trans.model.UserAddress).get(int(field_value)).get_html(),
- helptext=field['helptext'] + ' (' + required_label + ')'))
- else:
- request_widgets.append(dict(label=field['label'],
- value=None,
- helptext=field['helptext'] + ' (' + required_label + ')'))
- else:
- request_widgets.append(dict(label=field['label'],
- value=field_value,
- helptext=field['helptext'] + ' (' + required_label + ')'))
- return request_widgets
-
- def __get_sample_widgets(self, trans, request, samples, **kwd):
- """
- Returns a list of dictionaries, each representing the widgets that define a sample on a form.
- The widgets are populated from kwd based on the set of samples received. The set of samples
- corresponds to a request.samples list, but if the user checked specific check boxes on the form,
- those samples that were not checked will have None objects in the list of samples. In this case,
- the corresponding sample_widget is populated from the db rather than kwd.
- """
- params = util.Params(kwd)
- sample_operation = params.get('sample_operation', 'none')
- sample_widgets = []
- if sample_operation != 'none':
- # The sample_operatin param has a value other than 'none', and a specified
- # set of samples was received.
- library_id = util.restore_text(params.get('sample_operation_library_id', 'none'))
- folder_id = util.restore_text(params.get('sample_operation_folder_id', 'none'))
- # Build the list of widgets which will be used to render each sample row on the request page
- if not request:
- return sample_widgets
- libraries = trans.app.security_agent.get_accessible_libraries(trans, request.user)
- # Build the list if sample widgets, populating the values from kwd.
- for index, sample in enumerate(samples):
- if sample is None:
- # Use the sample from the request object since it will not have updated values from kwd.
- sample = request.samples[index]
- sample_id = sample.id
- name = sample.name
- bar_code = sample.bar_code
- library = sample.library
- folder = sample.folder
- history = sample.history
- workflow = sample.workflow
- field_values = sample.values.content
- if not history:
- history_id = 'none'
- else:
- history_id = history.id
- if not workflow:
- workflow_id = 'none'
- else:
- workflow_id = workflow.id
- workflow_dict = sample.workflow
- else:
- # Update the sample attributes from kwd
- sample_id = None
- name = util.restore_text(params.get('sample_%i_name' % index, sample.name))
- bar_code = util.restore_text(params.get('sample_%i_bar_code' % index, sample.bar_code))
- library_id = util.restore_text(params.get('sample_%i_library_id' % index, ''))
- if not library_id and sample.library:
- library_id = trans.security.encode_id(sample.library.id)
- folder_id = util.restore_text(params.get('sample_%i_folder_id' % index, ''))
- if not folder_id and sample.folder:
- folder_id = trans.security.encode_id(sample.folder.id)
- library, folder = self.__get_library_and_folder(trans, library_id, folder_id)
- history_id = util.restore_text(params.get('sample_%i_history_id' % index, ''))
- if not history_id and sample.history:
- history_id = trans.security.encode_id(sample.history.id)
- history = self.__get_history(trans, history_id)
- wf_tag = 'sample_%i_workflow_id' % index
- workflow_id = util.restore_text(params.get(wf_tag, ''))
- if not workflow_id and sample.workflow:
- workflow_id = trans.security.encode_id(sample.workflow['id'])
- workflow_dict = sample.workflow
- workflow = self.__get_workflow(trans, workflow_id)
- else:
- workflow_dict = None
- workflow = self.__get_workflow(trans, workflow_id)
- if workflow:
- workflow_dict = {'id': workflow.id,
- 'name': workflow.name,
- 'mappings': {}}
- for k, v in kwd.iteritems():
- kwd_tag = "%s_" % wf_tag
- if k.startswith(kwd_tag):
- # DBTODO Don't need to store the whole mapping word in the dict, only the step.
- workflow_dict['mappings'][int(k[len(kwd_tag):])] = {'ds_tag': v}
- field_values = {}
- for field_index, field in enumerate(request.type.sample_form.fields):
- field_name = field['name']
- input_value = params.get('sample_%i_field_%i' % (index, field_index), sample.values.content[field_name])
- if field['type'] == CheckboxField.__name__:
- field_value = CheckboxField.is_checked(input_value)
- else:
- field_value = util.restore_text(input_value)
- field_values[field_name] = field_value
- library_select_field, folder_select_field = self.__build_library_and_folder_select_fields(trans=trans,
- user=request.user,
- sample_index=index,
- libraries=libraries,
- sample=sample,
- library_id=library_id,
- folder_id=folder_id,
- **kwd)
- history_select_field = self.__build_history_select_field(trans=trans,
- user=request.user,
- sample_index=index,
- sample=sample,
- history_id=history_id,
- **kwd)
- workflow_select_field = self.__build_workflow_select_field(trans=trans,
- user=request.user,
- request=request,
- sample_index=index,
- sample=sample,
- workflow_dict=workflow_dict,
- history_id=history_id,
- **kwd)
- sample_widgets.append(dict(id=sample_id,
- name=name,
- bar_code=bar_code,
- library=library,
- folder=folder,
- history=history,
- workflow=workflow,
- workflow_dict=workflow_dict,
- field_values=field_values,
- library_select_field=library_select_field,
- folder_select_field=folder_select_field,
- history_select_field=history_select_field,
- workflow_select_field=workflow_select_field))
- # There may be additional new samples on the form that have not yet been associated with the request.
- # TODO: factor this code so it is not duplicating what's above.
- index = len(samples)
- while True:
- name = util.restore_text(params.get('sample_%i_name' % index, ''))
- if not name:
- break
- bar_code = util.restore_text(params.get('sample_%i_bar_code' % index, ''))
- library_id = util.restore_text(params.get('sample_%i_library_id' % index, ''))
- folder_id = util.restore_text(params.get('sample_%i_folder_id' % index, ''))
- library, folder = self.__get_library_and_folder(trans, library_id, folder_id)
- history_id = util.restore_text(params.get('sample_%i_history_id' % index, ''))
- if not history_id and sample.history:
- history_id = trans.security.encode_id(sample.history.id)
- history = self.__get_history(trans, history_id)
- wf_tag = 'sample_%i_workflow_id' % index
- workflow_id = util.restore_text(params.get(wf_tag, ''))
- if not workflow_id and sample.workflow:
- workflow_id = trans.security.encode_id(sample.workflow['id'])
- workflow_dict = sample.workflow
- workflow = self.__get_workflow(trans, workflow_id)
- else:
- workflow_dict = None
- workflow = self.__get_workflow(trans, workflow_id)
- if workflow:
- workflow_dict = {'id': workflow.id,
- 'name': workflow.name,
- 'mappings': {}}
- for k, v in kwd.iteritems():
- kwd_tag = "%s_" % wf_tag
- if k.startswith(kwd_tag):
- # DBTODO Change the key to include the dataset tag, not just the names.
- workflow_dict['mappings'][int(k[len(kwd_tag):])] = {'ds_tag': v}
- field_values = {}
- for field_index, field in enumerate(request.type.sample_form.fields):
- field_name = field['name']
- input_value = params.get('sample_%i_field_%i' % (index, field_index), '')
- if field['type'] == CheckboxField.__name__:
- field_value = CheckboxField.is_checked(input_value)
- else:
- field_value = util.restore_text(input_value)
- field_values[field_name] = field_value
- library_select_field, folder_select_field = self.__build_library_and_folder_select_fields(trans=trans,
- user=request.user,
- sample_index=index,
- libraries=libraries,
- sample=None,
- library_id=library_id,
- folder_id=folder_id,
- **kwd)
- history_select_field = self.__build_history_select_field(trans=trans,
- user=request.user,
- sample_index=index,
- sample=None,
- history_id=history_id,
- **kwd)
-
- workflow_select_field = self.__build_workflow_select_field(trans=trans,
- user=request.user,
- request=request,
- sample_index=index,
- sample=None,
- workflow_dict=workflow_dict,
- history_id=history_id,
- **kwd)
- sample_widgets.append(dict(id=None,
- name=name,
- bar_code=bar_code,
- library=library,
- folder=folder,
- field_values=field_values,
- history=history,
- workflow=workflow,
- workflow_dict=workflow_dict,
- history_select_field=history_select_field,
- workflow_select_field=workflow_select_field,
- library_select_field=library_select_field,
- folder_select_field=folder_select_field))
- index += 1
- return sample_widgets
-
- # ===== Methods for building SelectFields used on various request forms =====
- def __build_copy_sample_select_field(self, trans, displayable_sample_widgets):
- copy_sample_index_select_field = SelectField('copy_sample_index')
- copy_sample_index_select_field.add_option('None', -1, selected=True)
- for index, sample_dict in enumerate(displayable_sample_widgets):
- copy_sample_index_select_field.add_option(sample_dict['name'], index)
- return copy_sample_index_select_field
-
- def __build_request_type_id_select_field(self, trans, selected_value='none'):
- accessible_request_types = trans.app.security_agent.get_accessible_request_types(trans, trans.user)
- return build_select_field(trans, accessible_request_types, 'name', 'request_type_id', selected_value=selected_value, refresh_on_change=True)
-
- def __build_user_id_select_field(self, trans, selected_value='none'):
- active_users = trans.sa_session.query(trans.model.User) \
- .filter(trans.model.User.table.c.deleted == false()) \
- .order_by(trans.model.User.email.asc())
- # A refresh_on_change is required so the user's set of addresses can be displayed.
- return build_select_field(trans, active_users, 'email', 'user_id', selected_value=selected_value, refresh_on_change=True)
-
- def __build_sample_operation_select_field(self, trans, is_admin, request, selected_value):
- # The sample_operation SelectField is displayed only after the request has been submitted.
- # its label is "For selected samples"
- if is_admin:
- if request.is_complete:
- bulk_operations = [trans.model.Sample.bulk_operations.CHANGE_STATE]
- if request.is_rejected:
- bulk_operations = [trans.model.Sample.bulk_operations.SELECT_LIBRARY]
- else:
- bulk_operations = [s for i, s in trans.model.Sample.bulk_operations.items()]
- else:
- if request.is_complete:
- bulk_operations = []
- else:
- bulk_operations = [trans.model.Sample.bulk_operations.SELECT_LIBRARY]
- return build_select_field(trans, bulk_operations, 'self', 'sample_operation', selected_value=selected_value, refresh_on_change=True)
-
- def __build_library_and_folder_select_fields(self, trans, user, sample_index, libraries, sample=None, library_id=None, folder_id=None, **kwd):
- # Create the library_id SelectField for a specific sample. The received libraries param is a list of all the libraries
- # accessible to the current user, and we add them as options to the library_select_field. If the user has selected an
- # existing library then display all the folders of the selected library in the folder_select_field. Library folders do
- # not have ACCESS permissions associated with them (only LIBRARY_ADD, LIBRARY_MODIFY, LIBRARY_MANAGE), so all folders will
- # be present in the folder_select_field for each library selected.
- params = util.Params(kwd)
- if sample_index == 'sample_operation':
- # build the library selection widget for the bulk sample operation
- library_select_field_name = "sample_operation_library_id"
- folder_select_field_name = "sample_operation_folder_id"
- else:
- library_select_field_name = "sample_%i_library_id" % sample_index
- folder_select_field_name = "sample_%i_folder_id" % sample_index
- if not library_id:
- library_id = params.get(library_select_field_name, None)
- if not folder_id:
- folder_id = params.get(folder_select_field_name, None)
- selected_library = None
- if library_id not in [None, 'none']:
- for library in libraries:
- encoded_id = trans.security.encode_id(library.id)
- if encoded_id == str(library_id):
- selected_library = library
- break
- elif sample and sample.library and library_id == 'none':
- # The user previously selected a library but is now resetting the selection to 'none'
- selected_library = None
- elif sample and sample.library:
- library_id = trans.security.encode_id(sample.library.id)
- selected_library = sample.library
- # Build the sample_%i_library_id SelectField with refresh on change enabled
- library_select_field = build_select_field(trans,
- libraries,
- 'name',
- library_select_field_name,
- initial_value='none',
- selected_value=str(library_id).lower(),
- refresh_on_change=True)
- # Get all folders for the selected library, if one is indeed selected
- if selected_library:
- folders = self.__get_active_folders(selected_library.root_folder, active_folders_list=[selected_library.root_folder])
- if folder_id:
- selected_folder_id = folder_id
- elif sample and sample.folder:
- selected_folder_id = trans.security.encode_id(sample.folder.id)
- else:
- selected_folder_id = trans.security.encode_id(selected_library.root_folder.id)
- else:
- selected_folder_id = 'none'
- folders = []
- # Change the name of the library root folder to clarify that it is the root
- for folder in folders:
- if not folder.parent:
- folder.name = 'Data library root'
- break
- folder_select_field = build_select_field(trans,
- folders,
- 'name',
- folder_select_field_name,
- initial_value='none',
- selected_value=selected_folder_id)
- return library_select_field, folder_select_field
-
- def __build_history_select_field(self, trans, user, sample_index, sample=None, history_id=None, **kwd):
- params = util.Params(kwd)
- history_select_field_name = "sample_%i_history_id" % sample_index
- if not history_id:
- history_id = params.get(history_select_field_name, None)
- if history_id not in [None, 'none', 'new']:
- for history in user.histories:
- if not history.deleted:
- encoded_id = trans.security.encode_id(history.id)
- if encoded_id == str(history_id):
- break
- elif sample and sample.history and history_id == 'none' or history_id == 'new':
- # The user previously selected a history but is now resetting the selection to 'none'
- pass
- elif sample and sample.history:
- history_id = trans.security.encode_id(sample.history.id)
- # Build the sample_%i_history_id SelectField with refresh on change disabled
- hsf = build_select_field(trans,
- [h for h in user.histories if not h.deleted],
- 'name',
- history_select_field_name,
- initial_value='none',
- selected_value=str(history_id).lower(),
- refresh_on_change=True)
- # This is ugly, but allows for an explicit "New History", while still using build_select_field.
- # hsf.options = hsf.options[:1] + [( "Create a New History", 'new', 'new'==str( history_id ).lower() )] + hsf.options[1:]
- hsf.options = [("Select one", 'none', 'none' == str(history_id).lower())] + hsf.options[1:]
- return hsf
-
- def __build_workflow_select_field(self, trans, user, request, sample_index, sample=None, workflow_id=None, workflow_dict=None, history_id=None, **kwd):
- params = util.Params(kwd)
- workflow_select_field_name = "sample_%i_workflow_id" % sample_index
- selected_workflow = None
- if not workflow_id:
- workflow_id = params.get(workflow_select_field_name, None)
- if workflow_id not in [None, 'none']:
- selected_workflow = trans.sa_session.query(trans.model.Workflow).get(trans.security.decode_id(workflow_id))
- elif sample and sample.workflow and workflow_id == 'none':
- selected_workflow = None
- elif sample and sample.workflow:
- workflow_id = sample.workflow['id']
- selected_workflow = trans.sa_session.query(trans.model.Workflow).get(sample.workflow['id'])
- s_list = [w.latest_workflow for w in user.stored_workflows if not w.deleted]
- if selected_workflow and selected_workflow not in s_list:
- s_list.append(selected_workflow)
- workflow_select_field = build_select_field(trans,
- s_list,
- 'name',
- workflow_select_field_name,
- initial_value='none',
- selected_value=str(workflow_id).lower(),
- refresh_on_change=True)
- workflow_select_field.options = [("Select one", 'none', 'none' == str(workflow_id).lower())] + workflow_select_field.options[1:]
- wf_fieldset = [workflow_select_field]
- if selected_workflow and request.type.external_services:
- # DBTODO This will work for now, but should be handled more rigorously.
- ds_list = []
- external_service = request.type.external_services[0]
- dataset_name_re = re.compile('(dataset\d+)_(name)')
- for k, v in external_service.form_values.content.items():
- match = dataset_name_re.match(k)
- if match:
- ds_list.append(("ds|%s" % k[:-5], v))
- if history_id not in [None, 'none', 'new', '']:
- hist = trans.sa_session.query(trans.model.History).get(trans.security.decode_id(history_id))
- h_inputs = [("hi|%s" % trans.security.encode_id(ds.id), ds.name) for ds in hist.datasets if not ds.deleted]
- ds_list += h_inputs
- for step in selected_workflow.steps:
- if step.type == 'data_input':
- if step.tool_inputs and "name" in step.tool_inputs:
- sf_name = '%s_%s' % (workflow_select_field_name, step.id)
- select_field = SelectField(name=sf_name)
- sf = params.get(sf_name, None)
- if not sf and sample and sample.workflow:
- if str(step.id) in sample.workflow['mappings']:
- sf = sample.workflow['mappings'][str(step.id)]['ds_tag']
- for value, label in ds_list:
- if value == sf:
- select_field.add_option(label, value, selected=True)
- else:
- select_field.add_option(label, value)
- wf_fieldset.append((step.tool_inputs['name'], select_field))
- return wf_fieldset
-
- def __build_sample_state_id_select_field(self, trans, request, selected_value):
- if selected_value == 'none':
- if request.samples:
- selected_value = trans.security.encode_id(request.samples[0].state.id)
- else:
- selected_value = trans.security.encode_id(request.type.states[0].id)
- return build_select_field(trans,
- objs=request.type.states,
- label_attr='name',
- select_field_name='sample_state_id',
- selected_value=selected_value,
- refresh_on_change=False)
-
- # ===== Methods for validation forms and fields =====
- def __validate_request(self, trans, cntrller, request):
- """Validates the request entered by the user"""
- # TODO: Add checks for required sample fields here.
- empty_fields = []
- # Make sure required form fields are filled in.
- for index, field in enumerate(request.type.request_form.fields):
- if field['required'] == 'required' and request.values.content[field['name']] in ['', None]:
- empty_fields.append(field['label'])
- empty_sample_fields = []
- for s in request.samples:
- for field in request.type.sample_form.fields:
- log.debug("field: %s", field)
- log.debug("svc: %s", s.values.content)
- if field['required'] == 'required' and s.values.content[field['name']] in ['', None]:
- empty_sample_fields.append((s.name, field['label']))
- if empty_fields or empty_sample_fields:
- message = 'Complete the following fields of the request before submitting: '
- if empty_fields:
- for ef in empty_fields:
- message += '%s ' % ef
- if empty_sample_fields:
- for sname, ef in empty_sample_fields:
- message = message + '%s field of sample %s ' % (ef, sname)
- return message
- return None
-
- def __validate_sample_names(self, trans, cntrller, request, displayable_sample_widgets, **kwd):
- # Check for duplicate sample names for all samples of the request.
- message = ''
- for index in range(len(displayable_sample_widgets) - len(request.samples)):
- sample_index = index + len(request.samples)
- sample_widget = displayable_sample_widgets[sample_index]
- sample_name = sample_widget['name']
- if not sample_name.strip():
- message = 'Enter the name of sample number %i' % sample_index
- break
- count = 0
- for i in range(len(displayable_sample_widgets)):
- if sample_name == displayable_sample_widgets[i]['name']:
- count += 1
- if count > 1:
- message = "You tried to add %i samples with the name (%s). Samples belonging to a request must have unique names." % (count, sample_name)
- break
- if message:
- del kwd['save_samples_button']
- kwd['message'] = message
- kwd['status'] = 'error'
- return trans.response.send_redirect(web.url_for(controller='requests_common',
- action='edit_samples',
- cntrller=cntrller,
- **kwd))
-
- def __validate_bar_code(self, trans, sample, bar_code):
- """
- Make sure that the bar_code about to be assigned to a sample is globally unique.
- That is, bar_codes must be unique across requests in Galaxy sample tracking.
- Bar codes are not required, but if used, they can only be added to a sample after
- the request is submitted.
- """
- message = ''
- unique = True
- for index in range(len(sample.request.samples)):
- # TODO: Add a unique constraint to sample.bar_code table column
- # Make sure bar code is unique
- for sample_with_bar_code in trans.sa_session.query(trans.model.Sample) \
- .filter(trans.model.Sample.table.c.bar_code == bar_code):
- if sample_with_bar_code and sample_with_bar_code.id != sample.id:
- message = '''The bar code (%s) associated with the sample (%s) belongs to another sample.
- Bar codes must be unique across all samples, so use a different bar code
- for this sample.''' % (bar_code, sample.name)
- unique = False
- break
- if not unique:
- break
- return message
-
- # ===== Other miscellaneous utility methods =====
- def __get_encoded_selected_sample_ids(self, trans, request, **kwd):
- encoded_selected_sample_ids = []
- for sample in request.samples:
- if CheckboxField.is_checked(kwd.get('select_sample_%i' % sample.id, '')):
- encoded_selected_sample_ids.append(trans.security.encode_id(sample.id))
- return encoded_selected_sample_ids
-
-
-# ===== Miscellaneous utility methods outside of the RequestsCommon class =====
-def invalid_id_redirect(trans, cntrller, obj_id, item='sequencing request', action='browse_requests'):
- status = 'error'
- message = "Invalid %s id (%s)" % (item, str(obj_id))
- return trans.response.send_redirect(web.url_for(controller=cntrller,
- action=action,
- status=status,
- message=message))
diff --git a/lib/galaxy/webapps/galaxy/controllers/root.py b/lib/galaxy/webapps/galaxy/controllers/root.py
index 7cf6ae4689e..660f081d535 100644
--- a/lib/galaxy/webapps/galaxy/controllers/root.py
+++ b/lib/galaxy/webapps/galaxy/controllers/root.py
@@ -4,20 +4,28 @@ Contains the main interface in the Universe class
from __future__ import absolute_import
import cgi
+import logging
import os
import requests
-from paste.httpexceptions import HTTPNotFound, HTTPBadGateway
+from paste.httpexceptions import (
+ HTTPBadGateway,
+ HTTPNotFound
+)
-from galaxy import web
-from galaxy import util
-from galaxy.util import listify, string_as_bool, FILENAME_VALID_CHARS
-
-from galaxy.web.base import controller
+from galaxy import (
+ managers,
+ util,
+ web
+)
from galaxy.model.item_attrs import UsesAnnotations
-from galaxy import managers
+from galaxy.util import (
+ FILENAME_VALID_CHARS,
+ listify,
+ string_as_bool
+)
+from galaxy.web.base import controller
-import logging
log = logging.getLogger(__name__)
@@ -183,7 +191,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations):
if hid is not None:
try:
hid = int(hid)
- except:
+ except ValueError:
return "hid '%s' is invalid" % str(hid)
history = trans.get_history()
for dataset in history.datasets:
@@ -197,7 +205,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations):
id = self.decode_id(encoded_id)
try:
data = trans.sa_session.query(self.app.model.HistoryDatasetAssociation).get(id)
- except:
+ except Exception:
return "Dataset id '%s' is invalid" % str(id)
if data:
current_user_roles = trans.get_current_user_roles()
@@ -214,7 +222,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations):
trans.log_event("Display dataset id: %s" % str(id))
try:
return open(data.file_name)
- except:
+ except Exception:
return "This dataset contains no content"
else:
return "You are not allowed to access this dataset"
@@ -303,7 +311,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations):
association = trans.sa_session.query(trans.app.model.GalaxySessionToHistoryAssociation) \
.filter_by(session_id=galaxy_session.id, history_id=new_history.id) \
.first()
- except:
+ except Exception:
association = None
new_history.add_galaxy_session(galaxy_session, association=association)
trans.sa_session.add(new_history)
@@ -323,7 +331,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations):
association = trans.sa_session.query(trans.app.model.GalaxySessionToHistoryAssociation) \
.filter_by(session_id=galaxy_session.id, history_id=new_history.id) \
.first()
- except:
+ except Exception:
association = None
new_history.add_galaxy_session(galaxy_session, association=association)
trans.sa_session.add(new_history)
@@ -405,7 +413,7 @@ class RootController(controller.JSAppLauncher, UsesAnnotations):
trans.sa_session.add(new_data)
trans.sa_session.flush()
return trans.show_message("
a\";\n\n\t// IE strips leading whitespace when .innerHTML is used\n\tsupport.leadingWhitespace = div.firstChild.nodeType === 3;\n\n\t// Make sure that tbody elements aren't automatically inserted\n\t// IE will insert them into empty tables\n\tsupport.tbody = !div.getElementsByTagName( \"tbody\" ).length;\n\n\t// Make sure that link elements get serialized correctly by innerHTML\n\t// This requires a wrapper element in IE\n\tsupport.htmlSerialize = !!div.getElementsByTagName( \"link\" ).length;\n\n\t// Makes sure cloning an html5 element does not cause problems\n\t// Where outerHTML is undefined, this still works\n\tsupport.html5Clone =\n\t\tdocument.createElement( \"nav\" ).cloneNode( true ).outerHTML !== \"<:nav>\";\n\n\t// Check if a disconnected checkbox will retain its checked\n\t// value of true after appended to the DOM (IE6/7)\n\tinput.type = \"checkbox\";\n\tinput.checked = true;\n\tfragment.appendChild( input );\n\tsupport.appendChecked = input.checked;\n\n\t// Make sure textarea (and checkbox) defaultValue is properly cloned\n\t// Support: IE6-IE11+\n\tdiv.innerHTML = \"\";\n\tsupport.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;\n\n\t// #11217 - WebKit loses check when the name is after the checked attribute\n\tfragment.appendChild( div );\n\n\t// Support: Windows Web Apps (WWA)\n\t// `name` and `type` must use .setAttribute for WWA (#14901)\n\tinput = document.createElement( \"input\" );\n\tinput.setAttribute( \"type\", \"radio\" );\n\tinput.setAttribute( \"checked\", \"checked\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tdiv.appendChild( input );\n\n\t// Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3\n\t// old WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE<9\n\t// Cloned elements keep attachEvent handlers, we use addEventListener on IE9+\n\tsupport.noCloneEvent = !!div.addEventListener;\n\n\t// Support: IE<9\n\t// Since attributes and properties are the same in IE,\n\t// cleanData must set properties to undefined rather than use removeAttribute\n\tdiv[ jQuery.expando ] = 1;\n\tsupport.attributes = !div.getAttribute( jQuery.expando );\n} )();\n\n\n// We have to close these tags to support XHTML (#13200)\nvar wrapMap = {\n\toption: [ 1, \"\" ],\n\tlegend: [ 1, \"\" ],\n\tarea: [ 1, \"\" ],\n\n\t// Support: IE8\n\tparam: [ 1, \"\" ],\n\tthead: [ 1, \"
\", \"
\" ],\n\ttr: [ 2, \"
\", \"
\" ],\n\tcol: [ 2, \"
\", \"
\" ],\n\ttd: [ 3, \"
\", \"
\" ],\n\n\t// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,\n\t// unless wrapped in a div with non-breaking characters in front of it.\n\t_default: support.htmlSerialize ? [ 0, \"\", \"\" ] : [ 1, \"X
\", \"
\" ]\n};\n\n// Support: IE8-IE9\nwrapMap.optgroup = wrapMap.option;\n\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\n\nfunction getAll( context, tag ) {\n\tvar elems, elem,\n\t\ti = 0,\n\t\tfound = typeof context.getElementsByTagName !== \"undefined\" ?\n\t\t\tcontext.getElementsByTagName( tag || \"*\" ) :\n\t\t\ttypeof context.querySelectorAll !== \"undefined\" ?\n\t\t\t\tcontext.querySelectorAll( tag || \"*\" ) :\n\t\t\t\tundefined;\n\n\tif ( !found ) {\n\t\tfor ( found = [], elems = context.childNodes || context;\n\t\t\t( elem = elems[ i ] ) != null;\n\t\t\ti++\n\t\t) {\n\t\t\tif ( !tag || jQuery.nodeName( elem, tag ) ) {\n\t\t\t\tfound.push( elem );\n\t\t\t} else {\n\t\t\t\tjQuery.merge( found, getAll( elem, tag ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], found ) :\n\t\tfound;\n}\n\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar elem,\n\t\ti = 0;\n\tfor ( ; ( elem = elems[ i ] ) != null; i++ ) {\n\t\tjQuery._data(\n\t\t\telem,\n\t\t\t\"globalEval\",\n\t\t\t!refElements || jQuery._data( refElements[ i ], \"globalEval\" )\n\t\t);\n\t}\n}\n\n\nvar rhtml = /<|?\\w+;/,\n\trtbody = / from table fragments\n\t\t\t\tif ( !support.tbody ) {\n\n\t\t\t\t\t// String was a
, *may* have spurious \n\t\t\t\t\telem = tag === \"table\" && !rtbody.test( elem ) ?\n\t\t\t\t\t\ttmp.firstChild :\n\n\t\t\t\t\t\t// String was a bare or \n\t\t\t\t\t\twrap[ 1 ] === \"
\" && !rtbody.test( elem ) ?\n\t\t\t\t\t\t\ttmp :\n\t\t\t\t\t\t\t0;\n\n\t\t\t\t\tj = elem && elem.childNodes.length;\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\tif ( jQuery.nodeName( ( tbody = elem.childNodes[ j ] ), \"tbody\" ) &&\n\t\t\t\t\t\t\t!tbody.childNodes.length ) {\n\n\t\t\t\t\t\t\telem.removeChild( tbody );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t// Fix #12392 for WebKit and IE > 9\n\t\t\t\ttmp.textContent = \"\";\n\n\t\t\t\t// Fix #12392 for oldIE\n\t\t\t\twhile ( tmp.firstChild ) {\n\t\t\t\t\ttmp.removeChild( tmp.firstChild );\n\t\t\t\t}\n\n\t\t\t\t// Remember the top-level container for proper cleanup\n\t\t\t\ttmp = safe.lastChild;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Fix #11356: Clear elements from fragment\n\tif ( tmp ) {\n\t\tsafe.removeChild( tmp );\n\t}\n\n\t// Reset defaultChecked for any radios and checkboxes\n\t// about to be appended to the DOM in IE 6/7 (#8060)\n\tif ( !support.appendChecked ) {\n\t\tjQuery.grep( getAll( nodes, \"input\" ), fixDefaultChecked );\n\t}\n\n\ti = 0;\n\twhile ( ( elem = nodes[ i++ ] ) ) {\n\n\t\t// Skip elements already in the context collection (trac-4087)\n\t\tif ( selection && jQuery.inArray( elem, selection ) > -1 ) {\n\t\t\tif ( ignored ) {\n\t\t\t\tignored.push( elem );\n\t\t\t}\n\n\t\t\tcontinue;\n\t\t}\n\n\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t// Append to fragment\n\t\ttmp = getAll( safe.appendChild( elem ), \"script\" );\n\n\t\t// Preserve script evaluation history\n\t\tif ( contains ) {\n\t\t\tsetGlobalEval( tmp );\n\t\t}\n\n\t\t// Capture executables\n\t\tif ( scripts ) {\n\t\t\tj = 0;\n\t\t\twhile ( ( elem = tmp[ j++ ] ) ) {\n\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\tscripts.push( elem );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\ttmp = null;\n\n\treturn safe;\n}\n\n\n( function() {\n\tvar i, eventName,\n\t\tdiv = document.createElement( \"div\" );\n\n\t// Support: IE<9 (lack submit/change bubble), Firefox (lack focus(in | out) events)\n\tfor ( i in { submit: true, change: true, focusin: true } ) {\n\t\teventName = \"on\" + i;\n\n\t\tif ( !( support[ i ] = eventName in window ) ) {\n\n\t\t\t// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)\n\t\t\tdiv.setAttribute( eventName, \"t\" );\n\t\t\tsupport[ i ] = div.attributes[ eventName ].expando === false;\n\t\t}\n\t}\n\n\t// Null elements to avoid leaks in IE.\n\tdiv = null;\n} )();\n\n\nvar rformElems = /^(?:input|select|textarea)$/i,\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|pointer|contextmenu|drag|drop)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\n// Support: IE9\n// See #13393 for more info\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\nfunction on( elem, types, selector, data, fn, one ) {\n\tvar origFn, type;\n\n\t// Types can be a map of types/handlers\n\tif ( typeof types === \"object\" ) {\n\n\t\t// ( types-Object, selector, data )\n\t\tif ( typeof selector !== \"string\" ) {\n\n\t\t\t// ( types-Object, data )\n\t\t\tdata = data || selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tfor ( type in types ) {\n\t\t\ton( elem, type, selector, data, types[ type ], one );\n\t\t}\n\t\treturn elem;\n\t}\n\n\tif ( data == null && fn == null ) {\n\n\t\t// ( types, fn )\n\t\tfn = selector;\n\t\tdata = selector = undefined;\n\t} else if ( fn == null ) {\n\t\tif ( typeof selector === \"string\" ) {\n\n\t\t\t// ( types, selector, fn )\n\t\t\tfn = data;\n\t\t\tdata = undefined;\n\t\t} else {\n\n\t\t\t// ( types, data, fn )\n\t\t\tfn = data;\n\t\t\tdata = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t}\n\tif ( fn === false ) {\n\t\tfn = returnFalse;\n\t} else if ( !fn ) {\n\t\treturn elem;\n\t}\n\n\tif ( one === 1 ) {\n\t\torigFn = fn;\n\t\tfn = function( event ) {\n\n\t\t\t// Can use an empty set, since event contains the info\n\t\t\tjQuery().off( event );\n\t\t\treturn origFn.apply( this, arguments );\n\t\t};\n\n\t\t// Use same guid so caller can remove using origFn\n\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t}\n\treturn elem.each( function() {\n\t\tjQuery.event.add( this, types, fn, data, selector );\n\t} );\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\t\tvar tmp, events, t, handleObjIn,\n\t\t\tspecial, eventHandle, handleObj,\n\t\t\thandlers, type, namespaces, origType,\n\t\t\telemData = jQuery._data( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !( events = elemData.events ) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !( eventHandle = elemData.handle ) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== \"undefined\" &&\n\t\t\t\t\t( !e || jQuery.event.triggered !== e.type ) ?\n\t\t\t\t\tjQuery.event.dispatch.apply( eventHandle.elem, arguments ) :\n\t\t\t\t\tundefined;\n\t\t\t};\n\n\t\t\t// Add elem as a property of the handle fn to prevent a memory leak\n\t\t\t// with IE non-native events\n\t\t\teventHandle.elem = elem;\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[ t ] ) || [];\n\t\t\ttype = origType = tmp[ 1 ];\n\t\t\tnamespaces = ( tmp[ 2 ] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend( {\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join( \".\" )\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !( handlers = events[ type ] ) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener/attachEvent if the special events handler returns false\n\t\t\t\tif ( !special.setup ||\n\t\t\t\t\tspecial.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\n\t\t\t\t\t// Bind the global event handler to the element\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\n\t\t\t\t\t} else if ( elem.attachEvent ) {\n\t\t\t\t\t\telem.attachEvent( \"on\" + type, eventHandle );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t\t// Nullify elem to prevent memory leaks in IE\n\t\telem = null;\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\t\tvar j, handleObj, tmp,\n\t\t\torigCount, t, events,\n\t\t\tspecial, handlers, type,\n\t\t\tnamespaces, origType,\n\t\t\telemData = jQuery.hasData( elem ) && jQuery._data( elem );\n\n\t\tif ( !elemData || !( events = elemData.events ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( rnotwhite ) || [ \"\" ];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[ t ] ) || [];\n\t\t\ttype = origType = tmp[ 1 ];\n\t\t\tnamespaces = ( tmp[ 2 ] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[ 2 ] &&\n\t\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join( \"\\\\.(?:.*\\\\.|)\" ) + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector ||\n\t\t\t\t\t\tselector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown ||\n\t\t\t\t\tspecial.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\n\t\t\t// removeData also checks for emptiness and clears the expando if empty\n\t\t\t// so use it instead of delete\n\t\t\tjQuery._removeData( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\t\tvar handle, ontype, cur,\n\t\t\tbubbleType, special, tmp, i,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = hasOwn.call( event, \"namespace\" ) ? event.namespace.split( \".\" ) : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf( \".\" ) > -1 ) {\n\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split( \".\" );\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf( \":\" ) < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join( \".\" );\n\t\tevent.rnamespace = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join( \"\\\\.(?:.*\\\\.|)\" ) + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === ( elem.ownerDocument || document ) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( jQuery._data( cur, \"events\" ) || {} )[ event.type ] &&\n\t\t\t\tjQuery._data( cur, \"handle\" );\n\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && handle.apply && acceptData( cur ) ) {\n\t\t\t\tevent.result = handle.apply( cur, data );\n\t\t\t\tif ( event.result === false ) {\n\t\t\t\t\tevent.preventDefault();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif (\n\t\t\t\t( !special._default ||\n\t\t\t\t special._default.apply( eventPath.pop(), data ) === false\n\t\t\t\t) && acceptData( elem )\n\t\t\t) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Can't use an .isFunction() check here because IE6/7 fails that test.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\ttry {\n\t\t\t\t\t\telem[ type ]();\n\t\t\t\t\t} catch ( e ) {\n\n\t\t\t\t\t\t// IE<9 dies on focus/blur to hidden element (#1486,#12518)\n\t\t\t\t\t\t// only reproducible on winXP IE8 native, not IE9 in IE8 mode\n\t\t\t\t\t}\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, j, ret, matched, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\targs = slice.call( arguments ),\n\t\t\thandlers = ( jQuery._data( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[ 0 ] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( ( handleObj = matched.handlers[ j++ ] ) &&\n\t\t\t\t!event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or 2) have namespace(s)\n\t\t\t\t// a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.rnamespace || event.rnamespace.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||\n\t\t\t\t\t\thandleObj.handler ).apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( ( event.result = ret ) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar i, matches, sel, handleObj,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Support (at least): Chrome, IE9\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG